@planningcenter/chat-react-native 3.15.0-rc.7 → 3.15.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/display/icon.d.ts +26 -13
- package/build/components/display/icon.d.ts.map +1 -1
- package/build/components/display/icon.js +0 -12
- package/build/components/display/icon.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/polyfills/events/CustomEvent.d.ts +21 -0
- package/build/polyfills/events/CustomEvent.d.ts.map +1 -0
- package/build/polyfills/events/CustomEvent.js +22 -0
- package/build/polyfills/events/CustomEvent.js.map +1 -0
- package/build/polyfills/events/Event.d.ts +49 -0
- package/build/polyfills/events/Event.d.ts.map +1 -0
- package/build/polyfills/events/Event.js +125 -0
- package/build/polyfills/events/Event.js.map +1 -0
- package/build/polyfills/events/EventHandlerAttributes.d.ts +8 -0
- package/build/polyfills/events/EventHandlerAttributes.d.ts.map +1 -0
- package/build/polyfills/events/EventHandlerAttributes.js +46 -0
- package/build/polyfills/events/EventHandlerAttributes.js.map +1 -0
- package/build/polyfills/events/EventTarget.d.ts +33 -0
- package/build/polyfills/events/EventTarget.d.ts.map +1 -0
- package/build/polyfills/events/EventTarget.js +238 -0
- package/build/polyfills/events/EventTarget.js.map +1 -0
- package/build/polyfills/events/internals/EventInternals.d.ts +30 -0
- package/build/polyfills/events/internals/EventInternals.d.ts.map +1 -0
- package/build/polyfills/events/internals/EventInternals.js +76 -0
- package/build/polyfills/events/internals/EventInternals.js.map +1 -0
- package/build/polyfills/events/internals/EventTargetInternals.d.ts +9 -0
- package/build/polyfills/events/internals/EventTargetInternals.d.ts.map +1 -0
- package/build/polyfills/events/internals/EventTargetInternals.js +11 -0
- package/build/polyfills/events/internals/EventTargetInternals.js.map +1 -0
- package/build/polyfills/webidl/PlatformObjects.d.ts +31 -0
- package/build/polyfills/webidl/PlatformObjects.d.ts.map +1 -0
- package/build/polyfills/webidl/PlatformObjects.js +39 -0
- package/build/polyfills/webidl/PlatformObjects.js.map +1 -0
- package/build/screens/design_system_screen.js +1 -1
- package/build/screens/design_system_screen.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/event-polyfill.test.ts +314 -0
- package/src/components/display/icon.tsx +17 -14
- package/src/index.tsx +2 -0
- package/src/polyfills/events/CustomEvent.ts +32 -0
- package/src/polyfills/events/Event.ts +186 -0
- package/src/polyfills/events/EventHandlerAttributes.ts +67 -0
- package/src/polyfills/events/EventTarget.ts +360 -0
- package/src/polyfills/events/README.md +1 -0
- package/src/polyfills/events/internals/EventInternals.ts +95 -0
- package/src/polyfills/events/internals/EventTargetInternals.ts +16 -0
- package/src/polyfills/webidl/PlatformObjects.ts +50 -0
- package/src/screens/design_system_screen.tsx +1 -1
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Event/CustomEvent/EventTarget polyfills
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Event from '../polyfills/events/Event'
|
|
6
|
+
import EventTarget from '../polyfills/events/EventTarget'
|
|
7
|
+
import { CustomEvent } from '../polyfills/events/CustomEvent'
|
|
8
|
+
|
|
9
|
+
// Set up global polyfill for tests
|
|
10
|
+
;(global as any).Event = Event
|
|
11
|
+
;(global as any).CustomEvent = CustomEvent
|
|
12
|
+
|
|
13
|
+
describe('Event Polyfill', () => {
|
|
14
|
+
describe('Event', () => {
|
|
15
|
+
it('should create a basic event', () => {
|
|
16
|
+
const event = new Event('test')
|
|
17
|
+
expect(event.type).toBe('test')
|
|
18
|
+
expect(event.bubbles).toBe(false)
|
|
19
|
+
expect(event.cancelable).toBe(false)
|
|
20
|
+
expect(event.composed).toBe(false)
|
|
21
|
+
expect(event.defaultPrevented).toBe(false)
|
|
22
|
+
expect(event.isTrusted).toBe(false)
|
|
23
|
+
expect(event.eventPhase).toBe(Event.NONE)
|
|
24
|
+
expect(typeof event.timeStamp).toBe('number')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should create an event with options', () => {
|
|
28
|
+
const event = new Event('test', {
|
|
29
|
+
bubbles: true,
|
|
30
|
+
cancelable: true,
|
|
31
|
+
composed: true,
|
|
32
|
+
})
|
|
33
|
+
expect(event.bubbles).toBe(true)
|
|
34
|
+
expect(event.cancelable).toBe(true)
|
|
35
|
+
expect(event.composed).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should have correct phase constants', () => {
|
|
39
|
+
expect(Event.NONE).toBe(0)
|
|
40
|
+
expect(Event.CAPTURING_PHASE).toBe(1)
|
|
41
|
+
expect(Event.AT_TARGET).toBe(2)
|
|
42
|
+
expect(Event.BUBBLING_PHASE).toBe(3)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should prevent default when cancelable', () => {
|
|
46
|
+
const event = new Event('test', { cancelable: true })
|
|
47
|
+
expect(event.defaultPrevented).toBe(false)
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
expect(event.defaultPrevented).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should not prevent default when not cancelable', () => {
|
|
53
|
+
const event = new Event('test', { cancelable: false })
|
|
54
|
+
event.preventDefault()
|
|
55
|
+
expect(event.defaultPrevented).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should support stopPropagation', () => {
|
|
59
|
+
const event = new Event('test')
|
|
60
|
+
// No error thrown - internal flags are set
|
|
61
|
+
event.stopPropagation()
|
|
62
|
+
event.stopImmediatePropagation()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('CustomEvent', () => {
|
|
67
|
+
it('should create a custom event with detail', () => {
|
|
68
|
+
const detail = { foo: 'bar', count: 42 }
|
|
69
|
+
const event = new CustomEvent('custom', { detail })
|
|
70
|
+
expect(event.type).toBe('custom')
|
|
71
|
+
expect(event.detail).toEqual(detail)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should inherit from Event', () => {
|
|
75
|
+
const event = new CustomEvent('custom', {
|
|
76
|
+
bubbles: true,
|
|
77
|
+
cancelable: true,
|
|
78
|
+
detail: 'test',
|
|
79
|
+
})
|
|
80
|
+
expect(event instanceof Event).toBe(true)
|
|
81
|
+
expect(event.bubbles).toBe(true)
|
|
82
|
+
expect(event.cancelable).toBe(true)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should handle undefined detail', () => {
|
|
86
|
+
const event = new CustomEvent('custom')
|
|
87
|
+
expect(event.detail).toBeUndefined()
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('EventTarget', () => {
|
|
92
|
+
let target: EventTarget
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
target = new EventTarget()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should add and remove event listeners', () => {
|
|
99
|
+
const listener = jest.fn()
|
|
100
|
+
target.addEventListener('test', listener)
|
|
101
|
+
|
|
102
|
+
const event = new Event('test')
|
|
103
|
+
const result = target.dispatchEvent(event)
|
|
104
|
+
|
|
105
|
+
expect(listener).toHaveBeenCalledWith(event)
|
|
106
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
107
|
+
expect(result).toBe(true) // not prevented
|
|
108
|
+
|
|
109
|
+
target.removeEventListener('test', listener)
|
|
110
|
+
target.dispatchEvent(new Event('test'))
|
|
111
|
+
expect(listener).toHaveBeenCalledTimes(1) // not called again
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should handle multiple listeners for the same event', () => {
|
|
115
|
+
const listener1 = jest.fn()
|
|
116
|
+
const listener2 = jest.fn()
|
|
117
|
+
|
|
118
|
+
target.addEventListener('test', listener1)
|
|
119
|
+
target.addEventListener('test', listener2)
|
|
120
|
+
|
|
121
|
+
target.dispatchEvent(new Event('test'))
|
|
122
|
+
|
|
123
|
+
expect(listener1).toHaveBeenCalledTimes(1)
|
|
124
|
+
expect(listener2).toHaveBeenCalledTimes(1)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should handle once option', () => {
|
|
128
|
+
const listener = jest.fn()
|
|
129
|
+
target.addEventListener('test', listener, { once: true })
|
|
130
|
+
|
|
131
|
+
target.dispatchEvent(new Event('test'))
|
|
132
|
+
target.dispatchEvent(new Event('test'))
|
|
133
|
+
|
|
134
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should handle capture option', () => {
|
|
138
|
+
const listener = jest.fn()
|
|
139
|
+
target.addEventListener('test', listener, { capture: true })
|
|
140
|
+
|
|
141
|
+
target.dispatchEvent(new Event('test'))
|
|
142
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
143
|
+
|
|
144
|
+
target.removeEventListener('test', listener, { capture: true })
|
|
145
|
+
target.dispatchEvent(new Event('test'))
|
|
146
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should handle passive option', () => {
|
|
150
|
+
const listener = jest.fn()
|
|
151
|
+
target.addEventListener('test', listener, { passive: true })
|
|
152
|
+
|
|
153
|
+
target.dispatchEvent(new Event('test'))
|
|
154
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should handle object listeners with handleEvent', () => {
|
|
158
|
+
const listener = {
|
|
159
|
+
handleEvent: jest.fn(),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
target.addEventListener('test', listener)
|
|
163
|
+
target.dispatchEvent(new Event('test'))
|
|
164
|
+
|
|
165
|
+
expect(listener.handleEvent).toHaveBeenCalledTimes(1)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should return false when event is prevented', () => {
|
|
169
|
+
const listener = (event: Event) => {
|
|
170
|
+
event.preventDefault()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
target.addEventListener('test', listener)
|
|
174
|
+
const event = new Event('test', { cancelable: true })
|
|
175
|
+
const result = target.dispatchEvent(event)
|
|
176
|
+
|
|
177
|
+
expect(result).toBe(false)
|
|
178
|
+
expect(event.defaultPrevented).toBe(true)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should set event properties during dispatch', () => {
|
|
182
|
+
let capturedEvent: Event | null = null
|
|
183
|
+
|
|
184
|
+
const listener = (event: Event) => {
|
|
185
|
+
capturedEvent = event
|
|
186
|
+
expect(event.target).toBe(target)
|
|
187
|
+
expect(event.currentTarget).toBe(target)
|
|
188
|
+
expect(event.eventPhase).toBe(Event.AT_TARGET)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
target.addEventListener('test', listener)
|
|
192
|
+
target.dispatchEvent(new Event('test'))
|
|
193
|
+
|
|
194
|
+
expect(capturedEvent).not.toBeNull()
|
|
195
|
+
// After dispatch, these should be reset
|
|
196
|
+
expect(capturedEvent!.currentTarget).toBeNull()
|
|
197
|
+
expect(capturedEvent!.eventPhase).toBe(Event.NONE)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should handle bubbling events', () => {
|
|
201
|
+
const listener = jest.fn()
|
|
202
|
+
target.addEventListener('test', listener)
|
|
203
|
+
|
|
204
|
+
const bubblingEvent = new Event('test', { bubbles: true })
|
|
205
|
+
target.dispatchEvent(bubblingEvent)
|
|
206
|
+
|
|
207
|
+
expect(listener).toHaveBeenCalledWith(bubblingEvent)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('should handle error in listener gracefully', () => {
|
|
211
|
+
const errorListener = () => {
|
|
212
|
+
throw new Error('Test error')
|
|
213
|
+
}
|
|
214
|
+
const normalListener = jest.fn()
|
|
215
|
+
|
|
216
|
+
// Mock console.error to avoid test output noise
|
|
217
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
|
218
|
+
|
|
219
|
+
target.addEventListener('test', errorListener)
|
|
220
|
+
target.addEventListener('test', normalListener)
|
|
221
|
+
|
|
222
|
+
target.dispatchEvent(new Event('test'))
|
|
223
|
+
|
|
224
|
+
expect(normalListener).toHaveBeenCalledTimes(1)
|
|
225
|
+
expect(consoleErrorSpy).toHaveBeenCalled()
|
|
226
|
+
|
|
227
|
+
consoleErrorSpy.mockRestore()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should ignore null listeners', () => {
|
|
231
|
+
// Should not throw
|
|
232
|
+
target.addEventListener('test', null)
|
|
233
|
+
target.removeEventListener('test', null)
|
|
234
|
+
target.dispatchEvent(new Event('test'))
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should handle composed path', () => {
|
|
238
|
+
let composedPath: readonly EventTarget[] | null = null
|
|
239
|
+
|
|
240
|
+
const listener = (event: Event) => {
|
|
241
|
+
composedPath = event.composedPath()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
target.addEventListener('test', listener)
|
|
245
|
+
target.dispatchEvent(new Event('test'))
|
|
246
|
+
|
|
247
|
+
expect(composedPath).toEqual([target])
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should prevent duplicate listeners', () => {
|
|
251
|
+
const listener = jest.fn()
|
|
252
|
+
|
|
253
|
+
target.addEventListener('test', listener)
|
|
254
|
+
target.addEventListener('test', listener) // Same listener
|
|
255
|
+
|
|
256
|
+
target.dispatchEvent(new Event('test'))
|
|
257
|
+
expect(listener).toHaveBeenCalledTimes(1) // Only called once
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
describe('Event flow integration', () => {
|
|
262
|
+
it('should work with CustomEvent dispatch', () => {
|
|
263
|
+
const target = new EventTarget()
|
|
264
|
+
const listener = jest.fn()
|
|
265
|
+
const detail = { message: 'hello' }
|
|
266
|
+
|
|
267
|
+
target.addEventListener('custom', listener)
|
|
268
|
+
|
|
269
|
+
const event = new CustomEvent('custom', {
|
|
270
|
+
detail,
|
|
271
|
+
bubbles: true,
|
|
272
|
+
cancelable: true,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
const result = target.dispatchEvent(event)
|
|
276
|
+
|
|
277
|
+
expect(listener).toHaveBeenCalledWith(event)
|
|
278
|
+
expect(listener.mock.calls[0][0].detail).toEqual(detail)
|
|
279
|
+
expect(result).toBe(true)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('should handle stopImmediatePropagation', () => {
|
|
283
|
+
const target = new EventTarget()
|
|
284
|
+
const listener1 = jest.fn((event: Event) => {
|
|
285
|
+
event.stopImmediatePropagation()
|
|
286
|
+
})
|
|
287
|
+
const listener2 = jest.fn()
|
|
288
|
+
|
|
289
|
+
target.addEventListener('test', listener1)
|
|
290
|
+
target.addEventListener('test', listener2)
|
|
291
|
+
|
|
292
|
+
target.dispatchEvent(new Event('test'))
|
|
293
|
+
|
|
294
|
+
expect(listener1).toHaveBeenCalledTimes(1)
|
|
295
|
+
expect(listener2).toHaveBeenCalledTimes(0) // Stopped
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
describe('Global polyfill installation', () => {
|
|
300
|
+
it('should make Event available globally', () => {
|
|
301
|
+
// This tests that our polyfill setup works
|
|
302
|
+
expect(typeof (global as any).Event).toBe('function')
|
|
303
|
+
expect(typeof (global as any).CustomEvent).toBe('function')
|
|
304
|
+
|
|
305
|
+
const globalEvent = new (global as any).Event('test')
|
|
306
|
+
expect(globalEvent.type).toBe('test')
|
|
307
|
+
|
|
308
|
+
const globalCustomEvent = new (global as any).CustomEvent('custom', {
|
|
309
|
+
detail: 'test',
|
|
310
|
+
})
|
|
311
|
+
expect(globalCustomEvent.detail).toBe('test')
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
})
|
|
@@ -5,29 +5,17 @@ import { SvgXml } from 'react-native-svg'
|
|
|
5
5
|
import type { XmlProps } from 'react-native-svg'
|
|
6
6
|
import { useFontScale, useTheme } from '../../hooks'
|
|
7
7
|
|
|
8
|
-
// @ts-ignore
|
|
9
8
|
import * as accounts from '@planningcenter/icons/paths/accounts'
|
|
10
|
-
// @ts-ignore
|
|
11
9
|
import * as api from '@planningcenter/icons/paths/api'
|
|
12
|
-
// @ts-ignore
|
|
13
10
|
import * as brand from '@planningcenter/icons/paths/brand'
|
|
14
|
-
// @ts-ignore
|
|
15
11
|
import * as calendar from '@planningcenter/icons/paths/calendar'
|
|
16
|
-
// @ts-ignore
|
|
17
12
|
import * as chat from '@planningcenter/icons/paths/chat'
|
|
18
|
-
// @ts-ignore
|
|
19
13
|
import * as churchCenter from '@planningcenter/icons/paths/church-center'
|
|
20
|
-
// @ts-ignore
|
|
21
14
|
import * as general from '@planningcenter/icons/paths/general'
|
|
22
|
-
// @ts-ignore
|
|
23
15
|
import * as groups from '@planningcenter/icons/paths/groups'
|
|
24
|
-
// @ts-ignore
|
|
25
16
|
import * as logomark from '@planningcenter/icons/paths/logomark'
|
|
26
|
-
// @ts-ignore
|
|
27
17
|
import * as people from '@planningcenter/icons/paths/people'
|
|
28
|
-
// @ts-ignore
|
|
29
18
|
import * as services from '@planningcenter/icons/paths/services'
|
|
30
|
-
// @ts-ignore
|
|
31
19
|
import * as publishing from '@planningcenter/icons/paths/publishing'
|
|
32
20
|
|
|
33
21
|
// =================================
|
|
@@ -57,7 +45,22 @@ export type IconStyle = ViewStyle & {
|
|
|
57
45
|
}
|
|
58
46
|
|
|
59
47
|
export type IconSetName = keyof typeof ICONS
|
|
60
|
-
|
|
48
|
+
|
|
49
|
+
type IconName<T extends IconSetName> = keyof (typeof ICONS)[T] & string
|
|
50
|
+
|
|
51
|
+
export type IconString =
|
|
52
|
+
| `accounts.${IconName<'accounts'>}`
|
|
53
|
+
| `api.${IconName<'api'>}`
|
|
54
|
+
| `brand.${IconName<'brand'>}`
|
|
55
|
+
| `calendar.${IconName<'calendar'>}`
|
|
56
|
+
| `chat.${IconName<'chat'>}`
|
|
57
|
+
| `churchCenter.${IconName<'churchCenter'>}`
|
|
58
|
+
| `general.${IconName<'general'>}`
|
|
59
|
+
| `groups.${IconName<'groups'>}`
|
|
60
|
+
| `logomark.${IconName<'logomark'>}`
|
|
61
|
+
| `people.${IconName<'people'>}`
|
|
62
|
+
| `services.${IconName<'services'>}`
|
|
63
|
+
| `publishing.${IconName<'publishing'>}`
|
|
61
64
|
|
|
62
65
|
// =================================
|
|
63
66
|
// ====== Component ================
|
|
@@ -137,7 +140,7 @@ const useGetIconSize = (size?: number, style?: IconStyle, maxFontSizeMultiplier?
|
|
|
137
140
|
const getIconPath = (name: IconString): string => {
|
|
138
141
|
const [setName, iconName] = name.split('.')
|
|
139
142
|
|
|
140
|
-
return ICONS[setName as IconSetName]?.[iconName]
|
|
143
|
+
return (ICONS[setName as IconSetName] as Record<string, string>)?.[iconName]
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
// =================================
|
package/src/index.tsx
CHANGED
|
@@ -8,3 +8,5 @@ export * from './types'
|
|
|
8
8
|
export { platformFontWeightBold, Session, TemporaryDefaultColorsType, Uri } from './utils'
|
|
9
9
|
export * from './utils/client'
|
|
10
10
|
export * from './utils/native_adapters'
|
|
11
|
+
export { default as Event } from './polyfills/events/Event'
|
|
12
|
+
export { CustomEvent } from './polyfills/events/CustomEvent'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This module implements the `CustomEvent` interface from the DOM.
|
|
10
|
+
* See https://dom.spec.whatwg.org/#interface-customevent.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import Event from './Event'
|
|
14
|
+
import type { EventInit as RNEventInit } from './Event'
|
|
15
|
+
|
|
16
|
+
export interface CustomEventInit extends RNEventInit {
|
|
17
|
+
detail?: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class CustomEvent extends Event {
|
|
21
|
+
private _detail: unknown
|
|
22
|
+
|
|
23
|
+
constructor(type: string, options?: CustomEventInit) {
|
|
24
|
+
super(type, options)
|
|
25
|
+
|
|
26
|
+
this._detail = options?.detail
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get detail(): unknown {
|
|
30
|
+
return this._detail
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This module implements the `Event` interface from the DOM.
|
|
10
|
+
* See https://dom.spec.whatwg.org/#interface-event.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type RNEventTarget from './EventTarget'
|
|
14
|
+
import {
|
|
15
|
+
getComposedPath,
|
|
16
|
+
getCurrentTarget,
|
|
17
|
+
getEventPhase,
|
|
18
|
+
getInPassiveListenerFlag,
|
|
19
|
+
getIsTrusted,
|
|
20
|
+
getTarget,
|
|
21
|
+
setStopImmediatePropagationFlag,
|
|
22
|
+
setStopPropagationFlag,
|
|
23
|
+
setIsTrusted,
|
|
24
|
+
setEventPhase,
|
|
25
|
+
} from './internals/EventInternals'
|
|
26
|
+
import { setPlatformObject } from '../webidl/PlatformObjects'
|
|
27
|
+
|
|
28
|
+
export interface EventInit {
|
|
29
|
+
bubbles?: boolean
|
|
30
|
+
cancelable?: boolean
|
|
31
|
+
composed?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type EventPhase = 0 | 1 | 2 | 3
|
|
35
|
+
|
|
36
|
+
export default class Event {
|
|
37
|
+
static NONE: 0
|
|
38
|
+
static CAPTURING_PHASE: 1
|
|
39
|
+
static AT_TARGET: 2
|
|
40
|
+
static BUBBLING_PHASE: 3
|
|
41
|
+
|
|
42
|
+
readonly NONE!: 0
|
|
43
|
+
readonly CAPTURING_PHASE!: 1
|
|
44
|
+
readonly AT_TARGET!: 2
|
|
45
|
+
readonly BUBBLING_PHASE!: 3
|
|
46
|
+
|
|
47
|
+
private _bubbles: boolean
|
|
48
|
+
private _cancelable: boolean
|
|
49
|
+
private _composed: boolean
|
|
50
|
+
private _type: string
|
|
51
|
+
|
|
52
|
+
private _defaultPrevented: boolean = false
|
|
53
|
+
private _timeStamp: number = performance.now()
|
|
54
|
+
|
|
55
|
+
// Symbol-keyed fields are internal and managed via helpers; not declared here.
|
|
56
|
+
|
|
57
|
+
constructor(type: string, options?: EventInit | null) {
|
|
58
|
+
if (arguments.length < 1) {
|
|
59
|
+
throw new TypeError("Failed to construct 'Event': 1 argument required, but only 0 present.")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const typeOfOptions = typeof options
|
|
63
|
+
if (options != null && typeOfOptions !== 'object' && typeOfOptions !== 'function') {
|
|
64
|
+
throw new TypeError(
|
|
65
|
+
"Failed to construct 'Event': The provided value is not of type 'EventInit'."
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this._type = String(type)
|
|
70
|
+
this._bubbles = Boolean((options as EventInit | undefined)?.bubbles)
|
|
71
|
+
this._cancelable = Boolean((options as EventInit | undefined)?.cancelable)
|
|
72
|
+
this._composed = Boolean((options as EventInit | undefined)?.composed)
|
|
73
|
+
|
|
74
|
+
// Initialize internal flags
|
|
75
|
+
setIsTrusted(this, false)
|
|
76
|
+
setEventPhase(this, 0 as 0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get bubbles(): boolean {
|
|
80
|
+
return this._bubbles
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get cancelable(): boolean {
|
|
84
|
+
return this._cancelable
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get composed(): boolean {
|
|
88
|
+
return this._composed
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get currentTarget(): RNEventTarget | null {
|
|
92
|
+
return getCurrentTarget(this)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get defaultPrevented(): boolean {
|
|
96
|
+
return this._defaultPrevented
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get eventPhase(): EventPhase {
|
|
100
|
+
return getEventPhase(this)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get isTrusted(): boolean {
|
|
104
|
+
return getIsTrusted(this)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get target(): RNEventTarget | null {
|
|
108
|
+
return getTarget(this)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get timeStamp(): number {
|
|
112
|
+
return this._timeStamp
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get type(): string {
|
|
116
|
+
return this._type
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
composedPath(): ReadonlyArray<RNEventTarget> {
|
|
120
|
+
return getComposedPath(this).slice()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
preventDefault(): void {
|
|
124
|
+
if (!this._cancelable) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (getInPassiveListenerFlag(this)) {
|
|
129
|
+
console.error(new Error('Unable to preventDefault inside passive event listener invocation.'))
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this._defaultPrevented = true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
stopImmediatePropagation(): void {
|
|
137
|
+
setStopPropagationFlag(this, true)
|
|
138
|
+
setStopImmediatePropagationFlag(this, true)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
stopPropagation(): void {
|
|
142
|
+
setStopPropagationFlag(this, true)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Object.defineProperty(Event, 'NONE', {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
value: 0,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
Object.defineProperty(Event.prototype, 'NONE', {
|
|
152
|
+
enumerable: true,
|
|
153
|
+
value: 0,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
Object.defineProperty(Event, 'CAPTURING_PHASE', {
|
|
157
|
+
enumerable: true,
|
|
158
|
+
value: 1,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
Object.defineProperty(Event.prototype, 'CAPTURING_PHASE', {
|
|
162
|
+
enumerable: true,
|
|
163
|
+
value: 1,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
Object.defineProperty(Event, 'AT_TARGET', {
|
|
167
|
+
enumerable: true,
|
|
168
|
+
value: 2,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
Object.defineProperty(Event.prototype, 'AT_TARGET', {
|
|
172
|
+
enumerable: true,
|
|
173
|
+
value: 2,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
Object.defineProperty(Event, 'BUBBLING_PHASE', {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
value: 3,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
Object.defineProperty(Event.prototype, 'BUBBLING_PHASE', {
|
|
182
|
+
enumerable: true,
|
|
183
|
+
value: 3,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
setPlatformObject(Event)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers to implement event handler IDL attributes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type EventTarget from './EventTarget'
|
|
6
|
+
import type { EventCallback } from './EventTarget'
|
|
7
|
+
|
|
8
|
+
type EventHandler = Readonly<{
|
|
9
|
+
handleEvent: EventCallback
|
|
10
|
+
}>
|
|
11
|
+
type EventHandlerAttributeMap = Map<string, EventHandler | null>
|
|
12
|
+
|
|
13
|
+
const EVENT_HANDLER_CONTENT_ATTRIBUTE_MAP_KEY = Symbol('eventHandlerAttributeMap')
|
|
14
|
+
|
|
15
|
+
function getEventHandlerAttributeMap(
|
|
16
|
+
target: EventTarget
|
|
17
|
+
): EventHandlerAttributeMap | null | undefined {
|
|
18
|
+
return (target as any)[EVENT_HANDLER_CONTENT_ATTRIBUTE_MAP_KEY]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function setEventHandlerAttributeMap(target: EventTarget, map: EventHandlerAttributeMap | null) {
|
|
22
|
+
;(target as any)[EVENT_HANDLER_CONTENT_ATTRIBUTE_MAP_KEY] = map
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getEventHandlerAttribute(target: EventTarget, type: string): EventCallback | null {
|
|
26
|
+
const listener = getEventHandlerAttributeMap(target)?.get(type)
|
|
27
|
+
return listener != null ? listener.handleEvent : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function setEventHandlerAttribute(
|
|
31
|
+
target: EventTarget,
|
|
32
|
+
type: string,
|
|
33
|
+
callback: EventCallback | null | undefined
|
|
34
|
+
): void {
|
|
35
|
+
let map = getEventHandlerAttributeMap(target)
|
|
36
|
+
if (map != null) {
|
|
37
|
+
const currentListener = map.get(type)
|
|
38
|
+
if (currentListener) {
|
|
39
|
+
target.removeEventListener(type, currentListener)
|
|
40
|
+
map.delete(type)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
callback != null &&
|
|
46
|
+
(typeof callback === 'function' || typeof (callback as any) === 'object')
|
|
47
|
+
) {
|
|
48
|
+
const listener: EventHandler = {
|
|
49
|
+
handleEvent: callback as EventCallback,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
target.addEventListener(type, listener)
|
|
54
|
+
if (map == null) {
|
|
55
|
+
map = new Map()
|
|
56
|
+
setEventHandlerAttributeMap(target, map)
|
|
57
|
+
}
|
|
58
|
+
map.set(type, listener)
|
|
59
|
+
} catch (_e) {
|
|
60
|
+
// Assigning incorrect listener does not throw in setters.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (map != null && map.size === 0) {
|
|
65
|
+
setEventHandlerAttributeMap(target, null)
|
|
66
|
+
}
|
|
67
|
+
}
|