@ntnyq/utils 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs DELETED
@@ -1,1124 +0,0 @@
1
- "use strict";
2
-
3
- //#region src/fn/noop.ts
4
- /**
5
- * A function that does nothing.
6
- */
7
- function noop() {}
8
- /**
9
- * Alias of {@link noop}.
10
- */
11
- const NOOP = noop;
12
-
13
- //#endregion
14
- //#region src/fn/once.ts
15
- function once(func) {
16
- let called = false;
17
- return function(...args) {
18
- if (called) return false;
19
- called = true;
20
- func.apply(this, args);
21
- return true;
22
- };
23
- }
24
-
25
- //#endregion
26
- //#region src/is/dom.ts
27
- /**
28
- * @file is/dom.ts
29
- */
30
- /**
31
- * Check if given value is an HTMLElement
32
- * @param value - The value to check
33
- * @returns True if the value is an HTMLElement, false otherwise
34
- */
35
- function isHTMLElement(value) {
36
- return typeof value === "object" && value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE && value instanceof HTMLElement;
37
- }
38
-
39
- //#endregion
40
- //#region src/is/core.ts
41
- function getObjectType(value) {
42
- return Object.prototype.toString.call(value).slice(8, -1);
43
- }
44
- function isUndefined(value) {
45
- return value === void 0;
46
- }
47
- function isNull(value) {
48
- return value === null;
49
- }
50
- function isNil(value) {
51
- return isNull(value) || isUndefined(value);
52
- }
53
- const isNullOrUndefined = isNil;
54
- function isString(value) {
55
- return typeof value === "string";
56
- }
57
- function isEmptyString(value) {
58
- return isString(value) && value.length === 0;
59
- }
60
- function isNonEmptyString(value) {
61
- return isString(value) && value.length > 0;
62
- }
63
- function isWhitespaceString(value) {
64
- return isString(value) && /^\s*$/.test(value);
65
- }
66
- function isEmptyStringOrWhitespace(value) {
67
- return isEmptyString(value) || isWhitespaceString(value);
68
- }
69
- function isNumbericString(value) {
70
- return isString(value) && !isEmptyStringOrWhitespace(value) && !Number.isNaN(Number(value));
71
- }
72
- function isNumber(value) {
73
- return typeof value === "number";
74
- }
75
- function isZero(value) {
76
- return value === 0;
77
- }
78
- function isNaN(value) {
79
- return Number.isNaN(value);
80
- }
81
- function isInteger(value) {
82
- return Number.isInteger(value);
83
- }
84
- function isBigInt(value) {
85
- return typeof value === "bigint";
86
- }
87
- function isBoolean(value) {
88
- return typeof value === "boolean";
89
- }
90
- function isTruthy(value) {
91
- return Boolean(value);
92
- }
93
- function isFunction(value) {
94
- return typeof value === "function";
95
- }
96
- function isArray(value) {
97
- return Array.isArray(value);
98
- }
99
- function isEmptyArray(value) {
100
- return isArray(value) && value.length === 0;
101
- }
102
- function isNonEmptyArray(value) {
103
- return isArray(value) && value.length > 0;
104
- }
105
- function isObject(value) {
106
- return (typeof value === "object" || isFunction(value)) && !isNull(value);
107
- }
108
- function isEmptyObject(value) {
109
- return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length === 0;
110
- }
111
- function isMap(value) {
112
- return getObjectType(value) === "Map";
113
- }
114
- function isEmptyMap(value) {
115
- return isMap(value) && value.size === 0;
116
- }
117
- function isSet(value) {
118
- return getObjectType(value) === "Set";
119
- }
120
- function isEmptySet(value) {
121
- return isSet(value) && value.size === 0;
122
- }
123
- function isRegExp(value) {
124
- return getObjectType(value) === "RegExp";
125
- }
126
- function isError(value) {
127
- return getObjectType(value) === "Error";
128
- }
129
- /**
130
- * @internal
131
- */
132
- function hasPromiseApi(value) {
133
- return isFunction(value?.then) && isFunction(value?.catch);
134
- }
135
- function isNativePromise(value) {
136
- return getObjectType(value) === "Promise";
137
- }
138
- function isPromise(value) {
139
- return isNativePromise(value) || hasPromiseApi(value);
140
- }
141
- function isIterable(value) {
142
- return isFunction(value?.[Symbol.iterator]);
143
- }
144
-
145
- //#endregion
146
- //#region src/is/isDeepEqual.ts
147
- /**
148
- * check if two values are deeply equal
149
- */
150
- function isDeepEqual(value1, value2) {
151
- const type1 = getObjectType(value1);
152
- const type2 = getObjectType(value2);
153
- if (type1 !== type2) return false;
154
- if (isArray(value1) && isArray(value2)) {
155
- if (value1.length !== value2.length) return false;
156
- return value1.every((item, index) => isDeepEqual(item, value2[index]));
157
- }
158
- if (isObject(value1) && isObject(value2)) {
159
- const keys = Object.keys(value1);
160
- if (keys.length !== Object.keys(value2).length) return false;
161
- return keys.every((key) => isDeepEqual(value1[key], value2[key]));
162
- }
163
- return Object.is(value1, value2);
164
- }
165
-
166
- //#endregion
167
- //#region src/dom/scrollIntoView.ts
168
- /**
169
- * Scroll element into view if it is out of view.
170
- *
171
- * @param element - element to scroll
172
- * @param options - scroll options
173
- */
174
- function scrollElementIntoView(element, options = {}) {
175
- const body = document.body;
176
- const { parent = body,...scrollIntoViewOptions } = options;
177
- if (parent === body) {
178
- parent.scrollIntoView(scrollIntoViewOptions);
179
- return;
180
- }
181
- const parentRect = parent.getBoundingClientRect();
182
- const elementRect = element.getBoundingClientRect();
183
- const isHorizontal = parent.scrollWidth > parent.scrollHeight;
184
- const isOutOfView = isHorizontal ? elementRect.left < parentRect.left || elementRect.right > parentRect.right : elementRect.top < parentRect.top || elementRect.bottom > parentRect.bottom;
185
- if (isOutOfView) parent.scrollIntoView(scrollIntoViewOptions);
186
- }
187
-
188
- //#endregion
189
- //#region src/dom/openExternalURL.ts
190
- /**
191
- * Open external url
192
- * @param url - URL to open
193
- * @param options - open options
194
- * @returns window proxy
195
- */
196
- function openExternalURL(url, options = {}) {
197
- const { target = "_blank" } = options;
198
- const proxy = window.open(url, target);
199
- return proxy;
200
- }
201
-
202
- //#endregion
203
- //#region src/dom/isVisibleInViewport.ts
204
- /**
205
- * Check if element is in viewport
206
- * @param element - checked element
207
- * @param targetWindow - window
208
- * @returns true if element is in viewport, false otherwise
209
- */
210
- function isElementVisibleInViewport(element, targetWindow = window) {
211
- const { top, left, bottom, right } = element.getBoundingClientRect();
212
- const { innerWidth, innerHeight } = targetWindow;
213
- return (top >= 0 && top <= innerHeight || bottom >= 0 && bottom <= innerHeight) && (left >= 0 && left <= innerWidth || right >= 0 && right <= innerWidth);
214
- }
215
-
216
- //#endregion
217
- //#region src/env/isBrowser.ts
218
- /**
219
- * @file env.ts
220
- */
221
- /**
222
- * Checks if the code is running in a browser
223
- *
224
- * @returns boolean - true if the code is running in a browser
225
- */
226
- const isBrowser = () => typeof document !== "undefined";
227
-
228
- //#endregion
229
- //#region src/html/escape.ts
230
- const htmlEscapeMap = {
231
- "&": "&amp;",
232
- "<": "&lt;",
233
- ">": "&gt;",
234
- "'": "&#39;",
235
- "\"": "&quot;"
236
- };
237
- const htmlEscapeRegexp = /[&<>'"]/g;
238
- /**
239
- * Escape html chars
240
- */
241
- function escapeHTML(str) {
242
- return str.replace(htmlEscapeRegexp, (char) => htmlEscapeMap[char]);
243
- }
244
- const htmlUnescapeMap = {
245
- "&amp;": "&",
246
- "&#38;": "&",
247
- "&lt;": "<",
248
- "&#60;": "<",
249
- "&gt;": ">",
250
- "&#62;": ">",
251
- "&apos;": "'",
252
- "&#39;": "'",
253
- "&quot;": "\"",
254
- "&#34;": "\""
255
- };
256
- const htmlUnescapeRegexp = /&(amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g;
257
- /**
258
- * Unescape html chars
259
- */
260
- function unescapeHTML(str) {
261
- return str.replace(htmlUnescapeRegexp, (char) => htmlUnescapeMap[char]);
262
- }
263
-
264
- //#endregion
265
- //#region src/misc/raf.ts
266
- const root = isBrowser() ? window : globalThis;
267
- let prev = Date.now();
268
- function mockRAF(fn) {
269
- const curr = Date.now();
270
- const ms = Math.max(0, 16 - (curr - prev));
271
- const id = setTimeout(fn, ms);
272
- prev = curr + ms;
273
- return id;
274
- }
275
- /**
276
- * Request animation frame
277
- *
278
- * @param fn - callback
279
- * @returns id
280
- */
281
- function rAF(fn) {
282
- const raf = root.requestAnimationFrame || mockRAF;
283
- return raf.call(root, fn);
284
- }
285
- /**
286
- * Cancel animation frame
287
- *
288
- * @param id - id
289
- * @returns void
290
- */
291
- function cAF(id) {
292
- const caf = root.cancelAnimationFrame || root.clearTimeout;
293
- return caf.call(root, id);
294
- }
295
-
296
- //#endregion
297
- //#region src/misc/time.ts
298
- /**
299
- * @file time utils
300
- * @module Time
301
- */
302
- const ONE_SECOND = 1e3;
303
- const ONE_MINUTE = 60 * ONE_SECOND;
304
- const ONE_HOUR = 60 * ONE_MINUTE;
305
- const ONE_DAY = 24 * ONE_HOUR;
306
- const ONE_WEEK = 7 * ONE_DAY;
307
- function seconds(count) {
308
- return count * ONE_SECOND;
309
- }
310
- function minutes(count) {
311
- return count * ONE_MINUTE;
312
- }
313
- function hours(count) {
314
- return count * ONE_HOUR;
315
- }
316
- function days(count) {
317
- return count * ONE_DAY;
318
- }
319
- function weeks(count) {
320
- return count * ONE_WEEK;
321
- }
322
-
323
- //#endregion
324
- //#region src/misc/clamp.ts
325
- /**
326
- * Clamps a number between a minimum and maximum value
327
- * @param value - the value to clamp within the given range
328
- * @param min - the minimum value to clamp
329
- * @param max - the maximum value to clamp
330
- * @returns the new value
331
- */
332
- function clamp(value, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY) {
333
- return Math.min(Math.max(value, min), max);
334
- }
335
-
336
- //#endregion
337
- //#region src/misc/waitFor.ts
338
- /**
339
- * Wait for a number of milliseconds
340
- *
341
- * @param ms - millseconds to wait
342
- * @returns a promise that resolves after ms milliseconds
343
- *
344
- * @example
345
- * ```
346
- * import { waitFor } from '@ntnyq/utils'
347
- * await waitFor(3e3)
348
- * // do somthing after 3 seconds
349
- * ```
350
- */
351
- async function waitFor(ms) {
352
- return new Promise((resolve) => setTimeout(resolve, ms));
353
- }
354
-
355
- //#endregion
356
- //#region src/misc/throttle.ts
357
- /**
358
- * Throttle a function to limit its execution to a maximum of once per a specified time interval.
359
- *
360
- * @param delay - Zero or greater delay in milliseconds
361
- * @param callback - A function to be throttled
362
- * @param options - throttle options
363
- * @returns A throttled function
364
- */
365
- function throttle(delay, callback, options = {}) {
366
- const { isDebounce } = options;
367
- /**
368
- * Track the last time `callback` was executed
369
- */
370
- let lastExec = 0;
371
- let cancelled = false;
372
- let timeoutId;
373
- function clearExistingTimeout() {
374
- if (timeoutId) clearTimeout(timeoutId);
375
- }
376
- function cancel() {
377
- clearExistingTimeout();
378
- cancelled = true;
379
- }
380
- function wrapper(...args) {
381
- if (cancelled) return;
382
- const _this = this;
383
- const now = Date.now();
384
- const elapsed = now - lastExec;
385
- function clear() {
386
- timeoutId = void 0;
387
- }
388
- function exec(cur) {
389
- lastExec = cur || Date.now();
390
- callback.apply(_this, args);
391
- }
392
- if (isDebounce && !timeoutId) exec(now);
393
- clearExistingTimeout();
394
- if (!isDebounce && elapsed > delay) exec(now);
395
- else timeoutId = setTimeout(isDebounce ? clear : exec, isDebounce ? delay : delay - elapsed);
396
- }
397
- wrapper.cancel = cancel;
398
- return wrapper;
399
- }
400
- function debounce(delay, callback, options = {}) {
401
- return throttle(delay, callback, {
402
- ...options,
403
- isDebounce: true
404
- });
405
- }
406
-
407
- //#endregion
408
- //#region src/misc/warnOnce.ts
409
- /**
410
- * Cached warnings
411
- */
412
- const warned = new Set();
413
- /**
414
- * Warn message only once
415
- *
416
- * @param message - warning message
417
- */
418
- function warnOnce(message) {
419
- if (warned.has(message)) return;
420
- warned.add(message);
421
- console.warn(message);
422
- }
423
-
424
- //#endregion
425
- //#region src/array/at.ts
426
- /**
427
- * Get array item by index, negative for backward
428
- * @param array - given array
429
- * @param index - index of item
430
- * @returns undefined if not match, otherwise matched item
431
- */
432
- function at(array, index) {
433
- const length = array.length;
434
- if (!length) return void 0;
435
- if (index < 0) index += length;
436
- return array[index];
437
- }
438
- /**
439
- * Get the last item of given array
440
- * @param array - given array
441
- * @returns undefined if empty array, otherwise last item
442
- */
443
- function last(array) {
444
- return at(array, -1);
445
- }
446
-
447
- //#endregion
448
- //#region src/array/chunk.ts
449
- /**
450
- * Splits an array into smaller chunks of a given size.
451
- * @param array - The array to split
452
- * @param size - The size of each chunk
453
- * @returns An array of arrays, where each sub-array has `size` elements from the original array.
454
- */
455
- function chunk(array, size) {
456
- const result = [];
457
- for (let i = 0; i < array.length; i += size) result.push(array.slice(i, i + size));
458
- return result;
459
- }
460
-
461
- //#endregion
462
- //#region src/array/unique.ts
463
- /**
464
- * Returns a new array with unique values.
465
- * @param array - The array to process.
466
- * @returns The new array.
467
- */
468
- function unique(array) {
469
- return Array.from(new Set(array));
470
- }
471
- /**
472
- * Returns a new array with unique values.
473
- * @param array - The array to process.
474
- * @param equalFn - The function to compare values.
475
- * @returns The new array.
476
- */
477
- function uniqueBy(array, equalFn) {
478
- return array.reduce((acc, cur) => {
479
- const idx = acc.findIndex((item) => equalFn(item, cur));
480
- if (idx === -1) acc.push(cur);
481
- return acc;
482
- }, []);
483
- }
484
-
485
- //#endregion
486
- //#region src/array/toArray.ts
487
- /**
488
- * Converts a value to an array.
489
- * @param array - The value to convert.
490
- * @returns The array.
491
- */
492
- function toArray(array) {
493
- array = array ?? [];
494
- return Array.isArray(array) ? array : [array];
495
- }
496
-
497
- //#endregion
498
- //#region src/array/arrayable.ts
499
- /**
500
- * Convert `Arrayable<T>` to `Array<T>` and flatten the result
501
- * @param array - given array
502
- * @returns Array<T>
503
- */
504
- function flattenArrayable(array) {
505
- return toArray(array).flat();
506
- }
507
- /**
508
- * Use rest arguments to merge arrays
509
- * @param args - rest arguments
510
- * @returns Array<T>
511
- */
512
- function mergeArrayable(...args) {
513
- return args.flatMap((i) => toArray(i));
514
- }
515
-
516
- //#endregion
517
- //#region src/array/intersect.ts
518
- /**
519
- * Get intersect items
520
- *
521
- * @returns intersect items
522
- */
523
- function intersect(a, b) {
524
- return a.filter((item) => b.includes(item));
525
- }
526
-
527
- //#endregion
528
- //#region src/array/isArrayEqual.ts
529
- /**
530
- * Check if values of two arrays are equal
531
- * @param array1 - array 1
532
- * @param array2 - array 2
533
- * @returns `true` if equal
534
- */
535
- function isArrayEqual(array1, array2) {
536
- if (array1.length !== array2.length) return false;
537
- return array1.every((item, idx) => item === array2[idx]);
538
- }
539
-
540
- //#endregion
541
- //#region src/string/pad.ts
542
- function createPadString(options) {
543
- const { length, char } = options;
544
- return (value) => (char.repeat(length) + value).slice(-length);
545
- }
546
-
547
- //#endregion
548
- //#region src/string/join.ts
549
- /**
550
- * Joins an array of strings or numbers into a single string.
551
- * @param array - An array of strings or numbers.
552
- * @param options - An object of options.
553
- * @returns A string.
554
- */
555
- function join(array, options = {}) {
556
- const { separator = "" } = options;
557
- if (!Array.isArray(array) || !array.length) return "";
558
- return array.filter((v) => Boolean(v) || v === 0).join(separator);
559
- }
560
-
561
- //#endregion
562
- //#region src/string/slash.ts
563
- /**
564
- * Replace backslash to slash
565
- */
566
- function slash(input) {
567
- return input.replace(/\\/g, "/");
568
- }
569
-
570
- //#endregion
571
- //#region src/number/random.ts
572
- /**
573
- * random an integer by given range
574
- *
575
- * @param min - min value
576
- * @param max - max value
577
- * @returns random integer in range
578
- */
579
- function randomNumber(min, max = 0, options = {}) {
580
- if (max === 0) {
581
- max = min;
582
- min = 0;
583
- }
584
- if (min > max) [min, max] = [max, min];
585
- return Math.trunc(Math.random() * (max - min + (options.includeMax ? 1 : 0)) + min);
586
- }
587
-
588
- //#endregion
589
- //#region src/number/toInteger.ts
590
- /**
591
- * Transforms a value to an integer.
592
- * @param value - The value to convert to an integer.
593
- * @param options - Options for the conversion.
594
- * @returns The converted integer.
595
- */
596
- function toInteger(value, options = {}) {
597
- const { defaultValue = 0, allowDecimal = false, allowNaN = false, onError = "useDefault", min, max, outOfRange = "clamp" } = options;
598
- let numberValue;
599
- let result;
600
- if (isNumber(value)) numberValue = value;
601
- else if (isString(value)) {
602
- const trimmed = value.trim();
603
- if (isEmptyString(trimmed)) {
604
- if (onError === "throwError") throw new TypeError("Cannot convert empty string to an integer");
605
- return onError === "returnOriginal" ? value : defaultValue;
606
- }
607
- numberValue = Number(trimmed);
608
- } else if (isNullOrUndefined(value)) {
609
- if (onError === "throwError") throw new TypeError(`Cannot convert ${value} to an integer`);
610
- return onError === "useDefault" ? value : defaultValue;
611
- } else numberValue = Number(value);
612
- if (isNaN(numberValue)) {
613
- if (allowNaN) return numberValue;
614
- if (onError === "throwError") throw new TypeError(`Cannot convert NaN to an integer`);
615
- return onError === "returnOriginal" ? value : defaultValue;
616
- }
617
- if (allowDecimal) result = Math.floor(numberValue);
618
- else {
619
- if (numberValue % 1 !== 0) {
620
- if (onError === "throwError") throw new Error("Decimal values are not allowed");
621
- return onError === "returnOriginal" ? value : defaultValue;
622
- }
623
- result = numberValue;
624
- }
625
- if (!isUndefined(min) || !isUndefined(max)) {
626
- const minVal = min ?? -Infinity;
627
- const maxVal = max ?? Infinity;
628
- if (result < minVal || result > maxVal) {
629
- if (outOfRange === "throwError") throw new RangeError(`Value ${result} is out of range [${minVal}, ${maxVal}]`);
630
- if (outOfRange === "useDefault") return defaultValue;
631
- if (outOfRange === "clamp") result = Math.max(minVal, Math.min(maxVal, numberValue));
632
- }
633
- }
634
- return result;
635
- }
636
-
637
- //#endregion
638
- //#region src/string/random.ts
639
- /**
640
- * randome a string useing given chars
641
- *
642
- * @param length - string length
643
- * @param chars - string chars
644
- * @returns random string
645
- */
646
- function randomString(length = 16, chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
647
- const result = [];
648
- for (let i = length; i > 0; --i) result.push(chars[randomNumber(chars.length)]);
649
- return result.join("");
650
- }
651
-
652
- //#endregion
653
- //#region src/string/slugify.ts
654
- const rControl = /[\u0000-\u001F]/g;
655
- const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g;
656
- const rCombining = /[\u0300-\u036F]/g;
657
- /**
658
- * Default slugify function
659
- */
660
- function slugify(str) {
661
- return str.normalize("NFKD").replace(rCombining, "").replace(rControl, "").replace(rSpecial, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").replace(/^(\d)/, "_$1").toLowerCase();
662
- }
663
-
664
- //#endregion
665
- //#region src/string/unindent.ts
666
- const _RE_FULL_WS = /^\s*$/;
667
- /**
668
- * Remove common leading whitespace from a template string
669
- * Empty lines at the beginning and end of the template string are also removed.
670
- * @param input - template string
671
- *
672
- * @example
673
- *
674
- * ```ts
675
- * const str = unindent`
676
- * if (foo) {
677
- * bar()
678
- * }
679
- * `
680
- * ```
681
- */
682
- function unindent(input) {
683
- const lines = (typeof input === "string" ? input : input[0]).split("\n");
684
- const whitespaceLines = lines.map((line) => _RE_FULL_WS.test(line));
685
- const commonIndent = lines.reduce((min, line, idx) => {
686
- if (whitespaceLines[idx]) return min;
687
- const indent = line.match(/^\s*/)?.[0].length;
688
- return indent === void 0 ? min : Math.min(min, indent);
689
- }, Number.POSITIVE_INFINITY);
690
- let emptylinesHead = 0;
691
- while (emptylinesHead < lines.length && whitespaceLines[emptylinesHead]) emptylinesHead++;
692
- let emptylinesTail = 0;
693
- while (emptylinesTail < lines.length && whitespaceLines[lines.length - emptylinesTail - 1]) emptylinesTail++;
694
- return lines.slice(emptylinesHead, lines.length - emptylinesTail).map((line) => line.slice(commonIndent)).join("\n");
695
- }
696
-
697
- //#endregion
698
- //#region src/string/getLength.ts
699
- let segmenter;
700
- function isASCII(value) {
701
- return /^[\u0020-\u007F]*$/u.test(value);
702
- }
703
- /**
704
- * Counts graphemes in a given string
705
- * @param value - A string to count graphemes.
706
- * @returns The number of graphemes in `value`.
707
- */
708
- function getStringLength(value) {
709
- if (isASCII(value)) return value.length;
710
- segmenter ??= new Intl.Segmenter();
711
- return [...segmenter.segment(value)].length;
712
- }
713
-
714
- //#endregion
715
- //#region src/string/ensurePrefix.ts
716
- function ensurePrefix(input, prefix) {
717
- return input.startsWith(prefix) ? input : `${prefix}${input}`;
718
- }
719
-
720
- //#endregion
721
- //#region src/string/ensureSuffix.ts
722
- function ensureSuffix(input, suffix) {
723
- return input.endsWith(suffix) ? input : `${input}${suffix}`;
724
- }
725
-
726
- //#endregion
727
- //#region src/color/color.ts
728
- const pad2 = createPadString({
729
- length: 2,
730
- char: "0"
731
- });
732
- const RE_VALID_HEX_COLOR = /^#(?:[0-9a-f]{6}|[0-9a-f]{3})$/i;
733
- function validateHexColor(hex) {
734
- if (hex.length !== 4 && hex.length !== 7) return false;
735
- if (!hex.startsWith("#")) return false;
736
- return RE_VALID_HEX_COLOR.test(hex);
737
- }
738
- function normalizeHexString(hex) {
739
- return hex.length === 6 ? hex : hex.replace(/./g, "$&$&");
740
- }
741
- var Color = class Color {
742
- red = 0;
743
- green = 0;
744
- blue = 0;
745
- alpha = 1;
746
- constructor(red = 0, green = 0, blue = 0, alpha = 1) {
747
- this.red = red;
748
- this.green = green;
749
- this.blue = blue;
750
- this.alpha = alpha;
751
- }
752
- static fromRGB(red, green, blue) {
753
- return new Color(red, green, blue);
754
- }
755
- static fromRGBA(red, green, blue, alpha) {
756
- return new Color(red, green, blue, alpha);
757
- }
758
- static fromHex(hex) {
759
- if (!validateHexColor(hex)) throw new Error("Invalid hex color");
760
- const [red, green, blue] = normalizeHexString(hex.slice(1)).match(/.{2}/g)?.map((value) => Number.parseInt(value, 16)) ?? [
761
- 0,
762
- 0,
763
- 0
764
- ];
765
- return new Color(red, green, blue);
766
- }
767
- get brightness() {
768
- return (this.red * 299 + this.green * 587 + this.blue * 114) / 1e3;
769
- }
770
- get isDark() {
771
- return this.brightness < 128;
772
- }
773
- get isLight() {
774
- return !this.isDark;
775
- }
776
- toHexString(isUpperCase = true) {
777
- const hexString = `#${pad2(this.red.toString(16))}${pad2(this.green.toString(16))}${pad2(this.blue.toString(16))}`;
778
- return isUpperCase ? hexString.toUpperCase() : hexString;
779
- }
780
- toRGBAString() {
781
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
782
- }
783
- /**
784
- * add alpha value to {@link Color}
785
- *
786
- * @param alpha - alpha value
787
- * @returns instance of {@link Color}
788
- */
789
- withAlpha(alpha = 1) {
790
- return new Color(this.red, this.green, this.blue, alpha);
791
- }
792
- /**
793
- * lighten the color by percentage
794
- *
795
- * @param percentage - percentage to lighten
796
- */
797
- lighten(percentage = 0) {
798
- const amount = Math.round(percentage / 100 * 255);
799
- return new Color(Math.min(this.red + amount, 255), Math.min(this.green + amount, 255), Math.min(this.blue + amount, 255), this.alpha);
800
- }
801
- /**
802
- * darken the color by percentage
803
- *
804
- * @param percentage - percentage to darken
805
- */
806
- darken(percentage = 0) {
807
- const amount = Math.round(percentage / 100 * 255);
808
- return new Color(Math.max(this.red - amount, 0), Math.max(this.green - amount, 0), Math.max(this.blue - amount, 0), this.alpha);
809
- }
810
- };
811
-
812
- //#endregion
813
- //#region src/color/random.ts
814
- /**
815
- * the maximum value of RGB
816
- */
817
- const MAX_RGB = 255;
818
- /**
819
- * get a random RGB color
820
- * @returns a random RGB color
821
- */
822
- function randomRGBColor() {
823
- return `rgb(${randomNumber(MAX_RGB)}, ${randomNumber(MAX_RGB)}, ${randomNumber(MAX_RGB)})`;
824
- }
825
- /**
826
- * get a random RGBA color
827
- * @returns a random RGBA color
828
- */
829
- function randomRGBAColor() {
830
- return `rgba(${randomNumber(MAX_RGB)}, ${randomNumber(MAX_RGB)}, ${randomNumber(MAX_RGB)}, ${Math.random().toFixed(1)})`;
831
- }
832
- /**
833
- * get a random hex color
834
- * @returns a random hex color
835
- */
836
- function randomHexColor() {
837
- return `#${Math.random().toString(16).slice(2, 8)}`;
838
- }
839
-
840
- //#endregion
841
- //#region src/proxy/enhance.ts
842
- /**
843
- * enhance object
844
- * @module proxy
845
- */
846
- function enhance(module$1, extra) {
847
- return new Proxy(module$1, {
848
- get(target, key, receiver) {
849
- if (Reflect.has(extra, key)) return Reflect.get(extra, key, receiver);
850
- return Reflect.get(target, key, receiver);
851
- },
852
- has(target, key) {
853
- return Reflect.has(extra, key) || Reflect.has(target, key);
854
- }
855
- });
856
- }
857
-
858
- //#endregion
859
- //#region src/module/interopDefault.ts
860
- /**
861
- * Interop default export from a module
862
- *
863
- * @param mod - The module
864
- * @returns The default export
865
- *
866
- * @example
867
- *
868
- * ```ts
869
- * import { interopDefault } from '@ntnyq/utils'
870
- *
871
- * const { unindent } = await interopDefault(import('@ntnyq/utils'))
872
- * ```
873
- */
874
- async function interopDefault(mod) {
875
- const resolved = await mod;
876
- return resolved.default || resolved;
877
- }
878
-
879
- //#endregion
880
- //#region src/module/resolveSubOptions.ts
881
- /**
882
- * Resolve sub options `boolean | Options` to `Options`
883
- * @param options - core options
884
- * @param key - sub options key
885
- * @returns resolved sub options
886
- *
887
- * @example
888
- *
889
- * ```ts
890
- * import { resolveSubOptions } from '@ntnyq/utils'
891
- *
892
- * interface Options {
893
- * compile?: boolean | {
894
- * include?: string[]
895
- * exclude?: string[]
896
- * }
897
- * }
898
- *
899
- * const options: Options = {
900
- * compile: true
901
- * }
902
- *
903
- * console.log(resolveSubOptions(options, 'compile'))
904
- *
905
- * // => {}
906
- * ```
907
- */
908
- function resolveSubOptions(options, key) {
909
- return typeof options[key] === "boolean" ? {} : options[key] || {};
910
- }
911
-
912
- //#endregion
913
- //#region src/object/omit.ts
914
- function omit(object, ...keys) {
915
- keys.forEach((key) => delete object[key]);
916
- return object;
917
- }
918
-
919
- //#endregion
920
- //#region src/object/hasOwn.ts
921
- /**
922
- * check object has a property with given key
923
- * @param object - the object to check
924
- * @param key - the key to check
925
- * @returns true if object has a property with given key, false otherwise
926
- */
927
- function hasOwn(object, key) {
928
- if (object === null) return false;
929
- return Object.prototype.hasOwnProperty.call(object, key);
930
- }
931
-
932
- //#endregion
933
- //#region src/object/pick.ts
934
- function pick(object, keys) {
935
- return Object.assign(
936
- {},
937
- // eslint-disable-next-line array-callback-return
938
- ...keys.map((key) => {
939
- if (object && hasOwn(object, key)) return { [key]: object[key] };
940
- })
941
- );
942
- }
943
-
944
- //#endregion
945
- //#region src/object/clean.ts
946
- /**
947
- * clean undefined, null, zero, empty string, empty array, empty object from object
948
- * @param obj - object to be cleaned
949
- * @param options - clean options
950
- * @returns cleaned object
951
- */
952
- function cleanObject(obj, options = {}) {
953
- const { cleanUndefined = true, cleanNull = true, cleanNaN = true, cleanZero = false, cleanEmptyString = false, cleanEmptyArray = false, cleanEmptyObject = false, recursive = true } = options;
954
- Object.keys(obj).forEach((key) => {
955
- const v = obj[key];
956
- if (cleanUndefined && isUndefined(v)) delete obj[key];
957
- if (cleanNull && isNull(v)) delete obj[key];
958
- if (cleanZero && isZero(v)) delete obj[key];
959
- if (cleanNaN && isZero(v)) delete obj[key];
960
- if (cleanEmptyString && isEmptyString(v)) delete obj[key];
961
- if (cleanEmptyArray && isEmptyArray(v)) delete obj[key];
962
- if (cleanEmptyObject && isEmptyObject(v)) delete obj[key];
963
- if (recursive && isObject(v)) cleanObject(v, options);
964
- });
965
- return obj;
966
- }
967
-
968
- //#endregion
969
- //#region src/object/isPlainObject.ts
970
- /**
971
- * Check if a value is a plain object (not an array, Date, RegExp, Map, Set, etc.)
972
- *
973
- * @param value - Checked value
974
- * @copyright {@link https://github.com/sindresorhus/is/blob/main/source/index.ts}
975
- */
976
- function isPlainObject(value) {
977
- if (!isObject(value)) return false;
978
- const prototype = Object.getPrototypeOf(value);
979
- return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
980
- }
981
-
982
- //#endregion
983
- //#region src/object/sortObject.ts
984
- /**
985
- * Sort object properties
986
- */
987
- function sortObject(obj, options = {}) {
988
- const { compareFn = (a, b) => a.localeCompare(b) } = options;
989
- function sortKeys(obj$1) {
990
- const sortedKeys = Object.keys(obj$1).sort(compareFn);
991
- const result = {};
992
- for (const key of sortedKeys) {
993
- const value = obj$1[key];
994
- let newValue;
995
- if (options.deep && isPlainObject(value)) newValue = sortKeys(value);
996
- else newValue = value;
997
- Object.defineProperty(result, key, {
998
- ...Object.getOwnPropertyDescriptor(obj$1, key),
999
- value: newValue
1000
- });
1001
- }
1002
- return result;
1003
- }
1004
- return sortKeys(obj);
1005
- }
1006
-
1007
- //#endregion
1008
- //#region src/constants/char.ts
1009
- /**
1010
- * Special chars
1011
- */
1012
- const SPECIAL_CHAR = { newline: "\n" };
1013
-
1014
- //#endregion
1015
- //#region src/constants/regexp.ts
1016
- /**
1017
- * 注释正则
1018
- *
1019
- * 匹配 \<!-- 或 /* 开头的注释,直到 --> 或 *\/ 结尾
1020
- */
1021
- const RE_COMMENTS = /(?:<!--|\/\*)([\s\S]*?)(?:-->|\*\/)/g;
1022
- /**
1023
- * JavaScript line comment
1024
- */
1025
- const RE_LINE_COMMENT = /\/\/.*/;
1026
- /**
1027
- * JavaScript block comment
1028
- */
1029
- const RE_BLOCK_COMMENT = /\/\*[\s\S]*?\*\//g;
1030
-
1031
- //#endregion
1032
- exports.Color = Color
1033
- exports.NOOP = NOOP
1034
- exports.RE_BLOCK_COMMENT = RE_BLOCK_COMMENT
1035
- exports.RE_COMMENTS = RE_COMMENTS
1036
- exports.RE_LINE_COMMENT = RE_LINE_COMMENT
1037
- exports.SPECIAL_CHAR = SPECIAL_CHAR
1038
- exports.at = at
1039
- exports.cAF = cAF
1040
- exports.chunk = chunk
1041
- exports.clamp = clamp
1042
- exports.cleanObject = cleanObject
1043
- exports.createPadString = createPadString
1044
- exports.days = days
1045
- exports.debounce = debounce
1046
- exports.enhance = enhance
1047
- exports.ensurePrefix = ensurePrefix
1048
- exports.ensureSuffix = ensureSuffix
1049
- exports.escapeHTML = escapeHTML
1050
- exports.flattenArrayable = flattenArrayable
1051
- exports.getObjectType = getObjectType
1052
- exports.getStringLength = getStringLength
1053
- exports.hasOwn = hasOwn
1054
- exports.hours = hours
1055
- exports.interopDefault = interopDefault
1056
- exports.intersect = intersect
1057
- exports.isArray = isArray
1058
- exports.isArrayEqual = isArrayEqual
1059
- exports.isBigInt = isBigInt
1060
- exports.isBoolean = isBoolean
1061
- exports.isBrowser = isBrowser
1062
- exports.isDeepEqual = isDeepEqual
1063
- exports.isElementVisibleInViewport = isElementVisibleInViewport
1064
- exports.isEmptyArray = isEmptyArray
1065
- exports.isEmptyMap = isEmptyMap
1066
- exports.isEmptyObject = isEmptyObject
1067
- exports.isEmptySet = isEmptySet
1068
- exports.isEmptyString = isEmptyString
1069
- exports.isEmptyStringOrWhitespace = isEmptyStringOrWhitespace
1070
- exports.isError = isError
1071
- exports.isFunction = isFunction
1072
- exports.isHTMLElement = isHTMLElement
1073
- exports.isInteger = isInteger
1074
- exports.isIterable = isIterable
1075
- exports.isMap = isMap
1076
- exports.isNaN = isNaN
1077
- exports.isNativePromise = isNativePromise
1078
- exports.isNil = isNil
1079
- exports.isNonEmptyArray = isNonEmptyArray
1080
- exports.isNonEmptyString = isNonEmptyString
1081
- exports.isNull = isNull
1082
- exports.isNullOrUndefined = isNullOrUndefined
1083
- exports.isNumber = isNumber
1084
- exports.isNumbericString = isNumbericString
1085
- exports.isObject = isObject
1086
- exports.isPromise = isPromise
1087
- exports.isRegExp = isRegExp
1088
- exports.isSet = isSet
1089
- exports.isString = isString
1090
- exports.isTruthy = isTruthy
1091
- exports.isUndefined = isUndefined
1092
- exports.isWhitespaceString = isWhitespaceString
1093
- exports.isZero = isZero
1094
- exports.join = join
1095
- exports.last = last
1096
- exports.mergeArrayable = mergeArrayable
1097
- exports.minutes = minutes
1098
- exports.noop = noop
1099
- exports.omit = omit
1100
- exports.once = once
1101
- exports.openExternalURL = openExternalURL
1102
- exports.pick = pick
1103
- exports.rAF = rAF
1104
- exports.randomHexColor = randomHexColor
1105
- exports.randomNumber = randomNumber
1106
- exports.randomRGBAColor = randomRGBAColor
1107
- exports.randomRGBColor = randomRGBColor
1108
- exports.randomString = randomString
1109
- exports.resolveSubOptions = resolveSubOptions
1110
- exports.scrollElementIntoView = scrollElementIntoView
1111
- exports.seconds = seconds
1112
- exports.slash = slash
1113
- exports.slugify = slugify
1114
- exports.sortObject = sortObject
1115
- exports.throttle = throttle
1116
- exports.toArray = toArray
1117
- exports.toInteger = toInteger
1118
- exports.unescapeHTML = unescapeHTML
1119
- exports.unindent = unindent
1120
- exports.unique = unique
1121
- exports.uniqueBy = uniqueBy
1122
- exports.waitFor = waitFor
1123
- exports.warnOnce = warnOnce
1124
- exports.weeks = weeks