diff --git a/packages/radix-vue/src/RovingFocus/RovingFocusGroup.vue b/packages/radix-vue/src/RovingFocus/RovingFocusGroup.vue new file mode 100644 index 000000000..2c54eea69 --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/RovingFocusGroup.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/packages/radix-vue/src/RovingFocus/RovingFocusItem.vue b/packages/radix-vue/src/RovingFocus/RovingFocusItem.vue new file mode 100644 index 000000000..a14da79d1 --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/RovingFocusItem.vue @@ -0,0 +1,95 @@ + + + diff --git a/packages/radix-vue/src/RovingFocus/index.ts b/packages/radix-vue/src/RovingFocus/index.ts new file mode 100644 index 000000000..6272f1381 --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/index.ts @@ -0,0 +1,9 @@ +export { + default as RovingFocusGroup, + type RovingFocusGroupProps, + type RovingFocusGroupEmits, +} from "./RovingFocusGroup.vue"; +export { + default as RovingFocusItem, + type RovingFocusItemProps, +} from "./RovingFocusItem.vue"; diff --git a/packages/radix-vue/src/RovingFocus/story/RovingFocusBasic.story.vue b/packages/radix-vue/src/RovingFocus/story/RovingFocusBasic.story.vue new file mode 100644 index 000000000..8346b65ba --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/story/RovingFocusBasic.story.vue @@ -0,0 +1,70 @@ + + + diff --git a/packages/radix-vue/src/RovingFocus/story/_Button.vue b/packages/radix-vue/src/RovingFocus/story/_Button.vue new file mode 100644 index 000000000..afeea5d55 --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/story/_Button.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/radix-vue/src/RovingFocus/story/_ButtonGroup.vue b/packages/radix-vue/src/RovingFocus/story/_ButtonGroup.vue new file mode 100644 index 000000000..864517de9 --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/story/_ButtonGroup.vue @@ -0,0 +1,26 @@ + + + diff --git a/packages/radix-vue/src/RovingFocus/utils.ts b/packages/radix-vue/src/RovingFocus/utils.ts new file mode 100644 index 000000000..a6726f2ca --- /dev/null +++ b/packages/radix-vue/src/RovingFocus/utils.ts @@ -0,0 +1,55 @@ +export type Orientation = "horizontal" | "vertical"; +export type Direction = "ltr" | "rtl"; + +export const ENTRY_FOCUS = "rovingFocusGroup.onEntryFocus"; +export const EVENT_OPTIONS = { bubbles: false, cancelable: true }; + +// prettier-ignore +export const MAP_KEY_TO_FOCUS_INTENT: Record = { + ArrowLeft: 'prev', ArrowUp: 'prev', + ArrowRight: 'next', ArrowDown: 'next', + PageUp: 'first', Home: 'first', + PageDown: 'last', End: 'last', +}; + +export function getDirectionAwareKey(key: string, dir?: Direction) { + if (dir !== "rtl") return key; + return key === "ArrowLeft" + ? "ArrowRight" + : key === "ArrowRight" + ? "ArrowLeft" + : key; +} + +type FocusIntent = "first" | "last" | "prev" | "next"; + +export function getFocusIntent( + event: KeyboardEvent, + orientation?: Orientation, + dir?: Direction +) { + const key = getDirectionAwareKey(event.key, dir); + if (orientation === "vertical" && ["ArrowLeft", "ArrowRight"].includes(key)) + return undefined; + if (orientation === "horizontal" && ["ArrowUp", "ArrowDown"].includes(key)) + return undefined; + return MAP_KEY_TO_FOCUS_INTENT[key]; +} + +export function focusFirst(candidates: HTMLElement[]) { + const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement; + for (const candidate of candidates) { + // if focus is already where we want to go, we don't want to keep going through the candidates + if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return; + candidate.focus(); + if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return; + } +} + +/** + * Wraps an array around itself at a given start index + * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']` + */ +export function wrapArray(array: T[], startIndex: number) { + return array.map((_, index) => array[(startIndex + index) % array.length]); +} diff --git a/packages/radix-vue/src/Toggle/ToggleRoot.vue b/packages/radix-vue/src/Toggle/ToggleRoot.vue index 1de3d88a5..6ab6fc967 100644 --- a/packages/radix-vue/src/Toggle/ToggleRoot.vue +++ b/packages/radix-vue/src/Toggle/ToggleRoot.vue @@ -55,28 +55,19 @@ const togglePressed = () => { const dataState = computed(() => { return pressed.value ? "on" : "off"; }); - -const handleKeydown = (e: KeyboardEvent) => { - if (e.key === "Enter") { - togglePressed(); - } -};