// Copyright 2025 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import type { FC, ReactNode, RefCallback } from 'react'; import { cloneElement as _cloneElement, useCallback, useRef, useState } from '@lynx-js/react'; import type { SnapshotInstance } from '@lynx-js/react/internal'; import { cloneElement as _cloneElementMainThread } from '@lynx-js/react/lepus'; export interface DeferredListItemProps { defer?: boolean | { unmountRecycled?: boolean }; renderListItem: (children: ReactNode | undefined) => JSX.Element; renderChildren: () => ReactNode; } export const DeferredListItem: FC = ({ defer, renderListItem, renderChildren }) => { const __cloneElement = typeof __MAIN_THREAD__ !== 'undefined' && __MAIN_THREAD__ ? _cloneElementMainThread : _cloneElement; const initialDeferRef = useRef(defer); const prevDeferRef = useRef(defer); const [isReady, setIsReady] = useState(!defer); const onGetSnapshotInstance = useCallback>((ctx) => { 'background only'; ctx!.__extraProps ??= {}; // hack: preact ignore function property on dom ctx!.__extraProps['onComponentAtIndex'] = () => { setIsReady(true); }; ctx!.__extraProps['onRecycleComponent'] = () => { if (defer && typeof defer === 'object' && defer.unmountRecycled) { // unmount the component when recycled setIsReady(false); } }; return () => { delete ctx!.__extraProps!['onComponentAtIndex']; delete ctx!.__extraProps!['onRecycleComponent']; }; }, []); if (typeof __BACKGROUND__ !== 'undefined' && __BACKGROUND__) { if (prevDeferRef.current && !defer) { setIsReady(true); } prevDeferRef.current = defer; } return initialDeferRef.current ? __cloneElement(renderListItem(isReady ? renderChildren() : null), { isReady: +isReady, // hack: preact specially handled boolean props ref: onGetSnapshotInstance, }) : renderListItem(renderChildren()); };