tiny-essentials 1.17.1 โ†’ 1.18.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 (39) hide show
  1. package/dist/v1/TinyBasicsEs.js +164 -21
  2. package/dist/v1/TinyBasicsEs.min.js +1 -1
  3. package/dist/v1/TinyClipboard.js +459 -0
  4. package/dist/v1/TinyClipboard.min.js +1 -0
  5. package/dist/v1/TinyDragger.js +164 -21
  6. package/dist/v1/TinyDragger.min.js +1 -1
  7. package/dist/v1/TinyEssentials.js +1046 -21
  8. package/dist/v1/TinyEssentials.min.js +1 -1
  9. package/dist/v1/TinyHtml.js +164 -21
  10. package/dist/v1/TinyHtml.min.js +1 -1
  11. package/dist/v1/TinySmartScroller.js +164 -21
  12. package/dist/v1/TinySmartScroller.min.js +1 -1
  13. package/dist/v1/TinyTextRangeEditor.js +497 -0
  14. package/dist/v1/TinyTextRangeEditor.min.js +1 -0
  15. package/dist/v1/TinyUploadClicker.js +166 -21
  16. package/dist/v1/TinyUploadClicker.min.js +1 -1
  17. package/dist/v1/build/TinyClipboard.cjs +7 -0
  18. package/dist/v1/build/TinyClipboard.d.mts +3 -0
  19. package/dist/v1/build/TinyClipboard.mjs +2 -0
  20. package/dist/v1/build/TinyTextRangeEditor.cjs +7 -0
  21. package/dist/v1/build/TinyTextRangeEditor.d.mts +3 -0
  22. package/dist/v1/build/TinyTextRangeEditor.mjs +2 -0
  23. package/dist/v1/index.cjs +4 -0
  24. package/dist/v1/index.d.mts +3 -1
  25. package/dist/v1/index.mjs +3 -1
  26. package/dist/v1/libs/TinyClipboard.cjs +420 -0
  27. package/dist/v1/libs/TinyClipboard.d.mts +155 -0
  28. package/dist/v1/libs/TinyClipboard.mjs +398 -0
  29. package/dist/v1/libs/TinyHtml.cjs +164 -21
  30. package/dist/v1/libs/TinyHtml.d.mts +150 -27
  31. package/dist/v1/libs/TinyHtml.mjs +158 -20
  32. package/dist/v1/libs/TinyTextRangeEditor.cjs +458 -0
  33. package/dist/v1/libs/TinyTextRangeEditor.d.mts +200 -0
  34. package/dist/v1/libs/TinyTextRangeEditor.mjs +424 -0
  35. package/docs/v1/README.md +2 -0
  36. package/docs/v1/libs/TinyClipboard.md +213 -0
  37. package/docs/v1/libs/TinyHtml.md +112 -15
  38. package/docs/v1/libs/TinyTextRangeEditor.md +208 -0
  39. package/package.json +1 -1
@@ -0,0 +1,424 @@
1
+ /**
2
+ * A full-featured text range editor for `<input>` and `<textarea>` elements,
3
+ * including advanced utilities for BBCode or similar tag-based markup editing.
4
+ */
5
+ class TinyTextRangeEditor {
6
+ /** @type {HTMLInputElement | HTMLTextAreaElement} */
7
+ #el;
8
+ /** @type {string} */
9
+ #openTag;
10
+ /** @type {string} */
11
+ #closeTag;
12
+ /**
13
+ * @param {HTMLInputElement | HTMLTextAreaElement} elem - The target editable input or textarea element.
14
+ * @param {Object} [settings={}] - Optional tag symbol customization.
15
+ * @param {string} [settings.openTag='['] - The character or symbol used to start a tag (e.g., `'['`).
16
+ * @param {string} [settings.closeTag=']'] - The character or symbol used to end a tag (e.g., `']'`).
17
+ */
18
+ constructor(elem, { openTag = '[', closeTag = ']' } = {}) {
19
+ if (!(elem instanceof HTMLInputElement || elem instanceof HTMLTextAreaElement))
20
+ throw new TypeError('Element must be an input or textarea.');
21
+ if (typeof openTag !== 'string')
22
+ throw new TypeError('openTag must be a string.');
23
+ if (typeof closeTag !== 'string')
24
+ throw new TypeError('closeTag must be a string.');
25
+ this.#el = elem;
26
+ this.#openTag = openTag;
27
+ this.#closeTag = closeTag;
28
+ }
29
+ /** @returns {string} The current open tag symbol. */
30
+ getOpenTag() {
31
+ return this.#openTag;
32
+ }
33
+ /** @returns {string} The current close tag symbol. */
34
+ getCloseTag() {
35
+ return this.#closeTag;
36
+ }
37
+ /** @param {string} tag - New open tag symbol to use (e.g., `'['`). */
38
+ setOpenTag(tag) {
39
+ if (typeof tag !== 'string')
40
+ throw new TypeError('Open tag must be a string.');
41
+ this.#openTag = tag;
42
+ }
43
+ /** @param {string} tag - New close tag symbol to use (e.g., `']'`). */
44
+ setCloseTag(tag) {
45
+ if (typeof tag !== 'string')
46
+ throw new TypeError('Close tag must be a string.');
47
+ this.#closeTag = tag;
48
+ }
49
+ /**
50
+ * Ensures the element has focus.
51
+ * @returns {TinyTextRangeEditor}
52
+ */
53
+ ensureFocus() {
54
+ if (document.activeElement !== this.#el)
55
+ this.#el.focus();
56
+ return this;
57
+ }
58
+ /**
59
+ * Focus the element.
60
+ * @returns {TinyTextRangeEditor}
61
+ */
62
+ focus() {
63
+ this.#el.focus();
64
+ return this;
65
+ }
66
+ /** @returns {{ start: number, end: number }} The current selection range. */
67
+ getSelectionRange() {
68
+ return {
69
+ start: this.#el.selectionStart ?? NaN,
70
+ end: this.#el.selectionEnd ?? NaN,
71
+ };
72
+ }
73
+ /**
74
+ * Sets the current selection range.
75
+ * @param {number} start - Start index.
76
+ * @param {number} end - End index.
77
+ * @param {boolean} [preserveScroll=true] - Whether to preserve scroll position.
78
+ * @returns {TinyTextRangeEditor}
79
+ */
80
+ setSelectionRange(start, end, preserveScroll = true) {
81
+ if (typeof start !== 'number' || typeof end !== 'number')
82
+ throw new TypeError('start and end must be numbers.');
83
+ if (typeof preserveScroll !== 'boolean')
84
+ throw new TypeError('preserveScroll must be a boolean.');
85
+ const scrollTop = this.#el.scrollTop;
86
+ const scrollLeft = this.#el.scrollLeft;
87
+ this.#el.setSelectionRange(start, end);
88
+ if (preserveScroll) {
89
+ this.#el.scrollTop = scrollTop;
90
+ this.#el.scrollLeft = scrollLeft;
91
+ }
92
+ return this;
93
+ }
94
+ /** @returns {string} The full current text value. */
95
+ getValue() {
96
+ return this.#el.value;
97
+ }
98
+ /**
99
+ * Sets the full value of the element.
100
+ * @param {string} value - The new value to assign.
101
+ * @returns {TinyTextRangeEditor}
102
+ */
103
+ setValue(value) {
104
+ if (typeof value !== 'string')
105
+ throw new TypeError('Value must be a string.');
106
+ this.#el.value = value;
107
+ return this;
108
+ }
109
+ /** @returns {string} The currently selected text. */
110
+ getSelectedText() {
111
+ const { start, end } = this.getSelectionRange();
112
+ return this.#el.value.slice(start, end);
113
+ }
114
+ /**
115
+ * Inserts text at the current selection, replacing any selected content.
116
+ * @param {string} text - The text to insert.
117
+ * @param {Object} [settings={}] - Optional auto-spacing behavior.
118
+ * @param {'start' | 'end' | 'preserve'} [settings.newCursor='end'] - Controls caret position after insertion.
119
+ * @param {boolean} [settings.autoSpacing=false]
120
+ * @param {boolean} [settings.autoSpaceLeft=false]
121
+ * @param {boolean} [settings.autoSpaceRight=false]
122
+ * @returns {TinyTextRangeEditor}
123
+ */
124
+ insertText(text, { newCursor = 'end', autoSpacing = false, autoSpaceLeft = autoSpacing, autoSpaceRight = autoSpacing, } = {}) {
125
+ if (typeof text !== 'string')
126
+ throw new TypeError('Text must be a string.');
127
+ if (!['start', 'end', 'preserve'].includes(newCursor))
128
+ throw new TypeError("newCursor must be one of 'start', 'end', or 'preserve'.");
129
+ if (typeof autoSpacing !== 'boolean')
130
+ throw new TypeError('autoSpacing must be a boolean.');
131
+ if (typeof autoSpaceLeft !== 'boolean')
132
+ throw new TypeError('autoSpaceLeft must be a boolean.');
133
+ if (typeof autoSpaceRight !== 'boolean')
134
+ throw new TypeError('autoSpaceRight must be a boolean.');
135
+ const { start, end } = this.getSelectionRange();
136
+ const value = this.#el.value;
137
+ const leftChar = value[start - 1] || '';
138
+ const rightChar = value[end] || '';
139
+ const addLeft = autoSpaceLeft && leftChar && !/\s/.test(leftChar);
140
+ const addRight = autoSpaceRight && rightChar && !/\s/.test(rightChar);
141
+ const finalText = `${addLeft ? ' ' : ''}${text}${addRight ? ' ' : ''}`;
142
+ const newValue = value.slice(0, start) + finalText + value.slice(end);
143
+ this.setValue(newValue);
144
+ let cursorPos = start;
145
+ if (newCursor === 'end')
146
+ cursorPos = start + finalText.length;
147
+ else if (newCursor === 'preserve')
148
+ cursorPos = start;
149
+ this.setSelectionRange(cursorPos, cursorPos);
150
+ return this;
151
+ }
152
+ /**
153
+ * Deletes the currently selected text.
154
+ * @returns {TinyTextRangeEditor}
155
+ */
156
+ deleteSelection() {
157
+ this.insertText('');
158
+ return this;
159
+ }
160
+ /**
161
+ * Replaces the selection using a transformation function.
162
+ * @param {(selected: string) => string} transformer - Function that modifies the selected text.
163
+ * @returns {TinyTextRangeEditor}
164
+ */
165
+ transformSelection(transformer) {
166
+ if (typeof transformer !== 'function')
167
+ throw new TypeError('transformer must be a function.');
168
+ const { start } = this.getSelectionRange();
169
+ const selected = this.getSelectedText();
170
+ const transformed = transformer(selected);
171
+ this.insertText(transformed);
172
+ this.setSelectionRange(start, start + transformed.length);
173
+ return this;
174
+ }
175
+ /**
176
+ * Surrounds current selection with prefix and suffix.
177
+ * @param {string} prefix - Text to insert before.
178
+ * @param {string} suffix - Text to insert after.
179
+ * @returns {TinyTextRangeEditor}
180
+ */
181
+ surroundSelection(prefix, suffix) {
182
+ if (typeof prefix !== 'string' || typeof suffix !== 'string')
183
+ throw new TypeError('prefix and suffix must be strings.');
184
+ const selected = this.getSelectedText();
185
+ this.insertText(`${prefix}${selected}${suffix}`);
186
+ return this;
187
+ }
188
+ /**
189
+ * Moves the caret by a given offset.
190
+ * @param {number} offset - Characters to move.
191
+ * @returns {TinyTextRangeEditor}
192
+ */
193
+ moveCaret(offset) {
194
+ if (typeof offset !== 'number')
195
+ throw new TypeError('offset must be a number.');
196
+ const { start } = this.getSelectionRange();
197
+ const pos = Math.max(0, start + offset);
198
+ this.setSelectionRange(pos, pos);
199
+ return this;
200
+ }
201
+ /**
202
+ * Selects all content in the field.
203
+ * @returns {TinyTextRangeEditor}
204
+ */
205
+ selectAll() {
206
+ this.setSelectionRange(0, this.#el.value.length);
207
+ return this;
208
+ }
209
+ /**
210
+ * Expands the current selection by character amounts.
211
+ * @param {number} before - Characters to expand to the left.
212
+ * @param {number} after - Characters to expand to the right.
213
+ * @returns {TinyTextRangeEditor}
214
+ */
215
+ expandSelection(before, after) {
216
+ if (typeof before !== 'number' || typeof after !== 'number')
217
+ throw new TypeError('before and after must be numbers.');
218
+ const { start, end } = this.getSelectionRange();
219
+ const newStart = Math.max(0, start - before);
220
+ const newEnd = Math.min(this.#el.value.length, end + after);
221
+ this.setSelectionRange(newStart, newEnd);
222
+ return this;
223
+ }
224
+ /**
225
+ * Replaces all regex matches in the content.
226
+ * @param {RegExp} regex - Regex to match.
227
+ * @param {(match: string) => string} replacer - Replacement function.
228
+ * @returns {TinyTextRangeEditor}
229
+ */
230
+ replaceAll(regex, replacer) {
231
+ if (!(regex instanceof RegExp))
232
+ throw new TypeError('regex must be a RegExp.');
233
+ if (typeof replacer !== 'function')
234
+ throw new TypeError('replacer must be a function.');
235
+ const newValue = this.#el.value.replace(regex, replacer);
236
+ this.setValue(newValue);
237
+ return this;
238
+ }
239
+ /**
240
+ * Replaces all regex matches within the currently selected text.
241
+ *
242
+ * @param {RegExp} regex - Regular expression to match inside selection.
243
+ * @param {(match: string) => string} replacer - Function to replace each match.
244
+ * @returns {TinyTextRangeEditor}
245
+ */
246
+ replaceInSelection(regex, replacer) {
247
+ if (!(regex instanceof RegExp))
248
+ throw new TypeError('regex must be a RegExp.');
249
+ if (typeof replacer !== 'function')
250
+ throw new TypeError('replacer must be a function.');
251
+ const { start, end } = this.getSelectionRange();
252
+ const original = this.#el.value;
253
+ const selected = original.slice(start, end);
254
+ const replaced = selected.replace(regex, replacer);
255
+ const updated = original.slice(0, start) + replaced + original.slice(end);
256
+ this.setValue(updated);
257
+ this.setSelectionRange(start, start + replaced.length);
258
+ return this;
259
+ }
260
+ /**
261
+ * Toggles a code around the current selection.
262
+ * If it's already wrapped, unwraps it.
263
+ * @param {string} codeName - The code to toggle.
264
+ * @returns {TinyTextRangeEditor}
265
+ */
266
+ toggleCode(codeName) {
267
+ if (typeof codeName !== 'string')
268
+ throw new TypeError('codeName must be a string.');
269
+ const selected = this.getSelectedText();
270
+ if (selected.startsWith(codeName) && selected.endsWith(codeName)) {
271
+ const unwrapped = selected.slice(codeName.length, selected.length - codeName.length);
272
+ this.insertText(unwrapped);
273
+ }
274
+ else {
275
+ this.insertText(`${codeName}${selected}${codeName}`);
276
+ }
277
+ return this;
278
+ }
279
+ /**
280
+ * Converts a list of attributes into a string suitable for tag insertion.
281
+ *
282
+ * This method supports both standard key-value attribute objects (e.g., `{ key: "value" }`)
283
+ * and boolean-style attribute arrays (e.g., `[ "disabled", "autofocus" ]`).
284
+ *
285
+ * - Attributes passed as an array will render as boolean attributes (e.g., `disabled autofocus`)
286
+ * - Attributes passed as an object will render as `key="value"` pairs (or just `key` if the value is an empty string)
287
+ *
288
+ * @param {Record<string, string> | string[]} attributes - The attributes to serialize into a tag string.
289
+ * - If an array: treated as a list of boolean-style attributes.
290
+ * - If an object: treated as key-value pairs.
291
+ *
292
+ * @throws {TypeError} If the array contains non-strings, or the object contains non-string values.
293
+ * @returns {string} A string of serialized attributes for use inside a tag.
294
+ *
295
+ * @example
296
+ * // Using object attributes
297
+ * _insertAttr({ size: "12", color: "red" });
298
+ * // Returns: 'size="12" color="red"'
299
+ *
300
+ * @example
301
+ * // Using boolean attributes
302
+ * _insertAttr(["disabled", "autofocus"]);
303
+ * // Returns: 'disabled autofocus'
304
+ *
305
+ * @example
306
+ * // Using mixed/empty object values
307
+ * _insertAttr({ checked: "", class: "btn" });
308
+ * // Returns: 'checked class="btn"'
309
+ */
310
+ _insertAttr(attributes) {
311
+ // Reuse attribute logic
312
+ let attrStr = '';
313
+ if (Array.isArray(attributes)) {
314
+ // string[]
315
+ if (!attributes.every((attr) => typeof attr === 'string'))
316
+ throw new TypeError('All entries in attributes array must be strings.');
317
+ attrStr = attributes.map((attr) => `${attr}`).join(' ');
318
+ }
319
+ else if (typeof attributes === 'object' && attributes !== null) {
320
+ // Record<string, string>
321
+ attrStr = Object.entries(attributes)
322
+ .map(([key, val]) => {
323
+ if (typeof val !== 'string')
324
+ throw new TypeError('All entries in attributes object must be strings.');
325
+ return `${key}${val.length > 0 ? `="${val}"` : ''}`;
326
+ })
327
+ .join(' ');
328
+ }
329
+ else {
330
+ throw new TypeError('attributes must be an object or an array of strings.');
331
+ }
332
+ return attrStr;
333
+ }
334
+ /**
335
+ * Wraps the current selection with a tag, optionally including attributes.
336
+ *
337
+ * @param {string} tagName - The tag name (e.g., `b`, `color`, etc.).
338
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes for the opening tag.
339
+ * - If an object: key-value pairs (e.g., `{ color: "red" }` โ†’ `color="red"`).
340
+ * - If an array: boolean attributes (e.g., `["disabled", "readonly"]`).
341
+ *
342
+ * @returns {TinyTextRangeEditor}
343
+ */
344
+ wrapWithTag(tagName, attributes = {}) {
345
+ if (typeof tagName !== 'string')
346
+ throw new TypeError('tagName must be a string.');
347
+ const attrStr = this._insertAttr(attributes);
348
+ const openTag = attrStr
349
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
350
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
351
+ const closeTag = `${this.#openTag}/${tagName}${this.#closeTag}`;
352
+ this.surroundSelection(openTag, closeTag);
353
+ return this;
354
+ }
355
+ /**
356
+ * Inserts a tag with optional inner content.
357
+ * @param {string} tagName - The tag to insert.
358
+ * @param {string} [content=''] - Optional content between tags.
359
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
360
+ * @returns {TinyTextRangeEditor}
361
+ */
362
+ insertTag(tagName, content = '', attributes = {}) {
363
+ if (typeof tagName !== 'string')
364
+ throw new TypeError('tagName must be a string.');
365
+ if (typeof content !== 'string')
366
+ throw new TypeError('content must be a string.');
367
+ const attrStr = this._insertAttr(attributes);
368
+ const open = attrStr
369
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
370
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
371
+ const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
372
+ this.insertText(`${open}${content}${close}`);
373
+ return this;
374
+ }
375
+ /**
376
+ * Inserts a self-closing tag.
377
+ * @param {string} tagName - The tag name.
378
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes or list of empty attributes.
379
+ * @returns {TinyTextRangeEditor}
380
+ */
381
+ insertSelfClosingTag(tagName, attributes = {}) {
382
+ if (typeof tagName !== 'string')
383
+ throw new TypeError('tagName must be a string.');
384
+ const attrStr = this._insertAttr(attributes);
385
+ const tag = attrStr
386
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
387
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
388
+ this.insertText(tag);
389
+ return this;
390
+ }
391
+ /**
392
+ * Toggles a tag around the current selection.
393
+ * Supports tags with attributes. If already wrapped, it unwraps.
394
+ * @param {string} tagName - The tag to toggle.
395
+ * @param {Record<string,string> | string[]} [attributes={}] - Optional attributes to apply when wrapping.
396
+ * @returns {TinyTextRangeEditor}
397
+ */
398
+ toggleTag(tagName, attributes = {}) {
399
+ if (typeof tagName !== 'string')
400
+ throw new TypeError('tagName must be a string.');
401
+ const selected = this.getSelectedText();
402
+ // Regex: opening tag with optional attributes, and closing tag
403
+ const openRegex = new RegExp(`^\\[${tagName}(\\s+[^\\]]*)?\\]`);
404
+ const closeRegex = new RegExp(`\\[/${tagName}\\]$`);
405
+ const hasOpen = openRegex.test(selected);
406
+ const hasClose = closeRegex.test(selected);
407
+ if (hasOpen && hasClose) {
408
+ const unwrapped = selected
409
+ .replace(openRegex, '') // remove opening tag
410
+ .replace(closeRegex, ''); // remove closing tag
411
+ this.insertText(unwrapped);
412
+ }
413
+ else {
414
+ const attrStr = this._insertAttr(attributes);
415
+ const open = attrStr
416
+ ? `${this.#openTag}${tagName} ${attrStr}${this.#closeTag}`
417
+ : `${this.#openTag}${tagName}${this.#closeTag}`;
418
+ const close = `${this.#openTag}/${tagName}${this.#closeTag}`;
419
+ this.insertText(`${open}${selected}${close}`);
420
+ }
421
+ return this;
422
+ }
423
+ }
424
+ export default TinyTextRangeEditor;
package/docs/v1/README.md CHANGED
@@ -36,7 +36,9 @@ This folder contains the core scripts we have worked on so far. Each file is a m
36
36
  - ๐Ÿ“ฃ **[TinyNotifications](./libs/TinyNotifications.md)** โ€” A browser notification utility with sound support, permission management, truncation logic, default icons, and enforced validation to ensure safe and predictable usage.
37
37
  - ๐Ÿงฑ **[TinyHtml](./libs/TinyHtml.md)** โ€” A minimalist DOM utility class that offers jQuery-like methods in pure JavaScript for querying, styling, traversing, event handling, collision detection, and visibility logic โ€” all in a lightweight and chainable interface.
38
38
  - ๐ŸŒ€ **[TinySmartScroller](./libs/TinySmartScroller.md)** โ€” A smart scroll monitor that detects user scroll behavior, visibility changes, element sizes, and automatically handles scroll preservation, bottom detection, debounce, and more.
39
+ - ๐Ÿ“‹ **[TinyClipboard](./libs/TinyClipboard.md)** โ€” A clipboard management utility with support for modern APIs, legacy fallbacks, and custom copy handlers for text and blobs, plus flexible read operations and clipboard item filtering.
39
40
  - ๐Ÿฎ **[UltraRandomMsgGen](./libs/UltraRandomMsgGen.md)** โ€” A whimsical random message generator using grammar templates, word sets, emojis, and chaotic modes to craft playful text outputs.
41
+ - โœ๏ธ **[TinyTextRangeEditor](./libs/TinyTextRangeEditor.md)** โ€” An flexible text range manipulation utility for `input` and `textarea` elements. Supports selection, cursor control, tag insertion, attribute handling, inline editing, formatting, and advanced wrap/toggle logic with optional spacing auto-completion.
40
42
 
41
43
  ### 3. **`fileManager/`**
42
44
  * ๐Ÿ“ **[Main](./fileManager/main.md)** โ€” A Node.js file/directory utility module with support for JSON, backups, renaming, size analysis, and more.
@@ -0,0 +1,213 @@
1
+ # ๐Ÿ“‹ TinyClipboard Documentation
2
+
3
+ A lightweight utility class for clipboard interactions with support for modern Clipboard API, custom overrides, and legacy fallback methods like `execCommand`.
4
+
5
+ ## ๐Ÿš€ Features
6
+
7
+ * โœ… Supports copying and reading **plain text**
8
+ * โœ… Supports copying and reading **binary data (Blob)**
9
+ * โœ… Automatically detects support for modern Clipboard API
10
+ * โœ… Fallback for legacy `document.execCommand()`
11
+ * โœ… Allows **custom clipboard handler overrides** for Electron, Capacitor, etc.
12
+
13
+ ---
14
+
15
+ ## ๐Ÿ—๏ธ Class: `TinyClipboard`
16
+
17
+ ### ๐Ÿ”ง Constructor
18
+
19
+ ```ts
20
+ new TinyClipboard()
21
+ ```
22
+
23
+ Initializes the clipboard utility and detects available clipboard APIs.
24
+
25
+ ---
26
+
27
+ ## โœ๏ธ Copy Methods
28
+
29
+ ### ๐Ÿ“„ `copyText(text: string): Promise<void>`
30
+
31
+ Copies a plain text string to the clipboard.
32
+
33
+ #### Parameters:
34
+
35
+ * `text` โ€“ The string to copy.
36
+
37
+ #### Throws:
38
+
39
+ * `TypeError` if the input is not a string.
40
+ * `Error` if no clipboard method is available.
41
+
42
+ ---
43
+
44
+ ### ๐Ÿงฉ `copyBlob(blob: Blob): Promise<void>`
45
+
46
+ Copies binary data (e.g., images, files) to the clipboard.
47
+
48
+ #### Parameters:
49
+
50
+ * `blob` โ€“ The Blob object to copy.
51
+
52
+ #### Throws:
53
+
54
+ * `TypeError` if the input is not a Blob.
55
+ * `Error` if clipboard API is unavailable.
56
+
57
+ ---
58
+
59
+ ## ๐Ÿง  Override Methods
60
+
61
+ ### ๐Ÿ› ๏ธ `setCopyText(callback: (text: string) => Promise<void>)`
62
+
63
+ Overrides the default text copy handler (e.g., for Electron or Capacitor).
64
+
65
+ #### Throws:
66
+
67
+ * `TypeError` if the callback is not a function.
68
+
69
+ ---
70
+
71
+ ### ๐Ÿ› ๏ธ `setCopyBlob(callback: (blob: Blob) => Promise<void>)`
72
+
73
+ Overrides the default Blob copy handler.
74
+
75
+ #### Throws:
76
+
77
+ * `TypeError` if the callback is not a function.
78
+
79
+ ---
80
+
81
+ ## ๐Ÿ“ฅ Read Methods
82
+
83
+ ### ๐Ÿงพ `readText(index?: number = 0): Promise<string|null>`
84
+
85
+ Reads plain text from a specific clipboard index.
86
+
87
+ #### Throws:
88
+
89
+ * `Error` if the result is not a string.
90
+
91
+ ---
92
+
93
+ ### ๐Ÿ–ผ๏ธ `readCustom(mimeFormat?: string, fixValue?: boolean, index?: number): Promise<Blob|null>`
94
+
95
+ Reads a custom MIME-type item from the clipboard.
96
+
97
+ #### Parameters:
98
+
99
+ * `mimeFormat` โ€“ MIME type or prefix (e.g., `"image/"`)
100
+ * `fixValue` โ€“ Whether to match the MIME type exactly
101
+ * `index` โ€“ The clipboard item index
102
+
103
+ #### Throws:
104
+
105
+ * `Error` if the result is not a Blob.
106
+
107
+ ---
108
+
109
+ ### ๐Ÿ“š `readAllTexts(): Promise<string[]>`
110
+
111
+ Reads all plain text entries from the clipboard.
112
+
113
+ #### Throws:
114
+
115
+ * `Error` if any entry is not a string.
116
+
117
+ ---
118
+
119
+ ### ๐Ÿงณ `readAllCustom(mimeFormat?: string, fixValue?: boolean): Promise<Blob[]>`
120
+
121
+ Reads all custom MIME-type items from the clipboard.
122
+
123
+ #### Throws:
124
+
125
+ * `Error` if any entry is not a Blob.
126
+
127
+ ---
128
+
129
+ ### ๐Ÿ”Ž `readAllData(type?: 'text' | 'custom', mimeFormat?: string): Promise<Array<string | Blob>>`
130
+
131
+ Reads all clipboard content filtered by type or MIME.
132
+
133
+ #### Returns:
134
+
135
+ * An array of strings or Blobs.
136
+
137
+ ---
138
+
139
+ ### ๐Ÿ”ข `readIndex(index: number): Promise<ClipboardItem|null>`
140
+
141
+ Reads a single clipboard item by index.
142
+
143
+ ---
144
+
145
+ ### ๐Ÿ—ƒ๏ธ `readAll(): Promise<ClipboardItem[]>`
146
+
147
+ Reads all clipboard items.
148
+
149
+ #### Throws:
150
+
151
+ * `Error` if the returned items are not all valid `ClipboardItem` instances.
152
+
153
+ ---
154
+
155
+ ## ๐Ÿงช Internal (Private) Helpers
156
+
157
+ > These methods are not intended for external use.
158
+
159
+ * `_handleBlob(type: string, item: ClipboardItem): Promise<Blob>`
160
+ * `_handleText(type: string, item: ClipboardItem): Promise<string>`
161
+ * `_read(index: number|null): Promise<ClipboardItem|ClipboardItem[]>`
162
+ * `_readData(...)`: Internal filter mechanism used by public `read*` methods.
163
+
164
+ ---
165
+
166
+ ## โœ… Getters
167
+
168
+ ### ๐Ÿ“Ž `isExecCommandAvailable(): boolean`
169
+
170
+ Returns `true` if legacy `document.execCommand()` is available.
171
+
172
+ ---
173
+
174
+ ### ๐Ÿ“Ž `isNavigatorClipboardAvailable(): boolean`
175
+
176
+ Returns `true` if modern `navigator.clipboard` is available.
177
+
178
+ ---
179
+
180
+ ### ๐Ÿงช `getCopyTextFunc(): ((text: string) => Promise<void>) | null`
181
+
182
+ Gets the current function used for copying text.
183
+
184
+ ---
185
+
186
+ ### ๐Ÿงช `getCopyBlobFunc(): ((blob: Blob) => Promise<void>) | null`
187
+
188
+ Gets the current function used for copying blobs.
189
+
190
+ ---
191
+
192
+ ## ๐Ÿง  Usage Example
193
+
194
+ ```ts
195
+ import TinyClipboard from './TinyClipboard.js';
196
+
197
+ const clipboard = new TinyClipboard();
198
+
199
+ // Copy text
200
+ await clipboard.copyText("Hello world!");
201
+
202
+ // Read text
203
+ const value = await clipboard.readText();
204
+ console.log(value);
205
+ ```
206
+
207
+ ---
208
+
209
+ ## ๐Ÿ”’ Notes
210
+
211
+ * Browser permission is required to access the clipboard.
212
+ * Clipboard access may be restricted to user interactions (e.g., click events).
213
+ * This utility is designed for use in environments with **Clipboard API** support or suitable **fallbacks**.