@symbiote-native/clipboard 3.0.2 → 3.0.3

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 (61) hide show
  1. package/README.md +62 -28
  2. package/build/angular/clipboard-paste-button/index.d.ts +15 -0
  3. package/build/angular/clipboard-paste-button/index.js +119 -0
  4. package/build/core/clipboard-paste-button.d.ts +29 -0
  5. package/build/core/clipboard-paste-button.js +50 -0
  6. package/build/core/clipboard.d.ts +2 -0
  7. package/build/core/clipboard.js +6 -1
  8. package/build/core/index.d.ts +3 -2
  9. package/build/core/index.js +2 -1
  10. package/build/core/native-module.d.ts +1 -0
  11. package/build/core/types.d.ts +11 -0
  12. package/build/core/types.js +1 -4
  13. package/build/react/clipboard-paste-button.d.ts +4 -0
  14. package/build/react/clipboard-paste-button.js +7 -0
  15. package/build/react/index.d.ts +1 -0
  16. package/build/react/index.js +1 -0
  17. package/build/solid/clipboard-paste-button.d.ts +3 -0
  18. package/build/solid/clipboard-paste-button.js +10 -0
  19. package/build/solid/index.d.ts +1 -0
  20. package/build/solid/index.js +1 -0
  21. package/build/svelte/clipboard-paste-button.svelte +17 -0
  22. package/build/svelte/clipboard-paste-button.svelte.d.ts +4 -0
  23. package/build/svelte/index.d.ts +1 -0
  24. package/build/svelte/index.js +1 -0
  25. package/build/vue/clipboard-paste-button.d.ts +3 -0
  26. package/build/vue/clipboard-paste-button.js +15 -0
  27. package/build/vue/index.d.ts +1 -0
  28. package/build/vue/index.js +1 -0
  29. package/build-ngc/angular/clipboard-paste-button/index.d.ts +18 -0
  30. package/build-ngc/angular/clipboard-paste-button/index.js +58 -0
  31. package/build-ngc/angular/clipboard-paste-button/index.js.map +1 -0
  32. package/build-ngc/core/clipboard-paste-button.d.ts +29 -0
  33. package/build-ngc/core/clipboard-paste-button.js +51 -0
  34. package/build-ngc/core/clipboard-paste-button.js.map +1 -0
  35. package/build-ngc/core/clipboard.d.ts +2 -0
  36. package/build-ngc/core/clipboard.js +6 -1
  37. package/build-ngc/core/clipboard.js.map +1 -1
  38. package/build-ngc/core/index.d.ts +3 -2
  39. package/build-ngc/core/index.js +2 -1
  40. package/build-ngc/core/index.js.map +1 -1
  41. package/build-ngc/core/native-module.d.ts +1 -0
  42. package/build-ngc/core/native-module.js.map +1 -1
  43. package/build-ngc/core/types.d.ts +11 -0
  44. package/build-ngc/core/types.js +1 -4
  45. package/build-ngc/core/types.js.map +1 -1
  46. package/package.json +17 -19
  47. package/src/angular/clipboard-paste-button/index.ts +40 -0
  48. package/src/core/clipboard-paste-button.ts +113 -0
  49. package/src/core/clipboard.ts +7 -1
  50. package/src/core/index.ts +14 -0
  51. package/src/core/native-module.ts +2 -0
  52. package/src/core/types.ts +19 -4
  53. package/src/react/clipboard-paste-button.ts +12 -0
  54. package/src/react/index.ts +1 -0
  55. package/src/solid/clipboard-paste-button.ts +15 -0
  56. package/src/solid/index.ts +1 -0
  57. package/src/svelte/clipboard-paste-button.svelte +17 -0
  58. package/src/svelte/index.ts +1 -0
  59. package/src/svelte/svelte-compile.test-helper.ts +6 -0
  60. package/src/vue/clipboard-paste-button.ts +16 -0
  61. package/src/vue/index.ts +1 -0
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @symbiote-native/clipboard
2
2
 
3
3
  A wrapper package for [SymbioteNative](../../README.md) that makes
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,
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
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
- `@symbiote-native/local-auth`'s shape — mostly stateless async functions — plus **one**
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
10
10
  mount/unmount lifecycle (`useClipboard`).
11
11
 
@@ -23,38 +23,38 @@ npx @symbiote-native/cli new my-app --clipboard
23
23
  npx @symbiote-native/cli add --clipboard
24
24
  ```
25
25
 
26
- Either way: installs `@symbiote-native/clipboard` and wires the native autolinking automatically —
26
+ Either way: installs `@symbiote-native/clipboard` and wires the native autolinking automatically,
27
27
  see [`@symbiote-native/cli`](../cli).
28
28
 
29
29
  <details>
30
- <summary>Manual install (no CLI — installing and wiring native autolinking by hand)</summary>
30
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
31
31
 
32
32
  ```bash
33
33
  npm install @symbiote-native/clipboard
34
34
  ```
35
35
 
36
36
  Depends on `expo-clipboard` and `expo-modules-core` directly (regular dependencies, pinned to an
37
- exact version — never a caret range, since this package's `core/` is hand-ported against one
37
+ exact version - never a caret range, since this package's `core/` is hand-ported against one
38
38
  specific native API shape and a newer resolve could silently drift the two apart). Never install
39
- `expo-clipboard` yourself, and never add the `expo` meta-package to this project — it bundles its
39
+ `expo-clipboard` yourself, and never add the `expo` meta-package to this project - it bundles its
40
40
  own Metro/Babel pipeline that conflicts with this project's own.
41
41
 
42
42
  ### Required one-time step: native autolinking wiring
43
43
 
44
44
  Unlike a plain RN native module, `expo-clipboard`'s native code is discovered by
45
- `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
45
+ `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism - this needs wiring
46
46
  into the native host app **once**, covering this package and every other `expo-modules-core`
47
47
  package (`@symbiote-native/sensors`, `@symbiote-native/local-auth`) with zero further changes:
48
48
 
49
49
  | Platform | Touches |
50
50
  | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
51
- | iOS | `ios/Podfile` — add `use_expo_modules!` |
52
- | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
53
- | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
54
- | 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) |
51
+ | iOS | `ios/Podfile` - add `use_expo_modules!` |
52
+ | iOS | `AppDelegate.swift` - Expo's runtime-bootstrap hook |
53
+ | Android | `settings.gradle` / `app/build.gradle` - resolve and include the Expo Gradle projects |
54
+ | 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) |
55
55
 
56
56
  Full mechanics live in the `symbiote-expo-native-module` skill. Clipboard itself needs no
57
- `Info.plist`/`AndroidManifest.xml` permission entry on either platform — reading/writing the
57
+ `Info.plist`/`AndroidManifest.xml` permission entry on either platform - reading/writing the
58
58
  clipboard requires no platform permission string.
59
59
 
60
60
  </details>
@@ -67,19 +67,19 @@ src/core/ getStringAsync/setStringAsync/hasStringAsync, getUrlAsyn
67
67
  addClipboardListener/removeClipboardListener. native-module.ts resolves
68
68
  the ExpoClipboard native module via expo-modules-core's
69
69
  requireNativeModule.
70
- src/react/hooks/ @symbiote-native/clipboard/react — useClipboard
71
- src/vue/composables/ @symbiote-native/clipboard/vue — useClipboard (same name)
72
- src/svelte/runes/ @symbiote-native/clipboard/svelte — useClipboard (same name)
73
- src/solid/primitives/ @symbiote-native/clipboard/solid — createClipboard (Solid says
70
+ src/react/hooks/ @symbiote-native/clipboard/react - useClipboard
71
+ src/vue/composables/ @symbiote-native/clipboard/vue - useClipboard (same name)
72
+ src/svelte/runes/ @symbiote-native/clipboard/svelte - useClipboard (same name)
73
+ src/solid/primitives/ @symbiote-native/clipboard/solid - createClipboard (Solid says
74
74
  create*, not use*)
75
- src/angular/services/ @symbiote-native/clipboard/angular — ClipboardService
75
+ src/angular/services/ @symbiote-native/clipboard/angular - ClipboardService
76
76
  ```
77
77
 
78
78
  Every function except the listener is plain stateless async and re-exported as-is. The listener
79
79
  subscription (`addClipboardListener`) lives once in `core`, framework-agnostic; each adapter's
80
80
  `useClipboard` hook/composable/rune, Solid's `createClipboard`, and `ClipboardService.connect()`
81
81
  are thin mount/unmount wrappers (a DI-scoped `effect()` for Angular, a body-level subscription plus
82
- `onCleanup` for Solid) around that same subscription — the plumbing is written once and shared by
82
+ `onCleanup` for Solid) around that same subscription - the plumbing is written once and shared by
83
83
  every adapter.
84
84
 
85
85
  ## Use it
@@ -93,7 +93,7 @@ import {
93
93
  ```
94
94
 
95
95
  `useClipboard()`'s event only carries the clipboard's changed content _types_
96
- (`IClipboardEvent.contentTypes`), never the string itself — every adapter's demo screen treats a
96
+ (`IClipboardEvent.contentTypes`), never the string itself - every adapter's demo screen treats a
97
97
  change as a cue to re-fetch via `getStringAsync()`, not a value to render directly.
98
98
 
99
99
  ```tsx
@@ -234,10 +234,10 @@ export class ClipboardScreen {
234
234
  }
235
235
  ```
236
236
 
237
- These are trimmed from the real demo screens — `examples/expo-react/screens/ClipboardScreen.tsx`,
237
+ These are trimmed from the real demo screens - `examples/expo-react/screens/ClipboardScreen.tsx`,
238
238
  `examples/expo-vue-sfc/screens/ClipboardScreen.vue`, `examples/expo-vue-tsx/screens/ClipboardScreen.tsx`,
239
239
  `examples/expo-svelte/screens/ClipboardScreen.svelte`, `examples/expo-solid/screens/ClipboardScreen.tsx`,
240
- `examples/expo-angular/src/screens/ClipboardScreen.ts` — which additionally show `hasStringAsync()`
240
+ `examples/expo-angular/src/screens/ClipboardScreen.ts` - which additionally show `hasStringAsync()`
241
241
  status badges and, iOS-only, the `getUrlAsync`/`setUrlAsync`/`hasUrlAsync` URL surface.
242
242
 
243
243
  ## API
@@ -257,7 +257,8 @@ removeClipboardListener(subscription: EventSubscription) // deprecated, use subs
257
257
  ```
258
258
 
259
259
  Plus `ContentType`, `StringFormat`, `IGetStringOptions`, `ISetStringOptions`, `IGetImageOptions`,
260
- `IClipboardImage`, `IClipboardEvent` — ported from upstream's `Clipboard.types.ts`, renamed with
260
+ `IClipboardImage`, `IClipboardEvent`, `IAcceptedContentType`, `ICornerStyle`, `IDisplayMode` and
261
+ `IPasteEventPayload`, ported from upstream's `Clipboard.types.ts`, renamed with
261
262
  this repo's `I`-prefix convention for exported types (`ts-js-best-practices`); `ContentType` and
262
263
  `StringFormat` stay unprefixed enums, matching `AuthenticationType`/`SecurityLevel` in
263
264
  `@symbiote-native/local-auth`.
@@ -284,15 +285,48 @@ import { ClipboardService } from '@symbiote-native/clipboard/angular';
284
285
  readonly clipboardEvent = inject(ClipboardService).connect(); // Signal<IClipboardEvent | null>
285
286
  ```
286
287
 
287
- `ClipboardPasteButton` (upstream's native paste-button view component, iOS 16+) is **not**
288
- ported — out of scope for this pass. If it's ever wrapped, it follows
289
- `symbiote-third-party-native-view`, not this package's `expo-modules-core` recipe.
288
+ ### ClipboardPasteButton
289
+
290
+ `UIPasteControl`, iOS 16+, lets the user paste without the system permission prompt. It renders
291
+ nothing on Android, and `isPasteButtonAvailable` (from the package root) tells whether the device
292
+ supports it. Give it a width and a height in `style`, or it does not appear. Apple restricts the
293
+ control, so `style` takes no `backgroundColor`, `borderRadius` or `color`: use `backgroundColor`,
294
+ `foregroundColor`, `cornerStyle` and `displayMode` instead.
295
+
296
+ ```tsx
297
+ // React, `onPress` gets `{ type: 'text', text }` or `{ type: 'image', data, size }`
298
+ import { ClipboardPasteButton, isPasteButtonAvailable } from '@symbiote-native/clipboard/react';
299
+
300
+ {isPasteButtonAvailable && (
301
+ <ClipboardPasteButton
302
+ style={{ width: 160, height: 44 }}
303
+ cornerStyle="capsule"
304
+ acceptedContentTypes={['plain-text', 'image']}
305
+ onPress={data => (data.type === 'text' ? setText(data.text) : setImage(data.data))}
306
+ />
307
+ )}
308
+ ```
309
+
310
+ Vue takes `@press`, Angular `[onPress]`, Svelte and Solid `onPress`, all from their own entry
311
+ (`ClipboardPasteButton`). It is an Expo native view reached through `requireNativeViewManager`,
312
+ which the core registers when the button renders. The pasted payload is read through a guard in
313
+ `toPasteEventPayload`, a malformed native event is dropped.
314
+
315
+ ## Common questions
316
+
317
+ - **Empty string on iOS.** A denied paste on iOS 16+ returns `''`, indistinguishable from an empty
318
+ clipboard; no error is thrown.
319
+ - **Avoid the paste prompt?** Use `ClipboardPasteButton` (wraps `UIPasteControl`).
320
+ - **Copy an image?** `setImageAsync` takes a base64 string.
321
+
322
+ Sources: [Expo docs: Clipboard](https://docs.expo.dev/versions/latest/sdk/clipboard/),
323
+ [expo/expo#40189](https://github.com/expo/expo/issues/40189).
290
324
 
291
325
  ## Test it
292
326
 
293
327
  Tests exercise the JS layer only, against a fake native module in place of the real
294
328
  `requireNativeModule` resolution (`src/core/**/*.test.ts`,
295
- `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`) — no Fabric/Descriptor angle at all,
329
+ `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`) - no Fabric/Descriptor angle at all,
296
330
  since clipboard
297
331
  is a pure async-function + one-listener surface, never a view. Native rendering itself is verified
298
332
  on-device (see the parent [README](../../README.md) for the project's testing model).
@@ -300,4 +334,4 @@ on-device (see the parent [README](../../README.md) for the project's testing mo
300
334
  Native autolinking wiring (Android's 3-layer registration, iOS Podfile/pod install) is done across
301
335
  all six `examples/expo-*` canary apps. It isn't wired into the public non-Expo canaries
302
336
  (`examples/react`, `examples/vue-sfc`, `examples/vue-tsx`, `examples/svelte`, `examples/solid`,
303
- `examples/angular`) yet — those don't depend on any `expo-modules-core` package today.
337
+ `examples/angular`) yet - those don't depend on any `expo-modules-core` package today.
@@ -0,0 +1,15 @@
1
+ import { NativeViewBase } from '@symbiote-native/angular';
2
+ import type { IDescriptor } from '@symbiote-native/components';
3
+ import type { IClipboardPasteButtonProps } from '../../core/clipboard-paste-button';
4
+ /** Angular twin of `expo-clipboard`'s `ClipboardPasteButton` (`UIPasteControl`), iOS only */
5
+ export declare class ClipboardPasteButton extends NativeViewBase {
6
+ onPress: IClipboardPasteButtonProps['onPress'];
7
+ backgroundColor?: IClipboardPasteButtonProps['backgroundColor'];
8
+ foregroundColor?: IClipboardPasteButtonProps['foregroundColor'];
9
+ cornerStyle?: IClipboardPasteButtonProps['cornerStyle'];
10
+ displayMode?: IClipboardPasteButtonProps['displayMode'];
11
+ imageOptions?: IClipboardPasteButtonProps['imageOptions'];
12
+ acceptedContentTypes?: IClipboardPasteButtonProps['acceptedContentTypes'];
13
+ protected readonly propNames: readonly ["onPress", "backgroundColor", "foregroundColor", "cornerStyle", "displayMode", "imageOptions", "acceptedContentTypes"];
14
+ protected renderView(props: object): IDescriptor | null;
15
+ }
@@ -0,0 +1,119 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
36
+ import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
37
+ import { renderClipboardPasteButton } from '../../core/clipboard-paste-button.js';
38
+ /** Angular twin of `expo-clipboard`'s `ClipboardPasteButton` (`UIPasteControl`), iOS only */
39
+ let ClipboardPasteButton = (() => {
40
+ let _classDecorators = [Component({
41
+ selector: 'ClipboardPasteButton',
42
+ standalone: true,
43
+ imports: [DescriptorOutlet],
44
+ changeDetection: ChangeDetectionStrategy.OnPush,
45
+ template: `@if (descriptor; as node) {
46
+ <symbiote-descriptor-outlet [node]="node" />
47
+ }`,
48
+ })];
49
+ let _classDescriptor;
50
+ let _classExtraInitializers = [];
51
+ let _classThis;
52
+ let _classSuper = NativeViewBase;
53
+ let _onPress_decorators;
54
+ let _onPress_initializers = [];
55
+ let _onPress_extraInitializers = [];
56
+ let _backgroundColor_decorators;
57
+ let _backgroundColor_initializers = [];
58
+ let _backgroundColor_extraInitializers = [];
59
+ let _foregroundColor_decorators;
60
+ let _foregroundColor_initializers = [];
61
+ let _foregroundColor_extraInitializers = [];
62
+ let _cornerStyle_decorators;
63
+ let _cornerStyle_initializers = [];
64
+ let _cornerStyle_extraInitializers = [];
65
+ let _displayMode_decorators;
66
+ let _displayMode_initializers = [];
67
+ let _displayMode_extraInitializers = [];
68
+ let _imageOptions_decorators;
69
+ let _imageOptions_initializers = [];
70
+ let _imageOptions_extraInitializers = [];
71
+ let _acceptedContentTypes_decorators;
72
+ let _acceptedContentTypes_initializers = [];
73
+ let _acceptedContentTypes_extraInitializers = [];
74
+ var ClipboardPasteButton = class extends _classSuper {
75
+ static { _classThis = this; }
76
+ static {
77
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
78
+ _onPress_decorators = [Input({ required: true })];
79
+ _backgroundColor_decorators = [Input()];
80
+ _foregroundColor_decorators = [Input()];
81
+ _cornerStyle_decorators = [Input()];
82
+ _displayMode_decorators = [Input()];
83
+ _imageOptions_decorators = [Input()];
84
+ _acceptedContentTypes_decorators = [Input()];
85
+ __esDecorate(null, null, _onPress_decorators, { kind: "field", name: "onPress", static: false, private: false, access: { has: obj => "onPress" in obj, get: obj => obj.onPress, set: (obj, value) => { obj.onPress = value; } }, metadata: _metadata }, _onPress_initializers, _onPress_extraInitializers);
86
+ __esDecorate(null, null, _backgroundColor_decorators, { kind: "field", name: "backgroundColor", static: false, private: false, access: { has: obj => "backgroundColor" in obj, get: obj => obj.backgroundColor, set: (obj, value) => { obj.backgroundColor = value; } }, metadata: _metadata }, _backgroundColor_initializers, _backgroundColor_extraInitializers);
87
+ __esDecorate(null, null, _foregroundColor_decorators, { kind: "field", name: "foregroundColor", static: false, private: false, access: { has: obj => "foregroundColor" in obj, get: obj => obj.foregroundColor, set: (obj, value) => { obj.foregroundColor = value; } }, metadata: _metadata }, _foregroundColor_initializers, _foregroundColor_extraInitializers);
88
+ __esDecorate(null, null, _cornerStyle_decorators, { kind: "field", name: "cornerStyle", static: false, private: false, access: { has: obj => "cornerStyle" in obj, get: obj => obj.cornerStyle, set: (obj, value) => { obj.cornerStyle = value; } }, metadata: _metadata }, _cornerStyle_initializers, _cornerStyle_extraInitializers);
89
+ __esDecorate(null, null, _displayMode_decorators, { kind: "field", name: "displayMode", static: false, private: false, access: { has: obj => "displayMode" in obj, get: obj => obj.displayMode, set: (obj, value) => { obj.displayMode = value; } }, metadata: _metadata }, _displayMode_initializers, _displayMode_extraInitializers);
90
+ __esDecorate(null, null, _imageOptions_decorators, { kind: "field", name: "imageOptions", static: false, private: false, access: { has: obj => "imageOptions" in obj, get: obj => obj.imageOptions, set: (obj, value) => { obj.imageOptions = value; } }, metadata: _metadata }, _imageOptions_initializers, _imageOptions_extraInitializers);
91
+ __esDecorate(null, null, _acceptedContentTypes_decorators, { kind: "field", name: "acceptedContentTypes", static: false, private: false, access: { has: obj => "acceptedContentTypes" in obj, get: obj => obj.acceptedContentTypes, set: (obj, value) => { obj.acceptedContentTypes = value; } }, metadata: _metadata }, _acceptedContentTypes_initializers, _acceptedContentTypes_extraInitializers);
92
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
93
+ ClipboardPasteButton = _classThis = _classDescriptor.value;
94
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
95
+ __runInitializers(_classThis, _classExtraInitializers);
96
+ }
97
+ onPress = __runInitializers(this, _onPress_initializers, void 0);
98
+ backgroundColor = (__runInitializers(this, _onPress_extraInitializers), __runInitializers(this, _backgroundColor_initializers, void 0));
99
+ foregroundColor = (__runInitializers(this, _backgroundColor_extraInitializers), __runInitializers(this, _foregroundColor_initializers, void 0));
100
+ cornerStyle = (__runInitializers(this, _foregroundColor_extraInitializers), __runInitializers(this, _cornerStyle_initializers, void 0));
101
+ displayMode = (__runInitializers(this, _cornerStyle_extraInitializers), __runInitializers(this, _displayMode_initializers, void 0));
102
+ imageOptions = (__runInitializers(this, _displayMode_extraInitializers), __runInitializers(this, _imageOptions_initializers, void 0));
103
+ acceptedContentTypes = (__runInitializers(this, _imageOptions_extraInitializers), __runInitializers(this, _acceptedContentTypes_initializers, void 0));
104
+ propNames = (__runInitializers(this, _acceptedContentTypes_extraInitializers), [
105
+ 'onPress',
106
+ 'backgroundColor',
107
+ 'foregroundColor',
108
+ 'cornerStyle',
109
+ 'displayMode',
110
+ 'imageOptions',
111
+ 'acceptedContentTypes',
112
+ ]);
113
+ renderView(props) {
114
+ return renderClipboardPasteButton(props);
115
+ }
116
+ };
117
+ return ClipboardPasteButton = _classThis;
118
+ })();
119
+ export { ClipboardPasteButton };
@@ -0,0 +1,29 @@
1
+ import type { IAccessibilityProps, IAriaProps, IDescriptor, IResponderProps } from '@symbiote-native/components';
2
+ import type { IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
3
+ import type { IAcceptedContentType, ICornerStyle, IDisplayMode, IGetImageOptions, IPasteEventPayload } from './types';
4
+ export declare const CLIPBOARD_PASTE_BUTTON_MODULE_NAME = "ExpoClipboard";
5
+ export type IClipboardPasteButtonProps = IAccessibilityProps & IAriaProps & IResponderProps & {
6
+ /** Called with the pasted text or image, inspect `type` to tell them apart */
7
+ onPress: (data: IPasteEventPayload) => void;
8
+ /** Leaving it out lets the color follow the system theme */
9
+ backgroundColor?: string | null;
10
+ /** @default 'white' */
11
+ foregroundColor?: string | null;
12
+ /** @default 'capsule' */
13
+ cornerStyle?: ICornerStyle | null;
14
+ /** @default 'iconAndLabel' */
15
+ displayMode?: IDisplayMode | null;
16
+ /** Needs a width and a height, or the button does not appear */
17
+ style?: IStyleProp<Omit<IViewStyle, 'backgroundColor' | 'borderRadius' | 'color'>>;
18
+ imageOptions?: IGetImageOptions | null;
19
+ /** Do not combine `plain-text` with `html`, all text would be treated as `html` */
20
+ acceptedContentTypes?: IAcceptedContentType[];
21
+ testID?: string;
22
+ nativeID?: string;
23
+ onLayout?: (event: ISymbioteEvent) => void;
24
+ };
25
+ export declare function clipboardPasteButtonViewName(): string;
26
+ export declare function ensureClipboardPasteButtonRegistered(): boolean;
27
+ /** Reads the native paste event, `null` when its payload has neither a text nor an image shape */
28
+ export declare function toPasteEventPayload(event: ISymbioteEvent): IPasteEventPayload | null;
29
+ export declare function renderClipboardPasteButton(props: object): IDescriptor | null;
@@ -0,0 +1,50 @@
1
+ import { Platform, requireNativeViewManager } from 'expo-modules-core';
2
+ import { el } from '@symbiote-native/components';
3
+ import { expoViewManagerName, tryRegisterNativeView, } from '@symbiote-native/engine';
4
+ export const CLIPBOARD_PASTE_BUTTON_MODULE_NAME = 'ExpoClipboard';
5
+ const PASTE_EVENT_PROP = 'onPastePressed';
6
+ const PASTE_TYPE = { text: 'text', image: 'image' };
7
+ export function clipboardPasteButtonViewName() {
8
+ return expoViewManagerName(CLIPBOARD_PASTE_BUTTON_MODULE_NAME);
9
+ }
10
+ // Registers the view config in React Native's registry, which the engine derives the view's events
11
+ // and prop processors from. Called at render, not at import: a registration that only a barrel's
12
+ // load-time side effect performs is dropped by Metro's `inlineRequires` in release
13
+ export function ensureClipboardPasteButtonRegistered() {
14
+ if (!Platform.select({ ios: true, default: false }))
15
+ return false;
16
+ return tryRegisterNativeView(() => requireNativeViewManager(CLIPBOARD_PASTE_BUTTON_MODULE_NAME));
17
+ }
18
+ function isImageSize(value) {
19
+ return (typeof value === 'object' &&
20
+ value !== null &&
21
+ typeof Reflect.get(value, 'width') === 'number' &&
22
+ typeof Reflect.get(value, 'height') === 'number');
23
+ }
24
+ /** Reads the native paste event, `null` when its payload has neither a text nor an image shape */
25
+ export function toPasteEventPayload(event) {
26
+ const { type, text, data, size } = event.nativeEvent;
27
+ if (type === PASTE_TYPE.text && typeof text === 'string')
28
+ return { type, text };
29
+ if (type === PASTE_TYPE.image &&
30
+ typeof data === 'string' &&
31
+ isImageSize(size)) {
32
+ return { type, data, size };
33
+ }
34
+ return null;
35
+ }
36
+ // `object`, not the props type: Vue and Angular hand over an untyped attribute bag
37
+ export function renderClipboardPasteButton(props) {
38
+ if (!ensureClipboardPasteButtonRegistered())
39
+ return null;
40
+ const onPress = Reflect.get(props, 'onPress');
41
+ const viewProps = Object.fromEntries(Object.entries(props).filter(([key]) => key !== 'onPress'));
42
+ return el(clipboardPasteButtonViewName(), {
43
+ ...viewProps,
44
+ [PASTE_EVENT_PROP]: (event) => {
45
+ const payload = toPasteEventPayload(event);
46
+ if (payload && typeof onPress === 'function')
47
+ onPress(payload);
48
+ },
49
+ });
50
+ }
@@ -79,6 +79,8 @@ export declare function hasImageAsync(): Promise<boolean>;
79
79
  * content types whenever the clipboard changes.
80
80
  */
81
81
  export declare function addClipboardListener(listener: (event: IClipboardEvent) => void): EventSubscription;
82
+ /** Whether `ClipboardPasteButton` can render, needs iOS 16 or newer */
83
+ export declare const isPasteButtonAvailable: boolean;
82
84
  /**
83
85
  * Removes the listener added by `addClipboardListener`.
84
86
  * @deprecated use `subscription.remove()` instead.
@@ -1,4 +1,4 @@
1
- import { UnavailabilityError } from 'expo-modules-core';
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
2
  import { CLIPBOARD_CHANGED_EVENT_NAME, expoClipboard } from './native-module.js';
3
3
  // Matches the module name upstream's own Clipboard.ts passes to every UnavailabilityError.
4
4
  const NATIVE_MODULE_NAME = 'Clipboard';
@@ -133,6 +133,11 @@ export async function hasImageAsync() {
133
133
  export function addClipboardListener(listener) {
134
134
  return expoClipboard.addListener(CLIPBOARD_CHANGED_EVENT_NAME, listener);
135
135
  }
136
+ /** Whether `ClipboardPasteButton` can render, needs iOS 16 or newer */
137
+ export const isPasteButtonAvailable = Platform.select({
138
+ ios: expoClipboard.isPasteButtonAvailable ?? false,
139
+ default: false,
140
+ });
136
141
  /**
137
142
  * Removes the listener added by `addClipboardListener`.
138
143
  * @deprecated use `subscription.remove()` instead.
@@ -1,3 +1,4 @@
1
- export { getStringAsync, setStringAsync, hasStringAsync, getUrlAsync, setUrlAsync, hasUrlAsync, getImageAsync, setImageAsync, hasImageAsync, addClipboardListener, removeClipboardListener, } from './clipboard';
2
- export { ContentType, StringFormat, type IGetStringOptions, type ISetStringOptions, type IGetImageOptions, type IClipboardImage, type IClipboardEvent, } from './types';
1
+ export { getStringAsync, setStringAsync, hasStringAsync, getUrlAsync, setUrlAsync, hasUrlAsync, getImageAsync, setImageAsync, hasImageAsync, addClipboardListener, removeClipboardListener, isPasteButtonAvailable, } from './clipboard';
2
+ export { clipboardPasteButtonViewName, ensureClipboardPasteButtonRegistered, renderClipboardPasteButton, toPasteEventPayload, type IClipboardPasteButtonProps, } from './clipboard-paste-button';
3
+ export { ContentType, StringFormat, type IGetStringOptions, type ISetStringOptions, type IGetImageOptions, type IClipboardImage, type IClipboardEvent, type IAcceptedContentType, type ICornerStyle, type IDisplayMode, type ITextPasteEvent, type IImagePasteEvent, type IPasteEventPayload, } from './types';
3
4
  export type { EventSubscription } from 'expo-modules-core';
@@ -1,2 +1,3 @@
1
- export { getStringAsync, setStringAsync, hasStringAsync, getUrlAsync, setUrlAsync, hasUrlAsync, getImageAsync, setImageAsync, hasImageAsync, addClipboardListener, removeClipboardListener, } from './clipboard.js';
1
+ export { getStringAsync, setStringAsync, hasStringAsync, getUrlAsync, setUrlAsync, hasUrlAsync, getImageAsync, setImageAsync, hasImageAsync, addClipboardListener, removeClipboardListener, isPasteButtonAvailable, } from './clipboard.js';
2
+ export { clipboardPasteButtonViewName, ensureClipboardPasteButtonRegistered, renderClipboardPasteButton, toPasteEventPayload, } from './clipboard-paste-button.js';
2
3
  export { ContentType, StringFormat, } from './types.js';
@@ -13,5 +13,6 @@ export type INativeClipboardModule = {
13
13
  getImageAsync?(options: IGetImageOptions): Promise<IClipboardImage | null>;
14
14
  setImageAsync?(base64Image: string): Promise<void>;
15
15
  hasImageAsync?(): Promise<boolean>;
16
+ isPasteButtonAvailable?: boolean;
16
17
  };
17
18
  export declare const expoClipboard: INativeClipboardModule;
@@ -65,3 +65,14 @@ export type IClipboardEvent = {
65
65
  */
66
66
  contentTypes: ContentType[];
67
67
  };
68
+ export type IAcceptedContentType = 'plain-text' | 'image' | 'url' | 'html';
69
+ export type ICornerStyle = 'dynamic' | 'fixed' | 'capsule' | 'large' | 'medium' | 'small';
70
+ export type IDisplayMode = 'iconAndLabel' | 'iconOnly' | 'labelOnly';
71
+ export type ITextPasteEvent = {
72
+ text: string;
73
+ type: 'text';
74
+ };
75
+ export type IImagePasteEvent = {
76
+ type: 'image';
77
+ } & IClipboardImage;
78
+ export type IPasteEventPayload = ITextPasteEvent | IImagePasteEvent;
@@ -1,10 +1,7 @@
1
1
  // Ported from expo-clipboard's own Clipboard.types.ts. ContentType and StringFormat are enums —
2
2
  // kept unprefixed, matching this repo's convention for enums (see AuthenticationType /
3
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).
4
+ // `I`-prefix convention for exported types
8
5
  /**
9
6
  * Type used to define what type of data is stored in the clipboard.
10
7
  */
@@ -0,0 +1,4 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { IClipboardPasteButtonProps } from '../core/clipboard-paste-button';
3
+ /** React twin of `expo-clipboard`'s `ClipboardPasteButton` (`UIPasteControl`), iOS only */
4
+ export declare function ClipboardPasteButton(props: IClipboardPasteButtonProps): ReactElement | null;
@@ -0,0 +1,7 @@
1
+ import { descriptorToReact } from '@symbiote-native/react';
2
+ import { renderClipboardPasteButton } from '../core/clipboard-paste-button.js';
3
+ /** React twin of `expo-clipboard`'s `ClipboardPasteButton` (`UIPasteControl`), iOS only */
4
+ export function ClipboardPasteButton(props) {
5
+ const descriptor = renderClipboardPasteButton(props);
6
+ return descriptor ? descriptorToReact(descriptor) : null;
7
+ }
@@ -1,2 +1,3 @@
1
1
  export { useClipboard } from './hooks/use-clipboard';
2
+ export { ClipboardPasteButton } from './clipboard-paste-button';
2
3
  export * from '../core';
@@ -5,4 +5,5 @@
5
5
  // adapters/react/src/hooks (never `composables`, that's Vue's term) and the shape of
6
6
  // @symbiote-native/sensors' useAccelerometer.
7
7
  export { useClipboard } from './hooks/use-clipboard/index.js';
8
+ export { ClipboardPasteButton } from './clipboard-paste-button.js';
8
9
  export * from '../core/index.js';
@@ -0,0 +1,3 @@
1
+ import { descriptorToSolid } from '@symbiote-native/solid';
2
+ import type { IClipboardPasteButtonProps } from '../core/clipboard-paste-button';
3
+ export declare function ClipboardPasteButton(props: IClipboardPasteButtonProps): ReturnType<typeof descriptorToSolid> | null;
@@ -0,0 +1,10 @@
1
+ import { descriptorToSolid } from '@symbiote-native/solid';
2
+ import { renderClipboardPasteButton } from '../core/clipboard-paste-button.js';
3
+ // The platform decision is made once at mount: the descriptor bridge builds the node once and
4
+ // only diffs prop values afterwards, so the descriptor's shape must not change
5
+ export function ClipboardPasteButton(props) {
6
+ const initial = renderClipboardPasteButton({ ...props });
7
+ if (!initial)
8
+ return null;
9
+ return descriptorToSolid(() => renderClipboardPasteButton({ ...props }) ?? initial);
10
+ }
@@ -1,2 +1,3 @@
1
1
  export { createClipboard } from './primitives/create-clipboard';
2
+ export { ClipboardPasteButton } from './clipboard-paste-button';
2
3
  export * from '../core';
@@ -5,4 +5,5 @@
5
5
  // `create*` (never `hooks/`/`use*`, that is React's term and Solid's word for consuming an
6
6
  // existing thing) — see adapters/solid/src/primitives for the same convention.
7
7
  export { createClipboard } from './primitives/create-clipboard.js';
8
+ export { ClipboardPasteButton } from './clipboard-paste-button.js';
8
9
  export * from '../core/index.js';
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ // The native view name is mixed-case, which a literal tag would parse as a component reference,
3
+ // so the tag is dynamic
4
+ import { hostProps } from '@symbiote-native/svelte/native-view-bridge';
5
+ import { renderClipboardPasteButton } from '../core/clipboard-paste-button';
6
+ import type { IClipboardPasteButtonProps } from '../core/clipboard-paste-button';
7
+
8
+ const props: IClipboardPasteButtonProps = $props();
9
+ const descriptor = $derived(renderClipboardPasteButton({ ...props }));
10
+ </script>
11
+
12
+ {#if descriptor}
13
+ <svelte:element
14
+ this={descriptor.type}
15
+ {@attach hostProps(descriptor.props)}
16
+ />
17
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { IClipboardPasteButtonProps } from '../core/clipboard-paste-button';
2
+ declare const ClipboardPasteButton: import("svelte").Component<IClipboardPasteButtonProps, {}, "">;
3
+ type ClipboardPasteButton = ReturnType<typeof ClipboardPasteButton>;
4
+ export default ClipboardPasteButton;
@@ -1,2 +1,3 @@
1
1
  export { useClipboard } from './runes/use-clipboard.svelte';
2
+ export { default as ClipboardPasteButton } from './clipboard-paste-button.svelte';
2
3
  export * from '../core';
@@ -4,4 +4,5 @@
4
4
  // `$effect` (runes/use-clipboard.svelte.ts), mirroring the lifecycle-bucket naming convention of
5
5
  // adapters/svelte/src/runes (never `hooks/`/`composables/`, those are React's and Vue's terms).
6
6
  export { useClipboard } from './runes/use-clipboard.svelte';
7
+ export { default as ClipboardPasteButton } from './clipboard-paste-button.svelte';
7
8
  export * from '../core/index.js';
@@ -0,0 +1,3 @@
1
+ import { type VNode } from '@vue/runtime-core';
2
+ /** Vue twin of `expo-clipboard`'s `ClipboardPasteButton` (`UIPasteControl`), iOS only */
3
+ export declare const ClipboardPasteButton: import("vue").DefineComponent<{}, () => VNode | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,15 @@
1
+ import { defineComponent } from '@vue/runtime-core';
2
+ import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
3
+ import { renderClipboardPasteButton } from '../core/clipboard-paste-button.js';
4
+ /** Vue twin of `expo-clipboard`'s `ClipboardPasteButton` (`UIPasteControl`), iOS only */
5
+ export const ClipboardPasteButton = defineComponent({
6
+ name: 'ClipboardPasteButton',
7
+ // Every attribute is a native view prop, so none falls through onto a wrapper element
8
+ inheritAttrs: false,
9
+ setup(_props, { attrs }) {
10
+ return () => {
11
+ const descriptor = renderClipboardPasteButton(normalizeVueAttrs(attrs));
12
+ return descriptor ? descriptorToVue(descriptor) : null;
13
+ };
14
+ },
15
+ });
@@ -1,2 +1,3 @@
1
1
  export { useClipboard } from './composables/use-clipboard';
2
+ export { ClipboardPasteButton } from './clipboard-paste-button';
2
3
  export * from '../core';
@@ -5,4 +5,5 @@
5
5
  // convention of adapters/vue/src/composables (never `hooks/`, that's React's term) and the
6
6
  // shape of @symbiote-native/sensors' useAccelerometer composable.
7
7
  export { useClipboard } from './composables/use-clipboard/index.js';
8
+ export { ClipboardPasteButton } from './clipboard-paste-button.js';
8
9
  export * from '../core/index.js';