fdd2629795
* use esbuild for React instead of tsdx * remove tsdx from Vue * use consistent names * add jest and prettier * update scripts * ignore some folders for prettier * run lint script instead of tsdx lint * run prettier en-masse This has a few changes because of the new prettier version. * bump typescript to latest version * make typescript happy * cleanup playground package.json * make esbuild a dev dependency * make scripts consistent * fix husky hooks * add dedicated watch script * add `yarn playground-react` and `yarn react-playground` (alias) This will make sure to run a watcher for the actual @headlessui/react package, and start a development server in the playground-react package. * ignore formatting in the .next folder * run prettier on playground-react package * setup playground-vue Still not 100% working, but getting there! * add playground aliases in @headlessui/vue and @headlessui/react This allows you to run `yarn react playground` or `yarn vue playground` from the root. * add `clean` script * move examples folder in playground-vue to root * ensure new lines for consistency in scripts * fix typescript issue * fix typescript issues in playgrounds * make sure to run prettier on everything it can * run prettier on all files * improve error output If you minify the code, then it could happen that the errors are a bit obscure. This will hardcode the component name to improve errors. * add the `prettier-plugin-tailwindcss` plugin, party! * update changelog
85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
function assertNever(x: never): never {
|
|
throw new Error('Unexpected object: ' + x)
|
|
}
|
|
|
|
export enum Focus {
|
|
/** Focus the first non-disabled item. */
|
|
First,
|
|
|
|
/** Focus the previous non-disabled item. */
|
|
Previous,
|
|
|
|
/** Focus the next non-disabled item. */
|
|
Next,
|
|
|
|
/** Focus the last non-disabled item. */
|
|
Last,
|
|
|
|
/** Focus a specific item based on the `id` of the item. */
|
|
Specific,
|
|
|
|
/** Focus no items at all. */
|
|
Nothing,
|
|
}
|
|
|
|
export function calculateActiveIndex<TItem>(
|
|
action: { focus: Focus.Specific; id: string } | { focus: Exclude<Focus, Focus.Specific> },
|
|
resolvers: {
|
|
resolveItems(): TItem[]
|
|
resolveActiveIndex(): number | null
|
|
resolveId(item: TItem): string
|
|
resolveDisabled(item: TItem): boolean
|
|
}
|
|
) {
|
|
let items = resolvers.resolveItems()
|
|
if (items.length <= 0) return null
|
|
|
|
let currentActiveIndex = resolvers.resolveActiveIndex()
|
|
let activeIndex = currentActiveIndex ?? -1
|
|
|
|
let nextActiveIndex = (() => {
|
|
switch (action.focus) {
|
|
case Focus.First:
|
|
return items.findIndex((item) => !resolvers.resolveDisabled(item))
|
|
|
|
case Focus.Previous: {
|
|
let idx = items
|
|
.slice()
|
|
.reverse()
|
|
.findIndex((item, idx, all) => {
|
|
if (activeIndex !== -1 && all.length - idx - 1 >= activeIndex) return false
|
|
return !resolvers.resolveDisabled(item)
|
|
})
|
|
if (idx === -1) return idx
|
|
return items.length - 1 - idx
|
|
}
|
|
|
|
case Focus.Next:
|
|
return items.findIndex((item, idx) => {
|
|
if (idx <= activeIndex) return false
|
|
return !resolvers.resolveDisabled(item)
|
|
})
|
|
|
|
case Focus.Last: {
|
|
let idx = items
|
|
.slice()
|
|
.reverse()
|
|
.findIndex((item) => !resolvers.resolveDisabled(item))
|
|
if (idx === -1) return idx
|
|
return items.length - 1 - idx
|
|
}
|
|
|
|
case Focus.Specific:
|
|
return items.findIndex((item) => resolvers.resolveId(item) === action.id)
|
|
|
|
case Focus.Nothing:
|
|
return null
|
|
|
|
default:
|
|
assertNever(action)
|
|
}
|
|
})()
|
|
|
|
return nextActiveIndex === -1 ? currentActiveIndex : nextActiveIndex
|
|
}
|