@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # @symbiote-native/contacts
2
+
3
+ Look up a person, add a contact, edit one in the system form, or let the user grant access to just
4
+ the contacts you need (iOS 18). One API for every [SymbioteNative](../../README.md) adapter (React,
5
+ Vue, Svelte, Solid and Angular).
6
+
7
+ It wraps [`expo-contacts`](https://github.com/expo/expo/tree/main/packages/expo-contacts) the same
8
+ way [`@symbiote-native/print`](../print) wraps its upstream: `expo-modules-core` is a direct
9
+ dependency and the upstream JS is hand-ported into `core/`. See the `symbiote-expo-native-module`
10
+ project skill for the full mechanism.
11
+
12
+ **Both of upstream's API surfaces are ported**, matching its own layout: the modern
13
+ `SharedObject`-class `Contact`/`Group`/`Container` API (default entry) and the legacy
14
+ function-based API (`/legacy` subpath). Unlike `@symbiote-native/calendar` (which ships only its
15
+ modern surface, since upstream's `next` API had no real JS reference to port), `expo-contacts`
16
+ ships complete, real JS for both surfaces at sdk-57 - so both are ported here.
17
+
18
+ ## Install
19
+
20
+ **New app:**
21
+
22
+ ```bash
23
+ npx @symbiote-native/cli new my-app --contacts
24
+ ```
25
+
26
+ **Existing SymbioteNative app:**
27
+
28
+ ```bash
29
+ npx @symbiote-native/cli add --contacts
30
+ ```
31
+
32
+ Either way: installs `@symbiote-native/contacts` and wires the native autolinking
33
+ automatically, see [`@symbiote-native/cli`](../cli).
34
+
35
+ <details>
36
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
37
+
38
+ ```bash
39
+ npm install @symbiote-native/contacts
40
+ ```
41
+
42
+ `expo-contacts` and `expo-modules-core` come along as regular, pinned dependencies, never
43
+ install either yourself, and never add the `expo` meta-package to this project.
44
+
45
+ ### Required one-time step: native autolinking wiring
46
+
47
+ Same one-time app wiring every `expo-modules-core` package shares, see
48
+ [`@symbiote-native/print`'s README](../print/README.md#required-one-time-step-native-autolinking-wiring)
49
+ for the full table; nothing package-specific here.
50
+
51
+ ### Permissions and config plugin
52
+
53
+ `native-link.json` declares:
54
+
55
+ - **Android**: `READ_CONTACTS`/`WRITE_CONTACTS` permissions. Two native modules are registered:
56
+ `ExpoContacts` (legacy surface) and `ExpoContactsNext` (modern surface) - both are genuinely
57
+ called by our JS, since both surfaces are ported.
58
+ - **iOS**: `NSContactsUsageDescription` (custom wording; reword to fit the app).
59
+
60
+ </details>
61
+
62
+ ## ContactAccessButton
63
+
64
+ Every adapter exports `ContactAccessButton` from its own entry (`/react`, `/vue`, `/solid`,
65
+ `/svelte`, `/angular`), plus `ContactAccessButton.isAvailable()`. The button is a native view,
66
+ iOS 18+ only, and renders nothing on any other platform or version.
67
+
68
+ Props: `query`, `caption` (`default` / `email` / `phone`), `ignoredEmails`,
69
+ `ignoredPhoneNumbers`, `tintColor`, `backgroundColor`, `textColor`, `style`, `testID`,
70
+ `nativeID`, `onLayout`, the accessibility and aria props and the responder props.
71
+
72
+ The view is registered lazily, at the first render: bundlers that inline requires drop a barrel's
73
+ load-time side effects. Verifying it on a real iOS 18 device is the app owner's step.
74
+
75
+ ## Notes
76
+
77
+ - **Request permission first.** Reads and writes fail without it. On iOS 18 a `'limited'`
78
+ `accessPrivileges` means the app sees only the contacts the user shared.
79
+ - **`Group` and `Container` are iOS only.** On Android they fall back to a stub that throws
80
+ `Not implemented`.
81
+ - **`ContactAccessButton` needs iOS 18.** It renders nothing elsewhere; call
82
+ `ContactAccessButton.isAvailable()` to check.
83
+
84
+ ## What's not ported
85
+
86
+ Nothing from `expo-contacts`' public surface.
87
+
88
+ ## Platform availability (next API)
89
+
90
+ | Member | iOS | Android |
91
+ |---|---|---|
92
+ | `Contact` (all CRUD, static finders, field getters/setters) | yes | yes |
93
+ | `Contact.presentAccessPicker` | yes (18+) | no |
94
+ | `contact.getMaidenName/setMaidenName/getNickname/setNickname/getBirthday/setBirthday/getNonGregorianBirthday/setNonGregorianBirthday` | yes | no |
95
+ | `contact.getSocialProfiles/addSocialProfile/updateSocialProfile/deleteSocialProfile` | yes | no |
96
+ | `contact.getImAddresses/addImAddress/updateImAddress/deleteImAddress` | yes | no |
97
+ | `contact.getIsFavourite/setIsFavourite` | no | yes |
98
+ | `contact.getExtraNames/addExtraName/updateExtraName/deleteExtraName` | no | yes |
99
+ | `Group`/`Container` (whole classes) | yes | no - falls back to a stub that throws `Not implemented` |
100
+
101
+ An unavailable member throws `UnavailabilityError` (function-shaped) or is simply absent
102
+ (class-shaped optional method) on the other platform - check for presence before calling if the
103
+ call site must run on both.
104
+
105
+ ## Shape
106
+
107
+ ```
108
+ src/core/ modern SharedObject API (default entry)
109
+ enums.ts ContactField, ContactsSortOrder, NonGregorianCalendar
110
+ types.ts all record/patch/query types
111
+ native-module.ts declare class ambients for Contact/Group/Container + module functions
112
+ contact.ts export class Contact extends expoContactsNext.Contact {}
113
+ group.ts export class Group extends (expoContactsNext.Group ?? FallbackGroup) {}
114
+ container.ts export class Container extends (expoContactsNext.Container ?? FallbackContainer) {}
115
+ permissions.ts get/requestPermissionsAsync
116
+ listeners.ts add/removeAllContactsChangeListeners
117
+ src/legacy/ legacy function-based API (/legacy subpath)
118
+ enums.ts Fields, CalendarFormats, ContainerTypes, SortTypes, ContactTypes
119
+ types.ts legacy record/query types
120
+ native-module.ts requireNativeModule('ExpoContacts')
121
+ contacts.ts get/add/update/removeContactAsync, groups, containers, permissions
122
+ src/angular/ @symbiote-native/contacts/angular
123
+ ```
124
+
125
+ `./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto
126
+ `src/core/`. `./legacy` aliases onto `src/legacy/`; there is no separate Angular build for it,
127
+ matching `@symbiote-native/file-system`'s and `@symbiote-native/media-library`'s dual-surface
128
+ precedent.
129
+
130
+ `Contact`/`Group`/`Container` extend their native `SharedObject` base directly with no added
131
+ sugar - every method already exists on the native class, so the wrapper's only job is being a
132
+ named, `instanceof`-checkable subclass. iOS registers the next class as `ContactNext` (distinct
133
+ from the deprecated legacy `Contact` JS class); Android registers it directly as `Contact`.
134
+ `native-module.ts` aliases `.ContactNext` onto `.Contact` at import time on iOS so both
135
+ platforms expose the same `expoContactsNext.Contact`.
136
+
137
+ ## Use it
138
+
139
+ ```ts
140
+ import { Contact, requestPermissionsAsync } from '@symbiote-native/contacts';
141
+
142
+ await requestPermissionsAsync();
143
+ const contact = await Contact.create({ givenName: 'Jane', familyName: 'Doe' });
144
+ await contact.addEmail({ label: 'work', address: 'jane@example.com' });
145
+ ```
146
+
147
+ ```ts
148
+ // legacy surface
149
+ import { getContactsAsync, requestPermissionsAsync } from '@symbiote-native/contacts/legacy';
150
+
151
+ await requestPermissionsAsync();
152
+ const { data } = await getContactsAsync({ name: 'Jane' });
153
+ ```
154
+
155
+ ## Common questions
156
+
157
+ - **`getAll` returns too few contacts.** Check the permission; on iOS 18 `'limited'` means the user
158
+ shared only some. Use `ContactAccessButton` or `Contact.presentAccessPicker()`.
159
+ - **Slow with many contacts.** Page with `limit` / `offset` and use `Contact.getAllDetails(fields)`
160
+ for only the fields you show.
161
+ - **No phone or email in a list row.** Fetch `getPhones()` / `getEmails()` per contact when needed.
162
+ - **Writes fail on Android.** Request `WRITE_CONTACTS` via `requestPermissionsAsync()` first.
163
+ - **Own picker?** Prefer `Contact.presentPicker()` or `ContactAccessButton`.
164
+
165
+ Sources: [Expo docs: Contacts](https://docs.expo.dev/versions/latest/sdk/contacts/),
166
+ [expo/expo#386](https://github.com/expo/expo/issues/386),
167
+ [expo/expo#200](https://github.com/expo/expo/issues/200),
168
+ [expo/expo#29224](https://github.com/expo/expo/issues/29224).
169
+
170
+ ## Test it
171
+
172
+ ```bash
173
+ pnpm vitest run packages/contacts
174
+ ```
@@ -0,0 +1,18 @@
1
+ import { NativeViewBase } from '@symbiote-native/angular';
2
+ import type { IDescriptor } from '@symbiote-native/components';
3
+ import { isContactAccessButtonAvailable } from '../../core/contact-access-button';
4
+ import type { IContactAccessButtonProps } from '../../core/contact-access-button';
5
+ /** Angular twin of `expo-contacts`' `ContactAccessButton`, iOS 18+ only, nothing elsewhere */
6
+ export declare class ContactAccessButton extends NativeViewBase {
7
+ /** True only on iOS 18.0 and newer */
8
+ static readonly isAvailable: typeof isContactAccessButtonAvailable;
9
+ query?: string;
10
+ caption?: IContactAccessButtonProps['caption'];
11
+ ignoredEmails?: string[];
12
+ ignoredPhoneNumbers?: string[];
13
+ tintColor?: IContactAccessButtonProps['tintColor'];
14
+ backgroundColor?: IContactAccessButtonProps['backgroundColor'];
15
+ textColor?: IContactAccessButtonProps['textColor'];
16
+ protected readonly propNames: readonly ["query", "caption", "ignoredEmails", "ignoredPhoneNumbers", "tintColor", "backgroundColor", "textColor"];
17
+ protected renderView(props: object): IDescriptor | null;
18
+ }
@@ -0,0 +1,123 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
36
+ import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
37
+ import { isContactAccessButtonAvailable, renderContactAccessButton, } from '../../core/contact-access-button.js';
38
+ /** Angular twin of `expo-contacts`' `ContactAccessButton`, iOS 18+ only, nothing elsewhere */
39
+ let ContactAccessButton = (() => {
40
+ let _classDecorators = [Component({
41
+ selector: 'ContactAccessButton',
42
+ standalone: true,
43
+ imports: [DescriptorOutlet],
44
+ changeDetection: ChangeDetectionStrategy.OnPush,
45
+ template: `@if (descriptor; as node) {
46
+ <symbiote-descriptor-outlet [node]="node" />
47
+ }`,
48
+ })];
49
+ let _classDescriptor;
50
+ let _classExtraInitializers = [];
51
+ let _classThis;
52
+ let _classSuper = NativeViewBase;
53
+ let _query_decorators;
54
+ let _query_initializers = [];
55
+ let _query_extraInitializers = [];
56
+ let _caption_decorators;
57
+ let _caption_initializers = [];
58
+ let _caption_extraInitializers = [];
59
+ let _ignoredEmails_decorators;
60
+ let _ignoredEmails_initializers = [];
61
+ let _ignoredEmails_extraInitializers = [];
62
+ let _ignoredPhoneNumbers_decorators;
63
+ let _ignoredPhoneNumbers_initializers = [];
64
+ let _ignoredPhoneNumbers_extraInitializers = [];
65
+ let _tintColor_decorators;
66
+ let _tintColor_initializers = [];
67
+ let _tintColor_extraInitializers = [];
68
+ let _backgroundColor_decorators;
69
+ let _backgroundColor_initializers = [];
70
+ let _backgroundColor_extraInitializers = [];
71
+ let _textColor_decorators;
72
+ let _textColor_initializers = [];
73
+ let _textColor_extraInitializers = [];
74
+ var ContactAccessButton = class extends _classSuper {
75
+ static { _classThis = this; }
76
+ static {
77
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
78
+ _query_decorators = [Input()];
79
+ _caption_decorators = [Input()];
80
+ _ignoredEmails_decorators = [Input()];
81
+ _ignoredPhoneNumbers_decorators = [Input()];
82
+ _tintColor_decorators = [Input()];
83
+ _backgroundColor_decorators = [Input()];
84
+ _textColor_decorators = [Input()];
85
+ __esDecorate(null, null, _query_decorators, { kind: "field", name: "query", static: false, private: false, access: { has: obj => "query" in obj, get: obj => obj.query, set: (obj, value) => { obj.query = value; } }, metadata: _metadata }, _query_initializers, _query_extraInitializers);
86
+ __esDecorate(null, null, _caption_decorators, { kind: "field", name: "caption", static: false, private: false, access: { has: obj => "caption" in obj, get: obj => obj.caption, set: (obj, value) => { obj.caption = value; } }, metadata: _metadata }, _caption_initializers, _caption_extraInitializers);
87
+ __esDecorate(null, null, _ignoredEmails_decorators, { kind: "field", name: "ignoredEmails", static: false, private: false, access: { has: obj => "ignoredEmails" in obj, get: obj => obj.ignoredEmails, set: (obj, value) => { obj.ignoredEmails = value; } }, metadata: _metadata }, _ignoredEmails_initializers, _ignoredEmails_extraInitializers);
88
+ __esDecorate(null, null, _ignoredPhoneNumbers_decorators, { kind: "field", name: "ignoredPhoneNumbers", static: false, private: false, access: { has: obj => "ignoredPhoneNumbers" in obj, get: obj => obj.ignoredPhoneNumbers, set: (obj, value) => { obj.ignoredPhoneNumbers = value; } }, metadata: _metadata }, _ignoredPhoneNumbers_initializers, _ignoredPhoneNumbers_extraInitializers);
89
+ __esDecorate(null, null, _tintColor_decorators, { kind: "field", name: "tintColor", static: false, private: false, access: { has: obj => "tintColor" in obj, get: obj => obj.tintColor, set: (obj, value) => { obj.tintColor = value; } }, metadata: _metadata }, _tintColor_initializers, _tintColor_extraInitializers);
90
+ __esDecorate(null, null, _backgroundColor_decorators, { kind: "field", name: "backgroundColor", static: false, private: false, access: { has: obj => "backgroundColor" in obj, get: obj => obj.backgroundColor, set: (obj, value) => { obj.backgroundColor = value; } }, metadata: _metadata }, _backgroundColor_initializers, _backgroundColor_extraInitializers);
91
+ __esDecorate(null, null, _textColor_decorators, { kind: "field", name: "textColor", static: false, private: false, access: { has: obj => "textColor" in obj, get: obj => obj.textColor, set: (obj, value) => { obj.textColor = value; } }, metadata: _metadata }, _textColor_initializers, _textColor_extraInitializers);
92
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
93
+ ContactAccessButton = _classThis = _classDescriptor.value;
94
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
95
+ }
96
+ /** True only on iOS 18.0 and newer */
97
+ static isAvailable = isContactAccessButtonAvailable;
98
+ query = __runInitializers(this, _query_initializers, void 0);
99
+ caption = (__runInitializers(this, _query_extraInitializers), __runInitializers(this, _caption_initializers, void 0));
100
+ ignoredEmails = (__runInitializers(this, _caption_extraInitializers), __runInitializers(this, _ignoredEmails_initializers, void 0));
101
+ ignoredPhoneNumbers = (__runInitializers(this, _ignoredEmails_extraInitializers), __runInitializers(this, _ignoredPhoneNumbers_initializers, void 0));
102
+ tintColor = (__runInitializers(this, _ignoredPhoneNumbers_extraInitializers), __runInitializers(this, _tintColor_initializers, void 0));
103
+ backgroundColor = (__runInitializers(this, _tintColor_extraInitializers), __runInitializers(this, _backgroundColor_initializers, void 0));
104
+ textColor = (__runInitializers(this, _backgroundColor_extraInitializers), __runInitializers(this, _textColor_initializers, void 0));
105
+ propNames = (__runInitializers(this, _textColor_extraInitializers), [
106
+ 'query',
107
+ 'caption',
108
+ 'ignoredEmails',
109
+ 'ignoredPhoneNumbers',
110
+ 'tintColor',
111
+ 'backgroundColor',
112
+ 'textColor',
113
+ ]);
114
+ renderView(props) {
115
+ return renderContactAccessButton(props);
116
+ }
117
+ static {
118
+ __runInitializers(_classThis, _classExtraInitializers);
119
+ }
120
+ };
121
+ return ContactAccessButton = _classThis;
122
+ })();
123
+ export { ContactAccessButton };
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { ContactAccessButton } from './contact-access-button';
@@ -0,0 +1,3 @@
1
+ // No per-instance state or event stream to wrap in an injectable service - plain re-export.
2
+ export * from '../core/index.js';
3
+ export { ContactAccessButton } from './contact-access-button/index.js';
@@ -0,0 +1,29 @@
1
+ import type { IAccessibilityProps, IAriaProps, IDescriptor, IResponderProps } from '@symbiote-native/components';
2
+ import type { IColorValue, IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
3
+ export declare const CONTACT_ACCESS_BUTTON_MODULE_NAME = "ExpoContactAccessButton";
4
+ export type IContactAccessButtonCaption = 'default' | 'email' | 'phone';
5
+ export type IContactAccessButtonProps = IAccessibilityProps & IAriaProps & IResponderProps & {
6
+ /** Matched against contacts not yet exposed to the app, typically from a search field */
7
+ query?: string;
8
+ /** Caption under the contact name when the query has one match, nothing by default */
9
+ caption?: IContactAccessButtonCaption;
10
+ /** Contacts matching `query` that also match one of these emails are left out */
11
+ ignoredEmails?: string[];
12
+ /** Contacts matching `query` that also match one of these phone numbers are left out */
13
+ ignoredPhoneNumbers?: string[];
14
+ /** Tint of the button and of the modal shown when there is more than one match */
15
+ tintColor?: IColorValue;
16
+ /** Button background, keep it opaque to satisfy the platform legibility rules */
17
+ backgroundColor?: IColorValue;
18
+ /** Button title color, a dimmed version of it is used for the caption */
19
+ textColor?: IColorValue;
20
+ style?: IStyleProp<IViewStyle>;
21
+ testID?: string;
22
+ nativeID?: string;
23
+ onLayout?: (event: ISymbioteEvent) => void;
24
+ };
25
+ export declare function contactAccessButtonViewName(): string;
26
+ /** True only on iOS 18.0 and newer, where the native module reports the button as available */
27
+ export declare function isContactAccessButtonAvailable(): boolean;
28
+ export declare function ensureContactAccessButtonRegistered(): boolean;
29
+ export declare function renderContactAccessButton(props: object): IDescriptor | null;
@@ -0,0 +1,27 @@
1
+ import { Platform, requireNativeViewManager, requireOptionalNativeModule, } from 'expo-modules-core';
2
+ import { el } from '@symbiote-native/components';
3
+ import { expoViewManagerName, tryRegisterNativeView, } from '@symbiote-native/engine';
4
+ export const CONTACT_ACCESS_BUTTON_MODULE_NAME = 'ExpoContactAccessButton';
5
+ export function contactAccessButtonViewName() {
6
+ return expoViewManagerName(CONTACT_ACCESS_BUTTON_MODULE_NAME);
7
+ }
8
+ /** True only on iOS 18.0 and newer, where the native module reports the button as available */
9
+ export function isContactAccessButtonAvailable() {
10
+ return (Platform.OS === 'ios' &&
11
+ (requireOptionalNativeModule(CONTACT_ACCESS_BUTTON_MODULE_NAME)?.isAvailable ??
12
+ false));
13
+ }
14
+ // Registers the view config in React Native's registry, which the engine derives the view's events
15
+ // and prop processors from. Called at render, not at import: a registration that only a barrel's
16
+ // load-time side effect performs is dropped by Metro's `inlineRequires` in release
17
+ export function ensureContactAccessButtonRegistered() {
18
+ if (Platform.OS !== 'ios')
19
+ return false;
20
+ return tryRegisterNativeView(() => requireNativeViewManager(CONTACT_ACCESS_BUTTON_MODULE_NAME));
21
+ }
22
+ // `object`, not the props type: Vue and Angular hand over an untyped attribute bag
23
+ export function renderContactAccessButton(props) {
24
+ if (!ensureContactAccessButtonRegistered())
25
+ return null;
26
+ return el(contactAccessButtonViewName(), { ...props });
27
+ }
@@ -0,0 +1,3 @@
1
+ import { expoContactsNext } from './native-module';
2
+ export declare class Contact extends expoContactsNext.Contact {
3
+ }
@@ -0,0 +1,3 @@
1
+ import { expoContactsNext } from './native-module.js';
2
+ export class Contact extends expoContactsNext.Contact {
3
+ }
@@ -0,0 +1,5 @@
1
+ declare const Container_base: typeof import("./native-module").NativeContainer;
2
+ /** @platform ios - Android has no Container concept in expo-contacts. */
3
+ export declare class Container extends Container_base {
4
+ }
5
+ export {};
@@ -0,0 +1,5 @@
1
+ import { expoContactsNext, FallbackContainer } from './native-module.js';
2
+ /** @platform ios - Android has no Container concept in expo-contacts. */
3
+ export class Container extends (expoContactsNext.Container ??
4
+ FallbackContainer) {
5
+ }
@@ -0,0 +1,53 @@
1
+ export declare enum ContactField {
2
+ IS_FAVOURITE = "isFavourite",
3
+ FULL_NAME = "fullName",
4
+ GIVEN_NAME = "givenName",
5
+ MIDDLE_NAME = "middleName",
6
+ FAMILY_NAME = "familyName",
7
+ MAIDEN_NAME = "maidenName",
8
+ NICKNAME = "nickname",
9
+ PREFIX = "prefix",
10
+ SUFFIX = "suffix",
11
+ PHONETIC_GIVEN_NAME = "phoneticGivenName",
12
+ PHONETIC_MIDDLE_NAME = "phoneticMiddleName",
13
+ PHONETIC_FAMILY_NAME = "phoneticFamilyName",
14
+ COMPANY = "company",
15
+ PHONETIC_COMPANY_NAME = "phoneticCompanyName",
16
+ DEPARTMENT = "department",
17
+ JOB_TITLE = "jobTitle",
18
+ NOTE = "note",
19
+ IMAGE = "image",
20
+ THUMBNAIL = "thumbnail",
21
+ BIRTHDAY = "birthday",
22
+ NON_GREGORIAN_BIRTHDAY = "nonGregorianBirthday",
23
+ EMAILS = "emails",
24
+ PHONES = "phones",
25
+ ADDRESSES = "addresses",
26
+ EXTRA_NAMES = "extraNames",
27
+ DATES = "dates",
28
+ RELATIONS = "relations",
29
+ URL_ADDRESSES = "urlAddresses",
30
+ SOCIAL_PROFILES = "socialProfiles",
31
+ IM_ADDRESSES = "imAddresses"
32
+ }
33
+ export declare enum ContactsSortOrder {
34
+ UserDefault = "userDefault",
35
+ GivenName = "givenName",
36
+ FamilyName = "familyName",
37
+ None = "none"
38
+ }
39
+ /** @platform ios */
40
+ export declare enum NonGregorianCalendar {
41
+ buddhist = "buddhist",
42
+ chinese = "chinese",
43
+ coptic = "coptic",
44
+ ethiopicAmeteMihret = "ethiopicAmeteMihret",
45
+ ethiopicAmeteAlem = "ethiopicAmeteAlem",
46
+ hebrew = "hebrew",
47
+ indian = "indian",
48
+ islamic = "islamic",
49
+ islamicCivil = "islamicCivil",
50
+ japanese = "japanese",
51
+ persian = "persian",
52
+ republicOfChina = "republicOfChina"
53
+ }
@@ -0,0 +1,56 @@
1
+ export var ContactField;
2
+ (function (ContactField) {
3
+ ContactField["IS_FAVOURITE"] = "isFavourite";
4
+ ContactField["FULL_NAME"] = "fullName";
5
+ ContactField["GIVEN_NAME"] = "givenName";
6
+ ContactField["MIDDLE_NAME"] = "middleName";
7
+ ContactField["FAMILY_NAME"] = "familyName";
8
+ ContactField["MAIDEN_NAME"] = "maidenName";
9
+ ContactField["NICKNAME"] = "nickname";
10
+ ContactField["PREFIX"] = "prefix";
11
+ ContactField["SUFFIX"] = "suffix";
12
+ ContactField["PHONETIC_GIVEN_NAME"] = "phoneticGivenName";
13
+ ContactField["PHONETIC_MIDDLE_NAME"] = "phoneticMiddleName";
14
+ ContactField["PHONETIC_FAMILY_NAME"] = "phoneticFamilyName";
15
+ ContactField["COMPANY"] = "company";
16
+ ContactField["PHONETIC_COMPANY_NAME"] = "phoneticCompanyName";
17
+ ContactField["DEPARTMENT"] = "department";
18
+ ContactField["JOB_TITLE"] = "jobTitle";
19
+ ContactField["NOTE"] = "note";
20
+ ContactField["IMAGE"] = "image";
21
+ ContactField["THUMBNAIL"] = "thumbnail";
22
+ ContactField["BIRTHDAY"] = "birthday";
23
+ ContactField["NON_GREGORIAN_BIRTHDAY"] = "nonGregorianBirthday";
24
+ ContactField["EMAILS"] = "emails";
25
+ ContactField["PHONES"] = "phones";
26
+ ContactField["ADDRESSES"] = "addresses";
27
+ ContactField["EXTRA_NAMES"] = "extraNames";
28
+ ContactField["DATES"] = "dates";
29
+ ContactField["RELATIONS"] = "relations";
30
+ ContactField["URL_ADDRESSES"] = "urlAddresses";
31
+ ContactField["SOCIAL_PROFILES"] = "socialProfiles";
32
+ ContactField["IM_ADDRESSES"] = "imAddresses";
33
+ })(ContactField || (ContactField = {}));
34
+ export var ContactsSortOrder;
35
+ (function (ContactsSortOrder) {
36
+ ContactsSortOrder["UserDefault"] = "userDefault";
37
+ ContactsSortOrder["GivenName"] = "givenName";
38
+ ContactsSortOrder["FamilyName"] = "familyName";
39
+ ContactsSortOrder["None"] = "none";
40
+ })(ContactsSortOrder || (ContactsSortOrder = {}));
41
+ /** @platform ios */
42
+ export var NonGregorianCalendar;
43
+ (function (NonGregorianCalendar) {
44
+ NonGregorianCalendar["buddhist"] = "buddhist";
45
+ NonGregorianCalendar["chinese"] = "chinese";
46
+ NonGregorianCalendar["coptic"] = "coptic";
47
+ NonGregorianCalendar["ethiopicAmeteMihret"] = "ethiopicAmeteMihret";
48
+ NonGregorianCalendar["ethiopicAmeteAlem"] = "ethiopicAmeteAlem";
49
+ NonGregorianCalendar["hebrew"] = "hebrew";
50
+ NonGregorianCalendar["indian"] = "indian";
51
+ NonGregorianCalendar["islamic"] = "islamic";
52
+ NonGregorianCalendar["islamicCivil"] = "islamicCivil";
53
+ NonGregorianCalendar["japanese"] = "japanese";
54
+ NonGregorianCalendar["persian"] = "persian";
55
+ NonGregorianCalendar["republicOfChina"] = "republicOfChina";
56
+ })(NonGregorianCalendar || (NonGregorianCalendar = {}));
@@ -0,0 +1,5 @@
1
+ declare const Group_base: typeof import("./native-module").NativeGroup;
2
+ /** @platform ios - Android has no Group concept in expo-contacts. */
3
+ export declare class Group extends Group_base {
4
+ }
5
+ export {};
@@ -0,0 +1,4 @@
1
+ import { expoContactsNext, FallbackGroup } from './native-module.js';
2
+ /** @platform ios - Android has no Group concept in expo-contacts. */
3
+ export class Group extends (expoContactsNext.Group ?? FallbackGroup) {
4
+ }
@@ -0,0 +1,8 @@
1
+ export * from './enums';
2
+ export * from './types';
3
+ export type { IContactAccessButtonCaption, IContactAccessButtonProps, } from './contact-access-button';
4
+ export { Contact } from './contact';
5
+ export { Group } from './group';
6
+ export { Container } from './container';
7
+ export { getPermissionsAsync, requestPermissionsAsync } from './permissions';
8
+ export { addContactsChangeListener, removeAllContactsChangeListeners, } from './listeners';
@@ -0,0 +1,7 @@
1
+ export * from './enums.js';
2
+ export * from './types.js';
3
+ export { Contact } from './contact.js';
4
+ export { Group } from './group.js';
5
+ export { Container } from './container.js';
6
+ export { getPermissionsAsync, requestPermissionsAsync } from './permissions.js';
7
+ export { addContactsChangeListener, removeAllContactsChangeListeners, } from './listeners.js';
@@ -0,0 +1,3 @@
1
+ import type { EventSubscription } from 'expo-modules-core';
2
+ export declare function addContactsChangeListener(listener: () => void): EventSubscription;
3
+ export declare function removeAllContactsChangeListeners(): void;
@@ -0,0 +1,11 @@
1
+ import { UnavailabilityError } from 'expo-modules-core';
2
+ import { expoContactsNext, ON_CONTACTS_CHANGE_EVENT_NAME, } from './native-module.js';
3
+ export function addContactsChangeListener(listener) {
4
+ if (!expoContactsNext.addListener) {
5
+ throw new UnavailabilityError('Contacts', 'addContactsChangeListener');
6
+ }
7
+ return expoContactsNext.addListener(ON_CONTACTS_CHANGE_EVENT_NAME, listener);
8
+ }
9
+ export function removeAllContactsChangeListeners() {
10
+ expoContactsNext.removeAllListeners?.(ON_CONTACTS_CHANGE_EVENT_NAME);
11
+ }