From edf0123c71566a0be441857b420cd4036e569bdf Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sun, 12 Jul 2026 11:21:28 +0800 Subject: [PATCH 1/5] feat(pickAttrs): add missing pointer, animation, transition and form events pickAttrs' event allowlist was missing several categories of React DOM events, so they were silently dropped when spreading picked props onto a DOM node: - Pointer events: onPointerDown/Move/Up/Cancel/Enter/Leave/Over/Out and onGotPointerCapture/onLostPointerCapture - Animation events: onAnimationStart/End/Iteration - Transition events: onTransitionEnd - Form events: onBeforeInput, onReset, onInvalid Also removed a duplicate onError entry. --- src/pickAttrs.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pickAttrs.ts b/src/pickAttrs.ts index 70dea0b9..9fe6244c 100644 --- a/src/pickAttrs.ts +++ b/src/pickAttrs.ts @@ -17,7 +17,10 @@ const eventsName = `onCopy onCut onPaste onCompositionEnd onCompositionStart onC onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp onSelect onTouchCancel onTouchEnd onTouchMove onTouchStart onScroll onWheel onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata - onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting onLoad onError`; + onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting onLoad + onPointerDown onPointerMove onPointerUp onPointerCancel onPointerEnter onPointerLeave onPointerOver onPointerOut onGotPointerCapture onLostPointerCapture + onAnimationStart onAnimationEnd onAnimationIteration onTransitionEnd + onBeforeInput onReset onInvalid`; const propList = `${attributes} ${eventsName}`.split(/[\s\n]+/); From 8072f8fcbd00fbc9a80daf789397a6887e6c7233 Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sun, 12 Jul 2026 11:26:41 +0800 Subject: [PATCH 2/5] test(pickAttrs): cover pointer, animation, transition and form events --- tests/utils.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 580c303c..7eff5b45 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -291,6 +291,10 @@ describe('utils', () => { describe('pickAttrs', () => { const originProps = { onClick: null, + onPointerDown: null, + onAnimationEnd: null, + onTransitionEnd: null, + onInvalid: null, checked: true, 'data-my': 1, 'aria-this': 2, @@ -301,6 +305,10 @@ describe('utils', () => { it('default', () => { expect(pickAttrs(originProps)).toEqual({ onClick: null, + onPointerDown: null, + onAnimationEnd: null, + onTransitionEnd: null, + onInvalid: null, checked: true, 'data-my': 1, 'aria-this': 2, @@ -318,6 +326,10 @@ describe('utils', () => { it('attr only', () => { expect(pickAttrs(originProps, { attr: true })).toEqual({ onClick: null, + onPointerDown: null, + onAnimationEnd: null, + onTransitionEnd: null, + onInvalid: null, checked: true, role: 'button', }); From 5ff1bf2499001173a2a6aa9f40a21fde69ffcc9a Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sun, 12 Jul 2026 11:31:54 +0800 Subject: [PATCH 3/5] feat(pickAttrs): add remaining DOM events (toggle, dialog, resize, scrollEnd, transition run/start/cancel, auxClick) --- src/pickAttrs.ts | 6 ++++-- tests/utils.test.ts | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pickAttrs.ts b/src/pickAttrs.ts index 9fe6244c..f683b251 100644 --- a/src/pickAttrs.ts +++ b/src/pickAttrs.ts @@ -19,8 +19,10 @@ const eventsName = `onCopy onCut onPaste onCompositionEnd onCompositionStart onC onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting onLoad onPointerDown onPointerMove onPointerUp onPointerCancel onPointerEnter onPointerLeave onPointerOver onPointerOut onGotPointerCapture onLostPointerCapture - onAnimationStart onAnimationEnd onAnimationIteration onTransitionEnd - onBeforeInput onReset onInvalid`; + onAnimationStart onAnimationEnd onAnimationIteration + onTransitionEnd onTransitionRun onTransitionStart onTransitionCancel + onBeforeInput onReset onInvalid + onAuxClick onToggle onBeforeToggle onCancel onClose onResize onScrollEnd`; const propList = `${attributes} ${eventsName}`.split(/[\s\n]+/); diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 7eff5b45..9032a0f9 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -295,6 +295,7 @@ describe('utils', () => { onAnimationEnd: null, onTransitionEnd: null, onInvalid: null, + onToggle: null, checked: true, 'data-my': 1, 'aria-this': 2, @@ -309,6 +310,7 @@ describe('utils', () => { onAnimationEnd: null, onTransitionEnd: null, onInvalid: null, + onToggle: null, checked: true, 'data-my': 1, 'aria-this': 2, @@ -330,6 +332,7 @@ describe('utils', () => { onAnimationEnd: null, onTransitionEnd: null, onInvalid: null, + onToggle: null, checked: true, role: 'button', }); From a8c6cff4d8b67d6a103b6f883014cbd8ac648684 Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sun, 12 Jul 2026 11:46:45 +0800 Subject: [PATCH 4/5] chore(tests): update pickAttrs tests to sync with React event types --- .gitignore | 1 + tests/utils.test.ts | 51 ++++++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 32dd1191..d6bafd75 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ bun.lockb es docs-dist .vercel +.vscode diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 9032a0f9..5e396fd8 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,3 +1,5 @@ +import fs from 'fs'; +import path from 'path'; import pickAttrs from '../src/pickAttrs'; import get from '../src/utils/get'; import set, { mergeWith, merge } from '../src/utils/set'; @@ -291,11 +293,6 @@ describe('utils', () => { describe('pickAttrs', () => { const originProps = { onClick: null, - onPointerDown: null, - onAnimationEnd: null, - onTransitionEnd: null, - onInvalid: null, - onToggle: null, checked: true, 'data-my': 1, 'aria-this': 2, @@ -306,11 +303,6 @@ describe('utils', () => { it('default', () => { expect(pickAttrs(originProps)).toEqual({ onClick: null, - onPointerDown: null, - onAnimationEnd: null, - onTransitionEnd: null, - onInvalid: null, - onToggle: null, checked: true, 'data-my': 1, 'aria-this': 2, @@ -328,11 +320,6 @@ describe('utils', () => { it('attr only', () => { expect(pickAttrs(originProps, { attr: true })).toEqual({ onClick: null, - onPointerDown: null, - onAnimationEnd: null, - onTransitionEnd: null, - onInvalid: null, - onToggle: null, checked: true, role: 'button', }); @@ -345,5 +332,39 @@ describe('utils', () => { role: 'button', }); }); + + it('eventsName stays in full sync with @types/react', () => { + const dts = fs.readFileSync( + path.join( + path.dirname(require.resolve('@types/react/package.json')), + 'index.d.ts', + ), + 'utf8', + ); + + const isCapturePhase = (n: string) => + n.endsWith('Capture') && !/^on(Got|Lost)PointerCapture$/.test(n); + const reactEvents = new Set( + [...dts.matchAll(/\bon[A-Z]\w*(?=\?:)/g)] + .map(m => m[0]) + .filter(n => !isCapturePhase(n)), + ); + + const src = fs.readFileSync( + path.join(__dirname, '../src/pickAttrs.ts'), + 'utf8', + ); + const ourEvents = new Set( + src + .match(/const eventsName = `([\s\S]+?)`/)![1] + .split(/\s+/) + .filter(Boolean), + ); + + expect({ + missing: [...reactEvents].filter(e => !ourEvents.has(e)), // React has, we dropped + stale: [...ourEvents].filter(e => !reactEvents.has(e)), // we have, React removed + }).toEqual({ missing: [], stale: [] }); + }); }); }); From 91c6519f59657fa0912437fe879ba174e11c9411 Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sun, 12 Jul 2026 16:42:34 +0800 Subject: [PATCH 5/5] update --- tests/utils.test.ts | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 5e396fd8..842c172d 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; import pickAttrs from '../src/pickAttrs'; import get from '../src/utils/get'; import set, { mergeWith, merge } from '../src/utils/set'; @@ -333,10 +333,10 @@ describe('utils', () => { }); }); - it('eventsName stays in full sync with @types/react', () => { - const dts = fs.readFileSync( - path.join( - path.dirname(require.resolve('@types/react/package.json')), + it('forwards every React DOM event handler', () => { + const dts = readFileSync( + join( + dirname(require.resolve('@types/react/package.json')), 'index.d.ts', ), 'utf8', @@ -344,27 +344,14 @@ describe('utils', () => { const isCapturePhase = (n: string) => n.endsWith('Capture') && !/^on(Got|Lost)PointerCapture$/.test(n); - const reactEvents = new Set( - [...dts.matchAll(/\bon[A-Z]\w*(?=\?:)/g)] - .map(m => m[0]) - .filter(n => !isCapturePhase(n)), - ); + const reactEvents = [...dts.matchAll(/\bon[A-Z]\w*(?=\?:)/g)] + .map(m => m[0]) + .filter(n => !isCapturePhase(n)); - const src = fs.readFileSync( - path.join(__dirname, '../src/pickAttrs.ts'), - 'utf8', + const dropped = reactEvents.filter( + e => pickAttrs({ [e]: 1 }, { attr: true })[e] === undefined, ); - const ourEvents = new Set( - src - .match(/const eventsName = `([\s\S]+?)`/)![1] - .split(/\s+/) - .filter(Boolean), - ); - - expect({ - missing: [...reactEvents].filter(e => !ourEvents.has(e)), // React has, we dropped - stale: [...ourEvents].filter(e => !reactEvents.has(e)), // we have, React removed - }).toEqual({ missing: [], stale: [] }); + expect(dropped).toEqual([]); }); }); });