reka-ui
Version:
Vue port for Radix UI Primitives.
1 lines • 14.7 kB
Source Map (JSON)
{"version":3,"file":"ToastRootImpl.cjs","sources":["../../src/Toast/ToastRootImpl.vue"],"sourcesContent":["<script lang=\"ts\">\nimport { isClient } from '@vueuse/shared'\nimport type { PrimitiveProps } from '@/Primitive'\nimport type { SwipeEvent } from './utils'\nimport { createContext, getActiveElement, useForwardExpose } from '@/shared'\nimport { useCollection } from '@/Collection'\n\nexport type ToastRootImplEmits = {\n close: []\n /** Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. */\n escapeKeyDown: [event: KeyboardEvent]\n /** Event handler called when the dismiss timer is paused. This occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. */\n pause: []\n /** Event handler called when the dismiss timer is resumed. This occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. */\n resume: []\n /** Event handler called when starting a swipe interaction. It can be prevented by calling `event.preventDefault`. */\n swipeStart: [event: SwipeEvent]\n /** Event handler called during a swipe interaction. It can be prevented by calling `event.preventDefault`. */\n swipeMove: [event: SwipeEvent]\n swipeCancel: [event: SwipeEvent]\n /** Event handler called at the end of a swipe interaction. It can be prevented by calling `event.preventDefault`. */\n swipeEnd: [event: SwipeEvent]\n}\n\nexport interface ToastRootImplProps extends PrimitiveProps {\n /**\n * Control the sensitivity of the toast for accessibility purposes.\n *\n * For toasts that are the result of a user action, choose `foreground`. Toasts generated from background tasks should use `background`.\n */\n type?: 'foreground' | 'background'\n /**\n * The controlled open state of the dialog. Can be bind as `v-model:open`.\n */\n open?: boolean\n /**\n * Time in milliseconds that toast should remain visible for. Overrides value\n * given to `ToastProvider`.\n */\n duration?: number\n}\n\nexport const [injectToastRootContext, provideToastRootContext]\n = createContext<{ onClose: () => void }>('ToastRoot')\n</script>\n\n<script setup lang=\"ts\">\nimport { Primitive } from '@/Primitive'\nimport { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'\nimport { injectToastProviderContext } from './ToastProvider.vue'\nimport { TOAST_SWIPE_CANCEL, TOAST_SWIPE_END, TOAST_SWIPE_MOVE, TOAST_SWIPE_START, VIEWPORT_PAUSE, VIEWPORT_RESUME, getAnnounceTextContent, handleAndDispatchCustomEvent, isDeltaInDirection } from './utils'\nimport { onKeyStroke, useRafFn } from '@vueuse/core'\nimport ToastAnnounce from './ToastAnnounce.vue'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<ToastRootImplProps>(), {\n open: false,\n as: 'li',\n})\n\nconst emits = defineEmits<ToastRootImplEmits>()\n\nconst { forwardRef, currentElement } = useForwardExpose()\nconst { CollectionItem } = useCollection()\n\nconst providerContext = injectToastProviderContext()\nconst pointerStartRef = ref<{ x: number, y: number } | null>(null)\nconst swipeDeltaRef = ref<{ x: number, y: number } | null>(null)\nconst duration = computed(\n () => typeof props.duration === 'number'\n ? props.duration\n : providerContext.duration.value,\n)\n\nconst closeTimerStartTimeRef = ref(0)\nconst closeTimerRemainingTimeRef = ref(duration.value)\nconst closeTimerRef = ref(0)\nconst remainingTime = ref(duration.value)\n\nconst remainingRaf = useRafFn(() => {\n const elapsedTime = new Date().getTime() - closeTimerStartTimeRef.value\n remainingTime.value = Math.max(closeTimerRemainingTimeRef.value - elapsedTime, 0)\n}, { fpsLimit: 60 })\n\nfunction startTimer(duration: number) {\n if (duration <= 0 || duration === Number.POSITIVE_INFINITY)\n return\n // startTimer is used inside a watch with immediate set to true.\n // This results in code execution during SSR.\n // Ensure this code only runs in a browser environment\n if (!isClient)\n return\n window.clearTimeout(closeTimerRef.value)\n closeTimerStartTimeRef.value = new Date().getTime()\n closeTimerRef.value = window.setTimeout(handleClose, duration)\n}\n\nfunction handleClose(event?: PointerEvent) {\n const isNonPointerEvent = event?.pointerType === ''\n\n // reka: update to only perform focus when user focus via keyboard\n // focus viewport if focus is within toast to read the remaining toast\n // count to SR users and ensure focus isn't lost\n const isFocusInToast = currentElement.value?.contains(getActiveElement())\n if (isFocusInToast && isNonPointerEvent)\n providerContext.viewport.value?.focus()\n\n if (isNonPointerEvent) {\n // when manually close the toast, we reset isClosePausedRef\n providerContext.isClosePausedRef.value = false\n }\n\n emits('close')\n}\n\nconst announceTextContent = computed(() => currentElement.value ? getAnnounceTextContent(currentElement.value) : null)\n\nif (props.type && !['foreground', 'background'].includes(props.type)) {\n const error = 'Invalid prop `type` supplied to `Toast`. Expected `foreground | background`.'\n throw new Error(error)\n}\n\nwatchEffect((cleanupFn) => {\n const viewport = providerContext.viewport.value\n if (viewport) {\n const handleResume = () => {\n startTimer(closeTimerRemainingTimeRef.value)\n remainingRaf.resume()\n emits('resume')\n }\n const handlePause = () => {\n const elapsedTime = new Date().getTime() - closeTimerStartTimeRef.value\n closeTimerRemainingTimeRef.value = closeTimerRemainingTimeRef.value - elapsedTime\n window.clearTimeout(closeTimerRef.value)\n remainingRaf.pause()\n emits('pause')\n }\n viewport.addEventListener(VIEWPORT_PAUSE, handlePause)\n viewport.addEventListener(VIEWPORT_RESUME, handleResume)\n return () => {\n viewport.removeEventListener(VIEWPORT_PAUSE, handlePause)\n viewport.removeEventListener(VIEWPORT_RESUME, handleResume)\n }\n }\n})\n\n// start timer when toast opens or duration changes.\n// we include `open` in deps because closed !== unmounted when animating\n// so it could reopen before being completely unmounted\nwatch(() => [props.open, duration.value], () => {\n // Reset the timer when the toast is rerendered with the new duration\n closeTimerRemainingTimeRef.value = duration.value\n\n if (props.open && !providerContext.isClosePausedRef.value)\n startTimer(duration.value)\n}, { immediate: true })\n\nonKeyStroke('Escape', (event) => {\n emits('escapeKeyDown', event)\n if (!event.defaultPrevented) {\n providerContext.isFocusedToastEscapeKeyDownRef.value = true\n handleClose()\n }\n})\n\nonMounted(() => {\n providerContext.onToastAdd()\n})\nonUnmounted(() => {\n providerContext.onToastRemove()\n})\n\nprovideToastRootContext({ onClose: handleClose })\n</script>\n\n<template>\n <ToastAnnounce\n v-if=\"announceTextContent\"\n role=\"alert\"\n :aria-live=\"type === 'foreground' ? 'assertive' : 'polite'\"\n aria-atomic=\"true\"\n >\n {{ announceTextContent }}\n </ToastAnnounce>\n\n <Teleport\n v-if=\"providerContext.viewport.value\"\n :to=\"providerContext.viewport.value\"\n >\n <CollectionItem>\n <Primitive\n :ref=\"forwardRef\"\n role=\"alert\"\n aria-live=\"off\"\n aria-atomic=\"true\"\n tabindex=\"0\"\n v-bind=\"$attrs\"\n :as=\"as\"\n :as-child=\"asChild\"\n :data-state=\"open ? 'open' : 'closed'\"\n :data-swipe-direction=\"providerContext.swipeDirection.value\"\n :style=\"{ userSelect: 'none', touchAction: 'none' }\"\n @pointerdown.left=\"(event: PointerEvent) => {\n pointerStartRef = { x: event.clientX, y: event.clientY };\n }\"\n @pointermove=\"(event: PointerEvent) => {\n if (!pointerStartRef) return;\n const x = event.clientX - pointerStartRef.x;\n const y = event.clientY - pointerStartRef.y;\n const hasSwipeMoveStarted = Boolean(swipeDeltaRef);\n const isHorizontalSwipe = ['left', 'right'].includes(providerContext.swipeDirection.value);\n const clamp = ['left', 'up'].includes(providerContext.swipeDirection.value)\n ? Math.min\n : Math.max;\n const clampedX = isHorizontalSwipe ? clamp(0, x) : 0;\n const clampedY = !isHorizontalSwipe ? clamp(0, y) : 0;\n const moveStartBuffer = event.pointerType === 'touch' ? 10 : 2;\n const delta = { x: clampedX, y: clampedY };\n const eventDetail = { originalEvent: event, delta };\n if (hasSwipeMoveStarted) {\n swipeDeltaRef = delta;\n handleAndDispatchCustomEvent(TOAST_SWIPE_MOVE, (ev: SwipeEvent) => emits('swipeMove', ev), eventDetail);\n }\n else if (isDeltaInDirection(delta, providerContext.swipeDirection.value, moveStartBuffer)) {\n swipeDeltaRef = delta;\n handleAndDispatchCustomEvent(TOAST_SWIPE_START, (ev: SwipeEvent) => emits('swipeStart', ev), eventDetail);\n (event.target as HTMLElement).setPointerCapture(event.pointerId);\n }\n else if (Math.abs(x) > moveStartBuffer || Math.abs(y) > moveStartBuffer) {\n // User is swiping in wrong direction so we disable swipe gesture\n // for the current pointer down interaction\n pointerStartRef = null;\n }\n }\"\n @pointerup=\"(event: PointerEvent) => {\n const delta = swipeDeltaRef;\n const target = event.target as HTMLElement;\n if (target.hasPointerCapture(event.pointerId)) {\n target.releasePointerCapture(event.pointerId);\n }\n swipeDeltaRef = null;\n pointerStartRef = null;\n if (delta) {\n const toast = event.currentTarget;\n const eventDetail = { originalEvent: event, delta };\n if (\n isDeltaInDirection(delta, providerContext.swipeDirection.value, providerContext.swipeThreshold.value)\n ) {\n handleAndDispatchCustomEvent(TOAST_SWIPE_END, (ev: SwipeEvent) => emits('swipeEnd', ev), eventDetail);\n }\n else {\n handleAndDispatchCustomEvent(TOAST_SWIPE_CANCEL, (ev: SwipeEvent) => emits('swipeCancel', ev), eventDetail);\n }\n // Prevent click event from triggering on items within the toast when\n // pointer up is part of a swipe gesture\n toast?.addEventListener('click', (event) => event.preventDefault(), {\n once: true,\n });\n }\n }\"\n >\n <slot\n :remaining=\"remainingTime\"\n :duration=\"duration\"\n />\n </Primitive>\n </CollectionItem>\n </Teleport>\n</template>\n"],"names":["createContext","useForwardExpose","useCollection","injectToastProviderContext","ref","computed","useRafFn","duration","isClient","getActiveElement","getAnnounceTextContent","watchEffect","VIEWPORT_PAUSE","VIEWPORT_RESUME","watch","onKeyStroke","onMounted","onUnmounted"],"mappings":"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"}