@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.
- package/build/components/display/avatar.d.ts +3 -1
- package/build/components/display/avatar.d.ts.map +1 -1
- package/build/components/display/avatar.js +2 -2
- package/build/components/display/avatar.js.map +1 -1
- package/build/components/display/avatar_group.d.ts +3 -1
- package/build/components/display/avatar_group.d.ts.map +1 -1
- package/build/components/display/avatar_group.js +2 -2
- package/build/components/display/avatar_group.js.map +1 -1
- package/build/components/primitive/avatar_primitive.d.ts +2 -0
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +20 -19
- package/build/components/primitive/avatar_primitive.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/package.json +5 -5
- package/src/__tests__/event-polyfill.test.ts +314 -0
- package/src/components/display/avatar.tsx +5 -1
- package/src/components/display/avatar_group.tsx +5 -1
- package/src/components/primitive/avatar_primitive.tsx +35 -19
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.15.0-rc.
|
|
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": "^
|
|
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": "
|
|
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({
|
|
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
|
|
115
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
206
|
-
const {
|
|
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
|
|
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 = ({
|
|
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
|
|
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
|
+
}
|