@planningcenter/chat-react-native 3.15.0-rc.0 → 3.15.0-rc.10

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 (204) hide show
  1. package/build/components/conversations/conversation_actions.js +1 -1
  2. package/build/components/conversations/conversation_actions.js.map +1 -1
  3. package/build/components/conversations/conversation_preview.d.ts.map +1 -1
  4. package/build/components/conversations/conversation_preview.js +9 -5
  5. package/build/components/conversations/conversation_preview.js.map +1 -1
  6. package/build/components/conversations/mute_indicator.d.ts.map +1 -1
  7. package/build/components/conversations/mute_indicator.js +3 -1
  8. package/build/components/conversations/mute_indicator.js.map +1 -1
  9. package/build/components/conversations/swipeable_toggle_button.d.ts.map +1 -1
  10. package/build/components/conversations/swipeable_toggle_button.js +6 -3
  11. package/build/components/conversations/swipeable_toggle_button.js.map +1 -1
  12. package/build/components/conversations/unread_count_badge.js +9 -6
  13. package/build/components/conversations/unread_count_badge.js.map +1 -1
  14. package/build/components/display/action_button.d.ts.map +1 -1
  15. package/build/components/display/action_button.js +2 -4
  16. package/build/components/display/action_button.js.map +1 -1
  17. package/build/components/display/avatar.d.ts +3 -1
  18. package/build/components/display/avatar.d.ts.map +1 -1
  19. package/build/components/display/avatar.js +2 -2
  20. package/build/components/display/avatar.js.map +1 -1
  21. package/build/components/display/avatar_group.d.ts +3 -1
  22. package/build/components/display/avatar_group.d.ts.map +1 -1
  23. package/build/components/display/avatar_group.js +2 -2
  24. package/build/components/display/avatar_group.js.map +1 -1
  25. package/build/components/display/badge.d.ts +5 -1
  26. package/build/components/display/badge.d.ts.map +1 -1
  27. package/build/components/display/badge.js +2 -2
  28. package/build/components/display/badge.js.map +1 -1
  29. package/build/components/display/icon.d.ts +26 -13
  30. package/build/components/display/icon.d.ts.map +1 -1
  31. package/build/components/display/icon.js +0 -12
  32. package/build/components/display/icon.js.map +1 -1
  33. package/build/components/display/index.d.ts +1 -0
  34. package/build/components/display/index.d.ts.map +1 -1
  35. package/build/components/display/index.js +1 -0
  36. package/build/components/display/index.js.map +1 -1
  37. package/build/components/display/pressable_row.d.ts +14 -0
  38. package/build/components/display/pressable_row.d.ts.map +1 -0
  39. package/build/components/display/pressable_row.js +65 -0
  40. package/build/components/display/pressable_row.js.map +1 -0
  41. package/build/components/display/toggle_button.d.ts +3 -1
  42. package/build/components/display/toggle_button.d.ts.map +1 -1
  43. package/build/components/display/toggle_button.js +1 -1
  44. package/build/components/display/toggle_button.js.map +1 -1
  45. package/build/components/primitive/avatar_primitive.d.ts +2 -0
  46. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  47. package/build/components/primitive/avatar_primitive.js +20 -19
  48. package/build/components/primitive/avatar_primitive.js.map +1 -1
  49. package/build/components/primitive/form_sheet.d.ts +3 -2
  50. package/build/components/primitive/form_sheet.d.ts.map +1 -1
  51. package/build/components/primitive/form_sheet.js +5 -3
  52. package/build/components/primitive/form_sheet.js.map +1 -1
  53. package/build/hooks/index.d.ts +1 -0
  54. package/build/hooks/index.d.ts.map +1 -1
  55. package/build/hooks/index.js +1 -0
  56. package/build/hooks/index.js.map +1 -1
  57. package/build/hooks/use_api.d.ts +1 -1
  58. package/build/hooks/use_api.d.ts.map +1 -1
  59. package/build/hooks/use_api.js.map +1 -1
  60. package/build/hooks/use_api_client.d.ts +1 -1
  61. package/build/hooks/use_api_client.d.ts.map +1 -1
  62. package/build/hooks/use_api_client.js +1 -1
  63. package/build/hooks/use_api_client.js.map +1 -1
  64. package/build/hooks/use_app_name.d.ts +3 -0
  65. package/build/hooks/use_app_name.d.ts.map +1 -0
  66. package/build/hooks/use_app_name.js +12 -0
  67. package/build/hooks/use_app_name.js.map +1 -0
  68. package/build/hooks/use_async_storage.d.ts +1 -1
  69. package/build/hooks/use_async_storage.d.ts.map +1 -1
  70. package/build/hooks/use_async_storage.js +6 -5
  71. package/build/hooks/use_async_storage.js.map +1 -1
  72. package/build/hooks/use_report_bug_action.d.ts +1 -1
  73. package/build/hooks/use_report_bug_action.d.ts.map +1 -1
  74. package/build/hooks/use_report_bug_action.js +1 -9
  75. package/build/hooks/use_report_bug_action.js.map +1 -1
  76. package/build/hooks/use_scalable_number_of_lines.d.ts +2 -0
  77. package/build/hooks/use_scalable_number_of_lines.d.ts.map +1 -0
  78. package/build/hooks/use_scalable_number_of_lines.js +9 -0
  79. package/build/hooks/use_scalable_number_of_lines.js.map +1 -0
  80. package/build/hooks/use_suspense_api.d.ts +1 -1
  81. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  82. package/build/hooks/use_suspense_api.js.map +1 -1
  83. package/build/index.d.ts +2 -0
  84. package/build/index.d.ts.map +1 -1
  85. package/build/index.js +2 -0
  86. package/build/index.js.map +1 -1
  87. package/build/navigation/index.d.ts +20 -5
  88. package/build/navigation/index.d.ts.map +1 -1
  89. package/build/navigation/index.js +23 -15
  90. package/build/navigation/index.js.map +1 -1
  91. package/build/polyfills/events/CustomEvent.d.ts +21 -0
  92. package/build/polyfills/events/CustomEvent.d.ts.map +1 -0
  93. package/build/polyfills/events/CustomEvent.js +22 -0
  94. package/build/polyfills/events/CustomEvent.js.map +1 -0
  95. package/build/polyfills/events/Event.d.ts +49 -0
  96. package/build/polyfills/events/Event.d.ts.map +1 -0
  97. package/build/polyfills/events/Event.js +125 -0
  98. package/build/polyfills/events/Event.js.map +1 -0
  99. package/build/polyfills/events/EventHandlerAttributes.d.ts +8 -0
  100. package/build/polyfills/events/EventHandlerAttributes.d.ts.map +1 -0
  101. package/build/polyfills/events/EventHandlerAttributes.js +46 -0
  102. package/build/polyfills/events/EventHandlerAttributes.js.map +1 -0
  103. package/build/polyfills/events/EventTarget.d.ts +33 -0
  104. package/build/polyfills/events/EventTarget.d.ts.map +1 -0
  105. package/build/polyfills/events/EventTarget.js +238 -0
  106. package/build/polyfills/events/EventTarget.js.map +1 -0
  107. package/build/polyfills/events/internals/EventInternals.d.ts +30 -0
  108. package/build/polyfills/events/internals/EventInternals.d.ts.map +1 -0
  109. package/build/polyfills/events/internals/EventInternals.js +76 -0
  110. package/build/polyfills/events/internals/EventInternals.js.map +1 -0
  111. package/build/polyfills/events/internals/EventTargetInternals.d.ts +9 -0
  112. package/build/polyfills/events/internals/EventTargetInternals.d.ts.map +1 -0
  113. package/build/polyfills/events/internals/EventTargetInternals.js +11 -0
  114. package/build/polyfills/events/internals/EventTargetInternals.js.map +1 -0
  115. package/build/polyfills/webidl/PlatformObjects.d.ts +31 -0
  116. package/build/polyfills/webidl/PlatformObjects.d.ts.map +1 -0
  117. package/build/polyfills/webidl/PlatformObjects.js +39 -0
  118. package/build/polyfills/webidl/PlatformObjects.js.map +1 -0
  119. package/build/screens/bug_report_screen.d.ts.map +1 -1
  120. package/build/screens/bug_report_screen.js +62 -57
  121. package/build/screens/bug_report_screen.js.map +1 -1
  122. package/build/screens/conversation_filters/components/conversation_filters.js +9 -7
  123. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  124. package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
  125. package/build/screens/conversation_filters/components/rows.js +50 -31
  126. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  127. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  128. package/build/screens/conversations/components/list_header_component.js +2 -2
  129. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  130. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  131. package/build/screens/conversations/conversations_screen.js +6 -6
  132. package/build/screens/conversations/conversations_screen.js.map +1 -1
  133. package/build/screens/design_system_screen.js +1 -1
  134. package/build/screens/design_system_screen.js.map +1 -1
  135. package/build/screens/get_help_screen.d.ts +5 -0
  136. package/build/screens/get_help_screen.d.ts.map +1 -0
  137. package/build/screens/get_help_screen.js +94 -0
  138. package/build/screens/get_help_screen.js.map +1 -0
  139. package/build/screens/message_actions_screen.d.ts +1 -1
  140. package/build/screens/message_actions_screen.d.ts.map +1 -1
  141. package/build/screens/message_actions_screen.js +14 -11
  142. package/build/screens/message_actions_screen.js.map +1 -1
  143. package/build/utils/client/index.d.ts +1 -0
  144. package/build/utils/client/index.d.ts.map +1 -1
  145. package/build/utils/client/index.js +1 -0
  146. package/build/utils/client/index.js.map +1 -1
  147. package/build/utils/client/types.d.ts +61 -0
  148. package/build/utils/client/types.d.ts.map +1 -0
  149. package/build/utils/client/types.js +2 -0
  150. package/build/utils/client/types.js.map +1 -0
  151. package/build/utils/styles.d.ts +1 -1
  152. package/build/utils/styles.js +1 -1
  153. package/build/utils/styles.js.map +1 -1
  154. package/build/utils/theme.d.ts +1 -0
  155. package/build/utils/theme.d.ts.map +1 -1
  156. package/build/utils/theme.js +2 -0
  157. package/build/utils/theme.js.map +1 -1
  158. package/package.json +5 -5
  159. package/src/__tests__/event-polyfill.test.ts +314 -0
  160. package/src/components/conversations/conversation_actions.tsx +1 -1
  161. package/src/components/conversations/conversation_preview.tsx +15 -4
  162. package/src/components/conversations/mute_indicator.tsx +9 -1
  163. package/src/components/conversations/swipeable_toggle_button.tsx +18 -3
  164. package/src/components/conversations/unread_count_badge.tsx +9 -6
  165. package/src/components/display/action_button.tsx +3 -4
  166. package/src/components/display/avatar.tsx +5 -1
  167. package/src/components/display/avatar_group.tsx +5 -1
  168. package/src/components/display/badge.tsx +6 -1
  169. package/src/components/display/icon.tsx +17 -14
  170. package/src/components/display/index.ts +1 -0
  171. package/src/components/display/pressable_row.tsx +103 -0
  172. package/src/components/display/toggle_button.tsx +12 -1
  173. package/src/components/primitive/avatar_primitive.tsx +35 -19
  174. package/src/components/primitive/form_sheet.tsx +33 -5
  175. package/src/hooks/index.ts +1 -0
  176. package/src/hooks/use_api.ts +1 -1
  177. package/src/hooks/use_api_client.ts +2 -2
  178. package/src/hooks/use_app_name.ts +17 -0
  179. package/src/hooks/use_async_storage.ts +8 -5
  180. package/src/hooks/use_report_bug_action.ts +2 -10
  181. package/src/hooks/use_scalable_number_of_lines.ts +10 -0
  182. package/src/hooks/use_suspense_api.ts +1 -1
  183. package/src/index.tsx +2 -0
  184. package/src/navigation/index.tsx +38 -25
  185. package/src/polyfills/events/CustomEvent.ts +32 -0
  186. package/src/polyfills/events/Event.ts +186 -0
  187. package/src/polyfills/events/EventHandlerAttributes.ts +67 -0
  188. package/src/polyfills/events/EventTarget.ts +360 -0
  189. package/src/polyfills/events/README.md +1 -0
  190. package/src/polyfills/events/internals/EventInternals.ts +95 -0
  191. package/src/polyfills/events/internals/EventTargetInternals.ts +16 -0
  192. package/src/polyfills/webidl/PlatformObjects.ts +50 -0
  193. package/src/screens/bug_report_screen.tsx +79 -67
  194. package/src/screens/conversation_filters/components/conversation_filters.tsx +10 -7
  195. package/src/screens/conversation_filters/components/rows.tsx +63 -50
  196. package/src/screens/conversations/components/list_header_component.tsx +3 -1
  197. package/src/screens/conversations/conversations_screen.tsx +8 -6
  198. package/src/screens/design_system_screen.tsx +1 -1
  199. package/src/screens/get_help_screen.tsx +131 -0
  200. package/src/screens/message_actions_screen.tsx +34 -12
  201. package/src/utils/client/index.ts +1 -0
  202. package/src/utils/styles.ts +1 -1
  203. package/src/utils/theme.ts +3 -0
  204. /package/src/utils/client/{types.d.ts → types.ts} +0 -0
@@ -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
+ })
@@ -219,7 +219,7 @@ const useStyles = () => {
219
219
  const { colors } = useTheme()
220
220
  return StyleSheet.create({
221
221
  swipeableChildContainer: {
222
- backgroundColor: colors.surfaceColor100,
222
+ backgroundColor: colors.conversationActionsBackground,
223
223
  },
224
224
  actionButtonContainer: {
225
225
  flexDirection: 'row',
@@ -3,7 +3,12 @@ import { StyleSheet, View, ViewStyle } from 'react-native'
3
3
  import { ConversationResource } from '../../types'
4
4
  import { AvatarGroup, Heading, Text, Badge, type BadgeProps } from '../display'
5
5
  import { formatDatePreview } from '../../utils/date'
6
- import { useTheme, useAtFontScaleBreakpoint } from '../../hooks'
6
+ import {
7
+ useTheme,
8
+ useAtFontScaleBreakpoint,
9
+ useFontScale,
10
+ useScalableNumberOfLines,
11
+ } from '../../hooks'
7
12
  import { UnreadCountBadge } from './unread_count_badge'
8
13
  import { ConversationActions } from './conversation_actions'
9
14
  import { MuteIndicator } from './mute_indicator'
@@ -41,6 +46,9 @@ export const ConversationPreview = ({
41
46
  const fallbackIconName = emptyConversation ? 'people.noTextMessage' : 'general.person'
42
47
  const hasMetaContent = lastMessageCreatedAt || unreadCount > 0 || muted
43
48
 
49
+ const numberOfLinesScaledTitle = useScalableNumberOfLines(1)
50
+ const numberOfLinesScaledPreviewText = useScalableNumberOfLines(2)
51
+
44
52
  const conversationPreviewText = lastMessageTextPreview
45
53
  ? `${lastMessageAuthorName}: ${lastMessageTextPreview}`
46
54
  : EMPTY_CONVERSATION_PREVIEW_TEXT
@@ -64,12 +72,12 @@ export const ConversationPreview = ({
64
72
  fallbackIconName={fallbackIconName}
65
73
  />
66
74
  <View style={styles.conversationBody}>
67
- <Heading numberOfLines={1} variant="h3" style={styles.title}>
75
+ <Heading numberOfLines={numberOfLinesScaledTitle} variant="h3" style={styles.title}>
68
76
  {title}
69
77
  </Heading>
70
78
  <Text
71
79
  variant="tertiary"
72
- numberOfLines={2}
80
+ numberOfLines={numberOfLinesScaledPreviewText}
73
81
  style={emptyConversation && styles.emptyConversationPreviewText}
74
82
  >
75
83
  {conversationPreviewText}
@@ -96,6 +104,7 @@ interface BadgeShape {
96
104
  }
97
105
  const ConversationBadges = ({ visible, badges }: { visible: boolean; badges?: BadgeShape[] }) => {
98
106
  const styles = useStyles()
107
+ const numberOfLinesScaled = useScalableNumberOfLines(1)
99
108
 
100
109
  if (!visible || !badges || badges.length === 0) {
101
110
  return null
@@ -110,6 +119,7 @@ const ConversationBadges = ({ visible, badges }: { visible: boolean; badges?: Ba
110
119
  productLogoName={badge.appName}
111
120
  label={badge.pcoResourceType}
112
121
  metaLabel={badge.text || ''}
122
+ numberOfLinesMetaLabel={numberOfLinesScaled}
113
123
  />
114
124
  ))}
115
125
  </View>
@@ -183,6 +193,7 @@ const getConversationAccessibilityLabel = ({
183
193
  const useStyles = () => {
184
194
  const { colors } = useTheme()
185
195
  const atFontScaleBreakpoint = useAtFontScaleBreakpoint()
196
+ const fontScale = useFontScale()
186
197
 
187
198
  return StyleSheet.create({
188
199
  previewRow: {
@@ -238,7 +249,7 @@ const useStyles = () => {
238
249
  statusContainer: {
239
250
  flexDirection: 'row',
240
251
  alignItems: 'center',
241
- gap: 4,
252
+ gap: 4 * fontScale,
242
253
  },
243
254
  })
244
255
  }
@@ -2,13 +2,20 @@ import React from 'react'
2
2
  import { StyleSheet } from 'react-native'
3
3
  import { Icon } from '../display'
4
4
  import { useTheme } from '../../hooks'
5
+ import { MAX_FONT_SIZE_MULTIPLIER } from '../../utils'
5
6
 
6
7
  export const MuteIndicator = ({ muted }: { muted: boolean }) => {
7
8
  const styles = useStyles()
8
9
 
9
10
  if (!muted) return null
10
11
 
11
- return <Icon name="general.bellMuted" size={16} style={styles.mutedIcon} />
12
+ return (
13
+ <Icon
14
+ name="general.bellMuted"
15
+ style={styles.mutedIcon}
16
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}
17
+ />
18
+ )
12
19
  }
13
20
 
14
21
  const useStyles = () => {
@@ -16,6 +23,7 @@ const useStyles = () => {
16
23
  return StyleSheet.create({
17
24
  mutedIcon: {
18
25
  color: colors.iconColorDefaultDim,
26
+ fontSize: 16,
19
27
  },
20
28
  })
21
29
  }
@@ -3,7 +3,11 @@ import { StyleSheet } from 'react-native'
3
3
  import { Pressable, type PressableProps } from 'react-native-gesture-handler'
4
4
  import { Icon, IconString, Spinner, Text } from '../display'
5
5
  import { useCreateAndroidRippleColor, useTheme } from '../../hooks'
6
- import { platformFontWeightMedium, platformPressedOpacityStyle } from '../../utils/styles'
6
+ import {
7
+ MAX_FONT_SIZE_MULTIPLIER,
8
+ platformFontWeightMedium,
9
+ platformPressedOpacityStyle,
10
+ } from '../../utils/styles'
7
11
  import { tokens } from '../../vendor/tapestry/tokens'
8
12
  import { Haptic } from '../../utils/native_adapters/configuration'
9
13
 
@@ -50,8 +54,16 @@ export function SwipeableToggleButton({
50
54
  <Spinner size={20} />
51
55
  ) : (
52
56
  <>
53
- <Icon name={iconName} style={styles.icon} size={20} />
54
- <Text variant="footnote" style={styles.text}>
57
+ <Icon
58
+ name={iconName}
59
+ style={styles.icon}
60
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}
61
+ />
62
+ <Text
63
+ variant="footnote"
64
+ style={styles.text}
65
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}
66
+ >
55
67
  {label}
56
68
  </Text>
57
69
  </>
@@ -78,12 +90,15 @@ const useStyles = ({ backgroundColor, disabled }: Partial<SwipeableToggleButtonP
78
90
  backgroundColor: buttonBackgroundColor,
79
91
  },
80
92
  icon: {
93
+ fontSize: 20,
81
94
  color: fillColor,
82
95
  marginTop: 4,
83
96
  },
84
97
  text: {
85
98
  color: fillColor,
86
99
  fontWeight: platformFontWeightMedium,
100
+ textAlign: 'center',
101
+ paddingHorizontal: 8,
87
102
  },
88
103
  })
89
104
  }
@@ -1,8 +1,8 @@
1
1
  import React from 'react'
2
2
  import { StyleSheet, View } from 'react-native'
3
3
  import { Text } from '../display'
4
- import { useTheme } from '../../hooks'
5
- import { platformFontWeightBold } from '../../utils'
4
+ import { useFontScale, useTheme } from '../../hooks'
5
+ import { MAX_FONT_SIZE_MULTIPLIER, platformFontWeightBold } from '../../utils'
6
6
 
7
7
  export const UnreadCountBadge = ({ count, showDot }: { count: number; showDot: boolean }) => {
8
8
  const styles = useStyles()
@@ -22,6 +22,9 @@ export const UnreadCountBadge = ({ count, showDot }: { count: number; showDot: b
22
22
 
23
23
  const useStyles = () => {
24
24
  const { colors } = useTheme()
25
+ const dotScaleLimit = MAX_FONT_SIZE_MULTIPLIER * 1.5
26
+ const fontScale = useFontScale({ maxFontSizeMultiplier: dotScaleLimit })
27
+ const size = 8 * fontScale
25
28
 
26
29
  return StyleSheet.create({
27
30
  badge: {
@@ -29,16 +32,16 @@ const useStyles = () => {
29
32
  backgroundColor: colors.fillColorInteractionDefault,
30
33
  paddingVertical: 2,
31
34
  paddingHorizontal: 8,
32
- borderRadius: 24,
35
+ borderRadius: 24 * fontScale,
33
36
  },
34
37
  count: {
35
38
  fontWeight: platformFontWeightBold,
36
39
  color: 'white',
37
40
  },
38
41
  dot: {
39
- width: 8,
40
- height: 8,
41
- borderRadius: 8,
42
+ width: size,
43
+ height: size,
44
+ borderRadius: size,
42
45
  backgroundColor: colors.fillColorInteractionDefault,
43
46
  },
44
47
  })
@@ -58,20 +58,19 @@ export const ActionButton = ({
58
58
  iconNameLeft={buttonIconNameLeft}
59
59
  loading={loading}
60
60
  maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
61
+ accessibilityShowsLargeContentViewer
62
+ accessibilityLargeContentTitle={title}
61
63
  />
62
64
  </View>
63
65
  </Animated.View>
64
66
  )
65
67
  }
66
68
 
67
- const SCALE_THAT_BUTTONS_WRAP = 1.15
68
-
69
69
  const useStyles = () => {
70
70
  const { fontScale } = useWindowDimensions()
71
71
  const { bottom } = useSafeAreaInsets()
72
72
  const { colors } = useTheme()
73
73
  const containerVerticalPadding = 16
74
- const isButtonsWrapping = fontScale >= SCALE_THAT_BUTTONS_WRAP
75
74
 
76
75
  return StyleSheet.create({
77
76
  container: {
@@ -85,7 +84,7 @@ const useStyles = () => {
85
84
  },
86
85
  buttonRow: {
87
86
  flexDirection: 'row',
88
- justifyContent: isButtonsWrapping ? 'center' : 'space-between',
87
+ justifyContent: fontScale > 1 ? 'center' : 'space-between',
89
88
  alignItems: 'center',
90
89
  gap: 16,
91
90
  flexWrap: 'wrap-reverse',
@@ -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} />
@@ -91,6 +91,10 @@ export interface BadgeProps {
91
91
  * Callback function when the badge is pressed.
92
92
  */
93
93
  onPress?: () => void
94
+ /**
95
+ * Specifies the number of lines for the meta label before truncating.
96
+ */
97
+ numberOfLinesMetaLabel?: number
94
98
  }
95
99
 
96
100
  export function Badge({
@@ -103,6 +107,7 @@ export function Badge({
103
107
  productLogoName,
104
108
  variant = 'default',
105
109
  maxFontSizeMultiplier,
110
+ numberOfLinesMetaLabel = 1,
106
111
  onPress,
107
112
  }: BadgeProps) {
108
113
  const styles = useStyles({ appearance, maxFontSizeMultiplier, variant })
@@ -134,7 +139,7 @@ export function Badge({
134
139
  <Text
135
140
  variant="footnote"
136
141
  style={styles.metaLabel}
137
- numberOfLines={1}
142
+ numberOfLines={numberOfLinesMetaLabel}
138
143
  maxFontSizeMultiplier={maxFontSizeMultiplier}
139
144
  >
140
145
  {metaLabel}
@@ -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
  // =================================
@@ -19,3 +19,4 @@ export * from './text_inline_button'
19
19
  export * from './text'
20
20
  export * from './toggle_button'
21
21
  export * from './keyboard_view'
22
+ export * from './pressable_row'