From f4a793282350b72815583f078dff9dbb7f61beac Mon Sep 17 00:00:00 2001 From: atomiks Date: Thu, 19 Dec 2024 00:29:19 +1100 Subject: [PATCH] [useScrollLock] New implementation (#1159) Co-authored-by: Vlad Moroz --- .../app/(private)/experiments/scroll-lock.tsx | 18 +- docs/src/components/CodeBlock.css | 10 - docs/src/components/Demo/Demo.css | 15 -- docs/src/components/MobileNav.tsx | 17 -- docs/src/components/QuickNav/QuickNav.tsx | 27 +- docs/src/components/Table.css | 5 - packages/react/package.json | 1 + packages/react/src/utils/detectBrowser.ts | 6 + packages/react/src/utils/useScrollLock.ts | 235 +++++++---------- pnpm-lock.yaml | 238 ++++++++++++++++++ 10 files changed, 365 insertions(+), 207 deletions(-) diff --git a/docs/src/app/(private)/experiments/scroll-lock.tsx b/docs/src/app/(private)/experiments/scroll-lock.tsx index 02bad09376..028d5470d8 100644 --- a/docs/src/app/(private)/experiments/scroll-lock.tsx +++ b/docs/src/app/(private)/experiments/scroll-lock.tsx @@ -17,14 +17,28 @@ export default function ScrollLock() {

useScrollLock

On macOS, enable `Show scroll bars: Always` in `Appearance` Settings.

+
+ Fixed content should not shift +
diff --git a/docs/src/components/CodeBlock.css b/docs/src/components/CodeBlock.css index 1cc27afa0e..fcda468725 100644 --- a/docs/src/components/CodeBlock.css +++ b/docs/src/components/CodeBlock.css @@ -30,11 +30,6 @@ display: none; } - /* iOS Safari appears to flicker the scroll containers during tasking animations */ - [data-mobile-nav-open] & { - overflow: hidden; - } - /* Scroll containers may be focusable */ &:focus-visible { position: relative; @@ -65,11 +60,6 @@ /* Prevent Chrome/Safari page navigation gestures when scrolling horizontally */ overscroll-behavior-x: contain; - /* iOS Safari appears to flicker the scroll containers during tasking animations */ - [data-mobile-nav-open] & { - overflow: hidden; - } - & code { /* Different fonts may introduce vertical align issues */ display: block; diff --git a/docs/src/components/Demo/Demo.css b/docs/src/components/Demo/Demo.css index 7e8866c24d..60d750063f 100644 --- a/docs/src/components/Demo/Demo.css +++ b/docs/src/components/Demo/Demo.css @@ -16,11 +16,6 @@ overscroll-behavior-x: contain; scrollbar-width: thin; - /* iOS Safari appears to flicker the scroll containers during tasking animations */ - [data-mobile-nav-open] & { - overflow: hidden; - } - /* Scroll containers may be focusable */ &:focus-visible { position: relative; @@ -73,11 +68,6 @@ display: none; } - /* iOS Safari appears to flicker the scroll containers during tasking animations */ - [data-mobile-nav-open] & { - overflow: hidden; - } - /* Scroll containers may be focusable */ &:focus-visible { position: relative; @@ -180,11 +170,6 @@ /* Prevent Chrome/Safari page navigation gestures when scrolling horizontally */ overscroll-behavior-x: contain; - /* iOS Safari appears to flicker the scroll containers during tasking animations */ - [data-mobile-nav-open] & { - overflow: hidden; - } - /* Scroll containers may be focusable */ &:focus-visible { position: relative; diff --git a/docs/src/components/MobileNav.tsx b/docs/src/components/MobileNav.tsx index c8a5f25b4e..a030d1434d 100644 --- a/docs/src/components/MobileNav.tsx +++ b/docs/src/components/MobileNav.tsx @@ -46,23 +46,6 @@ function PopupImpl({ children }: React.PropsWithChildren) { rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize); }, []); - // iOS Safari appears to flicker the scroll containers during tasking animations - // As a workaround, we set an attribute on body to know when the nav is mounted - // and disable scroll on the problematic scroll containers in the meantime - const timeout = React.useRef(0); - React.useLayoutEffect(() => { - window.clearTimeout(timeout.current); - document.body.setAttribute('data-mobile-nav-open', ''); - return () => { - window.clearTimeout(timeout.current); - timeout.current = window.setTimeout(() => { - document.body.removeAttribute('data-mobile-nav-open'); - // Safari seems to need some arbitrary time to be done with whatever causes the flicker - // Using setTimeout 0, RAFs, or even double RAFs doesn't work reliably - }, 100); - }; - }, []); - return (
diff --git a/docs/src/components/QuickNav/QuickNav.tsx b/docs/src/components/QuickNav/QuickNav.tsx index 99a98ec2f2..5ccf1eb835 100644 --- a/docs/src/components/QuickNav/QuickNav.tsx +++ b/docs/src/components/QuickNav/QuickNav.tsx @@ -26,7 +26,7 @@ function onMounted(ref: React.RefObject) { let top: number; let bottom: number; - let prevScrollY = getScrollY(); + let prevScrollY = window.scrollY; let resizeObserver: ResizeObserver | undefined; let state: 'Scrollable' | 'StickyTop' | 'StickyBottom' = 'StickyTop'; let raf = 0; @@ -68,9 +68,9 @@ function onMounted(ref: React.RefObject) { // if it was `position: static` and `position: absolute` // relative to the start of the document ref.current.style.position = 'static'; - const staticTop = getScrollY() + ref.current.getBoundingClientRect().y; + const staticTop = window.scrollY + ref.current.getBoundingClientRect().y; ref.current.style.position = 'absolute'; - const absoluteTop = getScrollY() + ref.current.getBoundingClientRect().y; + const absoluteTop = window.scrollY + ref.current.getBoundingClientRect().y; // Get the nav bottom Y coordinate when it's at its maximum possible bottom position // relative to the start of the document @@ -78,7 +78,7 @@ function onMounted(ref: React.RefObject) { ref.current.style.top = 'auto'; ref.current.style.bottom = '0'; const rect = ref.current.getBoundingClientRect(); - const absoluteBottom = getScrollY() + rect.bottom; + const absoluteBottom = window.scrollY + rect.bottom; ref.current.style.position = ''; ref.current.style.top = initialStyles.top; @@ -128,8 +128,8 @@ function onMounted(ref: React.RefObject) { if (ref.current) { state = 'Scrollable'; const { absoluteTop, absoluteBottom, staticTop } = getCachedPositions(); - const marginTop = Math.max(staticTop - absoluteTop, getScrollY() + newTop - absoluteTop); - const marginBottom = Math.max(0, absoluteBottom - getScrollY() - newBottom); + const marginTop = Math.max(staticTop - absoluteTop, window.scrollY + newTop - absoluteTop); + const marginBottom = Math.max(0, absoluteBottom - window.scrollY - newBottom); // Choose the smaller margin because at document edges, // the larger one may push the nav out of the container edges @@ -150,12 +150,14 @@ function onMounted(ref: React.RefObject) { function handleUpdate() { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { - if (!ref.current) { + const isScrollLocked = document.documentElement.hasAttribute('data-base-ui-scroll-locked'); + + if (!ref.current || isScrollLocked) { return; } - const delta = getScrollY() - prevScrollY; - prevScrollY = getScrollY(); + const delta = window.scrollY - prevScrollY; + prevScrollY = window.scrollY; // We may get into <0.1px rounding issues in Safari and Firefox const rect = ref.current.getBoundingClientRect(); @@ -228,7 +230,7 @@ function onMounted(ref: React.RefObject) { unstick(Math.min(cssTop, top), Math.max(window.innerHeight, bottom)); } - prevScrollY = getScrollY(); + prevScrollY = window.scrollY; window.addEventListener('scroll', handleUpdate); }); } @@ -250,11 +252,6 @@ function onMounted(ref: React.RefObject) { }; } -/** Get window.scrollY accounting for a potential scroll lock offset top style */ -function getScrollY() { - return window.scrollY - parseFloat(document.documentElement.style.top || '0'); -} - export function Title({ className, ...props }: React.ComponentProps<'h2'>) { return
; } diff --git a/docs/src/components/Table.css b/docs/src/components/Table.css index 941dc4cc23..18de419757 100644 --- a/docs/src/components/Table.css +++ b/docs/src/components/Table.css @@ -78,10 +78,5 @@ &::-webkit-scrollbar { display: none; } - - /* iOS Safari appears to flicker the scroll containers during tasking animations */ - [data-mobile-nav-open] & { - overflow: hidden; - } } } diff --git a/packages/react/package.json b/packages/react/package.json index 62318b8646..a8fb06db16 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -79,6 +79,7 @@ "@babel/runtime": "^7.26.0", "@floating-ui/react": "^0.27.2", "@floating-ui/utils": "^0.2.8", + "@react-aria/overlays": "^3.24.0", "prop-types": "^15.8.1", "use-sync-external-store": "^1.4.0" }, diff --git a/packages/react/src/utils/detectBrowser.ts b/packages/react/src/utils/detectBrowser.ts index 9dc2bf5af8..dc260bb116 100644 --- a/packages/react/src/utils/detectBrowser.ts +++ b/packages/react/src/utils/detectBrowser.ts @@ -1,3 +1,5 @@ +import { getUserAgent } from '@floating-ui/react/utils'; + interface NavigatorUAData { brands: Array<{ brand: string; version: string }>; mobile: boolean; @@ -29,3 +31,7 @@ export function isWebKit() { export function isIOS() { return /iP(hone|ad|od)|iOS/.test(getPlatform()); } + +export function isFirefox() { + return /firefox/i.test(getUserAgent()); +} diff --git a/packages/react/src/utils/useScrollLock.ts b/packages/react/src/utils/useScrollLock.ts index 147ebfa15f..513f1d7f2d 100644 --- a/packages/react/src/utils/useScrollLock.ts +++ b/packages/react/src/utils/useScrollLock.ts @@ -1,5 +1,6 @@ -import { getUserAgent } from '@floating-ui/react/utils'; -import { isIOS, isWebKit } from './detectBrowser'; +import * as React from 'react'; +import { usePreventScroll } from '@react-aria/overlays'; +import { isFirefox, isIOS, isWebKit } from './detectBrowser'; import { ownerDocument, ownerWindow } from './owner'; import { useEnhancedEffect } from './useEnhancedEffect'; @@ -8,172 +9,105 @@ let originalBodyStyles = {}; let preventScrollCount = 0; let restore: () => void = () => {}; -function getVisualOffsets(doc: Document) { - const win = ownerWindow(doc); - const vV = win.visualViewport; - return { - x: Math.floor(vV?.offsetLeft || 0), - y: Math.floor(vV?.offsetTop || 0), - }; +function supportsDvh() { + return ( + typeof CSS !== 'undefined' && + typeof CSS.supports === 'function' && + CSS.supports('height', '1dvh') + ); } -function preventScrollIOS(referenceElement?: Element | null) { +function hasInsetScrollbars(referenceElement?: Element | null) { + if (typeof document === 'undefined') { + return false; + } const doc = ownerDocument(referenceElement); - const html = doc.documentElement; - const body = doc.body; - const htmlStyle = html.style; - const bodyStyle = body.style; - - // iOS 12 does not support `visualViewport`. - const { x, y } = getVisualOffsets(doc); - const scrollX = bodyStyle.left ? parseFloat(bodyStyle.left) : window.scrollX; - const scrollY = bodyStyle.top ? parseFloat(bodyStyle.top) : window.scrollY; - - originalHtmlStyles = { - overflowX: htmlStyle.overflowX, - overflowY: htmlStyle.overflowY, - }; - - originalBodyStyles = { - position: bodyStyle.position, - top: bodyStyle.top, - left: bodyStyle.left, - right: bodyStyle.right, - overflowX: bodyStyle.overflowX, - overflowY: bodyStyle.overflowY, - }; - - Object.assign(htmlStyle, { - overflow: 'visible', - }); - - Object.assign(bodyStyle, { - position: 'fixed', - top: `${-(scrollY - y)}px`, - left: `${-(scrollX - x)}px`, - right: '0', - overflow: 'hidden', - }); - - return () => { - Object.assign(htmlStyle, originalHtmlStyles); - Object.assign(bodyStyle, originalBodyStyles); - window.scrollTo({ left: scrollX, top: scrollY, behavior: 'instant' }); - }; + const win = ownerWindow(doc); + return win.innerWidth - doc.documentElement.clientWidth > 0; } function preventScrollStandard(referenceElement?: Element | null) { - const isFirefox = /firefox/i.test(getUserAgent()); const doc = ownerDocument(referenceElement); const html = doc.documentElement; const body = doc.body; - const win = ownerWindow(doc); - const htmlStyle = html.style; - const bodyStyle = body.style; - - let resizeRaf: number; - let scrollX: number; - let scrollY: number; - let paddingProp: 'paddingLeft' | 'paddingRight'; + const win = ownerWindow(html); - function lockScroll() { - if (isFirefox) { - // RTL scrollbar - const scrollbarX = - Math.round(doc.documentElement.getBoundingClientRect().left) + - doc.documentElement.scrollLeft; - paddingProp = scrollbarX ? 'paddingLeft' : 'paddingRight'; - const scrollbarWidth = win.innerWidth - doc.documentElement.clientWidth; - - bodyStyle.overflow = 'hidden'; - htmlStyle.overflow = 'visible'; - - if (scrollbarWidth) { - bodyStyle[paddingProp] = `${scrollbarWidth}px`; - } + let scrollTop = 0; + let scrollLeft = 0; + let resizeRaf = -1; - return; - } + // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom. + if (isWebKit() && (win.visualViewport?.scale ?? 1) !== 1) { + return () => {}; + } - const htmlComputedStyles = getComputedStyle(html); - const bodyComputedStyles = getComputedStyle(body); - const hasConstantOverflowY = - htmlComputedStyles.overflowY === 'scroll' || bodyComputedStyles.overflowY === 'scroll'; - const hasConstantOverflowX = - htmlComputedStyles.overflowX === 'scroll' || bodyComputedStyles.overflowX === 'scroll'; + function lockScroll() { + const htmlStyles = win.getComputedStyle(html); + const bodyStyles = win.getComputedStyle(body); - scrollX = htmlStyle.left ? parseFloat(htmlStyle.left) : window.scrollX; - scrollY = htmlStyle.top ? parseFloat(htmlStyle.top) : window.scrollY; + scrollTop = html.scrollTop; + scrollLeft = html.scrollLeft; originalHtmlStyles = { - position: htmlStyle.position, - top: htmlStyle.top, - left: htmlStyle.left, - right: htmlStyle.right, - overflowX: htmlStyle.overflowX, - overflowY: htmlStyle.overflowY, + overflowY: html.style.overflowY, + overflowX: html.style.overflowX, }; + originalBodyStyles = { - overflowX: bodyStyle.overflowX, - overflowY: bodyStyle.overflowY, + position: body.style.position, + height: body.style.height, + width: body.style.width, + boxSizing: body.style.boxSizing, + overflowY: body.style.overflowY, + overflowX: body.style.overflowX, }; + // Handle `scrollbar-gutter` in Chrome when there is no scrollable content. + const hasScrollbarGutterStable = htmlStyles.scrollbarGutter?.includes('stable'); + const isScrollableY = html.scrollHeight > html.clientHeight; const isScrollableX = html.scrollWidth > html.clientWidth; + const hasConstantOverflowY = + htmlStyles.overflowY === 'scroll' || bodyStyles.overflowY === 'scroll'; + const hasConstantOverflowX = + htmlStyles.overflowX === 'scroll' || bodyStyles.overflowX === 'scroll'; - // Handle `scrollbar-gutter` in Chrome when there is no scrollable content. - const hasScrollbarGutterStable = htmlComputedStyles.scrollbarGutter?.includes('stable'); - - // Safari needs visual viewport offsets added to account for pinch-zoom - const webkit = isWebKit(); - const { x, y } = getVisualOffsets(doc); - const visualX = webkit ? x : 0; - const visualY = webkit ? y : 0; - - if (!hasScrollbarGutterStable) { - Object.assign(htmlStyle, { - position: 'fixed', - top: `${-scrollY + visualY}px`, - left: `${-scrollX + visualX}px`, - right: '0', - }); - } + // Values can be negative in Firefox + const scrollbarWidth = Math.max(0, win.innerWidth - html.clientWidth); + const scrollbarHeight = Math.max(0, win.innerHeight - html.clientHeight); - Object.assign(htmlStyle, { + Object.assign(html.style, { overflowY: !hasScrollbarGutterStable && (isScrollableY || hasConstantOverflowY) ? 'scroll' : 'hidden', overflowX: !hasScrollbarGutterStable && (isScrollableX || hasConstantOverflowX) ? 'scroll' : 'hidden', }); - // Ensure two scrollbars can't appear since `` now has a forced scrollbar, but the - // `` may have one too. - if (isScrollableY || hasConstantOverflowY) { - bodyStyle.overflowY = 'visible'; - } - if (isScrollableX || hasConstantOverflowX) { - bodyStyle.overflowX = 'visible'; - } + // Avoid shift due to the default margin. This does cause elements to be clipped + // with whitespace. Warn if has margins? + const marginY = parseFloat(bodyStyles.marginTop) + parseFloat(bodyStyles.marginBottom); + const marginX = parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight); + + Object.assign(body.style, { + position: 'relative', + height: + marginY || scrollbarHeight ? `calc(100dvh - ${marginY + scrollbarHeight}px)` : '100dvh', + width: marginX || scrollbarWidth ? `calc(100vw - ${marginX + scrollbarWidth}px)` : '100vw', + boxSizing: 'border-box', + overflow: 'hidden', + }); + + body.scrollTop = scrollTop; + body.scrollLeft = scrollLeft; + html.setAttribute('data-base-ui-scroll-locked', ''); } function cleanup() { - if (isFirefox) { - Object.assign(bodyStyle, { - overflow: '', - [paddingProp]: '', - }); - Object.assign(htmlStyle, { - overflow: '', - }); - return; - } - - Object.assign(htmlStyle, originalHtmlStyles); - Object.assign(bodyStyle, originalBodyStyles); - - if (window.scrollTo.toString().includes('[native code]')) { - window.scrollTo({ left: scrollX, top: scrollY, behavior: 'instant' }); - } + Object.assign(html.style, originalHtmlStyles); + Object.assign(body.style, originalBodyStyles); + html.scrollTop = scrollTop; + html.scrollLeft = scrollLeft; + html.removeAttribute('data-base-ui-scroll-locked'); } function handleResize() { @@ -183,12 +117,12 @@ function preventScrollStandard(referenceElement?: Element | null) { } lockScroll(); - window.addEventListener('resize', handleResize); + win.addEventListener('resize', handleResize); return () => { cancelAnimationFrame(resizeRaf); cleanup(); - window.removeEventListener('resize', handleResize); + win.removeEventListener('resize', handleResize); }; } @@ -198,16 +132,31 @@ function preventScrollStandard(referenceElement?: Element | null) { * @param enabled - Whether to enable the scroll lock. */ export function useScrollLock(enabled: boolean = true, referenceElement?: Element | null) { + const isReactAriaHook = React.useMemo( + () => + enabled && + (isIOS() || + !supportsDvh() || + // macOS Firefox "pops" scroll containers' scrollbars with our standard scroll lock + (isFirefox() && !hasInsetScrollbars())), + [enabled], + ); + + usePreventScroll({ + // react-aria will remove the scrollbar offset immediately upon close, since we use `open`, + // not `mounted`, to disable/enable the scroll lock. However since there are no inset + // scrollbars, no layouting issues occur. + isDisabled: !isReactAriaHook, + }); + useEnhancedEffect(() => { - if (!enabled) { + if (!enabled || isReactAriaHook) { return undefined; } preventScrollCount += 1; if (preventScrollCount === 1) { - restore = isIOS() - ? preventScrollIOS(referenceElement) - : preventScrollStandard(referenceElement); + restore = preventScrollStandard(referenceElement); } return () => { @@ -216,5 +165,5 @@ export function useScrollLock(enabled: boolean = true, referenceElement?: Elemen restore(); } }; - }, [enabled, referenceElement]); + }, [enabled, isReactAriaHook, referenceElement]); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9163f1503e..9165f640a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -630,6 +630,9 @@ importers: '@floating-ui/utils': specifier: ^0.2.8 version: 0.2.8 + '@react-aria/overlays': + specifier: ^3.24.0 + version: 3.24.0(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) prop-types: specifier: ^15.8.1 version: 15.8.1 @@ -1907,6 +1910,21 @@ packages: '@floating-ui/utils@0.2.8': resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@formatjs/ecma402-abstract@2.3.1': + resolution: {integrity: sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==} + + '@formatjs/fast-memoize@2.2.5': + resolution: {integrity: sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g==} + + '@formatjs/icu-messageformat-parser@2.9.7': + resolution: {integrity: sha512-cuEHyRM5VqLQobANOjtjlgU7+qmk9Q3fDQuBiRRJ3+Wp3ZoZhpUPtUfuimZXsir6SaI2TaAJ+SLo9vLnV5QcbA==} + + '@formatjs/icu-skeleton-parser@1.8.11': + resolution: {integrity: sha512-8LlHHE/yL/zVJZHAX3pbKaCjZKmBIO6aJY1mkVh4RMSEu/2WRZ4Ysvv3kKXJ9M8RJLBHdnk1/dUQFdod1Dt7Dw==} + + '@formatjs/intl-localematcher@0.5.9': + resolution: {integrity: sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==} + '@gitbeaker/core@38.12.1': resolution: {integrity: sha512-8XMVcBIdVAAoxn7JtqmZ2Ee8f+AZLcCPmqEmPFOXY2jPS84y/DERISg/+sbhhb18iRy+ZsZhpWgQ/r3CkYNJOQ==} engines: {node: '>=18.0.0'} @@ -2069,6 +2087,18 @@ packages: peerDependencies: '@types/node': ^18.19.67 + '@internationalized/date@3.6.0': + resolution: {integrity: sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==} + + '@internationalized/message@3.1.6': + resolution: {integrity: sha512-JxbK3iAcTIeNr1p0WIFg/wQJjIzJt9l/2KNY/48vXV7GRGZSv3zMxJsce008fZclk2cDC8y0Ig3odceHO7EfNQ==} + + '@internationalized/number@3.6.0': + resolution: {integrity: sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw==} + + '@internationalized/string@3.2.5': + resolution: {integrity: sha512-rKs71Zvl2OKOHM+mzAFMIyqR5hI1d1O6BBkMK2/lkfg3fkmVh9Eeg0awcA8W2WqYqDOv6a86DIOlFpggwLtbuw==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2711,6 +2741,43 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@react-aria/focus@3.19.0': + resolution: {integrity: sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/i18n@3.12.4': + resolution: {integrity: sha512-j9+UL3q0Ls8MhXV9gtnKlyozq4aM95YywXqnmJtzT1rYeBx7w28hooqrWkCYLfqr4OIryv1KUnPiCSLwC2OC7w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/interactions@3.22.5': + resolution: {integrity: sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/overlays@3.24.0': + resolution: {integrity: sha512-0kAXBsMNTc/a3M07tK9Cdt/ea8CxTAEJ223g8YgqImlmoBBYAL7dl5G01IOj67TM64uWPTmZrOklBchHWgEm3A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/ssr@3.9.7': + resolution: {integrity: sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/utils@3.26.0': + resolution: {integrity: sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/visually-hidden@3.8.18': + resolution: {integrity: sha512-l/0igp+uub/salP35SsNWq5mGmg3G5F5QMS1gDZ8p28n7CgjvzyiGhJbbca7Oxvaw1HRFzVl9ev+89I7moNnFQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@react-spring/animated@9.7.5': resolution: {integrity: sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==} peerDependencies: @@ -2738,6 +2805,31 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@react-stately/overlays@3.6.12': + resolution: {integrity: sha512-QinvZhwZgj8obUyPIcyURSCjTZlqZYRRCS60TF8jH8ZpT0tEAuDb3wvhhSXuYA3Xo9EHLwvLjEf3tQKKdAQArw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-stately/utils@3.10.5': + resolution: {integrity: sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-types/button@3.10.1': + resolution: {integrity: sha512-XTtap8o04+4QjPNAshFWOOAusUTxQlBjU2ai0BTVLShQEjHhRVDBIWsI2B2FKJ4KXT6AZ25llaxhNrreWGonmA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-types/overlays@3.8.11': + resolution: {integrity: sha512-aw7T0rwVI3EuyG5AOaEIk8j7dZJQ9m34XAztXJVZ/W2+4pDDkLDbJ/EAPnuo2xGYRGhowuNDn4tDju01eHYi+w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-types/shared@3.26.0': + resolution: {integrity: sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@remix-run/router@1.21.0': resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} engines: {node: '>=14.0.0'} @@ -5838,6 +5930,9 @@ packages: resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} engines: {node: '>=10.13.0'} + intl-messageformat@10.7.10: + resolution: {integrity: sha512-hp7iejCBiJdW3zmOe18FdlJu8U/JsADSDiBPQhfdSeI8B9POtvPRvPh3nMlvhYayGMKLv6maldhR7y3Pf1vkpw==} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -10916,6 +11011,32 @@ snapshots: '@floating-ui/utils@0.2.8': {} + '@formatjs/ecma402-abstract@2.3.1': + dependencies: + '@formatjs/fast-memoize': 2.2.5 + '@formatjs/intl-localematcher': 0.5.9 + decimal.js: 10.4.3 + tslib: 2.6.2 + + '@formatjs/fast-memoize@2.2.5': + dependencies: + tslib: 2.6.2 + + '@formatjs/icu-messageformat-parser@2.9.7': + dependencies: + '@formatjs/ecma402-abstract': 2.3.1 + '@formatjs/icu-skeleton-parser': 1.8.11 + tslib: 2.6.2 + + '@formatjs/icu-skeleton-parser@1.8.11': + dependencies: + '@formatjs/ecma402-abstract': 2.3.1 + tslib: 2.6.2 + + '@formatjs/intl-localematcher@0.5.9': + dependencies: + tslib: 2.6.2 + '@gitbeaker/core@38.12.1': dependencies: '@gitbeaker/requester-utils': 38.12.1 @@ -11056,6 +11177,23 @@ snapshots: dependencies: '@types/node': 18.19.67 + '@internationalized/date@3.6.0': + dependencies: + '@swc/helpers': 0.5.13 + + '@internationalized/message@3.1.6': + dependencies: + '@swc/helpers': 0.5.13 + intl-messageformat: 10.7.10 + + '@internationalized/number@3.6.0': + dependencies: + '@swc/helpers': 0.5.13 + + '@internationalized/string@3.2.5': + dependencies: + '@swc/helpers': 0.5.13 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -11932,6 +12070,73 @@ snapshots: '@polka/url@1.0.0-next.25': {} + '@react-aria/focus@3.19.0(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-aria/interactions': 3.22.5(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/utils': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + clsx: 2.1.1 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-aria/i18n@3.12.4(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@internationalized/date': 3.6.0 + '@internationalized/message': 3.1.6 + '@internationalized/number': 3.6.0 + '@internationalized/string': 3.2.5 + '@react-aria/ssr': 3.9.7(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/utils': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-aria/interactions@3.22.5(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-aria/ssr': 3.9.7(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/utils': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-aria/overlays@3.24.0(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-aria/focus': 3.19.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/i18n': 3.12.4(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/interactions': 3.22.5(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/ssr': 3.9.7(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/utils': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/visually-hidden': 3.8.18(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-stately/overlays': 3.6.12(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/button': 3.10.1(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/overlays': 3.8.11(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + react-dom: 19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614) + + '@react-aria/ssr@3.9.7(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-aria/utils@3.26.0(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-aria/ssr': 3.9.7(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-stately/utils': 3.10.5(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + clsx: 2.1.1 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-aria/visually-hidden@3.8.18(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-aria/interactions': 3.22.5(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-aria/utils': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + '@react-spring/animated@9.7.5(react@19.0.0-rc-fb9a90fa48-20240614)': dependencies: '@react-spring/shared': 9.7.5(react@19.0.0-rc-fb9a90fa48-20240614) @@ -11964,6 +12169,32 @@ snapshots: react: 19.0.0-rc-fb9a90fa48-20240614 react-dom: 19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-stately/overlays@3.6.12(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-stately/utils': 3.10.5(react@19.0.0-rc-fb9a90fa48-20240614) + '@react-types/overlays': 3.8.11(react@19.0.0-rc-fb9a90fa48-20240614) + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-stately/utils@3.10.5(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@swc/helpers': 0.5.13 + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-types/button@3.10.1(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-types/overlays@3.8.11(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + '@react-types/shared': 3.26.0(react@19.0.0-rc-fb9a90fa48-20240614) + react: 19.0.0-rc-fb9a90fa48-20240614 + + '@react-types/shared@3.26.0(react@19.0.0-rc-fb9a90fa48-20240614)': + dependencies: + react: 19.0.0-rc-fb9a90fa48-20240614 + '@remix-run/router@1.21.0': {} '@rollup/rollup-android-arm-eabi@4.24.2': @@ -15726,6 +15957,13 @@ snapshots: interpret@3.1.1: {} + intl-messageformat@10.7.10: + dependencies: + '@formatjs/ecma402-abstract': 2.3.1 + '@formatjs/fast-memoize': 2.2.5 + '@formatjs/icu-messageformat-parser': 2.9.7 + tslib: 2.6.2 + ip-address@9.0.5: dependencies: jsbn: 1.1.0