@planningcenter/chat-react-native 3.15.0-rc.6 → 3.15.0-rc.8

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 (58) hide show
  1. package/build/components/display/avatar.d.ts +3 -1
  2. package/build/components/display/avatar.d.ts.map +1 -1
  3. package/build/components/display/avatar.js +2 -2
  4. package/build/components/display/avatar.js.map +1 -1
  5. package/build/components/display/avatar_group.d.ts +3 -1
  6. package/build/components/display/avatar_group.d.ts.map +1 -1
  7. package/build/components/display/avatar_group.js +2 -2
  8. package/build/components/display/avatar_group.js.map +1 -1
  9. package/build/components/primitive/avatar_primitive.d.ts +2 -0
  10. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  11. package/build/components/primitive/avatar_primitive.js +20 -19
  12. package/build/components/primitive/avatar_primitive.js.map +1 -1
  13. package/build/index.d.ts +2 -0
  14. package/build/index.d.ts.map +1 -1
  15. package/build/index.js +2 -0
  16. package/build/index.js.map +1 -1
  17. package/build/polyfills/events/CustomEvent.d.ts +21 -0
  18. package/build/polyfills/events/CustomEvent.d.ts.map +1 -0
  19. package/build/polyfills/events/CustomEvent.js +22 -0
  20. package/build/polyfills/events/CustomEvent.js.map +1 -0
  21. package/build/polyfills/events/Event.d.ts +49 -0
  22. package/build/polyfills/events/Event.d.ts.map +1 -0
  23. package/build/polyfills/events/Event.js +125 -0
  24. package/build/polyfills/events/Event.js.map +1 -0
  25. package/build/polyfills/events/EventHandlerAttributes.d.ts +8 -0
  26. package/build/polyfills/events/EventHandlerAttributes.d.ts.map +1 -0
  27. package/build/polyfills/events/EventHandlerAttributes.js +46 -0
  28. package/build/polyfills/events/EventHandlerAttributes.js.map +1 -0
  29. package/build/polyfills/events/EventTarget.d.ts +33 -0
  30. package/build/polyfills/events/EventTarget.d.ts.map +1 -0
  31. package/build/polyfills/events/EventTarget.js +238 -0
  32. package/build/polyfills/events/EventTarget.js.map +1 -0
  33. package/build/polyfills/events/internals/EventInternals.d.ts +30 -0
  34. package/build/polyfills/events/internals/EventInternals.d.ts.map +1 -0
  35. package/build/polyfills/events/internals/EventInternals.js +76 -0
  36. package/build/polyfills/events/internals/EventInternals.js.map +1 -0
  37. package/build/polyfills/events/internals/EventTargetInternals.d.ts +9 -0
  38. package/build/polyfills/events/internals/EventTargetInternals.d.ts.map +1 -0
  39. package/build/polyfills/events/internals/EventTargetInternals.js +11 -0
  40. package/build/polyfills/events/internals/EventTargetInternals.js.map +1 -0
  41. package/build/polyfills/webidl/PlatformObjects.d.ts +31 -0
  42. package/build/polyfills/webidl/PlatformObjects.d.ts.map +1 -0
  43. package/build/polyfills/webidl/PlatformObjects.js +39 -0
  44. package/build/polyfills/webidl/PlatformObjects.js.map +1 -0
  45. package/package.json +5 -5
  46. package/src/__tests__/event-polyfill.test.ts +314 -0
  47. package/src/components/display/avatar.tsx +5 -1
  48. package/src/components/display/avatar_group.tsx +5 -1
  49. package/src/components/primitive/avatar_primitive.tsx +35 -19
  50. package/src/index.tsx +2 -0
  51. package/src/polyfills/events/CustomEvent.ts +32 -0
  52. package/src/polyfills/events/Event.ts +186 -0
  53. package/src/polyfills/events/EventHandlerAttributes.ts +67 -0
  54. package/src/polyfills/events/EventTarget.ts +360 -0
  55. package/src/polyfills/events/README.md +1 -0
  56. package/src/polyfills/events/internals/EventInternals.ts +95 -0
  57. package/src/polyfills/events/internals/EventTargetInternals.ts +16 -0
  58. package/src/polyfills/webidl/PlatformObjects.ts +50 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.15.0-rc.6",
3
+ "version": "3.15.0-rc.8",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "@react-navigation/elements": "*",
27
27
  "@react-navigation/native": ">=7.0.0",
28
28
  "@react-navigation/native-stack": ">=7.0.0",
29
- "@shopify/flash-list": "*",
29
+ "@shopify/flash-list": "<2.0.0",
30
30
  "@tanstack/react-query": "^5.0.0",
31
31
  "color": "^3.1.2",
32
32
  "lodash": "*",
@@ -37,7 +37,7 @@
37
37
  "react-native-device-info": "*",
38
38
  "react-native-gesture-handler": "*",
39
39
  "react-native-linear-gradient": "^2.8.3",
40
- "react-native-reanimated": "*",
40
+ "react-native-reanimated": "<4.0.0",
41
41
  "react-native-safe-area-context": ">=4.0.0",
42
42
  "react-native-svg": "*"
43
43
  },
@@ -46,7 +46,7 @@
46
46
  "@testing-library/react-hooks": "^8.0.1",
47
47
  "@types/jest": "^29.5.14",
48
48
  "@typescript-eslint/parser": "^8.32.0",
49
- "expo-module-scripts": "^4.1.6",
49
+ "expo-module-scripts": "^5.0.7",
50
50
  "fast-text-encoding": "^1.0.6",
51
51
  "jest": "^29.7.0",
52
52
  "jest-fetch-mock": "^3.0.3",
@@ -55,5 +55,5 @@
55
55
  "react-native-url-polyfill": "^2.0.0",
56
56
  "typescript": "<5.6.0"
57
57
  },
58
- "gitHead": "c9061788178feeb893f4e54b88f2b515658061d7"
58
+ "gitHead": "98f7a5fc0b4d1fa7127a7fc4e151dc706147c29f"
59
59
  }
@@ -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
+ })
@@ -12,6 +12,8 @@ interface AvatarProps {
12
12
  presence?: AvatarPresenceProps['presence']
13
13
  showFallback?: boolean
14
14
  fallbackIconName?: IconString
15
+ style?: AvatarRootProps['style']
16
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
15
17
  }
16
18
 
17
19
  export function Avatar({
@@ -20,11 +22,13 @@ export function Avatar({
20
22
  sourceUri,
21
23
  showFallback = false,
22
24
  fallbackIconName = 'general.person',
25
+ style,
26
+ maxFontSizeMultiplier,
23
27
  }: AvatarProps) {
24
28
  const shouldShowFallback = showFallback || !sourceUri
25
29
 
26
30
  return (
27
- <AvatarPrimitive.Root size={size}>
31
+ <AvatarPrimitive.Root size={size} style={style} maxFontSizeMultiplier={maxFontSizeMultiplier}>
28
32
  <AvatarPrimitive.Mask>
29
33
  {shouldShowFallback ? (
30
34
  <AvatarPrimitive.ImageFallback name={fallbackIconName} />
@@ -10,6 +10,8 @@ interface AvatarGroupDisplayProps {
10
10
  showFallback?: boolean
11
11
  fallbackIconName?: IconString
12
12
  size?: AvatarRootProps['size']
13
+ style?: AvatarRootProps['style']
14
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
13
15
  }
14
16
 
15
17
  export function AvatarGroup({
@@ -17,11 +19,13 @@ export function AvatarGroup({
17
19
  showFallback = false,
18
20
  fallbackIconName = 'general.person',
19
21
  size = 'lg',
22
+ style,
23
+ maxFontSizeMultiplier,
20
24
  }: AvatarGroupDisplayProps) {
21
25
  const shouldShowFallback = showFallback || !sourceUris || sourceUris.length === 0
22
26
 
23
27
  return (
24
- <AvatarPrimitive.Root size={size}>
28
+ <AvatarPrimitive.Root size={size} style={style} maxFontSizeMultiplier={maxFontSizeMultiplier}>
25
29
  <AvatarPrimitive.Mask>
26
30
  {shouldShowFallback ? (
27
31
  <AvatarPrimitive.ImageFallback name={fallbackIconName} />
@@ -85,6 +85,7 @@ interface AvatarContextType {
85
85
  size: AvatarSize
86
86
  allImagesLoaded: boolean
87
87
  setAllImagesLoaded: React.Dispatch<React.SetStateAction<boolean>>
88
+ maxFontSizeMultiplier: number
88
89
  }
89
90
 
90
91
  const AvatarContext = createContext<AvatarContextType | null>(null)
@@ -104,15 +105,24 @@ function useAvatarContext() {
104
105
  interface AvatarRootProps {
105
106
  children: React.ReactNode
106
107
  size?: AvatarSize
108
+ style?: ViewProps['style']
109
+ maxFontSizeMultiplier?: number
107
110
  }
108
111
 
109
- function AvatarRoot({ children, size = 'md' }: AvatarRootProps) {
112
+ function AvatarRoot({
113
+ children,
114
+ size = 'md',
115
+ style,
116
+ maxFontSizeMultiplier = MAX_FONT_SIZE_MULTIPLIER,
117
+ }: AvatarRootProps) {
110
118
  const [allImagesLoaded, setAllImagesLoaded] = useState(false)
111
- const styles = useStyles({ size })
119
+ const styles = useStyles({ size, maxFontSizeMultiplier })
112
120
 
113
121
  return (
114
- <AvatarContext.Provider value={{ size, allImagesLoaded, setAllImagesLoaded }}>
115
- <View style={styles.rootContainer}>{children}</View>
122
+ <AvatarContext.Provider
123
+ value={{ size, allImagesLoaded, setAllImagesLoaded, maxFontSizeMultiplier }}
124
+ >
125
+ <View style={[styles.rootContainer, style]}>{children}</View>
116
126
  </AvatarContext.Provider>
117
127
  )
118
128
  }
@@ -126,7 +136,8 @@ AvatarRoot.displayName = 'Avatar.Root'
126
136
  type AvatarMaskProps = ViewProps
127
137
 
128
138
  function AvatarMask({ children, ...props }: AvatarMaskProps) {
129
- const styles = useStyles()
139
+ const { maxFontSizeMultiplier } = useAvatarContext()
140
+ const styles = useStyles({ maxFontSizeMultiplier })
130
141
 
131
142
  return (
132
143
  <View style={styles.mask} {...props}>
@@ -146,8 +157,8 @@ interface AvatarImageProps extends Omit<ImageProps, 'source' | 'alt'> {
146
157
  }
147
158
 
148
159
  function AvatarImage({ sourceUri, ...props }: AvatarImageProps) {
149
- const { size } = useAvatarContext()
150
- const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })
160
+ const { size, maxFontSizeMultiplier } = useAvatarContext()
161
+ const fontScale = useFontScale({ maxFontSizeMultiplier })
151
162
  const scaledAvatarSize = AVATAR_PX[size] * fontScale
152
163
 
153
164
  return <Image source={{ uri: sourceUri }} loaderSize={scaledAvatarSize} {...props} alt="" />
@@ -172,9 +183,9 @@ interface AvatarImageFallbackProps {
172
183
  }
173
184
 
174
185
  function AvatarImageFallback({ name = 'general.person' }: AvatarImageFallbackProps) {
175
- const { size } = useAvatarContext()
176
- const styles = useStyles()
177
- const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })
186
+ const { size, maxFontSizeMultiplier } = useAvatarContext()
187
+ const styles = useStyles({ maxFontSizeMultiplier })
188
+ const fontScale = useFontScale({ maxFontSizeMultiplier })
178
189
  const scaledIconSize = AVATAR_FALLBACK_ICON_PX[size] * fontScale
179
190
 
180
191
  return (
@@ -202,8 +213,8 @@ interface AvatarGroupProps {
202
213
  type AvatarIndex = 0 | 1 | 2 | 3
203
214
 
204
215
  function AvatarGroup({ sourceUris }: AvatarGroupProps) {
205
- const styles = useStyles()
206
- const { setAllImagesLoaded } = useAvatarContext()
216
+ const { setAllImagesLoaded, maxFontSizeMultiplier } = useAvatarContext()
217
+ const styles = useStyles({ maxFontSizeMultiplier })
207
218
  const [loadingStatus, setLoadingStatus] = useState<Record<AvatarIndex, boolean>>({
208
219
  0: false,
209
220
  1: false,
@@ -312,9 +323,9 @@ AvatarGroup.displayName = 'Avatar.Group'
312
323
  // =================================
313
324
 
314
325
  function AvatarGroupLoader() {
315
- const { size, allImagesLoaded } = useAvatarContext()
316
- const styles = useStyles({ size })
317
- const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })
326
+ const { size, allImagesLoaded, maxFontSizeMultiplier } = useAvatarContext()
327
+ const styles = useStyles({ size, maxFontSizeMultiplier })
328
+ const fontScale = useFontScale({ maxFontSizeMultiplier })
318
329
  const scaledSpinnerSize = AVATAR_PX[size] * fontScale
319
330
 
320
331
  if (allImagesLoaded) return null
@@ -337,8 +348,8 @@ interface AvatarPresenceProps extends ViewProps {
337
348
  }
338
349
 
339
350
  function AvatarPresence({ presence, ...props }: AvatarPresenceProps) {
340
- const { size } = useAvatarContext()
341
- const styles = useStyles({ size, presence })
351
+ const { size, maxFontSizeMultiplier } = useAvatarContext()
352
+ const styles = useStyles({ size, presence, maxFontSizeMultiplier })
342
353
 
343
354
  return <View style={styles.presence} {...props} />
344
355
  }
@@ -352,11 +363,16 @@ AvatarPresence.displayName = 'Avatar.Presence'
352
363
  interface Styles {
353
364
  size?: AvatarSize
354
365
  presence?: AvatarPresenceType
366
+ maxFontSizeMultiplier?: number
355
367
  }
356
368
 
357
- const useStyles = ({ size = 'md', presence = 'offline' }: Styles = {}) => {
369
+ const useStyles = ({
370
+ size = 'md',
371
+ presence = 'offline',
372
+ maxFontSizeMultiplier = MAX_FONT_SIZE_MULTIPLIER,
373
+ }: Styles = {}) => {
358
374
  const { colors } = useTheme()
359
- const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })
375
+ const fontScale = useFontScale({ maxFontSizeMultiplier })
360
376
  const PRESENCE_COLOR = {
361
377
  online: colors.fillColorInteractionOnlineDefault,
362
378
  offline: colors.iconColorDefaultDisabled,
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
+ }