diff --git a/.changeset/social-deer-brush.md b/.changeset/social-deer-brush.md new file mode 100644 index 0000000000..c64062ed45 --- /dev/null +++ b/.changeset/social-deer-brush.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-mainthread-apis": patch +--- + +fix: mts && bts events can be binded both diff --git a/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts b/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts index ac57f1ab8f..7056852b21 100644 --- a/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts +++ b/packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts @@ -210,48 +210,59 @@ export function createMainThreadGlobalThis( currentTarget as any as HTMLElement, ); if (runtimeInfo) { - const hname = isCapture + const handlerInfos = (isCapture ? runtimeInfo.eventHandlerMap[lynxEventName]?.capture - ?.handler - : runtimeInfo.eventHandlerMap[lynxEventName]?.bind - ?.handler; - const crossThreadEvent = createCrossThreadEvent( - event as MinimalRawEventObject, - lynxEventName, - ); - if (typeof hname === 'string') { - const parentComponentUniqueId = Number( - currentTarget.getAttribute(parentComponentUniqueIdAttribute)!, - ); - const parentComponent = lynxUniqueIdToElement[parentComponentUniqueId]! - .deref()!; - const componentId = - parentComponent?.getAttribute(lynxTagAttribute) !== 'page' - ? parentComponent?.getAttribute(componentIdAttribute) ?? undefined - : undefined; - if (componentId) { - callbacks.publicComponentEvent( - componentId, - hname, - crossThreadEvent, - ); - } else { - callbacks.publishEvent( - hname, - crossThreadEvent, + : runtimeInfo.eventHandlerMap[lynxEventName]?.bind) as unknown as { + handler: string | { type: 'worklet'; value: unknown }; + }[]; + let stopPropagation = false; + if (handlerInfos) { + for (const handlerInfo of handlerInfos) { + const hname = handlerInfo.handler; + const crossThreadEvent = createCrossThreadEvent( + event as MinimalRawEventObject, + lynxEventName, ); + if (typeof hname === 'string') { + const parentComponentUniqueId = Number( + currentTarget.getAttribute(parentComponentUniqueIdAttribute)!, + ); + const parentComponent = + lynxUniqueIdToElement[parentComponentUniqueId]! + .deref()!; + const componentId = + parentComponent?.getAttribute(lynxTagAttribute) !== 'page' + ? parentComponent?.getAttribute(componentIdAttribute) + ?? undefined + : undefined; + if (componentId) { + callbacks.publicComponentEvent( + componentId, + hname, + crossThreadEvent, + ); + } else { + callbacks.publishEvent( + hname, + crossThreadEvent, + ); + } + if (handlerInfos.length === 1) { + stopPropagation = true; + } + } else if (hname) { + (crossThreadEvent as MainThreadScriptEvent).target.elementRefptr = + event.target; + if (crossThreadEvent.currentTarget) { + (crossThreadEvent as MainThreadScriptEvent).currentTarget! + .elementRefptr = event.currentTarget; + } + (mtsRealm.globalWindow as typeof globalThis & MainThreadGlobalThis) + .runWorklet?.(hname.value, [crossThreadEvent]); + } } - return true; - } else if (hname) { - (crossThreadEvent as MainThreadScriptEvent).target.elementRefptr = - event.target; - if (crossThreadEvent.currentTarget) { - (crossThreadEvent as MainThreadScriptEvent).currentTarget! - .elementRefptr = event.currentTarget; - } - (mtsRealm.globalWindow as typeof globalThis & MainThreadGlobalThis) - .runWorklet?.(hname.value, [crossThreadEvent]); } + return stopPropagation; } return false; }; @@ -285,9 +296,10 @@ export function createMainThreadGlobalThis( componentAtIndex: undefined, enqueueComponent: undefined, }; - const currentHandler = isCapture + const handlerList = (isCapture ? runtimeInfo.eventHandlerMap[eventName]?.capture - : runtimeInfo.eventHandlerMap[eventName]?.bind; + : runtimeInfo.eventHandlerMap[eventName]?.bind) as unknown as any[]; + const currentHandler = handlerList && handlerList.length > 0; const currentRegisteredHandler = isCatch ? (isCapture ? catchCaptureHandler : defaultCatchHandler) : (isCapture ? captureHandler : defaultHandler); @@ -305,6 +317,13 @@ export function createMainThreadGlobalThis( if (isExposure && element.getAttribute('exposure-id') === '-1') { mtsGlobalThis.__SetAttribute(element, 'exposure-id', null); } + if (runtimeInfo.eventHandlerMap[eventName]) { + if (isCapture) { + runtimeInfo.eventHandlerMap[eventName]!.capture = undefined; + } else { + runtimeInfo.eventHandlerMap[eventName]!.bind = undefined; + } + } } } else { /** @@ -336,10 +355,28 @@ export function createMainThreadGlobalThis( bind: undefined, }; } + let targetList = (isCapture + ? runtimeInfo.eventHandlerMap[eventName]!.capture + : runtimeInfo.eventHandlerMap[eventName]!.bind) as unknown as any[]; + + if (!Array.isArray(targetList)) { + targetList = targetList ? [targetList] : []; + } + + const typeOfNew = typeof newEventHandler; + const index = targetList.findIndex((h: any) => + typeof h.handler === typeOfNew + ); + if (index !== -1) { + targetList[index] = info; + } else { + targetList.push(info); + } + if (isCapture) { - runtimeInfo.eventHandlerMap[eventName]!.capture = info; + runtimeInfo.eventHandlerMap[eventName]!.capture = targetList as any; } else { - runtimeInfo.eventHandlerMap[eventName]!.bind = info; + runtimeInfo.eventHandlerMap[eventName]!.bind = targetList as any; } } elementToRuntimeInfoMap.set(element, runtimeInfo); @@ -354,10 +391,13 @@ export function createMainThreadGlobalThis( if (runtimeInfo) { eventName = eventName.toLowerCase(); const isCapture = eventType.startsWith('capture'); - const handler = isCapture + const handler = (isCapture ? runtimeInfo.eventHandlerMap[eventName]?.capture - : runtimeInfo.eventHandlerMap[eventName]?.bind; - return handler?.handler; + : runtimeInfo.eventHandlerMap[eventName]?.bind) as unknown as any[]; + if (Array.isArray(handler)) { + return handler[0]?.handler; + } + return (handler as any)?.handler; } else { return undefined; } @@ -374,13 +414,18 @@ export function createMainThreadGlobalThis( for (const [lynxEventName, info] of Object.entries(eventHandlerMap)) { for (const atomInfo of [info.bind, info.capture]) { if (atomInfo) { - const { type, handler } = atomInfo; - if (handler) { - eventInfos.push({ - type: type as LynxEventType, - name: lynxEventName, - function: handler, - }); + const handlerList = (Array.isArray(atomInfo) + ? atomInfo + : [atomInfo]) as any[]; + for (const item of handlerList) { + const { type, handler } = item; + if (handler) { + eventInfos.push({ + type: type as LynxEventType, + name: lynxEventName, + function: handler, + }); + } } } } diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index eb094043c1..9a5536f2a0 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -116,6 +116,16 @@ test.describe('reactlynx3 tests', () => { await wait(100); await expect(await target.getAttribute('style')).toContain('pink'); }); + test('basic-bindtap-simultaneous', async ({ page }, { title }) => { + await goto(page, title); + await wait(100); + const target = page.locator('#target'); + await target.click(); + await wait(100); + await expect(await target.getAttribute('style')).toContain('green'); // BTS check + await expect(await target.getAttribute('data-mts-clicked')).toBe('true'); // MTS check + await expect(page.locator('#bts-status')).toHaveText('BTS Clicked'); + }); test('basic-bindtap-detail', async ({ page }, { title }) => { await goto(page, title); await wait(100); diff --git a/packages/web-platform/web-tests/tests/react/basic-bindtap-simultaneous/index.jsx b/packages/web-platform/web-tests/tests/react/basic-bindtap-simultaneous/index.jsx new file mode 100644 index 0000000000..f87fe49cbb --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-bindtap-simultaneous/index.jsx @@ -0,0 +1,37 @@ +import { useState, root } from '@lynx-js/react'; + +function App() { + const [btsClicked, setBtsClicked] = useState(false); + + const handleBtsTap = () => { + setBtsClicked(true); + console.log('BTS Clicked'); + }; + + const handleMtsTap = (event) => { + 'main thread'; + event.currentTarget.setAttribute('data-mts-clicked', 'true'); + console.log('MTS Clicked'); + }; + + return ( + + + + {btsClicked ? 'BTS Clicked' : 'BTS Not Clicked'} + + + ); +} + +root.render();