@samitouri / QOS-React / commits / f6a4882859

[DevTools] Show the Suspense boundary name in the rect if there's no overlap (#34918)

This shows the title in the top corner of the rect if there's enough space. The complex bit here is that it can be noisy if too many boundaries occupy the same space to overlap or partially overlap. This uses an R-tree to store all the rects to find overlapping boundaries to cut the available space to draw inside the rect. We use this to compute the rectangle within the rect which doesn't have any overlapping boundaries. The roots don't count as overlapping. Similarly, a parent rect is not consider overlapping a child. However, if two sibling boundaries occupy the same space, no title will be drawn. <img width="734" height="813" alt="Screenshot 2025-10-19 at 5 34 49 PM" src="https://github.com/user-attachments/assets/2b848b9c-3b78-48e5-9476-dd59a7baf6bf" /> We might also consider drawing the "Initial Paint" title at the root but that's less interesting. It's interesting in the beginning before you know about the special case at the root but after that it's just always the same value so just adds noise.

Sebastian Markbåge committed Oct 19, 2025 at 19:17 UTC f6a4882859e6e894a39e9216d01005212855689e
7 files changed +387 -91
packages/react-devtools-shared/package.json
+2 -1
@@ -23,6 +23,7 @@
23 "json5": "^2.2.3",
24 "local-storage-fallback": "^4.1.1",
25 "react-virtualized-auto-sizer": "^1.0.23",
26 - "react-window": "^1.8.10"
26 + "react-window": "^1.8.10",
27 + "rbush": "4.0.1"
28 }
29 }
packages/react-devtools-shared/src/devtools/store.js
+56 -3
@@ -62,6 +62,31 @@ import type {
62 import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
63 import type {DevToolsHookSettings} from '../backend/types';
64
65 +import RBush from 'rbush';
66 +
67 +// Custom version which works with our Rect data structure.
68 +class RectRBush extends RBush<Rect> {
69 + toBBox(rect: Rect): {
70 + minX: number,
71 + minY: number,
72 + maxX: number,
73 + maxY: number,
74 + } {
75 + return {
76 + minX: rect.x,
77 + minY: rect.y,
78 + maxX: rect.x + rect.width,
79 + maxY: rect.y + rect.height,
80 + };
81 + }
82 + compareMinX(a: Rect, b: Rect): number {
83 + return a.x - b.x;
84 + }
85 + compareMinY(a: Rect, b: Rect): number {
86 + return a.y - b.y;
87 + }
88 +}
89 +
90 const debug = (methodName: string, ...args: Array<string>) => {
91 if (__DEBUG__) {
92 console.log(
@@ -194,6 +219,9 @@ export default class Store extends EventEmitter<{
219 // Renderer ID is needed to support inspection fiber props, state, and hooks.
220 _rootIDToRendererID: Map<Element['id'], number> = new Map();
221
222 + // Stores all the SuspenseNode rects in an R-tree to make it fast to find overlaps.
223 + _rtree: RBush<Rect> = new RectRBush();
224 +
225 // These options may be initially set by a configuration option when constructing the Store.
226 _supportsInspectMatchingDOMElement: boolean = false;
227 _supportsClickToInspect: boolean = false;
@@ -1622,7 +1650,12 @@ export default class Store extends EventEmitter<{
1650 const y = operations[i + 1] / 1000;
1651 const width = operations[i + 2] / 1000;
1652 const height = operations[i + 3] / 1000;
1625 - rects.push({x, y, width, height});
1653 + const rect = {x, y, width, height};
1654 + if (parentID !== 0) {
1655 + // Track all rects except the root.
1656 + this._rtree.insert(rect);
1657 + }
1658 + rects.push(rect);
1659 i += 4;
1660 }
1661 }
@@ -1680,13 +1713,20 @@ export default class Store extends EventEmitter<{
1713
1714 i += 1;
1715
1683 - const {children, parentID} = suspense;
1716 + const {children, parentID, rects} = suspense;
1717 if (children.length > 0) {
1718 this._throwAndEmitError(
1719 Error(`Suspense node "${id}" was removed before its children.`),
1720 );
1721 }
1722
1723 + if (rects !== null && parentID !== 0) {
1724 + // Delete all the existing rects from the R-tree
1725 + for (let j = 0; j < rects.length; j++) {
1726 + this._rtree.remove(rects[j]);
1727 + }
1728 + }
1729 +
1730 this._idToSuspense.delete(id);
1731 removedSuspenseIDs.set(id, parentID);
1732
@@ -1785,6 +1825,14 @@ export default class Store extends EventEmitter<{
1825 break;
1826 }
1827
1828 + const prevRects = suspense.rects;
1829 + if (prevRects !== null && suspense.parentID !== 0) {
1830 + // Delete all the existing rects from the R-tree
1831 + for (let j = 0; j < prevRects.length; j++) {
1832 + this._rtree.remove(prevRects[j]);
1833 + }
1834 + }
1835 +
1836 let nextRects: SuspenseNode['rects'];
1837 if (numRects === -1) {
1838 nextRects = null;
@@ -1796,7 +1844,12 @@ export default class Store extends EventEmitter<{
1844 const width = operations[i + 2] / 1000;
1845 const height = operations[i + 3] / 1000;
1846
1799 - nextRects.push({x, y, width, height});
1847 + const rect = {x, y, width, height};
1848 + if (suspense.parentID !== 0) {
1849 + // Track all rects except the root.
1850 + this._rtree.insert(rect);
1851 + }
1852 + nextRects.push(rect);
1853
1854 i += 4;
1855 }
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
+19 -1
@@ -39,6 +39,24 @@
39 pointer-events: none;
40 }
41
42 +.SuspenseRectsTitle {
43 + pointer-events: none;
44 + color: var(--color-text);
45 + overflow: hidden;
46 + text-overflow: ellipsis;
47 + font-size: var(--font-size-sans-small);
48 + line-height: var(--font-size-sans-small);
49 + padding: .25rem;
50 + container-type: size;
51 + container-name: title;
52 +}
53 +
54 +@container title (width < 30px) or (height < 12px) {
55 + .SuspenseRectsTitle > span {
56 + display: none;
57 + }
58 +}
59 +
60 .SuspenseRectsScaledRect[data-visible='false'] > .SuspenseRectsBoundaryChildren {
61 overflow: initial;
62 }
@@ -75,7 +93,7 @@
93 transition: background-color 0.2s ease-out;
94 }
95
78 -.SuspenseRectsBoundary[data-selected='true'] {
96 +.SuspenseRectsBoundary[data-selected='true'][data-visible='true'] {
97 box-shadow: var(--elevation-4);
98 }
99
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
+104 -4
@@ -31,6 +31,7 @@ import {
31 SuspenseTreeDispatcherContext,
32 } from './SuspenseTreeContext';
33 import {getClassNameForEnvironment} from './SuspenseEnvironmentColors.js';
34 +import type RBush from 'rbush';
35
36 function ScaledRect({
37 className,
@@ -78,8 +79,10 @@ function ScaledRect({
79
80 function SuspenseRects({
81 suspenseID,
82 + parentRects,
83 }: {
84 suspenseID: SuspenseNode['id'],
85 + parentRects: null | Array<Rect>,
86 }): React$Node {
87 const store = useContext(StoreContext);
88 const treeDispatch = useContext(TreeDispatcherContext);
@@ -167,7 +170,20 @@ function SuspenseRects({
170 }
171 }
172
170 - const boundingBox = getBoundingBox(suspense.rects);
173 + const rects = suspense.rects;
174 + const boundingBox = getBoundingBox(rects);
175 +
176 + // Next we'll try to find a rect within one of our rects that isn't intersecting with
177 + // other rects.
178 + // TODO: This should probably be memoized based on if any changes to the rtree has been made.
179 + const titleBox: null | Rect =
180 + rects === null ? null : findTitleBox(store._rtree, rects, parentRects);
181 + const nextRects =
182 + rects === null || rects.length === 0
183 + ? parentRects
184 + : parentRects === null || parentRects.length === 0
185 + ? rects
186 + : parentRects.concat(rects);
187
188 return (
189 <ScaledRect
@@ -205,11 +221,22 @@ function SuspenseRects({
221 className={styles.SuspenseRectsBoundaryChildren}
222 rect={boundingBox}>
223 {suspense.children.map(childID => {
208 - return <SuspenseRects key={childID} suspenseID={childID} />;
224 + return (
225 + <SuspenseRects
226 + key={childID}
227 + suspenseID={childID}
228 + parentRects={nextRects}
229 + />
230 + );
231 })}
232 </ScaledRect>
233 )}
212 - {selected ? (
234 + {titleBox && suspense.name && visible ? (
235 + <ScaledRect className={styles.SuspenseRectsTitle} rect={titleBox}>
236 + <span>{suspense.name}</span>
237 + </ScaledRect>
238 + ) : null}
239 + {selected && visible ? (
240 <ScaledRect
241 className={styles.SuspenseRectOutline}
242 rect={boundingBox}
@@ -320,6 +347,77 @@ function getDocumentBoundingRect(
347 };
348 }
349
350 +function findTitleBox(
351 + rtree: RBush<Rect>,
352 + rects: Array<Rect>,
353 + parentRects: null | Array<Rect>,
354 +): null | Rect {
355 + for (let i = 0; i < rects.length; i++) {
356 + const rect = rects[i];
357 + if (rect.width < 20 || rect.height < 10) {
358 + // Skip small rects. They're likely not able to be contain anything useful anyway.
359 + continue;
360 + }
361 + // Find all overlapping rects elsewhere in the tree to limit our rect.
362 + const overlappingRects = rtree.search({
363 + minX: rect.x,
364 + minY: rect.y,
365 + maxX: rect.x + rect.width,
366 + maxY: rect.y + rect.height,
367 + });
368 + if (
369 + overlappingRects.length === 0 ||
370 + (overlappingRects.length === 1 && overlappingRects[0] === rect)
371 + ) {
372 + // There are no overlapping rects that isn't our own rect, so we can just use
373 + // the full space of the rect.
374 + return rect;
375 + }
376 + // We have some overlapping rects but they might not overlap everything. Let's
377 + // shrink it up toward the top left corner until it has no more overlap.
378 + const minX = rect.x;
379 + const minY = rect.y;
380 + let maxX = rect.x + rect.width;
381 + let maxY = rect.y + rect.height;
382 + for (let j = 0; j < overlappingRects.length; j++) {
383 + const overlappingRect = overlappingRects[j];
384 + if (overlappingRect === rect) {
385 + continue;
386 + }
387 + const x = overlappingRect.x;
388 + const y = overlappingRect.y;
389 + if (y < maxY && x < maxX) {
390 + if (
391 + parentRects !== null &&
392 + parentRects.indexOf(overlappingRect) !== -1
393 + ) {
394 + // This rect overlaps but it's part of a parent boundary. We let
395 + // title content render if it's on top and not a sibling.
396 + continue;
397 + }
398 + // This rect cuts into the remaining space. Let's figure out if we're
399 + // better off cutting on the x or y axis to maximize remaining space.
400 + const remainderX = x - minX;
401 + const remainderY = y - minY;
402 + if (remainderX > remainderY) {
403 + maxX = x;
404 + } else {
405 + maxY = y;
406 + }
407 + }
408 + }
409 + if (maxX > minX && maxY > minY) {
410 + return {
411 + x: minX,
412 + y: minY,
413 + width: maxX - minX,
414 + height: maxY - minY,
415 + };
416 + }
417 + }
418 + return null;
419 +}
420 +
421 function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node {
422 const store = useContext(StoreContext);
423 const root = store.getSuspenseByID(rootID);
@@ -329,7 +427,9 @@ function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node {
427 }
428
429 return root.children.map(childID => {
332 - return <SuspenseRects key={childID} suspenseID={childID} />;
430 + return (
431 + <SuspenseRects key={childID} suspenseID={childID} parentRects={null} />
432 + );
433 });
434 }
435
scripts/flow/environment.js
+188
@@ -456,3 +456,191 @@ declare class NavigationDestination {
456
457 getState(): mixed;
458 }
459 +
460 +// Ported from definitely-typed
461 +declare module 'rbush' {
462 + declare interface BBox {
463 + minX: number;
464 + minY: number;
465 + maxX: number;
466 + maxY: number;
467 + }
468 +
469 + declare export default class RBush<T> {
470 + /**
471 + * Constructs an `RBush`, a high-performance 2D spatial index for points and
472 + * rectangles. Based on an optimized __R-tree__ data structure with
473 + * __bulk-insertion__ support.
474 + *
475 + * @param maxEntries An optional argument to RBush defines the maximum
476 + * number of entries in a tree node. `9` (used by default)
477 + * is a reasonable choice for most applications. Higher
478 + * value means faster insertion and slower search, and
479 + * vice versa.
480 + */
481 + constructor(maxEntries?: number): void;
482 +
483 + /**
484 + * Inserts an item. To insert many items at once, use `load()`.
485 + *
486 + * @param item The item to insert.
487 + */
488 + insert(item: T): RBush<T>;
489 +
490 + /**
491 + * Bulk-inserts the given items into the tree.
492 + *
493 + * Bulk insertion is usually ~2-3 times faster than inserting items one by
494 + * one. After bulk loading (bulk insertion into an empty tree), subsequent
495 + * query performance is also ~20-30% better.
496 + *
497 + * Note that when you do bulk insertion into an existing tree, it bulk-loads
498 + * the given data into a separate tree and inserts the smaller tree into the
499 + * larger tree. This means that bulk insertion works very well for clustered
500 + * data (where items in one update are close to each other), but makes query
501 + * performance worse if the data is scattered.
502 + *
503 + * @param items The items to load.
504 + */
505 + load(items: $ReadOnlyArray<T>): RBush<T>;
506 +
507 + /**
508 + * Removes a previously inserted item, comparing by reference.
509 + *
510 + * To remove all items, use `clear()`.
511 + *
512 + * @param item The item to remove.
513 + * @param equals A custom function that allows comparing by value instead.
514 + * Useful when you have only a copy of the object you need
515 + * removed (e.g. loaded from server).
516 + */
517 + remove(item: T, equals?: (a: T, b: T) => boolean): RBush<T>;
518 +
519 + /**
520 + * Removes all items.
521 + */
522 + clear(): RBush<T>;
523 +
524 + /**
525 + * Returns an array of data items (points or rectangles) that the given
526 + * bounding box intersects.
527 + *
528 + * Note that the search method accepts a bounding box in `{minX, minY, maxX,
529 + * maxY}` format regardless of the data format.
530 + *
531 + * @param box The bounding box in which to search.
532 + */
533 + search(box: BBox): T[];
534 +
535 + /**
536 + * Returns all items contained in the tree.
537 + */
538 + all(): T[];
539 +
540 + /**
541 + * Returns `true` if there are any items intersecting the given bounding
542 + * box, otherwise `false`.
543 + *
544 + * @param box The bounding box in which to search.
545 + */
546 + collides(box: BBox): boolean;
547 +
548 + /**
549 + * Returns the bounding box for the provided item.
550 + *
551 + * By default, `RBush` assumes the format of data points to be an object
552 + * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a
553 + * custom item format by overriding `toBBox()`, `compareMinX()`, and
554 + * `compareMinY()`.
555 + *
556 + * @example
557 + * class MyRBush<T> extends RBush<T> {
558 + * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; }
559 + * compareMinX(a, b) { return a.x - b.x; }
560 + * compareMinY(a, b) { return a.y - b.y; }
561 + * }
562 + * const tree = new MyRBush<[number, number]>();
563 + * tree.insert([20, 50]); // accepts [x, y] points
564 + *
565 + * @param item The item whose bounding box should be returned.
566 + */
567 + toBBox(item: T): BBox;
568 +
569 + /**
570 + * Compares the minimum x coordinate of two items. Returns -1 if `a`'s
571 + * x-coordinate is smaller, 1 if `b`'s x coordinate is smaller, or 0 if
572 + * they're equal.
573 + *
574 + * By default, `RBush` assumes the format of data points to be an object
575 + * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a
576 + * custom item format by overriding `toBBox()`, `compareMinX()`, and
577 + * `compareMinY()`.
578 + *
579 + * @example
580 + * class MyRBush<T> extends RBush<T> {
581 + * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; }
582 + * compareMinX(a, b) { return a.x - b.x; }
583 + * compareMinY(a, b) { return a.y - b.y; }
584 + * }
585 + * const tree = new MyRBush<[number, number]>();
586 + * tree.insert([20, 50]); // accepts [x, y] points
587 + *
588 + * @param a The first item to compare.
589 + * @param b The second item to compare.
590 + */
591 + compareMinX(a: T, b: T): number;
592 +
593 + /**
594 + * Compares the minimum y coordinate of two items. Returns -1 if `a`'s
595 + * x-coordinate is smaller, 1 if `b`'s x coordinate is smaller, or 0 if
596 + * they're equal.
597 + *
598 + * By default, `RBush` assumes the format of data points to be an object
599 + * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a
600 + * custom item format by overriding `toBBox()`, `compareMinX()`, and
601 + * `compareMinY()`.
602 + *
603 + * @example
604 + * class MyRBush<T> extends RBush<T> {
605 + * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; }
606 + * compareMinX(a, b) { return a.x - b.x; }
607 + * compareMinY(a, b) { return a.y - b.y; }
608 + * }
609 + * const tree = new MyRBush<[number, number]>();
610 + * tree.insert([20, 50]); // accepts [x, y] points
611 + *
612 + * @param a The first item to compare.
613 + * @param b The second item to compare.
614 + */
615 + compareMinY(a: T, b: T): number;
616 +
617 + /**
618 + * Exports the tree's contents as a JSON object.
619 + *
620 + * Importing and exporting as JSON allows you to use RBush on both the
621 + * server (using Node.js) and the browser combined, e.g. first indexing the
622 + * data on the server and and then importing the resulting tree data on the
623 + * client for searching.
624 + *
625 + * Note that the `maxEntries` option from the constructor must be the same
626 + * in both trees for export/import to work properly.
627 + */
628 + toJSON(): any;
629 +
630 + /**
631 + * Imports previously exported data into the tree (i.e., data that was
632 + * emitted by `toJSON()`).
633 + *
634 + * Importing and exporting as JSON allows you to use RBush on both the
635 + * server (using Node.js) and the browser combined, e.g. first indexing the
636 + * data on the server and and then importing the resulting tree data on the
637 + * client for searching.
638 + *
639 + * Note that the `maxEntries` option from the constructor must be the same
640 + * in both trees for export/import to work properly.
641 + *
642 + * @param data The previously exported JSON data.
643 + */
644 + fromJSON(data: any): RBush<T>;
645 + }
646 +}
scripts/jest/config.build-devtools.js
+1 -1
@@ -63,7 +63,7 @@ module.exports = Object.assign({}, baseConfig, {
63 testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'],
64 // Exclude the build output from transforms
65 transformIgnorePatterns: [
66 - '/node_modules/',
66 + '/node_modules/(?!(rbush|quickselect)/)',
67 '<rootDir>/build/',
68 '/__compiled__/',
69 '/__untransformed__/',
yarn.lock
+17 -81
@@ -8271,7 +8271,7 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0:
8271 dependencies:
8272 eslint-visitor-keys "^1.1.0"
8273
8274 -"eslint-v7@npm:eslint@^7.7.0":
8274 +"eslint-v7@npm:eslint@^7.7.0", eslint@^7.7.0:
8275 version "7.32.0"
8276 resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
8277 integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
@@ -8470,52 +8470,6 @@ eslint@8.57.0:
8470 strip-ansi "^6.0.1"
8471 text-table "^0.2.0"
8472
8473 -eslint@^7.7.0:
8474 - version "7.32.0"
8475 - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
8476 - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
8477 - dependencies:
8478 - "@babel/code-frame" "7.12.11"
8479 - "@eslint/eslintrc" "^0.4.3"
8480 - "@humanwhocodes/config-array" "^0.5.0"
8481 - ajv "^6.10.0"
8482 - chalk "^4.0.0"
8483 - cross-spawn "^7.0.2"
8484 - debug "^4.0.1"
8485 - doctrine "^3.0.0"
8486 - enquirer "^2.3.5"
8487 - escape-string-regexp "^4.0.0"
8488 - eslint-scope "^5.1.1"
8489 - eslint-utils "^2.1.0"
8490 - eslint-visitor-keys "^2.0.0"
8491 - espree "^7.3.1"
8492 - esquery "^1.4.0"
8493 - esutils "^2.0.2"
8494 - fast-deep-equal "^3.1.3"
8495 - file-entry-cache "^6.0.1"
8496 - functional-red-black-tree "^1.0.1"
8497 - glob-parent "^5.1.2"
8498 - globals "^13.6.0"
8499 - ignore "^4.0.6"
8500 - import-fresh "^3.0.0"
8501 - imurmurhash "^0.1.4"
8502 - is-glob "^4.0.0"
8503 - js-yaml "^3.13.1"
8504 - json-stable-stringify-without-jsonify "^1.0.1"
8505 - levn "^0.4.1"
8506 - lodash.merge "^4.6.2"
8507 - minimatch "^3.0.4"
8508 - natural-compare "^1.4.0"
8509 - optionator "^0.9.1"
8510 - progress "^2.0.0"
8511 - regexpp "^3.1.0"
8512 - semver "^7.2.1"
8513 - strip-ansi "^6.0.0"
8514 - strip-json-comments "^3.1.0"
8515 - table "^6.0.9"
8516 - text-table "^0.2.0"
8517 - v8-compile-cache "^2.0.3"
8518 -
8473 espree@10.0.1, espree@^10.0.1:
8474 version "10.0.1"
8475 resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
@@ -14317,7 +14271,7 @@ prepend-http@^2.0.0:
14271 resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
14272 integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
14273
14320 -"prettier-2@npm:prettier@^2":
14274 +"prettier-2@npm:prettier@^2", prettier@^2.5.1:
14275 version "2.8.8"
14276 resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
14277 integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
@@ -14332,11 +14286,6 @@ prettier@^1.19.1:
14286 resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
14287 integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
14288
14335 -prettier@^2.5.1:
14336 - version "2.8.8"
14337 - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
14338 - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
14339 -
14289 pretty-format@^29.4.1:
14290 version "29.4.1"
14291 resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.1.tgz#0da99b532559097b8254298da7c75a0785b1751c"
@@ -14602,6 +14551,11 @@ quick-tmp@0.0.0:
14551 first-match "0.0.1"
14552 osenv "0.0.3"
14553
14554 +quickselect@^3.0.0:
14555 + version "3.0.0"
14556 + resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
14557 + integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==
14558 +
14559 random-seed@^0.3.0:
14560 version "0.3.0"
14561 resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd"
@@ -14639,6 +14593,13 @@ raw-loader@^3.1.0:
14593 loader-utils "^1.1.0"
14594 schema-utils "^2.0.1"
14595
14596 +rbush@4.0.1:
14597 + version "4.0.1"
14598 + resolved "https://registry.yarnpkg.com/rbush/-/rbush-4.0.1.tgz#1f55afa64a978f71bf9e9a99bc14ff84f3cb0d6d"
14599 + integrity sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==
14600 + dependencies:
14601 + quickselect "^3.0.0"
14602 +
14603 rc@1.2.8, rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
14604 version "1.2.8"
14605 resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -16211,7 +16172,7 @@ string-natural-compare@^3.0.1:
16172 resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
16173 integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
16174
16214 -"string-width-cjs@npm:string-width@^4.2.0":
16175 +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
16176 version "4.2.3"
16177 resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
16178 integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -16246,15 +16207,6 @@ string-width@^4.0.0:
16207 is-fullwidth-code-point "^3.0.0"
16208 strip-ansi "^6.0.0"
16209
16249 -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
16250 - version "4.2.3"
16251 - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
16252 - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
16253 - dependencies:
16254 - emoji-regex "^8.0.0"
16255 - is-fullwidth-code-point "^3.0.0"
16256 - strip-ansi "^6.0.1"
16257 -
16210 string-width@^5.0.1, string-width@^5.1.2:
16211 version "5.1.2"
16212 resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -16315,7 +16267,7 @@ string_decoder@~1.1.1:
16267 dependencies:
16268 safe-buffer "~5.1.0"
16269
16318 -"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
16270 +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
16271 version "6.0.1"
16272 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
16273 integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -16343,13 +16295,6 @@ strip-ansi@^5.1.0:
16295 dependencies:
16296 ansi-regex "^4.1.0"
16297
16346 -strip-ansi@^6.0.0, strip-ansi@^6.0.1:
16347 - version "6.0.1"
16348 - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
16349 - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
16350 - dependencies:
16351 - ansi-regex "^5.0.1"
16352 -
16298 strip-ansi@^7.0.1:
16299 version "7.1.0"
16300 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -17958,7 +17903,7 @@ workerize-loader@^2.0.2:
17903 dependencies:
17904 loader-utils "^2.0.0"
17905
17961 -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
17906 +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
17907 version "7.0.0"
17908 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
17909 integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -17976,15 +17921,6 @@ wrap-ansi@^6.2.0:
17921 string-width "^4.1.0"
17922 strip-ansi "^6.0.0"
17923
17979 -wrap-ansi@^7.0.0:
17980 - version "7.0.0"
17981 - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
17982 - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
17983 - dependencies:
17984 - ansi-styles "^4.0.0"
17985 - string-width "^4.1.0"
17986 - strip-ansi "^6.0.0"
17987 -
17924 wrap-ansi@^8.1.0:
17925 version "8.1.0"
17926 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"