@lynx-js/react 0.110.0 → 0.111.0

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 (164) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/components/lib/DeferredListItem.d.ts +7 -0
  3. package/components/lib/DeferredListItem.jsx +40 -0
  4. package/components/lib/DeferredListItem.jsx.map +1 -0
  5. package/components/lib/Page.js.map +1 -1
  6. package/components/lib/index.d.ts +1 -0
  7. package/components/lib/index.js +1 -0
  8. package/components/lib/index.js.map +1 -1
  9. package/components/src/DeferredListItem.tsx +56 -0
  10. package/components/src/Page.ts +1 -1
  11. package/components/src/index.ts +1 -0
  12. package/package.json +1 -1
  13. package/refresh/.turbo/turbo-build.log +1 -1
  14. package/runtime/lazy/react-lepus.js +1 -0
  15. package/runtime/lazy/react.js +1 -0
  16. package/runtime/lepus/index.d.ts +1 -1
  17. package/runtime/lepus/index.js +44 -0
  18. package/runtime/lib/backgroundSnapshot.d.ts +2 -1
  19. package/runtime/lib/backgroundSnapshot.js +64 -41
  20. package/runtime/lib/backgroundSnapshot.js.map +1 -1
  21. package/runtime/lib/compat/initData.d.ts +7 -5
  22. package/runtime/lib/compat/initData.js +11 -2
  23. package/runtime/lib/compat/initData.js.map +1 -1
  24. package/runtime/lib/compat/lynxComponent.js +10 -12
  25. package/runtime/lib/compat/lynxComponent.js.map +1 -1
  26. package/runtime/lib/debug/profile.js +1 -0
  27. package/runtime/lib/debug/profile.js.map +1 -1
  28. package/runtime/lib/gesture/processGestureBagkround.d.ts +1 -1
  29. package/runtime/lib/gesture/processGestureBagkround.js +4 -1
  30. package/runtime/lib/gesture/processGestureBagkround.js.map +1 -1
  31. package/runtime/lib/gesture/types.js.map +1 -1
  32. package/runtime/lib/hooks/useLynxGlobalEventListener.d.ts +1 -1
  33. package/runtime/lib/hydrate.d.ts +1 -1
  34. package/runtime/lib/hydrate.js +5 -4
  35. package/runtime/lib/hydrate.js.map +1 -1
  36. package/runtime/lib/index.d.ts +2 -2
  37. package/runtime/lib/index.js +2 -2
  38. package/runtime/lib/index.js.map +1 -1
  39. package/runtime/lib/internal.d.ts +3 -2
  40. package/runtime/lib/internal.js +3 -2
  41. package/runtime/lib/internal.js.map +1 -1
  42. package/runtime/lib/legacy-react-runtime/index.js +1 -1
  43. package/runtime/lib/legacy-react-runtime/index.js.map +1 -1
  44. package/runtime/lib/lifecycle/patch/commit.js +5 -5
  45. package/runtime/lib/lifecycle/patch/commit.js.map +1 -1
  46. package/runtime/lib/lifecycle/patch/snapshotPatch.d.ts +9 -9
  47. package/runtime/lib/lifecycle/patch/snapshotPatch.js +9 -10
  48. package/runtime/lib/lifecycle/patch/snapshotPatch.js.map +1 -1
  49. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js.map +1 -1
  50. package/runtime/lib/lifecycle/patch/updateMainThread.js +11 -12
  51. package/runtime/lib/lifecycle/patch/updateMainThread.js.map +1 -1
  52. package/runtime/lib/lifecycle/reload.js +1 -1
  53. package/runtime/lib/lifecycle/reload.js.map +1 -1
  54. package/runtime/lib/lifecycleConstant.d.ts +2 -1
  55. package/runtime/lib/lifecycleConstant.js +1 -0
  56. package/runtime/lib/lifecycleConstant.js.map +1 -1
  57. package/runtime/lib/list.d.ts +2 -46
  58. package/runtime/lib/list.js +124 -211
  59. package/runtime/lib/list.js.map +1 -1
  60. package/runtime/lib/listUpdateInfo.d.ts +38 -0
  61. package/runtime/lib/listUpdateInfo.js +152 -0
  62. package/runtime/lib/listUpdateInfo.js.map +1 -0
  63. package/runtime/lib/lynx/calledByNative.js +8 -11
  64. package/runtime/lib/lynx/calledByNative.js.map +1 -1
  65. package/runtime/lib/lynx/component.js +11 -14
  66. package/runtime/lib/lynx/component.js.map +1 -1
  67. package/runtime/lib/lynx/env.js +1 -2
  68. package/runtime/lib/lynx/env.js.map +1 -1
  69. package/runtime/lib/lynx/lazy-bundle.js +48 -21
  70. package/runtime/lib/lynx/lazy-bundle.js.map +1 -1
  71. package/runtime/lib/lynx/performance.d.ts +3 -19
  72. package/runtime/lib/lynx/performance.js +25 -26
  73. package/runtime/lib/lynx/performance.js.map +1 -1
  74. package/runtime/lib/lynx/tt.js +10 -5
  75. package/runtime/lib/lynx/tt.js.map +1 -1
  76. package/runtime/lib/lynx-api.d.ts +78 -1
  77. package/runtime/lib/lynx-api.js +3 -0
  78. package/runtime/lib/lynx-api.js.map +1 -1
  79. package/runtime/lib/lynx.js +1 -0
  80. package/runtime/lib/lynx.js.map +1 -1
  81. package/runtime/lib/opcodes.js +2 -1
  82. package/runtime/lib/opcodes.js.map +1 -1
  83. package/runtime/lib/pendingListUpdates.d.ts +6 -0
  84. package/runtime/lib/pendingListUpdates.js +16 -0
  85. package/runtime/lib/pendingListUpdates.js.map +1 -0
  86. package/runtime/lib/renderToOpcodes/index.js +7 -7
  87. package/runtime/lib/renderToOpcodes/index.js.map +1 -1
  88. package/runtime/lib/snapshot/dynamicPartType.d.ts +12 -0
  89. package/runtime/lib/snapshot/dynamicPartType.js +17 -0
  90. package/runtime/lib/snapshot/dynamicPartType.js.map +1 -0
  91. package/runtime/lib/snapshot/gesture.js +3 -0
  92. package/runtime/lib/snapshot/gesture.js.map +1 -1
  93. package/runtime/lib/snapshot/list.d.ts +3 -0
  94. package/runtime/lib/snapshot/list.js +23 -0
  95. package/runtime/lib/snapshot/list.js.map +1 -0
  96. package/runtime/lib/snapshot/platformInfo.d.ts +10 -0
  97. package/runtime/lib/snapshot/platformInfo.js +6 -3
  98. package/runtime/lib/snapshot/platformInfo.js.map +1 -1
  99. package/runtime/lib/snapshot/ref.d.ts +3 -0
  100. package/runtime/lib/snapshot/ref.js.map +1 -1
  101. package/runtime/lib/snapshot/spread.d.ts +2 -2
  102. package/runtime/lib/snapshot/spread.js +4 -5
  103. package/runtime/lib/snapshot/spread.js.map +1 -1
  104. package/runtime/lib/snapshot/workletEvent.js +1 -1
  105. package/runtime/lib/snapshot/workletEvent.js.map +1 -1
  106. package/runtime/lib/snapshot/workletRef.d.ts +1 -1
  107. package/runtime/lib/snapshot/workletRef.js.map +1 -1
  108. package/runtime/lib/snapshot.d.ts +7 -14
  109. package/runtime/lib/snapshot.js +36 -31
  110. package/runtime/lib/snapshot.js.map +1 -1
  111. package/runtime/lib/snapshotInstanceHydrationMap.js.map +1 -1
  112. package/runtime/lib/utils.d.ts +1 -0
  113. package/runtime/lib/utils.js +7 -1
  114. package/runtime/lib/utils.js.map +1 -1
  115. package/runtime/src/backgroundSnapshot.ts +78 -63
  116. package/runtime/src/compat/initData.ts +20 -9
  117. package/runtime/src/compat/lynxComponent.ts +12 -13
  118. package/runtime/src/debug/profile.ts +1 -0
  119. package/runtime/src/gesture/processGestureBagkround.ts +5 -1
  120. package/runtime/src/gesture/types.ts +3 -0
  121. package/runtime/src/hooks/useLynxGlobalEventListener.ts +1 -1
  122. package/runtime/src/hydrate.ts +6 -4
  123. package/runtime/src/index.ts +2 -0
  124. package/runtime/src/internal.ts +3 -2
  125. package/runtime/src/legacy-react-runtime/index.ts +1 -1
  126. package/runtime/src/lifecycle/patch/commit.ts +5 -11
  127. package/runtime/src/lifecycle/patch/snapshotPatch.ts +9 -9
  128. package/runtime/src/lifecycle/patch/snapshotPatchApply.ts +1 -1
  129. package/runtime/src/lifecycle/patch/updateMainThread.ts +11 -12
  130. package/runtime/src/lifecycle/reload.ts +1 -1
  131. package/runtime/src/lifecycleConstant.ts +1 -0
  132. package/runtime/src/list.ts +143 -278
  133. package/runtime/src/listUpdateInfo.ts +221 -0
  134. package/runtime/src/lynx/calledByNative.ts +8 -10
  135. package/runtime/src/lynx/component.ts +17 -29
  136. package/runtime/src/lynx/env.ts +1 -2
  137. package/runtime/src/lynx/lazy-bundle.ts +55 -20
  138. package/runtime/src/lynx/performance.ts +26 -27
  139. package/runtime/src/lynx/tt.ts +10 -11
  140. package/runtime/src/lynx-api.ts +81 -2
  141. package/runtime/src/lynx.ts +1 -0
  142. package/runtime/src/opcodes.ts +2 -1
  143. package/runtime/src/pendingListUpdates.ts +18 -0
  144. package/runtime/src/renderToOpcodes/index.ts +7 -7
  145. package/runtime/src/snapshot/dynamicPartType.ts +16 -0
  146. package/runtime/src/snapshot/gesture.ts +3 -0
  147. package/runtime/src/snapshot/list.ts +36 -0
  148. package/runtime/src/snapshot/platformInfo.ts +19 -5
  149. package/runtime/src/snapshot/ref.ts +1 -0
  150. package/runtime/src/snapshot/spread.ts +42 -17
  151. package/runtime/src/snapshot/workletEvent.ts +1 -1
  152. package/runtime/src/snapshot/workletRef.ts +1 -1
  153. package/runtime/src/snapshot.ts +41 -34
  154. package/runtime/src/snapshotInstanceHydrationMap.ts +1 -1
  155. package/runtime/src/utils.ts +12 -3
  156. package/testing-library/dist/env/vitest.js +3 -3
  157. package/testing-library/dist/index.d.ts +4 -1
  158. package/testing-library/dist/pure.js +1 -1
  159. package/testing-library/dist/vitest.config.js +45 -8
  160. package/testing-library/types/entry.d.ts +3 -2
  161. package/transform/cjs/main.cjs +4 -0
  162. package/transform/dist/wasm.cjs +1 -1
  163. package/types/react.d.ts +21 -1
  164. package/types/react.docs.d.ts +1 -1
@@ -1,240 +1,15 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
- import { hydrate } from './hydrate.js';
4
+ import { LifecycleConstant } from './lifecycleConstant.js';
5
5
  import { applyRefQueue } from './snapshot/workletRef.js';
6
- import { type SnapshotInstance } from './snapshot.js';
7
-
8
- export interface ListUpdateInfo {
9
- flush(): void;
10
- onInsertBefore(
11
- newNode: SnapshotInstance,
12
- existingNode?: SnapshotInstance,
13
- ): void;
14
- onRemoveChild(child: SnapshotInstance): void;
15
- onSetAttribute(child: SnapshotInstance, attr: any, oldAttr: any): void;
16
- }
17
-
18
- interface InsertAction {
19
- position: number;
20
- type: string;
21
- }
22
-
23
- interface UpdateAction {
24
- from: number;
25
- to: number;
26
- type: string;
27
- flush: boolean;
28
- }
29
-
30
- interface ListOperations {
31
- insertAction: InsertAction[];
32
- removeAction: number[];
33
- updateAction: UpdateAction[];
34
- }
35
-
36
- // class ListUpdateInfoDiffing implements ListUpdateInfo {
37
- // private oldChildNodes: SnapshotInstance[];
38
- // constructor(private list: SnapshotInstance) {
39
- // this.oldChildNodes = list.childNodes;
40
- // }
41
- // flush(): void {
42
- // Object.defineProperty(SnapshotInstance.prototype, "key", {
43
- // get: function () {
44
- // return this.values[0]["item-key"];
45
- // },
46
- // });
47
-
48
- // }
49
- // onInsertBefore(newNode: SnapshotInstance, existingNode?: SnapshotInstance | undefined): void {}
50
- // onRemoveChild(child: SnapshotInstance): void {}
51
- // onSetAttribute(child: SnapshotInstance, attr: any): void {
52
- // throw new Error("Method not implemented.");
53
- // }
54
- // }
55
-
56
- export class ListUpdateInfoRecording implements ListUpdateInfo {
57
- constructor(private list: SnapshotInstance) {
58
- this.oldChildNodes = list.childNodes;
59
- // this.oldChildNodesSet = new Set(this.oldChildNodes);
60
- }
61
-
62
- // private __commitAndReset() {
63
- // (this.__pendingAttributes ??= []).push(this.__toAttribute());
64
- // this.oldChildNodes = this.list.childNodes;
65
- // this.oldChildNodesSet = new Set(this.oldChildNodes);
66
- // this.removeChild1.clear();
67
- // this.removeChild2.clear();
68
- // this.insertBefore.clear();
69
- // this.appendChild.length = 0;
70
- // this.platformInfoUpdate.clear();
71
- // }
72
-
73
- flush(): void {
74
- const elementIndex = this.list.__snapshot_def.slot[0]![1];
75
- const listElement = this.list.__elements![elementIndex]!;
76
- // this.__pendingAttributes?.forEach(pendingAttribute => {
77
- // __SetAttribute(listElement, "update-list-info", pendingAttribute);
78
- // __FlushElementTree(listElement);
79
- // });
80
- __SetAttribute(listElement, 'update-list-info', this.__toAttribute());
81
- const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(this.list.childNodes);
82
- __UpdateListCallbacks(
83
- listElement,
84
- componentAtIndex,
85
- enqueueComponentFactory(),
86
- componentAtIndexes,
87
- );
88
- }
89
-
90
- private oldChildNodes: SnapshotInstance[];
91
- // private oldChildNodesSet: Set<SnapshotInstance>;
92
- private removeChild = new Set<SnapshotInstance>();
93
- private insertBefore = new Map<SnapshotInstance, SnapshotInstance[]>(); // insert V before K
94
- private appendChild = [] as SnapshotInstance[];
95
- private platformInfoUpdate = new Map<SnapshotInstance, any>();
96
-
97
- onInsertBefore(newNode: SnapshotInstance, existingNode?: SnapshotInstance): void {
98
- // @ts-ignore
99
- if (newNode.__parent) {
100
- // if (!this.oldChildNodesSet.has(newNode)) {
101
- // this.__commitAndReset();
102
- // }
103
- this.removeChild.add(newNode);
104
- }
105
- if (existingNode) {
106
- // if (!this.oldChildNodesSet.has(existingNode)) {
107
- // this.__commitAndReset();
108
- // }
109
- const newChildren = this.insertBefore.get(existingNode) ?? [];
110
- newChildren.push(newNode);
111
- this.insertBefore.set(existingNode, newChildren);
112
- } else {
113
- this.appendChild.push(newNode);
114
- }
115
- }
116
-
117
- onRemoveChild(child: SnapshotInstance): void {
118
- // if (!this.oldChildNodesSet.has(child)) {
119
- // this.__commitAndReset();
120
- // }
121
- this.removeChild.add(child);
122
- }
123
-
124
- onSetAttribute(child: SnapshotInstance, attr: any, _oldAttr: any): void {
125
- this.platformInfoUpdate.set(child, attr);
126
- }
127
-
128
- private __toAttribute(): ListOperations {
129
- const { removeChild, insertBefore, appendChild, platformInfoUpdate } = this;
130
-
131
- const removals: number[] = [];
132
- const insertions: InsertAction[] = [];
133
- const updates: UpdateAction[] = [];
134
-
135
- let j = 0;
136
- for (let i = 0; i < this.oldChildNodes.length; i++, j++) {
137
- const child = this.oldChildNodes[i]!;
138
- if (platformInfoUpdate.has(child)) {
139
- updates.push({
140
- ...platformInfoUpdate.get(child),
141
- from: +j,
142
- to: +j,
143
- // no flush
144
- flush: false,
145
- type: child.type,
146
- });
147
- }
148
- if (insertBefore.has(child)) {
149
- const children = insertBefore.get(child)!;
150
- children.forEach(c => {
151
- insertions.push({
152
- position: j,
153
- type: c.type,
154
- ...c.__listItemPlatformInfo,
155
- });
156
- j++;
157
- });
158
- }
159
- if (removeChild.has(child)) {
160
- removals.push(i);
161
- removeChild.delete(child);
162
- j--;
163
- }
164
- }
165
- for (let i = 0; i < appendChild.length; i++) {
166
- const child = appendChild[i]!;
167
- insertions.push({
168
- position: j + i,
169
- type: child.type,
170
- ...child.__listItemPlatformInfo,
171
- });
172
- }
173
-
174
- insertions.sort((a, b) => a.position - b.position);
175
- removals.sort((a, b) => a - b);
176
-
177
- if (
178
- SystemInfo.lynxSdkVersion === '2.14'
179
- || SystemInfo.lynxSdkVersion === '2.15'
180
- || SystemInfo.lynxSdkVersion === '2.16'
181
- || SystemInfo.lynxSdkVersion === '2.17'
182
- || SystemInfo.lynxSdkVersion === '2.18'
183
- ) {
184
- const elementIndex = this.list.__snapshot_def.slot[0]![1];
185
- const listElement = this.list.__elements![elementIndex]!;
186
-
187
- // `__GetAttributeByName` is available since Lynx 2.14
188
- if (__GetAttributeByName(listElement, 'custom-list-name') === 'list-container') {
189
- // `updateAction` must be full (not incremental) when Lynx version <= 2.18 and
190
- // when `custom-list-name` is `list-container` (available when Lynx version >= 2.14) is true,
191
- updates.length = 0;
192
- this.list.childNodes.forEach((child, index) => {
193
- updates.push({
194
- ...child.__listItemPlatformInfo,
195
- from: index,
196
- to: index,
197
- // no flush
198
- flush: false,
199
- type: child.type,
200
- });
201
- });
202
- }
203
- }
204
-
205
- return {
206
- insertAction: insertions,
207
- removeAction: removals,
208
- updateAction: updates,
209
- };
210
- }
211
-
212
- toJSON(): [ListOperations] {
213
- // if (this.__pendingAttributes) {
214
- // return [...this.__pendingAttributes, this.__toAttribute()];
215
- // } else {
216
- // return [this.__toAttribute()];
217
- // }
218
-
219
- return [this.__toAttribute()] as const;
220
- }
221
- }
222
-
223
- export const __pendingListUpdates = {
224
- values: {} as Record<number, ListUpdateInfo>,
225
- clear(): void {
226
- this.values = {};
227
- },
228
- flush(): void {
229
- Object.values(this.values).forEach(update => {
230
- update.flush();
231
- });
232
- this.clear();
233
- },
234
- };
6
+ import type { SnapshotInstance } from './snapshot.js';
7
+ import { maybePromise } from './utils.js';
235
8
 
236
9
  export const gSignMap: Record<number, Map<number, SnapshotInstance>> = {};
237
10
  export const gRecycleMap: Record<number, Map<string, Map<number, SnapshotInstance>>> = {};
11
+ const gParentWeakMap: WeakMap<SnapshotInstance, unknown> = new WeakMap();
12
+ const resolvedPromise = /* @__PURE__ */ Promise.resolve();
238
13
 
239
14
  export function clearListGlobal(): void {
240
15
  for (const key in gSignMap) {
@@ -247,11 +22,29 @@ export function clearListGlobal(): void {
247
22
 
248
23
  export function componentAtIndexFactory(
249
24
  ctx: SnapshotInstance[],
25
+ hydrateFunction: (before: SnapshotInstance, after: SnapshotInstance) => void,
250
26
  ): [ComponentAtIndexCallback, ComponentAtIndexesCallback] {
251
- const componentAtIndex = (
27
+ // A hack workaround to ensure childCtx has no direct reference through `__parent` to list,
28
+ // to avoid memory leak.
29
+ // TODO(hzy): make `__parent` a WeakRef or `#__parent` in the future.
30
+ ctx.forEach((childCtx) => {
31
+ if (gParentWeakMap.has(childCtx)) {
32
+ // do it only once
33
+ } else {
34
+ gParentWeakMap.set(childCtx, childCtx.parentNode!);
35
+ Object.defineProperty(childCtx, '__parent', {
36
+ get: () => gParentWeakMap.get(childCtx)!,
37
+ set: (value: unknown) => {
38
+ gParentWeakMap.set(childCtx, value);
39
+ },
40
+ });
41
+ }
42
+ });
43
+
44
+ const componentAtChildCtx = (
252
45
  list: FiberElement,
253
46
  listID: number,
254
- cellIndex: number,
47
+ childCtx: SnapshotInstance,
255
48
  operationID: number,
256
49
  enableReuseNotification: boolean,
257
50
  enableBatchRender: boolean = false,
@@ -263,12 +56,54 @@ export function componentAtIndexFactory(
263
56
  throw new Error('componentAtIndex called on removed list');
264
57
  }
265
58
 
266
- const childCtx = ctx[cellIndex];
267
- if (!childCtx) {
268
- throw new Error('childCtx not found');
269
- }
59
+ const platformInfo = childCtx.__listItemPlatformInfo ?? {};
60
+
61
+ // The lifecycle of this `__extraProps.isReady`:
62
+ // 0 -> Promise<number> -> 1
63
+ // 0: The initial state, the list-item is not ready yet, we will send a event to background
64
+ // when `componentAtIndex` is called on it
65
+ // Promise<number>: A promise that will be resolved when the list-item is ready
66
+ // 1: The list-item is ready, we can use it to render the list
67
+ if (childCtx.__extraProps?.['isReady'] === 0) {
68
+ if (
69
+ typeof __GetAttributeByName === 'function'
70
+ && __GetAttributeByName(list, 'custom-list-name') === 'list-container'
71
+ ) {
72
+ // we are in supported env
73
+ // do not throw
74
+ } else {
75
+ throw new Error(
76
+ 'Unsupported: `<list-item/>` with `defer={true}` must be used with `<list custom-list-name="list-container"/>`',
77
+ );
78
+ }
270
79
 
271
- const platformInfo = childCtx.__listItemPlatformInfo || {};
80
+ __OnLifecycleEvent([LifecycleConstant.publishEvent, {
81
+ handlerName: `${childCtx.__id}:__extraProps:onComponentAtIndex`,
82
+ data: {},
83
+ }]);
84
+
85
+ let p: Promise<number>;
86
+ return (p = new Promise<number>((resolve) => {
87
+ Object.defineProperty(childCtx.__extraProps, 'isReady', {
88
+ set(isReady) {
89
+ if (isReady === 1) {
90
+ delete childCtx.__extraProps!['isReady'];
91
+ childCtx.__extraProps!['isReady'] = 1;
92
+
93
+ void resolvedPromise.then(() => {
94
+ // the cellIndex may be changed already, but the `childCtx` is the same
95
+ resolve(componentAtChildCtx(list, listID, childCtx, operationID, enableReuseNotification));
96
+ });
97
+ }
98
+ },
99
+ get() {
100
+ return p;
101
+ },
102
+ });
103
+ }));
104
+ } else if (maybePromise<number>(childCtx.__extraProps?.['isReady'])) {
105
+ throw new Error('componentAtIndex was called on a pending deferred list item');
106
+ }
272
107
 
273
108
  const uniqID = childCtx.type + (platformInfo['reuse-identifier'] ?? '');
274
109
  const recycleSignMap = recycleMap.get(uniqID);
@@ -308,10 +143,15 @@ export function componentAtIndexFactory(
308
143
  const [first] = recycleSignMap;
309
144
  const [sign, oldCtx] = first!;
310
145
  recycleSignMap.delete(sign);
311
- hydrate(oldCtx, childCtx);
146
+ hydrateFunction(oldCtx, childCtx);
312
147
  oldCtx.unRenderElements();
313
148
  if (!oldCtx.__id) {
314
149
  oldCtx.tearDown();
150
+ } else if (oldCtx.__extraProps?.['isReady'] === 1) {
151
+ __OnLifecycleEvent([LifecycleConstant.publishEvent, {
152
+ handlerName: `${oldCtx.__id}:__extraProps:onRecycleComponent`,
153
+ data: {},
154
+ }]);
315
155
  }
316
156
  const root = childCtx.__element_root!;
317
157
  applyRefQueue();
@@ -325,7 +165,7 @@ export function componentAtIndexFactory(
325
165
  if (enableReuseNotification) {
326
166
  flushOptions.listReuseNotification = {
327
167
  listElement: list,
328
- itemKey: platformInfo['item-key'],
168
+ itemKey: platformInfo['item-key']!,
329
169
  };
330
170
  }
331
171
  __FlushElementTree(root, flushOptions);
@@ -336,7 +176,7 @@ export function componentAtIndexFactory(
336
176
  if (enableReuseNotification) {
337
177
  flushOptions.listReuseNotification = {
338
178
  listElement: list,
339
- itemKey: platformInfo['item-key'],
179
+ itemKey: platformInfo['item-key']!,
340
180
  };
341
181
  }
342
182
  __FlushElementTree(root, flushOptions);
@@ -366,29 +206,83 @@ export function componentAtIndexFactory(
366
206
  return sign;
367
207
  };
368
208
 
369
- const componentAtIndexes = (
209
+ function componentAtIndex(
210
+ list: FiberElement,
211
+ listID: number,
212
+ cellIndex: number,
213
+ operationID: number,
214
+ enableReuseNotification: boolean,
215
+ ) {
216
+ const childCtx = ctx[cellIndex];
217
+ if (!childCtx) {
218
+ throw new Error('childCtx not found');
219
+ }
220
+ const r = componentAtChildCtx(list, listID, childCtx, operationID, enableReuseNotification);
221
+
222
+ /* v8 ignore start */
223
+ if (process.env['NODE_ENV'] === 'test') {
224
+ return r;
225
+ } else {
226
+ return typeof r === 'number' ? r : undefined;
227
+ }
228
+ /* v8 ignore end */
229
+ }
230
+
231
+ function componentAtIndexes(
370
232
  list: FiberElement,
371
233
  listID: number,
372
234
  cellIndexes: number[],
373
235
  operationIDs: number[],
374
236
  enableReuseNotification: boolean,
375
237
  asyncFlush: boolean,
376
- ) => {
377
- const uiSigns = cellIndexes.map((cellIndex, index) => {
378
- const operationID = operationIDs[index] ?? 0;
379
- return componentAtIndex(list, listID, cellIndex, operationID, enableReuseNotification, true, asyncFlush);
238
+ ) {
239
+ let hasUnready = false;
240
+ const p: Array<Promise<number> | number> = [];
241
+
242
+ cellIndexes.forEach((cellIndex, index) => {
243
+ const operationID = operationIDs[index]!;
244
+ const childCtx = ctx[cellIndex];
245
+ if (!childCtx) {
246
+ throw new Error('childCtx not found');
247
+ }
248
+
249
+ const u = componentAtChildCtx(list, listID, childCtx, operationID, enableReuseNotification, true, asyncFlush);
250
+ if (typeof u === 'number') {
251
+ // ready
252
+ } else {
253
+ hasUnready = true;
254
+ }
255
+ p.push(u);
380
256
  });
257
+
258
+ // We need __FlushElementTree twice:
259
+ // 1. The first time is sync, we flush the items that are ready, with unready items' uiSign as -1.
260
+ // 2. The second time is async, with all the uiSigns.
261
+ // NOTE: The `operationIDs` passed to __FlushElementTree must be the one passed in,
262
+ // not the one generated by any code here, to workaround a bug of Lynx Engine.
263
+ // So we CANNOT split the `operationIDs` into two parts: one for ready items, one for unready items.
264
+ if (hasUnready) {
265
+ void Promise.all(p).then((uiSigns) => {
266
+ __FlushElementTree(list, {
267
+ triggerLayout: true,
268
+ operationIDs,
269
+ elementIDs: uiSigns,
270
+ listID,
271
+ });
272
+ });
273
+ }
381
274
  __FlushElementTree(list, {
382
275
  triggerLayout: true,
383
- operationIDs: operationIDs,
384
- elementIDs: uiSigns,
276
+ operationIDs,
277
+ elementIDs: cellIndexes.map((_, index) => typeof p[index] === 'number' ? p[index] : -1),
385
278
  listID,
386
279
  });
387
- };
280
+ }
388
281
  return [componentAtIndex, componentAtIndexes] as const;
389
282
  }
390
283
 
391
284
  export function enqueueComponentFactory(): EnqueueComponentCallback {
285
+ // eslint-disable-next-line unicorn/consistent-function-scoping
392
286
  const enqueueComponent = (_: FiberElement, listID: number, sign: number) => {
393
287
  const signMap = gSignMap[listID];
394
288
  const recycleMap = gRecycleMap[listID];
@@ -401,7 +295,7 @@ export function enqueueComponentFactory(): EnqueueComponentCallback {
401
295
  return;
402
296
  }
403
297
 
404
- const platformInfo = childCtx.__listItemPlatformInfo || {};
298
+ const platformInfo = childCtx.__listItemPlatformInfo ?? {};
405
299
 
406
300
  const uniqID = childCtx.type + (platformInfo['reuse-identifier'] ?? '');
407
301
  if (!recycleMap.has(uniqID)) {
@@ -411,32 +305,3 @@ export function enqueueComponentFactory(): EnqueueComponentCallback {
411
305
  };
412
306
  return enqueueComponent;
413
307
  }
414
-
415
- export function snapshotCreateList(
416
- pageId: number,
417
- _ctx: SnapshotInstance,
418
- _expIndex: number,
419
- ): FiberElement {
420
- const signMap = new Map<number, SnapshotInstance>();
421
- const recycleMap = new Map<string, Map<number, SnapshotInstance>>();
422
- const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory([]);
423
- const list = __CreateList(
424
- pageId,
425
- componentAtIndex,
426
- enqueueComponentFactory(),
427
- {},
428
- componentAtIndexes,
429
- );
430
- const listID = __GetElementUniqueID(list);
431
- gSignMap[listID] = signMap;
432
- gRecycleMap[listID] = recycleMap;
433
- return list;
434
- }
435
-
436
- export function snapshotDestroyList(si: SnapshotInstance): void {
437
- const [, elementIndex] = si.__snapshot_def.slot[0]!;
438
- const list = si.__elements![elementIndex]!;
439
- const listID = __GetElementUniqueID(list);
440
- delete gSignMap[listID];
441
- delete gRecycleMap[listID];
442
- }