From ff18b03a259bcec34349ae92bc7bf57e755b6255 Mon Sep 17 00:00:00 2001 From: Zayooo Date: Mon, 31 Aug 2026 12:45:07 +0200 Subject: [PATCH] feat(Combobox): portal + anchor the dropdown menu, add menuWidth (MET-3088) Migrate the Combobox code component from Headless UI v1 to v2 so the dropdown menu is portalled and positioned by Floating UI instead of being an abspos child of the input. This lets the menu escape any `overflow` ancestor (it was previously clipped inside narrow containers, e.g. the Institutional Program Assessment sidebar) and flip/shift to stay in the viewport. - `Combobox.Input/Button/Options/Option` -> v2 named components; drop the `` wrapper for the `transition` prop + `[data-closed]`; drop `nullable`; reset the query on `onClose`. - Replace the `arrowIconRef.click()` focus hack with `immediate` (kept a slim `onClick` so re-clicking a focused input reopens the menu). - New `menuWidth` prop: `"input"` (default, unchanged behaviour) or `"fit"` (grows to the widest option, never narrower than the input). `width`/`min-width` are applied inline so they win over a leftover `width` in `optionsClassName`; `max-width` is left to CSS so callers can still cap it. - Split the menu into `.positioner` (what Floating UI portals/positions and stamps an inline `max-width: ` onto) and an inner `.options` wrapper that carries all author-facing sizing/look, so a caller's `max-width` is no longer overridden. - `anchor` gap of 1px + restored top `clip-path` keep `"input"` mode visually identical to before. - Option-state selectors for `optionClassName` move from `[data-headlessui-state*=active]` etc. to v2's `[data-focus]` / `[data-selected]` / `[data-disabled]`. - Bump the `@headlessui/react` peer range to ^2.2.0 (2.2.x carries the Combobox open-on-trigger and `immediate` fixes we need). --- package.json | 4 +- .../Combobox/Combobox.module.css | 28 +- .../Combobox/Combobox.register.ts | 27 +- src/code-components/Combobox/Combobox.tsx | 347 ++++++++++-------- yarn.lock | 146 +++++++- 5 files changed, 361 insertions(+), 191 deletions(-) diff --git a/package.json b/package.json index abccebce..4a6c560d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ ], "repository": "github:fullstackhouse/plasmic-utils", "devDependencies": { - "@headlessui/react": "^2.1.8", + "@headlessui/react": "^2.2.0", "@hookform/resolvers": "^3.3.4", "@plasmicapp/loader-nextjs": "^1.0.415", "@plasmicapp/react-web": "^0.2.374", @@ -58,7 +58,7 @@ "zod": "^3.22.4" }, "peerDependencies": { - "@headlessui/react": "^1.7.18", + "@headlessui/react": "^2.2.0", "@hookform/resolvers": "^3.3.4", "@plasmicapp/react-web": "^0.2.327", "@radix-ui/react-dialog": "^1.0.5", diff --git a/src/code-components/Combobox/Combobox.module.css b/src/code-components/Combobox/Combobox.module.css index 263bddf9..73a6204c 100644 --- a/src/code-components/Combobox/Combobox.module.css +++ b/src/code-components/Combobox/Combobox.module.css @@ -24,22 +24,26 @@ transform: rotate(180deg) translateY(5px); } -/* Clip-path to get rid of overflowing box shadow on input */ +/* + * `.positioner` is the element Headless UI portals + positions via Floating UI. + * It also gets an inline `max-width: px` from Floating UI's `size` + * middleware on every reposition, so all author-facing sizing (width via the + * `menuWidth` prop, max-width via `optionsClassName`) lives on the inner + * `.options` wrapper, which Floating UI never touches. + */ +.positioner { + transition: opacity 0.1s ease-in; +} + +.positioner[data-closed] { + opacity: 0; +} + .options { list-style: none; scrollbar-width: thin; user-select: none; - transition: opacity 0.1s ease-in; - position: absolute; - top: 100%; margin: 0; + /* Clip the top edge so the box-shadow does not bleed up over the input. */ clip-path: inset(0 -48px -48px -48px); } - -.options:global(.hide-start) { - opacity: 1; -} - -.options:global(.hide-end) { - opacity: 0; -} diff --git a/src/code-components/Combobox/Combobox.register.ts b/src/code-components/Combobox/Combobox.register.ts index f7c2277d..a2aff004 100644 --- a/src/code-components/Combobox/Combobox.register.ts +++ b/src/code-components/Combobox/Combobox.register.ts @@ -5,10 +5,10 @@ export function registerCombobox( plasmic: PlasmicLoader, modulePath = "@fullstackhouse/plasmic-utils/dist", ) { - const activeSelector = "[data-headlessui-state*=active]"; - const selectedSelector = "[aria-selected=true]"; + const activeSelector = "[data-focus]"; + const selectedSelector = "[data-selected]"; const highlightSelector = "[data-highlight=true]"; - const disabledSelector = "[aria-disabled=true]"; + const disabledSelector = "[data-disabled]"; plasmic.registerComponent(Combobox, { name: "RawCombobox", @@ -48,6 +48,27 @@ export function registerCombobox( ], }, disabled: { type: "boolean", defaultValue: false }, + menuPlacement: { + type: "choice", + options: [ + "bottom start", + "bottom end", + "bottom", + "top start", + "top end", + "top", + ], + defaultValue: "bottom start", + description: + "Where the dropdown menu opens relative to the input. The menu is portalled and kept on-screen, so it may be wider than the input.", + }, + menuWidth: { + type: "choice", + options: ["input", "fit"], + defaultValue: "input", + description: + "'input' matches the input width; 'fit' grows to the widest option (long labels don't wrap), never narrower than the input, capped to stay on screen.", + }, "aria-label": "string", "aria-labelledby": "string", placeholder: { type: "string", defaultValue: "Select" }, diff --git a/src/code-components/Combobox/Combobox.tsx b/src/code-components/Combobox/Combobox.tsx index 8f391adf..c1d300fe 100644 --- a/src/code-components/Combobox/Combobox.tsx +++ b/src/code-components/Combobox/Combobox.tsx @@ -1,5 +1,11 @@ -import { Combobox as HeadlessCombobox, Transition } from "@headlessui/react"; -import { useState, useRef, Fragment, ReactNode } from "react"; +import { + Combobox as HeadlessCombobox, + ComboboxButton, + ComboboxInput, + ComboboxOption, + ComboboxOptions, +} from "@headlessui/react"; +import { Fragment, useRef, useState, ReactNode } from "react"; import { MemoDataProvider } from "../MemoDataProvider/MemoDataProvider"; import styles from "./Combobox.module.css"; @@ -22,6 +28,31 @@ interface ComboboxProps { options?: ComboboxOption[]; optionLeftIcon?: ReactNode; disabled?: boolean; + /** + * Placement of the dropdown menu relative to the input, forwarded to Headless + * UI's `anchor` prop. The menu is portalled to the document body and kept in + * the viewport by Floating UI, so it can be wider than the input without being + * clipped by an overflow ancestor. Defaults to `"bottom start"`. + */ + menuPlacement?: + | "top" + | "top start" + | "top end" + | "bottom" + | "bottom start" + | "bottom end"; + /** + * How wide the dropdown menu is. + * - `"input"` (default): exactly as wide as the input. + * - `"fit"`: grows to the widest option (so long labels don't wrap), never + * narrower than the input. + * + * `width` / `min-width` are applied inline so they win over any `width` left in + * `optionsClassName`. `max-width` is left entirely to CSS — set your own cap on + * the options element via `optionsClassName` if you want one. Without a cap the + * menu is still kept inside the viewport by Floating UI. + */ + menuWidth?: "input" | "fit"; "aria-label"?: string; "aria-labelledby"?: string; placeholder?: string; @@ -68,6 +99,8 @@ export function Combobox({ options, optionLeftIcon, disabled, + menuPlacement = "bottom start", + menuWidth = "input", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, placeholder, @@ -90,7 +123,15 @@ export function Combobox({ arrowIconClassName, }: ComboboxProps) { const [query, setQuery] = useState(""); - const arrowIconRef = useRef(null); + const buttonRef = useRef(null); + + // `width` / `min-width` are inline so they beat any leftover `width` on + // `optionsClassName` (e.g. a `width: 100%` from the design tool). `max-width` + // is deliberately left to CSS so it stays overridable via `optionsClassName`. + const optionsStyle = + menuWidth === "fit" + ? { width: "max-content", minWidth: "var(--input-width)" } + : { width: "var(--input-width)" }; const optionGroups = groupOptions(options ?? []); const visibleOptionGroups = filterOptionGroupsByQuery(optionGroups, query); @@ -103,169 +144,165 @@ export function Combobox({ return (
onChange?.(option ? option.value : null)} + onChange={(option: ComboboxOption | null) => + onChange?.(option ? option.value : null) + } + onClose={() => setQuery("")} > - {({ open }) => { - const openMenu = () => { - arrowIconRef.current?.click(); - }; - - return ( -
-
- {leftIcon} -
- { - setQuery(event.target.value); - }} - // Open menu whenever input is focused or clicked - // (The latter is still needed. When clicking on an input that's already focused, e.g. after selecting an option, - // user may want to select another option). - // - // P.S. Once `immediate` prop gets released in a stable version - // ( https://github.com/tailwindlabs/headlessui/discussions/1236 ) - // we may simply switch to that. - onClick={() => { - if (!open) { - openMenu(); - } - }} - onFocus={(event) => { - if (!open) { - openMenu(); - event.currentTarget.select(); - event.currentTarget.setSelectionRange( - 0, - event.currentTarget.value.length, - ); - } - }} - displayValue={(option: ComboboxOption | null) => { - return option ? (option.label ?? String(option.value)) : ""; - }} - className={[inputClassName, styles.input].join(" ")} - placeholder={placeholder} - autoComplete="off" - spellCheck="false" - aria-labelledby={ariaLabelledBy} - aria-label={ariaLabel} - // Hide 1password autocomplete - // ( https://1password.community/discussion/117501/as-a-web-developer-how-can-i-disable-1password-filling-for-a-specific-field/p4 ) - data-1p-ignore - /> -
- {rightIcon} -
- ( +
+
+ {leftIcon} +
+ { + setQuery(event.target.value); + }} + // `immediate` opens the menu on focus. We still handle click so + // that clicking an already-focused input (e.g. right after + // selecting an option) reopens the menu. + onClick={() => { + if (!open) { + buttonRef.current?.click(); + } + }} + onFocus={(event) => { + event.currentTarget.select(); + event.currentTarget.setSelectionRange( + 0, + event.currentTarget.value.length, + ); + }} + displayValue={(option: ComboboxOption | null) => { + return option ? (option.label ?? String(option.value)) : ""; + }} + className={[inputClassName, styles.input].join(" ")} + placeholder={placeholder} + autoComplete="off" + spellCheck="false" + aria-labelledby={ariaLabelledBy} + aria-label={ariaLabel} + // Hide 1password autocomplete + // ( https://1password.community/discussion/117501/as-a-web-developer-how-can-i-disable-1password-filling-for-a-specific-field/p4 ) + data-1p-ignore + /> +
+ {rightIcon} +
+ + - - - - - setQuery("")} + + + + + {/* + * Inner wrapper carries all sizing + look. Headless UI's `anchor` + * writes an inline `max-width: px` (Floating UI `size` + * middleware) onto the positioned element on every reposition, + * which would override a caller's `max-width`. That element is the + * `ComboboxOptions` above; this wrapper is untouched by it. + */} +
- - {visibleOptionGroups.length === 0 ? ( -

{emptyOptionText}

- ) : ( - limitedOptionGroups.map(({ name, options }) => ( - - {name && ( - - - - )} - {options.map((option, optionIndex) => ( - - {optionLeftIcon ? ( - {emptyOptionText}

+ ) : ( + limitedOptionGroups.map(({ name, options }) => ( + + {name && ( +
+ +
+ )} + {options.map((option, optionIndex) => ( + + {optionLeftIcon ? ( + +
+ {optionLeftIcon} +
+
+ ) : null} +
+

+ +

+ {option.description ? ( +

-

- {optionLeftIcon} -
- - ) : null} -
-

- {option.description ? ( -

- -

- ) : null} -
- - ))} - - )) - )} - {overLimit && ( -

{typeToSearchText}

- )} - {footer &&
{footer}
} - - -
- ); - }} + ) : null} +
+ + ))} + + )) + )} + {overLimit && ( +

{typeToSearchText}

+ )} + {footer &&
{footer}
} +
+ +
+ )}
); diff --git a/yarn.lock b/yarn.lock index 73df7583..3003bdc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -437,15 +437,16 @@ resolved "https://registry.yarnpkg.com/@fullstackhouse/react-ssr-prepass/-/react-ssr-prepass-2.0.11.tgz#669a6d100282b13506a71a7cc5356817fdbed486" integrity sha512-MJiupFz2sxQYqipaN+TcOyJvNZuQ4bcuwO4ZvM0HInc2mgHxrUApWvRZmhipEKJdPG+rPmIMLYx9ec7nl9nA4g== -"@headlessui/react@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.1.8.tgz#b0e254fdf8b1f65dbf28de444f70d0a9b61705dd" - integrity sha512-uajqVkAcVG/wHwG9Fh5PFMcFpf2VxM4vNRNKxRjuK009kePVur8LkuuygHfIE+2uZ7z7GnlTtYsyUe6glPpTLg== +"@headlessui/react@^2.2.0": + version "2.2.10" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.10.tgz#ad69258abcb9efea27a4b37e06e8c9cafd39ca63" + integrity sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA== dependencies: "@floating-ui/react" "^0.26.16" - "@react-aria/focus" "^3.17.1" - "@react-aria/interactions" "^3.21.3" - "@tanstack/react-virtual" "^3.8.1" + "@react-aria/focus" "^3.20.2" + "@react-aria/interactions" "^3.25.0" + "@tanstack/react-virtual" "^3.13.9" + use-sync-external-store "^1.5.0" "@hookform/resolvers@^3.3.4": version "3.9.0" @@ -516,6 +517,13 @@ dependencies: mute-stream "^1.0.0" +"@internationalized/date@^3.12.3": + version "3.12.3" + resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.12.3.tgz#e11d14dbf12bf45057dd5996bac77f7c2f89ff9c" + integrity sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q== + dependencies: + "@swc/helpers" "^0.5.0" + "@internationalized/date@^3.5.4": version "3.5.4" resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.4.tgz#49ba11634fd4350b7a9308e297032267b4063c44" @@ -538,6 +546,20 @@ dependencies: "@swc/helpers" "^0.5.0" +"@internationalized/number@^3.6.7": + version "3.6.7" + resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.7.tgz#5a0a8fa413b5f8679a59dcf37e2a74dc508b8371" + integrity sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg== + dependencies: + "@swc/helpers" "^0.5.0" + +"@internationalized/string@^3.2.10": + version "3.2.10" + resolved "https://registry.yarnpkg.com/@internationalized/string/-/string-3.2.10.tgz#c38bd59a69509a41bf7422f1b2fc3af7468e4322" + integrity sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA== + dependencies: + "@swc/helpers" "^0.5.0" + "@internationalized/string@^3.2.3": version "3.2.3" resolved "https://registry.yarnpkg.com/@internationalized/string/-/string-3.2.3.tgz#b0a8379e779a69e7874979714e27f2ae86761d3c" @@ -1097,6 +1119,14 @@ "@swc/helpers" "^0.5.0" clsx "^2.0.0" +"@react-aria/focus@^3.20.2": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.22.1.tgz#7e7ba6a2250135728eb68ffa21e422ec25a16800" + integrity sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA== + dependencies: + "@swc/helpers" "^0.5.0" + react-aria "^3.48.0" + "@react-aria/form@^3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@react-aria/form/-/form-3.0.5.tgz#abaf6ac005dc3f98760ac74fdb6524ad189399d6" @@ -1132,6 +1162,15 @@ "@react-types/shared" "^3.23.1" "@swc/helpers" "^0.5.0" +"@react-aria/interactions@^3.25.0": + version "3.28.1" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.28.1.tgz#852294ae63f6a7d8aeeba3a1e7343daa8cd8507c" + integrity sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA== + dependencies: + "@react-types/shared" "^3.34.0" + "@swc/helpers" "^0.5.0" + react-aria "^3.48.0" + "@react-aria/label@^3.7.8": version "3.7.8" resolved "https://registry.yarnpkg.com/@react-aria/label/-/label-3.7.8.tgz#69f1c184836b04445fcedce78db9fd939a0570ea" @@ -1438,6 +1477,11 @@ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.23.1.tgz#2f23c81d819d0ef376df3cd4c944be4d6bce84c3" integrity sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw== +"@react-types/shared@^3.34.0", "@react-types/shared@^3.36.1": + version "3.36.1" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.36.1.tgz#edb8081e3872ae68a4eb4a65e62233d0754ec186" + integrity sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag== + "@react-types/switch@^3.5.3": version "3.5.3" resolved "https://registry.yarnpkg.com/@react-types/switch/-/switch-3.5.3.tgz#2a5faaf513e03972df3077e4ff5ef21738239d7c" @@ -1555,17 +1599,17 @@ dependencies: tslib "^2.4.0" -"@tanstack/react-virtual@^3.8.1": - version "3.10.8" - resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz#bf4b06f157ed298644a96ab7efc1a2b01ab36e3c" - integrity sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA== +"@tanstack/react-virtual@^3.13.9": + version "3.14.10" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.14.10.tgz#371cb86ea221302768331981b6a0c11875262354" + integrity sha512-SRyoUbdFMRHuYXMijV5H4ZarQWpXkj3iANq8OFre+pybeVap8ZJjZ3Nz9bVjx4d8PfobVUQUdKyyyHYk3E+djw== dependencies: - "@tanstack/virtual-core" "3.10.8" + "@tanstack/virtual-core" "3.17.8" -"@tanstack/virtual-core@3.10.8": - version "3.10.8" - resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz#975446a667755222f62884c19e5c3c66d959b8b4" - integrity sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA== +"@tanstack/virtual-core@3.17.8": + version "3.17.8" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.17.8.tgz#3a801d8b0569a67b7490d3fe64c83758d18a230c" + integrity sha512-BfEvehNpOT75r5Ksc5xW6NZuXujTfb7nlSEyVu4XHG3gdxNg1KqXruWbDewXOUaUYIo4oRbSfkjIajz4MAT8tA== "@testing-library/dom@^10.4.0": version "10.4.0" @@ -1914,6 +1958,13 @@ aria-hidden@^1.1.1: dependencies: tslib "^2.0.0" +aria-hidden@^1.2.3: + version "1.2.6" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a" + integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== + dependencies: + tslib "^2.0.0" + aria-query@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" @@ -4333,6 +4384,21 @@ quill@~2.0.2: parchment "^3.0.0" quill-delta "^5.1.0" +react-aria@^3.48.0: + version "3.51.0" + resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.51.0.tgz#b8129df769eda51a0f92d256fddcc105badf6ed7" + integrity sha512-AyWLw0XR38cFPwBu/ErgGaVrc5dupLEKmRlMXTGvFKOtbaGRQ2+yQJkjVhpdHhoRhU4+G+tJDFeHDTS8tK3bfQ== + dependencies: + "@internationalized/date" "^3.12.3" + "@internationalized/number" "^3.6.7" + "@internationalized/string" "^3.2.10" + "@react-types/shared" "^3.36.1" + "@swc/helpers" "^0.5.0" + aria-hidden "^1.2.3" + clsx "^2.0.0" + react-stately "3.49.0" + use-sync-external-store "^1.6.0" + react-dom@^18: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -4408,6 +4474,18 @@ react-remove-scroll@2.5.7: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-stately@3.49.0: + version "3.49.0" + resolved "https://registry.yarnpkg.com/react-stately/-/react-stately-3.49.0.tgz#bc7fda7d0245e2a1239a8f0451950b27639d0e17" + integrity sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A== + dependencies: + "@internationalized/date" "^3.12.3" + "@internationalized/number" "^3.6.7" + "@internationalized/string" "^3.2.10" + "@react-types/shared" "^3.36.1" + "@swc/helpers" "^0.5.0" + use-sync-external-store "^1.6.0" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" @@ -4713,7 +4791,16 @@ strict-event-emitter@^0.5.1: resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + 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@^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== @@ -4793,7 +4880,14 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm: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@^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== @@ -5131,6 +5225,11 @@ use-sync-external-store@^1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== +use-sync-external-store@^1.5.0, use-sync-external-store@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + valtio@^1.6.3: version "1.13.2" resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.13.2.tgz#e31d452d5da3550935417670aafd34d832dc7241" @@ -5299,7 +5398,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm: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== @@ -5317,6 +5416,15 @@ 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"