diff --git a/change/@fluentui-react-carousel-806c25e9-e23b-4c65-b072-007f894a236a.json b/change/@fluentui-react-carousel-806c25e9-e23b-4c65-b072-007f894a236a.json new file mode 100644 index 00000000000000..d07af7f3e2ce69 --- /dev/null +++ b/change/@fluentui-react-carousel-806c25e9-e23b-4c65-b072-007f894a236a.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: Add autoplay index change callback and fix autoplay pause on interaction", + "packageName": "@fluentui/react-carousel", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/package.json b/package.json index db0ed7153a91eb..416ab89efa43d2 100644 --- a/package.json +++ b/package.json @@ -218,9 +218,9 @@ "doctrine": "3.0.0", "dotparser": "1.1.1", "ejs": "3.1.10", - "embla-carousel": "8.3.0", - "embla-carousel-autoplay": "8.3.0", - "embla-carousel-fade": "8.3.0", + "embla-carousel": "8.5.1", + "embla-carousel-autoplay": "8.5.1", + "embla-carousel-fade": "8.5.1", "enquirer": "2.3.6", "enzyme": "3.10.0", "enzyme-to-json": "3.6.2", diff --git a/packages/react-components/react-carousel/library/etc/react-carousel.api.md b/packages/react-components/react-carousel/library/etc/react-carousel.api.md index 981198ec2ee4f9..5ca840719cc5b7 100644 --- a/packages/react-components/react-carousel/library/etc/react-carousel.api.md +++ b/packages/react-components/react-carousel/library/etc/react-carousel.api.md @@ -103,7 +103,7 @@ export type CarouselContextValue = { selectPageByDirection: (event: React_2.MouseEvent, direction: 'next' | 'prev') => number; selectPageByIndex: (event: React_2.MouseEvent, value: number, jump?: boolean) => void; subscribeForValues: (listener: (data: CarouselUpdateData) => void) => () => void; - enableAutoplay: (autoplay: boolean) => void; + enableAutoplay: (autoplay: boolean, temporary?: boolean) => void; resetAutoplay: () => void; containerRef?: React_2.RefObject; viewportRef?: React_2.RefObject; @@ -115,7 +115,7 @@ export type CarouselContextValues = { }; // @public (undocumented) -export type CarouselIndexChangeData = (EventData<'click', React_2.MouseEvent> | EventData<'focus', React_2.FocusEvent> | EventData<'drag', PointerEvent | MouseEvent>) & { +export type CarouselIndexChangeData = (EventData<'click', React_2.MouseEvent> | EventData<'focus', React_2.FocusEvent> | EventData<'drag', PointerEvent | MouseEvent> | EventData<'autoplay', Event>) & { index: number; }; diff --git a/packages/react-components/react-carousel/library/package.json b/packages/react-components/react-carousel/library/package.json index 1b566c4500af1b..34abc44ff59dae 100644 --- a/packages/react-components/react-carousel/library/package.json +++ b/packages/react-components/react-carousel/library/package.json @@ -36,9 +36,9 @@ "@fluentui/react-utilities": "^9.18.17", "@griffel/react": "^1.5.22", "@swc/helpers": "^0.5.1", - "embla-carousel": "^8.3.0", - "embla-carousel-autoplay": "^8.3.0", - "embla-carousel-fade": "^8.3.0" + "embla-carousel": "^8.5.1", + "embla-carousel-autoplay": "^8.5.1", + "embla-carousel-fade": "^8.5.1" }, "peerDependencies": { "@types/react": ">=16.14.0 <19.0.0", diff --git a/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts b/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts index 89e7228e1cf430..1f61ea71e13a9e 100644 --- a/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts +++ b/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts @@ -49,6 +49,7 @@ export function useCarousel_unstable(props: CarouselProps, ref: React.Ref { diff --git a/packages/react-components/react-carousel/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx b/packages/react-components/react-carousel/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx index ad3a8e838eeeb2..8c650e593f4813 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx +++ b/packages/react-components/react-carousel/library/src/components/CarouselAutoplayButton/useCarouselAutoplayButton.tsx @@ -1,13 +1,7 @@ import type { ARIAButtonElement } from '@fluentui/react-aria'; import { useToggleButton_unstable } from '@fluentui/react-button'; import { PlayCircleRegular, PauseCircleRegular } from '@fluentui/react-icons'; -import { - mergeCallbacks, - slot, - useControllableState, - useEventCallback, - useIsomorphicLayoutEffect, -} from '@fluentui/react-utilities'; +import { mergeCallbacks, slot, useControllableState, useEventCallback } from '@fluentui/react-utilities'; import * as React from 'react'; import type { CarouselAutoplayButtonProps, CarouselAutoplayButtonState } from './CarouselAutoplayButton.types'; @@ -36,15 +30,13 @@ export const useCarouselAutoplayButton_unstable = ( const enableAutoplay = useCarouselContext(ctx => ctx.enableAutoplay); React.useEffect(() => { + // Initialize carousel autoplay based on button state + enableAutoplay(autoplay); + return () => { - // We disable autoplay if the button gets unmounted. + // We uninitialize autoplay if the button gets unmounted. enableAutoplay(false); }; - }, [enableAutoplay]); - - useIsomorphicLayoutEffect(() => { - // Enable/disable autoplay on state change - enableAutoplay(autoplay); }, [autoplay, enableAutoplay]); const handleClick = (event: React.MouseEvent) => { diff --git a/packages/react-components/react-carousel/library/src/components/CarouselContext.types.ts b/packages/react-components/react-carousel/library/src/components/CarouselContext.types.ts index be6ee9911d55b9..51fae7eeb29c15 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselContext.types.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselContext.types.ts @@ -7,6 +7,7 @@ export type CarouselIndexChangeData = ( | EventData<'click', React.MouseEvent> | EventData<'focus', React.FocusEvent> | EventData<'drag', PointerEvent | MouseEvent> + | EventData<'autoplay', Event> ) & { /** * The index to be set after event has occurred. @@ -28,7 +29,7 @@ export type CarouselContextValue = { jump?: boolean, ) => void; subscribeForValues: (listener: (data: CarouselUpdateData) => void) => () => void; - enableAutoplay: (autoplay: boolean) => void; + enableAutoplay: (autoplay: boolean, temporary?: boolean) => void; resetAutoplay: () => void; // Container with controls passed to carousel engine containerRef?: React.RefObject; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselViewport/useCarouselViewport.ts b/packages/react-components/react-carousel/library/src/components/CarouselViewport/useCarouselViewport.ts index b04c25996074d0..674983b1779b9d 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselViewport/useCarouselViewport.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselViewport/useCarouselViewport.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getIntrinsicElementProps, slot, useMergedRefs } from '@fluentui/react-utilities'; +import { getIntrinsicElementProps, mergeCallbacks, slot, useMergedRefs } from '@fluentui/react-utilities'; import type { CarouselViewportProps, CarouselViewportState } from './CarouselViewport.types'; import { useCarouselContext_unstable as useCarouselContext } from '../CarouselContext'; @@ -16,7 +16,54 @@ export const useCarouselViewport_unstable = ( props: CarouselViewportProps, ref: React.Ref, ): CarouselViewportState => { + const hasFocus = React.useRef(false); + const hasMouse = React.useRef(false); const viewportRef = useCarouselContext(ctx => ctx.viewportRef); + const enableAutoplay = useCarouselContext(ctx => ctx.enableAutoplay); + + const handleFocusCapture = React.useCallback( + (e: React.FocusEvent) => { + hasFocus.current = true; + // Will pause autoplay when focus is captured within viewport (if autoplay is initialized) + enableAutoplay(false, true); + }, + [enableAutoplay], + ); + + const handleBlurCapture = React.useCallback( + (e: React.FocusEvent) => { + // Will enable autoplay (if initialized) when focus exits viewport + if (!e.currentTarget.contains(e.relatedTarget)) { + hasFocus.current = false; + if (!hasMouse.current) { + enableAutoplay(true, true); + } + } + }, + [enableAutoplay], + ); + + const handleMouseEnter = React.useCallback( + (event: React.MouseEvent) => { + hasMouse.current = true; + enableAutoplay(false, true); + }, + [enableAutoplay], + ); + const handleMouseLeave = React.useCallback( + (event: React.MouseEvent) => { + hasMouse.current = false; + if (!hasFocus.current) { + enableAutoplay(true, true); + } + }, + [enableAutoplay], + ); + + const onFocusCapture = mergeCallbacks(props.onFocusCapture, handleFocusCapture); + const onBlurCapture = mergeCallbacks(props.onBlurCapture, handleBlurCapture); + const onMouseEnter = mergeCallbacks(props.onMouseEnter, handleMouseEnter); + const onMouseLeave = mergeCallbacks(props.onMouseLeave, handleMouseLeave); return { components: { @@ -29,6 +76,10 @@ export const useCarouselViewport_unstable = ( // Draggable ensures dragging is supported (even if not enabled) draggable: true, ...props, + onFocusCapture, + onBlurCapture, + onMouseEnter, + onMouseLeave, }), { elementType: 'div' }, ), diff --git a/packages/react-components/react-carousel/library/src/components/useEmblaCarousel.ts b/packages/react-components/react-carousel/library/src/components/useEmblaCarousel.ts index 1ad4203fc4b93a..c23796005a4185 100644 --- a/packages/react-components/react-carousel/library/src/components/useEmblaCarousel.ts +++ b/packages/react-components/react-carousel/library/src/components/useEmblaCarousel.ts @@ -10,6 +10,8 @@ import Fade from 'embla-carousel-fade'; import { pointerEventPlugin } from './pointerEvents'; import type { CarouselIndexChangeData } from './CarouselContext.types'; +type EmblaEventHandler = Parameters[1]; + const sliderClassname = `.${carouselSliderClassNames.root}`; const DEFAULT_EMBLA_OPTIONS: EmblaOptionsType = { @@ -43,9 +45,20 @@ export function useEmblaCarousel( activeIndex: number | undefined; motion?: CarouselMotion; onDragIndexChange?: EventHandler; + onAutoplayIndexChange?: EventHandler; }, ) { - const { align, direction, loop, slidesToScroll, watchDrag, containScroll, motion, onDragIndexChange } = options; + const { + align, + direction, + loop, + slidesToScroll, + watchDrag, + containScroll, + motion, + onDragIndexChange, + onAutoplayIndexChange, + } = options; const [activeIndex, setActiveIndex] = useControllableState({ defaultState: options.defaultActiveIndex, state: options.activeIndex, @@ -70,33 +83,22 @@ export function useEmblaCarousel( const autoplayRef = React.useRef(false); const resetAutoplay = React.useCallback(() => { - emblaApi.current?.plugins().autoplay.reset(); + emblaApi.current?.plugins().autoplay?.reset(); }, []); - /* Our autoplay button, which is required by standards for autoplay to be enabled, will handle controlled state */ - const enableAutoplay = React.useCallback( - (autoplay: boolean) => { - autoplayRef.current = autoplay; - if (autoplay) { - emblaApi.current?.plugins().autoplay.play(); - // Reset after play to ensure timing and any focus/mouse pause state is reset. - resetAutoplay(); - } else { - emblaApi.current?.plugins().autoplay.stop(); - } - }, - [resetAutoplay], - ); - const getPlugins = React.useCallback(() => { - const plugins: EmblaPluginType[] = [ + const plugins: EmblaPluginType[] = []; + + plugins.push( Autoplay({ playOnInit: autoplayRef.current, - stopOnInteraction: !autoplayRef.current, - stopOnMouseEnter: true, - stopOnFocusIn: true, + /* stopOnInteraction: false causes autoplay to restart on interaction end*/ + /* we'll handle this logic to ensure autoplay state is respected */ + stopOnInteraction: true, + stopOnFocusIn: false, // We'll handle this one manually to prevent conflicts with tabster + stopOnMouseEnter: false, // We will handle this manually to align functionality }), - ]; + ); // Optionally add Fade plugin if (motion === 'fade') { @@ -114,6 +116,27 @@ export function useEmblaCarousel( return plugins; }, [motion, onDragEvent, watchDrag]); + /* This function enables autoplay to pause/play without affecting underlying state + * Useful for pausing on focus etc. without having to reinitialize or set autoplay to off + */ + const enableAutoplay = React.useCallback( + (autoplay: boolean, temporary?: boolean) => { + if (!temporary) { + autoplayRef.current = autoplay; + } + + if (autoplay && autoplayRef.current) { + // Autoplay should only enable in the case where underlying state is true, temporary should not override + emblaApi.current?.plugins().autoplay?.play(); + // Reset after play to ensure timing and any focus/mouse pause state is reset. + resetAutoplay(); + } else if (!autoplay) { + emblaApi.current?.plugins().autoplay?.stop(); + } + }, + [resetAutoplay], + ); + // Listeners contains callbacks for UI elements that may require state update based on embla changes const listeners = React.useRef(new Set<(data: CarouselUpdateData) => void>()); const subscribeForValues = React.useCallback((listener: (data: CarouselUpdateData) => void) => { @@ -142,22 +165,27 @@ export function useEmblaCarousel( } }); + const handleIndexChange: EmblaEventHandler = useEventCallback((_, eventType) => { + const newIndex = emblaApi.current?.selectedScrollSnap() ?? 0; + const slides = emblaApi.current?.slideNodes(); + const actualIndex = emblaApi.current?.internalEngine().slideRegistry[newIndex][0] ?? 0; + + // We set the active or first index of group on-screen as the selected tabster index + slides?.forEach((slide, slideIndex) => { + setTabsterDefault(slide, slideIndex === actualIndex); + }); + setActiveIndex(newIndex); + + if (eventType === 'autoplay:select') { + const noopEvent = new Event('autoplay'); + onAutoplayIndexChange?.(noopEvent, { event: noopEvent, type: 'autoplay', index: newIndex }); + } + }); + const viewportRef: React.RefObject = React.useRef(null); const containerRef: React.RefObject = React.useMemo(() => { let currentElement: HTMLDivElement | null = null; - const handleIndexChange = () => { - const newIndex = emblaApi.current?.selectedScrollSnap() ?? 0; - const slides = emblaApi.current?.slideNodes(); - const actualIndex = emblaApi.current?.internalEngine().slideRegistry[newIndex][0] ?? 0; - - // We set the active or first index of group on-screen as the selected tabster index - slides?.forEach((slide, slideIndex) => { - setTabsterDefault(slide, slideIndex === actualIndex); - }); - setActiveIndex(newIndex); - }; - const handleVisibilityChange = () => { const cardElements = emblaApi.current?.slideNodes(); const visibleIndexes = emblaApi.current?.slidesInView() ?? []; @@ -172,6 +200,7 @@ export function useEmblaCarousel( }); }; + // Get plugins using autoplayRef to prevent state change recreating EmblaCarousel const plugins = getPlugins(); return { @@ -180,15 +209,15 @@ export function useEmblaCarousel( emblaApi.current?.off('slidesInView', handleVisibilityChange); emblaApi.current?.off('select', handleIndexChange); emblaApi.current?.off('reInit', handleReinit); + emblaApi.current?.off('autoplay:select', handleIndexChange); emblaApi.current?.destroy(); } // Use direct viewport if available, else fallback to container (includes Carousel controls). - const wrapperElement = viewportRef.current ?? newElement; - if (wrapperElement) { - currentElement = wrapperElement; + currentElement = viewportRef.current ?? newElement; + if (currentElement) { emblaApi.current = EmblaCarousel( - wrapperElement, + currentElement, { ...DEFAULT_EMBLA_OPTIONS, ...emblaOptions.current, @@ -199,10 +228,11 @@ export function useEmblaCarousel( emblaApi.current?.on('reInit', handleReinit); emblaApi.current?.on('slidesInView', handleVisibilityChange); emblaApi.current?.on('select', handleIndexChange); + emblaApi.current?.on('autoplay:select', handleIndexChange); } }, }; - }, [getPlugins, setActiveIndex, handleReinit]); + }, [getPlugins, handleIndexChange, handleReinit]); const carouselApi = React.useMemo( () => ({ @@ -234,17 +264,6 @@ export function useEmblaCarousel( [], ); - React.useEffect(() => { - // Scroll to controlled values on update - // If active index is out of bounds, re-init will handle instead - const currentActiveIndex = emblaApi.current?.selectedScrollSnap() ?? 0; - const slideLength = emblaApi.current?.slideNodes()?.length ?? 0; - emblaOptions.current.startIndex = activeIndex; - if (activeIndex < slideLength && activeIndex !== currentActiveIndex) { - emblaApi.current?.scrollTo(activeIndex); - } - }, [activeIndex]); - React.useEffect(() => { const plugins = getPlugins(); @@ -257,6 +276,7 @@ export function useEmblaCarousel( watchDrag, containScroll, }; + emblaApi.current?.reInit( { ...DEFAULT_EMBLA_OPTIONS, @@ -264,7 +284,18 @@ export function useEmblaCarousel( }, plugins, ); - }, [align, direction, loop, slidesToScroll, watchDrag, containScroll, getPlugins]); + }, [align, containScroll, direction, getPlugins, loop, slidesToScroll, watchDrag]); + + React.useEffect(() => { + // Scroll to controlled values on update + // If active index is out of bounds, re-init will handle instead + const currentActiveIndex = emblaApi.current?.selectedScrollSnap() ?? 0; + const slideLength = emblaApi.current?.slideNodes()?.length ?? 0; + emblaOptions.current.startIndex = activeIndex; + if (activeIndex < slideLength && activeIndex !== currentActiveIndex) { + emblaApi.current?.scrollTo(activeIndex); + } + }, [activeIndex]); return { activeIndex, diff --git a/packages/react-components/react-carousel/stories/src/Carousel/CarouselControlled.stories.tsx b/packages/react-components/react-carousel/stories/src/Carousel/CarouselControlled.stories.tsx index 8080d76da36066..26ca5acceb4803 100644 --- a/packages/react-components/react-carousel/stories/src/Carousel/CarouselControlled.stories.tsx +++ b/packages/react-components/react-carousel/stories/src/Carousel/CarouselControlled.stories.tsx @@ -9,6 +9,7 @@ import { Toolbar, ToolbarButton, CarouselSlider, + CarouselAutoplayButton, } from '@fluentui/react-components'; import { Carousel, @@ -40,6 +41,7 @@ const useClasses = makeStyles({ gap: '10px', alignSelf: 'center', + justifySelf: 'center', width: 'max-content', border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`, @@ -125,58 +127,61 @@ export const Controlled = () => {
setActiveIndex(data.index)} announcement={getAnnouncement} > - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + {JSON.stringify({ activeIndex }, null, 2)} + + + {new Array(5).fill(null).map((_, index) => ( + setActiveIndex(index)} + > + {index} + + ))} + +
- -
- {JSON.stringify({ activeIndex }, null, 2)} - - - {new Array(5).fill(null).map((_, index) => ( - setActiveIndex(index)} - > - {index} - - ))} - -
); }; diff --git a/packages/react-components/react-carousel/stories/src/Carousel/CarouselEventing.stories.tsx b/packages/react-components/react-carousel/stories/src/Carousel/CarouselEventing.stories.tsx index 81a150bb0ba3c1..94ab3e4e4f89d4 100644 --- a/packages/react-components/react-carousel/stories/src/Carousel/CarouselEventing.stories.tsx +++ b/packages/react-components/react-carousel/stories/src/Carousel/CarouselEventing.stories.tsx @@ -139,7 +139,7 @@ export const Eventing = () => { const [activeIndex, setActiveIndex] = React.useState(0); const [statusLog, setStatusLog] = React.useState< - [number, { type: 'click' | 'focus' | 'drag' | undefined; index: number }][] + [number, { type: 'click' | 'focus' | 'drag' | 'autoplay' | undefined; index: number }][] >([]); return ( @@ -209,6 +209,7 @@ export const Eventing = () => { layout="inline" next={{ 'aria-label': 'go to next' }} prev={{ 'aria-label': 'go to prev' }} + autoplay={{ 'aria-label': 'Carousel autoplay' }} > {index => } diff --git a/yarn.lock b/yarn.lock index 63990c4e70db99..2e29da842db8d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10267,20 +10267,20 @@ electron-to-chromium@^1.4.820: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz#cd477c830dd6fca41fbd5465c1ff6ce08ac22343" integrity sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA== -embla-carousel-autoplay@8.3.0, embla-carousel-autoplay@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/embla-carousel-autoplay/-/embla-carousel-autoplay-8.3.0.tgz#2878e7c67c7c6f5c4cb0a06a8cb06e53d8f32f2f" - integrity sha512-h7DFJLf9uQD+XDxr1NwA3/oFIjsnj/iED2RjET5u6/svMec46IbF1CYPhmB5Q/1Fc0WkcvhPpsEsrtVXQLxNzA== +embla-carousel-autoplay@8.5.1, embla-carousel-autoplay@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/embla-carousel-autoplay/-/embla-carousel-autoplay-8.5.1.tgz#d0213ab6d7beeafcfcb8f7b1fa023a4d3882f0a2" + integrity sha512-FnZklFpePfp8wbj177UwVaGFehgs+ASVcJvYLWTtHuYKURynCc3IdDn2qrn0E5Qpa3g9yeGwCS4p8QkrZmO8xg== -embla-carousel-fade@8.3.0, embla-carousel-fade@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/embla-carousel-fade/-/embla-carousel-fade-8.3.0.tgz#44be8f2c00a771828bd02078fed26bce005d1f7a" - integrity sha512-m0NbkNPTAr6ghINhJrCnI0BRgWWoGRIGUd1tYCxTK00Exm9+kzOVL5KBPkrMVzXRXHe6TRgkmsCkb/7npfwRFQ== +embla-carousel-fade@8.5.1, embla-carousel-fade@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/embla-carousel-fade/-/embla-carousel-fade-8.5.1.tgz#216b27198ee7ed71b27c545d231da76697e34185" + integrity sha512-n7vRe2tsTW0vc0Xxtk3APoxhUSXIGh/lGRKYtBJS/SWDeXf9E3qVUst4MfHhwXaHlfu5PLqG3xIEDAr2gwbbNA== -embla-carousel@8.3.0, embla-carousel@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/embla-carousel/-/embla-carousel-8.3.0.tgz#dc27c63c405aa98320cb36893e4be2fbdc787ee1" - integrity sha512-Ve8dhI4w28qBqR8J+aMtv7rLK89r1ZA5HocwFz6uMB/i5EiC7bGI7y+AM80yAVUJw3qqaZYK7clmZMUR8kM3UA== +embla-carousel@8.5.1, embla-carousel@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/embla-carousel/-/embla-carousel-8.5.1.tgz#8d83217e831666f6df573b0d3727ff0ae9208002" + integrity sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A== emittery@^0.13.1: version "0.13.1" @@ -21515,7 +21515,7 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"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== @@ -21550,15 +21550,6 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.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" @@ -21659,7 +21650,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.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== @@ -21694,13 +21685,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.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, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -23945,7 +23929,7 @@ workspace-tools@^0.27.0: js-yaml "^4.1.0" micromatch "^4.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== @@ -23980,15 +23964,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"