@reckona/mreact-compat 0.0.65 → 0.0.67

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.
@@ -0,0 +1,364 @@
1
+ import {
2
+ ERROR_BOUNDARY_TYPE,
3
+ FORWARD_REF_TYPE,
4
+ Fragment,
5
+ LAZY_TYPE,
6
+ MEMO_TYPE,
7
+ STRICT_MODE_TYPE,
8
+ Suspense,
9
+ SuspenseList,
10
+ isReactCompatElement,
11
+ isReactCompatPortal,
12
+ type ReactCompatElement,
13
+ type ReactCompatNode,
14
+ } from "./element.js";
15
+ import {
16
+ isReactCompatConsumer,
17
+ isReactCompatProvider,
18
+ } from "./context.js";
19
+ import { isClassComponentType } from "./class-component.js";
20
+ import { ChildDeletion, Placement, Ref, Update } from "./fiber-flags.js";
21
+ import { createFiber, createWorkInProgress, type Fiber } from "./fiber.js";
22
+ import { getPendingProps } from "./prop-comparison.js";
23
+
24
+ export function reconcileChildFibers(
25
+ parent: Fiber,
26
+ currentFirstChild: Fiber | undefined,
27
+ newChildren: ReactCompatNode,
28
+ ): Fiber | undefined {
29
+ const children = normalizeChildren(newChildren);
30
+ const keyed = collectKeyedChildren(currentFirstChild);
31
+ const oldIndexes = collectChildIndexes(currentFirstChild);
32
+ const used = new Set<Fiber>();
33
+ let currentUnkeyed = currentFirstChild;
34
+ let lastPlacedIndex = 0;
35
+ let first: Fiber | undefined;
36
+ let previous: Fiber | undefined;
37
+
38
+ for (const child of children) {
39
+ const key = getNodeKey(child);
40
+ const matchedCurrent =
41
+ key === undefined ? findNextUnkeyedChild(currentUnkeyed) : keyed.get(key);
42
+ const fiber = reconcileSingleChild(parent, matchedCurrent, child, key);
43
+
44
+ if (key === undefined) {
45
+ currentUnkeyed = matchedCurrent?.sibling ?? currentUnkeyed?.sibling;
46
+ }
47
+
48
+ if (fiber === undefined) {
49
+ if (matchedCurrent !== undefined) {
50
+ used.add(matchedCurrent);
51
+ markChildForDeletion(parent, matchedCurrent);
52
+ }
53
+ continue;
54
+ }
55
+
56
+ if (matchedCurrent !== undefined) {
57
+ used.add(matchedCurrent);
58
+
59
+ if (fiber.alternate === matchedCurrent) {
60
+ const oldIndex = oldIndexes.get(matchedCurrent) ?? 0;
61
+ if (oldIndex < lastPlacedIndex) {
62
+ fiber.flags |= Placement;
63
+ } else {
64
+ lastPlacedIndex = oldIndex;
65
+ }
66
+ } else {
67
+ markChildForDeletion(parent, matchedCurrent);
68
+ }
69
+ }
70
+
71
+ fiber.lanes |= parent.lanes;
72
+ fiber.return = parent;
73
+ fiber.sibling = undefined;
74
+
75
+ if (first === undefined) {
76
+ first = fiber;
77
+ } else if (previous !== undefined) {
78
+ previous.sibling = fiber;
79
+ }
80
+
81
+ previous = fiber;
82
+ }
83
+
84
+ markRemainingChildrenForDeletion(parent, currentFirstChild, used);
85
+ parent.child = first;
86
+ return first;
87
+ }
88
+
89
+ function reconcileSingleChild(
90
+ parent: Fiber,
91
+ current: Fiber | undefined,
92
+ child: ReactCompatNode,
93
+ key: string | undefined,
94
+ ): Fiber | undefined {
95
+ if (child === null || child === undefined || typeof child === "boolean") {
96
+ return undefined;
97
+ }
98
+
99
+ if (typeof child === "string" || typeof child === "number") {
100
+ if (current?.tag === "host-text") {
101
+ const fiber = createWorkInProgress(current, String(child));
102
+ markUpdateEffectIfChanged(fiber, current);
103
+ return fiber;
104
+ }
105
+
106
+ const fiber = createFiber("host-text", String(child), key);
107
+ fiber.flags |= Placement;
108
+ fiber.return = parent;
109
+ return fiber;
110
+ }
111
+
112
+ if (Array.isArray(child)) {
113
+ if (current?.tag === "fragment") {
114
+ const fiber = createWorkInProgress(current, child);
115
+ markUpdateEffectIfChanged(fiber, current);
116
+ return fiber;
117
+ }
118
+
119
+ const fiber = createFiber("fragment", child, key);
120
+ fiber.flags |= Placement;
121
+ fiber.return = parent;
122
+ return fiber;
123
+ }
124
+
125
+ if (isReactCompatPortal(child)) {
126
+ if (current?.tag === "portal" && current.stateNode === child.container) {
127
+ const fiber = createWorkInProgress(current, child.children);
128
+ markUpdateEffectIfChanged(fiber, current);
129
+ return fiber;
130
+ }
131
+
132
+ const fiber = createFiber("portal", child.children, key);
133
+ fiber.stateNode = child.container;
134
+ fiber.flags |= Placement;
135
+ fiber.return = parent;
136
+ return fiber;
137
+ }
138
+
139
+ if (!isReactCompatElement(child)) {
140
+ return undefined;
141
+ }
142
+
143
+ if (current !== undefined && canReuseElementFiber(current, child)) {
144
+ const fiber = createWorkInProgress(current, getPendingProps(child));
145
+ markUpdateEffectIfChanged(fiber, current);
146
+ markRefEffectIfChanged(fiber, current);
147
+ return fiber;
148
+ }
149
+
150
+ const fiber = createElementFiber(child, key);
151
+ fiber.flags |= Placement;
152
+ fiber.return = parent;
153
+ return fiber;
154
+ }
155
+
156
+ function canReuseElementFiber(
157
+ current: Fiber,
158
+ element: ReactCompatElement,
159
+ ): boolean {
160
+ if (current.type !== element.type) {
161
+ return false;
162
+ }
163
+
164
+ if (typeof element.type === "string") {
165
+ return current.tag === "host-component";
166
+ }
167
+
168
+ if (element.type === Fragment) {
169
+ return current.tag === "fragment";
170
+ }
171
+
172
+ return current.tag !== "host-component" && current.tag !== "host-text";
173
+ }
174
+
175
+ function createElementFiber(
176
+ element: ReactCompatElement,
177
+ key: string | undefined,
178
+ ): Fiber {
179
+ const tag =
180
+ typeof element.type === "string"
181
+ ? "host-component"
182
+ : element.type === Fragment
183
+ ? "fragment"
184
+ : element.type === Suspense
185
+ ? "suspense"
186
+ : element.type === SuspenseList
187
+ ? "suspense-list"
188
+ : element.type === STRICT_MODE_TYPE
189
+ ? "strict-mode"
190
+ : element.type === ERROR_BOUNDARY_TYPE
191
+ ? "error-boundary"
192
+ : isLazyType(element.type)
193
+ ? "lazy"
194
+ : isReactCompatProvider(element.type)
195
+ ? "context-provider"
196
+ : isReactCompatConsumer(element.type)
197
+ ? "context-consumer"
198
+ : isForwardRefType(element.type)
199
+ ? "forward-ref"
200
+ : isMemoType(element.type)
201
+ ? "memo"
202
+ : isClassComponentType(element.type)
203
+ ? "class-component"
204
+ : "function-component";
205
+ const fiber = createFiber(tag, getPendingProps(element), key);
206
+ fiber.type = element.type;
207
+ return fiber;
208
+ }
209
+
210
+ function isForwardRefType(value: unknown): boolean {
211
+ return (
212
+ typeof value === "object" &&
213
+ value !== null &&
214
+ (value as { $$typeof?: unknown }).$$typeof === FORWARD_REF_TYPE
215
+ );
216
+ }
217
+
218
+ function isMemoType(value: unknown): boolean {
219
+ return (
220
+ typeof value === "object" &&
221
+ value !== null &&
222
+ (value as { $$typeof?: unknown }).$$typeof === MEMO_TYPE
223
+ );
224
+ }
225
+
226
+ function isLazyType(value: unknown): boolean {
227
+ return (
228
+ typeof value === "object" &&
229
+ value !== null &&
230
+ (value as { $$typeof?: unknown }).$$typeof === LAZY_TYPE
231
+ );
232
+ }
233
+
234
+ function markChildForDeletion(parent: Fiber, child: Fiber): void {
235
+ parent.flags |= ChildDeletion;
236
+ parent.deletions = parent.deletions ?? [];
237
+ if (parent.deletions.includes(child)) {
238
+ return;
239
+ }
240
+ parent.deletions.push(child);
241
+ }
242
+
243
+ function collectKeyedChildren(
244
+ firstChild: Fiber | undefined,
245
+ ): Map<string, Fiber> {
246
+ const keyed = new Map<string, Fiber>();
247
+ let cursor = firstChild;
248
+
249
+ while (cursor !== undefined) {
250
+ if (cursor.key !== undefined) {
251
+ keyed.set(cursor.key, cursor);
252
+ }
253
+
254
+ cursor = cursor.sibling;
255
+ }
256
+
257
+ return keyed;
258
+ }
259
+
260
+ function collectChildIndexes(
261
+ firstChild: Fiber | undefined,
262
+ ): Map<Fiber, number> {
263
+ const indexes = new Map<Fiber, number>();
264
+ let cursor = firstChild;
265
+ let index = 0;
266
+
267
+ while (cursor !== undefined) {
268
+ indexes.set(cursor, index);
269
+ cursor = cursor.sibling;
270
+ index += 1;
271
+ }
272
+
273
+ return indexes;
274
+ }
275
+
276
+ function findNextUnkeyedChild(firstChild: Fiber | undefined): Fiber | undefined {
277
+ let cursor = firstChild;
278
+
279
+ while (cursor !== undefined && cursor.key !== undefined) {
280
+ cursor = cursor.sibling;
281
+ }
282
+
283
+ return cursor;
284
+ }
285
+
286
+ function markRemainingChildrenForDeletion(
287
+ parent: Fiber,
288
+ firstChild: Fiber | undefined,
289
+ used: Set<Fiber>,
290
+ ): void {
291
+ let cursor = firstChild;
292
+
293
+ while (cursor !== undefined) {
294
+ if (!used.has(cursor)) {
295
+ markChildForDeletion(parent, cursor);
296
+ }
297
+
298
+ cursor = cursor.sibling;
299
+ }
300
+ }
301
+
302
+ function normalizeChildren(node: ReactCompatNode): ReactCompatNode[] {
303
+ if (node === null || node === undefined || typeof node === "boolean") {
304
+ return [];
305
+ }
306
+
307
+ return Array.isArray(node) ? node : [node];
308
+ }
309
+
310
+ function getNodeKey(node: ReactCompatNode): string | undefined {
311
+ return isReactCompatElement(node) && node.key !== null ? node.key : undefined;
312
+ }
313
+
314
+ function markUpdateEffectIfChanged(fiber: Fiber, current: Fiber): void {
315
+ const previousProps =
316
+ current.memoizedProps === undefined
317
+ ? current.pendingProps
318
+ : current.memoizedProps;
319
+
320
+ if (!arePropsEqual(previousProps, fiber.pendingProps)) {
321
+ fiber.flags |= Update;
322
+ }
323
+ }
324
+
325
+ function markRefEffectIfChanged(fiber: Fiber, current: Fiber): void {
326
+ if (
327
+ getRef(current.memoizedProps ?? current.pendingProps) !==
328
+ getRef(fiber.pendingProps)
329
+ ) {
330
+ fiber.flags |= Ref;
331
+ }
332
+ }
333
+
334
+ function getRef(props: unknown): unknown {
335
+ return typeof props === "object" && props !== null
336
+ ? (props as { ref?: unknown }).ref
337
+ : undefined;
338
+ }
339
+
340
+ function arePropsEqual(left: unknown, right: unknown): boolean {
341
+ if (Object.is(left, right)) {
342
+ return true;
343
+ }
344
+
345
+ if (
346
+ typeof left !== "object" ||
347
+ left === null ||
348
+ typeof right !== "object" ||
349
+ right === null
350
+ ) {
351
+ return false;
352
+ }
353
+
354
+ const leftRecord = left as Record<string, unknown>;
355
+ const rightRecord = right as Record<string, unknown>;
356
+ const leftKeys = Object.keys(leftRecord);
357
+ const rightKeys = Object.keys(rightRecord);
358
+
359
+ if (leftKeys.length !== rightKeys.length) {
360
+ return false;
361
+ }
362
+
363
+ return leftKeys.every((key) => Object.is(leftRecord[key], rightRecord[key]));
364
+ }
@@ -0,0 +1,83 @@
1
+ import type { Fiber, FiberRoot } from "./fiber.js";
2
+ import { commitHostFiberRoot } from "./fiber-host.js";
3
+ import { markRootFinished } from "./fiber-lanes.js";
4
+ import type { RenderOptions } from "./hydration.js";
5
+
6
+ interface RefRecord {
7
+ ref: unknown;
8
+ node: unknown;
9
+ }
10
+
11
+ export function commitFiberRoot(
12
+ root: FiberRoot,
13
+ options: RenderOptions = {},
14
+ ): void {
15
+ const finishedWork = root.finishedWork;
16
+
17
+ if (finishedWork === undefined) {
18
+ return;
19
+ }
20
+
21
+ cleanupDeletedRefs(root.current, finishedWork);
22
+ commitHostFiberRoot(root, finishedWork, options);
23
+ root.current = finishedWork;
24
+ root.current.stateNode = root;
25
+ markRootFinished(root, finishedWork.lanes);
26
+ root.callbackPriority = root.pendingLanes & -root.pendingLanes;
27
+ root.finishedWork = undefined;
28
+ root.workInProgress = undefined;
29
+ root.workInProgressRootRenderLanes = 0;
30
+ }
31
+
32
+ function cleanupDeletedRefs(previous: Fiber, next: Fiber): void {
33
+ const nextRefs = new Set<unknown>();
34
+
35
+ collectRefRecords(next, nextRefs);
36
+
37
+ for (const record of collectRefRecords(previous)) {
38
+ if (!nextRefs.has(record.ref)) {
39
+ detachRef(record.ref);
40
+ }
41
+ }
42
+ }
43
+
44
+ function collectRefRecords(
45
+ fiber: Fiber | undefined,
46
+ refs: Set<unknown> = new Set(),
47
+ ): RefRecord[] {
48
+ const records: RefRecord[] = [];
49
+ let cursor = fiber;
50
+
51
+ while (cursor !== undefined) {
52
+ const ref = getFiberRef(cursor);
53
+
54
+ if (ref !== undefined && ref !== null) {
55
+ refs.add(ref);
56
+ records.push({ ref, node: cursor.stateNode });
57
+ }
58
+
59
+ records.push(...collectRefRecords(cursor.child, refs));
60
+ cursor = cursor.sibling;
61
+ }
62
+
63
+ return records;
64
+ }
65
+
66
+ function getFiberRef(fiber: Fiber): unknown {
67
+ const props = (fiber.memoizedProps ?? fiber.pendingProps) as
68
+ | { ref?: unknown }
69
+ | undefined;
70
+
71
+ return props?.ref;
72
+ }
73
+
74
+ function detachRef(ref: unknown): void {
75
+ if (typeof ref === "function") {
76
+ ref(null);
77
+ return;
78
+ }
79
+
80
+ if (typeof ref === "object" && ref !== null && "current" in ref) {
81
+ (ref as { current: unknown }).current = null;
82
+ }
83
+ }
@@ -0,0 +1,21 @@
1
+ export type Flags = number;
2
+
3
+ export const NoFlags = 0;
4
+ export const Placement = 1 << 0;
5
+ export const Update = 1 << 1;
6
+ export const Deletion = 1 << 2;
7
+ export const ChildDeletion = 1 << 3;
8
+ export const Ref = 1 << 4;
9
+ export const Visibility = 1 << 5;
10
+ export const Hydrating = 1 << 6;
11
+ export const DidCapture = 1 << 7;
12
+ export const Passive = 1 << 8;
13
+ export const Layout = 1 << 9;
14
+
15
+ export function mergeFlags(left: Flags, right: Flags): Flags {
16
+ return left | right;
17
+ }
18
+
19
+ export function includesFlag(flags: Flags, flag: Flags): boolean {
20
+ return (flags & flag) !== NoFlags;
21
+ }