@symbiote-native/clipboard 0.0.1 → 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/README.md +180 -26
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +7 -0
- package/build/angular/services/clipboard.service/index.d.ts +6 -0
- package/build/angular/services/clipboard.service/index.js +77 -0
- package/build/core/clipboard.d.ts +86 -0
- package/build/core/clipboard.js +137 -0
- package/build/core/index.d.ts +3 -0
- package/build/core/index.js +2 -0
- package/build/core/native-module.d.ts +17 -0
- package/build/core/native-module.js +7 -0
- package/build/core/types.d.ts +67 -0
- package/build/core/types.js +28 -0
- package/build/react/hooks/use-clipboard/index.d.ts +2 -0
- package/build/react/hooks/use-clipboard/index.js +14 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +8 -0
- package/build/vue/composables/use-clipboard/index.d.ts +3 -0
- package/build/vue/composables/use-clipboard/index.js +20 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +8 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +8 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/services/clipboard.service/index.d.ts +9 -0
- package/build-ngc/angular/services/clipboard.service/index.js +36 -0
- package/build-ngc/angular/services/clipboard.service/index.js.map +1 -0
- package/build-ngc/core/clipboard.d.ts +86 -0
- package/build-ngc/core/clipboard.js +138 -0
- package/build-ngc/core/clipboard.js.map +1 -0
- package/build-ngc/core/index.d.ts +3 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +17 -0
- package/build-ngc/core/native-module.js +8 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +67 -0
- package/build-ngc/core/types.js +29 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +92 -1
- package/src/angular/index.ts +8 -0
- package/src/angular/services/clipboard.service/index.ts +35 -0
- package/src/core/clipboard.ts +163 -0
- package/src/core/index.ts +23 -0
- package/src/core/native-module.ts +42 -0
- package/src/core/types.ts +81 -0
- package/src/react/hooks/use-clipboard/index.ts +17 -0
- package/src/react/index.ts +9 -0
- package/src/vue/composables/use-clipboard/index.ts +25 -0
- package/src/vue/index.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/clipboard",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "expo-clipboard wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Reads and writes clipboard text, URLs, and images, plus a clipboard-change listener.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,98 @@
|
|
|
13
13
|
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
14
14
|
},
|
|
15
15
|
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./build/core/index.js",
|
|
18
|
+
"module": "./build/core/index.js",
|
|
19
|
+
"types": "./build/core/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./build/core/index.d.ts",
|
|
23
|
+
"default": "./build/core/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./vue": {
|
|
26
|
+
"types": "./build/vue/index.d.ts",
|
|
27
|
+
"default": "./build/vue/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./react": {
|
|
30
|
+
"types": "./build/react/index.d.ts",
|
|
31
|
+
"default": "./build/react/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./angular": {
|
|
34
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
35
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
36
|
+
"default": "./src/angular/index.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src",
|
|
41
|
+
"build",
|
|
42
|
+
"build-ngc",
|
|
43
|
+
"native-link.json",
|
|
44
|
+
"!src/**/*.test.*",
|
|
45
|
+
"!src/**/*.spec.*",
|
|
46
|
+
"!src/**/*.detox.*"
|
|
47
|
+
],
|
|
16
48
|
"publishConfig": {
|
|
17
49
|
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"expo-clipboard": "57.0.1",
|
|
53
|
+
"expo-modules-core": "57.0.5"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@symbiote-native/engine": ">=0.1.7",
|
|
57
|
+
"@angular/core": ">=20",
|
|
58
|
+
"@vue/runtime-core": "^3.5.13",
|
|
59
|
+
"react": ">=19.0.0",
|
|
60
|
+
"react-native": ">=0.86",
|
|
61
|
+
"vue": ">=3.5.0",
|
|
62
|
+
"@symbiote-native/angular": "0.6.2",
|
|
63
|
+
"@symbiote-native/react": "0.2.8",
|
|
64
|
+
"@symbiote-native/vue": "0.3.8"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@symbiote-native/angular": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@symbiote-native/react": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@symbiote-native/vue": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@angular/core": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@vue/runtime-core": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"react": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"vue": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@angular/compiler": "^22",
|
|
91
|
+
"@angular/compiler-cli": "^22",
|
|
92
|
+
"@angular/core": "^22",
|
|
93
|
+
"@types/node": "^26.0.0",
|
|
94
|
+
"@types/react": "^19.2.0",
|
|
95
|
+
"@vue/runtime-core": "^3.5.13",
|
|
96
|
+
"react": "19.2.3",
|
|
97
|
+
"typescript": "~6.0.0",
|
|
98
|
+
"@symbiote-native/angular": "0.6.2",
|
|
99
|
+
"@symbiote-native/engine": "0.1.7",
|
|
100
|
+
"@symbiote-native/react": "0.2.8",
|
|
101
|
+
"@symbiote-native/test-utils": "0.1.6",
|
|
102
|
+
"@symbiote-native/vue": "0.3.8"
|
|
103
|
+
},
|
|
104
|
+
"scripts": {
|
|
105
|
+
"typecheck": "tsc --build",
|
|
106
|
+
"clean": "rm -rf build-ngc",
|
|
107
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
108
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
18
109
|
}
|
|
19
110
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/angular: the Angular entry over the framework-agnostic core.
|
|
2
|
+
// Unlike @symbiote-native/local-auth (all stateless functions, plain re-export), clipboard has
|
|
3
|
+
// one listener-based piece — ClipboardService.connect() is the Angular-only lifecycle half; the
|
|
4
|
+
// addClipboardListener subscription plumbing all lives in core, shared with React/Vue. Mirrors
|
|
5
|
+
// @symbiote-native/sensors' AccelerometerService.
|
|
6
|
+
|
|
7
|
+
export { ClipboardService } from './services/clipboard.service';
|
|
8
|
+
export * from '../core';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { effect, inject, Injectable, Injector, signal, type Signal } from '@angular/core';
|
|
2
|
+
import { addClipboardListener, type IClipboardEvent } from '../../../core';
|
|
3
|
+
|
|
4
|
+
// Angular twin of React's `useClipboard` hook and Vue's `useClipboard` composable. Angular has
|
|
5
|
+
// no per-instance hook — state and lifecycle live in DI instead, so `connect()` stands in for
|
|
6
|
+
// the hook's role: call it ONCE (typically from a component's field initializer, inside an
|
|
7
|
+
// injection context).
|
|
8
|
+
//
|
|
9
|
+
// readonly clipboardEvent = inject(ClipboardService).connect();
|
|
10
|
+
// // template: {{ clipboardEvent()?.contentTypes }}
|
|
11
|
+
//
|
|
12
|
+
// Mirrors AccelerometerService.connect() from @symbiote-native/sensors: the subscription doesn't
|
|
13
|
+
// depend on anything the caller's own signals could change between renders, so a single
|
|
14
|
+
// effect() that subscribes once and cleans up once is enough.
|
|
15
|
+
@Injectable({ providedIn: 'root' })
|
|
16
|
+
export class ClipboardService {
|
|
17
|
+
// Captured in the constructor (itself always run inside an injection context by Angular's own
|
|
18
|
+
// DI) so `connect()` can create an `effect()` even when called from plain field-initializer
|
|
19
|
+
// code that is not, on its own, an active injection context — mirrors AccelerometerService.
|
|
20
|
+
private readonly injector = inject(Injector);
|
|
21
|
+
|
|
22
|
+
connect(): Signal<IClipboardEvent | null> {
|
|
23
|
+
const event = signal<IClipboardEvent | null>(null);
|
|
24
|
+
|
|
25
|
+
effect(
|
|
26
|
+
onCleanup => {
|
|
27
|
+
const subscription = addClipboardListener(next => event.set(next));
|
|
28
|
+
onCleanup(() => subscription.remove());
|
|
29
|
+
},
|
|
30
|
+
{ injector: this.injector },
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return event.asReadonly();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import type { EventSubscription } from 'expo-modules-core';
|
|
3
|
+
|
|
4
|
+
import { CLIPBOARD_CHANGED_EVENT_NAME, expoClipboard } from './native-module';
|
|
5
|
+
import type {
|
|
6
|
+
IClipboardEvent,
|
|
7
|
+
IClipboardImage,
|
|
8
|
+
IGetImageOptions,
|
|
9
|
+
IGetStringOptions,
|
|
10
|
+
ISetStringOptions,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
// Matches the module name upstream's own Clipboard.ts passes to every UnavailabilityError.
|
|
14
|
+
const NATIVE_MODULE_NAME = 'Clipboard';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Gets the content of the user's clipboard.
|
|
18
|
+
*
|
|
19
|
+
* Note: On iOS 16+, if the user denies paste permission, this method returns an empty string.
|
|
20
|
+
* Due to iOS platform limitations, there is no way to distinguish between an empty clipboard
|
|
21
|
+
* and denied permission.
|
|
22
|
+
*
|
|
23
|
+
* @param options Options for the clipboard content to be retrieved.
|
|
24
|
+
* @returns A promise that resolves to the content of the clipboard, or an empty string if the
|
|
25
|
+
* clipboard is empty or permission was denied.
|
|
26
|
+
*/
|
|
27
|
+
export async function getStringAsync(options: IGetStringOptions = {}): Promise<string> {
|
|
28
|
+
if (!expoClipboard.getStringAsync) {
|
|
29
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getStringAsync');
|
|
30
|
+
}
|
|
31
|
+
return expoClipboard.getStringAsync(options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sets the content of the user's clipboard.
|
|
36
|
+
*
|
|
37
|
+
* @param text The string to save to the clipboard.
|
|
38
|
+
* @param options Options for the clipboard content to be set.
|
|
39
|
+
* @returns A promise that resolves to `true` once the string has been saved to the clipboard.
|
|
40
|
+
*/
|
|
41
|
+
export async function setStringAsync(
|
|
42
|
+
text: string,
|
|
43
|
+
options: ISetStringOptions = {},
|
|
44
|
+
): Promise<boolean> {
|
|
45
|
+
if (!expoClipboard.setStringAsync) {
|
|
46
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setStringAsync');
|
|
47
|
+
}
|
|
48
|
+
return expoClipboard.setStringAsync(text, options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns whether the clipboard has text content. Returns `true` for both plain text and rich
|
|
53
|
+
* text (e.g. HTML).
|
|
54
|
+
*/
|
|
55
|
+
export function hasStringAsync(): Promise<boolean> {
|
|
56
|
+
if (!expoClipboard.hasStringAsync) {
|
|
57
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasStringAsync');
|
|
58
|
+
}
|
|
59
|
+
return expoClipboard.hasStringAsync();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gets the URL from the user's clipboard.
|
|
64
|
+
*
|
|
65
|
+
* Note: On iOS 16+, if the user denies paste permission, this method returns `null`. Due to iOS
|
|
66
|
+
* platform limitations, there is no way to distinguish between no URL in clipboard and denied
|
|
67
|
+
* permission.
|
|
68
|
+
* @platform ios
|
|
69
|
+
*/
|
|
70
|
+
export async function getUrlAsync(): Promise<string | null> {
|
|
71
|
+
if (!expoClipboard.getUrlAsync) {
|
|
72
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getUrlAsync');
|
|
73
|
+
}
|
|
74
|
+
return expoClipboard.getUrlAsync();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sets a URL in the user's clipboard. Behaves the same as `setStringAsync`, except that it sets
|
|
79
|
+
* the clipboard content type to be a URL, letting your app or other apps know that the clipboard
|
|
80
|
+
* contains a URL and behave accordingly.
|
|
81
|
+
* @platform ios
|
|
82
|
+
*/
|
|
83
|
+
export async function setUrlAsync(url: string): Promise<void> {
|
|
84
|
+
if (!expoClipboard.setUrlAsync) {
|
|
85
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setUrlAsync');
|
|
86
|
+
}
|
|
87
|
+
return expoClipboard.setUrlAsync(url);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns whether the clipboard has URL content.
|
|
92
|
+
* @platform ios
|
|
93
|
+
*/
|
|
94
|
+
export async function hasUrlAsync(): Promise<boolean> {
|
|
95
|
+
if (!expoClipboard.hasUrlAsync) {
|
|
96
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasUrlAsync');
|
|
97
|
+
}
|
|
98
|
+
return expoClipboard.hasUrlAsync();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Gets the image from the user's clipboard and returns it in the specified format.
|
|
103
|
+
*
|
|
104
|
+
* Note: On iOS 16+, if the user denies paste permission, this method returns `null`. Due to iOS
|
|
105
|
+
* platform limitations, there is no way to distinguish between no image in clipboard and denied
|
|
106
|
+
* permission.
|
|
107
|
+
*
|
|
108
|
+
* @param options Specifies the desired format of the image.
|
|
109
|
+
* @returns If there was an image in the clipboard, resolves to an `IClipboardImage` object
|
|
110
|
+
* containing the base64 string and metadata of the image. Otherwise resolves to `null` (this
|
|
111
|
+
* includes cases where permission was denied).
|
|
112
|
+
*/
|
|
113
|
+
export async function getImageAsync(options: IGetImageOptions): Promise<IClipboardImage | null> {
|
|
114
|
+
if (!expoClipboard.getImageAsync) {
|
|
115
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getImageAsync');
|
|
116
|
+
}
|
|
117
|
+
return expoClipboard.getImageAsync(options);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Sets an image in the user's clipboard.
|
|
122
|
+
*
|
|
123
|
+
* @param base64Image Image encoded as a base64 string, without MIME type.
|
|
124
|
+
*/
|
|
125
|
+
export async function setImageAsync(base64Image: string): Promise<void> {
|
|
126
|
+
if (!expoClipboard.setImageAsync) {
|
|
127
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setImageAsync');
|
|
128
|
+
}
|
|
129
|
+
return expoClipboard.setImageAsync(base64Image);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns whether the clipboard has image content.
|
|
134
|
+
*/
|
|
135
|
+
export async function hasImageAsync(): Promise<boolean> {
|
|
136
|
+
if (!expoClipboard.hasImageAsync) {
|
|
137
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasImageAsync');
|
|
138
|
+
}
|
|
139
|
+
return expoClipboard.hasImageAsync();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Adds a listener that fires whenever the content of the user's clipboard changes. Kept here at
|
|
144
|
+
* the core level, framework-agnostic, exactly like `Accelerometer.addListener` lives in
|
|
145
|
+
* `@symbiote-native/sensors`' core — each adapter's `useClipboard` hook/composable/service wraps
|
|
146
|
+
* this in its own mount/unmount lifecycle rather than reimplementing the subscription.
|
|
147
|
+
*
|
|
148
|
+
* @param listener Callback invoked with an `IClipboardEvent` describing the new clipboard
|
|
149
|
+
* content types whenever the clipboard changes.
|
|
150
|
+
*/
|
|
151
|
+
export function addClipboardListener(
|
|
152
|
+
listener: (event: IClipboardEvent) => void,
|
|
153
|
+
): EventSubscription {
|
|
154
|
+
return expoClipboard.addListener(CLIPBOARD_CHANGED_EVENT_NAME, listener);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Removes the listener added by `addClipboardListener`.
|
|
159
|
+
* @deprecated use `subscription.remove()` instead.
|
|
160
|
+
*/
|
|
161
|
+
export function removeClipboardListener(subscription: EventSubscription): void {
|
|
162
|
+
subscription.remove();
|
|
163
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
getStringAsync,
|
|
3
|
+
setStringAsync,
|
|
4
|
+
hasStringAsync,
|
|
5
|
+
getUrlAsync,
|
|
6
|
+
setUrlAsync,
|
|
7
|
+
hasUrlAsync,
|
|
8
|
+
getImageAsync,
|
|
9
|
+
setImageAsync,
|
|
10
|
+
hasImageAsync,
|
|
11
|
+
addClipboardListener,
|
|
12
|
+
removeClipboardListener,
|
|
13
|
+
} from './clipboard';
|
|
14
|
+
export {
|
|
15
|
+
ContentType,
|
|
16
|
+
StringFormat,
|
|
17
|
+
type IGetStringOptions,
|
|
18
|
+
type ISetStringOptions,
|
|
19
|
+
type IGetImageOptions,
|
|
20
|
+
type IClipboardImage,
|
|
21
|
+
type IClipboardEvent,
|
|
22
|
+
} from './types';
|
|
23
|
+
export type { EventSubscription } from 'expo-modules-core';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { EventSubscription } from 'expo-modules-core';
|
|
3
|
+
import type {
|
|
4
|
+
IClipboardEvent,
|
|
5
|
+
IClipboardImage,
|
|
6
|
+
IGetImageOptions,
|
|
7
|
+
IGetStringOptions,
|
|
8
|
+
ISetStringOptions,
|
|
9
|
+
} from './types';
|
|
10
|
+
|
|
11
|
+
const EXPO_CLIPBOARD_MODULE_NAME = 'ExpoClipboard';
|
|
12
|
+
|
|
13
|
+
// The event name upstream's own ExpoClipboard.ts pairs with the native module's event map —
|
|
14
|
+
// kept here, next to the module resolution, since core/clipboard.ts's addClipboardListener is
|
|
15
|
+
// the only caller.
|
|
16
|
+
export const CLIPBOARD_CHANGED_EVENT_NAME = 'onClipboardChanged';
|
|
17
|
+
|
|
18
|
+
// addListener/removeAllListeners come from expo-modules-core's own NativeModule<Events> base
|
|
19
|
+
// class (an EventEmitter) — always present, unlike the async methods below, each of which is
|
|
20
|
+
// optional and checked at its own call site before throwing an UnavailabilityError, mirroring
|
|
21
|
+
// @symbiote-native/local-auth's native-module.ts and upstream's own per-platform capability
|
|
22
|
+
// checks (getUrlAsync/setUrlAsync/hasUrlAsync are iOS-only, hence optional there too).
|
|
23
|
+
export type INativeClipboardModule = {
|
|
24
|
+
addListener(
|
|
25
|
+
eventName: typeof CLIPBOARD_CHANGED_EVENT_NAME,
|
|
26
|
+
listener: (event: IClipboardEvent) => void,
|
|
27
|
+
): EventSubscription;
|
|
28
|
+
removeAllListeners(eventName: typeof CLIPBOARD_CHANGED_EVENT_NAME): void;
|
|
29
|
+
getStringAsync?(options?: IGetStringOptions): Promise<string>;
|
|
30
|
+
setStringAsync?(text: string, options?: ISetStringOptions): Promise<boolean>;
|
|
31
|
+
hasStringAsync?(): Promise<boolean>;
|
|
32
|
+
getUrlAsync?(): Promise<string | null>;
|
|
33
|
+
setUrlAsync?(url: string): Promise<void>;
|
|
34
|
+
hasUrlAsync?(): Promise<boolean>;
|
|
35
|
+
getImageAsync?(options: IGetImageOptions): Promise<IClipboardImage | null>;
|
|
36
|
+
setImageAsync?(base64Image: string): Promise<void>;
|
|
37
|
+
hasImageAsync?(): Promise<boolean>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const expoClipboard = requireNativeModule<INativeClipboardModule>(
|
|
41
|
+
EXPO_CLIPBOARD_MODULE_NAME,
|
|
42
|
+
);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Ported from expo-clipboard's own Clipboard.types.ts. ContentType and StringFormat are enums —
|
|
2
|
+
// kept unprefixed, matching this repo's convention for enums (see AuthenticationType /
|
|
3
|
+
// SecurityLevel in @symbiote-native/local-auth); the plain option/result shapes get this repo's
|
|
4
|
+
// `I`-prefix convention for exported types. `ClipboardPasteButton`'s own types
|
|
5
|
+
// (PasteEventPayload, TextPasteEvent, ImagePasteEvent, AcceptedContentType, CornerStyleType,
|
|
6
|
+
// DisplayModeType) are intentionally not ported — that native paste-button view is out of scope
|
|
7
|
+
// for this pass (see the package README).
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type used to define what type of data is stored in the clipboard.
|
|
11
|
+
*/
|
|
12
|
+
export enum ContentType {
|
|
13
|
+
PLAIN_TEXT = 'plain-text',
|
|
14
|
+
HTML = 'html',
|
|
15
|
+
IMAGE = 'image',
|
|
16
|
+
/**
|
|
17
|
+
* @platform iOS
|
|
18
|
+
*/
|
|
19
|
+
URL = 'url',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Type used to determine string format stored in the clipboard.
|
|
24
|
+
*/
|
|
25
|
+
export enum StringFormat {
|
|
26
|
+
PLAIN_TEXT = 'plainText',
|
|
27
|
+
HTML = 'html',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type IGetStringOptions = {
|
|
31
|
+
/**
|
|
32
|
+
* The target format of the clipboard string to be converted to, if possible.
|
|
33
|
+
* @default StringFormat.PLAIN_TEXT
|
|
34
|
+
*/
|
|
35
|
+
preferredFormat?: StringFormat;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ISetStringOptions = {
|
|
39
|
+
/**
|
|
40
|
+
* The input format of the provided string. Adjusting this option can help other applications
|
|
41
|
+
* interpret copied string properly.
|
|
42
|
+
* @default StringFormat.PLAIN_TEXT
|
|
43
|
+
*/
|
|
44
|
+
inputFormat?: StringFormat;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type IGetImageOptions = {
|
|
48
|
+
/**
|
|
49
|
+
* The format of the clipboard image to be converted to.
|
|
50
|
+
*/
|
|
51
|
+
format: 'png' | 'jpeg';
|
|
52
|
+
/**
|
|
53
|
+
* Specify the quality of the returned image, between `0` and `1`. Defaults to `1` (highest
|
|
54
|
+
* quality). Applicable only when `format` is set to `jpeg`, ignored otherwise.
|
|
55
|
+
* @default 1
|
|
56
|
+
*/
|
|
57
|
+
jpegQuality?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type IClipboardImage = {
|
|
61
|
+
/**
|
|
62
|
+
* A Base64-encoded string of the image data, already prepended with a
|
|
63
|
+
* `data:image/png;base64,` or `data:image/jpeg;base64,` prefix. Its format depends on the
|
|
64
|
+
* `format` option passed to `getImageAsync`.
|
|
65
|
+
*/
|
|
66
|
+
data: string;
|
|
67
|
+
/**
|
|
68
|
+
* Dimensions (`width` and `height`) of the image pasted from clipboard.
|
|
69
|
+
*/
|
|
70
|
+
size: {
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type IClipboardEvent = {
|
|
77
|
+
/**
|
|
78
|
+
* An array of content types that are available on the clipboard.
|
|
79
|
+
*/
|
|
80
|
+
contentTypes: ContentType[];
|
|
81
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic addClipboardListener subscription (core/) —
|
|
2
|
+
// mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks and the shape of
|
|
3
|
+
// @symbiote-native/sensors' useAccelerometer, adjusted for clipboard's single always-on
|
|
4
|
+
// subscription (there is no per-call config like updateIntervalMs to resubscribe on).
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { addClipboardListener, type IClipboardEvent } from '../../../core';
|
|
7
|
+
|
|
8
|
+
export function useClipboard(): IClipboardEvent | null {
|
|
9
|
+
const [event, setEvent] = useState<IClipboardEvent | null>(null);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const subscription = addClipboardListener(setEvent);
|
|
13
|
+
return () => subscription.remove();
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
return event;
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/react: the React entry over the framework-agnostic core. Unlike
|
|
2
|
+
// @symbiote-native/local-auth (all stateless functions, plain re-export), clipboard has one
|
|
3
|
+
// listener-based piece — useClipboard wraps addClipboardListener with React's own lifecycle
|
|
4
|
+
// (hooks/use-clipboard), mirroring the lifecycle-bucket naming convention of
|
|
5
|
+
// adapters/react/src/hooks (never `composables`, that's Vue's term) and the shape of
|
|
6
|
+
// @symbiote-native/sensors' useAccelerometer.
|
|
7
|
+
|
|
8
|
+
export { useClipboard } from './hooks/use-clipboard';
|
|
9
|
+
export * from '../core';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vue lifecycle wiring over the framework-agnostic addClipboardListener subscription (core/) —
|
|
2
|
+
// mirrors the lifecycle-bucket naming convention of adapters/vue/src/composables and the
|
|
3
|
+
// onMounted/onUnmounted shape of @symbiote-native/sensors' useAccelerometer composable, adjusted
|
|
4
|
+
// for clipboard's single always-on subscription (no per-call config to resubscribe on).
|
|
5
|
+
|
|
6
|
+
import { onMounted, onUnmounted, ref, type Ref } from '@vue/runtime-core';
|
|
7
|
+
import { addClipboardListener, type EventSubscription, type IClipboardEvent } from '../../../core';
|
|
8
|
+
|
|
9
|
+
export function useClipboard(): Ref<IClipboardEvent | null> {
|
|
10
|
+
// A plain ref: the value is a POJO clipboard event, not an engine node, so no shallowRef needed.
|
|
11
|
+
const event = ref<IClipboardEvent | null>(null);
|
|
12
|
+
let subscription: EventSubscription | undefined;
|
|
13
|
+
|
|
14
|
+
onMounted(() => {
|
|
15
|
+
subscription = addClipboardListener(next => {
|
|
16
|
+
event.value = next;
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
onUnmounted(() => {
|
|
21
|
+
subscription?.remove();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return event;
|
|
25
|
+
}
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/vue: the Vue entry over the framework-agnostic core. Unlike
|
|
2
|
+
// @symbiote-native/local-auth (all stateless functions, plain re-export), clipboard has one
|
|
3
|
+
// listener-based piece — useClipboard wires addClipboardListener's lifecycle onto Vue's own
|
|
4
|
+
// onMounted/onUnmounted (composables/use-clipboard), mirroring the lifecycle-bucket naming
|
|
5
|
+
// convention of adapters/vue/src/composables (never `hooks/`, that's React's term) and the
|
|
6
|
+
// shape of @symbiote-native/sensors' useAccelerometer composable.
|
|
7
|
+
|
|
8
|
+
export { useClipboard } from './composables/use-clipboard';
|
|
9
|
+
export * from '../core';
|