Skip to content

Commit

Permalink
Carousel: Fix autoplay and enable callback on autoplay index change (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Mitch-At-Work authored Nov 27, 2024
1 parent c62fbbf commit 2fc8151
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Add autoplay index change callback and fix autoplay pause on interaction",
"packageName": "@fluentui/react-carousel",
"email": "[email protected]",
"dependentChangeType": "patch"
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export type CarouselContextValue = {
selectPageByDirection: (event: React_2.MouseEvent<HTMLButtonElement | HTMLAnchorElement>, direction: 'next' | 'prev') => number;
selectPageByIndex: (event: React_2.MouseEvent<HTMLButtonElement | HTMLAnchorElement>, 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<HTMLDivElement>;
viewportRef?: React_2.RefObject<HTMLDivElement>;
Expand All @@ -115,7 +115,7 @@ export type CarouselContextValues = {
};

// @public (undocumented)
export type CarouselIndexChangeData = (EventData<'click', React_2.MouseEvent<HTMLButtonElement | HTMLAnchorElement>> | EventData<'focus', React_2.FocusEvent> | EventData<'drag', PointerEvent | MouseEvent>) & {
export type CarouselIndexChangeData = (EventData<'click', React_2.MouseEvent<HTMLButtonElement | HTMLAnchorElement>> | EventData<'focus', React_2.FocusEvent> | EventData<'drag', PointerEvent | MouseEvent> | EventData<'autoplay', Event>) & {
index: number;
};

Expand Down
6 changes: 3 additions & 3 deletions packages/react-components/react-carousel/library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function useCarousel_unstable(props: CarouselProps, ref: React.Ref<HTMLDi
containScroll: whitespace ? false : 'keepSnaps',
motion,
onDragIndexChange: onActiveIndexChange,
onAutoplayIndexChange: onActiveIndexChange,
});

const selectPageByElement: CarouselContextValue['selectPageByElement'] = useEventCallback((event, element, jump) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<HTMLButtonElement & HTMLAnchorElement>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type CarouselIndexChangeData = (
| EventData<'click', React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>>
| EventData<'focus', React.FocusEvent>
| EventData<'drag', PointerEvent | MouseEvent>
| EventData<'autoplay', Event>
) & {
/**
* The index to be set after event has occurred.
Expand All @@ -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<HTMLDivElement>;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -16,7 +16,54 @@ export const useCarouselViewport_unstable = (
props: CarouselViewportProps,
ref: React.Ref<HTMLDivElement>,
): 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: {
Expand All @@ -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' },
),
Expand Down
Loading

0 comments on commit 2fc8151

Please sign in to comment.