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.
- package/dist/legacy/get/countObj.cjs +2 -2
- package/dist/legacy/get/countObj.d.mts +1 -1
- package/dist/legacy/get/countObj.mjs +1 -1
- package/dist/legacy/index.cjs +2 -1
- package/dist/v1/TinyBasicsEs.js +559 -411
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyClipboard.js +459 -0
- package/dist/v1/TinyClipboard.min.js +1 -0
- package/dist/v1/TinyDragger.js +170 -2454
- package/dist/v1/TinyDragger.min.js +1 -2
- package/dist/v1/TinyEssentials.js +1093 -63
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyHtml.js +164 -21
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinySmartScroller.js +164 -21
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextRangeEditor.js +497 -0
- package/dist/v1/TinyTextRangeEditor.min.js +1 -0
- package/dist/v1/TinyUploadClicker.js +219 -467
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/basics/html.cjs +3 -3
- package/dist/v1/basics/html.mjs +1 -1
- package/dist/v1/basics/index.cjs +3 -2
- package/dist/v1/basics/index.d.mts +2 -2
- package/dist/v1/basics/index.mjs +2 -1
- package/dist/v1/basics/objChecker.cjs +46 -0
- package/dist/v1/basics/objChecker.d.mts +29 -0
- package/dist/v1/basics/objChecker.mjs +45 -0
- package/dist/v1/basics/objFilter.cjs +4 -45
- package/dist/v1/basics/objFilter.d.mts +3 -28
- package/dist/v1/basics/objFilter.mjs +2 -45
- package/dist/v1/build/TinyClipboard.cjs +7 -0
- package/dist/v1/build/TinyClipboard.d.mts +3 -0
- package/dist/v1/build/TinyClipboard.mjs +2 -0
- package/dist/v1/build/TinyTextRangeEditor.cjs +7 -0
- package/dist/v1/build/TinyTextRangeEditor.d.mts +3 -0
- package/dist/v1/build/TinyTextRangeEditor.mjs +2 -0
- package/dist/v1/index.cjs +7 -2
- package/dist/v1/index.d.mts +5 -3
- package/dist/v1/index.mjs +5 -2
- package/dist/v1/libs/TinyClipboard.cjs +420 -0
- package/dist/v1/libs/TinyClipboard.d.mts +155 -0
- package/dist/v1/libs/TinyClipboard.mjs +398 -0
- package/dist/v1/libs/TinyDragger.cjs +3 -3
- package/dist/v1/libs/TinyDragger.mjs +1 -1
- package/dist/v1/libs/TinyHtml.cjs +164 -21
- package/dist/v1/libs/TinyHtml.d.mts +150 -27
- package/dist/v1/libs/TinyHtml.mjs +158 -20
- package/dist/v1/libs/TinyTextRangeEditor.cjs +458 -0
- package/dist/v1/libs/TinyTextRangeEditor.d.mts +200 -0
- package/dist/v1/libs/TinyTextRangeEditor.mjs +424 -0
- package/dist/v1/libs/TinyUploadClicker.cjs +5 -4
- package/docs/v1/README.md +3 -0
- package/docs/v1/basics/objChecker.md +47 -0
- package/docs/v1/basics/objFilter.md +0 -40
- package/docs/v1/libs/TinyClipboard.md +213 -0
- package/docs/v1/libs/TinyHtml.md +112 -15
- package/docs/v1/libs/TinyTextRangeEditor.md +208 -0
- package/package.json +1 -1
- 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
|
-
|
|
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 (!
|
|
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 && !
|
|
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 && !
|
|
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).
|