import * as R from "ramda"; import * as React from "react"; import { v4 as uuid } from "uuid"; import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation"; import { useTheme } from "@applicaster/zapp-react-native-utils/theme"; import { useScreenState } from "@applicaster/zapp-react-native-utils/screenState"; import { defaultComponents } from "./DefaultComponents"; import { defaultDataAdapter } from "./dataAdapter"; import { elementMapper, mapElementWithKey } from "./elementMapper"; import { getEntryState, hasFocusableInsideBuilder } from "./utils"; type Props = { item: ZappEntry; state: null | "selected" | "focused"; }; const defaultContainerStyle = { flex: 1 }; /** * Constructs a MasterCell with the provided options * @param {Object} options * @param {Object} options.components maps of React components to use when rendering the master cell * @param {Object} options.cellConfiguration master cell config coming from Zapp * @param {Function} options.dataAdapter optional dataAdapter to use instead of the default one. * See dataAdapter.js for more info * @param {containerStyles} options.containerStyle style to apply at the top level of the master cell * @returns {Function} Functional React Component to render the masterCell with the provided options */ type MasterCellBuilderOptions = { components?: typeof defaultComponents; dataAdapter?: typeof defaultDataAdapter; cellConfiguration: any; containerStyle: any; cellOptions?: { isRTL?: boolean; generateId?: () => string; skipButtons?: boolean; }; }; export function masterCellBuilder({ components = defaultComponents, cellConfiguration, dataAdapter = defaultDataAdapter, containerStyle = defaultContainerStyle, cellOptions = {}, }: MasterCellBuilderOptions) { const { generateId = uuid, skipButtons = false } = cellOptions; const componentsMap = R.merge(defaultComponents, components); const { View } = componentsMap; const elementsBuilder = dataAdapter(cellConfiguration); // This is very expensive operation // Keep calls to minimum const hasFocusableInside = hasFocusableInsideBuilder(elementsBuilder); /** * Functional React Component to use to render the master cell * @param {Object} props * @param {Object} props.item item to be rendered. an item is the result of the master cell configuration, * inflated with the data coming from the data source * @param {"selected" | "focused" | null} props.state state of the component */ function MasterCell({ item, state, ...otherProps }: Props) { const { screenData } = useRoute(); const theme = useTheme(); const screenId = screenData && "targetScreen" in screenData ? screenData?.targetScreen?.id : screenData?.id; const { screen } = useScreenState(screenId); const selectedState = screen?.selectedEntry; const entryIsSelected = React.useMemo( () => !!(selectedState?.id && selectedState?.id === item?.id), [selectedState?.id, item?.id] ); const elements = React.useMemo( () => elementsBuilder({ entry: item, state: getEntryState(state, entryIsSelected), allowDynamicColorsOutsideExtensions: theme?.allow_dynamic_colors_outside_extensions, }), [ state, item, entryIsSelected, theme?.allow_dynamic_colors_outside_extensions, ] // Assuming that item won't mutate ); const wrapperRef = React.useRef(null); const cellUUID = React.useMemo(generateId, [ generateId, screenId, item?.id, ]); const remapElements = mapElementWithKey( elementMapper(componentsMap, { ...otherProps, wrapperRef, cellUUID, skipButtons, hasFocusableInside, entry: item, }) ); return ( {elements.map(remapElements)} ); } const _MasterCell = React.memo(MasterCell); // @ts-ignore _MasterCell.hasFocusableInside = hasFocusableInside; return _MasterCell; }