-
Notifications
You must be signed in to change notification settings - Fork 240
/
interceptor.ts
79 lines (69 loc) 路 2.16 KB
/
interceptor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const Interceptor = Symbol('Interceptor for programmatical calls')
interface Interceptable {
[Interceptor]?: typeof Interceptor
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type anyFunc = (...a: any[]) => any
type Params<Prop> = Prop extends anyFunc ? Parameters<Prop> : [Prop]
type ImplReturn<Prop> = Prop extends anyFunc ? Parameters<Prop> : Prop
export function prepareInterceptor<
ElementType extends Node,
PropName extends keyof ElementType,
>(
element: ElementType,
propName: PropName,
interceptorImpl: (
this: ElementType,
...args: Params<ElementType[PropName]>
) => {
/**
* React tracks the changes on element properties.
* This workaround tries to alter the DOM element without React noticing,
* so that it later picks up the change.
*
* @see https://github.com/facebook/react/blob/148f8e497c7d37a3c7ab99f01dec2692427272b1/packages/react-dom/src/client/inputValueTracking.js#L51-L104
*/
applyNative?: boolean
realArgs?: ImplReturn<ElementType[PropName]>
then?: () => void
},
) {
const prototypeDescriptor = Object.getOwnPropertyDescriptor(
element.constructor.prototype,
propName,
)
const objectDescriptor = Object.getOwnPropertyDescriptor(element, propName)
const target = prototypeDescriptor?.set ? 'set' : 'value'
if (
typeof prototypeDescriptor?.[target] !== 'function' ||
(prototypeDescriptor[target] as Interceptable)[Interceptor]
) {
return
}
function intercept(
this: ElementType,
...args: Params<ElementType[PropName]>
) {
const {
applyNative = true,
realArgs,
then,
} = interceptorImpl.call(this, ...args)
const realFunc = ((!applyNative && objectDescriptor) ||
(prototypeDescriptor as PropertyDescriptor))[target] as (
this: ElementType,
...a: unknown[]
) => unknown
if (target === 'set') {
realFunc.call(this, realArgs)
} else {
realFunc.call(this, ...realArgs)
}
then?.()
}
;(intercept as Interceptable)[Interceptor] = Interceptor
Object.defineProperty(element, propName, {
...(objectDescriptor ?? prototypeDescriptor),
[target]: intercept,
})
}