reka-ui
Version:
Vue port for Radix UI Primitives.
1 lines • 7.33 kB
Source Map (JSON)
{"version":3,"file":"RovingFocusGroup.cjs","sources":["../../src/RovingFocus/RovingFocusGroup.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { Ref } from 'vue'\nimport type { PrimitiveProps } from '@/Primitive'\nimport { createContext, useDirection } from '@/shared'\nimport type {\n Direction,\n Orientation,\n} from './utils'\n\nexport interface RovingFocusGroupProps extends PrimitiveProps {\n /**\n * The orientation of the group.\n * Mainly so arrow navigation is done accordingly (left & right vs. up & down)\n */\n orientation?: Orientation\n /**\n * The direction of navigation between items.\n */\n dir?: Direction\n /**\n * Whether keyboard navigation should loop around\n * @defaultValue false\n */\n loop?: boolean\n /** The controlled value of the current stop item. Can be binded as `v-model`. */\n currentTabStopId?: string | null\n /**\n * The value of the current stop item.\n *\n * Use when you do not need to control the state of the stop item.\n */\n defaultCurrentTabStopId?: string\n /**\n * When `true`, will prevent scrolling to the focus item when focused.\n */\n preventScrollOnEntryFocus?: boolean\n}\n\nexport type RovingFocusGroupEmits = {\n 'entryFocus': [event: Event]\n 'update:currentTabStopId': [value: string | null | undefined]\n}\n\ninterface RovingContext {\n orientation: Ref<Orientation | undefined>\n dir: Ref<Direction>\n loop: Ref<boolean>\n currentTabStopId: Ref<string | null | undefined>\n onItemFocus: (tabStopId: string) => void\n onItemShiftTab: () => void\n onFocusableItemAdd: () => void\n onFocusableItemRemove: () => void\n}\n\nexport const [injectRovingFocusGroupContext, provideRovingFocusGroupContext]\n = createContext<RovingContext>('RovingFocusGroup')\n</script>\n\n<script setup lang=\"ts\">\nimport { ref, toRefs } from 'vue'\nimport { useVModel } from '@vueuse/core'\nimport { Primitive } from '@/Primitive'\nimport { ENTRY_FOCUS, EVENT_OPTIONS, focusFirst } from './utils'\nimport { useCollection } from '@/Collection'\n\nconst props = withDefaults(defineProps<RovingFocusGroupProps>(), {\n loop: false,\n orientation: undefined,\n preventScrollOnEntryFocus: false,\n})\nconst emits = defineEmits<RovingFocusGroupEmits>()\n\nconst { loop, orientation, dir: propDir } = toRefs(props)\nconst dir = useDirection(propDir)\nconst currentTabStopId = useVModel(props, 'currentTabStopId', emits, {\n defaultValue: props.defaultCurrentTabStopId,\n passive: (props.currentTabStopId === undefined) as false,\n})\nconst isTabbingBackOut = ref(false)\nconst isClickFocus = ref(false)\nconst focusableItemsCount = ref(0)\n\nconst { getItems, CollectionSlot } = useCollection({ isProvider: true })\n\nfunction handleFocus(event: FocusEvent) {\n // We normally wouldn't need this check, because we already check\n // that the focus is on the current target and not bubbling to it.\n // We do this because Safari doesn't focus buttons when clicked, and\n // instead, the wrapper will get focused and not through a bubbling event.\n const isKeyboardFocus = !isClickFocus.value\n\n if (\n event.currentTarget\n && event.target === event.currentTarget\n && isKeyboardFocus\n && !isTabbingBackOut.value\n ) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS)\n event.currentTarget.dispatchEvent(entryFocusEvent)\n emits('entryFocus', entryFocusEvent)\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = getItems().map(i => i.ref).filter(i => i.dataset.disabled !== '')\n const activeItem = items.find(item => item.getAttribute('data-active') === '')\n const currentItem = items.find(\n item => item.id === currentTabStopId.value,\n )\n const candidateItems = [activeItem, currentItem, ...items].filter(\n Boolean,\n ) as typeof items\n focusFirst(candidateItems, props.preventScrollOnEntryFocus)\n }\n }\n isClickFocus.value = false\n}\n\nfunction handleMouseUp() {\n // reset `isClickFocus` after 1 tick because handleFocus might not triggered due to focused element\n setTimeout(() => {\n isClickFocus.value = false\n }, 1)\n}\n\ndefineExpose({\n getItems,\n})\n\nprovideRovingFocusGroupContext({\n loop,\n dir,\n orientation,\n currentTabStopId,\n onItemFocus: (tabStopId) => {\n currentTabStopId.value = tabStopId\n },\n onItemShiftTab: () => {\n isTabbingBackOut.value = true\n },\n onFocusableItemAdd: () => {\n focusableItemsCount.value++\n },\n onFocusableItemRemove: () => {\n focusableItemsCount.value--\n },\n})\n</script>\n\n<template>\n <CollectionSlot>\n <Primitive\n :tabindex=\"isTabbingBackOut || focusableItemsCount === 0 ? -1 : 0\"\n :data-orientation=\"orientation\"\n :as=\"as\"\n :as-child=\"asChild\"\n :dir=\"dir\"\n style=\"outline: none\"\n @mousedown=\"isClickFocus = true\"\n @mouseup=\"handleMouseUp\"\n @focus=\"handleFocus\"\n @blur=\"isTabbingBackOut = false\"\n >\n <slot />\n </Primitive>\n </CollectionSlot>\n</template>\n"],"names":["createContext","toRefs","useDirection","useVModel","ref","useCollection","ENTRY_FOCUS","EVENT_OPTIONS","focusFirst"],"mappings":"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"}