tiny-essentials 1.18.1 → 1.19.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/README.md +17 -3
- package/dist/node_modules/firebase-functions/lib/common/trace.cjs +0 -1
- package/dist/node_modules/firebase-functions/lib/logger/index.cjs +1 -0
- package/dist/v1/ColorSafeStringify.min.js +1 -1
- package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
- package/dist/v1/TinyBasicsEs.js +13 -6
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyClipboard.min.js +1 -1
- package/dist/v1/TinyColorConverter.js +617 -0
- package/dist/v1/TinyColorConverter.min.js +1 -0
- package/dist/v1/TinyDomReadyManager.min.js +1 -1
- package/dist/v1/TinyDragger.min.js +1 -1
- package/dist/v1/TinyEssentials.js +2635 -482
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyEvents.js +402 -0
- package/dist/v1/TinyEvents.min.js +1 -0
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinyLocalStorage.js +1292 -0
- package/dist/v1/TinyLocalStorage.min.js +1 -0
- package/dist/v1/TinyNotifications.min.js +1 -1
- package/dist/v1/TinyNotifyCenter.min.js +1 -1
- package/dist/v1/TinyPromiseQueue.min.js +1 -1
- package/dist/v1/TinyRateLimiter.js +2 -1
- package/dist/v1/TinyRateLimiter.min.js +1 -1
- package/dist/v1/TinySmartScroller.js +570 -52
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextRangeEditor.min.js +1 -1
- package/dist/v1/TinyTimeout.js +233 -0
- package/dist/v1/TinyTimeout.min.js +1 -0
- package/dist/v1/TinyToastNotify.min.js +1 -1
- package/dist/v1/TinyUploadClicker.js +1457 -106
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/UltraRandomMsgGen.min.js +1 -1
- package/dist/v1/basics/html.cjs +13 -6
- package/dist/v1/basics/html.d.mts +12 -4
- package/dist/v1/basics/html.mjs +13 -6
- package/dist/v1/build/TinyColorConverter.cjs +7 -0
- package/dist/v1/build/TinyColorConverter.d.mts +3 -0
- package/dist/v1/build/TinyColorConverter.mjs +2 -0
- package/dist/v1/build/TinyEvents.cjs +7 -0
- package/dist/v1/build/TinyEvents.d.mts +3 -0
- package/dist/v1/build/TinyEvents.mjs +2 -0
- package/dist/v1/build/TinyLocalStorage.cjs +7 -0
- package/dist/v1/build/TinyLocalStorage.d.mts +3 -0
- package/dist/v1/build/TinyLocalStorage.mjs +2 -0
- package/dist/v1/build/TinyTimeout.cjs +7 -0
- package/dist/v1/build/TinyTimeout.d.mts +3 -0
- package/dist/v1/build/TinyTimeout.mjs +2 -0
- package/dist/v1/index.cjs +8 -0
- package/dist/v1/index.d.mts +5 -1
- package/dist/v1/index.mjs +5 -1
- package/dist/v1/libs/TinyColorConverter.cjs +578 -0
- package/dist/v1/libs/TinyColorConverter.d.mts +396 -0
- package/dist/v1/libs/TinyColorConverter.mjs +520 -0
- package/dist/v1/libs/TinyEvents.cjs +363 -0
- package/dist/v1/libs/TinyEvents.d.mts +160 -0
- package/dist/v1/libs/TinyEvents.mjs +328 -0
- package/dist/v1/libs/TinyLocalStorage.cjs +847 -0
- package/dist/v1/libs/TinyLocalStorage.d.mts +407 -0
- package/dist/v1/libs/TinyLocalStorage.mjs +740 -0
- package/dist/v1/libs/TinySmartScroller.cjs +207 -52
- package/dist/v1/libs/TinySmartScroller.d.mts +164 -16
- package/dist/v1/libs/TinySmartScroller.mjs +181 -52
- package/dist/v1/libs/TinyTimeout.cjs +194 -0
- package/dist/v1/libs/TinyTimeout.d.mts +89 -0
- package/dist/v1/libs/TinyTimeout.mjs +179 -0
- package/dist/v1/libs/TinyUploadClicker.cjs +1 -0
- package/docs/v1/README.md +4 -0
- package/docs/v1/libs/TinyColorConverter.md +220 -0
- package/docs/v1/libs/TinyEvents.md +199 -0
- package/docs/v1/libs/TinyLocalStorage.md +350 -0
- package/docs/v1/libs/TinyRateLimiter.md +0 -3
- package/docs/v1/libs/TinyTimeout.md +190 -0
- package/package.json +28 -5
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { isJsonObject } from '../basics/objChecker.mjs';
|
|
2
|
+
import TinyEvents from './TinyEvents.mjs';
|
|
3
|
+
/** @type {Map<any, EncodeFn>} */
|
|
4
|
+
const customEncoders = new Map();
|
|
5
|
+
/** @type {Map<any, DecodeFn>} */
|
|
6
|
+
const customDecoders = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* A function that encodes a value into a serializable JSON-compatible format.
|
|
9
|
+
*
|
|
10
|
+
* @callback EncodeFn
|
|
11
|
+
* @param {any} value - The value to encode.
|
|
12
|
+
* @param {encodeSpecialJson} encodeSpecialJson - Recursive encoder helper.
|
|
13
|
+
* @returns {any} The encoded value.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* An object that defines how to check and decode a specific serialized type.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {Object} DecodeFn
|
|
19
|
+
* @property {(value: any) => any} check - Checks if the value matches the custom encoded structure.
|
|
20
|
+
* @property {(value: any, decodeSpecialJson: decodeSpecialJson) => any} decode - Decodes the structure back into its original form.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Encodes extended JSON-compatible structures recursively.
|
|
24
|
+
* @callback encodeSpecialJson
|
|
25
|
+
* @param {any} value
|
|
26
|
+
* @returns {any}
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Decodes extended JSON-compatible structures recursively.
|
|
30
|
+
* @callback decodeSpecialJson
|
|
31
|
+
* @param {any} value
|
|
32
|
+
* @returns {any}
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Represents a value that can be safely stored and restored using JSON in `localStorage`,
|
|
36
|
+
* including structures like arrays, plain objects, Map and Set.
|
|
37
|
+
*
|
|
38
|
+
* - `Record<string|number|symbol, any>` → plain object (e.g., `{ key: value }`)
|
|
39
|
+
* - `any[]` → array of any JSON-serializable values
|
|
40
|
+
* - `Map<string|number|symbol, any>` → converted to `{ __map__: true, data: [[k, v], ...] }`
|
|
41
|
+
* - `Set<any>` → converted to `{ __set__: true, data: [v1, v2, ...] }`
|
|
42
|
+
*
|
|
43
|
+
* These conversions allow complex structures to be restored after JSON serialization.
|
|
44
|
+
*
|
|
45
|
+
* @typedef {(Record<string|number|symbol, any> | any[] | Map<string|number|symbol, any> | Set<any>)} LocalStorageJsonValue
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* A powerful wrapper for Web Storage (`localStorage` or `sessionStorage`) that supports
|
|
49
|
+
* type-safe methods and full JSON-like structure encoding and decoding.
|
|
50
|
+
*
|
|
51
|
+
* `TinyLocalStorage` allows storing and retrieving complex types such as:
|
|
52
|
+
* - `Map`, `Set`
|
|
53
|
+
* - `Date`, `RegExp`
|
|
54
|
+
* - `BigInt`, `Symbol`
|
|
55
|
+
* - `undefined`, `null`
|
|
56
|
+
* - Plain objects and arrays
|
|
57
|
+
*
|
|
58
|
+
* Includes:
|
|
59
|
+
* - Type-specific `set` and `get` methods (`setDate`, `getBool`, etc.)
|
|
60
|
+
* - `getValue()` to retrieve any structure regardless of type
|
|
61
|
+
* - Auto-encoding/decoding with support for custom types via `registerJsonType`
|
|
62
|
+
* - Built-in event system (`TinyEvents`) to listen for changes
|
|
63
|
+
* - Optional fallback values on decoding errors
|
|
64
|
+
*
|
|
65
|
+
* Supports registering and unregistering custom types via:
|
|
66
|
+
* - `registerJsonType(...)`
|
|
67
|
+
* - `deleteJsonType(...)`
|
|
68
|
+
*
|
|
69
|
+
* This class is suitable for applications that require structured persistence in the browser.
|
|
70
|
+
*/
|
|
71
|
+
class TinyLocalStorage {
|
|
72
|
+
/** @typedef {import('./TinyEvents.mjs').handler} handler */
|
|
73
|
+
#events = new TinyEvents();
|
|
74
|
+
/**
|
|
75
|
+
* Enables or disables throwing an error when the maximum number of listeners is exceeded.
|
|
76
|
+
*
|
|
77
|
+
* @param {boolean} shouldThrow - If true, an error will be thrown when the max is exceeded.
|
|
78
|
+
*/
|
|
79
|
+
setThrowOnMaxListeners(shouldThrow) {
|
|
80
|
+
return this.#events.setThrowOnMaxListeners(shouldThrow);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Checks whether an error will be thrown when the max listener limit is exceeded.
|
|
84
|
+
*
|
|
85
|
+
* @returns {boolean} True if an error will be thrown, false if only a warning is shown.
|
|
86
|
+
*/
|
|
87
|
+
getThrowOnMaxListeners() {
|
|
88
|
+
return this.#events.getThrowOnMaxListeners();
|
|
89
|
+
}
|
|
90
|
+
/////////////////////////////////////////////////////////////
|
|
91
|
+
/**
|
|
92
|
+
* Adds a listener to the beginning of the listeners array for the specified event.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} event - Event name.
|
|
95
|
+
* @param {handler} handler - The callback function.
|
|
96
|
+
*/
|
|
97
|
+
prependListener(event, handler) {
|
|
98
|
+
return this.#events.prependListener(event, handler);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Adds a one-time listener to the beginning of the listeners array for the specified event.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} event - Event name.
|
|
104
|
+
* @param {handler} handler - The callback function.
|
|
105
|
+
* @returns {handler} - The wrapped handler used internally.
|
|
106
|
+
*/
|
|
107
|
+
prependListenerOnce(event, handler) {
|
|
108
|
+
return this.#events.prependListenerOnce(event, handler);
|
|
109
|
+
}
|
|
110
|
+
//////////////////////////////////////////////////////////////////////
|
|
111
|
+
/**
|
|
112
|
+
* Adds a event listener.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
115
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
116
|
+
*/
|
|
117
|
+
appendListener(event, handler) {
|
|
118
|
+
return this.#events.appendListener(event, handler);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Registers an event listener that runs only once, then is removed.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
124
|
+
* @param {handler} handler - The callback function to run on event.
|
|
125
|
+
* @returns {handler} - The wrapped version of the handler.
|
|
126
|
+
*/
|
|
127
|
+
appendListenerOnce(event, handler) {
|
|
128
|
+
return this.#events.appendListenerOnce(event, handler);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Adds a event listener.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
134
|
+
* @param {handler} handler - Callback function to be called when event fires.
|
|
135
|
+
*/
|
|
136
|
+
on(event, handler) {
|
|
137
|
+
return this.#events.on(event, handler);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Registers an event listener that runs only once, then is removed.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} event - Event name, such as 'onScrollBoundary' or 'onAutoScroll'.
|
|
143
|
+
* @param {handler} handler - The callback function to run on event.
|
|
144
|
+
* @returns {handler} - The wrapped version of the handler.
|
|
145
|
+
*/
|
|
146
|
+
once(event, handler) {
|
|
147
|
+
return this.#events.once(event, handler);
|
|
148
|
+
}
|
|
149
|
+
////////////////////////////////////////////////////////////////////
|
|
150
|
+
/**
|
|
151
|
+
* Removes a previously registered event listener.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} event - The name of the event to remove the handler from.
|
|
154
|
+
* @param {handler} handler - The specific callback function to remove.
|
|
155
|
+
*/
|
|
156
|
+
off(event, handler) {
|
|
157
|
+
return this.#events.off(event, handler);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Removes all event listeners of a specific type from the element.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} event - The event type to remove (e.g. 'onScrollBoundary').
|
|
163
|
+
*/
|
|
164
|
+
offAll(event) {
|
|
165
|
+
return this.#events.offAll(event);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Removes all event listeners of all types from the element.
|
|
169
|
+
*/
|
|
170
|
+
offAllTypes() {
|
|
171
|
+
return this.#events.offAllTypes();
|
|
172
|
+
}
|
|
173
|
+
////////////////////////////////////////////////////////////
|
|
174
|
+
/**
|
|
175
|
+
* Returns the number of listeners for a given event.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} event - The name of the event.
|
|
178
|
+
* @returns {number} Number of listeners for the event.
|
|
179
|
+
*/
|
|
180
|
+
listenerCount(event) {
|
|
181
|
+
return this.#events.listenerCount(event);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} event - The name of the event.
|
|
187
|
+
* @returns {handler[]} Array of listener functions.
|
|
188
|
+
*/
|
|
189
|
+
listeners(event) {
|
|
190
|
+
return this.#events.listeners(event);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Returns a copy of the array of listeners for the specified event.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} event - The name of the event.
|
|
196
|
+
* @returns {handler[]} Array of listener functions.
|
|
197
|
+
*/
|
|
198
|
+
onceListeners(event) {
|
|
199
|
+
return this.#events.onceListeners(event);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Returns a copy of the internal listeners array for the specified event,
|
|
203
|
+
* including wrapper functions like those used by `.once()`.
|
|
204
|
+
* @param {string | symbol} event - The event name.
|
|
205
|
+
* @returns {handler[]} An array of raw listener functions.
|
|
206
|
+
*/
|
|
207
|
+
allListeners(event) {
|
|
208
|
+
return this.#events.allListeners(event);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Returns an array of event names for which there are registered listeners.
|
|
212
|
+
*
|
|
213
|
+
* @returns {string[]} Array of registered event names.
|
|
214
|
+
*/
|
|
215
|
+
eventNames() {
|
|
216
|
+
return this.#events.eventNames();
|
|
217
|
+
}
|
|
218
|
+
//////////////////////////////////////////////////////
|
|
219
|
+
/**
|
|
220
|
+
* Emits an event, triggering all registered handlers for that event.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} event - The event name to emit.
|
|
223
|
+
* @param {...any} payload - Optional data to pass to each handler.
|
|
224
|
+
* @returns {boolean} True if any listeners were called, false otherwise.
|
|
225
|
+
*/
|
|
226
|
+
emit(event, ...payload) {
|
|
227
|
+
return this.#events.emit(event, ...payload);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Sets the maximum number of listeners per event before a warning is shown.
|
|
231
|
+
*
|
|
232
|
+
* @param {number} n - The maximum number of listeners.
|
|
233
|
+
*/
|
|
234
|
+
setMaxListeners(n) {
|
|
235
|
+
return this.#events.setMaxListeners(n);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Gets the maximum number of listeners allowed per event.
|
|
239
|
+
*
|
|
240
|
+
* @returns {number} The maximum number of listeners.
|
|
241
|
+
*/
|
|
242
|
+
getMaxListeners() {
|
|
243
|
+
return this.#events.getMaxListeners();
|
|
244
|
+
}
|
|
245
|
+
///////////////////////////////////////////////////
|
|
246
|
+
/**
|
|
247
|
+
* Registers a new JSON-serializable type with its encoder and decoder.
|
|
248
|
+
*
|
|
249
|
+
* @param {any} type - The type or primitive type name (e.g. `"bigint"`, `"symbol"`, etc).
|
|
250
|
+
* @param {EncodeFn} encodeFn - The function that encodes the value.
|
|
251
|
+
* @param {DecodeFn} decodeFn - An object with `check` and `decode` methods for restoring the value.
|
|
252
|
+
*/
|
|
253
|
+
static registerJsonType(type, encodeFn, decodeFn) {
|
|
254
|
+
customEncoders.set(type, encodeFn);
|
|
255
|
+
customDecoders.set(type, decodeFn);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Removes a previously registered custom type from the encoding/decoding system.
|
|
259
|
+
*
|
|
260
|
+
* @param {string} type - The primitive name or constructor reference used in registration.
|
|
261
|
+
*/
|
|
262
|
+
static deleteJsonType(type) {
|
|
263
|
+
customEncoders.delete(type);
|
|
264
|
+
customDecoders.delete(type);
|
|
265
|
+
}
|
|
266
|
+
//////////////////////////////////////////////////////
|
|
267
|
+
/**
|
|
268
|
+
* Recursively serializes a value to a JSON-compatible format.
|
|
269
|
+
*
|
|
270
|
+
* This includes custom types (via `registerJsonType`), plus support for:
|
|
271
|
+
* - `undefined` → `{ __undefined__: true }`
|
|
272
|
+
* - `null` → `{ __null__: true }`
|
|
273
|
+
*
|
|
274
|
+
* @type {encodeSpecialJson}
|
|
275
|
+
*/
|
|
276
|
+
static encodeSpecialJson(value) {
|
|
277
|
+
if (typeof value === 'undefined')
|
|
278
|
+
return { __undefined__: true };
|
|
279
|
+
if (value === null)
|
|
280
|
+
return { __null__: true };
|
|
281
|
+
for (const [type, encoder] of customEncoders.entries()) {
|
|
282
|
+
if ((typeof type !== 'string' && value instanceof type) || typeof value === type) {
|
|
283
|
+
return encoder(value, TinyLocalStorage.encodeSpecialJson);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(value)) {
|
|
287
|
+
return value.map(TinyLocalStorage.encodeSpecialJson);
|
|
288
|
+
}
|
|
289
|
+
if (isJsonObject(value)) {
|
|
290
|
+
const encoded = {};
|
|
291
|
+
for (const key in value) {
|
|
292
|
+
// @ts-ignore
|
|
293
|
+
encoded[key] = TinyLocalStorage.encodeSpecialJson(value[key]);
|
|
294
|
+
}
|
|
295
|
+
return encoded;
|
|
296
|
+
}
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Recursively deserializes a JSON-compatible value into its original structure.
|
|
301
|
+
*
|
|
302
|
+
* Automatically handles:
|
|
303
|
+
* - `__undefined__` → `undefined`
|
|
304
|
+
* - `__null__` → `null`
|
|
305
|
+
* - Any type registered via `registerJsonType`
|
|
306
|
+
*
|
|
307
|
+
* @type {decodeSpecialJson}
|
|
308
|
+
*/
|
|
309
|
+
static decodeSpecialJson(value) {
|
|
310
|
+
const isJson = isJsonObject(value);
|
|
311
|
+
if (isJson) {
|
|
312
|
+
if (value.__undefined__)
|
|
313
|
+
return undefined;
|
|
314
|
+
if (value.__null__)
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
if (Array.isArray(value)) {
|
|
318
|
+
return value.map(TinyLocalStorage.decodeSpecialJson);
|
|
319
|
+
}
|
|
320
|
+
if (isJson) {
|
|
321
|
+
for (const [type, decoder] of customDecoders.entries()) {
|
|
322
|
+
if (decoder.check && decoder.check(value)) {
|
|
323
|
+
return decoder.decode(value, TinyLocalStorage.decodeSpecialJson);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const decoded = {};
|
|
327
|
+
for (const key in value) {
|
|
328
|
+
// @ts-ignore
|
|
329
|
+
decoded[key] = TinyLocalStorage.decodeSpecialJson(value[key]);
|
|
330
|
+
}
|
|
331
|
+
return decoded;
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
//////////////////////////////////////////////////////
|
|
336
|
+
/** @type {Storage} */
|
|
337
|
+
#localStorage = window.localStorage;
|
|
338
|
+
/** @type {(ev: StorageEvent) => any} */
|
|
339
|
+
#storageEvent = (ev) => this.emit('storage', ev);
|
|
340
|
+
/**
|
|
341
|
+
* Initializes the TinyLocalStorage instance and sets up cross-tab sync.
|
|
342
|
+
*
|
|
343
|
+
* Adds listener for the native `storage` event to support tab synchronization.
|
|
344
|
+
*/
|
|
345
|
+
constructor() {
|
|
346
|
+
window.addEventListener('storage', this.#storageEvent);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Defines a custom storage interface (e.g. `sessionStorage`).
|
|
350
|
+
*
|
|
351
|
+
* @param {Storage} localstorage - A valid Storage object (localStorage or sessionStorage).
|
|
352
|
+
*/
|
|
353
|
+
setLocalStorage(localstorage) {
|
|
354
|
+
if (!(localstorage instanceof Storage))
|
|
355
|
+
throw new Error('Argument must be a valid instance of Storage.');
|
|
356
|
+
this.#localStorage = localstorage;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Checks if `localStorage` is supported by the current environment.
|
|
360
|
+
*
|
|
361
|
+
* @returns {boolean} True if `localStorage` exists, false otherwise.
|
|
362
|
+
*/
|
|
363
|
+
localStorageExists() {
|
|
364
|
+
return this.#localStorage instanceof Storage;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Automatically serializes nested instances.
|
|
368
|
+
*
|
|
369
|
+
* @param {string} name - The key under which to store the data.
|
|
370
|
+
* @param {*} data - The data to be serialized.
|
|
371
|
+
* @returns {*}
|
|
372
|
+
*/
|
|
373
|
+
#setJson(name, data) {
|
|
374
|
+
if (typeof name !== 'string' || !name.length)
|
|
375
|
+
throw new Error('Key must be a non-empty string.');
|
|
376
|
+
return TinyLocalStorage.encodeSpecialJson(data);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Stores a JSON-compatible value in `localStorage`.
|
|
380
|
+
*
|
|
381
|
+
* Automatically serializes nested `Map` and `Set` instances.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} name - The key under which to store the data.
|
|
384
|
+
* @param {LocalStorageJsonValue} data - The data to be serialized and stored.
|
|
385
|
+
*/
|
|
386
|
+
setJson(name, data) {
|
|
387
|
+
if (!isJsonObject(data) &&
|
|
388
|
+
!Array.isArray(data) &&
|
|
389
|
+
!(data instanceof Map) &&
|
|
390
|
+
!(data instanceof Set)) {
|
|
391
|
+
throw new Error('The storage value is not a valid JSON-compatible structure.');
|
|
392
|
+
}
|
|
393
|
+
const encoded = this.#setJson(name, data);
|
|
394
|
+
this.emit('setJson', name, data);
|
|
395
|
+
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Retrieves a value from `localStorage`.
|
|
399
|
+
*
|
|
400
|
+
* Automatically restores nested instances.
|
|
401
|
+
*
|
|
402
|
+
* @param {string} name - The key to retrieve.
|
|
403
|
+
* @param {'array'|'obj'|'map'|'set'|'null'} [defaultData] - Default fallback format if value is invalid.
|
|
404
|
+
* @returns {{ decoded: any, fallback: any }} The parsed object or fallback.
|
|
405
|
+
*/
|
|
406
|
+
#getJson(name, defaultData) {
|
|
407
|
+
if (typeof name !== 'string' || !name.length)
|
|
408
|
+
throw new Error('Key must be a non-empty string.');
|
|
409
|
+
const raw = this.#localStorage.getItem(name);
|
|
410
|
+
const fallbackTypes = {
|
|
411
|
+
obj: () => ({}),
|
|
412
|
+
array: () => [],
|
|
413
|
+
map: () => new Map(),
|
|
414
|
+
set: () => new Set(),
|
|
415
|
+
};
|
|
416
|
+
const fallback =
|
|
417
|
+
// @ts-ignore
|
|
418
|
+
typeof fallbackTypes[defaultData] === 'function' ? fallbackTypes[defaultData]() : null;
|
|
419
|
+
let parsed;
|
|
420
|
+
try {
|
|
421
|
+
// @ts-ignore
|
|
422
|
+
parsed = JSON.parse(raw);
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
// @ts-ignore
|
|
426
|
+
return fallback;
|
|
427
|
+
}
|
|
428
|
+
return { decoded: TinyLocalStorage.decodeSpecialJson(parsed), fallback };
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Retrieves and parses a JSON value from `localStorage`.
|
|
432
|
+
*
|
|
433
|
+
* Automatically restores nested `Map` and `Set` instances.
|
|
434
|
+
*
|
|
435
|
+
* @param {string} name - The key to retrieve.
|
|
436
|
+
* @param {'array'|'obj'|'map'|'set'|'null'} [defaultData] - Default fallback format if value is invalid.
|
|
437
|
+
* @returns {LocalStorageJsonValue|null} The parsed object or fallback.
|
|
438
|
+
*/
|
|
439
|
+
getJson(name, defaultData) {
|
|
440
|
+
const { decoded, fallback } = this.#getJson(name, defaultData);
|
|
441
|
+
if (decoded instanceof Map ||
|
|
442
|
+
decoded instanceof Set ||
|
|
443
|
+
Array.isArray(decoded) ||
|
|
444
|
+
isJsonObject(decoded))
|
|
445
|
+
return decoded;
|
|
446
|
+
return fallback;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Stores a Date in localStorage.
|
|
450
|
+
* @param {string} name
|
|
451
|
+
* @param {Date} data
|
|
452
|
+
*/
|
|
453
|
+
setDate(name, data) {
|
|
454
|
+
if (!(data instanceof Date))
|
|
455
|
+
throw new Error('Value must be a Date.');
|
|
456
|
+
const encoded = this.#setJson(name, data);
|
|
457
|
+
this.emit('setDate', name, data);
|
|
458
|
+
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Retrieves a Date from localStorage.
|
|
462
|
+
* @param {string} name
|
|
463
|
+
* @returns {Date|null}
|
|
464
|
+
*/
|
|
465
|
+
getDate(name) {
|
|
466
|
+
const value = this.#getJson(name).decoded;
|
|
467
|
+
return value instanceof Date ? value : null;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Stores a RegExp in localStorage.
|
|
471
|
+
* @param {string} name
|
|
472
|
+
* @param {RegExp} data
|
|
473
|
+
*/
|
|
474
|
+
setRegExp(name, data) {
|
|
475
|
+
if (!(data instanceof RegExp))
|
|
476
|
+
throw new Error('Value must be a RegExp.');
|
|
477
|
+
const encoded = this.#setJson(name, data);
|
|
478
|
+
this.emit('setRegExp', name, data);
|
|
479
|
+
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Retrieves a RegExp from localStorage.
|
|
483
|
+
* @param {string} name
|
|
484
|
+
* @returns {RegExp|null}
|
|
485
|
+
*/
|
|
486
|
+
getRegExp(name) {
|
|
487
|
+
const value = this.#getJson(name).decoded;
|
|
488
|
+
return value instanceof RegExp ? value : null;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Stores a BigInt in localStorage.
|
|
492
|
+
* @param {string} name
|
|
493
|
+
* @param {bigint} data
|
|
494
|
+
*/
|
|
495
|
+
setBigInt(name, data) {
|
|
496
|
+
if (typeof data !== 'bigint')
|
|
497
|
+
throw new Error('Value must be a BigInt.');
|
|
498
|
+
const encoded = this.#setJson(name, data);
|
|
499
|
+
this.emit('setBigInt', name, data);
|
|
500
|
+
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Retrieves a BigInt from localStorage.
|
|
504
|
+
* @param {string} name
|
|
505
|
+
* @returns {bigint|null}
|
|
506
|
+
*/
|
|
507
|
+
getBigInt(name) {
|
|
508
|
+
const value = this.#getJson(name).decoded;
|
|
509
|
+
return typeof value === 'bigint' ? value : null;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Stores a Symbol in localStorage.
|
|
513
|
+
* Only global symbols (`Symbol.for`) will preserve the key.
|
|
514
|
+
* @param {string} name
|
|
515
|
+
* @param {symbol} data
|
|
516
|
+
*/
|
|
517
|
+
setSymbol(name, data) {
|
|
518
|
+
if (typeof data !== 'symbol')
|
|
519
|
+
throw new Error('Value must be a Symbol.');
|
|
520
|
+
const encoded = this.#setJson(name, data);
|
|
521
|
+
this.emit('setSymbol', name, data);
|
|
522
|
+
return this.#localStorage.setItem(name, JSON.stringify(encoded));
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Retrieves a Symbol from localStorage.
|
|
526
|
+
* @param {string} name
|
|
527
|
+
* @returns {symbol|null}
|
|
528
|
+
*/
|
|
529
|
+
getSymbol(name) {
|
|
530
|
+
const value = this.#getJson(name).decoded;
|
|
531
|
+
return typeof value === 'symbol' ? value : null;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Retrieves a value from `localStorage`.
|
|
535
|
+
*
|
|
536
|
+
* @param {string} name - The key to retrieve.
|
|
537
|
+
* @returns {any} The stored value or null if not found.
|
|
538
|
+
*/
|
|
539
|
+
getValue(name) {
|
|
540
|
+
return this.#getJson(name).decoded ?? null;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Stores a raw string value in `localStorage`.
|
|
544
|
+
*
|
|
545
|
+
* @param {string} name - The key to use.
|
|
546
|
+
* @param {any} data - The data to store.
|
|
547
|
+
*/
|
|
548
|
+
setItem(name, data) {
|
|
549
|
+
if (typeof name !== 'string' || !name.length)
|
|
550
|
+
throw new Error('Key must be a non-empty string.');
|
|
551
|
+
this.emit('setItem', name, data);
|
|
552
|
+
return this.#localStorage.setItem(name, data);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Retrieves a raw string value from `localStorage`.
|
|
556
|
+
*
|
|
557
|
+
* @param {string} name - The key to retrieve.
|
|
558
|
+
* @returns {string|null} The stored value or null if not found.
|
|
559
|
+
*/
|
|
560
|
+
getItem(name) {
|
|
561
|
+
if (typeof name !== 'string' || !name.length)
|
|
562
|
+
throw new Error('Key must be a non-empty string.');
|
|
563
|
+
return this.#localStorage.getItem(name);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Stores a string in `localStorage`, ensuring the data is a valid string.
|
|
567
|
+
*
|
|
568
|
+
* @param {string} name - The key to store the string under.
|
|
569
|
+
* @param {string} data - The string to store.
|
|
570
|
+
*/
|
|
571
|
+
setString(name, data) {
|
|
572
|
+
if (typeof name !== 'string' || !name.length)
|
|
573
|
+
throw new Error('Key must be a non-empty string.');
|
|
574
|
+
if (typeof data !== 'string')
|
|
575
|
+
throw new Error('Value must be a string.');
|
|
576
|
+
this.emit('setString', name, data);
|
|
577
|
+
return this.#localStorage.setItem(name, data);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Retrieves a string value from `localStorage`.
|
|
581
|
+
*
|
|
582
|
+
* @param {string} name - The key to retrieve.
|
|
583
|
+
* @returns {string|null} The string if valid, or null.
|
|
584
|
+
*/
|
|
585
|
+
getString(name) {
|
|
586
|
+
if (typeof name !== 'string' || !name.length)
|
|
587
|
+
throw new Error('Key must be a non-empty string.');
|
|
588
|
+
let value = this.#localStorage.getItem(name);
|
|
589
|
+
if (typeof value === 'string')
|
|
590
|
+
return value;
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Stores a number value in `localStorage`.
|
|
595
|
+
*
|
|
596
|
+
* @param {string} name - The key to use.
|
|
597
|
+
* @param {number} data - The number to store.
|
|
598
|
+
*/
|
|
599
|
+
setNumber(name, data) {
|
|
600
|
+
if (typeof name !== 'string' || !name.length)
|
|
601
|
+
throw new Error('Key must be a non-empty string.');
|
|
602
|
+
if (typeof data !== 'number')
|
|
603
|
+
throw new Error('Value must be a number.');
|
|
604
|
+
this.emit('setNumber', name, data);
|
|
605
|
+
return this.#localStorage.setItem(name, String(data));
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Retrieves a number from `localStorage`.
|
|
609
|
+
*
|
|
610
|
+
* @param {string} name - The key to retrieve.
|
|
611
|
+
* @returns {number|null} The number or null if invalid.
|
|
612
|
+
*/
|
|
613
|
+
getNumber(name) {
|
|
614
|
+
if (typeof name !== 'string' || !name.length)
|
|
615
|
+
throw new Error('Key must be a non-empty string.');
|
|
616
|
+
/** @type {number|string|null} */
|
|
617
|
+
let number = this.#localStorage.getItem(name);
|
|
618
|
+
if (typeof number === 'number')
|
|
619
|
+
return number;
|
|
620
|
+
if (typeof number === 'string' && number.length > 0) {
|
|
621
|
+
number = parseFloat(number);
|
|
622
|
+
if (!Number.isNaN(number))
|
|
623
|
+
return number;
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Stores a boolean value in `localStorage`.
|
|
629
|
+
*
|
|
630
|
+
* @param {string} name - The key to use.
|
|
631
|
+
* @param {boolean} data - The boolean value to store.
|
|
632
|
+
*/
|
|
633
|
+
setBool(name, data) {
|
|
634
|
+
if (typeof name !== 'string' || !name.length)
|
|
635
|
+
throw new Error('Key must be a non-empty string.');
|
|
636
|
+
if (typeof data !== 'boolean')
|
|
637
|
+
throw new Error('Value must be a boolean.');
|
|
638
|
+
this.emit('setBool', name, data);
|
|
639
|
+
return this.#localStorage.setItem(name, String(data));
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Retrieves a boolean value from `localStorage`.
|
|
643
|
+
*
|
|
644
|
+
* @param {string} name - The key to retrieve.
|
|
645
|
+
* @returns {boolean|null} The boolean or null if invalid.
|
|
646
|
+
*/
|
|
647
|
+
getBool(name) {
|
|
648
|
+
if (typeof name !== 'string' || !name.length)
|
|
649
|
+
throw new Error('Key must be a non-empty string.');
|
|
650
|
+
const value = this.#localStorage.getItem(name);
|
|
651
|
+
if (typeof value === 'boolean')
|
|
652
|
+
return value;
|
|
653
|
+
if (typeof value === 'string') {
|
|
654
|
+
if (value === 'true')
|
|
655
|
+
return true;
|
|
656
|
+
if (value === 'false')
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Removes a value from `localStorage`.
|
|
663
|
+
*
|
|
664
|
+
* @param {string} name - The key to remove.
|
|
665
|
+
*/
|
|
666
|
+
removeItem(name) {
|
|
667
|
+
if (typeof name !== 'string' || !name.length)
|
|
668
|
+
throw new Error('Key must be a non-empty string.');
|
|
669
|
+
this.emit('removeItem', name);
|
|
670
|
+
return this.#localStorage.removeItem(name);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Clears all data from `localStorage`.
|
|
674
|
+
*/
|
|
675
|
+
clearLocalStorage() {
|
|
676
|
+
return this.#localStorage.clear();
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Destroys the storage instance by removing the storage event listener.
|
|
680
|
+
*/
|
|
681
|
+
destroy() {
|
|
682
|
+
window.removeEventListener('storage', this.#storageEvent);
|
|
683
|
+
this.#events.offAllTypes();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// First registers
|
|
687
|
+
// Map
|
|
688
|
+
TinyLocalStorage.registerJsonType(Map, (value, encodeSpecialJson) => ({
|
|
689
|
+
__map__: true,
|
|
690
|
+
data: Array.from(value.entries()).map(([k, v]) => [k, encodeSpecialJson(v)]),
|
|
691
|
+
}), {
|
|
692
|
+
check: (value) => value.__map__,
|
|
693
|
+
/** @param {{ data: any[] }} value */
|
|
694
|
+
decode: (value, decodeSpecialJson) => new Map(value.data.map(([k, v]) => [k, decodeSpecialJson(v)])),
|
|
695
|
+
});
|
|
696
|
+
// Set
|
|
697
|
+
TinyLocalStorage.registerJsonType(Set, (value, encodeSpecialJson) => ({
|
|
698
|
+
__set__: true,
|
|
699
|
+
data: Array.from(value).map(encodeSpecialJson),
|
|
700
|
+
}), {
|
|
701
|
+
check: (value) => value.__set__,
|
|
702
|
+
decode: (value, decodeSpecialJson) => new Set(value.data.map(decodeSpecialJson)),
|
|
703
|
+
});
|
|
704
|
+
// Date
|
|
705
|
+
TinyLocalStorage.registerJsonType(Date, (value) => ({
|
|
706
|
+
__date__: true,
|
|
707
|
+
value: value.toISOString(),
|
|
708
|
+
}), {
|
|
709
|
+
check: (value) => value.__date__,
|
|
710
|
+
decode: (value) => new Date(value.value),
|
|
711
|
+
});
|
|
712
|
+
// Regex
|
|
713
|
+
TinyLocalStorage.registerJsonType(RegExp, (value) => ({
|
|
714
|
+
__regexp__: true,
|
|
715
|
+
source: value.source,
|
|
716
|
+
flags: value.flags,
|
|
717
|
+
}), {
|
|
718
|
+
check: (value) => value.__regexp__,
|
|
719
|
+
decode: (value) => new RegExp(value.source, value.flags),
|
|
720
|
+
});
|
|
721
|
+
// Big Int
|
|
722
|
+
TinyLocalStorage.registerJsonType('bigint', (value) => ({
|
|
723
|
+
__bigint__: true,
|
|
724
|
+
value: value.toString(),
|
|
725
|
+
}), {
|
|
726
|
+
check: (value) => value.__bigint__,
|
|
727
|
+
decode: (value) => BigInt(value.value),
|
|
728
|
+
});
|
|
729
|
+
// Symbol
|
|
730
|
+
TinyLocalStorage.registerJsonType('symbol', (value) => ({
|
|
731
|
+
__symbol__: true,
|
|
732
|
+
key: Symbol.keyFor(value) ?? value.description ?? null,
|
|
733
|
+
}), {
|
|
734
|
+
check: (value) => value.__symbol__,
|
|
735
|
+
decode: (value) => {
|
|
736
|
+
const key = value.key;
|
|
737
|
+
return key != null ? Symbol.for(key) : Symbol();
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
export default TinyLocalStorage;
|