diff --git a/.changeset/calm-needles-rule.md b/.changeset/calm-needles-rule.md new file mode 100644 index 0000000000..da0d254795 --- /dev/null +++ b/.changeset/calm-needles-rule.md @@ -0,0 +1,6 @@ +--- +"@lynx-js/web-constants": patch +"@lynx-js/web-mainthread-apis": patch +--- + +feat: add main-thread API: __QuerySelector diff --git a/packages/web-platform/web-constants/src/types/MainThreadGlobalThis.ts b/packages/web-platform/web-constants/src/types/MainThreadGlobalThis.ts index 44b3d7de2a..e2e0ed8db1 100644 --- a/packages/web-platform/web-constants/src/types/MainThreadGlobalThis.ts +++ b/packages/web-platform/web-constants/src/types/MainThreadGlobalThis.ts @@ -319,12 +319,17 @@ export type QueryComponentPAPI = ( }) => void, ) => null; export type InvokeUIMethodPAPI = ( - element: unknown, + element: HTMLElement, method: string, params: object, callback: (result: InvokeCallbackRes) => void, ) => void; +export type QuerySelectorPAPI = ( + element: HTMLElement, + selector: string, +) => unknown; + export interface ElementPAPIs { __ElementFromBinary: ElementFromBinaryPAPI; @@ -390,6 +395,7 @@ export interface ElementPAPIs { options?: FlushElementTreeOptions, ) => void; __InvokeUIMethod: InvokeUIMethodPAPI; + __QuerySelector: QuerySelectorPAPI; } export interface MainThreadGlobalThis extends ElementPAPIs { diff --git a/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts b/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts index c729345ea3..ac57f1ab8f 100644 --- a/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts +++ b/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts @@ -62,6 +62,8 @@ import { type QueryComponentPAPI, lynxEntryNameAttribute, ErrorCode, + type QuerySelectorPAPI, + type InvokeUIMethodPAPI, } from '@lynx-js/web-constants'; import { createMainThreadLynx } from './createMainThreadLynx.js'; import { @@ -649,6 +651,76 @@ export function createMainThreadGlobalThis( ); }; + const __InvokeUIMethod: InvokeUIMethodPAPI = ( + element, + method, + params, + callback, + ) => { + try { + if (method === 'boundingClientRect') { + const rect = (element as HTMLElement).getBoundingClientRect(); + callback({ + code: ErrorCode.SUCCESS, + data: { + id: (element as HTMLElement).id, + width: rect.width, + height: rect.height, + left: rect.left, + right: rect.right, + top: rect.top, + bottom: rect.bottom, + }, + }); + return; + } + if (typeof (element as any)[method] === 'function') { + const data = (element as any)[method](params); + callback({ + code: ErrorCode.SUCCESS, + data, + }); + return; + } + callback({ + code: ErrorCode.METHOD_NOT_FOUND, + }); + } catch (e) { + console.error( + `[lynx-web] invokeUIMethod: apply method failed with`, + e, + element, + ); + callback({ + code: ErrorCode.PARAM_INVALID, + }); + } + }; + + const __QuerySelector: QuerySelectorPAPI = ( + element, + selector, + ) => { + if (!element) return null; + const el = (element as HTMLElement).querySelector(selector); + if (el) { + if (!(el as any).invoke) { + (el as any).invoke = (method: string, params: object) => { + return new Promise((resolve, reject) => { + __InvokeUIMethod(el as HTMLElement, method, params, (res) => { + if (res.code === ErrorCode.SUCCESS) { + resolve(res.data); + } else { + reject(res); + } + }); + }); + }; + } + } + return el; + }; + const __GetPageElement: GetPageElementPAPI = () => { return pageElement; }; @@ -817,46 +889,8 @@ export function createMainThreadGlobalThis( _I18nResourceTranslation: callbacks._I18nResourceTranslation, _AddEventListener: () => {}, renderPage: undefined, - __InvokeUIMethod: (element, method, params, callback) => { - try { - if (method === 'boundingClientRect') { - const rect = (element as HTMLElement).getBoundingClientRect(); - callback({ - code: ErrorCode.SUCCESS, - data: { - id: (element as HTMLElement).id, - width: rect.width, - height: rect.height, - left: rect.left, - right: rect.right, - top: rect.top, - bottom: rect.bottom, - }, - }); - return; - } - if (typeof (element as any)[method] === 'function') { - const data = (element as any)[method](params); - callback({ - code: ErrorCode.SUCCESS, - data, - }); - return; - } - callback({ - code: ErrorCode.METHOD_NOT_FOUND, - }); - } catch (e) { - console.error( - `[lynx-web] invokeUIMethod: apply method failed with`, - e, - element, - ); - callback({ - code: ErrorCode.PARAM_INVALID, - }); - } - }, + __InvokeUIMethod, + __QuerySelector, }; Object.assign(mtsRealm.globalWindow, mtsGlobalThis); Object.defineProperty(mtsRealm.globalWindow, 'renderPage', { diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index 865ef0e743..eb094043c1 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -464,6 +464,22 @@ test.describe('reactlynx3 tests', () => { expect(scrollTopAfter).toBeGreaterThan(100); }); + test('basic-main-query-selector', async ({ page }, { title }) => { + await goto(page, title); + await wait(100); + const scrollView = page.locator('scroll-view'); + const scrollTopBefore = await scrollView.evaluate((node) => + node.scrollTop + ); + expect(scrollTopBefore).toBe(0); + await page.locator('#tap-me').click(); + await wait(3000); + const scrollTopAfter = await scrollView.evaluate((node) => + node.scrollTop + ); + expect(scrollTopAfter).toBeGreaterThan(100); + }); + // lazy component test( 'basic-lazy-component', diff --git a/packages/web-platform/web-tests/tests/react/basic-main-query-selector/index.jsx b/packages/web-platform/web-tests/tests/react/basic-main-query-selector/index.jsx new file mode 100644 index 0000000000..21655323c7 --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-main-query-selector/index.jsx @@ -0,0 +1,57 @@ +import { root } from '@lynx-js/react'; + +export const App = () => { + const handleTap = () => { + 'main thread'; + const scrollView = lynx.querySelector('#scroll-view'); + scrollView?.invoke('autoScroll', { + rate: 120, + start: true, + }); + }; + + return ( + + + + Tap me to enable auto-scroll + + + + {Array.from({ length: 20 }).map((item, index) => ( + + Item {index} + + ))} + + + ); +}; + +root.render();