@symbiote-native/clipboard 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +180 -26
  2. package/build/angular/index.d.ts +2 -0
  3. package/build/angular/index.js +7 -0
  4. package/build/angular/services/clipboard.service/index.d.ts +6 -0
  5. package/build/angular/services/clipboard.service/index.js +77 -0
  6. package/build/core/clipboard.d.ts +86 -0
  7. package/build/core/clipboard.js +142 -0
  8. package/build/core/index.d.ts +3 -0
  9. package/build/core/index.js +2 -0
  10. package/build/core/native-module.d.ts +17 -0
  11. package/build/core/native-module.js +7 -0
  12. package/build/core/types.d.ts +67 -0
  13. package/build/core/types.js +28 -0
  14. package/build/react/hooks/use-clipboard/index.d.ts +2 -0
  15. package/build/react/hooks/use-clipboard/index.js +14 -0
  16. package/build/react/index.d.ts +2 -0
  17. package/build/react/index.js +8 -0
  18. package/build/svelte/index.d.ts +2 -0
  19. package/build/svelte/index.js +7 -0
  20. package/build/svelte/runes/use-clipboard.svelte.d.ts +4 -0
  21. package/build/svelte/runes/use-clipboard.svelte.js +30 -0
  22. package/build/vue/composables/use-clipboard/index.d.ts +3 -0
  23. package/build/vue/composables/use-clipboard/index.js +20 -0
  24. package/build/vue/index.d.ts +2 -0
  25. package/build/vue/index.js +8 -0
  26. package/build-ngc/angular/index.d.ts +2 -0
  27. package/build-ngc/angular/index.js +8 -0
  28. package/build-ngc/angular/index.js.map +1 -0
  29. package/build-ngc/angular/services/clipboard.service/index.d.ts +9 -0
  30. package/build-ngc/angular/services/clipboard.service/index.js +36 -0
  31. package/build-ngc/angular/services/clipboard.service/index.js.map +1 -0
  32. package/build-ngc/core/clipboard.d.ts +86 -0
  33. package/build-ngc/core/clipboard.js +143 -0
  34. package/build-ngc/core/clipboard.js.map +1 -0
  35. package/build-ngc/core/index.d.ts +3 -0
  36. package/build-ngc/core/index.js +3 -0
  37. package/build-ngc/core/index.js.map +1 -0
  38. package/build-ngc/core/native-module.d.ts +17 -0
  39. package/build-ngc/core/native-module.js +8 -0
  40. package/build-ngc/core/native-module.js.map +1 -0
  41. package/build-ngc/core/types.d.ts +67 -0
  42. package/build-ngc/core/types.js +29 -0
  43. package/build-ngc/core/types.js.map +1 -0
  44. package/native-link.json +12 -0
  45. package/package.json +106 -1
  46. package/src/angular/index.ts +8 -0
  47. package/src/angular/services/clipboard.service/index.ts +35 -0
  48. package/src/core/clipboard.ts +168 -0
  49. package/src/core/index.ts +23 -0
  50. package/src/core/native-module.ts +42 -0
  51. package/src/core/types.ts +81 -0
  52. package/src/react/hooks/use-clipboard/index.ts +17 -0
  53. package/src/react/index.ts +9 -0
  54. package/src/svelte/index.ts +8 -0
  55. package/src/svelte/runes/use-clipboard.svelte.ts +33 -0
  56. package/src/svelte/svelte-ambient.d.ts +6 -0
  57. package/src/vue/composables/use-clipboard/index.ts +25 -0
  58. 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.1",
3
+ "version": "0.2.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,112 @@
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
+ "./svelte": {
34
+ "types": "./build/svelte/index.d.ts",
35
+ "default": "./build/svelte/index.js"
36
+ },
37
+ "./angular": {
38
+ "types": "./build-ngc/angular/index.d.ts",
39
+ "react-native": "./build-ngc/angular/index.js",
40
+ "default": "./src/angular/index.ts"
41
+ }
42
+ },
43
+ "files": [
44
+ "src",
45
+ "build",
46
+ "build-ngc",
47
+ "native-link.json",
48
+ "!src/**/*.test.*",
49
+ "!src/**/*.spec.*",
50
+ "!src/**/*.detox.*"
51
+ ],
16
52
  "publishConfig": {
17
53
  "access": "public"
54
+ },
55
+ "dependencies": {
56
+ "expo-clipboard": "57.0.1",
57
+ "expo-modules-core": "57.0.5"
58
+ },
59
+ "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
+ "@angular/core": ">=20",
66
+ "@vue/runtime-core": "^3.5.13",
67
+ "react": ">=19.0.0",
68
+ "react-native": ">=0.86",
69
+ "svelte": ">=5.56.0",
70
+ "vue": ">=3.5.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "@symbiote-native/angular": {
74
+ "optional": true
75
+ },
76
+ "@symbiote-native/react": {
77
+ "optional": true
78
+ },
79
+ "@symbiote-native/svelte": {
80
+ "optional": true
81
+ },
82
+ "@symbiote-native/vue": {
83
+ "optional": true
84
+ },
85
+ "@angular/core": {
86
+ "optional": true
87
+ },
88
+ "@vue/runtime-core": {
89
+ "optional": true
90
+ },
91
+ "react": {
92
+ "optional": true
93
+ },
94
+ "svelte": {
95
+ "optional": true
96
+ },
97
+ "vue": {
98
+ "optional": true
99
+ }
100
+ },
101
+ "devDependencies": {
102
+ "@angular/compiler": "~22.0.8",
103
+ "@angular/compiler-cli": "~22.0.8",
104
+ "@angular/core": "~22.0.8",
105
+ "@types/node": "^26.0.0",
106
+ "@types/react": "^19.2.0",
107
+ "@vue/runtime-core": "^3.5.13",
108
+ "react": "19.2.3",
109
+ "svelte": "^5.56.0",
110
+ "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"
117
+ },
118
+ "scripts": {
119
+ "typecheck": "tsc --build",
120
+ "clean": "rm -rf build-ngc",
121
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
122
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
18
123
  }
19
124
  }
@@ -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,168 @@
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
+ // DELIBERATE DIVERGENCE from expo: Clipboard.ts:57 declares this one plain `function`, not
56
+ // `async` like its 8 siblings, so on a build without the native method the UnavailabilityError
57
+ // escapes synchronously at the call site and `hasStringAsync().catch(handler)` never catches it.
58
+ // Parity would hand callers a guard that fires differently from every other method in this API,
59
+ // so we add `async` here instead of tagging it. Everything else stays a verbatim port.
60
+ export async function hasStringAsync(): Promise<boolean> {
61
+ if (!expoClipboard.hasStringAsync) {
62
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasStringAsync');
63
+ }
64
+ return expoClipboard.hasStringAsync();
65
+ }
66
+
67
+ /**
68
+ * Gets the URL from the user's clipboard.
69
+ *
70
+ * Note: On iOS 16+, if the user denies paste permission, this method returns `null`. Due to iOS
71
+ * platform limitations, there is no way to distinguish between no URL in clipboard and denied
72
+ * permission.
73
+ * @platform ios
74
+ */
75
+ export async function getUrlAsync(): Promise<string | null> {
76
+ if (!expoClipboard.getUrlAsync) {
77
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getUrlAsync');
78
+ }
79
+ return expoClipboard.getUrlAsync();
80
+ }
81
+
82
+ /**
83
+ * Sets a URL in the user's clipboard. Behaves the same as `setStringAsync`, except that it sets
84
+ * the clipboard content type to be a URL, letting your app or other apps know that the clipboard
85
+ * contains a URL and behave accordingly.
86
+ * @platform ios
87
+ */
88
+ export async function setUrlAsync(url: string): Promise<void> {
89
+ if (!expoClipboard.setUrlAsync) {
90
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setUrlAsync');
91
+ }
92
+ return expoClipboard.setUrlAsync(url);
93
+ }
94
+
95
+ /**
96
+ * Returns whether the clipboard has URL content.
97
+ * @platform ios
98
+ */
99
+ export async function hasUrlAsync(): Promise<boolean> {
100
+ if (!expoClipboard.hasUrlAsync) {
101
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasUrlAsync');
102
+ }
103
+ return expoClipboard.hasUrlAsync();
104
+ }
105
+
106
+ /**
107
+ * Gets the image from the user's clipboard and returns it in the specified format.
108
+ *
109
+ * Note: On iOS 16+, if the user denies paste permission, this method returns `null`. Due to iOS
110
+ * platform limitations, there is no way to distinguish between no image in clipboard and denied
111
+ * permission.
112
+ *
113
+ * @param options Specifies the desired format of the image.
114
+ * @returns If there was an image in the clipboard, resolves to an `IClipboardImage` object
115
+ * containing the base64 string and metadata of the image. Otherwise resolves to `null` (this
116
+ * includes cases where permission was denied).
117
+ */
118
+ export async function getImageAsync(options: IGetImageOptions): Promise<IClipboardImage | null> {
119
+ if (!expoClipboard.getImageAsync) {
120
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getImageAsync');
121
+ }
122
+ return expoClipboard.getImageAsync(options);
123
+ }
124
+
125
+ /**
126
+ * Sets an image in the user's clipboard.
127
+ *
128
+ * @param base64Image Image encoded as a base64 string, without MIME type.
129
+ */
130
+ export async function setImageAsync(base64Image: string): Promise<void> {
131
+ if (!expoClipboard.setImageAsync) {
132
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setImageAsync');
133
+ }
134
+ return expoClipboard.setImageAsync(base64Image);
135
+ }
136
+
137
+ /**
138
+ * Returns whether the clipboard has image content.
139
+ */
140
+ export async function hasImageAsync(): Promise<boolean> {
141
+ if (!expoClipboard.hasImageAsync) {
142
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'hasImageAsync');
143
+ }
144
+ return expoClipboard.hasImageAsync();
145
+ }
146
+
147
+ /**
148
+ * Adds a listener that fires whenever the content of the user's clipboard changes. Kept here at
149
+ * the core level, framework-agnostic, exactly like `Accelerometer.addListener` lives in
150
+ * `@symbiote-native/sensors`' core — each adapter's `useClipboard` hook/composable/service wraps
151
+ * this in its own mount/unmount lifecycle rather than reimplementing the subscription.
152
+ *
153
+ * @param listener Callback invoked with an `IClipboardEvent` describing the new clipboard
154
+ * content types whenever the clipboard changes.
155
+ */
156
+ export function addClipboardListener(
157
+ listener: (event: IClipboardEvent) => void,
158
+ ): EventSubscription {
159
+ return expoClipboard.addListener(CLIPBOARD_CHANGED_EVENT_NAME, listener);
160
+ }
161
+
162
+ /**
163
+ * Removes the listener added by `addClipboardListener`.
164
+ * @deprecated use `subscription.remove()` instead.
165
+ */
166
+ export function removeClipboardListener(subscription: EventSubscription): void {
167
+ subscription.remove();
168
+ }
@@ -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,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,33 @@
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, type EventSubscription, type IClipboardEvent } from '../../core';
17
+
18
+ export function useClipboard(): { readonly current: IClipboardEvent | null } {
19
+ let event = $state<IClipboardEvent | null>(null);
20
+
21
+ $effect(() => {
22
+ const subscription: EventSubscription = addClipboardListener(next => {
23
+ event = next;
24
+ });
25
+ return () => subscription.remove();
26
+ });
27
+
28
+ return {
29
+ get current(): IClipboardEvent | null {
30
+ return event;
31
+ },
32
+ };
33
+ }
@@ -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" />
@@ -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
+ }
@@ -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';