From ec544dac5244b85493f01d2b214edf678eef9579 Mon Sep 17 00:00:00 2001
From: Sherry-hue <37186915+Sherry-hue@users.noreply.github.com>
Date: Tue, 20 Jan 2026 21:20:37 +0800
Subject: [PATCH] fix(web): mts && bts events can be binded both
---
.changeset/social-deer-brush.md | 5 +
.../ts/createMainThreadGlobalThis.ts | 149 ++++++++++++------
.../web-tests/tests/react.spec.ts | 10 ++
.../basic-bindtap-simultaneous/index.jsx | 37 +++++
4 files changed, 149 insertions(+), 52 deletions(-)
create mode 100644 .changeset/social-deer-brush.md
create mode 100644 packages/web-platform/web-tests/tests/react/basic-bindtap-simultaneous/index.jsx
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();