// Copyright 2024 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 { render } from 'preact'; import { createContext, createElement } from 'preact/compat'; import { useState } from 'preact/hooks'; import type { Consumer, FC, ReactNode } from 'react'; import { createGlobalProps } from './core/globalProps.js'; import type { GlobalProps } from './core/globalProps.js'; import { useLynxGlobalEventListener } from './core/hooks/useLynxGlobalEventListener.js'; import { factory, withInitDataInState } from './core/initData.js'; import { __root } from './root.js'; import { profileEnd, profileStart } from './shared/profile.js'; import { LifecycleConstant } from './snapshot/lifecycle/constant.js'; import { onFirstScreenSyncReady } from './snapshot/lifecycle/event/firstScreenSync.js'; import { flushDelayedLifecycleEvents } from './snapshot/lynx/tt.js'; /** * The default root exported by `@lynx-js/react` for you to render a JSX * @public * * @group Types */ export interface Root { /** * Use this API to pass in your JSX to render * * @example * * ```ts * import { root } from "@lynx-js/react" * * function App() { * // Your app * return ... * } * * root.render(); * ``` * * @example * * ```tsx * import { root } from "@lynx-js/react" * * function App() { * // Your app * return ... * } * * if (__MAIN_THREAD__) { * root.render( * * * * ); * } else if (__BACKGROUND__) { * fetchData().then((data) => { * root.render( * * * * ); // You can render later after your data is ready * }) * } * ``` * * @public */ render: (jsx: ReactNode) => void; /** * {@inheritDoc Lynx.registerDataProcessors} * @deprecated use {@link Lynx.registerDataProcessors | lynx.registerDataProcessors} instead * @public */ registerDataProcessors: (dataProcessorDefinition: DataProcessorDefinition) => void; } /** * The default and only root of ReactLynx for you to render JSX * @example * ```ts * import { root } from "@lynx-js/react" * ``` * * @public * * @group Functions */ export const root: Root = { render: (jsx: ReactNode): void => { /* v8 ignore next 2 */ if (typeof __MAIN_THREAD__ !== 'undefined' && __MAIN_THREAD__) { __root.__jsx = jsx; } else { __root.__jsx = jsx; if (typeof __PROFILE__ !== 'undefined' && __PROFILE__) { profileStart('ReactLynx::renderBackground'); } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument render(jsx, __root as any); if (typeof __PROFILE__ !== 'undefined' && __PROFILE__) { profileEnd(); } if (__FIRST_SCREEN_SYNC_TIMING__ === 'jsReady') { // `jsReady` is a special case of the `manual` first-screen sync: the // framework marks ready automatically once the background is ready. lynx.getNativeApp().callLepusMethod(LifecycleConstant.firstScreenSyncReady, {}); } else { // `immediately` or `manual`: the first screen is synced without waiting // for the background, so the `firstScreen` message might have been // reached when `root.render()` is called asynchronously. flushDelayedLifecycleEvents(); } } }, /* v8 ignore next 3 */ registerDataProcessors: (dataProcessorDefinition: DataProcessorDefinition): void => { lynx.registerDataProcessors(dataProcessorDefinition); }, }; /** * Mark the first screen as ready to sync when `firstScreenSyncTiming` is `'manual'`. * * The main thread holds the UI control until this is called, so the handover timing to * the background thread (for hydration) is fully controlled by the user. It can be called * from both threads (a background-thread call is forwarded to the main thread), is a no-op * unless `firstScreenSyncTiming` is `'manual'`, and has no further effect once called. * * @example * * ```ts * import { markFirstScreenSyncReady } from "@lynx-js/react" * * markFirstScreenSyncReady(); * ``` * * @public * * @group Functions */ export function markFirstScreenSyncReady(): void { if (__FIRST_SCREEN_SYNC_TIMING__ !== 'manual') { return; } if (typeof __BACKGROUND__ !== 'undefined' && __BACKGROUND__) { // The sync happens on the main thread, forward the mark to it. lynx.getNativeApp().callLepusMethod(LifecycleConstant.firstScreenSyncReady, {}); return; } onFirstScreenSyncReady(); } const _InitData = /* @__PURE__ */ factory( { createContext, useState, createElement, useLynxGlobalEventListener, }, '__initData', 'onDataChanged', ); /** * The {@link https://react.dev/reference/react/createContext#provider | Provider} Component that provide `initData`, * you must wrap your JSX inside it * @group Components * * @example * * ```ts * import { root } from "@lynx-js/react" * * function App() { * return ( * ...}/> * ) * } * * root.render( * * * * ); * * ``` * * @public * * @function */ // @ts-expect-error make preact and react types work export const InitDataProvider: FC<{ children?: ReactNode | undefined }> = /* @__PURE__ */ _InitData.Provider(); /** * The {@link https://react.dev/reference/react/createContext#consumer | Consumer} Component that provide `initData`. * This should be used with {@link InitDataProvider} * @group Components * @public * * @function */ // @ts-expect-error make preact and react types work export const InitDataConsumer: Consumer = /* @__PURE__ */ _InitData.Consumer(); /** * A React Hooks for you to get `initData`. * If `initData` is changed, a re-render will be triggered automatically. * * @example * * ```ts * function App() { * const initData = useInitData(); * * initData.someProperty // use it * } * ``` * * @function * @public * * @group Hooks */ export const useInitData: () => InitData = /* @__PURE__ */ _InitData.use(); /** * A React Hooks for you to get notified when `initData` changed. * * @example * ```ts * function App() { * useInitDataChanged((data) => { * data.someProperty // can use it * }) * } * ``` * @function * @public * * @group Hooks */ export const useInitDataChanged: (callback: (data: InitData) => void) => void = /* @__PURE__ */ _InitData.useChanged(); export type { GlobalProps } from './core/globalProps.js'; const _GlobalProps = /* @__PURE__ */ createGlobalProps({ createContext, useState, createElement, useLynxGlobalEventListener, }); /** * The {@link https://react.dev/reference/react/createContext#provider | Provider} Component that provide `lynx.__globalProps`. * Only needed with `globalPropsMode: 'event'`; in the default `'reactive'` mode it renders its children directly * and updates come from a full re-render. * @group Components * * @example * * With `globalPropsMode: 'event'`: * * ```ts * import { root } from "@lynx-js/react" * * function App() { * return ( * ...}/> * ) * } * * root.render( * * * * ); * * ``` * * @public * * @function */ // @ts-expect-error make preact and react types work export const GlobalPropsProvider: FC<{ children?: ReactNode | undefined }> = /* @__PURE__ */ _GlobalProps.Provider(); /** * The {@link https://react.dev/reference/react/createContext#consumer | Consumer} Component that provide `lynx.__globalProps`. * Only needed with `globalPropsMode: 'event'`, together with {@link GlobalPropsProvider}; in the default `'reactive'` mode * it calls `children` with `lynx.__globalProps` directly and updates come from a full re-render. * @group Components * @public * * @function */ // @ts-expect-error make preact and react types work export const GlobalPropsConsumer: Consumer = /* @__PURE__ */ _GlobalProps.Consumer(); /** * A React Hooks for you to get `lynx.__globalProps`. * With `globalPropsMode: 'event'`, the component re-renders when `lynx.__globalProps` changes; * in the default `'reactive'` mode, updates arrive through a full re-render. * * @example * * ```ts * function App() { * const globalProps = useGlobalProps(); * * globalProps.someProperty // use it * } * ``` * * @function * @public * * @group Hooks */ export const useGlobalProps: () => GlobalProps = /* @__PURE__ */ _GlobalProps.use(); /** * A React Hooks for you to get notified when `__globalProps` changed. * * @example * ```ts * function App() { * useGlobalPropsChanged((data) => { * lynx.__globalProps.someProperty // can use lynx.__globalProps * data.someProperty // can use data * }) * } * ``` * @function * @public * * @group Hooks */ export const useGlobalPropsChanged: (callback: (data: GlobalProps) => void) => void = /* @__PURE__ */ _GlobalProps .useChanged(); /** * The interface you can extends so that the `defaultDataProcessor` parameter can be customized * * Should be used with `lynx.registerDataProcessors`. See more examples at {@link Lynx.registerDataProcessors}. * * @public * * @group Types */ export interface InitDataRaw {} /** * The interface you can extends so that the `defaultDataProcessor` returning value can be customized * * Should be used with `lynx.registerDataProcessors`. See more examples at {@link Lynx.registerDataProcessors}. * * @public * * @group Types */ export interface InitData {} export { withInitDataInState }; /** * The data processors that registered with {@link Lynx.registerDataProcessors}. * * @example * * Extending `dataProcessors` interface * * ```ts * import type { DataProcessors as WellKnownDataProcessors } from '@lynx-js/react'; * * declare module '@lynx-js/react' { * interface DataProcessors extends WellKnownDataProcessors { * foo(bar: string): number; * } * } * ``` * * Then you can use `lynx.registerDataProcessors` with types. * * ```js * lynx.registerDataProcessors({ * dataProcessors: { * foo(bar) { * return 1; * } * } * }) * ``` * * @public * * @group Types */ export interface DataProcessors { /** * Optional processor to override screen metrics used by the app * * @param metrics - The physical screen dimensions in pixels * * @returns New screen dimensions to be used by the app * * @example * * ```ts * lynx.registerDataProcessors({ * dataProcessors: { * getScreenMetricsOverride: (metrics) => { * // Force a specific aspect ratio * return { * width: metrics.width, * height: metrics.width * (16/9) * }; * } * } * }); * ``` */ getScreenMetricsOverride?(metrics: { /** * The physical pixel width of the screen */ width: number; /** * The physical pixel height of the screen */ height: number; }): { width: number; height: number }; /** * Custom unknown data processors. * * @remarks * * You may extends the `DataProcessors` interface for better TypeScript types. See {@link DataProcessors}. */ [processorName: string]: (...args: any[]) => any; } /** * Definition of DataProcessor(s) * @public * * @group Types */ export interface DataProcessorDefinition { /** * You can custom input and output type of `defaultDataProcessor` by extends {@link InitDataRaw} and {@link InitData} * * Should be used with `lynx.registerDataProcessors`. See more examples at {@link Lynx.registerDataProcessors}. * * @param rawInitData - initData passed from native code * @returns * @public */ defaultDataProcessor?: (rawInitData: InitDataRaw) => InitData; /** * Should be used with `lynx.registerDataProcessors`. See more examples at {@link Lynx.registerDataProcessors}. * * @public */ dataProcessors?: DataProcessors; } /** * APIs under `lynx` global variable that added by ReactLynx. * * @example * * ```ts * lynx.registerDataProcessors(...); * lynx.querySelector(...); * lynx.querySelectorAll(...); * ``` * * @public * * @group Types */ export interface Lynx { /** * An alias of `lynx.getJSModule("GlobalEventEmitter").trigger(eventName, params)` only in Lepus * * @public */ triggerGlobalEventFromLepus: (eventName: string, params: any) => void; /** * Register DataProcessors. You MUST call this before `root.render()`. * * @example * * You MUST call `lynx.registerDataProcessors` before calling `root.render()`. * * ```ts * import { root } from "@lynx-js/react" * * // You MUST call this before `root.render()` * lynx.registerDataProcessors({ * defaultDataProcessor: () => {...} // default DataProcessor * dataProcessors: { * getScreenMetricsOverride: () => {...} // named DataProcessor * } * }) * * root.render(); * ``` * * @example * * If you have a class component with `static defaultDataProcessor` * or `static dataProcessors`, you can use it to register DataProcessors. * * ```ts * import { root, Component } from "@lynx-js/react" * * class App extends Component { * static defaultDataProcessor() { * ... * } * * static dataProcessors = { * getScreenMetricsOverride() { * ... * } * } * } * * lynx.registerDataProcessors(App); // You can pass `App` because it has the required shape * root.render(); * ``` * * @example * * For developers who want fully typed `defaultDataProcessor`, * they can achieve it by extends interface `InitDataRaw` and `InitData`. * * ```ts * import { root } from "@lynx-js/react" * * interface ExistingInterface { * somePropertyFromExistingInterface: number * } * * declare module '@lynx-js/react' { * interface InitDataRaw extends ExistingInterface { * someAnotherCustomProperty: string * } * } * * lynx.registerDataProcessors({ * defaultDataProcessor: (initDataRaw) => { * initDataRaw.somePropertyFromExistingInterface // will be typed * } * }) * * ``` * * @example * * For developers who want fully typed `defaultDataProcessor`, * they can achieve it by extends interface `InitDataRaw` and `InitData`. * * ```ts * import { useInitData } from "@lynx-js/react" * * interface AnotherExistingInterface { * someAnotherPropertyFromExistingInterface: number * } * * declare module '@lynx-js/react' { * interface InitData extends AnotherExistingInterface { * someCustomProperty: string * } * } * * lynx.registerDataProcessors({ * defaultDataProcessor: () => { * return { * someCustomProperty: 'value', // will be typed * someAnotherPropertyFromExistingInterface: 1, // will be typed * } * } * }) * * function App() { * const initData = useInitData(); * * initData.someCustomProperty // will be typed * initData.someAnotherPropertyFromExistingInterface // will be typed * } * * ``` * @public */ registerDataProcessors: (dataProcessorDefinition?: DataProcessorDefinition) => void; } export { useLynxGlobalEventListener } from './core/hooks/useLynxGlobalEventListener.js'; export { runOnBackground } from './core/background-function/run-on-background.js'; export { runOnMainThread } from './snapshot/worklet/call/runOnMainThread.js'; export { MainThreadRef, useMainThreadRef } from './core/main-thread-ref.js'; export { defineMainThreadObjectType, useMainThreadObject } from './core/main-thread-object.js'; export type { MainThreadObjectHandle, MainThreadObjectType, MainThreadObjectTypeDefinition, } from './core/main-thread-object.js';