@symbiote-native/clipboard 0.2.0 → 1.0.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 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, React,
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 | `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) |
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/`ClipboardService.connect()` is a thin mount/unmount (or DI-scoped
58
- `effect()`, for Angular) wrapper around that same subscription — the plumbing is written once and
59
- shared by all three.
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 { getStringAsync, setStringAsync, addClipboardListener } from '@symbiote-native/clipboard';
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 *types*
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) => setStringAsync(input).then(() => getStringAsync().then(setText));
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 => { text.value = 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 => { if (event) refresh(); });
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);
@@ -113,12 +126,70 @@ function handleCopy(input: string): void {
113
126
  </template>
114
127
  ```
115
128
 
129
+ ```svelte
130
+ <!-- Svelte -->
131
+ <script lang="ts">
132
+ import { getStringAsync, setStringAsync } from '@symbiote-native/clipboard';
133
+ import { useClipboard } from '@symbiote-native/clipboard/svelte';
134
+
135
+ let text = $state('checking…');
136
+ function refresh(): void {
137
+ void getStringAsync().then(value => (text = value));
138
+ }
139
+
140
+ const clipboard = useClipboard(); // { current: IClipboardEvent | null }
141
+
142
+ $effect(() => {
143
+ refresh();
144
+ });
145
+ $effect(() => {
146
+ if (clipboard.current !== null) refresh();
147
+ });
148
+
149
+ function handleCopy(input: string): void {
150
+ void setStringAsync(input).then(refresh);
151
+ }
152
+ </script>
153
+
154
+ <Text>{text}</Text>
155
+ ```
156
+
157
+ ```tsx
158
+ // Solid
159
+ import { createEffect, createSignal } from 'solid-js';
160
+ import { getStringAsync, setStringAsync } from '@symbiote-native/clipboard';
161
+ import { createClipboard } from '@symbiote-native/clipboard/solid';
162
+
163
+ function ClipboardScreen() {
164
+ const clipboardEvent = createClipboard(); // Accessor<IClipboardEvent | null>
165
+ const [text, setText] = createSignal('checking…');
166
+
167
+ function refresh(): void {
168
+ getStringAsync().then(setText);
169
+ }
170
+ refresh();
171
+
172
+ createEffect(() => {
173
+ if (clipboardEvent() !== null) refresh();
174
+ });
175
+
176
+ const handleCopy = (input: string) =>
177
+ setStringAsync(input).then(refresh);
178
+
179
+ return <Text>{text()}</Text>;
180
+ }
181
+ ```
182
+
116
183
  ```ts
117
184
  // Angular
118
185
  import { Component, Injector, effect, inject, signal } from '@angular/core';
119
- import { ClipboardService, getStringAsync, setStringAsync } from '@symbiote-native/clipboard/angular';
186
+ import {
187
+ ClipboardService,
188
+ getStringAsync,
189
+ setStringAsync,
190
+ } from '@symbiote-native/clipboard/angular';
120
191
 
121
- @Component({ /* ... */ })
192
+ @Component({/* ... */})
122
193
  export class ClipboardScreen {
123
194
  private readonly injector = inject(Injector);
124
195
  private readonly clipboardEvent = inject(ClipboardService).connect(); // Signal<IClipboardEvent | null>
@@ -126,7 +197,12 @@ export class ClipboardScreen {
126
197
 
127
198
  constructor() {
128
199
  this.refresh();
129
- effect(() => { if (this.clipboardEvent() !== null) this.refresh(); }, { injector: this.injector });
200
+ effect(
201
+ () => {
202
+ if (this.clipboardEvent() !== null) this.refresh();
203
+ },
204
+ { injector: this.injector },
205
+ );
130
206
  }
131
207
 
132
208
  handleCopy(input: string): void {
@@ -141,6 +217,7 @@ export class ClipboardScreen {
141
217
 
142
218
  These are trimmed from the real demo screens — `examples/expo-react/screens/ClipboardScreen.tsx`,
143
219
  `examples/expo-vue-sfc/screens/ClipboardScreen.vue`, `examples/expo-vue-tsx/screens/ClipboardScreen.tsx`,
220
+ `examples/expo-svelte/screens/ClipboardScreen.svelte`, `examples/expo-solid/screens/ClipboardScreen.tsx`,
144
221
  `examples/expo-angular/src/screens/ClipboardScreen.ts` — which additionally show `hasStringAsync()`
145
222
  status badges and, iOS-only, the `getUrlAsync`/`setUrlAsync`/`hasUrlAsync` URL surface.
146
223
 
@@ -175,6 +252,14 @@ const clipboardEvent = useClipboard(); // IClipboardEvent | null
175
252
  import { useClipboard } from '@symbiote-native/clipboard/vue';
176
253
  const clipboardEvent = useClipboard(); // Ref<IClipboardEvent | null>
177
254
 
255
+ // Svelte
256
+ import { useClipboard } from '@symbiote-native/clipboard/svelte';
257
+ const clipboard = useClipboard(); // clipboard.current: IClipboardEvent | null
258
+
259
+ // Solid
260
+ import { createClipboard } from '@symbiote-native/clipboard/solid';
261
+ const clipboardEvent = createClipboard(); // Accessor<IClipboardEvent | null>
262
+
178
263
  // Angular
179
264
  import { ClipboardService } from '@symbiote-native/clipboard/angular';
180
265
  readonly clipboardEvent = inject(ClipboardService).connect(); // Signal<IClipboardEvent | null>
@@ -188,11 +273,12 @@ ported — out of scope for this pass. If it's ever wrapped, it follows
188
273
 
189
274
  Tests exercise the JS layer only, against a fake native module in place of the real
190
275
  `requireNativeModule` resolution (`src/core/**/*.test.ts`,
191
- `src/{react,vue,angular}/**/*.test.{ts,tsx}`) — no Fabric/Descriptor angle at all, since clipboard
276
+ `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`) — no Fabric/Descriptor angle at all,
277
+ since clipboard
192
278
  is a pure async-function + one-listener surface, never a view. Native rendering itself is verified
193
279
  on-device (see the parent [README](../../README.md) for the project's testing model).
194
280
 
195
281
  Native autolinking wiring (Android's 3-layer registration, iOS Podfile/pod install) is done across
196
- all four `examples/expo-*` canary apps. It isn't wired into the public non-Expo canaries
197
- (`examples/react`, `examples/vue-sfc`, `examples/vue-tsx`, `examples/angular`) yet — those don't
198
- depend on any `expo-modules-core` package today.
282
+ all six `examples/expo-*` canary apps. It isn't wired into the public non-Expo canaries
283
+ (`examples/react`, `examples/vue-sfc`, `examples/vue-tsx`, `examples/svelte`, `examples/solid`,
284
+ `examples/angular`) yet — those don't depend on any `expo-modules-core` package today.
@@ -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
@@ -0,0 +1,2 @@
1
+ export { createClipboard } from './primitives/create-clipboard';
2
+ export * from '../core';
@@ -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,3 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import { type IClipboardEvent } from '../../core';
3
+ export declare function createClipboard(): Accessor<IClipboardEvent | null>;
@@ -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
+ }
@@ -13,7 +13,7 @@
13
13
  // Returns a boxed getter object, NOT a bare `$state`: Svelte 5 reactivity is lexically scoped to
14
14
  // the declaring module, so a raw `let x = $state(...)` handed out of a plain function arrives
15
15
  // dead at the caller. `.current` is the Svelte equivalent of unwrapping Vue's `Ref` via `.value`.
16
- import { addClipboardListener } from '../../core/index.js';
16
+ import { addClipboardListener, } from '../../core/index.js';
17
17
  export function useClipboard() {
18
18
  let event = $state(null);
19
19
  $effect(() => {
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/clipboard.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAC1F,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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/core/clipboard.ts"],"names":[],"mappings":"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"}
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.2.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": "1.0.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",
@@ -13,6 +13,18 @@
13
13
  "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
14
  },
15
15
  "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "keywords": [
17
+ "react-native",
18
+ "symbiote-native",
19
+ "expo-clipboard",
20
+ "react",
21
+ "vue",
22
+ "svelte",
23
+ "solid",
24
+ "angular",
25
+ "clipboard",
26
+ "copy-paste"
27
+ ],
16
28
  "type": "module",
17
29
  "main": "./build/core/index.js",
18
30
  "module": "./build/core/index.js",
@@ -34,6 +46,10 @@
34
46
  "types": "./build/svelte/index.d.ts",
35
47
  "default": "./build/svelte/index.js"
36
48
  },
49
+ "./solid": {
50
+ "types": "./build/solid/index.d.ts",
51
+ "default": "./build/solid/index.js"
52
+ },
37
53
  "./angular": {
38
54
  "types": "./build-ngc/angular/index.d.ts",
39
55
  "react-native": "./build-ngc/angular/index.js",
@@ -57,17 +73,19 @@
57
73
  "expo-modules-core": "57.0.5"
58
74
  },
59
75
  "peerDependencies": {
60
- "@symbiote-native/angular": ">=0.6.2",
61
- "@symbiote-native/engine": ">=0.1.7",
62
- "@symbiote-native/react": ">=0.2.8",
63
- "@symbiote-native/svelte": ">=0.1.0",
64
- "@symbiote-native/vue": ">=0.3.8",
65
76
  "@angular/core": ">=20",
66
77
  "@vue/runtime-core": "^3.5.13",
67
78
  "react": ">=19.0.0",
68
79
  "react-native": ">=0.86",
80
+ "solid-js": ">=1.9.0",
69
81
  "svelte": ">=5.56.0",
70
- "vue": ">=3.5.0"
82
+ "vue": ">=3.5.0",
83
+ "@symbiote-native/angular": "^1.0.0",
84
+ "@symbiote-native/engine": "^0.4.0",
85
+ "@symbiote-native/react": "^1.0.0",
86
+ "@symbiote-native/solid": "^1.0.0",
87
+ "@symbiote-native/svelte": "^1.0.0",
88
+ "@symbiote-native/vue": "^1.0.0"
71
89
  },
72
90
  "peerDependenciesMeta": {
73
91
  "@symbiote-native/angular": {
@@ -76,6 +94,9 @@
76
94
  "@symbiote-native/react": {
77
95
  "optional": true
78
96
  },
97
+ "@symbiote-native/solid": {
98
+ "optional": true
99
+ },
79
100
  "@symbiote-native/svelte": {
80
101
  "optional": true
81
102
  },
@@ -91,6 +112,9 @@
91
112
  "react": {
92
113
  "optional": true
93
114
  },
115
+ "solid-js": {
116
+ "optional": true
117
+ },
94
118
  "svelte": {
95
119
  "optional": true
96
120
  },
@@ -106,14 +130,16 @@
106
130
  "@types/react": "^19.2.0",
107
131
  "@vue/runtime-core": "^3.5.13",
108
132
  "react": "19.2.3",
133
+ "solid-js": "^1.9.14",
109
134
  "svelte": "^5.56.0",
110
135
  "typescript": "~6.0.0",
111
- "@symbiote-native/angular": "0.7.0",
112
- "@symbiote-native/engine": "0.2.0",
113
- "@symbiote-native/react": "0.3.0",
114
- "@symbiote-native/svelte": "0.2.0",
115
- "@symbiote-native/test-utils": "0.1.6",
116
- "@symbiote-native/vue": "0.4.0"
136
+ "@symbiote-native/angular": "1.0.0",
137
+ "@symbiote-native/engine": "0.4.0",
138
+ "@symbiote-native/react": "1.0.0",
139
+ "@symbiote-native/solid": "1.0.0",
140
+ "@symbiote-native/svelte": "1.0.0",
141
+ "@symbiote-native/test-utils": "0.3.0",
142
+ "@symbiote-native/vue": "1.0.0"
117
143
  },
118
144
  "scripts": {
119
145
  "typecheck": "tsc --build",
@@ -1,4 +1,11 @@
1
- import { effect, inject, Injectable, Injector, signal, type Signal } from '@angular/core';
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
@@ -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(options: IGetStringOptions = {}): Promise<string> {
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
  }
@@ -115,7 +117,9 @@ export async function hasUrlAsync(): Promise<boolean> {
115
117
  * containing the base64 string and metadata of the image. Otherwise resolves to `null` (this
116
118
  * includes cases where permission was denied).
117
119
  */
118
- export async function getImageAsync(options: IGetImageOptions): Promise<IClipboardImage | null> {
120
+ export async function getImageAsync(
121
+ options: IGetImageOptions,
122
+ ): Promise<IClipboardImage | null> {
119
123
  if (!expoClipboard.getImageAsync) {
120
124
  throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getImageAsync');
121
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
+ }
@@ -13,7 +13,11 @@
13
13
  // Returns a boxed getter object, NOT a bare `$state`: Svelte 5 reactivity is lexically scoped to
14
14
  // the declaring module, so a raw `let x = $state(...)` handed out of a plain function arrives
15
15
  // dead at the caller. `.current` is the Svelte equivalent of unwrapping Vue's `Ref` via `.value`.
16
- import { addClipboardListener, type EventSubscription, type IClipboardEvent } from '../../core';
16
+ import {
17
+ addClipboardListener,
18
+ type EventSubscription,
19
+ type IClipboardEvent,
20
+ } from '../../core';
17
21
 
18
22
  export function useClipboard(): { readonly current: IClipboardEvent | null } {
19
23
  let event = $state<IClipboardEvent | null>(null);
@@ -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 { addClipboardListener, type EventSubscription, type IClipboardEvent } from '../../../core';
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.