@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.
Files changed (51) hide show
  1. package/build/components/display/icon.d.ts +26 -13
  2. package/build/components/display/icon.d.ts.map +1 -1
  3. package/build/components/display/icon.js +0 -12
  4. package/build/components/display/icon.js.map +1 -1
  5. package/build/index.d.ts +2 -0
  6. package/build/index.d.ts.map +1 -1
  7. package/build/index.js +2 -0
  8. package/build/index.js.map +1 -1
  9. package/build/polyfills/events/CustomEvent.d.ts +21 -0
  10. package/build/polyfills/events/CustomEvent.d.ts.map +1 -0
  11. package/build/polyfills/events/CustomEvent.js +22 -0
  12. package/build/polyfills/events/CustomEvent.js.map +1 -0
  13. package/build/polyfills/events/Event.d.ts +49 -0
  14. package/build/polyfills/events/Event.d.ts.map +1 -0
  15. package/build/polyfills/events/Event.js +125 -0
  16. package/build/polyfills/events/Event.js.map +1 -0
  17. package/build/polyfills/events/EventHandlerAttributes.d.ts +8 -0
  18. package/build/polyfills/events/EventHandlerAttributes.d.ts.map +1 -0
  19. package/build/polyfills/events/EventHandlerAttributes.js +46 -0
  20. package/build/polyfills/events/EventHandlerAttributes.js.map +1 -0
  21. package/build/polyfills/events/EventTarget.d.ts +33 -0
  22. package/build/polyfills/events/EventTarget.d.ts.map +1 -0
  23. package/build/polyfills/events/EventTarget.js +238 -0
  24. package/build/polyfills/events/EventTarget.js.map +1 -0
  25. package/build/polyfills/events/internals/EventInternals.d.ts +30 -0
  26. package/build/polyfills/events/internals/EventInternals.d.ts.map +1 -0
  27. package/build/polyfills/events/internals/EventInternals.js +76 -0
  28. package/build/polyfills/events/internals/EventInternals.js.map +1 -0
  29. package/build/polyfills/events/internals/EventTargetInternals.d.ts +9 -0
  30. package/build/polyfills/events/internals/EventTargetInternals.d.ts.map +1 -0
  31. package/build/polyfills/events/internals/EventTargetInternals.js +11 -0
  32. package/build/polyfills/events/internals/EventTargetInternals.js.map +1 -0
  33. package/build/polyfills/webidl/PlatformObjects.d.ts +31 -0
  34. package/build/polyfills/webidl/PlatformObjects.d.ts.map +1 -0
  35. package/build/polyfills/webidl/PlatformObjects.js +39 -0
  36. package/build/polyfills/webidl/PlatformObjects.js.map +1 -0
  37. package/build/screens/design_system_screen.js +1 -1
  38. package/build/screens/design_system_screen.js.map +1 -1
  39. package/package.json +5 -5
  40. package/src/__tests__/event-polyfill.test.ts +314 -0
  41. package/src/components/display/icon.tsx +17 -14
  42. package/src/index.tsx +2 -0
  43. package/src/polyfills/events/CustomEvent.ts +32 -0
  44. package/src/polyfills/events/Event.ts +186 -0
  45. package/src/polyfills/events/EventHandlerAttributes.ts +67 -0
  46. package/src/polyfills/events/EventTarget.ts +360 -0
  47. package/src/polyfills/events/README.md +1 -0
  48. package/src/polyfills/events/internals/EventInternals.ts +95 -0
  49. package/src/polyfills/events/internals/EventTargetInternals.ts +16 -0
  50. package/src/polyfills/webidl/PlatformObjects.ts +50 -0
  51. 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
- export type IconString = `${IconSetName}.${(typeof ICONS)[IconSetName]}`
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
+ }