@reckona/mreact-compat 0.0.169 → 0.0.170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reckona/mreact-compat",
3
- "version": "0.0.169",
3
+ "version": "0.0.170",
4
4
  "description": "React-compatible runtime implementation for mreact.",
5
5
  "keywords": [
6
6
  "compatibility",
@@ -69,7 +69,7 @@
69
69
  "access": "public"
70
70
  },
71
71
  "dependencies": {
72
- "@reckona/mreact-reactive-core": "0.0.169",
73
- "@reckona/mreact-shared": "0.0.169"
72
+ "@reckona/mreact-shared": "0.0.170",
73
+ "@reckona/mreact-reactive-core": "0.0.170"
74
74
  }
75
75
  }
@@ -45,6 +45,10 @@ export function syncScopedChildNodes(
45
45
  after: ChildNode | null,
46
46
  nextNodes: readonly Node[],
47
47
  ): void {
48
+ if (replaceEmptyFullChildList(parent, before, after, nextNodes)) {
49
+ return;
50
+ }
51
+
48
52
  if (replaceDisjointFullChildList(parent, before, after, nextNodes)) {
49
53
  return;
50
54
  }
@@ -57,6 +61,10 @@ export function syncScopedChildNodes(
57
61
  return;
58
62
  }
59
63
 
64
+ if (nextNodes.length > 16 && moveSingleSwappedPair(parent, before, after, nextNodes)) {
65
+ return;
66
+ }
67
+
60
68
  let cursor = parent.firstChild;
61
69
 
62
70
  if (before !== null) {
@@ -91,6 +99,25 @@ export function syncScopedChildNodes(
91
99
  }
92
100
  }
93
101
 
102
+ function replaceEmptyFullChildList(
103
+ parent: ParentNode,
104
+ before: ChildNode | null,
105
+ after: ChildNode | null,
106
+ nextNodes: readonly Node[],
107
+ ): boolean {
108
+ if (
109
+ before !== null ||
110
+ after !== null ||
111
+ nextNodes.length <= 16 ||
112
+ parent.childNodes.length !== 0
113
+ ) {
114
+ return false;
115
+ }
116
+
117
+ parent.replaceChildren(...nextNodes);
118
+ return true;
119
+ }
120
+
94
121
  function replaceDisjointFullChildList(
95
122
  parent: ParentNode,
96
123
  before: ChildNode | null,
@@ -177,6 +204,57 @@ function removeSingleMissingChild(
177
204
  return true;
178
205
  }
179
206
 
207
+ function moveSingleSwappedPair(
208
+ parent: ParentNode,
209
+ before: ChildNode | null,
210
+ after: ChildNode | null,
211
+ nextNodes: readonly Node[],
212
+ ): boolean {
213
+ let cursor = before === null ? parent.firstChild : before.nextSibling;
214
+ let index = 0;
215
+ let firstMismatchIndex = -1;
216
+ let secondMismatchIndex = -1;
217
+ let firstCurrent: ChildNode | null = null;
218
+ let secondCurrent: ChildNode | null = null;
219
+
220
+ while (cursor !== null && cursor !== after && index < nextNodes.length) {
221
+ const current = cursor;
222
+
223
+ if (current !== nextNodes[index]) {
224
+ if (firstMismatchIndex === -1) {
225
+ firstMismatchIndex = index;
226
+ firstCurrent = current;
227
+ } else if (secondMismatchIndex === -1) {
228
+ secondMismatchIndex = index;
229
+ secondCurrent = current;
230
+ } else {
231
+ return false;
232
+ }
233
+ }
234
+
235
+ cursor = current.nextSibling;
236
+ index += 1;
237
+ }
238
+
239
+ if (
240
+ index !== nextNodes.length ||
241
+ cursor !== after ||
242
+ firstCurrent === null ||
243
+ secondCurrent === null ||
244
+ firstCurrent !== nextNodes[secondMismatchIndex] ||
245
+ secondCurrent !== nextNodes[firstMismatchIndex]
246
+ ) {
247
+ return false;
248
+ }
249
+
250
+ parent.insertBefore(secondCurrent, firstCurrent);
251
+ parent.insertBefore(
252
+ firstCurrent,
253
+ (nextNodes[secondMismatchIndex + 1] as ChildNode | undefined) ?? after,
254
+ );
255
+ return true;
256
+ }
257
+
180
258
  export function removeChildIfPresent(parent: ParentNode, child: Node): void {
181
259
  if (child.parentNode !== parent) {
182
260
  return;
package/src/element.ts CHANGED
@@ -100,17 +100,21 @@ export function createElement<P extends object>(
100
100
  type: ElementType<P>,
101
101
  config: (P & ReactReservedProps) | null,
102
102
  ...children: ReactCompatNode[]
103
+ ): ReactCompatElement<P>;
104
+ export function createElement<P extends object>(
105
+ type: ElementType<P>,
106
+ config: (P & ReactReservedProps) | null,
103
107
  ): ReactCompatElement<P> {
108
+ const childCount = arguments.length - 2;
109
+
104
110
  if (typeof type === "string") {
105
111
  const key = config?.key === undefined ? null : String(config.key);
106
112
  const ref = config?.ref ?? null;
107
- const props = copyElementProps(config) as P & { children?: ReactCompatNode };
113
+ const props = copyCreateElementProps(config) as P & {
114
+ children?: ReactCompatNode;
115
+ };
108
116
 
109
- if (children.length === 1) {
110
- props.children = children[0];
111
- } else if (children.length > 1) {
112
- props.children = children;
113
- }
117
+ assignCreateElementChildren(props, childCount, arguments);
114
118
 
115
119
  setHostOwnPropsMeta(props);
116
120
 
@@ -127,15 +131,14 @@ export function createElement<P extends object>(
127
131
  typeof type === "object" && type !== null ? normalizeElementType(type) : type;
128
132
  const key = config?.key === undefined ? null : String(config.key);
129
133
  const ref = config?.ref ?? null;
130
- const props = applyDefaultProps(normalizedType, copyElementProps(config)) as P & {
134
+ const props = applyDefaultProps(
135
+ normalizedType,
136
+ copyCreateElementProps(config),
137
+ ) as P & {
131
138
  children?: ReactCompatNode;
132
139
  };
133
140
 
134
- if (children.length === 1) {
135
- props.children = children[0];
136
- } else if (children.length > 1) {
137
- props.children = children;
138
- }
141
+ assignCreateElementChildren(props, childCount, arguments);
139
142
 
140
143
  if (typeof normalizedType === "string") {
141
144
  setHostOwnPropsMeta(props);
@@ -150,6 +153,30 @@ export function createElement<P extends object>(
150
153
  };
151
154
  }
152
155
 
156
+ function assignCreateElementChildren(
157
+ props: { children?: ReactCompatNode },
158
+ childCount: number,
159
+ args: IArguments,
160
+ ): void {
161
+ if (childCount === 1) {
162
+ props.children = args[2] as ReactCompatNode;
163
+ return;
164
+ }
165
+
166
+ if (childCount <= 1) {
167
+ return;
168
+ }
169
+
170
+ const children: ReactCompatNode[] = [];
171
+ children.length = childCount;
172
+
173
+ for (let index = 0; index < childCount; index += 1) {
174
+ children[index] = args[index + 2] as ReactCompatNode;
175
+ }
176
+
177
+ props.children = children;
178
+ }
179
+
153
180
  /** Creates a React-compatible element from JSX runtime arguments. */
154
181
  export function createElementFromJsxConfig<P extends object>(
155
182
  type: ElementType<P>,
@@ -312,6 +339,7 @@ function copyElementProps(
312
339
  source: object | null | undefined,
313
340
  base?: object,
314
341
  omitChildren = false,
342
+ copySymbols: boolean | "internal" = true,
315
343
  ): Record<string, unknown> {
316
344
  const props: Record<PropertyKey, unknown> = {};
317
345
 
@@ -324,7 +352,40 @@ function copyElementProps(
324
352
  }
325
353
 
326
354
  copyOwnStringElementProps(source, props, omitChildren);
327
- copyOwnSymbolElementProps(source, props);
355
+ if (copySymbols === "internal") {
356
+ copyInternalElementSymbolProps(source, props);
357
+ } else if (copySymbols) {
358
+ copyOwnSymbolElementProps(source, props);
359
+ }
360
+ return props as Record<string, unknown>;
361
+ }
362
+
363
+ function copyCreateElementProps(
364
+ source: object | null | undefined,
365
+ ): Record<string, unknown> {
366
+ const props: Record<PropertyKey, unknown> = {};
367
+
368
+ if (source === null || source === undefined) {
369
+ return props as Record<string, unknown>;
370
+ }
371
+
372
+ const stringSource = source as Record<string, unknown>;
373
+ for (const name in source) {
374
+ if (!hasOwnProperty.call(source, name)) {
375
+ continue;
376
+ }
377
+
378
+ if (
379
+ name !== "key" &&
380
+ name !== "ref" &&
381
+ name !== "__self" &&
382
+ name !== "__source"
383
+ ) {
384
+ props[name] = stringSource[name];
385
+ }
386
+ }
387
+
388
+ copyInternalElementSymbolProps(source, props);
328
389
  return props as Record<string, unknown>;
329
390
  }
330
391
 
@@ -361,6 +422,16 @@ function copyOwnSymbolElementProps(
361
422
  }
362
423
  }
363
424
 
425
+ function copyInternalElementSymbolProps(
426
+ source: object,
427
+ target: Record<PropertyKey, unknown>,
428
+ ): void {
429
+ const symbolSource = source as Record<PropertyKey, unknown>;
430
+ if (hasOwnProperty.call(source, REACTIVE_TEXT_BINDING_META)) {
431
+ target[REACTIVE_TEXT_BINDING_META] = symbolSource[REACTIVE_TEXT_BINDING_META];
432
+ }
433
+ }
434
+
364
435
  function normalizeElementType<P>(type: ElementType<P>): ElementType<P> {
365
436
  return isReactCompatContextProviderShorthand(type) ? (type.Provider as ElementType<P>) : type;
366
437
  }
package/src/hooks.ts CHANGED
@@ -2214,6 +2214,10 @@ function cleanupInactiveInstances(runtime: RootRuntime): void {
2214
2214
  return;
2215
2215
  }
2216
2216
 
2217
+ if (activeInstanceKeys.size === runtime.instances.size) {
2218
+ return;
2219
+ }
2220
+
2217
2221
  for (const [key, instance] of runtime.instances) {
2218
2222
  if (!activeInstanceKeys.has(key)) {
2219
2223
  cleanupInstance(instance);
@@ -2224,7 +2228,7 @@ function cleanupInactiveInstances(runtime: RootRuntime): void {
2224
2228
  }
2225
2229
 
2226
2230
  function indexInstanceKey(runtime: RootRuntime, key: string): void {
2227
- for (const prefix of instanceKeyPrefixes(key)) {
2231
+ forEachInstanceKeyPrefix(key, (prefix) => {
2228
2232
  let keys = runtime.instanceKeysByPrefix.get(prefix);
2229
2233
 
2230
2234
  if (keys === undefined) {
@@ -2233,15 +2237,15 @@ function indexInstanceKey(runtime: RootRuntime, key: string): void {
2233
2237
  }
2234
2238
 
2235
2239
  keys.add(key);
2236
- }
2240
+ });
2237
2241
  }
2238
2242
 
2239
2243
  function removeInstanceKeyFromIndex(runtime: RootRuntime, key: string): void {
2240
- for (const prefix of instanceKeyPrefixes(key)) {
2244
+ forEachInstanceKeyPrefix(key, (prefix) => {
2241
2245
  const keys = runtime.instanceKeysByPrefix.get(prefix);
2242
2246
 
2243
2247
  if (keys === undefined) {
2244
- continue;
2248
+ return;
2245
2249
  }
2246
2250
 
2247
2251
  keys.delete(key);
@@ -2249,18 +2253,27 @@ function removeInstanceKeyFromIndex(runtime: RootRuntime, key: string): void {
2249
2253
  if (keys.size === 0) {
2250
2254
  runtime.instanceKeysByPrefix.delete(prefix);
2251
2255
  }
2252
- }
2256
+ });
2253
2257
  }
2254
2258
 
2255
- function instanceKeyPrefixes(key: string): string[] {
2256
- const parts = key.split(".");
2257
- const prefixes: string[] = [];
2259
+ function forEachInstanceKeyPrefix(
2260
+ key: string,
2261
+ callback: (prefix: string) => void,
2262
+ ): void {
2263
+ let start = 0;
2264
+
2265
+ while (start < key.length) {
2266
+ const next = key.indexOf(".", start);
2267
+
2268
+ if (next === -1) {
2269
+ break;
2270
+ }
2258
2271
 
2259
- for (let index = 1; index <= parts.length; index += 1) {
2260
- prefixes.push(parts.slice(0, index).join("."));
2272
+ callback(key.slice(0, next));
2273
+ start = next + 1;
2261
2274
  }
2262
2275
 
2263
- return prefixes;
2276
+ callback(key);
2264
2277
  }
2265
2278
 
2266
2279
  function cleanupInstance(instance: ComponentInstance): void {