{"version":3,"file":"ToastViewport.cjs","sources":["../../src/Toast/ToastViewport.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { ComponentPublicInstance } from 'vue'\nimport type { PrimitiveProps } from '@/Primitive'\nimport { useCollection } from '@/Collection'\nimport { getActiveElement, useForwardExpose } from '@/shared'\n\nexport interface ToastViewportProps extends PrimitiveProps {\n  /**\n   * The keys to use as the keyboard shortcut that will move focus to the toast viewport.\n   * @defaultValue ['F8']\n   */\n  hotkey?: string[]\n  /**\n   * An author-localized label for the toast viewport to provide context for screen reader users\n   * when navigating page landmarks. The available `{hotkey}` placeholder will be replaced for you.\n   * Alternatively, you can pass in a custom function to generate the label.\n   * @defaultValue 'Notifications ({hotkey})'\n   */\n  label?: string | ((hotkey: string) => string)\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRefs, watchEffect } from 'vue'\nimport { Primitive } from '@/Primitive'\nimport { injectToastProviderContext } from './ToastProvider.vue'\nimport { onKeyStroke, unrefElement } from '@vueuse/core'\nimport FocusProxy from './FocusProxy.vue'\nimport { focusFirst, getTabbableCandidates } from '@/FocusScope/utils'\nimport { VIEWPORT_PAUSE, VIEWPORT_RESUME } from './utils'\nimport { DismissableLayerBranch } from '@/DismissableLayer'\n\ndefineOptions({\n  inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<ToastViewportProps>(), {\n  hotkey: () => ['F8'], // from VIEWPORT_DEFAULT_HOTKEY\n  label: 'Notifications ({hotkey})',\n  as: 'ol',\n})\nconst { hotkey, label } = toRefs(props)\n\nconst { forwardRef, currentElement } = useForwardExpose()\nconst { CollectionSlot, getItems } = useCollection()\nconst providerContext = injectToastProviderContext()\nconst hasToasts = computed(() => providerContext.toastCount.value > 0)\nconst headFocusProxyRef = ref<HTMLElement>()\nconst tailFocusProxyRef = ref<HTMLElement>()\n\nconst hotkeyMessage = computed(() => hotkey.value.join('+').replace(/Key/g, '').replace(/Digit/g, ''))\n\nonKeyStroke(hotkey.value, () => {\n  currentElement.value.focus()\n})\n\nonMounted(() => {\n  providerContext.onViewportChange(currentElement.value)\n})\n\nwatchEffect((cleanupFn) => {\n  const viewport = currentElement.value\n  if (hasToasts.value && viewport) {\n    const handlePause = () => {\n      if (!providerContext.isClosePausedRef.value) {\n        const pauseEvent = new CustomEvent(VIEWPORT_PAUSE)\n        viewport.dispatchEvent(pauseEvent)\n        providerContext.isClosePausedRef.value = true\n      }\n    }\n\n    const handleResume = () => {\n      if (providerContext.isClosePausedRef.value) {\n        const resumeEvent = new CustomEvent(VIEWPORT_RESUME)\n        viewport.dispatchEvent(resumeEvent)\n        providerContext.isClosePausedRef.value = false\n      }\n    }\n\n    const handleFocusOutResume = (event: FocusEvent) => {\n      const isFocusMovingOutside = !viewport.contains(event.relatedTarget as HTMLElement)\n      if (isFocusMovingOutside)\n        handleResume()\n    }\n\n    const handlePointerLeaveResume = () => {\n      const isFocusInside = viewport.contains(getActiveElement())\n      if (!isFocusInside)\n        handleResume()\n    }\n\n    // We programmatically manage tabbing as we are unable to influence\n    // the source order with portals, this allows us to reverse the\n    // tab order so that it runs from most recent toast to least\n    const handleKeyDown = (event: KeyboardEvent) => {\n      const isMetaKey = event.altKey || event.ctrlKey || event.metaKey\n      const isTabKey = event.key === 'Tab' && !isMetaKey\n\n      if (isTabKey) {\n        const focusedElement = getActiveElement()\n        const isTabbingBackwards = event.shiftKey\n        const targetIsViewport = event.target === viewport\n\n        // If we're back tabbing after jumping to the viewport then we simply\n        // proxy focus out to the preceding document\n        if (targetIsViewport && isTabbingBackwards) {\n          headFocusProxyRef.value?.focus()\n          return\n        }\n\n        const tabbingDirection = isTabbingBackwards ? 'backwards' : 'forwards'\n        const sortedCandidates = getSortedTabbableCandidates({ tabbingDirection })\n        const index = sortedCandidates.findIndex(candidate => candidate === focusedElement)\n        if (focusFirst(sortedCandidates.slice(index + 1))) {\n          event.preventDefault()\n        }\n        else {\n          // If we can't focus that means we're at the edges so we\n          // proxy to the corresponding exit point and let the browser handle\n          // tab/shift+tab keypress and implicitly pass focus to the next valid element in the document\n          isTabbingBackwards\n            ? headFocusProxyRef.value?.focus()\n            : tailFocusProxyRef.value?.focus()\n        }\n      }\n    }\n\n    viewport.addEventListener('focusin', handlePause)\n    viewport.addEventListener('focusout', handleFocusOutResume)\n    viewport.addEventListener('pointermove', handlePause)\n    viewport.addEventListener('pointerleave', handlePointerLeaveResume)\n    viewport.addEventListener('keydown', handleKeyDown)\n    window.addEventListener('blur', handlePause)\n    window.addEventListener('focus', handleResume)\n\n    cleanupFn(() => {\n      viewport.removeEventListener('focusin', handlePause)\n      viewport.removeEventListener('focusout', handleFocusOutResume)\n      viewport.removeEventListener('pointermove', handlePause)\n      viewport.removeEventListener('pointerleave', handlePointerLeaveResume)\n      viewport.removeEventListener('keydown', handleKeyDown)\n      window.removeEventListener('blur', handlePause)\n      window.removeEventListener('focus', handleResume)\n    })\n  }\n})\n\nfunction getSortedTabbableCandidates({ tabbingDirection }: { tabbingDirection: 'forwards' | 'backwards' }) {\n  const toastItems = getItems().map(i => i.ref)\n  const tabbableCandidates = toastItems.map((toastNode) => {\n    const toastTabbableCandidates = [toastNode, ...getTabbableCandidates(toastNode)]\n    return tabbingDirection === 'forwards'\n      ? toastTabbableCandidates\n      : toastTabbableCandidates.reverse()\n  })\n  return (\n    tabbingDirection === 'forwards' ? tabbableCandidates.reverse() : tabbableCandidates\n  ).flat()\n}\n</script>\n\n<template>\n  <DismissableLayerBranch\n    role=\"region\"\n    :aria-label=\"typeof label === 'string' ? label.replace('{hotkey}', hotkeyMessage) : label(hotkeyMessage)\"\n    tabindex=\"-1\"\n    :style=\"{\n      // incase list has size when empty (e.g. padding), we remove pointer events so\n      // it doesn't prevent interactions with page elements that it overlays\n      pointerEvents: hasToasts ? undefined : 'none',\n    }\"\n  >\n    <FocusProxy\n      v-if=\"hasToasts\"\n      :ref=\"(node: ComponentPublicInstance) => {\n        headFocusProxyRef = unrefElement(node) as HTMLElement\n        return undefined\n      }\"\n      @focus-from-outside-viewport=\"() => {\n        const tabbableCandidates = getSortedTabbableCandidates({\n          tabbingDirection: 'forwards',\n        })\n        focusFirst(tabbableCandidates)\n      }\"\n    />\n    <CollectionSlot>\n      <Primitive\n        :ref=\"forwardRef\"\n        tabindex=\"-1\"\n        :as=\"as\"\n        :as-child=\"asChild\"\n        v-bind=\"$attrs\"\n      >\n        <slot />\n      </Primitive>\n    </CollectionSlot>\n    <FocusProxy\n      v-if=\"hasToasts\"\n      :ref=\"(node: ComponentPublicInstance) => {\n        tailFocusProxyRef = unrefElement(node) as HTMLElement\n        return undefined\n      }\"\n      @focus-from-outside-viewport=\"() => {\n        const tabbableCandidates = getSortedTabbableCandidates({\n          tabbingDirection: 'backwards',\n        })\n        focusFirst(tabbableCandidates)\n      }\"\n    />\n  </DismissableLayerBranch>\n</template>\n"],"names":["toRefs","useForwardExpose","useCollection","injectToastProviderContext","computed","ref","onKeyStroke","onMounted","watchEffect","VIEWPORT_PAUSE","VIEWPORT_RESUME","getActiveElement","focusFirst","getTabbableCandidates"],"mappings":"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"}