@symbiote-native/clipboard 0.1.0 → 0.3.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 +51 -20
- package/build/angular/services/clipboard.service/index.js +1 -1
- package/build/core/clipboard.js +6 -1
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +8 -0
- package/build/solid/primitives/create-clipboard.d.ts +3 -0
- package/build/solid/primitives/create-clipboard.js +30 -0
- package/build/svelte/index.d.ts +2 -0
- package/build/svelte/index.js +7 -0
- package/build/svelte/runes/use-clipboard.svelte.d.ts +4 -0
- package/build/svelte/runes/use-clipboard.svelte.js +30 -0
- package/build/vue/composables/use-clipboard/index.js +1 -1
- package/build-ngc/angular/services/clipboard.service/index.js +4 -4
- package/build-ngc/angular/services/clipboard.service/index.js.map +1 -1
- package/build-ngc/core/clipboard.js +6 -1
- package/build-ngc/core/clipboard.js.map +1 -1
- package/package.json +42 -14
- package/src/angular/services/clipboard.service/index.ts +8 -1
- package/src/core/clipboard.ts +12 -3
- package/src/solid/index.ts +9 -0
- package/src/solid/primitives/create-clipboard.ts +38 -0
- package/src/svelte/index.ts +8 -0
- package/src/svelte/runes/use-clipboard.svelte.ts +37 -0
- package/src/svelte/svelte-ambient.d.ts +6 -0
- package/src/vue/composables/use-clipboard/index.ts +5 -1
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A wrapper package for [SymbioteNative](../../README.md) that makes
|
|
4
4
|
[`expo-clipboard`](https://docs.expo.dev/versions/latest/sdk/clipboard/) — read/write clipboard
|
|
5
|
-
text, URLs, and images, plus a clipboard-change listener — usable from **every** adapter
|
|
6
|
-
Vue, and Angular, not just React. Unlike `@symbiote-native/sensors`, which is all
|
|
5
|
+
text, URLs, and images, plus a clipboard-change listener — usable from **every** adapter: React,
|
|
6
|
+
Vue, Svelte, Solid, and Angular, not just React. Unlike `@symbiote-native/sensors`, which is all
|
|
7
7
|
`DeviceSensor`-shaped classes plus one free-function module (`Pedometer`), clipboard is closer to
|
|
8
8
|
`@symbiote-native/local-auth`'s shape — mostly stateless async functions — plus **one**
|
|
9
9
|
listener-based subscription (`addClipboardListener`) that each adapter wraps in its own
|
|
@@ -28,12 +28,12 @@ Unlike a plain RN native module, `expo-clipboard`'s native code is discovered by
|
|
|
28
28
|
into the native host app **once**, covering this package and every other `expo-modules-core`
|
|
29
29
|
package (`@symbiote-native/sensors`, `@symbiote-native/local-auth`) with zero further changes:
|
|
30
30
|
|
|
31
|
-
| Platform | Touches
|
|
32
|
-
|
|
33
|
-
| iOS
|
|
34
|
-
| iOS
|
|
35
|
-
| Android
|
|
36
|
-
| Android
|
|
31
|
+
| Platform | Touches |
|
|
32
|
+
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| iOS | `ios/Podfile` — add `use_expo_modules!` |
|
|
34
|
+
| iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
|
|
35
|
+
| Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
|
|
36
|
+
| Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
|
|
37
37
|
|
|
38
38
|
Full mechanics live in the `symbiote-expo-native-module` skill. Clipboard itself needs no
|
|
39
39
|
`Info.plist`/`AndroidManifest.xml` permission entry on either platform — reading/writing the
|
|
@@ -49,22 +49,30 @@ src/core/ getStringAsync/setStringAsync/hasStringAsync, getUrlAsyn
|
|
|
49
49
|
requireNativeModule.
|
|
50
50
|
src/react/hooks/ @symbiote-native/clipboard/react — useClipboard
|
|
51
51
|
src/vue/composables/ @symbiote-native/clipboard/vue — useClipboard (same name)
|
|
52
|
+
src/svelte/runes/ @symbiote-native/clipboard/svelte — useClipboard (same name)
|
|
53
|
+
src/solid/primitives/ @symbiote-native/clipboard/solid — createClipboard (Solid says
|
|
54
|
+
create*, not use*)
|
|
52
55
|
src/angular/services/ @symbiote-native/clipboard/angular — ClipboardService
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
Every function except the listener is plain stateless async and re-exported as-is. The listener
|
|
56
59
|
subscription (`addClipboardListener`) lives once in `core`, framework-agnostic; each adapter's
|
|
57
|
-
`useClipboard` hook/composable
|
|
58
|
-
`effect()
|
|
59
|
-
shared by
|
|
60
|
+
`useClipboard` hook/composable/rune, Solid's `createClipboard`, and `ClipboardService.connect()`
|
|
61
|
+
are thin mount/unmount wrappers (a DI-scoped `effect()` for Angular, a body-level subscription plus
|
|
62
|
+
`onCleanup` for Solid) around that same subscription — the plumbing is written once and shared by
|
|
63
|
+
every adapter.
|
|
60
64
|
|
|
61
65
|
## Use it
|
|
62
66
|
|
|
63
67
|
```ts
|
|
64
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
getStringAsync,
|
|
70
|
+
setStringAsync,
|
|
71
|
+
addClipboardListener,
|
|
72
|
+
} from '@symbiote-native/clipboard';
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
`useClipboard()`'s event only carries the clipboard's changed content
|
|
75
|
+
`useClipboard()`'s event only carries the clipboard's changed content _types_
|
|
68
76
|
(`IClipboardEvent.contentTypes`), never the string itself — every adapter's demo screen treats a
|
|
69
77
|
change as a cue to re-fetch via `getStringAsync()`, not a value to render directly.
|
|
70
78
|
|
|
@@ -82,7 +90,8 @@ function ClipboardScreen() {
|
|
|
82
90
|
getStringAsync().then(setText);
|
|
83
91
|
}, [clipboardEvent]);
|
|
84
92
|
|
|
85
|
-
const handleCopy = (input: string) =>
|
|
93
|
+
const handleCopy = (input: string) =>
|
|
94
|
+
setStringAsync(input).then(() => getStringAsync().then(setText));
|
|
86
95
|
|
|
87
96
|
return <Text>{text ?? 'checking…'}</Text>;
|
|
88
97
|
}
|
|
@@ -97,12 +106,16 @@ import { useClipboard } from '@symbiote-native/clipboard/vue';
|
|
|
97
106
|
|
|
98
107
|
const text = ref('checking…');
|
|
99
108
|
function refresh(): void {
|
|
100
|
-
void getStringAsync().then(value => {
|
|
109
|
+
void getStringAsync().then(value => {
|
|
110
|
+
text.value = value;
|
|
111
|
+
});
|
|
101
112
|
}
|
|
102
113
|
onMounted(refresh);
|
|
103
114
|
|
|
104
115
|
const clipboardEvent = useClipboard(); // Ref<IClipboardEvent | null>
|
|
105
|
-
watch(clipboardEvent, event => {
|
|
116
|
+
watch(clipboardEvent, event => {
|
|
117
|
+
if (event) refresh();
|
|
118
|
+
});
|
|
106
119
|
|
|
107
120
|
function handleCopy(input: string): void {
|
|
108
121
|
void setStringAsync(input).then(refresh);
|
|
@@ -116,9 +129,13 @@ function handleCopy(input: string): void {
|
|
|
116
129
|
```ts
|
|
117
130
|
// Angular
|
|
118
131
|
import { Component, Injector, effect, inject, signal } from '@angular/core';
|
|
119
|
-
import {
|
|
132
|
+
import {
|
|
133
|
+
ClipboardService,
|
|
134
|
+
getStringAsync,
|
|
135
|
+
setStringAsync,
|
|
136
|
+
} from '@symbiote-native/clipboard/angular';
|
|
120
137
|
|
|
121
|
-
@Component({
|
|
138
|
+
@Component({/* ... */})
|
|
122
139
|
export class ClipboardScreen {
|
|
123
140
|
private readonly injector = inject(Injector);
|
|
124
141
|
private readonly clipboardEvent = inject(ClipboardService).connect(); // Signal<IClipboardEvent | null>
|
|
@@ -126,7 +143,12 @@ export class ClipboardScreen {
|
|
|
126
143
|
|
|
127
144
|
constructor() {
|
|
128
145
|
this.refresh();
|
|
129
|
-
effect(
|
|
146
|
+
effect(
|
|
147
|
+
() => {
|
|
148
|
+
if (this.clipboardEvent() !== null) this.refresh();
|
|
149
|
+
},
|
|
150
|
+
{ injector: this.injector },
|
|
151
|
+
);
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
handleCopy(input: string): void {
|
|
@@ -175,6 +197,14 @@ const clipboardEvent = useClipboard(); // IClipboardEvent | null
|
|
|
175
197
|
import { useClipboard } from '@symbiote-native/clipboard/vue';
|
|
176
198
|
const clipboardEvent = useClipboard(); // Ref<IClipboardEvent | null>
|
|
177
199
|
|
|
200
|
+
// Svelte
|
|
201
|
+
import { useClipboard } from '@symbiote-native/clipboard/svelte';
|
|
202
|
+
const clipboard = useClipboard(); // clipboard.current: IClipboardEvent | null
|
|
203
|
+
|
|
204
|
+
// Solid
|
|
205
|
+
import { createClipboard } from '@symbiote-native/clipboard/solid';
|
|
206
|
+
const clipboardEvent = createClipboard(); // Accessor<IClipboardEvent | null>
|
|
207
|
+
|
|
178
208
|
// Angular
|
|
179
209
|
import { ClipboardService } from '@symbiote-native/clipboard/angular';
|
|
180
210
|
readonly clipboardEvent = inject(ClipboardService).connect(); // Signal<IClipboardEvent | null>
|
|
@@ -188,7 +218,8 @@ ported — out of scope for this pass. If it's ever wrapped, it follows
|
|
|
188
218
|
|
|
189
219
|
Tests exercise the JS layer only, against a fake native module in place of the real
|
|
190
220
|
`requireNativeModule` resolution (`src/core/**/*.test.ts`,
|
|
191
|
-
`src/{react,vue,angular}/**/*.test.{ts,tsx}`) — no Fabric/Descriptor angle at all,
|
|
221
|
+
`src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`) — no Fabric/Descriptor angle at all,
|
|
222
|
+
since clipboard
|
|
192
223
|
is a pure async-function + one-listener surface, never a view. Native rendering itself is verified
|
|
193
224
|
on-device (see the parent [README](../../README.md) for the project's testing model).
|
|
194
225
|
|
|
@@ -32,7 +32,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
32
32
|
}
|
|
33
33
|
return useValue ? value : void 0;
|
|
34
34
|
};
|
|
35
|
-
import { effect, inject, Injectable, Injector, signal } from '@angular/core';
|
|
35
|
+
import { effect, inject, Injectable, Injector, signal, } from '@angular/core';
|
|
36
36
|
import { addClipboardListener } from '../../../core/index.js';
|
|
37
37
|
// Angular twin of React's `useClipboard` hook and Vue's `useClipboard` composable. Angular has
|
|
38
38
|
// no per-instance hook — state and lifecycle live in DI instead, so `connect()` stands in for
|
package/build/core/clipboard.js
CHANGED
|
@@ -36,7 +36,12 @@ export async function setStringAsync(text, options = {}) {
|
|
|
36
36
|
* Returns whether the clipboard has text content. Returns `true` for both plain text and rich
|
|
37
37
|
* text (e.g. HTML).
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
// DELIBERATE DIVERGENCE from expo: Clipboard.ts:57 declares this one plain `function`, not
|
|
40
|
+
// `async` like its 8 siblings, so on a build without the native method the UnavailabilityError
|
|
41
|
+
// escapes synchronously at the call site and `hasStringAsync().catch(handler)` never catches it.
|
|
42
|
+
// Parity would hand callers a guard that fires differently from every other method in this API,
|
|
43
|
+
// so we add `async` here instead of tagging it. Everything else stays a verbatim port.
|
|
44
|
+
export async function hasStringAsync() {
|
|
40
45
|
if (!expoClipboard.hasStringAsync) {
|
|
41
46
|
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasStringAsync');
|
|
42
47
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/solid: the Solid 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 — createClipboard wires addClipboardListener onto Solid's own ownership
|
|
4
|
+
// tree (primitives/create-clipboard.ts). The lifecycle bucket is `primitives/` and the name is
|
|
5
|
+
// `create*` (never `hooks/`/`use*`, that is React's term and Solid's word for consuming an
|
|
6
|
+
// existing thing) — see adapters/solid/src/primitives for the same convention.
|
|
7
|
+
export { createClipboard } from './primitives/create-clipboard.js';
|
|
8
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Solid lifecycle wiring over the framework-agnostic addClipboardListener subscription (core/) —
|
|
2
|
+
// the Solid twin of React's useClipboard hook, Vue's composable, Svelte's rune and Angular's
|
|
3
|
+
// ClipboardService.connect(), adjusted for clipboard's single always-on subscription (no per-call
|
|
4
|
+
// config to resubscribe on).
|
|
5
|
+
//
|
|
6
|
+
// `primitives/` and `create*`, not `hooks/`/`use*`: Solid's ecosystem calls a composable reactive
|
|
7
|
+
// function a PRIMITIVE and reserves `use*` for consuming something that already exists. Full
|
|
8
|
+
// rationale in adapters/solid/src/primitives/create-color-scheme.ts's header.
|
|
9
|
+
//
|
|
10
|
+
// Returns an ACCESSOR, never a snapshot: a Solid component body runs ONCE, so a plain returned
|
|
11
|
+
// value would pin the consumer to `null` forever.
|
|
12
|
+
//
|
|
13
|
+
// Subscribes SYNCHRONOUSLY in the body with `onCleanup` for teardown, not from a mount hook
|
|
14
|
+
// (React's useEffect, Vue's onMounted, Svelte's $effect) — this closes the gap those three have
|
|
15
|
+
// between construction and subscription, in which a clipboard change fires and is never seen.
|
|
16
|
+
//
|
|
17
|
+
// Outside a component / createRoot there is no owner for `onCleanup`, so the subscription lives
|
|
18
|
+
// for the process — see create-color-scheme's header for the same documented caveat.
|
|
19
|
+
import { createSignal, onCleanup } from 'solid-js';
|
|
20
|
+
import { addClipboardListener, } from '../../core/index.js';
|
|
21
|
+
export function createClipboard() {
|
|
22
|
+
const [event, setEvent] = createSignal(null);
|
|
23
|
+
const subscription = addClipboardListener(next => {
|
|
24
|
+
setEvent(next);
|
|
25
|
+
});
|
|
26
|
+
onCleanup(() => {
|
|
27
|
+
subscription.remove();
|
|
28
|
+
});
|
|
29
|
+
return event;
|
|
30
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/svelte: the Svelte 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 a Svelte
|
|
4
|
+
// `$effect` (runes/use-clipboard.svelte.ts), mirroring the lifecycle-bucket naming convention of
|
|
5
|
+
// adapters/svelte/src/runes (never `hooks/`/`composables/`, those are React's and Vue's terms).
|
|
6
|
+
export { useClipboard } from './runes/use-clipboard.svelte';
|
|
7
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Svelte lifecycle wiring over the framework-agnostic addClipboardListener subscription (core/) —
|
|
2
|
+
// the Svelte twin of vue/composables/use-clipboard and react/hooks/use-clipboard, adjusted for
|
|
3
|
+
// clipboard's single always-on subscription (no per-call config to resubscribe on).
|
|
4
|
+
//
|
|
5
|
+
// `.svelte.ts` (not `.ts`): runes ($state/$effect) are only usable in a file with this extension
|
|
6
|
+
// outside an actual `.svelte` component. `runes/` is Svelte's own term for the bucket React calls
|
|
7
|
+
// `hooks/` and Vue calls `composables/` — see adapters/svelte/src/runes.
|
|
8
|
+
//
|
|
9
|
+
// Vue's onMounted/onUnmounted pair collapses into ONE `$effect` whose returned function is the
|
|
10
|
+
// teardown. The effect only WRITES `event`, never reads it, so its dependency set stays empty: it
|
|
11
|
+
// subscribes exactly once on mount and removes the subscription exactly once on unmount.
|
|
12
|
+
//
|
|
13
|
+
// Returns a boxed getter object, NOT a bare `$state`: Svelte 5 reactivity is lexically scoped to
|
|
14
|
+
// the declaring module, so a raw `let x = $state(...)` handed out of a plain function arrives
|
|
15
|
+
// dead at the caller. `.current` is the Svelte equivalent of unwrapping Vue's `Ref` via `.value`.
|
|
16
|
+
import { addClipboardListener, } from '../../core/index.js';
|
|
17
|
+
export function useClipboard() {
|
|
18
|
+
let event = $state(null);
|
|
19
|
+
$effect(() => {
|
|
20
|
+
const subscription = addClipboardListener(next => {
|
|
21
|
+
event = next;
|
|
22
|
+
});
|
|
23
|
+
return () => subscription.remove();
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
get current() {
|
|
27
|
+
return event;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// onMounted/onUnmounted shape of @symbiote-native/sensors' useAccelerometer composable, adjusted
|
|
4
4
|
// for clipboard's single always-on subscription (no per-call config to resubscribe on).
|
|
5
5
|
import { onMounted, onUnmounted, ref } from '@vue/runtime-core';
|
|
6
|
-
import { addClipboardListener } from '../../../core/index.js';
|
|
6
|
+
import { addClipboardListener, } from '../../../core/index.js';
|
|
7
7
|
export function useClipboard() {
|
|
8
8
|
// A plain ref: the value is a POJO clipboard event, not an engine node, so no shallowRef needed.
|
|
9
9
|
const event = ref(null);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { effect, inject, Injectable, Injector, signal } from '@angular/core';
|
|
1
|
+
import { effect, inject, Injectable, Injector, signal, } from '@angular/core';
|
|
2
2
|
import { addClipboardListener } from '../../../core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
// Angular twin of React's `useClipboard` hook and Vue's `useClipboard` composable. Angular has
|
|
@@ -26,10 +26,10 @@ export class ClipboardService {
|
|
|
26
26
|
}, { injector: this.injector });
|
|
27
27
|
return event.asReadonly();
|
|
28
28
|
}
|
|
29
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
30
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ClipboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
30
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ClipboardService, providedIn: 'root' });
|
|
31
31
|
}
|
|
32
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ClipboardService, decorators: [{
|
|
33
33
|
type: Injectable,
|
|
34
34
|
args: [{ providedIn: 'root' }]
|
|
35
35
|
}] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/clipboard.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/clipboard.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,GAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAwB,MAAM,eAAe,CAAC;;AAE3E,+FAA+F;AAC/F,8FAA8F;AAC9F,2FAA2F;AAC3F,sBAAsB;AACtB,EAAE;AACF,kEAAkE;AAClE,sDAAsD;AACtD,EAAE;AACF,iGAAiG;AACjG,wFAAwF;AACxF,8DAA8D;AAE9D,MAAM,OAAO,gBAAgB;IAC3B,8FAA8F;IAC9F,4FAA4F;IAC5F,4FAA4F;IAC3E,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,OAAO;QACL,MAAM,KAAK,GAAG,MAAM,CAAyB,IAAI;kFAAC,CAAC;QAEnD,MAAM,CACJ,SAAS,CAAC,EAAE;YACV,MAAM,YAAY,GAAG,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YACnE,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,CAAC,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAC5B,CAAC;QAEF,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC;IAC5B,CAAC;uGAlBU,gBAAgB;2GAAhB,gBAAgB,cADH,MAAM;;2FACnB,gBAAgB;kBAD5B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
|
|
@@ -36,7 +36,12 @@ export async function setStringAsync(text, options = {}) {
|
|
|
36
36
|
* Returns whether the clipboard has text content. Returns `true` for both plain text and rich
|
|
37
37
|
* text (e.g. HTML).
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
// DELIBERATE DIVERGENCE from expo: Clipboard.ts:57 declares this one plain `function`, not
|
|
40
|
+
// `async` like its 8 siblings, so on a build without the native method the UnavailabilityError
|
|
41
|
+
// escapes synchronously at the call site and `hasStringAsync().catch(handler)` never catches it.
|
|
42
|
+
// Parity would hand callers a guard that fires differently from every other method in this API,
|
|
43
|
+
// so we add `async` here instead of tagging it. Everything else stays a verbatim port.
|
|
44
|
+
export async function hasStringAsync() {
|
|
40
45
|
if (!expoClipboard.hasStringAsync) {
|
|
41
46
|
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasStringAsync');
|
|
42
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/core/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,4BAA4B,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAS9E,2FAA2F;AAC3F,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/core/clipboard.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/clipboard",
|
|
3
|
-
"version": "0.
|
|
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.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "expo-clipboard wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Reads and writes clipboard text, URLs, and images, plus a clipboard-change listener.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"types": "./build/react/index.d.ts",
|
|
31
31
|
"default": "./build/react/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./svelte": {
|
|
34
|
+
"types": "./build/svelte/index.d.ts",
|
|
35
|
+
"default": "./build/svelte/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./solid": {
|
|
38
|
+
"types": "./build/solid/index.d.ts",
|
|
39
|
+
"default": "./build/solid/index.js"
|
|
40
|
+
},
|
|
33
41
|
"./angular": {
|
|
34
42
|
"types": "./build-ngc/angular/index.d.ts",
|
|
35
43
|
"react-native": "./build-ngc/angular/index.js",
|
|
@@ -53,15 +61,19 @@
|
|
|
53
61
|
"expo-modules-core": "57.0.5"
|
|
54
62
|
},
|
|
55
63
|
"peerDependencies": {
|
|
64
|
+
"@symbiote-native/angular": ">=0.6.2",
|
|
56
65
|
"@symbiote-native/engine": ">=0.1.7",
|
|
66
|
+
"@symbiote-native/react": ">=0.2.8",
|
|
67
|
+
"@symbiote-native/solid": ">=0.0.1",
|
|
68
|
+
"@symbiote-native/svelte": ">=0.1.0",
|
|
69
|
+
"@symbiote-native/vue": ">=0.3.8",
|
|
57
70
|
"@angular/core": ">=20",
|
|
58
71
|
"@vue/runtime-core": "^3.5.13",
|
|
59
72
|
"react": ">=19.0.0",
|
|
60
73
|
"react-native": ">=0.86",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"@symbiote-native/vue": "0.3.8"
|
|
74
|
+
"solid-js": ">=1.9.0",
|
|
75
|
+
"svelte": ">=5.56.0",
|
|
76
|
+
"vue": ">=3.5.0"
|
|
65
77
|
},
|
|
66
78
|
"peerDependenciesMeta": {
|
|
67
79
|
"@symbiote-native/angular": {
|
|
@@ -70,6 +82,12 @@
|
|
|
70
82
|
"@symbiote-native/react": {
|
|
71
83
|
"optional": true
|
|
72
84
|
},
|
|
85
|
+
"@symbiote-native/solid": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@symbiote-native/svelte": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
73
91
|
"@symbiote-native/vue": {
|
|
74
92
|
"optional": true
|
|
75
93
|
},
|
|
@@ -82,24 +100,34 @@
|
|
|
82
100
|
"react": {
|
|
83
101
|
"optional": true
|
|
84
102
|
},
|
|
103
|
+
"solid-js": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"svelte": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
85
109
|
"vue": {
|
|
86
110
|
"optional": true
|
|
87
111
|
}
|
|
88
112
|
},
|
|
89
113
|
"devDependencies": {
|
|
90
|
-
"@angular/compiler": "
|
|
91
|
-
"@angular/compiler-cli": "
|
|
92
|
-
"@angular/core": "
|
|
114
|
+
"@angular/compiler": "~22.0.8",
|
|
115
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
116
|
+
"@angular/core": "~22.0.8",
|
|
93
117
|
"@types/node": "^26.0.0",
|
|
94
118
|
"@types/react": "^19.2.0",
|
|
95
119
|
"@vue/runtime-core": "^3.5.13",
|
|
96
120
|
"react": "19.2.3",
|
|
121
|
+
"solid-js": "^1.9.14",
|
|
122
|
+
"svelte": "^5.56.0",
|
|
97
123
|
"typescript": "~6.0.0",
|
|
98
|
-
"@symbiote-native/angular": "0.
|
|
99
|
-
"@symbiote-native/engine": "0.
|
|
100
|
-
"@symbiote-native/react": "0.
|
|
101
|
-
"@symbiote-native/
|
|
102
|
-
"@symbiote-native/
|
|
124
|
+
"@symbiote-native/angular": "0.8.0",
|
|
125
|
+
"@symbiote-native/engine": "0.3.0",
|
|
126
|
+
"@symbiote-native/react": "0.4.0",
|
|
127
|
+
"@symbiote-native/solid": "0.1.0",
|
|
128
|
+
"@symbiote-native/svelte": "0.3.0",
|
|
129
|
+
"@symbiote-native/test-utils": "0.2.0",
|
|
130
|
+
"@symbiote-native/vue": "0.5.0"
|
|
103
131
|
},
|
|
104
132
|
"scripts": {
|
|
105
133
|
"typecheck": "tsc --build",
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
effect,
|
|
3
|
+
inject,
|
|
4
|
+
Injectable,
|
|
5
|
+
Injector,
|
|
6
|
+
signal,
|
|
7
|
+
type Signal,
|
|
8
|
+
} from '@angular/core';
|
|
2
9
|
import { addClipboardListener, type IClipboardEvent } from '../../../core';
|
|
3
10
|
|
|
4
11
|
// Angular twin of React's `useClipboard` hook and Vue's `useClipboard` composable. Angular has
|
package/src/core/clipboard.ts
CHANGED
|
@@ -24,7 +24,9 @@ const NATIVE_MODULE_NAME = 'Clipboard';
|
|
|
24
24
|
* @returns A promise that resolves to the content of the clipboard, or an empty string if the
|
|
25
25
|
* clipboard is empty or permission was denied.
|
|
26
26
|
*/
|
|
27
|
-
export async function getStringAsync(
|
|
27
|
+
export async function getStringAsync(
|
|
28
|
+
options: IGetStringOptions = {},
|
|
29
|
+
): Promise<string> {
|
|
28
30
|
if (!expoClipboard.getStringAsync) {
|
|
29
31
|
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getStringAsync');
|
|
30
32
|
}
|
|
@@ -52,7 +54,12 @@ export async function setStringAsync(
|
|
|
52
54
|
* Returns whether the clipboard has text content. Returns `true` for both plain text and rich
|
|
53
55
|
* text (e.g. HTML).
|
|
54
56
|
*/
|
|
55
|
-
|
|
57
|
+
// DELIBERATE DIVERGENCE from expo: Clipboard.ts:57 declares this one plain `function`, not
|
|
58
|
+
// `async` like its 8 siblings, so on a build without the native method the UnavailabilityError
|
|
59
|
+
// escapes synchronously at the call site and `hasStringAsync().catch(handler)` never catches it.
|
|
60
|
+
// Parity would hand callers a guard that fires differently from every other method in this API,
|
|
61
|
+
// so we add `async` here instead of tagging it. Everything else stays a verbatim port.
|
|
62
|
+
export async function hasStringAsync(): Promise<boolean> {
|
|
56
63
|
if (!expoClipboard.hasStringAsync) {
|
|
57
64
|
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasStringAsync');
|
|
58
65
|
}
|
|
@@ -110,7 +117,9 @@ export async function hasUrlAsync(): Promise<boolean> {
|
|
|
110
117
|
* containing the base64 string and metadata of the image. Otherwise resolves to `null` (this
|
|
111
118
|
* includes cases where permission was denied).
|
|
112
119
|
*/
|
|
113
|
-
export async function getImageAsync(
|
|
120
|
+
export async function getImageAsync(
|
|
121
|
+
options: IGetImageOptions,
|
|
122
|
+
): Promise<IClipboardImage | null> {
|
|
114
123
|
if (!expoClipboard.getImageAsync) {
|
|
115
124
|
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getImageAsync');
|
|
116
125
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/solid: the Solid 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 — createClipboard wires addClipboardListener onto Solid's own ownership
|
|
4
|
+
// tree (primitives/create-clipboard.ts). The lifecycle bucket is `primitives/` and the name is
|
|
5
|
+
// `create*` (never `hooks/`/`use*`, that is React's term and Solid's word for consuming an
|
|
6
|
+
// existing thing) — see adapters/solid/src/primitives for the same convention.
|
|
7
|
+
|
|
8
|
+
export { createClipboard } from './primitives/create-clipboard';
|
|
9
|
+
export * from '../core';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Solid lifecycle wiring over the framework-agnostic addClipboardListener subscription (core/) —
|
|
2
|
+
// the Solid twin of React's useClipboard hook, Vue's composable, Svelte's rune and Angular's
|
|
3
|
+
// ClipboardService.connect(), adjusted for clipboard's single always-on subscription (no per-call
|
|
4
|
+
// config to resubscribe on).
|
|
5
|
+
//
|
|
6
|
+
// `primitives/` and `create*`, not `hooks/`/`use*`: Solid's ecosystem calls a composable reactive
|
|
7
|
+
// function a PRIMITIVE and reserves `use*` for consuming something that already exists. Full
|
|
8
|
+
// rationale in adapters/solid/src/primitives/create-color-scheme.ts's header.
|
|
9
|
+
//
|
|
10
|
+
// Returns an ACCESSOR, never a snapshot: a Solid component body runs ONCE, so a plain returned
|
|
11
|
+
// value would pin the consumer to `null` forever.
|
|
12
|
+
//
|
|
13
|
+
// Subscribes SYNCHRONOUSLY in the body with `onCleanup` for teardown, not from a mount hook
|
|
14
|
+
// (React's useEffect, Vue's onMounted, Svelte's $effect) — this closes the gap those three have
|
|
15
|
+
// between construction and subscription, in which a clipboard change fires and is never seen.
|
|
16
|
+
//
|
|
17
|
+
// Outside a component / createRoot there is no owner for `onCleanup`, so the subscription lives
|
|
18
|
+
// for the process — see create-color-scheme's header for the same documented caveat.
|
|
19
|
+
import { createSignal, onCleanup, type Accessor } from 'solid-js';
|
|
20
|
+
import {
|
|
21
|
+
addClipboardListener,
|
|
22
|
+
type EventSubscription,
|
|
23
|
+
type IClipboardEvent,
|
|
24
|
+
} from '../../core';
|
|
25
|
+
|
|
26
|
+
export function createClipboard(): Accessor<IClipboardEvent | null> {
|
|
27
|
+
const [event, setEvent] = createSignal<IClipboardEvent | null>(null);
|
|
28
|
+
|
|
29
|
+
const subscription: EventSubscription = addClipboardListener(next => {
|
|
30
|
+
setEvent(next);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
onCleanup(() => {
|
|
34
|
+
subscription.remove();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return event;
|
|
38
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @symbiote-native/clipboard/svelte: the Svelte 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 a Svelte
|
|
4
|
+
// `$effect` (runes/use-clipboard.svelte.ts), mirroring the lifecycle-bucket naming convention of
|
|
5
|
+
// adapters/svelte/src/runes (never `hooks/`/`composables/`, those are React's and Vue's terms).
|
|
6
|
+
|
|
7
|
+
export { useClipboard } from './runes/use-clipboard.svelte';
|
|
8
|
+
export * from '../core';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Svelte lifecycle wiring over the framework-agnostic addClipboardListener subscription (core/) —
|
|
2
|
+
// the Svelte twin of vue/composables/use-clipboard and react/hooks/use-clipboard, adjusted for
|
|
3
|
+
// clipboard's single always-on subscription (no per-call config to resubscribe on).
|
|
4
|
+
//
|
|
5
|
+
// `.svelte.ts` (not `.ts`): runes ($state/$effect) are only usable in a file with this extension
|
|
6
|
+
// outside an actual `.svelte` component. `runes/` is Svelte's own term for the bucket React calls
|
|
7
|
+
// `hooks/` and Vue calls `composables/` — see adapters/svelte/src/runes.
|
|
8
|
+
//
|
|
9
|
+
// Vue's onMounted/onUnmounted pair collapses into ONE `$effect` whose returned function is the
|
|
10
|
+
// teardown. The effect only WRITES `event`, never reads it, so its dependency set stays empty: it
|
|
11
|
+
// subscribes exactly once on mount and removes the subscription exactly once on unmount.
|
|
12
|
+
//
|
|
13
|
+
// Returns a boxed getter object, NOT a bare `$state`: Svelte 5 reactivity is lexically scoped to
|
|
14
|
+
// the declaring module, so a raw `let x = $state(...)` handed out of a plain function arrives
|
|
15
|
+
// dead at the caller. `.current` is the Svelte equivalent of unwrapping Vue's `Ref` via `.value`.
|
|
16
|
+
import {
|
|
17
|
+
addClipboardListener,
|
|
18
|
+
type EventSubscription,
|
|
19
|
+
type IClipboardEvent,
|
|
20
|
+
} from '../../core';
|
|
21
|
+
|
|
22
|
+
export function useClipboard(): { readonly current: IClipboardEvent | null } {
|
|
23
|
+
let event = $state<IClipboardEvent | null>(null);
|
|
24
|
+
|
|
25
|
+
$effect(() => {
|
|
26
|
+
const subscription: EventSubscription = addClipboardListener(next => {
|
|
27
|
+
event = next;
|
|
28
|
+
});
|
|
29
|
+
return () => subscription.remove();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
get current(): IClipboardEvent | null {
|
|
34
|
+
return event;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Pulls in svelte's ambient global declarations ($state/$effect/...) for this package's tsc
|
|
2
|
+
// program. adapters/svelte gets these for free because render.ts does a real value import from
|
|
3
|
+
// 'svelte' (any file in a program importing a module loads its ambient globals for the whole
|
|
4
|
+
// program); this package has no such import, so a triple-slash reference does the same job
|
|
5
|
+
// explicitly rather than relying on an incidental side-effect.
|
|
6
|
+
/// <reference types="svelte" />
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
// for clipboard's single always-on subscription (no per-call config to resubscribe on).
|
|
5
5
|
|
|
6
6
|
import { onMounted, onUnmounted, ref, type Ref } from '@vue/runtime-core';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
addClipboardListener,
|
|
9
|
+
type EventSubscription,
|
|
10
|
+
type IClipboardEvent,
|
|
11
|
+
} from '../../../core';
|
|
8
12
|
|
|
9
13
|
export function useClipboard(): Ref<IClipboardEvent | null> {
|
|
10
14
|
// A plain ref: the value is a POJO clipboard event, not an engine node, so no shallowRef needed.
|