@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.
@@ -172,6 +172,7 @@ function reconcileHostChild(parent, currentFirstChild, node, runtime, path, opti
172
172
  }
173
173
  const childCount = children === undefined ? 1 : children.length;
174
174
  const hasKeyedChildren = children !== undefined && hasKeyedChild(children);
175
+ const canReuseCurrentFibersInList = !hasKeyedChildren || currentFirstChild === undefined;
175
176
  let existingByKey;
176
177
  let currentKeyed = currentFirstChild;
177
178
  let currentUnkeyed = currentFirstChild;
@@ -179,46 +180,77 @@ function reconcileHostChild(parent, currentFirstChild, node, runtime, path, opti
179
180
  let previous;
180
181
  let consumed = 0;
181
182
  let skipRemainingKeyedLookup = false;
182
- const usedCurrentChildren = currentFirstChild === undefined ? undefined : new Set();
183
+ let sequentialCurrentCursor = currentFirstChild;
184
+ let childListOrderChanged = false;
185
+ let usedCurrentChildren = currentFirstChild === undefined || hasKeyedChildren ? undefined : new Set();
186
+ const ensureUsedCurrentChildren = () => {
187
+ if (usedCurrentChildren === undefined) {
188
+ usedCurrentChildren = new Set();
189
+ let cursor = currentFirstChild;
190
+ while (cursor !== undefined && cursor !== sequentialCurrentCursor) {
191
+ usedCurrentChildren.add(cursor);
192
+ cursor = cursor.sibling;
193
+ }
194
+ }
195
+ return usedCurrentChildren;
196
+ };
183
197
  for (let index = 0; index < childCount; index += 1) {
184
198
  const child = children === undefined ? node : children[index];
185
199
  const key = getNodeKey(child);
186
200
  let matchedCurrent;
201
+ let canReuseMatchedCurrentFiber = canReuseCurrentFibersInList;
187
202
  if (key === undefined) {
203
+ if (hasKeyedChildren && currentUnkeyed !== undefined) {
204
+ childListOrderChanged = true;
205
+ ensureUsedCurrentChildren();
206
+ }
188
207
  matchedCurrent = currentUnkeyed;
189
208
  }
190
209
  else if (skipRemainingKeyedLookup) {
210
+ childListOrderChanged = true;
191
211
  matchedCurrent = undefined;
192
212
  }
193
213
  else if (existingByKey !== undefined) {
214
+ childListOrderChanged = true;
194
215
  matchedCurrent = existingByKey.get(key);
216
+ canReuseMatchedCurrentFiber = matchedCurrent === undefined;
195
217
  }
196
218
  else if (currentKeyed?.key === key) {
197
219
  matchedCurrent = currentKeyed;
220
+ canReuseMatchedCurrentFiber = true;
198
221
  currentKeyed = currentKeyed.sibling;
222
+ sequentialCurrentCursor = currentKeyed;
199
223
  }
200
224
  else if (children !== undefined &&
201
225
  currentKeyed?.sibling?.key === key &&
202
226
  canSkipSingleDeletedKeyedFiber(children, index, currentKeyed.sibling)) {
227
+ childListOrderChanged = true;
228
+ ensureUsedCurrentChildren();
203
229
  matchedCurrent = currentKeyed.sibling;
230
+ canReuseMatchedCurrentFiber = false;
204
231
  currentKeyed = currentKeyed.sibling.sibling;
205
232
  }
206
233
  else {
207
234
  if (children !== undefined &&
208
235
  hasKeyedChildren &&
209
236
  canSkipRemainingKeyedLookup(currentKeyed, children, index)) {
237
+ childListOrderChanged = true;
210
238
  skipRemainingKeyedLookup = true;
211
239
  currentKeyed = undefined;
212
240
  }
213
241
  else if (hasKeyedChildren) {
242
+ childListOrderChanged = true;
243
+ ensureUsedCurrentChildren();
214
244
  existingByKey = collectExistingKeyedFibers(currentKeyed);
215
245
  matchedCurrent = existingByKey.get(key);
246
+ canReuseMatchedCurrentFiber = matchedCurrent === undefined;
216
247
  }
217
248
  }
218
- const previousNodes = options.previousNodes === undefined
249
+ const memoBailout = tryReuseDependencyFreeMemoBailout(matchedCurrent, child, runtime, options, canReuseMatchedCurrentFiber);
250
+ const previousNodes = memoBailout !== undefined || options.previousNodes === undefined
219
251
  ? undefined
220
252
  : options.previousNodes.slice(consumed);
221
- const result = createHostFiber(parent, matchedCurrent, child, key, runtime, getReconcileChildPath(path, child, index, options), previousNodes === undefined ? options : { ...options, previousNodes });
253
+ const result = memoBailout ?? createHostFiber(parent, matchedCurrent, child, key, runtime, getReconcileChildPath(path, child, index, options), previousNodes === undefined ? options : { ...options, previousNodes }, canReuseMatchedCurrentFiber);
222
254
  const fiber = result.fiber;
223
255
  if (fiber === undefined) {
224
256
  if (matchedCurrent !== undefined) {
@@ -257,14 +289,21 @@ function reconcileHostChild(parent, currentFirstChild, node, runtime, path, opti
257
289
  }
258
290
  previous = fiber;
259
291
  }
260
- markUnusedCurrentChildrenForDeletion(parent, currentFirstChild, usedCurrentChildren);
261
- parent.childListChanged = childFiberListShapeChanged(currentFirstChild, first);
292
+ if (usedCurrentChildren === undefined && hasKeyedChildren && currentKeyed !== undefined) {
293
+ markOptimizedChildrenForDeletion(parent, currentKeyed);
294
+ }
295
+ else {
296
+ markUnusedCurrentChildrenForDeletion(parent, currentFirstChild, usedCurrentChildren);
297
+ }
298
+ parent.childListChanged =
299
+ childListOrderChanged || childFiberListShapeChanged(currentFirstChild, first);
262
300
  return { fiber: first, consumed };
263
301
  }
264
302
  function reconcileKeyedRowHostChildren(parent, currentFirstChild, children, options) {
265
303
  if (children.length === 0 ||
266
304
  currentFirstChild === undefined ||
267
305
  options.previousNodes !== undefined ||
306
+ !isKeyedRowHostElementCandidate(children[0]) ||
268
307
  !shouldUseDirectHostTextChild()) {
269
308
  return undefined;
270
309
  }
@@ -430,6 +469,12 @@ function createKeyedRowHostElementScratch() {
430
469
  text: "",
431
470
  };
432
471
  }
472
+ function isKeyedRowHostElementCandidate(node) {
473
+ return (isReactCompatElement(node) &&
474
+ typeof node.type === "string" &&
475
+ node.key !== null &&
476
+ node.ref === null);
477
+ }
433
478
  function readKeyedRowHostElement(node, row) {
434
479
  if (!isReactCompatElement(node) ||
435
480
  typeof node.type !== "string" ||
@@ -611,8 +656,8 @@ function markHostFiberEffects(fiber, current, node) {
611
656
  fiber.flags |= Update;
612
657
  }
613
658
  }
614
- function createHostFiber(parent, current, node, key, runtime, path, options = {}) {
615
- const result = createHostFiberImpl(parent, current, node, key, runtime, path, options);
659
+ function createHostFiber(parent, current, node, key, runtime, path, options = {}, canReuseCurrentFiber = true) {
660
+ const result = createHostFiberImpl(parent, current, node, key, runtime, path, options, canReuseCurrentFiber);
616
661
  if (result.fiber !== undefined) {
617
662
  if (canFinalizeNewHostFiber(result.fiber, current, node, options)) {
618
663
  result.fiber.flags |= Placement;
@@ -633,7 +678,7 @@ function canFinalizeNewHostFiber(fiber, current, node, options) {
633
678
  node.ref === null &&
634
679
  typeof node.type === "string");
635
680
  }
636
- function createHostFiberImpl(parent, current, node, key, runtime, path, options = {}) {
681
+ function createHostFiberImpl(parent, current, node, key, runtime, path, options = {}, canReuseCurrentFiber = true) {
637
682
  if (node === null || node === undefined || typeof node === "boolean") {
638
683
  return { fiber: undefined, consumed: 0 };
639
684
  }
@@ -667,6 +712,10 @@ function createHostFiberImpl(parent, current, node, key, runtime, path, options
667
712
  fiber.child = childResult.fiber;
668
713
  return { fiber, consumed: childResult.consumed };
669
714
  }
715
+ const memoBailout = tryReuseMemoBailout(current, node, runtime, path, options, canReuseCurrentFiber);
716
+ if (memoBailout !== undefined) {
717
+ return memoBailout;
718
+ }
670
719
  if (!isReactCompatElement(node)) {
671
720
  if (isReactCompatPortal(node)) {
672
721
  return createPortalFiber(parent, current, node, key, runtime, path, options);
@@ -774,7 +823,7 @@ function createHostFiberImpl(parent, current, node, key, runtime, path, options
774
823
  fiber.type = forwardRefType;
775
824
  const rendered = renderWithRootRuntime(runtime, path, () => forwardRefType.render(node.props, node.ref), forwardRefType);
776
825
  fiber.memoizedState = getDevToolsHookState(runtime, path);
777
- const childOptions = withHydrationComponentStack(options, getComponentName(forwardRefType.render));
826
+ const childOptions = getHydrationChildOptions(options, forwardRefType.render);
778
827
  const childResult = reconcileHostChild(fiber, current?.tag === "forward-ref" ? current.child : undefined, rendered, runtime, `${path}.forwardRef`, childOptions);
779
828
  fiber.child = childResult.fiber;
780
829
  return { fiber, consumed: childResult.consumed };
@@ -785,22 +834,29 @@ function createHostFiberImpl(parent, current, node, key, runtime, path, options
785
834
  }
786
835
  const memoType = node.type;
787
836
  const memoPath = `${path}.memo`;
788
- const previousMemoState = current?.tag === "memo"
789
- ? current.memoizedState
837
+ const previousMemoFiber = current?.tag === "memo" && current.type === memoType ? current : undefined;
838
+ const previousMemoState = previousMemoFiber !== undefined
839
+ ? previousMemoFiber.memoizedState
790
840
  : undefined;
791
- const fiber = current?.tag === "memo" && current.type === memoType
792
- ? createWorkInProgress(current, node.props)
793
- : createFiber("memo", node.props, key);
794
- fiber.type = memoType;
795
- if (previousMemoState !== undefined &&
796
- !hasDirtyInstance(runtime, previousMemoState.instanceKeys, memoPath) &&
797
- !hasUnflushedMountEffectInstance(runtime, previousMemoState.instanceKeys) &&
841
+ if (previousMemoFiber !== undefined &&
842
+ previousMemoState !== undefined &&
843
+ !(memoStateNeedsDirtyInstanceCheck(previousMemoState) &&
844
+ hasDirtyInstance(runtime, previousMemoState.instanceKeys, memoPath)) &&
845
+ !(memoStateNeedsEffectCheck(previousMemoState) &&
846
+ hasUnflushedMountEffectInstance(runtime, previousMemoState.instanceKeys)) &&
798
847
  areMemoPropsEqual(memoType, previousMemoState.props, node.props)) {
799
- markActiveInstanceKeys(runtime, previousMemoState.instanceKeys);
800
- fiber.child = getSkippedChild(current);
848
+ const fiber = getMemoBailoutFiber(runtime, previousMemoFiber, node.props, previousMemoState, canReuseCurrentFiber);
849
+ fiber.child = getSkippedChild(previousMemoFiber);
801
850
  fiber.memoizedState = previousMemoState;
802
- return { fiber, consumed: options.previousNodes?.length ?? 0 };
851
+ return {
852
+ fiber,
853
+ consumed: options.previousNodes?.length ?? 0,
854
+ };
803
855
  }
856
+ const fiber = previousMemoFiber !== undefined
857
+ ? createWorkInProgress(previousMemoFiber, node.props)
858
+ : createFiber("memo", node.props, key);
859
+ fiber.type = memoType;
804
860
  const renderedElement = {
805
861
  ...node,
806
862
  type: memoType.type,
@@ -812,9 +868,14 @@ function createHostFiberImpl(parent, current, node, key, runtime, path, options
812
868
  fiber.child.sibling = undefined;
813
869
  bubbleHostChild(fiber, fiber.child);
814
870
  }
871
+ const instanceKeys = collectInstanceKeys(runtime, memoPath);
872
+ const hasClassDescendant = hasClassComponentDescendant(fiber.child);
815
873
  fiber.memoizedState = {
816
- props: { ...node.props },
817
- instanceKeys: collectInstanceKeys(runtime, memoPath),
874
+ props: node.props,
875
+ instanceKeys,
876
+ hasDirtyInstanceDependencies: hasDirtyInstanceDependencies(runtime, instanceKeys) || hasClassDescendant,
877
+ hasUnflushedEffectDependencies: hasUnflushedEffectDependencies(runtime, instanceKeys),
878
+ hasRetainedInstanceDependencies: hasRetainedInstanceDependencies(runtime, instanceKeys) || hasClassDescendant,
818
879
  };
819
880
  return { fiber, consumed: childResult.consumed };
820
881
  }
@@ -891,7 +952,7 @@ function createHostFiberImpl(parent, current, node, key, runtime, path, options
891
952
  fiber.child = getSkippedChild(current);
892
953
  return { fiber, consumed: options.previousNodes?.length ?? 0 };
893
954
  }
894
- const childOptions = withHydrationComponentStack(options, getComponentName(classType));
955
+ const childOptions = getHydrationChildOptions(options, classType);
895
956
  try {
896
957
  const childResult = reconcileHostChild(fiber, current?.tag === "class-component" ? current.child : undefined, rendered.node, runtime, `${path}.class`, childOptions);
897
958
  fiber.child = childResult.fiber;
@@ -939,7 +1000,7 @@ function createHostFiberImpl(parent, current, node, key, runtime, path, options
939
1000
  }
940
1001
  const rendered = renderWithRootRuntime(runtime, path, () => node.type(node.props), node.type);
941
1002
  fiber.memoizedState = getDevToolsHookState(runtime, path);
942
- const childOptions = withHydrationComponentStack(options, getComponentName(node.type));
1003
+ const childOptions = getHydrationChildOptions(options, node.type);
943
1004
  const childResult = reconcileHostChild(fiber, current?.tag === "function-component" ? current.child : undefined, rendered, runtime, `${path}.0`, childOptions);
944
1005
  fiber.child = childResult.fiber;
945
1006
  const instanceKeys = collectInstanceKeys(runtime, path);
@@ -1323,13 +1384,27 @@ function collectCommittedHostNodes(fiber) {
1323
1384
  }
1324
1385
  return nodes;
1325
1386
  }
1387
+ function hasCommittedHostNode(fiber) {
1388
+ if ((fiber.tag === "host-component" || fiber.tag === "host-text") &&
1389
+ fiber.stateNode instanceof Node) {
1390
+ return true;
1391
+ }
1392
+ let child = fiber.child;
1393
+ while (child !== undefined) {
1394
+ if (hasCommittedHostNode(child)) {
1395
+ return true;
1396
+ }
1397
+ child = child.sibling;
1398
+ }
1399
+ return false;
1400
+ }
1326
1401
  function getSkippedChild(current) {
1327
1402
  const child = current?.child;
1328
1403
  const alternateChild = current?.alternate?.child;
1329
1404
  if (child !== undefined &&
1330
1405
  alternateChild !== undefined &&
1331
- collectCommittedHostNodes(child).length === 0 &&
1332
- collectCommittedHostNodes(alternateChild).length > 0) {
1406
+ !hasCommittedHostNode(child) &&
1407
+ hasCommittedHostNode(alternateChild)) {
1333
1408
  return alternateChild;
1334
1409
  }
1335
1410
  return child;
@@ -1923,6 +1998,59 @@ function hasPendingAsyncChild(fiber) {
1923
1998
  }
1924
1999
  return false;
1925
2000
  }
2001
+ function tryReuseMemoBailout(current, node, runtime, path, options, canReuseCurrentFiber = true) {
2002
+ if (current?.tag !== "memo" ||
2003
+ runtime === undefined ||
2004
+ !isReactCompatElement(node) ||
2005
+ node.type !== current.type ||
2006
+ !isMemoType(node.type)) {
2007
+ return undefined;
2008
+ }
2009
+ const previousMemoState = current.memoizedState;
2010
+ const memoPath = `${path}.memo`;
2011
+ if (previousMemoState === undefined ||
2012
+ (memoStateNeedsDirtyInstanceCheck(previousMemoState) &&
2013
+ hasDirtyInstance(runtime, previousMemoState.instanceKeys, memoPath)) ||
2014
+ (memoStateNeedsEffectCheck(previousMemoState) &&
2015
+ hasUnflushedMountEffectInstance(runtime, previousMemoState.instanceKeys)) ||
2016
+ !areMemoPropsEqual(node.type, previousMemoState.props, node.props)) {
2017
+ return undefined;
2018
+ }
2019
+ const fiber = getMemoBailoutFiber(runtime, current, node.props, previousMemoState, canReuseCurrentFiber);
2020
+ fiber.type = node.type;
2021
+ fiber.child = getSkippedChild(current);
2022
+ fiber.memoizedState = previousMemoState;
2023
+ return {
2024
+ fiber,
2025
+ consumed: options.previousNodes?.length ?? 0,
2026
+ };
2027
+ }
2028
+ function tryReuseDependencyFreeMemoBailout(current, node, runtime, options, canReuseCurrentFiber) {
2029
+ if (options.previousNodes !== undefined ||
2030
+ current?.tag !== "memo" ||
2031
+ runtime === undefined ||
2032
+ !isReactCompatElement(node) ||
2033
+ node.type !== current.type ||
2034
+ !isMemoType(node.type)) {
2035
+ return undefined;
2036
+ }
2037
+ const previousMemoState = current.memoizedState;
2038
+ if (previousMemoState === undefined ||
2039
+ previousMemoState.hasDirtyInstanceDependencies !== false ||
2040
+ previousMemoState.hasUnflushedEffectDependencies !== false ||
2041
+ previousMemoState.hasRetainedInstanceDependencies !== false ||
2042
+ !areMemoPropsEqual(node.type, previousMemoState.props, node.props)) {
2043
+ return undefined;
2044
+ }
2045
+ const fiber = getMemoBailoutFiber(runtime, current, node.props, previousMemoState, canReuseCurrentFiber);
2046
+ fiber.type = node.type;
2047
+ fiber.child = getSkippedChild(current);
2048
+ fiber.memoizedState = previousMemoState;
2049
+ return {
2050
+ fiber,
2051
+ consumed: 0,
2052
+ };
2053
+ }
1926
2054
  function isPendingLazyFiber(fiber) {
1927
2055
  if (fiber.tag !== "lazy" || !isLazyType(fiber.type)) {
1928
2056
  return false;
@@ -2082,6 +2210,11 @@ function getRootCommitPath(options) {
2082
2210
  function joinCommitPath(path, segment) {
2083
2211
  return path === SKIP_COMMIT_PATH ? "" : joinPath(path, segment);
2084
2212
  }
2213
+ function getHydrationChildOptions(options, component) {
2214
+ return options.hydration === undefined
2215
+ ? options
2216
+ : withHydrationComponentStack(options, getComponentName(component));
2217
+ }
2085
2218
  function getComponentName(component) {
2086
2219
  return component.name === "" ? "Anonymous" : component.name;
2087
2220
  }
@@ -2111,6 +2244,94 @@ function markActiveInstanceKeys(runtime, keys) {
2111
2244
  runtime.activeInstanceKeys?.add(key);
2112
2245
  }
2113
2246
  }
2247
+ function memoStateNeedsDirtyInstanceCheck(state) {
2248
+ return state.hasDirtyInstanceDependencies !== false;
2249
+ }
2250
+ function memoStateNeedsEffectCheck(state) {
2251
+ return state.hasUnflushedEffectDependencies !== false;
2252
+ }
2253
+ function memoStateNeedsActiveInstanceMark(state) {
2254
+ return state.hasRetainedInstanceDependencies !== false;
2255
+ }
2256
+ function getMemoBailoutFiber(runtime, current, pendingProps, state, canReuseCurrentFiber) {
2257
+ if (canReuseCurrentFiber && canReuseMemoBailoutFiber(current, state)) {
2258
+ current.pendingProps = pendingProps;
2259
+ current.flags = NoFlags;
2260
+ current.subtreeFlags = NoFlags;
2261
+ current.childListChanged = false;
2262
+ current.subtreeChildListChanged = false;
2263
+ current.hostChildListChanged = false;
2264
+ return current;
2265
+ }
2266
+ const fiber = createWorkInProgress(current, pendingProps);
2267
+ if (memoStateNeedsActiveInstanceMark(state)) {
2268
+ markActiveInstanceKeys(runtime, state.instanceKeys);
2269
+ }
2270
+ return fiber;
2271
+ }
2272
+ function canReuseMemoBailoutFiber(current, state) {
2273
+ return (state.hasRetainedInstanceDependencies === false &&
2274
+ current.hasRefSubtree !== true &&
2275
+ current.hydrateExisting !== true);
2276
+ }
2277
+ function hasDirtyInstanceDependencies(runtime, keys) {
2278
+ for (const key of keys) {
2279
+ const instance = runtime.instances.get(key);
2280
+ if (instance === undefined || instance.contextDependencies !== undefined) {
2281
+ return true;
2282
+ }
2283
+ if (instance.hooks?.some(isDirtyCapableHookSlot) === true) {
2284
+ return true;
2285
+ }
2286
+ }
2287
+ return false;
2288
+ }
2289
+ function hasRetainedInstanceDependencies(runtime, keys) {
2290
+ for (const key of keys) {
2291
+ const instance = runtime.instances.get(key);
2292
+ if (instance === undefined || instance.contextDependencies !== undefined) {
2293
+ return true;
2294
+ }
2295
+ if (instance.hooks !== undefined && instance.hooks.length > 0) {
2296
+ return true;
2297
+ }
2298
+ }
2299
+ return false;
2300
+ }
2301
+ function isDirtyCapableHookSlot(slot) {
2302
+ if (slot === undefined) {
2303
+ return false;
2304
+ }
2305
+ return (slot.kind !== "ref" &&
2306
+ slot.kind !== "memo" &&
2307
+ slot.kind !== "debug" &&
2308
+ slot.kind !== "effect");
2309
+ }
2310
+ function hasUnflushedEffectDependencies(runtime, keys) {
2311
+ for (const key of keys) {
2312
+ const instance = runtime.instances.get(key);
2313
+ if (instance === undefined) {
2314
+ return true;
2315
+ }
2316
+ if (instance.hooks?.some((slot) => slot?.kind === "effect") === true) {
2317
+ return true;
2318
+ }
2319
+ }
2320
+ return false;
2321
+ }
2322
+ function hasClassComponentDescendant(fiber) {
2323
+ let cursor = fiber;
2324
+ while (cursor !== undefined) {
2325
+ if (cursor.tag === "class-component") {
2326
+ return true;
2327
+ }
2328
+ if (hasClassComponentDescendant(cursor.child)) {
2329
+ return true;
2330
+ }
2331
+ cursor = cursor.sibling;
2332
+ }
2333
+ return false;
2334
+ }
2114
2335
  function hasDirtyInstance(runtime, keys, prefix) {
2115
2336
  if (runtime === undefined) {
2116
2337
  return false;
@@ -2118,16 +2339,27 @@ function hasDirtyInstance(runtime, keys, prefix) {
2118
2339
  if (hasDirtyClassUpdate(runtime, keys, prefix)) {
2119
2340
  return true;
2120
2341
  }
2342
+ if (keys.length === 0) {
2343
+ return false;
2344
+ }
2121
2345
  if (keys.some((key) => runtime.instances.get(key)?.dirty === true)) {
2122
2346
  return true;
2123
2347
  }
2124
2348
  if (prefix === undefined) {
2125
2349
  return false;
2126
2350
  }
2127
- for (const [key, instance] of runtime.instances) {
2128
- if ((key === prefix || key.startsWith(`${prefix}.`)) &&
2129
- instance.dirty === true) {
2130
- return true;
2351
+ // Resolve dirty descendants through the prefix index instead of scanning
2352
+ // every runtime instance. The previous full-map scan made memo/function
2353
+ // bailout O(total instances) per node, i.e. O(n^2) for large keyed lists
2354
+ // (js-framework-benchmark update-every-10th / select). The index is the same
2355
+ // source of truth used by collectRuntimeInstanceKeys.
2356
+ const keysUnderPrefix = runtime.instanceKeysByPrefix.get(prefix);
2357
+ if (keysUnderPrefix !== undefined) {
2358
+ for (const key of keysUnderPrefix) {
2359
+ if (runtime.instances.get(key)?.dirty ===
2360
+ true) {
2361
+ return true;
2362
+ }
2131
2363
  }
2132
2364
  }
2133
2365
  return false;