@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.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/build/angular/contact-access-button/index.d.ts +18 -0
- package/build/angular/contact-access-button/index.js +123 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +3 -0
- package/build/core/contact-access-button.d.ts +29 -0
- package/build/core/contact-access-button.js +27 -0
- package/build/core/contact.d.ts +3 -0
- package/build/core/contact.js +3 -0
- package/build/core/container.d.ts +5 -0
- package/build/core/container.js +5 -0
- package/build/core/enums.d.ts +53 -0
- package/build/core/enums.js +56 -0
- package/build/core/group.d.ts +5 -0
- package/build/core/group.js +4 -0
- package/build/core/index.d.ts +8 -0
- package/build/core/index.js +7 -0
- package/build/core/listeners.d.ts +3 -0
- package/build/core/listeners.js +11 -0
- package/build/core/native-module.d.ts +177 -0
- package/build/core/native-module.js +63 -0
- package/build/core/permissions.d.ts +3 -0
- package/build/core/permissions.js +7 -0
- package/build/core/types.d.ts +244 -0
- package/build/core/types.js +1 -0
- package/build/legacy/contacts.d.ts +30 -0
- package/build/legacy/contacts.js +177 -0
- package/build/legacy/enums.d.ts +68 -0
- package/build/legacy/enums.js +73 -0
- package/build/legacy/index.d.ts +3 -0
- package/build/legacy/index.js +3 -0
- package/build/legacy/native-module.d.ts +30 -0
- package/build/legacy/native-module.js +4 -0
- package/build/legacy/types.d.ts +158 -0
- package/build/legacy/types.js +1 -0
- package/build/react/contact-access-button.d.ts +10 -0
- package/build/react/contact-access-button.js +11 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +2 -0
- package/build/solid/contact-access-button.d.ts +10 -0
- package/build/solid/contact-access-button.js +15 -0
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +2 -0
- package/build/svelte/contact-access-button.svelte +18 -0
- package/build/svelte/contact-access-button.svelte.d.ts +4 -0
- package/build/svelte/index.d.ts +7 -0
- package/build/svelte/index.js +8 -0
- package/build/vue/contact-access-button.d.ts +19 -0
- package/build/vue/contact-access-button.js +19 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +2 -0
- package/build-ngc/angular/contact-access-button/index.d.ts +21 -0
- package/build-ngc/angular/contact-access-button/index.js +59 -0
- package/build-ngc/angular/contact-access-button/index.js.map +1 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +4 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/contact-access-button.d.ts +29 -0
- package/build-ngc/core/contact-access-button.js +28 -0
- package/build-ngc/core/contact-access-button.js.map +1 -0
- package/build-ngc/core/contact.d.ts +3 -0
- package/build-ngc/core/contact.js +4 -0
- package/build-ngc/core/contact.js.map +1 -0
- package/build-ngc/core/container.d.ts +5 -0
- package/build-ngc/core/container.js +6 -0
- package/build-ngc/core/container.js.map +1 -0
- package/build-ngc/core/enums.d.ts +53 -0
- package/build-ngc/core/enums.js +57 -0
- package/build-ngc/core/enums.js.map +1 -0
- package/build-ngc/core/group.d.ts +5 -0
- package/build-ngc/core/group.js +5 -0
- package/build-ngc/core/group.js.map +1 -0
- package/build-ngc/core/index.d.ts +8 -0
- package/build-ngc/core/index.js +8 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/listeners.d.ts +3 -0
- package/build-ngc/core/listeners.js +12 -0
- package/build-ngc/core/listeners.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +177 -0
- package/build-ngc/core/native-module.js +64 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/permissions.d.ts +3 -0
- package/build-ngc/core/permissions.js +8 -0
- package/build-ngc/core/permissions.js.map +1 -0
- package/build-ngc/core/types.d.ts +244 -0
- package/build-ngc/core/types.js +2 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +26 -0
- package/package.json +152 -0
- package/src/angular/contact-access-button/index.ts +45 -0
- package/src/angular/index.ts +3 -0
- package/src/core/contact-access-button.ts +83 -0
- package/src/core/contact.ts +3 -0
- package/src/core/container.ts +5 -0
- package/src/core/enums.ts +55 -0
- package/src/core/group.ts +4 -0
- package/src/core/index.ts +14 -0
- package/src/core/listeners.ts +19 -0
- package/src/core/native-module.ts +281 -0
- package/src/core/permissions.ts +10 -0
- package/src/core/types.ts +220 -0
- package/src/legacy/contacts.ts +274 -0
- package/src/legacy/enums.ts +72 -0
- package/src/legacy/index.ts +3 -0
- package/src/legacy/native-module.ts +66 -0
- package/src/legacy/types.ts +159 -0
- package/src/react/contact-access-button.ts +20 -0
- package/src/react/index.ts +2 -0
- package/src/solid/contact-access-button.ts +24 -0
- package/src/solid/index.ts +2 -0
- package/src/svelte/contact-access-button.svelte +18 -0
- package/src/svelte/index.ts +10 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/vue/contact-access-button.ts +24 -0
- 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,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,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,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,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
|
+
}
|