Files
headlessui/packages/@headlessui-react/src/utils/calculate-active-index.ts
T
Robin Malfait fdd2629795 Improve overal codebase, use modern tech like esbuild and TypeScript 4! (#1055)
* 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
2022-01-27 17:07:38 +01:00

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
}