import { useIsomorphicLayoutEffect } from '@tamagui/constants' import type { GetProps, TamaguiElement } from '@tamagui/core' import { View, Text, styled, useEvent } from '@tamagui/core' import { Portal } from '@tamagui/portal' import { startTransition } from '@tamagui/start-transition' import { VisuallyHidden } from '@tamagui/visually-hidden' import * as React from 'react' import type { ScopedProps } from './ToastProvider' import { useToastProviderContext } from './ToastProvider' const ToastAnnounceExcludeFrame = styled(View, { name: 'ToastAnnounceExclude', }) type ToastAnnounceExcludeFrameProps = GetProps type ToastAnnounceExcludeExtraProps = { altText?: string } type ToastAnnounceExcludeProps = ToastAnnounceExcludeFrameProps & ToastAnnounceExcludeExtraProps const ToastAnnounceExclude = React.forwardRef( (props, forwardedRef) => { const { altText, ...announceExcludeProps } = props return ( ) } ) /* -----------------------------------------------------------------------------------------------*/ interface ToastAnnounceProps extends Omit, 'children'>, ScopedProps<{ children: string[] }> {} const ToastAnnounce: React.FC = ( props: ScopedProps ) => { const { scope, children, ...announceProps } = props const context = useToastProviderContext(scope) const [renderAnnounceText, setRenderAnnounceText] = React.useState(false) const [isAnnounced, setIsAnnounced] = React.useState(false) // render text content in the next frame to ensure toast is announced in NVDA useNextFrame(() => { startTransition(() => { setRenderAnnounceText(true) }) }) // cleanup after announcing React.useEffect(() => { const timer = setTimeout(() => setIsAnnounced(true), 1000) return () => clearTimeout(timer) }, []) return isAnnounced ? null : ( {renderAnnounceText && ( {context.label} {children} )} ) } /* -----------------------------------------------------------------------------------------------*/ function useNextFrame(callback = () => {}) { const fn = useEvent(callback) useIsomorphicLayoutEffect(() => { let raf1 = 0 let raf2 = 0 raf1 = requestAnimationFrame(() => { raf2 = requestAnimationFrame(fn) }) return () => { cancelAnimationFrame(raf1) cancelAnimationFrame(raf2) } }, [fn]) } export { ToastAnnounce, ToastAnnounceExclude, type ToastAnnounceExcludeProps, type ToastAnnounceProps, }