import { createStoreContext, react, useReaction } from '@o/use-store' import { selectDefined } from '@o/utils' import React from 'react' import { useOnUnmount } from './hooks/useOnUnmount' import { useVisibilityStore } from './Visibility' // TODO // this has two parts // 1. a nestable, focus managing store with order for moving between // 2. a simpler, value passing // part 1 is contained in the FocusManagerStore stuff, but on hold for now // part 2 is here, simpler for now // // part 1 // type FocusProps = { focused: boolean } class FocusStore { props: FocusProps = { focused: true, } get focused() { return this.props.focused } } const FocusContext = createStoreContext(FocusStore) // focus parents override children export const ProvideFocus = (props: FocusProps & { children: any }) => { const parent = FocusContext.useStore() const focused = selectDefined(props.focused, parent ? selectDefined(parent.focused, true) : true) return {props.children} } export function useFocus() { try { const store = FocusContext.useStore() return selectDefined(store.focused, true) } catch { // no focus store provided return true } } // // part 2 // export class FocusManagerStore { active = -1 ids = [] focusOrder = [] next() { this.active = Math.min(this.ids.length - 1, this.active + 1) } prev() { this.active = Math.max(-1, this.active - 1) } isFocused(id: string) { return this.focusOrder.findIndex(x => x === id) === this.active } // must be a unique id mountItem(id: string) { this.ids = [...this.ids, id] this.deferUpdate() } unmountItem(id: string) { const index = this.ids.indexOf(id) if (index >= 0) { this.ids = this.ids.splice(index, 1) } this.deferUpdate() } tm = 0 deferUpdate = () => { this.tm = Math.random() } update = react( () => this.tm, async (_, { sleep }) => { await sleep(200) this.setOrder() }, { lazy: true, }, ) setOrder() { this.focusOrder = this.getOrder() } // gets the order of things in the DOM getOrder() { if (!this.ids.length) { return [] } const selectors = this.ids.map(id => `#${id}`).join(', ') const items = Array.from(document.querySelectorAll(selectors)) return items.map((_, index) => { return this.ids[index] }) } } const context = createStoreContext(FocusManagerStore) export const useFocusManager = context.useStore export const useCreateFocusManager = context.useCreateStore export const ProvideFocusManager = context.Provider // for sub-items // attaches them as focsed when visibile export function useFocusableItem(id: string, forceVisible?: boolean) { const focusStore = useFocusManager() const visStore = useVisibilityStore() useReaction( () => selectDefined(forceVisible, visStore.visible), isVisible => { if (isVisible) { focusStore.mountItem(id) } else { focusStore.unmountItem(id) } }, ) useOnUnmount(() => { focusStore.unmountItem(id) }) return focusStore.isFocused(id) }