@symbiote-native/contacts 0.1.0

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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/build/angular/contact-access-button/index.d.ts +18 -0
  4. package/build/angular/contact-access-button/index.js +123 -0
  5. package/build/angular/index.d.ts +2 -0
  6. package/build/angular/index.js +3 -0
  7. package/build/core/contact-access-button.d.ts +29 -0
  8. package/build/core/contact-access-button.js +27 -0
  9. package/build/core/contact.d.ts +3 -0
  10. package/build/core/contact.js +3 -0
  11. package/build/core/container.d.ts +5 -0
  12. package/build/core/container.js +5 -0
  13. package/build/core/enums.d.ts +53 -0
  14. package/build/core/enums.js +56 -0
  15. package/build/core/group.d.ts +5 -0
  16. package/build/core/group.js +4 -0
  17. package/build/core/index.d.ts +8 -0
  18. package/build/core/index.js +7 -0
  19. package/build/core/listeners.d.ts +3 -0
  20. package/build/core/listeners.js +11 -0
  21. package/build/core/native-module.d.ts +177 -0
  22. package/build/core/native-module.js +63 -0
  23. package/build/core/permissions.d.ts +3 -0
  24. package/build/core/permissions.js +7 -0
  25. package/build/core/types.d.ts +244 -0
  26. package/build/core/types.js +1 -0
  27. package/build/legacy/contacts.d.ts +30 -0
  28. package/build/legacy/contacts.js +177 -0
  29. package/build/legacy/enums.d.ts +68 -0
  30. package/build/legacy/enums.js +73 -0
  31. package/build/legacy/index.d.ts +3 -0
  32. package/build/legacy/index.js +3 -0
  33. package/build/legacy/native-module.d.ts +30 -0
  34. package/build/legacy/native-module.js +4 -0
  35. package/build/legacy/types.d.ts +158 -0
  36. package/build/legacy/types.js +1 -0
  37. package/build/react/contact-access-button.d.ts +10 -0
  38. package/build/react/contact-access-button.js +11 -0
  39. package/build/react/index.d.ts +2 -0
  40. package/build/react/index.js +2 -0
  41. package/build/solid/contact-access-button.d.ts +10 -0
  42. package/build/solid/contact-access-button.js +15 -0
  43. package/build/solid/index.d.ts +2 -0
  44. package/build/solid/index.js +2 -0
  45. package/build/svelte/contact-access-button.svelte +18 -0
  46. package/build/svelte/contact-access-button.svelte.d.ts +4 -0
  47. package/build/svelte/index.d.ts +7 -0
  48. package/build/svelte/index.js +8 -0
  49. package/build/vue/contact-access-button.d.ts +19 -0
  50. package/build/vue/contact-access-button.js +19 -0
  51. package/build/vue/index.d.ts +2 -0
  52. package/build/vue/index.js +2 -0
  53. package/build-ngc/angular/contact-access-button/index.d.ts +21 -0
  54. package/build-ngc/angular/contact-access-button/index.js +59 -0
  55. package/build-ngc/angular/contact-access-button/index.js.map +1 -0
  56. package/build-ngc/angular/index.d.ts +2 -0
  57. package/build-ngc/angular/index.js +4 -0
  58. package/build-ngc/angular/index.js.map +1 -0
  59. package/build-ngc/core/contact-access-button.d.ts +29 -0
  60. package/build-ngc/core/contact-access-button.js +28 -0
  61. package/build-ngc/core/contact-access-button.js.map +1 -0
  62. package/build-ngc/core/contact.d.ts +3 -0
  63. package/build-ngc/core/contact.js +4 -0
  64. package/build-ngc/core/contact.js.map +1 -0
  65. package/build-ngc/core/container.d.ts +5 -0
  66. package/build-ngc/core/container.js +6 -0
  67. package/build-ngc/core/container.js.map +1 -0
  68. package/build-ngc/core/enums.d.ts +53 -0
  69. package/build-ngc/core/enums.js +57 -0
  70. package/build-ngc/core/enums.js.map +1 -0
  71. package/build-ngc/core/group.d.ts +5 -0
  72. package/build-ngc/core/group.js +5 -0
  73. package/build-ngc/core/group.js.map +1 -0
  74. package/build-ngc/core/index.d.ts +8 -0
  75. package/build-ngc/core/index.js +8 -0
  76. package/build-ngc/core/index.js.map +1 -0
  77. package/build-ngc/core/listeners.d.ts +3 -0
  78. package/build-ngc/core/listeners.js +12 -0
  79. package/build-ngc/core/listeners.js.map +1 -0
  80. package/build-ngc/core/native-module.d.ts +177 -0
  81. package/build-ngc/core/native-module.js +64 -0
  82. package/build-ngc/core/native-module.js.map +1 -0
  83. package/build-ngc/core/permissions.d.ts +3 -0
  84. package/build-ngc/core/permissions.js +8 -0
  85. package/build-ngc/core/permissions.js.map +1 -0
  86. package/build-ngc/core/types.d.ts +244 -0
  87. package/build-ngc/core/types.js +2 -0
  88. package/build-ngc/core/types.js.map +1 -0
  89. package/native-link.json +26 -0
  90. package/package.json +152 -0
  91. package/src/angular/contact-access-button/index.ts +45 -0
  92. package/src/angular/index.ts +3 -0
  93. package/src/core/contact-access-button.ts +83 -0
  94. package/src/core/contact.ts +3 -0
  95. package/src/core/container.ts +5 -0
  96. package/src/core/enums.ts +55 -0
  97. package/src/core/group.ts +4 -0
  98. package/src/core/index.ts +14 -0
  99. package/src/core/listeners.ts +19 -0
  100. package/src/core/native-module.ts +281 -0
  101. package/src/core/permissions.ts +10 -0
  102. package/src/core/types.ts +220 -0
  103. package/src/legacy/contacts.ts +274 -0
  104. package/src/legacy/enums.ts +72 -0
  105. package/src/legacy/index.ts +3 -0
  106. package/src/legacy/native-module.ts +66 -0
  107. package/src/legacy/types.ts +159 -0
  108. package/src/react/contact-access-button.ts +20 -0
  109. package/src/react/index.ts +2 -0
  110. package/src/solid/contact-access-button.ts +24 -0
  111. package/src/solid/index.ts +2 -0
  112. package/src/svelte/contact-access-button.svelte +18 -0
  113. package/src/svelte/index.ts +10 -0
  114. package/src/svelte/svelte-ambient.d.ts +3 -0
  115. package/src/svelte/svelte-compile.test-helper.ts +7 -0
  116. package/src/vue/contact-access-button.ts +24 -0
  117. package/src/vue/index.ts +2 -0
@@ -0,0 +1,274 @@
1
+ import { UnavailabilityError, uuid } from 'expo-modules-core';
2
+ import type { EventSubscription } from 'expo-modules-core';
3
+ import { Platform, Share } from 'react-native';
4
+ import type { ShareOptions } from 'react-native';
5
+ import { expoContacts, ON_CONTACTS_CHANGE_EVENT_NAME } from './native-module';
6
+ import type {
7
+ Contact,
8
+ ContactQuery,
9
+ ContactResponse,
10
+ Container,
11
+ ContainerQuery,
12
+ ContactsPermissionResponse,
13
+ ExistingContact,
14
+ FieldType,
15
+ FormOptions,
16
+ Group,
17
+ GroupQuery,
18
+ } from './types';
19
+
20
+ export async function isAvailableAsync(): Promise<boolean> {
21
+ return !!expoContacts.getContactsAsync;
22
+ }
23
+
24
+ export async function hasContactsAsync(): Promise<boolean> {
25
+ if (!expoContacts.hasContactsAsync) {
26
+ throw new UnavailabilityError('Contacts', 'hasContactsAsync');
27
+ }
28
+ return expoContacts.hasContactsAsync();
29
+ }
30
+
31
+ export async function writeContactToFileAsync(
32
+ contactQuery: ContactQuery = {},
33
+ ): Promise<string | undefined> {
34
+ if (!expoContacts.writeContactToFileAsync) {
35
+ throw new UnavailabilityError('Contacts', 'writeContactToFileAsync');
36
+ }
37
+ return expoContacts.writeContactToFileAsync(contactQuery);
38
+ }
39
+
40
+ export async function shareContactAsync(
41
+ contactId: string,
42
+ message: string,
43
+ shareOptions: ShareOptions = {},
44
+ ): Promise<unknown> {
45
+ if (Platform.OS === 'ios') {
46
+ const url = await writeContactToFileAsync({ id: contactId });
47
+ return Share.share({ url, message }, shareOptions);
48
+ }
49
+ if (!expoContacts.shareContactAsync) {
50
+ throw new UnavailabilityError('Contacts', 'shareContactAsync');
51
+ }
52
+ return expoContacts.shareContactAsync(contactId, message);
53
+ }
54
+
55
+ export async function getContactsAsync(
56
+ contactQuery: ContactQuery = {},
57
+ ): Promise<ContactResponse> {
58
+ if (!expoContacts.getContactsAsync) {
59
+ throw new UnavailabilityError('Contacts', 'getContactsAsync');
60
+ }
61
+ return expoContacts.getContactsAsync(contactQuery);
62
+ }
63
+
64
+ export async function getPagedContactsAsync(
65
+ contactQuery: ContactQuery = {},
66
+ ): Promise<ContactResponse> {
67
+ const { pageSize, ...rest } = contactQuery;
68
+ if (pageSize != null && pageSize <= 0) {
69
+ throw new Error(
70
+ 'Error: Contacts.getPagedContactsAsync: `pageSize` must be greater than 0',
71
+ );
72
+ }
73
+ return getContactsAsync({ ...rest, pageSize });
74
+ }
75
+
76
+ export async function getContactByIdAsync(
77
+ id: string,
78
+ fields?: FieldType[],
79
+ ): Promise<ExistingContact | undefined> {
80
+ if (!expoContacts.getContactsAsync) {
81
+ throw new UnavailabilityError('Contacts', 'getContactsAsync');
82
+ }
83
+ const results = await expoContacts.getContactsAsync({
84
+ pageSize: 1,
85
+ pageOffset: 0,
86
+ fields,
87
+ id,
88
+ });
89
+ return results.data.length > 0 ? results.data[0] : undefined;
90
+ }
91
+
92
+ export async function addContactAsync(
93
+ contact: Contact,
94
+ containerId?: string,
95
+ ): Promise<string> {
96
+ if (!expoContacts.addContactAsync) {
97
+ throw new UnavailabilityError('Contacts', 'addContactAsync');
98
+ }
99
+ return expoContacts.addContactAsync(contact, containerId);
100
+ }
101
+
102
+ export async function updateContactAsync(
103
+ contact: { id: string } & Partial<ExistingContact>,
104
+ ): Promise<string> {
105
+ if (!expoContacts.updateContactAsync) {
106
+ throw new UnavailabilityError('Contacts', 'updateContactAsync');
107
+ }
108
+ return expoContacts.updateContactAsync(contact);
109
+ }
110
+
111
+ export async function removeContactAsync(contactId: string): Promise<unknown> {
112
+ if (!expoContacts.removeContactAsync) {
113
+ throw new UnavailabilityError('Contacts', 'removeContactAsync');
114
+ }
115
+ return expoContacts.removeContactAsync(contactId);
116
+ }
117
+
118
+ export async function presentFormAsync(
119
+ contactId?: string | null,
120
+ contact?: Contact | null,
121
+ formOptions: FormOptions = {},
122
+ ): Promise<unknown> {
123
+ if (!expoContacts.presentFormAsync) {
124
+ throw new UnavailabilityError('Contacts', 'presentFormAsync');
125
+ }
126
+ if (Platform.OS !== 'ios') {
127
+ return expoContacts.presentFormAsync(contactId, contact, formOptions);
128
+ }
129
+ let adjustedContact = contact;
130
+ if (contactId) {
131
+ if (adjustedContact) {
132
+ adjustedContact = undefined;
133
+ console.warn(
134
+ 'Expo.Contacts.presentFormAsync: You should define either a `contact` or a `contactId` but not both.',
135
+ );
136
+ }
137
+ if (formOptions.isNew !== undefined) {
138
+ console.warn(
139
+ 'Expo.Contacts.presentFormAsync: `formOptions.isNew` is not supported with `contactId`',
140
+ );
141
+ }
142
+ }
143
+ return expoContacts.presentFormAsync(contactId, adjustedContact, formOptions);
144
+ }
145
+
146
+ export async function addExistingGroupToContainerAsync(
147
+ groupId: string,
148
+ containerId: string,
149
+ ): Promise<unknown> {
150
+ if (!expoContacts.addExistingGroupToContainerAsync) {
151
+ throw new UnavailabilityError(
152
+ 'Contacts',
153
+ 'addExistingGroupToContainerAsync',
154
+ );
155
+ }
156
+ return expoContacts.addExistingGroupToContainerAsync(groupId, containerId);
157
+ }
158
+
159
+ export async function getDefaultContainerIdAsync(): Promise<string> {
160
+ if (!expoContacts.getDefaultContainerIdentifierAsync) {
161
+ throw new UnavailabilityError(
162
+ 'Contacts',
163
+ 'getDefaultContainerIdentifierAsync',
164
+ );
165
+ }
166
+ return expoContacts.getDefaultContainerIdentifierAsync();
167
+ }
168
+
169
+ export async function createGroupAsync(
170
+ name?: string,
171
+ containerId?: string,
172
+ ): Promise<string> {
173
+ if (!expoContacts.createGroupAsync) {
174
+ throw new UnavailabilityError('Contacts', 'createGroupAsync');
175
+ }
176
+ const groupName = name ?? uuid.v4();
177
+ const resolvedContainerId =
178
+ containerId ?? (await getDefaultContainerIdAsync());
179
+ return expoContacts.createGroupAsync(groupName, resolvedContainerId);
180
+ }
181
+
182
+ export async function updateGroupNameAsync(
183
+ groupName: string,
184
+ groupId: string,
185
+ ): Promise<unknown> {
186
+ if (!expoContacts.updateGroupNameAsync) {
187
+ throw new UnavailabilityError('Contacts', 'updateGroupNameAsync');
188
+ }
189
+ return expoContacts.updateGroupNameAsync(groupName, groupId);
190
+ }
191
+
192
+ export async function removeGroupAsync(groupId: string): Promise<unknown> {
193
+ if (!expoContacts.removeGroupAsync) {
194
+ throw new UnavailabilityError('Contacts', 'removeGroupAsync');
195
+ }
196
+ return expoContacts.removeGroupAsync(groupId);
197
+ }
198
+
199
+ export async function addExistingContactToGroupAsync(
200
+ contactId: string,
201
+ groupId: string,
202
+ ): Promise<unknown> {
203
+ if (!expoContacts.addExistingContactToGroupAsync) {
204
+ throw new UnavailabilityError('Contacts', 'addExistingContactToGroupAsync');
205
+ }
206
+ return expoContacts.addExistingContactToGroupAsync(contactId, groupId);
207
+ }
208
+
209
+ export async function removeContactFromGroupAsync(
210
+ contactId: string,
211
+ groupId: string,
212
+ ): Promise<unknown> {
213
+ if (!expoContacts.removeContactFromGroupAsync) {
214
+ throw new UnavailabilityError('Contacts', 'removeContactFromGroupAsync');
215
+ }
216
+ return expoContacts.removeContactFromGroupAsync(contactId, groupId);
217
+ }
218
+
219
+ export async function getGroupsAsync(groupQuery: GroupQuery): Promise<Group[]> {
220
+ if (!expoContacts.getGroupsAsync) {
221
+ throw new UnavailabilityError('Contacts', 'getGroupsAsync');
222
+ }
223
+ return expoContacts.getGroupsAsync(groupQuery);
224
+ }
225
+
226
+ export async function presentContactPickerAsync(): Promise<ExistingContact | null> {
227
+ if (!expoContacts.presentContactPickerAsync) {
228
+ throw new UnavailabilityError('Contacts', 'presentContactPickerAsync');
229
+ }
230
+ return expoContacts.presentContactPickerAsync();
231
+ }
232
+
233
+ export async function getContainersAsync(
234
+ containerQuery: ContainerQuery,
235
+ ): Promise<Container[]> {
236
+ if (!expoContacts.getContainersAsync) {
237
+ throw new UnavailabilityError('Contacts', 'getContainersAsync');
238
+ }
239
+ return expoContacts.getContainersAsync(containerQuery);
240
+ }
241
+
242
+ export async function presentAccessPickerAsync(): Promise<string[]> {
243
+ if (!expoContacts.presentAccessPickerAsync) {
244
+ throw new UnavailabilityError('Contacts', 'presentAccessPickerAsync');
245
+ }
246
+ return expoContacts.presentAccessPickerAsync();
247
+ }
248
+
249
+ export async function getPermissionsAsync(): Promise<ContactsPermissionResponse> {
250
+ if (!expoContacts.getPermissionsAsync) {
251
+ throw new UnavailabilityError('Contacts', 'getPermissionsAsync');
252
+ }
253
+ return expoContacts.getPermissionsAsync();
254
+ }
255
+
256
+ export async function requestPermissionsAsync(): Promise<ContactsPermissionResponse> {
257
+ if (!expoContacts.requestPermissionsAsync) {
258
+ throw new UnavailabilityError('Contacts', 'requestPermissionsAsync');
259
+ }
260
+ return expoContacts.requestPermissionsAsync();
261
+ }
262
+
263
+ export function addContactsChangeListener(
264
+ listener: () => void,
265
+ ): EventSubscription {
266
+ if (!expoContacts.addListener) {
267
+ throw new UnavailabilityError('Contacts', 'addContactsChangeListener');
268
+ }
269
+ const subscription = expoContacts.addListener(
270
+ ON_CONTACTS_CHANGE_EVENT_NAME,
271
+ listener,
272
+ );
273
+ return { remove: () => subscription.remove() };
274
+ }
@@ -0,0 +1,72 @@
1
+ export enum Fields {
2
+ ID = 'id',
3
+ ContactType = 'contactType',
4
+ Name = 'name',
5
+ FirstName = 'firstName',
6
+ MiddleName = 'middleName',
7
+ LastName = 'lastName',
8
+ MaidenName = 'maidenName',
9
+ NamePrefix = 'namePrefix',
10
+ NameSuffix = 'nameSuffix',
11
+ Nickname = 'nickname',
12
+ PhoneticFirstName = 'phoneticFirstName',
13
+ PhoneticMiddleName = 'phoneticMiddleName',
14
+ PhoneticLastName = 'phoneticLastName',
15
+ Birthday = 'birthday',
16
+ NonGregorianBirthday = 'nonGregorianBirthday',
17
+ Emails = 'emails',
18
+ PhoneNumbers = 'phoneNumbers',
19
+ Addresses = 'addresses',
20
+ SocialProfiles = 'socialProfiles',
21
+ InstantMessageAddresses = 'instantMessageAddresses',
22
+ UrlAddresses = 'urlAddresses',
23
+ Company = 'company',
24
+ JobTitle = 'jobTitle',
25
+ Department = 'department',
26
+ ImageAvailable = 'imageAvailable',
27
+ Image = 'image',
28
+ RawImage = 'rawImage',
29
+ ExtraNames = 'extraNames',
30
+ Note = 'note',
31
+ Dates = 'dates',
32
+ Relationships = 'relationships',
33
+ IsFavorite = 'isFavorite',
34
+ }
35
+
36
+ export enum CalendarFormats {
37
+ Gregorian = 'gregorian',
38
+ Buddhist = 'buddhist',
39
+ Chinese = 'chinese',
40
+ Coptic = 'coptic',
41
+ EthiopicAmeteMihret = 'ethiopicAmeteMihret',
42
+ EthiopicAmeteAlem = 'ethiopicAmeteAlem',
43
+ Hebrew = 'hebrew',
44
+ ISO8601 = 'iso8601',
45
+ Indian = 'indian',
46
+ Islamic = 'islamic',
47
+ IslamicCivil = 'islamicCivil',
48
+ Japanese = 'japanese',
49
+ Persian = 'persian',
50
+ RepublicOfChina = 'republicOfChina',
51
+ IslamicTabular = 'islamicTabular',
52
+ IslamicUmmAlQura = 'islamicUmmAlQura',
53
+ }
54
+
55
+ export enum ContainerTypes {
56
+ Local = 'local',
57
+ Exchange = 'exchange',
58
+ CardDAV = 'cardDAV',
59
+ Unassigned = 'unassigned',
60
+ }
61
+
62
+ export enum SortTypes {
63
+ UserDefault = 'userDefault',
64
+ FirstName = 'firstName',
65
+ LastName = 'lastName',
66
+ None = 'none',
67
+ }
68
+
69
+ export enum ContactTypes {
70
+ Person = 'person',
71
+ Company = 'company',
72
+ }
@@ -0,0 +1,3 @@
1
+ export * from './enums';
2
+ export * from './types';
3
+ export * from './contacts';
@@ -0,0 +1,66 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ import type { EventSubscription } from 'expo-modules-core';
3
+ import type {
4
+ Contact,
5
+ ContactQuery,
6
+ ContactResponse,
7
+ Container,
8
+ ContainerQuery,
9
+ ContactsPermissionResponse,
10
+ ExistingContact,
11
+ FormOptions,
12
+ Group,
13
+ GroupQuery,
14
+ } from './types';
15
+
16
+ const EXPO_CONTACTS_MODULE_NAME = 'ExpoContacts';
17
+ export const ON_CONTACTS_CHANGE_EVENT_NAME = 'onContactsChange';
18
+
19
+ export type INativeContactsModule = {
20
+ hasContactsAsync?(): Promise<boolean>;
21
+ shareContactAsync?(contactId: string, message: string): Promise<unknown>;
22
+ getContactsAsync?(contactQuery: ContactQuery): Promise<ContactResponse>;
23
+ addContactAsync?(contact: Contact, containerId?: string): Promise<string>;
24
+ updateContactAsync?(
25
+ contact: { id: string } & Partial<ExistingContact>,
26
+ ): Promise<string>;
27
+ removeContactAsync?(contactId: string): Promise<unknown>;
28
+ writeContactToFileAsync?(
29
+ contactQuery: ContactQuery,
30
+ ): Promise<string | undefined>;
31
+ presentFormAsync?(
32
+ contactId?: string | null,
33
+ contact?: Contact | null,
34
+ formOptions?: FormOptions,
35
+ ): Promise<unknown>;
36
+ addExistingGroupToContainerAsync?(
37
+ groupId: string,
38
+ containerId: string,
39
+ ): Promise<unknown>;
40
+ createGroupAsync?(name: string, containerId?: string): Promise<string>;
41
+ updateGroupNameAsync?(groupName: string, groupId: string): Promise<unknown>;
42
+ removeGroupAsync?(groupId: string): Promise<unknown>;
43
+ addExistingContactToGroupAsync?(
44
+ contactId: string,
45
+ groupId: string,
46
+ ): Promise<unknown>;
47
+ removeContactFromGroupAsync?(
48
+ contactId: string,
49
+ groupId: string,
50
+ ): Promise<unknown>;
51
+ getGroupsAsync?(groupQuery: GroupQuery): Promise<Group[]>;
52
+ presentContactPickerAsync?(): Promise<ExistingContact | null>;
53
+ getDefaultContainerIdentifierAsync?(): Promise<string>;
54
+ getContainersAsync?(containerQuery: ContainerQuery): Promise<Container[]>;
55
+ presentAccessPickerAsync?(): Promise<string[]>;
56
+ getPermissionsAsync?(): Promise<ContactsPermissionResponse>;
57
+ requestPermissionsAsync?(): Promise<ContactsPermissionResponse>;
58
+ addListener?(
59
+ eventName: typeof ON_CONTACTS_CHANGE_EVENT_NAME,
60
+ listener: () => void,
61
+ ): EventSubscription;
62
+ };
63
+
64
+ export const expoContacts = requireNativeModule<INativeContactsModule>(
65
+ EXPO_CONTACTS_MODULE_NAME,
66
+ );
@@ -0,0 +1,159 @@
1
+ import type { PermissionResponse } from 'expo-modules-core';
2
+ import type {
3
+ CalendarFormats,
4
+ ContactTypes,
5
+ ContainerTypes,
6
+ Fields,
7
+ SortTypes,
8
+ } from './enums';
9
+
10
+ export type ContactsPermissionResponse = PermissionResponse & {
11
+ accessPrivileges?: 'all' | 'limited' | 'none';
12
+ };
13
+
14
+ export type CalendarFormatType = CalendarFormats | `${CalendarFormats}`;
15
+ export type ContainerType = ContainerTypes | `${ContainerTypes}`;
16
+ export type ContactType = ContactTypes | `${ContactTypes}`;
17
+ export type FieldType = Fields | `${Fields}`;
18
+
19
+ export type Date = {
20
+ day: number;
21
+ month: number;
22
+ year?: number;
23
+ id?: string;
24
+ label?: string;
25
+ format?: CalendarFormatType;
26
+ };
27
+
28
+ export type Relationship = { label: string; name?: string; id?: string };
29
+ export type Email = {
30
+ email?: string;
31
+ isPrimary?: boolean;
32
+ label: string;
33
+ id?: string;
34
+ };
35
+ export type PhoneNumber = {
36
+ number?: string;
37
+ isPrimary?: boolean;
38
+ digits?: string;
39
+ countryCode?: string;
40
+ label: string;
41
+ id?: string;
42
+ };
43
+ export type Address = {
44
+ street?: string;
45
+ city?: string;
46
+ country?: string;
47
+ region?: string;
48
+ neighborhood?: string;
49
+ postalCode?: string;
50
+ poBox?: string;
51
+ isoCountryCode?: string;
52
+ label: string;
53
+ id?: string;
54
+ };
55
+ export type SocialProfile = {
56
+ service?: string;
57
+ localizedProfile?: string;
58
+ url?: string;
59
+ username?: string;
60
+ userId?: string;
61
+ label: string;
62
+ id?: string;
63
+ };
64
+ export type InstantMessageAddress = {
65
+ service?: string;
66
+ username?: string;
67
+ localizedService?: string;
68
+ label: string;
69
+ id?: string;
70
+ };
71
+ export type UrlAddress = { label: string; url?: string; id?: string };
72
+ export type Image = {
73
+ uri?: string;
74
+ width?: number;
75
+ height?: number;
76
+ base64?: string;
77
+ };
78
+
79
+ export type Contact = {
80
+ contactType: ContactType;
81
+ name: string;
82
+ firstName?: string;
83
+ middleName?: string;
84
+ lastName?: string;
85
+ maidenName?: string;
86
+ namePrefix?: string;
87
+ nameSuffix?: string;
88
+ nickname?: string;
89
+ phoneticFirstName?: string;
90
+ phoneticMiddleName?: string;
91
+ phoneticLastName?: string;
92
+ company?: string;
93
+ jobTitle?: string;
94
+ department?: string;
95
+ note?: string;
96
+ imageAvailable?: boolean;
97
+ image?: Image;
98
+ rawImage?: Image;
99
+ birthday?: Date;
100
+ dates?: Date[];
101
+ relationships?: Relationship[];
102
+ emails?: Email[];
103
+ phoneNumbers?: PhoneNumber[];
104
+ addresses?: Address[];
105
+ instantMessageAddresses?: InstantMessageAddress[];
106
+ urlAddresses?: UrlAddress[];
107
+ nonGregorianBirthday?: Date;
108
+ socialProfiles?: SocialProfile[];
109
+ isFavorite?: boolean;
110
+ };
111
+
112
+ export type ExistingContact = Contact & { id: string };
113
+
114
+ export type ContactResponse = {
115
+ data: ExistingContact[];
116
+ hasNextPage: boolean;
117
+ hasPreviousPage: boolean;
118
+ };
119
+
120
+ export type ContactSort = `${SortTypes}`;
121
+
122
+ export type ContactQuery = {
123
+ pageSize?: number;
124
+ pageOffset?: number;
125
+ fields?: FieldType[];
126
+ sort?: ContactSort;
127
+ name?: string;
128
+ id?: string | string[];
129
+ groupId?: string;
130
+ containerId?: string;
131
+ rawContacts?: boolean;
132
+ };
133
+
134
+ export type FormOptions = {
135
+ displayedPropertyKeys?: FieldType[];
136
+ message?: string;
137
+ alternateName?: string;
138
+ allowsEditing?: boolean;
139
+ allowsActions?: boolean;
140
+ shouldShowLinkedContacts?: boolean;
141
+ isNew?: boolean;
142
+ cancelButtonTitle?: string;
143
+ preventAnimation?: boolean;
144
+ groupId?: string;
145
+ };
146
+
147
+ export type GroupQuery = {
148
+ groupId?: string;
149
+ groupName?: string;
150
+ containerId?: string;
151
+ };
152
+ export type Group = { name?: string; id?: string };
153
+
154
+ export type ContainerQuery = {
155
+ contactId?: string;
156
+ groupId?: string;
157
+ containerId?: string | string[];
158
+ };
159
+ export type Container = { name: string; id: string; type: ContainerType };
@@ -0,0 +1,20 @@
1
+ import type { ReactElement } from 'react';
2
+ import { descriptorToReact } from '@symbiote-native/react';
3
+ import {
4
+ isContactAccessButtonAvailable,
5
+ renderContactAccessButton,
6
+ } from '../core/contact-access-button';
7
+ import type { IContactAccessButtonProps } from '../core/contact-access-button';
8
+
9
+ function ContactAccessButtonView(
10
+ props: IContactAccessButtonProps,
11
+ ): ReactElement | null {
12
+ const descriptor = renderContactAccessButton(props);
13
+ return descriptor ? descriptorToReact(descriptor) : null;
14
+ }
15
+
16
+ /** React twin of `expo-contacts`' `ContactAccessButton`, iOS 18+ only, nothing elsewhere */
17
+ export const ContactAccessButton = Object.assign(ContactAccessButtonView, {
18
+ /** True only on iOS 18.0 and newer */
19
+ isAvailable: isContactAccessButtonAvailable,
20
+ });
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { ContactAccessButton } from './contact-access-button';
@@ -0,0 +1,24 @@
1
+ import { descriptorToSolid } from '@symbiote-native/solid';
2
+ import {
3
+ isContactAccessButtonAvailable,
4
+ renderContactAccessButton,
5
+ } from '../core/contact-access-button';
6
+ import type { IContactAccessButtonProps } from '../core/contact-access-button';
7
+
8
+ // The platform decision is made once at mount: the descriptor bridge builds the node once and
9
+ // only diffs prop values afterwards, so the descriptor's shape must not change
10
+ function ContactAccessButtonView(
11
+ props: IContactAccessButtonProps,
12
+ ): ReturnType<typeof descriptorToSolid> | null {
13
+ const initial = renderContactAccessButton({ ...props });
14
+ if (!initial) return null;
15
+ return descriptorToSolid(
16
+ () => renderContactAccessButton({ ...props }) ?? initial,
17
+ );
18
+ }
19
+
20
+ /** Solid twin of `expo-contacts`' `ContactAccessButton`, iOS 18+ only, nothing elsewhere */
21
+ export const ContactAccessButton = Object.assign(ContactAccessButtonView, {
22
+ /** True only on iOS 18.0 and newer */
23
+ isAvailable: isContactAccessButtonAvailable,
24
+ });
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { ContactAccessButton } from './contact-access-button';
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ // The native view name is mixed-case, which a literal tag would parse as a component reference,
3
+ // so the tag is dynamic. The platform decision is made once at mount, like the other adapters
4
+ import { hostProps } from '@symbiote-native/svelte/native-view-bridge';
5
+ import {
6
+ contactAccessButtonViewName,
7
+ ensureContactAccessButtonRegistered,
8
+ } from '../core/contact-access-button';
9
+ import type { IContactAccessButtonProps } from '../core/contact-access-button';
10
+
11
+ const props: IContactAccessButtonProps = $props();
12
+ const isRegistered = ensureContactAccessButtonRegistered();
13
+ const viewName = contactAccessButtonViewName();
14
+ </script>
15
+
16
+ {#if isRegistered}
17
+ <svelte:element this={viewName} {@attach hostProps({ ...props })} />
18
+ {/if}
@@ -0,0 +1,10 @@
1
+ import { isContactAccessButtonAvailable } from '../core/contact-access-button';
2
+ import ContactAccessButtonView from './contact-access-button.svelte';
3
+
4
+ export * from '../core';
5
+
6
+ /** Svelte twin of `expo-contacts`' `ContactAccessButton`, iOS 18+ only, nothing elsewhere */
7
+ export const ContactAccessButton = Object.assign(ContactAccessButtonView, {
8
+ /** True only on iOS 18.0 and newer */
9
+ isAvailable: isContactAccessButtonAvailable,
10
+ });
@@ -0,0 +1,3 @@
1
+ // Pulls svelte's ambient globals ($state/$effect/...) into this package's tsc program,
2
+ // same fix as `packages/navigation/src/svelte/svelte-ambient.d.ts`
3
+ /// <reference types="svelte" />
@@ -0,0 +1,7 @@
1
+ // Re-exports the existing harness instead of a third copy (packages/sqlite and
2
+ // packages/navigation already carry one, not part of either package's public exports)
3
+ export {
4
+ createSvelteHarness,
5
+ loadComponent,
6
+ type ISvelteHarness,
7
+ } from '../../../sqlite/src/svelte/svelte-compile.test-helper';