UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

161 lines (126 loc) 4.14 kB
import * as React from "react"; import * as R from "ramda"; import { focusManager } from "./FocusManager"; import { NON_FOCUSABLE_COMPONENTS } from "./const"; import { useFocusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable/index.android"; import { toBooleanWithDefaultTrue } from "../booleanUtils"; import { isNilOrEmpty } from "../reactUtils/helpers"; import { isAndroidTVPlatform } from "../reactUtils"; import { noop } from "../functionUtils"; const EMPTY_GROUP_COMPONENT = "empty_group_component"; export function useFocusManager() { return focusManager; } export function useInitialFocus( focused: boolean, initialRef?: FocusManager.TouchableReactRef | string, options: { refsList?: Array<FocusManager.TouchableReactRef> | Array<string>; withStateMemory?: boolean; initialFocusDirection?: FocusManager.Android.FocusNavigationDirections; initialScrollIndex?: number; } = {} ): ((index: number) => void) | (() => void) { const { withStateMemory, refsList } = options; const currentlyFocusedIndex = React.useRef<number>( options?.initialScrollIndex || 0 ); const setCurrentlyFocusedIndex = React.useCallback( (index: number) => (currentlyFocusedIndex.current = index), [] ); const focusManager = useFocusManager(); const nextFocus = (withStateMemory && refsList?.[currentlyFocusedIndex.current]) || initialRef; React.useEffect(() => { if (focused && nextFocus) { focusManager.setFocus(nextFocus, { initialFocusDirection: options?.initialFocusDirection, }); } }, [focused, initialRef]); if (withStateMemory) { return setCurrentlyFocusedIndex; } return noop; } export function useFocusRefs() { const { current } = React.useRef([]); return current; } export function useFocusEffect(onFocus, focusWatcher) { const [focused] = focusWatcher; React.useLayoutEffect(() => { let onBlur = () => null; if (focused) { onBlur = onFocus?.(); } else { onBlur?.(); } }, focusWatcher); } export function shouldShowEmptyGroup(component) { return ( component?.ui_components?.filter( ({ component_type }) => component_type === EMPTY_GROUP_COMPONENT )?.length > 0 ); } export const isFocusable = (component: ZappEntry | ZappUIComponent | any) => R.tryCatch( R.compose( R.not, R.includes(R.__, NON_FOCUSABLE_COMPONENTS), R.prop("component_type") ), R.T )(component); type Props = { component: ZappUIComponent; zappPipesData?: ZappPipesData; } & Record<string, any>; export const useIsFocusable = (props: Props): void => { const { component, zappPipesData } = props; const { setIsFocusable } = useFocusable(); const componentCellsFocusable = toBooleanWithDefaultTrue( component?.rules?.component_cells_focusable ); const hideIfDataEmpty = toBooleanWithDefaultTrue( component.rules.hide_component_if_data_is_empty ); const isLoadingData = !!zappPipesData?.loading; const hasNoData = isNilOrEmpty(zappPipesData?.data) || isNilOrEmpty(zappPipesData?.data?.entry); const hasNoUIComponents = isNilOrEmpty(component?.ui_components); const hasDataError = !!zappPipesData?.error; const hasEmptyGroup = shouldShowEmptyGroup(component); const isGallery = component?.component_type === "gallery-qb"; const isGroup = component?.component_type === "group-qb"; const isHiddenDueToEmptyData = !isLoadingData && hasNoData && hideIfDataEmpty && !hasEmptyGroup; const isEmptyGroup = isGroup && hasNoUIComponents; const defaultFocusableLogic = componentCellsFocusable && (!hasNoData || !hideIfDataEmpty) && !isEmptyGroup; React.useEffect(() => { if (isAndroidTVPlatform()) { let isFocusableNew = false; if (isGallery) { isFocusableNew = true; } else if (isFocusable(component)) { isFocusableNew = defaultFocusableLogic; } setIsFocusable(isFocusableNew); } }, [ defaultFocusableLogic, isHiddenDueToEmptyData, isGallery, setIsFocusable, hasDataError, hasEmptyGroup, ]); };