Skip to content

Commit 0cba393

Browse files
committed
feat: rich-paste
1 parent d8610c0 commit 0cba393

4 files changed

Lines changed: 273 additions & 0 deletions

File tree

.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export default defineConfig({
7474
{ text: 'Fluid Segmented Bar', link: '/plugins/fluid-segmented-bar' },
7575
{ text: 'Label Marquee', link: '/plugins/label-marquee' },
7676
{ text: 'Markdown View', link: '/plugins/markdown-view' },
77+
{ text: 'Rich Paste', link: '/plugins/rich-paste' },
7778
],
7879
},
7980
{

.vitepress/theme/PluginsHome.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,15 @@ const plugins = [
304304
href: '/plugins/smartlook',
305305
featured: false,
306306
},
307+
{
308+
name: '@nstudio/nativescript-rich-paste',
309+
title: 'Rich Paste',
310+
description: 'Rich paste and drag-and-drop for text inputs. Handles images, GIFs, files, and text.',
311+
icon: '📋',
312+
gradient: 'from-rose-500 to-pink-500',
313+
href: '/plugins/rich-paste',
314+
featured: false,
315+
},
307316
];
308317
309318
const featuredPlugin = plugins.find(p => p.featured);

content/plugins/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A collection of high-quality NativeScript plugins from nStudio. Native performan
1515
- [Filterable Listpicker](/plugins/filterable-listpicker) - Modal listpicker with search filtering
1616
- [Input Mask](/plugins/input-mask) - Format user input for phone numbers, credit cards, etc.
1717
- [Menu](/plugins/menu) - Native anchored menus with submenus, palette actions, and selected events
18+
- [Rich Paste](/plugins/rich-paste) - Rich paste and drag-and-drop for text inputs with images, GIFs, and files
1819
- [ExoPlayer](/plugins/exoplayer) - Video player using ExoPlayer/AVPlayer
1920

2021
## Barcode & Scanning

content/plugins/rich-paste.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# Rich Paste
2+
3+
Rich paste and drag-and-drop support for NativeScript text inputs. Handles images, GIFs, files, and text from the clipboard with one shared TypeScript API across iOS and Android.
4+
5+
The plugin supports:
6+
7+
- Pasting images, GIFs, and static images from the clipboard
8+
- Pasting files (PDF, RTF, HTML) from the clipboard
9+
- Drag-and-drop of images and files into text inputs
10+
- MIME-based accept filtering (`all`, `image/*`, `text/plain`, etc.)
11+
- Drop-in replacements for TextField and TextView
12+
- Automatic temp file management for pasted binary content
13+
14+
## Installation
15+
16+
```bash
17+
npm install @nstudio/nativescript-rich-paste
18+
```
19+
20+
## Usage
21+
22+
### NativeScript Core
23+
24+
Drop-in replacements for [TextField](https://docs.nativescript.org/ui/text-field) and [TextView](https://docs.nativescript.org/ui/text-view) which work identically but with enhanced rich paste features. Just append `RichPaste` to the end of the element name.
25+
26+
```xml
27+
<Page xmlns:rp="@nstudio/nativescript-rich-paste">
28+
<StackLayout class="p-20">
29+
<rp:TextFieldRichPaste accept="all" hint="Paste rich data..." paste="{{ onPaste }}" />
30+
<rp:TextViewRichPaste accept="image/*" hint="Drop images here..." enableDragDrop="true" paste="{{ onPaste }}" drop="{{ onDrop }}" />
31+
</StackLayout>
32+
</Page>
33+
```
34+
35+
```typescript
36+
import { EventData, Page } from '@nativescript/core';
37+
import { PasteEventData } from '@nstudio/nativescript-rich-paste';
38+
39+
export function navigatingTo(args: EventData) {
40+
const page = args.object as Page;
41+
page.bindingContext = new DemoModel();
42+
}
43+
44+
class DemoModel {
45+
onPaste(args: PasteEventData) {
46+
const payload = args.data;
47+
switch (payload.type) {
48+
case 'text':
49+
console.log('Text:', payload.value);
50+
break;
51+
case 'images':
52+
payload.items.forEach((img) => {
53+
console.log(img.uri, img.mimeType, img.animated);
54+
});
55+
break;
56+
case 'files':
57+
payload.items.forEach((file) => {
58+
console.log(file.name, file.mimeType, file.size);
59+
});
60+
break;
61+
}
62+
}
63+
64+
onDrop(args: PasteEventData) {
65+
console.log('Dropped:', args.data);
66+
}
67+
}
68+
```
69+
70+
### Angular
71+
72+
```typescript
73+
import { registerElement } from '@nativescript/angular';
74+
import { TextFieldRichPaste, TextViewRichPaste } from '@nstudio/nativescript-rich-paste';
75+
76+
registerElement('TextFieldRichPaste', () => TextFieldRichPaste);
77+
registerElement('TextViewRichPaste', () => TextViewRichPaste);
78+
```
79+
80+
```html
81+
<TextFieldRichPaste
82+
accept="all"
83+
hint="Paste rich data..."
84+
(paste)="onPaste($event)"
85+
></TextFieldRichPaste>
86+
87+
<TextViewRichPaste
88+
accept="image/*"
89+
hint="Drop images here..."
90+
enableDragDrop="true"
91+
(paste)="onPaste($event)"
92+
(drop)="onDrop($event)"
93+
></TextViewRichPaste>
94+
```
95+
96+
### Other Flavors
97+
98+
```typescript
99+
import { TextFieldRichPaste, TextViewRichPaste } from '@nstudio/nativescript-rich-paste';
100+
101+
// Vue
102+
registerElement('TextFieldRichPaste', () => TextFieldRichPaste);
103+
registerElement('TextViewRichPaste', () => TextViewRichPaste);
104+
105+
// React
106+
registerElement('textFieldRichPaste', () => TextFieldRichPaste);
107+
registerElement('textViewRichPaste', () => TextViewRichPaste);
108+
109+
// Svelte
110+
registerNativeViewElement('textFieldRichPaste', () => TextFieldRichPaste);
111+
registerNativeViewElement('textViewRichPaste', () => TextViewRichPaste);
112+
113+
// Solid
114+
registerElement('textFieldRichPaste', TextFieldRichPaste);
115+
registerElement('textViewRichPaste', TextViewRichPaste);
116+
```
117+
118+
## Paste Payload
119+
120+
The `paste` and `drop` events emit a `PasteEventData` object whose `data` property is a discriminated union:
121+
122+
### Text
123+
124+
```typescript
125+
interface PasteTextPayload {
126+
type: 'text';
127+
value: string;
128+
}
129+
```
130+
131+
### Images
132+
133+
```typescript
134+
interface PasteImagesPayload {
135+
type: 'images';
136+
items: PasteImageItem[];
137+
}
138+
139+
interface PasteImageItem {
140+
uri: string; // file:// URI to a temp file
141+
mimeType: string; // e.g. 'image/png', 'image/gif'
142+
width?: number;
143+
height?: number;
144+
animated: boolean; // true for GIFs
145+
}
146+
```
147+
148+
### Files
149+
150+
```typescript
151+
interface PasteFilesPayload {
152+
type: 'files';
153+
items: PasteFileItem[];
154+
}
155+
156+
interface PasteFileItem {
157+
uri: string; // file:// URI to a temp file
158+
mimeType: string; // e.g. 'application/pdf'
159+
name?: string;
160+
size?: number;
161+
}
162+
```
163+
164+
### Unsupported
165+
166+
Returned when the clipboard contains data that does not match the `accept` filter.
167+
168+
```typescript
169+
interface PasteUnsupportedPayload {
170+
type: 'unsupported';
171+
availableTypes: string[];
172+
}
173+
```
174+
175+
## Events
176+
177+
### paste
178+
179+
Emitted when rich content is pasted from the clipboard.
180+
181+
```typescript
182+
import { PasteEventData } from '@nstudio/nativescript-rich-paste';
183+
184+
function onPaste(args: PasteEventData) {
185+
const payload = args.data;
186+
console.log('Paste type:', payload.type);
187+
}
188+
```
189+
190+
For text pastes, the default TextField/TextView behavior still applies (the text is inserted into the field) and the `paste` event is also fired.
191+
192+
### drop
193+
194+
Emitted when content is dragged and dropped onto the input. Requires `enableDragDrop="true"`.
195+
196+
```typescript
197+
import { DropEventData } from '@nstudio/nativescript-rich-paste';
198+
199+
function onDrop(args: DropEventData) {
200+
const payload = args.data;
201+
console.log('Drop type:', payload.type);
202+
}
203+
```
204+
205+
## Properties
206+
207+
| Property | Type | Default | Description |
208+
| --- | --- | --- | --- |
209+
| accept | string | `'all'` | MIME filter for accepted paste content |
210+
| enableDragDrop | boolean | `false` | Enable drag-and-drop support |
211+
212+
### accept filter
213+
214+
The `accept` property controls which content types are processed. Supported values:
215+
216+
- `'all'` — accept everything (default)
217+
- `'image/*'` — accept all image types
218+
- `'image/png'` — accept only PNG images
219+
- `'image/gif'` — accept only GIF images
220+
- `'text/plain'` — accept only plain text
221+
- `'application/pdf'` — accept only PDF files
222+
- Comma-separated — e.g. `'image/*,application/pdf'`
223+
224+
## Classes
225+
226+
- **TextFieldRichPaste** — extends NativeScript TextField
227+
- **TextViewRichPaste** — extends NativeScript TextView
228+
229+
Both classes expose the same `accept`, `enableDragDrop` properties and emit `paste` and `drop` events.
230+
231+
## Temp File Management
232+
233+
Pasted binary content (images, files) is written to temporary files under the app's temp directory. You can clean up temp files at any time:
234+
235+
```typescript
236+
import { PasteInputTempFiles } from '@nstudio/nativescript-rich-paste';
237+
238+
// Remove all temp files created by rich paste
239+
PasteInputTempFiles.cleanupAll();
240+
```
241+
242+
## Platform Notes
243+
244+
### iOS
245+
246+
- Uses custom UITextField/UITextView subclasses that intercept the native `paste:` action
247+
- Reads from `UIPasteboard.generalPasteboard` with support for images, GIFs, file URLs, and UTI-based document types
248+
- Drag-and-drop uses `UIDropInteraction` with a native delegate
249+
- GIF animation is preserved by detecting `com.compuserve.gif` UTI and writing raw data to temp files
250+
- Supports file reference URL resolution including Finder bookmark data
251+
252+
### Android
253+
254+
- Uses `OnReceiveContentListener` on API 31+ for unified paste and drag-and-drop handling
255+
- Falls back to `onTextContextMenuItem` interception on older API levels
256+
- Resolves MIME types via ContentResolver, file extension mapping, and ClipDescription
257+
- Validates GIF files by checking the file header magic bytes
258+
- Images are decoded via `BitmapFactory` and compressed to JPEG temp files
259+
260+
## License
261+
262+
Apache License Version 2.0

0 commit comments

Comments
 (0)