diff --git a/packages/react-devtools-shared/package.json b/packages/react-devtools-shared/package.json index 543ac37e97614..721cc9954b3d7 100644 --- a/packages/react-devtools-shared/package.json +++ b/packages/react-devtools-shared/package.json @@ -23,6 +23,7 @@ "json5": "^2.2.3", "local-storage-fallback": "^4.1.1", "react-virtualized-auto-sizer": "^1.0.23", - "react-window": "^1.8.10" + "react-window": "^1.8.10", + "rbush": "4.0.1" } } diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index aaf0584a4c51a..9377fa01dfeac 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -62,6 +62,31 @@ import type { import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError'; import type {DevToolsHookSettings} from '../backend/types'; +import RBush from 'rbush'; + +// Custom version which works with our Rect data structure. +class RectRBush extends RBush { + toBBox(rect: Rect): { + minX: number, + minY: number, + maxX: number, + maxY: number, + } { + return { + minX: rect.x, + minY: rect.y, + maxX: rect.x + rect.width, + maxY: rect.y + rect.height, + }; + } + compareMinX(a: Rect, b: Rect): number { + return a.x - b.x; + } + compareMinY(a: Rect, b: Rect): number { + return a.y - b.y; + } +} + const debug = (methodName: string, ...args: Array) => { if (__DEBUG__) { console.log( @@ -194,6 +219,9 @@ export default class Store extends EventEmitter<{ // Renderer ID is needed to support inspection fiber props, state, and hooks. _rootIDToRendererID: Map = new Map(); + // Stores all the SuspenseNode rects in an R-tree to make it fast to find overlaps. + _rtree: RBush = new RectRBush(); + // These options may be initially set by a configuration option when constructing the Store. _supportsInspectMatchingDOMElement: boolean = false; _supportsClickToInspect: boolean = false; @@ -1622,7 +1650,12 @@ export default class Store extends EventEmitter<{ const y = operations[i + 1] / 1000; const width = operations[i + 2] / 1000; const height = operations[i + 3] / 1000; - rects.push({x, y, width, height}); + const rect = {x, y, width, height}; + if (parentID !== 0) { + // Track all rects except the root. + this._rtree.insert(rect); + } + rects.push(rect); i += 4; } } @@ -1680,13 +1713,20 @@ export default class Store extends EventEmitter<{ i += 1; - const {children, parentID} = suspense; + const {children, parentID, rects} = suspense; if (children.length > 0) { this._throwAndEmitError( Error(`Suspense node "${id}" was removed before its children.`), ); } + if (rects !== null && parentID !== 0) { + // Delete all the existing rects from the R-tree + for (let j = 0; j < rects.length; j++) { + this._rtree.remove(rects[j]); + } + } + this._idToSuspense.delete(id); removedSuspenseIDs.set(id, parentID); @@ -1785,6 +1825,14 @@ export default class Store extends EventEmitter<{ break; } + const prevRects = suspense.rects; + if (prevRects !== null && suspense.parentID !== 0) { + // Delete all the existing rects from the R-tree + for (let j = 0; j < prevRects.length; j++) { + this._rtree.remove(prevRects[j]); + } + } + let nextRects: SuspenseNode['rects']; if (numRects === -1) { nextRects = null; @@ -1796,7 +1844,12 @@ export default class Store extends EventEmitter<{ const width = operations[i + 2] / 1000; const height = operations[i + 3] / 1000; - nextRects.push({x, y, width, height}); + const rect = {x, y, width, height}; + if (suspense.parentID !== 0) { + // Track all rects except the root. + this._rtree.insert(rect); + } + nextRects.push(rect); i += 4; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css index 574ceb0236987..bd13a89c6c901 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css @@ -2,7 +2,7 @@ padding: 0.25rem; } -.CallSite { +.CallSite, .BuiltInCallSite { display: block; padding-left: 1rem; } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css index db0a84d8d87c2..d8dd990850aa0 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css @@ -39,6 +39,24 @@ pointer-events: none; } +.SuspenseRectsTitle { + pointer-events: none; + color: var(--color-text); + overflow: hidden; + text-overflow: ellipsis; + font-size: var(--font-size-sans-small); + line-height: var(--font-size-sans-small); + padding: .25rem; + container-type: size; + container-name: title; +} + +@container title (width < 30px) or (height < 12px) { + .SuspenseRectsTitle > span { + display: none; + } +} + .SuspenseRectsScaledRect[data-visible='false'] > .SuspenseRectsBoundaryChildren { overflow: initial; } @@ -75,7 +93,7 @@ transition: background-color 0.2s ease-out; } -.SuspenseRectsBoundary[data-selected='true'] { +.SuspenseRectsBoundary[data-selected='true'][data-visible='true'] { box-shadow: var(--elevation-4); } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index 67a6bf277398c..d5f44eeb32b88 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -31,6 +31,7 @@ import { SuspenseTreeDispatcherContext, } from './SuspenseTreeContext'; import {getClassNameForEnvironment} from './SuspenseEnvironmentColors.js'; +import type RBush from 'rbush'; function ScaledRect({ className, @@ -78,8 +79,10 @@ function ScaledRect({ function SuspenseRects({ suspenseID, + parentRects, }: { suspenseID: SuspenseNode['id'], + parentRects: null | Array, }): React$Node { const store = useContext(StoreContext); const treeDispatch = useContext(TreeDispatcherContext); @@ -167,7 +170,20 @@ function SuspenseRects({ } } - const boundingBox = getBoundingBox(suspense.rects); + const rects = suspense.rects; + const boundingBox = getBoundingBox(rects); + + // Next we'll try to find a rect within one of our rects that isn't intersecting with + // other rects. + // TODO: This should probably be memoized based on if any changes to the rtree has been made. + const titleBox: null | Rect = + rects === null ? null : findTitleBox(store._rtree, rects, parentRects); + const nextRects = + rects === null || rects.length === 0 + ? parentRects + : parentRects === null || parentRects.length === 0 + ? rects + : parentRects.concat(rects); return ( {suspense.children.map(childID => { - return ; + return ( + + ); })} )} - {selected ? ( + {titleBox && suspense.name && visible ? ( + + {suspense.name} + + ) : null} + {selected && visible ? ( , + rects: Array, + parentRects: null | Array, +): null | Rect { + for (let i = 0; i < rects.length; i++) { + const rect = rects[i]; + if (rect.width < 20 || rect.height < 10) { + // Skip small rects. They're likely not able to be contain anything useful anyway. + continue; + } + // Find all overlapping rects elsewhere in the tree to limit our rect. + const overlappingRects = rtree.search({ + minX: rect.x, + minY: rect.y, + maxX: rect.x + rect.width, + maxY: rect.y + rect.height, + }); + if ( + overlappingRects.length === 0 || + (overlappingRects.length === 1 && overlappingRects[0] === rect) + ) { + // There are no overlapping rects that isn't our own rect, so we can just use + // the full space of the rect. + return rect; + } + // We have some overlapping rects but they might not overlap everything. Let's + // shrink it up toward the top left corner until it has no more overlap. + const minX = rect.x; + const minY = rect.y; + let maxX = rect.x + rect.width; + let maxY = rect.y + rect.height; + for (let j = 0; j < overlappingRects.length; j++) { + const overlappingRect = overlappingRects[j]; + if (overlappingRect === rect) { + continue; + } + const x = overlappingRect.x; + const y = overlappingRect.y; + if (y < maxY && x < maxX) { + if ( + parentRects !== null && + parentRects.indexOf(overlappingRect) !== -1 + ) { + // This rect overlaps but it's part of a parent boundary. We let + // title content render if it's on top and not a sibling. + continue; + } + // This rect cuts into the remaining space. Let's figure out if we're + // better off cutting on the x or y axis to maximize remaining space. + const remainderX = x - minX; + const remainderY = y - minY; + if (remainderX > remainderY) { + maxX = x; + } else { + maxY = y; + } + } + } + if (maxX > minX && maxY > minY) { + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } + } + return null; +} + function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node { const store = useContext(StoreContext); const root = store.getSuspenseByID(rootID); @@ -329,7 +427,9 @@ function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node { } return root.children.map(childID => { - return ; + return ( + + ); }); } diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 5556fad1a0001..f201c2e0e85bd 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -456,3 +456,191 @@ declare class NavigationDestination { getState(): mixed; } + +// Ported from definitely-typed +declare module 'rbush' { + declare interface BBox { + minX: number; + minY: number; + maxX: number; + maxY: number; + } + + declare export default class RBush { + /** + * Constructs an `RBush`, a high-performance 2D spatial index for points and + * rectangles. Based on an optimized __R-tree__ data structure with + * __bulk-insertion__ support. + * + * @param maxEntries An optional argument to RBush defines the maximum + * number of entries in a tree node. `9` (used by default) + * is a reasonable choice for most applications. Higher + * value means faster insertion and slower search, and + * vice versa. + */ + constructor(maxEntries?: number): void; + + /** + * Inserts an item. To insert many items at once, use `load()`. + * + * @param item The item to insert. + */ + insert(item: T): RBush; + + /** + * Bulk-inserts the given items into the tree. + * + * Bulk insertion is usually ~2-3 times faster than inserting items one by + * one. After bulk loading (bulk insertion into an empty tree), subsequent + * query performance is also ~20-30% better. + * + * Note that when you do bulk insertion into an existing tree, it bulk-loads + * the given data into a separate tree and inserts the smaller tree into the + * larger tree. This means that bulk insertion works very well for clustered + * data (where items in one update are close to each other), but makes query + * performance worse if the data is scattered. + * + * @param items The items to load. + */ + load(items: $ReadOnlyArray): RBush; + + /** + * Removes a previously inserted item, comparing by reference. + * + * To remove all items, use `clear()`. + * + * @param item The item to remove. + * @param equals A custom function that allows comparing by value instead. + * Useful when you have only a copy of the object you need + * removed (e.g. loaded from server). + */ + remove(item: T, equals?: (a: T, b: T) => boolean): RBush; + + /** + * Removes all items. + */ + clear(): RBush; + + /** + * Returns an array of data items (points or rectangles) that the given + * bounding box intersects. + * + * Note that the search method accepts a bounding box in `{minX, minY, maxX, + * maxY}` format regardless of the data format. + * + * @param box The bounding box in which to search. + */ + search(box: BBox): T[]; + + /** + * Returns all items contained in the tree. + */ + all(): T[]; + + /** + * Returns `true` if there are any items intersecting the given bounding + * box, otherwise `false`. + * + * @param box The bounding box in which to search. + */ + collides(box: BBox): boolean; + + /** + * Returns the bounding box for the provided item. + * + * By default, `RBush` assumes the format of data points to be an object + * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a + * custom item format by overriding `toBBox()`, `compareMinX()`, and + * `compareMinY()`. + * + * @example + * class MyRBush extends RBush { + * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; } + * compareMinX(a, b) { return a.x - b.x; } + * compareMinY(a, b) { return a.y - b.y; } + * } + * const tree = new MyRBush<[number, number]>(); + * tree.insert([20, 50]); // accepts [x, y] points + * + * @param item The item whose bounding box should be returned. + */ + toBBox(item: T): BBox; + + /** + * Compares the minimum x coordinate of two items. Returns -1 if `a`'s + * x-coordinate is smaller, 1 if `b`'s x coordinate is smaller, or 0 if + * they're equal. + * + * By default, `RBush` assumes the format of data points to be an object + * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a + * custom item format by overriding `toBBox()`, `compareMinX()`, and + * `compareMinY()`. + * + * @example + * class MyRBush extends RBush { + * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; } + * compareMinX(a, b) { return a.x - b.x; } + * compareMinY(a, b) { return a.y - b.y; } + * } + * const tree = new MyRBush<[number, number]>(); + * tree.insert([20, 50]); // accepts [x, y] points + * + * @param a The first item to compare. + * @param b The second item to compare. + */ + compareMinX(a: T, b: T): number; + + /** + * Compares the minimum y coordinate of two items. Returns -1 if `a`'s + * x-coordinate is smaller, 1 if `b`'s x coordinate is smaller, or 0 if + * they're equal. + * + * By default, `RBush` assumes the format of data points to be an object + * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a + * custom item format by overriding `toBBox()`, `compareMinX()`, and + * `compareMinY()`. + * + * @example + * class MyRBush extends RBush { + * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; } + * compareMinX(a, b) { return a.x - b.x; } + * compareMinY(a, b) { return a.y - b.y; } + * } + * const tree = new MyRBush<[number, number]>(); + * tree.insert([20, 50]); // accepts [x, y] points + * + * @param a The first item to compare. + * @param b The second item to compare. + */ + compareMinY(a: T, b: T): number; + + /** + * Exports the tree's contents as a JSON object. + * + * Importing and exporting as JSON allows you to use RBush on both the + * server (using Node.js) and the browser combined, e.g. first indexing the + * data on the server and and then importing the resulting tree data on the + * client for searching. + * + * Note that the `maxEntries` option from the constructor must be the same + * in both trees for export/import to work properly. + */ + toJSON(): any; + + /** + * Imports previously exported data into the tree (i.e., data that was + * emitted by `toJSON()`). + * + * Importing and exporting as JSON allows you to use RBush on both the + * server (using Node.js) and the browser combined, e.g. first indexing the + * data on the server and and then importing the resulting tree data on the + * client for searching. + * + * Note that the `maxEntries` option from the constructor must be the same + * in both trees for export/import to work properly. + * + * @param data The previously exported JSON data. + */ + fromJSON(data: any): RBush; + } +} diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js index 9baf33a39b1d4..3f03b50c3a829 100644 --- a/scripts/jest/config.build-devtools.js +++ b/scripts/jest/config.build-devtools.js @@ -63,7 +63,7 @@ module.exports = Object.assign({}, baseConfig, { testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'], // Exclude the build output from transforms transformIgnorePatterns: [ - '/node_modules/', + '/node_modules/(?!(rbush|quickselect)/)', '/build/', '/__compiled__/', '/__untransformed__/', diff --git a/yarn.lock b/yarn.lock index ad8a3f0085cbf..2543e4d17603d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8271,7 +8271,7 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" -"eslint-v7@npm:eslint@^7.7.0": +"eslint-v7@npm:eslint@^7.7.0", eslint@^7.7.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -8470,52 +8470,6 @@ eslint@8.57.0: strip-ansi "^6.0.1" text-table "^0.2.0" -eslint@^7.7.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - espree@10.0.1, espree@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" @@ -14317,7 +14271,7 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -"prettier-2@npm:prettier@^2": +"prettier-2@npm:prettier@^2", prettier@^2.5.1: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -14332,11 +14286,6 @@ prettier@^1.19.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -prettier@^2.5.1: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - pretty-format@^29.4.1: version "29.4.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.1.tgz#0da99b532559097b8254298da7c75a0785b1751c" @@ -14602,6 +14551,11 @@ quick-tmp@0.0.0: first-match "0.0.1" osenv "0.0.3" +quickselect@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603" + integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g== + random-seed@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd" @@ -14639,6 +14593,13 @@ raw-loader@^3.1.0: loader-utils "^1.1.0" schema-utils "^2.0.1" +rbush@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/rbush/-/rbush-4.0.1.tgz#1f55afa64a978f71bf9e9a99bc14ff84f3cb0d6d" + integrity sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ== + dependencies: + quickselect "^3.0.0" + rc@1.2.8, rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -16211,7 +16172,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16246,15 +16207,6 @@ string-width@^4.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -16315,7 +16267,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16343,13 +16295,6 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -17958,7 +17903,7 @@ workerize-loader@^2.0.2: dependencies: loader-utils "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -17976,15 +17921,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"