tiny-essentials 1.17.1 โ†’ 1.18.1

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 (60) hide show
  1. package/dist/legacy/get/countObj.cjs +2 -2
  2. package/dist/legacy/get/countObj.d.mts +1 -1
  3. package/dist/legacy/get/countObj.mjs +1 -1
  4. package/dist/legacy/index.cjs +2 -1
  5. package/dist/v1/TinyBasicsEs.js +559 -411
  6. package/dist/v1/TinyBasicsEs.min.js +1 -1
  7. package/dist/v1/TinyClipboard.js +459 -0
  8. package/dist/v1/TinyClipboard.min.js +1 -0
  9. package/dist/v1/TinyDragger.js +170 -2454
  10. package/dist/v1/TinyDragger.min.js +1 -2
  11. package/dist/v1/TinyEssentials.js +1093 -63
  12. package/dist/v1/TinyEssentials.min.js +1 -1
  13. package/dist/v1/TinyHtml.js +164 -21
  14. package/dist/v1/TinyHtml.min.js +1 -1
  15. package/dist/v1/TinySmartScroller.js +164 -21
  16. package/dist/v1/TinySmartScroller.min.js +1 -1
  17. package/dist/v1/TinyTextRangeEditor.js +497 -0
  18. package/dist/v1/TinyTextRangeEditor.min.js +1 -0
  19. package/dist/v1/TinyUploadClicker.js +219 -467
  20. package/dist/v1/TinyUploadClicker.min.js +1 -1
  21. package/dist/v1/basics/html.cjs +3 -3
  22. package/dist/v1/basics/html.mjs +1 -1
  23. package/dist/v1/basics/index.cjs +3 -2
  24. package/dist/v1/basics/index.d.mts +2 -2
  25. package/dist/v1/basics/index.mjs +2 -1
  26. package/dist/v1/basics/objChecker.cjs +46 -0
  27. package/dist/v1/basics/objChecker.d.mts +29 -0
  28. package/dist/v1/basics/objChecker.mjs +45 -0
  29. package/dist/v1/basics/objFilter.cjs +4 -45
  30. package/dist/v1/basics/objFilter.d.mts +3 -28
  31. package/dist/v1/basics/objFilter.mjs +2 -45
  32. package/dist/v1/build/TinyClipboard.cjs +7 -0
  33. package/dist/v1/build/TinyClipboard.d.mts +3 -0
  34. package/dist/v1/build/TinyClipboard.mjs +2 -0
  35. package/dist/v1/build/TinyTextRangeEditor.cjs +7 -0
  36. package/dist/v1/build/TinyTextRangeEditor.d.mts +3 -0
  37. package/dist/v1/build/TinyTextRangeEditor.mjs +2 -0
  38. package/dist/v1/index.cjs +7 -2
  39. package/dist/v1/index.d.mts +5 -3
  40. package/dist/v1/index.mjs +5 -2
  41. package/dist/v1/libs/TinyClipboard.cjs +420 -0
  42. package/dist/v1/libs/TinyClipboard.d.mts +155 -0
  43. package/dist/v1/libs/TinyClipboard.mjs +398 -0
  44. package/dist/v1/libs/TinyDragger.cjs +3 -3
  45. package/dist/v1/libs/TinyDragger.mjs +1 -1
  46. package/dist/v1/libs/TinyHtml.cjs +164 -21
  47. package/dist/v1/libs/TinyHtml.d.mts +150 -27
  48. package/dist/v1/libs/TinyHtml.mjs +158 -20
  49. package/dist/v1/libs/TinyTextRangeEditor.cjs +458 -0
  50. package/dist/v1/libs/TinyTextRangeEditor.d.mts +200 -0
  51. package/dist/v1/libs/TinyTextRangeEditor.mjs +424 -0
  52. package/dist/v1/libs/TinyUploadClicker.cjs +5 -4
  53. package/docs/v1/README.md +3 -0
  54. package/docs/v1/basics/objChecker.md +47 -0
  55. package/docs/v1/basics/objFilter.md +0 -40
  56. package/docs/v1/libs/TinyClipboard.md +213 -0
  57. package/docs/v1/libs/TinyHtml.md +112 -15
  58. package/docs/v1/libs/TinyTextRangeEditor.md +208 -0
  59. package/package.json +1 -1
  60. package/dist/v1/TinyDragger.min.js.LICENSE.txt +0 -8
@@ -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;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var objFilter = require('../basics/objFilter.cjs');
3
+ require('../basics/objFilter.cjs');
4
+ var objChecker = require('../basics/objChecker.cjs');
4
5
  require('./TinyHtml.cjs');
5
6
  require('fs');
6
7
  require('path');
@@ -79,7 +80,7 @@ class TinyUploadClicker {
79
80
  * @throws {TypeError} If the config is invalid or required options are missing.
80
81
  */
81
82
  constructor(options) {
82
- if (!objFilter.isJsonObject(options))
83
+ if (!objChecker.isJsonObject(options))
83
84
  throw new TypeError('TinyUploadClicker: "options" must be a valid object.');
84
85
 
85
86
  this.#config = {
@@ -145,10 +146,10 @@ class TinyUploadClicker {
145
146
  )
146
147
  throw new TypeError('TinyUploadClicker: "onFileLoad" must be a function or null.');
147
148
 
148
- if (options.inputAttributes !== undefined && !objFilter.isJsonObject(options.inputAttributes))
149
+ if (options.inputAttributes !== undefined && !objChecker.isJsonObject(options.inputAttributes))
149
150
  throw new TypeError('TinyUploadClicker: "inputAttributes" must be an object.');
150
151
 
151
- if (options.inputStyles !== undefined && !objFilter.isJsonObject(options.inputStyles))
152
+ if (options.inputStyles !== undefined && !objChecker.isJsonObject(options.inputStyles))
152
153
  throw new TypeError('TinyUploadClicker: "inputStyles" must be an object.');
153
154
 
154
155
  this.#boundClick = this.#handleClick.bind(this);
package/docs/v1/README.md CHANGED
@@ -15,6 +15,7 @@ This folder contains the core scripts we have worked on so far. Each file is a m
15
15
  - ๐Ÿ“ฆ **[Array](./basics/array.md)** โ€” A tiny utility for shuffling arrays using the Fisherโ€“Yates algorithm.
16
16
  - โฐ **[Clock](./basics/clock.md)** โ€” A versatile time utility module for calculating and formatting time durations.
17
17
  - ๐Ÿง  **[ObjFilter](./basics/objFilter.md)** โ€” Type detection, extension, and analysis made easy with simple and extensible type validation.
18
+ * ๐Ÿงฎ **[objChecker](./basics/objChecker.md)** โ€” Utilities for counting keys in objects or arrays and for safely detecting plain JSON-compatible objects.
18
19
  - ๐Ÿ”ข **[SimpleMath](./basics/simpleMath.md)** โ€” A collection of simple math utilities for calculations like the Rule of Three and percentages.
19
20
  - โœ๏ธ **[Text](./basics/text.md)** โ€” A utility for transforming text into title case formats, with multiple options for capitalization.
20
21
  - ๐Ÿ”„ **[AsyncReplace](./basics/asyncReplace.md)** โ€” Asynchronously replaces matches in a string using a regex and an async function.
@@ -36,7 +37,9 @@ This folder contains the core scripts we have worked on so far. Each file is a m
36
37
  - ๐Ÿ“ฃ **[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
38
  - ๐Ÿงฑ **[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
39
  - ๐ŸŒ€ **[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.
40
+ - ๐Ÿ“‹ **[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
41
  - ๐Ÿฎ **[UltraRandomMsgGen](./libs/UltraRandomMsgGen.md)** โ€” A whimsical random message generator using grammar templates, word sets, emojis, and chaotic modes to craft playful text outputs.
42
+ - โœ๏ธ **[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
43
 
41
44
  ### 3. **`fileManager/`**
42
45
  * ๐Ÿ“ **[Main](./fileManager/main.md)** โ€” A Node.js file/directory utility module with support for JSON, backups, renaming, size analysis, and more.
@@ -0,0 +1,47 @@
1
+ # ๐Ÿง  objChecker.mjs
2
+
3
+ ## ๐Ÿ“˜ Object & Type Utilities
4
+
5
+ This document introduces utility functions designed to help you safely and efficiently work with JavaScript objects and types. These helpers are particularly useful when dealing with dynamic values โ€” especially in situations where data might be coming from APIs, user input, or JSON files.
6
+
7
+ These tools are built to reduce boilerplate code and prevent common mistakes when handling unknown or loosely-typed values in JavaScript.
8
+
9
+ ---
10
+
11
+ ### ๐Ÿงฎ `countObj(obj)`
12
+
13
+ Returns the number of elements in an array or the number of keys in an object.
14
+
15
+ ```js
16
+ countObj([1, 2, 3]); // 3
17
+ countObj({ a: 1, b: 2 }); // 2
18
+ countObj('hi'); // 0
19
+ ```
20
+
21
+ ---
22
+
23
+ ### ๐Ÿงผ `isJsonObject(value)`
24
+
25
+ Check if a value is a **plain JSON-compatible object** โ€” meaning it's created via `{}` or `new Object()`, with a prototype of `Object.prototype`, and **not** a special object like `Date`, `Map`, `Array`, etc.
26
+
27
+ ```js
28
+ isJsonObject({}); // true
29
+ isJsonObject(Object.create({})); // true
30
+ isJsonObject(Object.create(Object.prototype)); // true
31
+ isJsonObject(Object.assign({}, { a: 1 })); // true
32
+ ```
33
+
34
+ ```js
35
+ isJsonObject([]); // false
36
+ isJsonObject(new Date()); // false
37
+ isJsonObject(new Map()); // false
38
+ isJsonObject(Object.create(null)); // false
39
+ ```
40
+
41
+ ๐Ÿ”’ This function ensures the object:
42
+
43
+ * is not `null`
44
+ * has `typeof === 'object'`
45
+ * is **directly** inherited from `Object.prototype`
46
+
47
+ Use this when you need to strictly validate a raw JSON object (like the output of `JSON.parse()` or manual object literals).
@@ -121,18 +121,6 @@ const currentOrder = cloneObjTypeOrder();
121
121
 
122
122
  ---
123
123
 
124
- ### ๐Ÿงฎ `countObj(obj)`
125
-
126
- Returns the number of elements in an array or the number of keys in an object.
127
-
128
- ```js
129
- countObj([1, 2, 3]); // 3
130
- countObj({ a: 1, b: 2 }); // 2
131
- countObj('hi'); // 0
132
- ```
133
-
134
- ---
135
-
136
124
  ## Supported Types
137
125
 
138
126
  Hereโ€™s a full list of supported type names (in their default order):
@@ -164,31 +152,3 @@ You can change this order or insert your own types with `extendObjType`.
164
152
  ### ๐Ÿ› ๏ธ `getCheckObj()`
165
153
 
166
154
  This function creates a clone of the functions from the `typeValidator` object. It returns a new object where the keys are the same and the values are the cloned functions.
167
-
168
- ---
169
-
170
- ### ๐Ÿงผ `isJsonObject(value)`
171
-
172
- Check if a value is a **plain JSON-compatible object** โ€” meaning it's created via `{}` or `new Object()`, with a prototype of `Object.prototype`, and **not** a special object like `Date`, `Map`, `Array`, etc.
173
-
174
- ```js
175
- isJsonObject({}); // true
176
- isJsonObject(Object.create({})); // true
177
- isJsonObject(Object.create(Object.prototype)); // true
178
- isJsonObject(Object.assign({}, { a: 1 })); // true
179
- ```
180
-
181
- ```js
182
- isJsonObject([]); // false
183
- isJsonObject(new Date()); // false
184
- isJsonObject(new Map()); // false
185
- isJsonObject(Object.create(null)); // false
186
- ```
187
-
188
- ๐Ÿ”’ This function ensures the object:
189
-
190
- * is not `null`
191
- * has `typeof === 'object'`
192
- * is **directly** inherited from `Object.prototype`
193
-
194
- Use this when you need to strictly validate a raw JSON object (like the output of `JSON.parse()` or manual object literals).