@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
@@ -86,6 +86,7 @@ export const root: Root = {
86
86
  __root.__jsx = jsx;
87
87
  } else {
88
88
  __root.__jsx = jsx;
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
89
90
  render(jsx, __root as any);
90
91
  if (__FIRST_SCREEN_SYNC_TIMING__ === 'immediately') {
91
92
  // This is for cases where `root.render()` is called asynchronously,
@@ -107,7 +108,7 @@ const _InitData = /* @__PURE__ */ factory<InitData>(
107
108
  useState,
108
109
  createElement,
109
110
  useLynxGlobalEventListener,
110
- } as any,
111
+ },
111
112
  '__initData',
112
113
  'onDataChanged',
113
114
  );
@@ -137,6 +138,7 @@ const _InitData = /* @__PURE__ */ factory<InitData>(
137
138
  *
138
139
  * @public
139
140
  */
141
+ // @ts-expect-error make preact and react types work
140
142
  export const InitDataProvider: FC<{ children?: ReactNode | undefined }> = /* @__PURE__ */ _InitData.Provider();
141
143
  /**
142
144
  * The {@link https://react.dev/reference/react/createContext#consumer | Consumer} Component that provide `initData`.
@@ -144,6 +146,7 @@ export const InitDataProvider: FC<{ children?: ReactNode | undefined }> = /* @__
144
146
  * @group Components
145
147
  * @public
146
148
  */
149
+ // @ts-expect-error make preact and react types work
147
150
  export const InitDataConsumer: Consumer<InitData> = /* @__PURE__ */ _InitData.Consumer();
148
151
  /**
149
152
  * A React Hooks for you to get `initData`.
@@ -215,6 +218,82 @@ export interface InitData {}
215
218
 
216
219
  export { withInitDataInState };
217
220
 
221
+ /**
222
+ * The data processors that registered with {@link Lynx.registerDataProcessors}.
223
+ *
224
+ * @example
225
+ *
226
+ * Extending `dataProcessors` interface
227
+ *
228
+ * ```ts
229
+ * import type { DataProcessors as WellKnownDataProcessors } from '@lynx-js/react';
230
+ *
231
+ * declare module '@lynx-js/react' {
232
+ * interface DataProcessors extends WellKnownDataProcessors {
233
+ * foo(bar: string): number;
234
+ * }
235
+ * }
236
+ * ```
237
+ *
238
+ * Then you can use `lynx.registerDataProcessors` with types.
239
+ *
240
+ * ```js
241
+ * lynx.registerDataProcessors({
242
+ * dataProcessors: {
243
+ * foo(bar) {
244
+ * return 1;
245
+ * }
246
+ * }
247
+ * })
248
+ * ```
249
+ *
250
+ * @public
251
+ */
252
+ export interface DataProcessors {
253
+ /**
254
+ * Optional processor to override screen metrics used by the app
255
+ *
256
+ * @param metrics - The physical screen dimensions in pixels
257
+ *
258
+ * @returns New screen dimensions to be used by the app
259
+ *
260
+ * @example
261
+ *
262
+ * ```ts
263
+ * lynx.registerDataProcessors({
264
+ * dataProcessors: {
265
+ * getScreenMetricsOverride: (metrics) => {
266
+ * // Force a specific aspect ratio
267
+ * return {
268
+ * width: metrics.width,
269
+ * height: metrics.width * (16/9)
270
+ * };
271
+ * }
272
+ * }
273
+ * });
274
+ * ```
275
+ */
276
+ getScreenMetricsOverride?(metrics: {
277
+ /**
278
+ * The physical pixel width of the screen
279
+ */
280
+ width: number;
281
+ /**
282
+ * The physical pixel height of the screen
283
+ */
284
+ height: number;
285
+ }): { width: number; height: number };
286
+
287
+ /**
288
+ * Custom unknown data processors.
289
+ *
290
+ * @remarks
291
+ *
292
+ * You may extends the `DataProcessors` interface for better TypeScript types. See {@link DataProcessors}.
293
+ */
294
+ [processorName: string]: (...args: any[]) => any;
295
+ }
296
+
218
297
  /**
219
298
  * Definition of DataProcessor(s)
220
299
  * @public
@@ -235,7 +314,7 @@ export interface DataProcessorDefinition {
235
314
  *
236
315
  * @public
237
316
  */
238
- dataProcessors?: Record<string, ((rawInitData: InitDataRaw) => InitData)>;
317
+ dataProcessors?: DataProcessors;
239
318
  }
240
319
 
241
320
  /**
@@ -42,6 +42,7 @@ if (__PROFILE__) {
42
42
 
43
43
  if (__BACKGROUND__) {
44
44
  // Trick Preact and TypeScript to accept our custom document adapter.
45
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
45
46
  options.document = document as any;
46
47
  setupBackgroundDocument();
47
48
  injectTt();
@@ -1,6 +1,7 @@
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
5
  import { componentAtIndexFactory, enqueueComponentFactory, gRecycleMap, gSignMap } from './list.js';
5
6
  import { CHILDREN } from './renderToOpcodes/constants.js';
6
7
  import { SnapshotInstance } from './snapshot.js';
@@ -52,7 +53,7 @@ export function ssrHydrateByOpcodes(
52
53
  const signMap = gSignMap[listElementUniqueID] = new Map();
53
54
  gRecycleMap[listElementUniqueID] = new Map();
54
55
  const enqueueFunc = enqueueComponentFactory();
55
- const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(top.childNodes);
56
+ const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(top.childNodes, hydrate);
56
57
  for (const child of top.childNodes) {
57
58
  if (child.__element_root) {
58
59
  const childElementUniqueID = __GetElementUniqueID(child.__element_root);
@@ -0,0 +1,18 @@
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import type { ListUpdateInfo } from './listUpdateInfo.js';
6
+
7
+ export const __pendingListUpdates = {
8
+ values: {} as Record<number, ListUpdateInfo>,
9
+ clear(): void {
10
+ this.values = {};
11
+ },
12
+ flush(): void {
13
+ Object.values(this.values).forEach(update => {
14
+ update.flush();
15
+ });
16
+ this.clear();
17
+ },
18
+ };
@@ -97,9 +97,9 @@ export const __OpText = 3;
97
97
  * @param {Record<string, unknown>} context
98
98
  */
99
99
  function renderClassComponent(vnode, context) {
100
- let type = /** @type {import("preact").ComponentClass<typeof vnode.props>} */ (vnode.type);
100
+ const type = /** @type {import("preact").ComponentClass<typeof vnode.props>} */ (vnode.type);
101
101
 
102
- let c = new type(vnode.props, context);
102
+ const c = new type(vnode.props, context);
103
103
 
104
104
  vnode[COMPONENT] = c;
105
105
  c[VNODE] = vnode;
@@ -162,7 +162,7 @@ function _renderToString(
162
162
  if (isArray(vnode)) {
163
163
  parent[CHILDREN] = vnode;
164
164
  for (let i = 0; i < vnode.length; i++) {
165
- let child = vnode[i];
165
+ const child = vnode[i];
166
166
  if (child == null || typeof child === 'boolean') continue;
167
167
 
168
168
  _renderToString(child, context, isSvgMode, selectValue, parent, opcodes);
@@ -190,7 +190,7 @@ function _renderToString(
190
190
  } else {
191
191
  contextType = type.contextType;
192
192
  if (contextType != null) {
193
- let provider = context[contextType.__c];
193
+ const provider = context[contextType.__c];
194
194
  cctx = provider ? provider.props.value : contextType.__;
195
195
  }
196
196
 
@@ -236,7 +236,7 @@ function _renderToString(
236
236
 
237
237
  // When a component returns a Fragment node we flatten it in core, so we
238
238
  // need to mirror that logic here too
239
- let isTopLevelFragment = rendered != null && rendered.type === Fragment
239
+ const isTopLevelFragment = rendered != null && rendered.type === Fragment
240
240
  && rendered.key == null;
241
241
  rendered = isTopLevelFragment ? rendered.props.children : rendered;
242
242
 
@@ -254,8 +254,8 @@ function _renderToString(
254
254
 
255
255
  opcodes.push(__OpBegin, vnode);
256
256
 
257
- for (let name in props) {
258
- let v = props[name];
257
+ for (const name in props) {
258
+ const v = props[name];
259
259
 
260
260
  switch (name) {
261
261
  case 'children':
@@ -0,0 +1,16 @@
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ /**
6
+ * Types of dynamic parts that can be updated in a snapshot
7
+ * These are determined at compile time through static analysis
8
+ */
9
+ export const enum DynamicPartType {
10
+ Attr = 0, // Regular attribute updates
11
+ Spread, // Spread operator in JSX
12
+ Slot, // Slot for component children
13
+ Children, // Regular children updates
14
+ ListChildren, // List/array children updates
15
+ MultiChildren, // Multiple children updates (compat layer)
16
+ }
@@ -1,3 +1,6 @@
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
1
4
  import { processGesture } from '../gesture/processGesture.js';
2
5
  import type { GestureKind } from '../gesture/types.js';
3
6
  import { isMainThreadHydrationFinished } from '../lifecycle/patch/isMainThreadHydrationFinished.js';
@@ -0,0 +1,36 @@
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import { hydrate } from '../hydrate.js';
6
+ import { componentAtIndexFactory, enqueueComponentFactory, gRecycleMap, gSignMap } from '../list.js';
7
+ import type { SnapshotInstance } from '../snapshot.js';
8
+
9
+ export function snapshotCreateList(
10
+ pageId: number,
11
+ _ctx: SnapshotInstance,
12
+ _expIndex: number,
13
+ ): FiberElement {
14
+ const signMap = new Map<number, SnapshotInstance>();
15
+ const recycleMap = new Map<string, Map<number, SnapshotInstance>>();
16
+ const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory([], hydrate);
17
+ const list = __CreateList(
18
+ pageId,
19
+ componentAtIndex,
20
+ enqueueComponentFactory(),
21
+ {},
22
+ componentAtIndexes,
23
+ );
24
+ const listID = __GetElementUniqueID(list);
25
+ gSignMap[listID] = signMap;
26
+ gRecycleMap[listID] = recycleMap;
27
+ return list;
28
+ }
29
+
30
+ export function snapshotDestroyList(si: SnapshotInstance): void {
31
+ const [, elementIndex] = si.__snapshot_def.slot[0]!;
32
+ const list = si.__elements![elementIndex]!;
33
+ const listID = __GetElementUniqueID(list);
34
+ delete gSignMap[listID];
35
+ delete gRecycleMap[listID];
36
+ }
@@ -1,4 +1,8 @@
1
- import { __pendingListUpdates, ListUpdateInfoRecording } from '../list.js';
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+ import { ListUpdateInfoRecording } from '../listUpdateInfo.js';
5
+ import { __pendingListUpdates } from '../pendingListUpdates.js';
2
6
  import { SnapshotInstance } from '../snapshot.js';
3
7
 
4
8
  const platformInfoVirtualAttributes: Set<string> = /* @__PURE__ */ new Set<string>(['reuse-identifier']);
@@ -13,16 +17,26 @@ const platformInfoAttributes: Set<string> = /* @__PURE__ */ new Set<string>([
13
17
  'estimated-main-axis-size-px',
14
18
  ]);
15
19
 
20
+ export interface PlatformInfo {
21
+ 'reuse-identifier'?: string;
22
+ 'full-span'?: boolean;
23
+ 'item-key'?: string;
24
+ 'sticky-top'?: boolean;
25
+ 'sticky-bottom'?: boolean;
26
+ 'estimated-height'?: number;
27
+ 'estimated-height-px'?: number;
28
+ 'estimated-main-axis-size-px'?: number;
29
+ }
30
+
16
31
  function updateListItemPlatformInfo(
17
32
  ctx: SnapshotInstance,
18
33
  index: number,
19
34
  oldValue: any,
20
35
  elementIndex: number,
21
36
  ): void {
22
- const newValue = ctx.__listItemPlatformInfo = ctx.__values![index];
37
+ const newValue = ctx.__listItemPlatformInfo = ctx.__values![index] as PlatformInfo;
23
38
 
24
- // @ts-ignore
25
- const list = ctx.__parent;
39
+ const list = ctx.parentNode;
26
40
  if (list?.__snapshot_def.isListHolder) {
27
41
  (__pendingListUpdates.values[list.__id] ??= new ListUpdateInfoRecording(list)).onSetAttribute(
28
42
  ctx,
@@ -35,7 +49,7 @@ function updateListItemPlatformInfo(
35
49
  // No adding / removing keys.
36
50
  if (ctx.__elements) {
37
51
  const e = ctx.__elements[elementIndex]!;
38
- const value = ctx.__values![index];
52
+ const value = ctx.__values![index] as Record<string, unknown>;
39
53
  for (const k in value) {
40
54
  if (platformInfoVirtualAttributes.has(k)) {
41
55
  continue;
@@ -12,6 +12,7 @@ const refsToApply: (Ref | [snapshotInstanceId: number, expIndex: number])[] = []
12
12
 
13
13
  type Ref = (((ref: RefProxy) => () => void) | { current: RefProxy | null }) & {
14
14
  _unmount?: () => void;
15
+ __ref?: { value: number };
15
16
  };
16
17
 
17
18
  function unref(snapshot: SnapshotInstance, recursive: boolean): void {
@@ -9,10 +9,11 @@
9
9
  * optimized attribute updates at compile time, avoiding runtime object spreads.
10
10
  */
11
11
 
12
- import type { Worklet } from '@lynx-js/react/worklet-runtime/bindings';
12
+ import type { Element, Worklet, WorkletRefImpl } from '@lynx-js/react/worklet-runtime/bindings';
13
13
 
14
- import { BackgroundSnapshotInstance } from '../backgroundSnapshot.js';
15
- import { ListUpdateInfoRecording, __pendingListUpdates } from '../list.js';
14
+ import type { BackgroundSnapshotInstance } from '../backgroundSnapshot.js';
15
+ import { ListUpdateInfoRecording } from '../listUpdateInfo.js';
16
+ import { __pendingListUpdates } from '../pendingListUpdates.js';
16
17
  import { SnapshotInstance } from '../snapshot.js';
17
18
  import { isDirectOrDeepEqual, isEmptyObject, pick } from '../utils.js';
18
19
  import { updateEvent } from './event.js';
@@ -22,6 +23,7 @@ import { transformRef, updateRef } from './ref.js';
22
23
  import { updateWorkletEvent } from './workletEvent.js';
23
24
  import { updateWorkletRef } from './workletRef.js';
24
25
 
26
+ // eslint-disable-next-line regexp/no-unused-capturing-group
25
27
  const eventRegExp = /^(([A-Za-z-]*):)?(bind|catch|capture-bind|capture-catch|global-bind)([A-Za-z]+)$/;
26
28
  const eventTypeMap: Record<string, string> = {
27
29
  bind: 'bindEvent',
@@ -38,12 +40,16 @@ const noFlattenAttributes = /* @__PURE__ */ new Set<string>([
38
40
  'exposure-id',
39
41
  ]);
40
42
 
41
- function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any, elementIndex: number): void {
43
+ function updateSpread(
44
+ snapshot: SnapshotInstance,
45
+ index: number,
46
+ oldValue: Record<string, unknown> | undefined | null,
47
+ elementIndex: number,
48
+ ): void {
42
49
  oldValue ??= {};
43
- let newValue: Record<string, any> = snapshot.__values![index]; // compiler guarantee this must be an object;
50
+ let newValue: Record<string, unknown> = snapshot.__values![index] as Record<string, unknown>; // compiler guarantee this must be an object;
44
51
 
45
- // @ts-ignore
46
- const list = snapshot.__parent;
52
+ const list = snapshot.parentNode;
47
53
  if (list?.__snapshot_def.isListHolder) {
48
54
  const oldPlatformInfo = pick(oldValue, platformInfoAttributes);
49
55
  const platformInfo = pick(newValue, platformInfoAttributes);
@@ -79,17 +85,17 @@ function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any,
79
85
  snapshot.__values![index] = newValue;
80
86
  }
81
87
 
82
- const dataset: Record<string, any> = {};
88
+ const dataset: Record<string, unknown> = {};
83
89
  let match: RegExpMatchArray | null = null;
84
90
  for (const key in newValue) {
85
91
  const v = newValue[key];
86
92
  if (v !== oldValue[key]) {
87
93
  if (key === 'className') {
88
- __SetClasses(snapshot.__elements[elementIndex]!, v);
94
+ __SetClasses(snapshot.__elements[elementIndex]!, v as string);
89
95
  } else if (key === 'style') {
90
- __SetInlineStyles(snapshot.__elements[elementIndex]!, v);
96
+ __SetInlineStyles(snapshot.__elements[elementIndex]!, v as string);
91
97
  } else if (key === 'id') {
92
- __SetID(snapshot.__elements[elementIndex]!, v);
98
+ __SetID(snapshot.__elements[elementIndex]!, v as string);
93
99
  } else if (key.startsWith('data-')) {
94
100
  // collected below
95
101
  } else if (key === 'ref') {
@@ -106,7 +112,7 @@ function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any,
106
112
  __id: snapshot.__id,
107
113
  __elements: snapshot.__elements,
108
114
  } as SnapshotInstance;
109
- updateRef(fakeSnapshot, index, oldValue[key], elementIndex);
115
+ updateRef(fakeSnapshot, index, oldValue[key] as string | null, elementIndex);
110
116
  } else if (key.endsWith(':ref')) {
111
117
  snapshot.__worklet_ref_set ??= new Set();
112
118
  const fakeSnapshot = {
@@ -119,7 +125,13 @@ function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any,
119
125
  __elements: snapshot.__elements,
120
126
  __worklet_ref_set: snapshot.__worklet_ref_set,
121
127
  } as SnapshotInstance;
122
- updateWorkletRef(fakeSnapshot, index, oldValue[key], elementIndex, key.slice(0, -4));
128
+ updateWorkletRef(
129
+ fakeSnapshot,
130
+ index,
131
+ oldValue[key] as WorkletRefImpl<Element> | Worklet | null | undefined,
132
+ elementIndex,
133
+ key.slice(0, -4),
134
+ );
123
135
  } else if (key.endsWith(':gesture')) {
124
136
  const workletType = key.slice(0, -8);
125
137
  const fakeSnapshot = {
@@ -200,7 +212,7 @@ function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any,
200
212
  __id: snapshot.__id,
201
213
  __elements: snapshot.__elements,
202
214
  } as SnapshotInstance;
203
- updateRef(fakeSnapshot, index, oldValue[key], elementIndex);
215
+ updateRef(fakeSnapshot, index, oldValue[key] as string | null, elementIndex);
204
216
  } else if (key.endsWith(':ref')) {
205
217
  snapshot.__worklet_ref_set ??= new Set();
206
218
  const fakeSnapshot = {
@@ -213,7 +225,13 @@ function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any,
213
225
  __elements: snapshot.__elements,
214
226
  __worklet_ref_set: snapshot.__worklet_ref_set,
215
227
  } as SnapshotInstance;
216
- updateWorkletRef(fakeSnapshot, index, oldValue[key], elementIndex, key.slice(0, -4));
228
+ updateWorkletRef(
229
+ fakeSnapshot,
230
+ index,
231
+ oldValue[key] as WorkletRefImpl<Element> | Worklet | null | undefined,
232
+ elementIndex,
233
+ key.slice(0, -4),
234
+ );
217
235
  } else if (key.endsWith(':gesture')) {
218
236
  const workletType = key.slice(0, -8);
219
237
  const fakeSnapshot = {
@@ -243,7 +261,15 @@ function updateSpread(snapshot: SnapshotInstance, index: number, oldValue: any,
243
261
  __elements: snapshot.__elements,
244
262
  } as SnapshotInstance;
245
263
  if (workletType) {
246
- updateWorkletEvent(fakeSnapshot, index, oldValue[key], elementIndex, workletType, eventType, eventName);
264
+ updateWorkletEvent(
265
+ fakeSnapshot,
266
+ index,
267
+ oldValue[key] as Worklet,
268
+ elementIndex,
269
+ workletType,
270
+ eventType,
271
+ eventName,
272
+ );
247
273
  } else {
248
274
  updateEvent(fakeSnapshot, index, oldValue[key], elementIndex, eventType, eventName, key);
249
275
  }
@@ -283,7 +309,6 @@ function transformSpread(
283
309
  if (__LEPUS__) {
284
310
  result[key] = value ? 1 : undefined;
285
311
  } else {
286
- // @ts-ignore
287
312
  result[key] = transformRef(value)?.__ref;
288
313
  }
289
314
  } else if (typeof value === 'function') {
@@ -4,8 +4,8 @@
4
4
  import { onWorkletCtxUpdate } from '@lynx-js/react/worklet-runtime/bindings';
5
5
  import type { Worklet } from '@lynx-js/react/worklet-runtime/bindings';
6
6
 
7
- import { SnapshotInstance } from '../snapshot.js';
8
7
  import { isMainThreadHydrationFinished } from '../lifecycle/patch/isMainThreadHydrationFinished.js';
8
+ import { SnapshotInstance } from '../snapshot.js';
9
9
 
10
10
  function updateWorkletEvent(
11
11
  snapshot: SnapshotInstance,
@@ -43,7 +43,7 @@ export function workletUnRef(value: Worklet | WorkletRefImpl<Element>): void {
43
43
  export function updateWorkletRef(
44
44
  snapshot: SnapshotInstance,
45
45
  expIndex: number,
46
- oldValue: WorkletRefImpl<Element> | Worklet | undefined,
46
+ oldValue: WorkletRefImpl<Element> | Worklet | null | undefined,
47
47
  elementIndex: number,
48
48
  _workletType: string,
49
49
  ): void {
@@ -18,23 +18,14 @@ import type { Worklet, WorkletRefImpl } from '@lynx-js/react/worklet-runtime/bin
18
18
 
19
19
  import type { BackgroundSnapshotInstance } from './backgroundSnapshot.js';
20
20
  import { SnapshotOperation, __globalSnapshotPatch } from './lifecycle/patch/snapshotPatch.js';
21
- import { ListUpdateInfoRecording, __pendingListUpdates, snapshotDestroyList } from './list.js';
21
+ import { ListUpdateInfoRecording } from './listUpdateInfo.js';
22
+ import { __pendingListUpdates } from './pendingListUpdates.js';
23
+ import { DynamicPartType } from './snapshot/dynamicPartType.js';
24
+ import { snapshotDestroyList } from './snapshot/list.js';
25
+ import type { PlatformInfo } from './snapshot/platformInfo.js';
22
26
  import { unref } from './snapshot/ref.js';
23
27
  import { isDirectOrDeepEqual } from './utils.js';
24
28
 
25
- /**
26
- * Types of dynamic parts that can be updated in a snapshot
27
- * These are determined at compile time through static analysis
28
- */
29
- export const enum DynamicPartType {
30
- Attr = 0, // Regular attribute updates
31
- Spread, // Spread operator in JSX
32
- Slot, // Slot for component children
33
- Children, // Regular children updates
34
- ListChildren, // List/array children updates
35
- MultiChildren, // Multiple children updates (compat layer)
36
- }
37
-
38
29
  /**
39
30
  * A snapshot definition that contains all the information needed to create and update elements
40
31
  * This is generated at compile time through static analysis of the JSX
@@ -174,10 +165,17 @@ export const backgroundSnapshotInstanceManager: {
174
165
  return null;
175
166
  }
176
167
  const spreadKey = res[2];
177
- if (spreadKey) {
178
- return ctx.__values![expIndex][spreadKey];
168
+ if (res[1] === '__extraProps') {
169
+ if (spreadKey) {
170
+ return ctx.__extraProps![spreadKey];
171
+ }
172
+ throw new Error('unreachable');
179
173
  } else {
180
- return ctx.__values![expIndex];
174
+ if (spreadKey) {
175
+ return (ctx.__values![expIndex] as { [spreadKey]: unknown })[spreadKey];
176
+ } else {
177
+ return ctx.__values![expIndex];
178
+ }
181
179
  }
182
180
  },
183
181
  };
@@ -250,6 +248,7 @@ export interface SerializedSnapshotInstance {
250
248
  id: number;
251
249
  type: string;
252
250
  values?: any[] | undefined;
251
+ extraProps?: Record<string, unknown> | undefined;
253
252
  children?: SerializedSnapshotInstance[] | undefined;
254
253
  }
255
254
 
@@ -268,10 +267,11 @@ export class SnapshotInstance {
268
267
  __snapshot_def: Snapshot;
269
268
  __elements?: FiberElement[] | undefined;
270
269
  __element_root?: FiberElement | undefined;
271
- __values?: any[] | undefined;
270
+ __values?: unknown[] | undefined;
272
271
  __current_slot_index = 0;
273
272
  __worklet_ref_set?: Set<WorkletRefImpl<any> | Worklet>;
274
- __listItemPlatformInfo?: any;
273
+ __listItemPlatformInfo?: PlatformInfo;
274
+ __extraProps?: Record<string, unknown> | undefined;
275
275
 
276
276
  constructor(public type: string, id?: number) {
277
277
  this.__snapshot_def = snapshotManager.values.get(type)!;
@@ -280,7 +280,7 @@ export class SnapshotInstance {
280
280
  throw new Error('Snapshot not found: ' + type);
281
281
  }
282
282
 
283
- id ||= snapshotInstanceManager.nextId -= 1;
283
+ id ??= snapshotInstanceManager.nextId -= 1;
284
284
  this.__id = id;
285
285
  snapshotInstanceManager.values.set(id, this);
286
286
  }
@@ -589,31 +589,30 @@ export class SnapshotInstance {
589
589
  }
590
590
 
591
591
  setAttribute(key: string | number, value: any): void {
592
- const helper = (index: number, oldValue: any, newValue: any) => {
593
- if (isDirectOrDeepEqual(oldValue, newValue)) {}
594
- else {
595
- this.__snapshot_def.update![index]!(this, index, oldValue);
596
- }
597
- };
598
-
599
592
  if (key === 'values') {
600
593
  const oldValues = this.__values;
601
- this.__values = value;
594
+ const values = value as unknown[];
595
+ this.__values = values;
602
596
  if (oldValues) {
603
- for (let index = 0; index < value.length; index++) {
604
- helper(index, oldValues[index], value[index]);
597
+ for (let index = 0; index < values.length; index++) {
598
+ this.callUpdateIfNotDirectOrDeepEqual(index, oldValues[index], values[index]);
605
599
  }
606
600
  } else {
607
- for (let index = 0; index < value.length; index++) {
608
- helper(index, undefined, value[index]);
601
+ for (let index = 0; index < values.length; index++) {
602
+ this.callUpdateIfNotDirectOrDeepEqual(index, undefined, values[index]);
609
603
  }
610
604
  }
611
605
  return;
612
606
  }
613
607
 
614
- const index = typeof key === 'string' ? Number(key.slice(2)) : key;
608
+ if (typeof key === 'string') {
609
+ // for more flexible usage, we allow setting non-indexed attributes
610
+ (this.__extraProps ??= {})[key] = value;
611
+ return;
612
+ }
613
+
615
614
  this.__values ??= [];
616
- helper(index, this.__values[index], this.__values[index] = value);
615
+ this.callUpdateIfNotDirectOrDeepEqual(key, this.__values[key], this.__values[key] = value);
617
616
  }
618
617
 
619
618
  toJSON(): Omit<SerializedSnapshotInstance, 'children'> & { children: SnapshotInstance[] | undefined } {
@@ -621,7 +620,15 @@ export class SnapshotInstance {
621
620
  id: this.__id,
622
621
  type: this.type,
623
622
  values: this.__values,
623
+ extraProps: this.__extraProps,
624
624
  children: this.__firstChild ? this.childNodes : undefined,
625
625
  };
626
626
  }
627
+
628
+ callUpdateIfNotDirectOrDeepEqual(index: number, oldValue: any, newValue: any): void {
629
+ if (isDirectOrDeepEqual(oldValue, newValue)) {}
630
+ else {
631
+ this.__snapshot_def.update![index]!(this, index, oldValue);
632
+ }
633
+ }
627
634
  }
@@ -9,7 +9,7 @@
9
9
  * The map is used by the ref system to translate between snapshot instance IDs when
10
10
  * operations need to cross the thread boundary during the commit phase.
11
11
  */
12
- const hydrationMap: Map<number, number> = new Map();
12
+ const hydrationMap: Map<number, number> = new Map<number, number>();
13
13
 
14
14
  /**
15
15
  * @internal