osl-base-extended 0.1.18 → 0.1.19
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.
|
@@ -2478,6 +2478,1551 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2478
2478
|
}]
|
|
2479
2479
|
}] });
|
|
2480
2480
|
|
|
2481
|
+
// ─── Array Utilities ────────────────────────────────────────────────────────
|
|
2482
|
+
/** Splits an array into chunks of the given size. */
|
|
2483
|
+
function chunk(array, size) {
|
|
2484
|
+
if (size <= 0)
|
|
2485
|
+
return [];
|
|
2486
|
+
const result = [];
|
|
2487
|
+
for (let i = 0; i < array.length; i += size) {
|
|
2488
|
+
result.push(array.slice(i, i + size));
|
|
2489
|
+
}
|
|
2490
|
+
return result;
|
|
2491
|
+
}
|
|
2492
|
+
/** Removes duplicate primitive values. */
|
|
2493
|
+
function unique(array) {
|
|
2494
|
+
return [...new Set(array)];
|
|
2495
|
+
}
|
|
2496
|
+
/** Removes duplicates by object key. */
|
|
2497
|
+
function uniqueBy(array, key) {
|
|
2498
|
+
const seen = new Set();
|
|
2499
|
+
return array.filter(item => {
|
|
2500
|
+
const val = item[key];
|
|
2501
|
+
if (seen.has(val))
|
|
2502
|
+
return false;
|
|
2503
|
+
seen.add(val);
|
|
2504
|
+
return true;
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
/** Flattens one level of nesting. */
|
|
2508
|
+
function flatten(array) {
|
|
2509
|
+
return array.flat();
|
|
2510
|
+
}
|
|
2511
|
+
/** Deeply flattens a nested array. */
|
|
2512
|
+
function flattenDeep(array) {
|
|
2513
|
+
return array.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : [...acc, val], []);
|
|
2514
|
+
}
|
|
2515
|
+
/** Groups array elements by a key. */
|
|
2516
|
+
function groupBy(array, key) {
|
|
2517
|
+
return array.reduce((acc, item) => {
|
|
2518
|
+
const group = String(item[key]);
|
|
2519
|
+
acc[group] = acc[group] ?? [];
|
|
2520
|
+
acc[group].push(item);
|
|
2521
|
+
return acc;
|
|
2522
|
+
}, {});
|
|
2523
|
+
}
|
|
2524
|
+
/** Sorts array by key ascending or descending. */
|
|
2525
|
+
function sortBy(array, key, direction = 'asc') {
|
|
2526
|
+
return [...array].sort((a, b) => {
|
|
2527
|
+
const aVal = a[key];
|
|
2528
|
+
const bVal = b[key];
|
|
2529
|
+
if (aVal < bVal)
|
|
2530
|
+
return direction === 'asc' ? -1 : 1;
|
|
2531
|
+
if (aVal > bVal)
|
|
2532
|
+
return direction === 'asc' ? 1 : -1;
|
|
2533
|
+
return 0;
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
/** Filters array where key matches value (strict equality). */
|
|
2537
|
+
function filterBy(array, key, value) {
|
|
2538
|
+
return array.filter(item => item[key] === value);
|
|
2539
|
+
}
|
|
2540
|
+
/** Sums values at the given numeric key. */
|
|
2541
|
+
function sumBy(array, key) {
|
|
2542
|
+
return array.reduce((acc, item) => acc + Number(item[key]), 0);
|
|
2543
|
+
}
|
|
2544
|
+
/** Returns the item with the minimum value at key. */
|
|
2545
|
+
function minBy(array, key) {
|
|
2546
|
+
if (!array.length)
|
|
2547
|
+
return undefined;
|
|
2548
|
+
return array.reduce((min, item) => (item[key] < min[key] ? item : min));
|
|
2549
|
+
}
|
|
2550
|
+
/** Returns the item with the maximum value at key. */
|
|
2551
|
+
function maxBy(array, key) {
|
|
2552
|
+
if (!array.length)
|
|
2553
|
+
return undefined;
|
|
2554
|
+
return array.reduce((max, item) => (item[key] > max[key] ? item : max));
|
|
2555
|
+
}
|
|
2556
|
+
/** Computes the average of an array of numbers. */
|
|
2557
|
+
function average$1(numbers) {
|
|
2558
|
+
if (!numbers.length)
|
|
2559
|
+
return 0;
|
|
2560
|
+
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
|
|
2561
|
+
}
|
|
2562
|
+
/** Returns true if the array is null, undefined, or has no elements. */
|
|
2563
|
+
function isEmpty$2(array) {
|
|
2564
|
+
return !array || array.length === 0;
|
|
2565
|
+
}
|
|
2566
|
+
/** Returns the last element. */
|
|
2567
|
+
function last(array) {
|
|
2568
|
+
return array[array.length - 1];
|
|
2569
|
+
}
|
|
2570
|
+
/** Returns the first element. */
|
|
2571
|
+
function first(array) {
|
|
2572
|
+
return array[0];
|
|
2573
|
+
}
|
|
2574
|
+
/** Returns a slice of the array for the given page (1-based). */
|
|
2575
|
+
function paginate(array, page, pageSize) {
|
|
2576
|
+
const start = (page - 1) * pageSize;
|
|
2577
|
+
return array.slice(start, start + pageSize);
|
|
2578
|
+
}
|
|
2579
|
+
/** Moves an element from one index to another (immutable). */
|
|
2580
|
+
function move(array, fromIndex, toIndex) {
|
|
2581
|
+
const result = [...array];
|
|
2582
|
+
const [item] = result.splice(fromIndex, 1);
|
|
2583
|
+
result.splice(toIndex, 0, item);
|
|
2584
|
+
return result;
|
|
2585
|
+
}
|
|
2586
|
+
/** Adds the item if absent, removes it if present. */
|
|
2587
|
+
function toggle(array, item) {
|
|
2588
|
+
return array.includes(item) ? array.filter(i => i !== item) : [...array, item];
|
|
2589
|
+
}
|
|
2590
|
+
/** Returns elements present in both arrays. */
|
|
2591
|
+
function intersection(a, b) {
|
|
2592
|
+
const setB = new Set(b);
|
|
2593
|
+
return a.filter(item => setB.has(item));
|
|
2594
|
+
}
|
|
2595
|
+
/** Returns elements in `a` not present in `b`. */
|
|
2596
|
+
function difference(a, b) {
|
|
2597
|
+
const setB = new Set(b);
|
|
2598
|
+
return a.filter(item => !setB.has(item));
|
|
2599
|
+
}
|
|
2600
|
+
/** Converts an array to a lookup map keyed by the given property. */
|
|
2601
|
+
function toMap(array, key) {
|
|
2602
|
+
return array.reduce((acc, item) => {
|
|
2603
|
+
acc[String(item[key])] = item;
|
|
2604
|
+
return acc;
|
|
2605
|
+
}, {});
|
|
2606
|
+
}
|
|
2607
|
+
/** Removes falsy values (null, undefined, 0, '', false). */
|
|
2608
|
+
function compact(array) {
|
|
2609
|
+
return array.filter(Boolean);
|
|
2610
|
+
}
|
|
2611
|
+
/** Returns a new randomly shuffled copy. */
|
|
2612
|
+
function shuffle(array) {
|
|
2613
|
+
const result = [...array];
|
|
2614
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
2615
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
2616
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
2617
|
+
}
|
|
2618
|
+
return result;
|
|
2619
|
+
}
|
|
2620
|
+
function sample(array, count) {
|
|
2621
|
+
if (count === undefined)
|
|
2622
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
2623
|
+
return shuffle(array).slice(0, count);
|
|
2624
|
+
}
|
|
2625
|
+
/** Counts occurrences of each value at the given key. */
|
|
2626
|
+
function countBy(array, key) {
|
|
2627
|
+
return array.reduce((acc, item) => {
|
|
2628
|
+
const group = String(item[key]);
|
|
2629
|
+
acc[group] = (acc[group] ?? 0) + 1;
|
|
2630
|
+
return acc;
|
|
2631
|
+
}, {});
|
|
2632
|
+
}
|
|
2633
|
+
/** Returns the union of two arrays (unique values from both). */
|
|
2634
|
+
function union(a, b) {
|
|
2635
|
+
return unique([...a, ...b]);
|
|
2636
|
+
}
|
|
2637
|
+
/** Zips multiple arrays into an array of tuples. */
|
|
2638
|
+
function zip(...arrays) {
|
|
2639
|
+
const length = Math.max(...arrays.map(a => a.length));
|
|
2640
|
+
return Array.from({ length }, (_, i) => arrays.map(a => a[i]));
|
|
2641
|
+
}
|
|
2642
|
+
/** Returns true if predicate holds for all elements. */
|
|
2643
|
+
function every(array, predicate) {
|
|
2644
|
+
return array.every(predicate);
|
|
2645
|
+
}
|
|
2646
|
+
/** Returns true if predicate holds for at least one element. */
|
|
2647
|
+
function some(array, predicate) {
|
|
2648
|
+
return array.some(predicate);
|
|
2649
|
+
}
|
|
2650
|
+
/** Partitions an array into two groups based on predicate. */
|
|
2651
|
+
function partition(array, predicate) {
|
|
2652
|
+
const pass = [];
|
|
2653
|
+
const fail = [];
|
|
2654
|
+
for (const item of array) {
|
|
2655
|
+
(predicate(item) ? pass : fail).push(item);
|
|
2656
|
+
}
|
|
2657
|
+
return [pass, fail];
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
var array_util = /*#__PURE__*/Object.freeze({
|
|
2661
|
+
__proto__: null,
|
|
2662
|
+
average: average$1,
|
|
2663
|
+
chunk: chunk,
|
|
2664
|
+
compact: compact,
|
|
2665
|
+
countBy: countBy,
|
|
2666
|
+
difference: difference,
|
|
2667
|
+
every: every,
|
|
2668
|
+
filterBy: filterBy,
|
|
2669
|
+
first: first,
|
|
2670
|
+
flatten: flatten,
|
|
2671
|
+
flattenDeep: flattenDeep,
|
|
2672
|
+
groupBy: groupBy,
|
|
2673
|
+
intersection: intersection,
|
|
2674
|
+
isEmpty: isEmpty$2,
|
|
2675
|
+
last: last,
|
|
2676
|
+
maxBy: maxBy,
|
|
2677
|
+
minBy: minBy,
|
|
2678
|
+
move: move,
|
|
2679
|
+
paginate: paginate,
|
|
2680
|
+
partition: partition,
|
|
2681
|
+
sample: sample,
|
|
2682
|
+
shuffle: shuffle,
|
|
2683
|
+
some: some,
|
|
2684
|
+
sortBy: sortBy,
|
|
2685
|
+
sumBy: sumBy,
|
|
2686
|
+
toMap: toMap,
|
|
2687
|
+
toggle: toggle,
|
|
2688
|
+
union: union,
|
|
2689
|
+
unique: unique,
|
|
2690
|
+
uniqueBy: uniqueBy,
|
|
2691
|
+
zip: zip
|
|
2692
|
+
});
|
|
2693
|
+
|
|
2694
|
+
// ─── Date Utilities ───────────────────────────────────────────────────────────
|
|
2695
|
+
const MONTH_NAMES_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
2696
|
+
const MONTH_NAMES_FULL = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
2697
|
+
const DAY_NAMES_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
2698
|
+
const DAY_NAMES_FULL = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
2699
|
+
function pad(n) {
|
|
2700
|
+
return String(n).padStart(2, '0');
|
|
2701
|
+
}
|
|
2702
|
+
/** Returns the current date and time. */
|
|
2703
|
+
function now() {
|
|
2704
|
+
return new Date();
|
|
2705
|
+
}
|
|
2706
|
+
/** Returns today's date with time set to midnight. */
|
|
2707
|
+
function today() {
|
|
2708
|
+
return startOfDay(new Date());
|
|
2709
|
+
}
|
|
2710
|
+
/** Returns true if the value is a valid Date object. */
|
|
2711
|
+
function isValidDate$1(value) {
|
|
2712
|
+
return value instanceof Date && !isNaN(value.getTime());
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Formats a Date using a pattern string.
|
|
2716
|
+
* Tokens: YYYY, YY, MM, M, DD, D, HH, H, mm, m, ss, s,
|
|
2717
|
+
* MMM (short month), MMMM (full month),
|
|
2718
|
+
* ddd (short day), dddd (full day).
|
|
2719
|
+
*/
|
|
2720
|
+
function formatDate(date, pattern) {
|
|
2721
|
+
const d = new Date(date);
|
|
2722
|
+
if (!isValidDate$1(d))
|
|
2723
|
+
return '';
|
|
2724
|
+
const year = d.getFullYear();
|
|
2725
|
+
const month = d.getMonth();
|
|
2726
|
+
const day = d.getDate();
|
|
2727
|
+
const hours = d.getHours();
|
|
2728
|
+
const mins = d.getMinutes();
|
|
2729
|
+
const secs = d.getSeconds();
|
|
2730
|
+
const dayOfWeek = d.getDay();
|
|
2731
|
+
return pattern
|
|
2732
|
+
.replace('YYYY', String(year))
|
|
2733
|
+
.replace('YY', String(year).slice(-2))
|
|
2734
|
+
.replace('MMMM', MONTH_NAMES_FULL[month])
|
|
2735
|
+
.replace('MMM', MONTH_NAMES_SHORT[month])
|
|
2736
|
+
.replace('MM', pad(month + 1))
|
|
2737
|
+
.replace('M', String(month + 1))
|
|
2738
|
+
.replace('dddd', DAY_NAMES_FULL[dayOfWeek])
|
|
2739
|
+
.replace('ddd', DAY_NAMES_SHORT[dayOfWeek])
|
|
2740
|
+
.replace('DD', pad(day))
|
|
2741
|
+
.replace('D', String(day))
|
|
2742
|
+
.replace('HH', pad(hours))
|
|
2743
|
+
.replace('H', String(hours))
|
|
2744
|
+
.replace('mm', pad(mins))
|
|
2745
|
+
.replace('m', String(mins))
|
|
2746
|
+
.replace('ss', pad(secs))
|
|
2747
|
+
.replace('s', String(secs));
|
|
2748
|
+
}
|
|
2749
|
+
/** Returns date as `YYYY-MM-DD`. */
|
|
2750
|
+
function toDateOnly(date) {
|
|
2751
|
+
return formatDate(date, 'YYYY-MM-DD');
|
|
2752
|
+
}
|
|
2753
|
+
/** Returns the ISO 8601 string for a date. */
|
|
2754
|
+
function toISOString(date) {
|
|
2755
|
+
return date.toISOString();
|
|
2756
|
+
}
|
|
2757
|
+
/** Returns a new date with `days` added. */
|
|
2758
|
+
function addDays(date, days) {
|
|
2759
|
+
const result = new Date(date);
|
|
2760
|
+
result.setDate(result.getDate() + days);
|
|
2761
|
+
return result;
|
|
2762
|
+
}
|
|
2763
|
+
/** Returns a new date with `months` added. */
|
|
2764
|
+
function addMonths(date, months) {
|
|
2765
|
+
const result = new Date(date);
|
|
2766
|
+
result.setMonth(result.getMonth() + months);
|
|
2767
|
+
return result;
|
|
2768
|
+
}
|
|
2769
|
+
/** Returns a new date with `years` added. */
|
|
2770
|
+
function addYears(date, years) {
|
|
2771
|
+
const result = new Date(date);
|
|
2772
|
+
result.setFullYear(result.getFullYear() + years);
|
|
2773
|
+
return result;
|
|
2774
|
+
}
|
|
2775
|
+
/** Returns a new date with `hours` added. */
|
|
2776
|
+
function addHours(date, hours) {
|
|
2777
|
+
return new Date(date.getTime() + hours * 3_600_000);
|
|
2778
|
+
}
|
|
2779
|
+
/** Returns a new date with `minutes` added. */
|
|
2780
|
+
function addMinutes(date, minutes) {
|
|
2781
|
+
return new Date(date.getTime() + minutes * 60_000);
|
|
2782
|
+
}
|
|
2783
|
+
/** Returns a new date with `days` subtracted. */
|
|
2784
|
+
function subtractDays(date, days) {
|
|
2785
|
+
return addDays(date, -days);
|
|
2786
|
+
}
|
|
2787
|
+
/** Returns a new date with `months` subtracted. */
|
|
2788
|
+
function subtractMonths(date, months) {
|
|
2789
|
+
return addMonths(date, -months);
|
|
2790
|
+
}
|
|
2791
|
+
/** Returns a new date with `years` subtracted. */
|
|
2792
|
+
function subtractYears(date, years) {
|
|
2793
|
+
return addYears(date, -years);
|
|
2794
|
+
}
|
|
2795
|
+
/** Returns the absolute difference in whole days between two dates. */
|
|
2796
|
+
function diffInDays(date1, date2) {
|
|
2797
|
+
return Math.abs(Math.floor((date1.getTime() - date2.getTime()) / 86_400_000));
|
|
2798
|
+
}
|
|
2799
|
+
/** Returns the absolute difference in whole months between two dates. */
|
|
2800
|
+
function diffInMonths(date1, date2) {
|
|
2801
|
+
return Math.abs((date1.getFullYear() - date2.getFullYear()) * 12 +
|
|
2802
|
+
(date1.getMonth() - date2.getMonth()));
|
|
2803
|
+
}
|
|
2804
|
+
/** Returns the absolute difference in whole years between two dates. */
|
|
2805
|
+
function diffInYears(date1, date2) {
|
|
2806
|
+
return Math.abs(date1.getFullYear() - date2.getFullYear());
|
|
2807
|
+
}
|
|
2808
|
+
/** Returns the difference in minutes between two dates (date1 - date2). */
|
|
2809
|
+
function diffInMinutes(date1, date2) {
|
|
2810
|
+
return Math.floor((date1.getTime() - date2.getTime()) / 60_000);
|
|
2811
|
+
}
|
|
2812
|
+
/** Returns true if date1 is strictly before date2. */
|
|
2813
|
+
function isBefore(date1, date2) {
|
|
2814
|
+
return date1.getTime() < date2.getTime();
|
|
2815
|
+
}
|
|
2816
|
+
/** Returns true if date1 is strictly after date2. */
|
|
2817
|
+
function isAfter(date1, date2) {
|
|
2818
|
+
return date1.getTime() > date2.getTime();
|
|
2819
|
+
}
|
|
2820
|
+
/** Returns true if both dates fall on the same calendar day. */
|
|
2821
|
+
function isSameDay(date1, date2) {
|
|
2822
|
+
return (date1.getFullYear() === date2.getFullYear() &&
|
|
2823
|
+
date1.getMonth() === date2.getMonth() &&
|
|
2824
|
+
date1.getDate() === date2.getDate());
|
|
2825
|
+
}
|
|
2826
|
+
/** Returns true if the date falls on a Saturday or Sunday. */
|
|
2827
|
+
function isWeekend(date) {
|
|
2828
|
+
return date.getDay() === 0 || date.getDay() === 6;
|
|
2829
|
+
}
|
|
2830
|
+
/** Returns true if the date is today. */
|
|
2831
|
+
function isToday(date) {
|
|
2832
|
+
return isSameDay(date, new Date());
|
|
2833
|
+
}
|
|
2834
|
+
/** Returns true if the date falls in the past (before now). */
|
|
2835
|
+
function isPast(date) {
|
|
2836
|
+
return date.getTime() < Date.now();
|
|
2837
|
+
}
|
|
2838
|
+
/** Returns true if the date falls in the future (after now). */
|
|
2839
|
+
function isFuture(date) {
|
|
2840
|
+
return date.getTime() > Date.now();
|
|
2841
|
+
}
|
|
2842
|
+
/** Returns the date with time set to 00:00:00.000. */
|
|
2843
|
+
function startOfDay(date) {
|
|
2844
|
+
const result = new Date(date);
|
|
2845
|
+
result.setHours(0, 0, 0, 0);
|
|
2846
|
+
return result;
|
|
2847
|
+
}
|
|
2848
|
+
/** Returns the date with time set to 23:59:59.999. */
|
|
2849
|
+
function endOfDay(date) {
|
|
2850
|
+
const result = new Date(date);
|
|
2851
|
+
result.setHours(23, 59, 59, 999);
|
|
2852
|
+
return result;
|
|
2853
|
+
}
|
|
2854
|
+
/** Returns the first day of the month (time 00:00:00.000). */
|
|
2855
|
+
function startOfMonth(date) {
|
|
2856
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
2857
|
+
}
|
|
2858
|
+
/** Returns the last day of the month (time 23:59:59.999). */
|
|
2859
|
+
function endOfMonth(date) {
|
|
2860
|
+
const result = new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
2861
|
+
result.setHours(23, 59, 59, 999);
|
|
2862
|
+
return result;
|
|
2863
|
+
}
|
|
2864
|
+
/** Returns the first day of the year (Jan 1, 00:00:00.000). */
|
|
2865
|
+
function startOfYear(date) {
|
|
2866
|
+
return new Date(date.getFullYear(), 0, 1);
|
|
2867
|
+
}
|
|
2868
|
+
/** Returns the last day of the year (Dec 31, 23:59:59.999). */
|
|
2869
|
+
function endOfYear(date) {
|
|
2870
|
+
const result = new Date(date.getFullYear(), 11, 31);
|
|
2871
|
+
result.setHours(23, 59, 59, 999);
|
|
2872
|
+
return result;
|
|
2873
|
+
}
|
|
2874
|
+
/** Calculates the age in years from a birth date. */
|
|
2875
|
+
function getAge(birthDate) {
|
|
2876
|
+
const n = new Date();
|
|
2877
|
+
let age = n.getFullYear() - birthDate.getFullYear();
|
|
2878
|
+
const monthDiff = n.getMonth() - birthDate.getMonth();
|
|
2879
|
+
if (monthDiff < 0 || (monthDiff === 0 && n.getDate() < birthDate.getDate()))
|
|
2880
|
+
age--;
|
|
2881
|
+
return age;
|
|
2882
|
+
}
|
|
2883
|
+
/** Returns the number of days in a given month (0-based month). */
|
|
2884
|
+
function daysInMonth(year, month) {
|
|
2885
|
+
return new Date(year, month + 1, 0).getDate();
|
|
2886
|
+
}
|
|
2887
|
+
/** Returns true if the given year is a leap year. */
|
|
2888
|
+
function isLeapYear(year) {
|
|
2889
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* Returns a human-friendly relative time string.
|
|
2893
|
+
* @example timeAgo(new Date(Date.now() - 3600_000)) → '1 hour ago'
|
|
2894
|
+
*/
|
|
2895
|
+
function timeAgo(date) {
|
|
2896
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
2897
|
+
const abs = Math.abs(seconds);
|
|
2898
|
+
const future = seconds < 0;
|
|
2899
|
+
const intervals = [
|
|
2900
|
+
[31_536_000, 'year'],
|
|
2901
|
+
[2_592_000, 'month'],
|
|
2902
|
+
[604_800, 'week'],
|
|
2903
|
+
[86_400, 'day'],
|
|
2904
|
+
[3_600, 'hour'],
|
|
2905
|
+
[60, 'minute'],
|
|
2906
|
+
[1, 'second'],
|
|
2907
|
+
];
|
|
2908
|
+
for (const [secs, label] of intervals) {
|
|
2909
|
+
const count = Math.floor(abs / secs);
|
|
2910
|
+
if (count >= 1) {
|
|
2911
|
+
const unit = count === 1 ? label : `${label}s`;
|
|
2912
|
+
return future ? `in ${count} ${unit}` : `${count} ${unit} ago`;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
return 'just now';
|
|
2916
|
+
}
|
|
2917
|
+
/** Returns the ISO week number (1–53) for the given date. */
|
|
2918
|
+
function getWeekNumber(date) {
|
|
2919
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
2920
|
+
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
|
|
2921
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
2922
|
+
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86_400_000) + 1) / 7);
|
|
2923
|
+
}
|
|
2924
|
+
/** Returns the next weekday (Mon–Fri) after the given date. */
|
|
2925
|
+
function nextWorkday(date) {
|
|
2926
|
+
const result = addDays(date, 1);
|
|
2927
|
+
while (isWeekend(result)) {
|
|
2928
|
+
result.setDate(result.getDate() + 1);
|
|
2929
|
+
}
|
|
2930
|
+
return result;
|
|
2931
|
+
}
|
|
2932
|
+
/** Returns true if a date falls within the range [start, end] (inclusive). */
|
|
2933
|
+
function inRange$2(date, start, end) {
|
|
2934
|
+
return date >= start && date <= end;
|
|
2935
|
+
}
|
|
2936
|
+
/** Parses a `YYYY-MM-DD` string and returns a local-midnight Date or null. */
|
|
2937
|
+
function parseDate(dateStr) {
|
|
2938
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr);
|
|
2939
|
+
if (!match)
|
|
2940
|
+
return null;
|
|
2941
|
+
const [, y, mo, d] = match.map(Number);
|
|
2942
|
+
const result = new Date(y, mo - 1, d);
|
|
2943
|
+
return isValidDate$1(result) ? result : null;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
var date_util = /*#__PURE__*/Object.freeze({
|
|
2947
|
+
__proto__: null,
|
|
2948
|
+
addDays: addDays,
|
|
2949
|
+
addHours: addHours,
|
|
2950
|
+
addMinutes: addMinutes,
|
|
2951
|
+
addMonths: addMonths,
|
|
2952
|
+
addYears: addYears,
|
|
2953
|
+
daysInMonth: daysInMonth,
|
|
2954
|
+
diffInDays: diffInDays,
|
|
2955
|
+
diffInMinutes: diffInMinutes,
|
|
2956
|
+
diffInMonths: diffInMonths,
|
|
2957
|
+
diffInYears: diffInYears,
|
|
2958
|
+
endOfDay: endOfDay,
|
|
2959
|
+
endOfMonth: endOfMonth,
|
|
2960
|
+
endOfYear: endOfYear,
|
|
2961
|
+
formatDate: formatDate,
|
|
2962
|
+
getAge: getAge,
|
|
2963
|
+
getWeekNumber: getWeekNumber,
|
|
2964
|
+
inRange: inRange$2,
|
|
2965
|
+
isAfter: isAfter,
|
|
2966
|
+
isBefore: isBefore,
|
|
2967
|
+
isFuture: isFuture,
|
|
2968
|
+
isLeapYear: isLeapYear,
|
|
2969
|
+
isPast: isPast,
|
|
2970
|
+
isSameDay: isSameDay,
|
|
2971
|
+
isToday: isToday,
|
|
2972
|
+
isValidDate: isValidDate$1,
|
|
2973
|
+
isWeekend: isWeekend,
|
|
2974
|
+
nextWorkday: nextWorkday,
|
|
2975
|
+
now: now,
|
|
2976
|
+
parseDate: parseDate,
|
|
2977
|
+
startOfDay: startOfDay,
|
|
2978
|
+
startOfMonth: startOfMonth,
|
|
2979
|
+
startOfYear: startOfYear,
|
|
2980
|
+
subtractDays: subtractDays,
|
|
2981
|
+
subtractMonths: subtractMonths,
|
|
2982
|
+
subtractYears: subtractYears,
|
|
2983
|
+
timeAgo: timeAgo,
|
|
2984
|
+
toDateOnly: toDateOnly,
|
|
2985
|
+
toISOString: toISOString,
|
|
2986
|
+
today: today
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2989
|
+
// ─── Number Utilities ────────────────────────────────────────────────────────
|
|
2990
|
+
/** Rounds to the given number of decimal places (default 0). */
|
|
2991
|
+
function round(num, decimals = 0) {
|
|
2992
|
+
const factor = Math.pow(10, decimals);
|
|
2993
|
+
return Math.round(num * factor) / factor;
|
|
2994
|
+
}
|
|
2995
|
+
/** Floors to the given number of decimal places (default 0). */
|
|
2996
|
+
function floor(num, decimals = 0) {
|
|
2997
|
+
const factor = Math.pow(10, decimals);
|
|
2998
|
+
return Math.floor(num * factor) / factor;
|
|
2999
|
+
}
|
|
3000
|
+
/** Ceils to the given number of decimal places (default 0). */
|
|
3001
|
+
function ceil(num, decimals = 0) {
|
|
3002
|
+
const factor = Math.pow(10, decimals);
|
|
3003
|
+
return Math.ceil(num * factor) / factor;
|
|
3004
|
+
}
|
|
3005
|
+
/** Clamps a number within [min, max]. */
|
|
3006
|
+
function clamp(num, min, max) {
|
|
3007
|
+
return Math.min(Math.max(num, min), max);
|
|
3008
|
+
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Formats a number as currency using Intl.NumberFormat.
|
|
3011
|
+
* @example formatCurrency(1234.5, 'USD', 'en-US') → '$1,234.50'
|
|
3012
|
+
*/
|
|
3013
|
+
function formatCurrency(num, currency = 'USD', locale = 'en-US') {
|
|
3014
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(num);
|
|
3015
|
+
}
|
|
3016
|
+
/**
|
|
3017
|
+
* Formats a number with grouping separators and fixed decimal places.
|
|
3018
|
+
* @example formatNumber(1234567.89, 2, 'en-US') → '1,234,567.89'
|
|
3019
|
+
*/
|
|
3020
|
+
function formatNumber(num, decimals = 2, locale = 'en-US') {
|
|
3021
|
+
return new Intl.NumberFormat(locale, {
|
|
3022
|
+
minimumFractionDigits: decimals,
|
|
3023
|
+
maximumFractionDigits: decimals,
|
|
3024
|
+
}).format(num);
|
|
3025
|
+
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Calculates what percentage `value` is of `total`.
|
|
3028
|
+
* @example percentage(25, 200) → 12.5
|
|
3029
|
+
*/
|
|
3030
|
+
function percentage(value, total, decimals = 2) {
|
|
3031
|
+
if (total === 0)
|
|
3032
|
+
return 0;
|
|
3033
|
+
return round((value / total) * 100, decimals);
|
|
3034
|
+
}
|
|
3035
|
+
/** Returns true if `num` is a prime number. */
|
|
3036
|
+
function isPrime(num) {
|
|
3037
|
+
if (num < 2)
|
|
3038
|
+
return false;
|
|
3039
|
+
if (num === 2)
|
|
3040
|
+
return true;
|
|
3041
|
+
if (num % 2 === 0)
|
|
3042
|
+
return false;
|
|
3043
|
+
for (let i = 3; i <= Math.sqrt(num); i += 2) {
|
|
3044
|
+
if (num % i === 0)
|
|
3045
|
+
return false;
|
|
3046
|
+
}
|
|
3047
|
+
return true;
|
|
3048
|
+
}
|
|
3049
|
+
/** Returns true if the number is even. */
|
|
3050
|
+
function isEven(num) {
|
|
3051
|
+
return num % 2 === 0;
|
|
3052
|
+
}
|
|
3053
|
+
/** Returns true if the number is odd. */
|
|
3054
|
+
function isOdd(num) {
|
|
3055
|
+
return num % 2 !== 0;
|
|
3056
|
+
}
|
|
3057
|
+
/** Returns a random floating-point number in [min, max). */
|
|
3058
|
+
function random(min, max) {
|
|
3059
|
+
return Math.random() * (max - min) + min;
|
|
3060
|
+
}
|
|
3061
|
+
/** Returns a random integer in [min, max] (inclusive). */
|
|
3062
|
+
function randomInt(min, max) {
|
|
3063
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
3064
|
+
}
|
|
3065
|
+
/** Returns the sum of an array of numbers. */
|
|
3066
|
+
function sum(numbers) {
|
|
3067
|
+
return numbers.reduce((acc, n) => acc + n, 0);
|
|
3068
|
+
}
|
|
3069
|
+
/** Returns the average of an array of numbers. */
|
|
3070
|
+
function average(numbers) {
|
|
3071
|
+
if (!numbers.length)
|
|
3072
|
+
return 0;
|
|
3073
|
+
return sum(numbers) / numbers.length;
|
|
3074
|
+
}
|
|
3075
|
+
/** Returns the minimum value in an array. */
|
|
3076
|
+
function min(numbers) {
|
|
3077
|
+
return Math.min(...numbers);
|
|
3078
|
+
}
|
|
3079
|
+
/** Returns the maximum value in an array. */
|
|
3080
|
+
function max(numbers) {
|
|
3081
|
+
return Math.max(...numbers);
|
|
3082
|
+
}
|
|
3083
|
+
/**
|
|
3084
|
+
* Returns the ordinal string for a number.
|
|
3085
|
+
* @example toOrdinal(3) → '3rd'
|
|
3086
|
+
*/
|
|
3087
|
+
function toOrdinal(num) {
|
|
3088
|
+
const abs = Math.abs(num);
|
|
3089
|
+
const suffix = abs % 100 >= 11 && abs % 100 <= 13 ? 'th' :
|
|
3090
|
+
abs % 10 === 1 ? 'st' :
|
|
3091
|
+
abs % 10 === 2 ? 'nd' :
|
|
3092
|
+
abs % 10 === 3 ? 'rd' : 'th';
|
|
3093
|
+
return `${num}${suffix}`;
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Abbreviates large numbers with K / M / B / T suffixes.
|
|
3097
|
+
* @example abbreviate(1500000) → '1.5M'
|
|
3098
|
+
*/
|
|
3099
|
+
function abbreviate(num, decimals = 1) {
|
|
3100
|
+
const tiers = [
|
|
3101
|
+
[1e12, 'T'],
|
|
3102
|
+
[1e9, 'B'],
|
|
3103
|
+
[1e6, 'M'],
|
|
3104
|
+
[1e3, 'K'],
|
|
3105
|
+
];
|
|
3106
|
+
const abs = Math.abs(num);
|
|
3107
|
+
for (const [threshold, suffix] of tiers) {
|
|
3108
|
+
if (abs >= threshold) {
|
|
3109
|
+
return `${round(num / threshold, decimals)}${suffix}`;
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
return String(num);
|
|
3113
|
+
}
|
|
3114
|
+
/** Returns true if `num` is within [min, max] (inclusive). */
|
|
3115
|
+
function inRange$1(num, min, max) {
|
|
3116
|
+
return num >= min && num <= max;
|
|
3117
|
+
}
|
|
3118
|
+
/** Parses a string to float; returns null if not a valid number. */
|
|
3119
|
+
function safeParseFloat(str) {
|
|
3120
|
+
const n = parseFloat(str);
|
|
3121
|
+
return isNaN(n) ? null : n;
|
|
3122
|
+
}
|
|
3123
|
+
/** Parses a string to integer; returns null if not a valid number. */
|
|
3124
|
+
function safeParseInt(str, radix = 10) {
|
|
3125
|
+
const n = parseInt(str, radix);
|
|
3126
|
+
return isNaN(n) ? null : n;
|
|
3127
|
+
}
|
|
3128
|
+
/** Returns the greatest common divisor of two integers. */
|
|
3129
|
+
function gcd(a, b) {
|
|
3130
|
+
a = Math.abs(a);
|
|
3131
|
+
b = Math.abs(b);
|
|
3132
|
+
while (b) {
|
|
3133
|
+
[a, b] = [b, a % b];
|
|
3134
|
+
}
|
|
3135
|
+
return a;
|
|
3136
|
+
}
|
|
3137
|
+
/** Returns the least common multiple of two integers. */
|
|
3138
|
+
function lcm(a, b) {
|
|
3139
|
+
return Math.abs(a * b) / gcd(a, b);
|
|
3140
|
+
}
|
|
3141
|
+
/** Returns the Nth Fibonacci number (0-indexed). */
|
|
3142
|
+
function fibonacci(n) {
|
|
3143
|
+
if (n <= 1)
|
|
3144
|
+
return n;
|
|
3145
|
+
let a = 0, b = 1;
|
|
3146
|
+
for (let i = 2; i <= n; i++) {
|
|
3147
|
+
[a, b] = [b, a + b];
|
|
3148
|
+
}
|
|
3149
|
+
return b;
|
|
3150
|
+
}
|
|
3151
|
+
/** Returns true if the value is a finite number (not NaN, not ±Infinity). */
|
|
3152
|
+
function isFiniteNumber(value) {
|
|
3153
|
+
return typeof value === 'number' && isFinite(value);
|
|
3154
|
+
}
|
|
3155
|
+
/** Returns true if the value is NaN. */
|
|
3156
|
+
function isNaNValue(value) {
|
|
3157
|
+
return typeof value === 'number' && isNaN(value);
|
|
3158
|
+
}
|
|
3159
|
+
/** Returns true if the number is positive (> 0). */
|
|
3160
|
+
function isPositive$1(num) {
|
|
3161
|
+
return num > 0;
|
|
3162
|
+
}
|
|
3163
|
+
/** Returns true if the number is negative (< 0). */
|
|
3164
|
+
function isNegative$1(num) {
|
|
3165
|
+
return num < 0;
|
|
3166
|
+
}
|
|
3167
|
+
/** Converts degrees to radians. */
|
|
3168
|
+
function toRadians(degrees) {
|
|
3169
|
+
return degrees * (Math.PI / 180);
|
|
3170
|
+
}
|
|
3171
|
+
/** Converts radians to degrees. */
|
|
3172
|
+
function toDegrees(radians) {
|
|
3173
|
+
return radians * (180 / Math.PI);
|
|
3174
|
+
}
|
|
3175
|
+
/** Linearly interpolates between `start` and `end` by factor `t` (0–1). */
|
|
3176
|
+
function lerp(start, end, t) {
|
|
3177
|
+
return start + (end - start) * clamp(t, 0, 1);
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
var number_util = /*#__PURE__*/Object.freeze({
|
|
3181
|
+
__proto__: null,
|
|
3182
|
+
abbreviate: abbreviate,
|
|
3183
|
+
average: average,
|
|
3184
|
+
ceil: ceil,
|
|
3185
|
+
clamp: clamp,
|
|
3186
|
+
fibonacci: fibonacci,
|
|
3187
|
+
floor: floor,
|
|
3188
|
+
formatCurrency: formatCurrency,
|
|
3189
|
+
formatNumber: formatNumber,
|
|
3190
|
+
gcd: gcd,
|
|
3191
|
+
inRange: inRange$1,
|
|
3192
|
+
isEven: isEven,
|
|
3193
|
+
isFiniteNumber: isFiniteNumber,
|
|
3194
|
+
isNaNValue: isNaNValue,
|
|
3195
|
+
isNegative: isNegative$1,
|
|
3196
|
+
isOdd: isOdd,
|
|
3197
|
+
isPositive: isPositive$1,
|
|
3198
|
+
isPrime: isPrime,
|
|
3199
|
+
lcm: lcm,
|
|
3200
|
+
lerp: lerp,
|
|
3201
|
+
max: max,
|
|
3202
|
+
min: min,
|
|
3203
|
+
percentage: percentage,
|
|
3204
|
+
random: random,
|
|
3205
|
+
randomInt: randomInt,
|
|
3206
|
+
round: round,
|
|
3207
|
+
safeParseFloat: safeParseFloat,
|
|
3208
|
+
safeParseInt: safeParseInt,
|
|
3209
|
+
sum: sum,
|
|
3210
|
+
toDegrees: toDegrees,
|
|
3211
|
+
toOrdinal: toOrdinal,
|
|
3212
|
+
toRadians: toRadians
|
|
3213
|
+
});
|
|
3214
|
+
|
|
3215
|
+
// ─── Object Utilities ────────────────────────────────────────────────────────
|
|
3216
|
+
/** Returns a deep clone of the value using structured clone. */
|
|
3217
|
+
function deepClone(obj) {
|
|
3218
|
+
return structuredClone(obj);
|
|
3219
|
+
}
|
|
3220
|
+
/** Deep-merges one or more source objects into target (immutable). */
|
|
3221
|
+
function deepMerge(target, ...sources) {
|
|
3222
|
+
const result = deepClone(target);
|
|
3223
|
+
for (const source of sources) {
|
|
3224
|
+
for (const key in source) {
|
|
3225
|
+
const srcVal = source[key];
|
|
3226
|
+
const tgtVal = result[key];
|
|
3227
|
+
if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
|
|
3228
|
+
result[key] = deepMerge(tgtVal, srcVal);
|
|
3229
|
+
}
|
|
3230
|
+
else if (srcVal !== undefined) {
|
|
3231
|
+
result[key] = deepClone(srcVal);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
return result;
|
|
3236
|
+
}
|
|
3237
|
+
/** Returns true if `value` is a plain (non-null) object literal. */
|
|
3238
|
+
function isPlainObject(value) {
|
|
3239
|
+
return typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype;
|
|
3240
|
+
}
|
|
3241
|
+
/** Creates a new object containing only the specified keys. */
|
|
3242
|
+
function pick(obj, keys) {
|
|
3243
|
+
return keys.reduce((acc, key) => {
|
|
3244
|
+
if (key in obj)
|
|
3245
|
+
acc[key] = obj[key];
|
|
3246
|
+
return acc;
|
|
3247
|
+
}, {});
|
|
3248
|
+
}
|
|
3249
|
+
/** Creates a new object excluding the specified keys. */
|
|
3250
|
+
function omit(obj, keys) {
|
|
3251
|
+
const keySet = new Set(keys);
|
|
3252
|
+
return Object.fromEntries(Object.entries(obj).filter(([k]) => !keySet.has(k)));
|
|
3253
|
+
}
|
|
3254
|
+
/** Returns true if the object has no own enumerable properties. */
|
|
3255
|
+
function isEmpty$1(obj) {
|
|
3256
|
+
if (obj == null)
|
|
3257
|
+
return true;
|
|
3258
|
+
return Object.keys(obj).length === 0;
|
|
3259
|
+
}
|
|
3260
|
+
/** Deep equality check. */
|
|
3261
|
+
function isEqual(a, b) {
|
|
3262
|
+
if (a === b)
|
|
3263
|
+
return true;
|
|
3264
|
+
if (a == null || b == null)
|
|
3265
|
+
return a === b;
|
|
3266
|
+
if (typeof a !== typeof b)
|
|
3267
|
+
return false;
|
|
3268
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
3269
|
+
if (a.length !== b.length)
|
|
3270
|
+
return false;
|
|
3271
|
+
return a.every((v, i) => isEqual(v, b[i]));
|
|
3272
|
+
}
|
|
3273
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
3274
|
+
const keysA = Object.keys(a);
|
|
3275
|
+
const keysB = Object.keys(b);
|
|
3276
|
+
if (keysA.length !== keysB.length)
|
|
3277
|
+
return false;
|
|
3278
|
+
return keysA.every(k => isEqual(a[k], b[k]));
|
|
3279
|
+
}
|
|
3280
|
+
return false;
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Flattens a nested object to a single depth using `delimiter` as key separator.
|
|
3284
|
+
* @example flattenObject({ a: { b: 1 } }) → { 'a.b': 1 }
|
|
3285
|
+
*/
|
|
3286
|
+
function flattenObject(obj, delimiter = '.', prefix = '') {
|
|
3287
|
+
const result = {};
|
|
3288
|
+
for (const key in obj) {
|
|
3289
|
+
const fullKey = prefix ? `${prefix}${delimiter}${key}` : key;
|
|
3290
|
+
const value = obj[key];
|
|
3291
|
+
if (isPlainObject(value)) {
|
|
3292
|
+
Object.assign(result, flattenObject(value, delimiter, fullKey));
|
|
3293
|
+
}
|
|
3294
|
+
else {
|
|
3295
|
+
result[fullKey] = value;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
return result;
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Converts a flat object with delimiter-separated keys back to a nested object.
|
|
3302
|
+
* @example unflattenObject({ 'a.b': 1 }) → { a: { b: 1 } }
|
|
3303
|
+
*/
|
|
3304
|
+
function unflattenObject(obj, delimiter = '.') {
|
|
3305
|
+
const result = {};
|
|
3306
|
+
for (const flatKey in obj) {
|
|
3307
|
+
const parts = flatKey.split(delimiter);
|
|
3308
|
+
let current = result;
|
|
3309
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3310
|
+
const part = parts[i];
|
|
3311
|
+
if (!isPlainObject(current[part]))
|
|
3312
|
+
current[part] = {};
|
|
3313
|
+
current = current[part];
|
|
3314
|
+
}
|
|
3315
|
+
current[parts[parts.length - 1]] = obj[flatKey];
|
|
3316
|
+
}
|
|
3317
|
+
return result;
|
|
3318
|
+
}
|
|
3319
|
+
/** Serializes an object to a URL query string (excludes null/undefined values). */
|
|
3320
|
+
function toQueryString(obj) {
|
|
3321
|
+
const params = new URLSearchParams();
|
|
3322
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3323
|
+
if (value != null)
|
|
3324
|
+
params.append(key, String(value));
|
|
3325
|
+
}
|
|
3326
|
+
return params.toString();
|
|
3327
|
+
}
|
|
3328
|
+
/** Parses a URL query string into a key-value map. */
|
|
3329
|
+
function fromQueryString(queryString) {
|
|
3330
|
+
const params = new URLSearchParams(queryString.startsWith('?') ? queryString.slice(1) : queryString);
|
|
3331
|
+
const result = {};
|
|
3332
|
+
params.forEach((value, key) => { result[key] = value; });
|
|
3333
|
+
return result;
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3336
|
+
* Gets a deeply nested value by dot-path string.
|
|
3337
|
+
* @example getPath({ a: { b: 2 } }, 'a.b') → 2
|
|
3338
|
+
*/
|
|
3339
|
+
function getPath(obj, path, defaultValue) {
|
|
3340
|
+
const keys = path.split('.');
|
|
3341
|
+
let current = obj;
|
|
3342
|
+
for (const key of keys) {
|
|
3343
|
+
if (current == null || typeof current !== 'object')
|
|
3344
|
+
return defaultValue;
|
|
3345
|
+
current = current[key];
|
|
3346
|
+
}
|
|
3347
|
+
return current ?? defaultValue;
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3350
|
+
* Sets a deeply nested value by dot-path string (mutates the object).
|
|
3351
|
+
* @example setPath(obj, 'a.b', 42)
|
|
3352
|
+
*/
|
|
3353
|
+
function setPath(obj, path, value) {
|
|
3354
|
+
const keys = path.split('.');
|
|
3355
|
+
let current = obj;
|
|
3356
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
3357
|
+
const key = keys[i];
|
|
3358
|
+
if (!isPlainObject(current[key]))
|
|
3359
|
+
current[key] = {};
|
|
3360
|
+
current = current[key];
|
|
3361
|
+
}
|
|
3362
|
+
current[keys[keys.length - 1]] = value;
|
|
3363
|
+
}
|
|
3364
|
+
/** Returns true if a dot-path resolves to a defined value. */
|
|
3365
|
+
function hasPath(obj, path) {
|
|
3366
|
+
return getPath(obj, path) !== undefined;
|
|
3367
|
+
}
|
|
3368
|
+
/** Applies a mapping function to every value in an object. */
|
|
3369
|
+
function mapValues(obj, fn) {
|
|
3370
|
+
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v, k)]));
|
|
3371
|
+
}
|
|
3372
|
+
/** Returns a new object containing only entries where the predicate returns true. */
|
|
3373
|
+
function filterValues(obj, fn) {
|
|
3374
|
+
return Object.fromEntries(Object.entries(obj).filter(([k, v]) => fn(v, k)));
|
|
3375
|
+
}
|
|
3376
|
+
/** Swaps keys and values in an object (both must be strings). */
|
|
3377
|
+
function invertObject(obj) {
|
|
3378
|
+
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k]));
|
|
3379
|
+
}
|
|
3380
|
+
/** Returns a new object with keys sorted alphabetically. */
|
|
3381
|
+
function sortByKey(obj) {
|
|
3382
|
+
return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Returns the keys of obj2 whose values differ from obj1 (shallow comparison).
|
|
3386
|
+
* Useful for detecting changed form fields.
|
|
3387
|
+
*/
|
|
3388
|
+
function diff(obj1, obj2) {
|
|
3389
|
+
const result = {};
|
|
3390
|
+
for (const key in obj2) {
|
|
3391
|
+
if (obj1[key] !== obj2[key]) {
|
|
3392
|
+
result[key] = obj2[key];
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
return result;
|
|
3396
|
+
}
|
|
3397
|
+
/** Returns the number of own enumerable keys. */
|
|
3398
|
+
function size(obj) {
|
|
3399
|
+
return Object.keys(obj).length;
|
|
3400
|
+
}
|
|
3401
|
+
/** Returns true if `obj` has the given own enumerable key. */
|
|
3402
|
+
function hasKey(obj, key) {
|
|
3403
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
var object_util = /*#__PURE__*/Object.freeze({
|
|
3407
|
+
__proto__: null,
|
|
3408
|
+
deepClone: deepClone,
|
|
3409
|
+
deepMerge: deepMerge,
|
|
3410
|
+
diff: diff,
|
|
3411
|
+
filterValues: filterValues,
|
|
3412
|
+
flattenObject: flattenObject,
|
|
3413
|
+
fromQueryString: fromQueryString,
|
|
3414
|
+
getPath: getPath,
|
|
3415
|
+
hasKey: hasKey,
|
|
3416
|
+
hasPath: hasPath,
|
|
3417
|
+
invertObject: invertObject,
|
|
3418
|
+
isEmpty: isEmpty$1,
|
|
3419
|
+
isEqual: isEqual,
|
|
3420
|
+
isPlainObject: isPlainObject,
|
|
3421
|
+
mapValues: mapValues,
|
|
3422
|
+
omit: omit,
|
|
3423
|
+
pick: pick,
|
|
3424
|
+
setPath: setPath,
|
|
3425
|
+
size: size,
|
|
3426
|
+
sortByKey: sortByKey,
|
|
3427
|
+
toQueryString: toQueryString,
|
|
3428
|
+
unflattenObject: unflattenObject
|
|
3429
|
+
});
|
|
3430
|
+
|
|
3431
|
+
// ─── Storage Utilities ───────────────────────────────────────────────────────
|
|
3432
|
+
// Values are JSON-serialized before storage and deserialized on retrieval.
|
|
3433
|
+
// ── Local Storage ────────────────────────────────────────────────────────────
|
|
3434
|
+
/** Serializes `value` and writes it to localStorage. */
|
|
3435
|
+
function setLocal(key, value) {
|
|
3436
|
+
try {
|
|
3437
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
3438
|
+
}
|
|
3439
|
+
catch {
|
|
3440
|
+
/* quota exceeded or private browsing — silently ignore */
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
/** Reads and deserializes a value from localStorage. Returns null if absent or invalid. */
|
|
3444
|
+
function getLocal(key) {
|
|
3445
|
+
try {
|
|
3446
|
+
const item = localStorage.getItem(key);
|
|
3447
|
+
return item !== null ? JSON.parse(item) : null;
|
|
3448
|
+
}
|
|
3449
|
+
catch {
|
|
3450
|
+
return null;
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
/** Removes a key from localStorage. */
|
|
3454
|
+
function removeLocal(key) {
|
|
3455
|
+
localStorage.removeItem(key);
|
|
3456
|
+
}
|
|
3457
|
+
/** Clears all entries from localStorage. */
|
|
3458
|
+
function clearLocal() {
|
|
3459
|
+
localStorage.clear();
|
|
3460
|
+
}
|
|
3461
|
+
/** Returns true if the key exists in localStorage. */
|
|
3462
|
+
function hasLocal(key) {
|
|
3463
|
+
return localStorage.getItem(key) !== null;
|
|
3464
|
+
}
|
|
3465
|
+
/** Returns all keys currently in localStorage. */
|
|
3466
|
+
function localKeys() {
|
|
3467
|
+
return Object.keys(localStorage);
|
|
3468
|
+
}
|
|
3469
|
+
// ── Session Storage ───────────────────────────────────────────────────────────
|
|
3470
|
+
/** Serializes `value` and writes it to sessionStorage. */
|
|
3471
|
+
function setSession(key, value) {
|
|
3472
|
+
try {
|
|
3473
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
3474
|
+
}
|
|
3475
|
+
catch {
|
|
3476
|
+
/* quota exceeded — silently ignore */
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
/** Reads and deserializes a value from sessionStorage. Returns null if absent or invalid. */
|
|
3480
|
+
function getSession(key) {
|
|
3481
|
+
try {
|
|
3482
|
+
const item = sessionStorage.getItem(key);
|
|
3483
|
+
return item !== null ? JSON.parse(item) : null;
|
|
3484
|
+
}
|
|
3485
|
+
catch {
|
|
3486
|
+
return null;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
/** Removes a key from sessionStorage. */
|
|
3490
|
+
function removeSession(key) {
|
|
3491
|
+
sessionStorage.removeItem(key);
|
|
3492
|
+
}
|
|
3493
|
+
/** Clears all entries from sessionStorage. */
|
|
3494
|
+
function clearSession() {
|
|
3495
|
+
sessionStorage.clear();
|
|
3496
|
+
}
|
|
3497
|
+
/** Returns true if the key exists in sessionStorage. */
|
|
3498
|
+
function hasSession(key) {
|
|
3499
|
+
return sessionStorage.getItem(key) !== null;
|
|
3500
|
+
}
|
|
3501
|
+
/** Returns all keys currently in sessionStorage. */
|
|
3502
|
+
function sessionKeys() {
|
|
3503
|
+
return Object.keys(sessionStorage);
|
|
3504
|
+
}
|
|
3505
|
+
// ── Cookies ──────────────────────────────────────────────────────────────────
|
|
3506
|
+
/**
|
|
3507
|
+
* Sets a cookie.
|
|
3508
|
+
* @param days - Expiry in days. Omit or pass 0 for a session cookie.
|
|
3509
|
+
* @param path - Cookie path (default '/').
|
|
3510
|
+
*/
|
|
3511
|
+
function setCookie(name, value, days, path = '/') {
|
|
3512
|
+
let expires = '';
|
|
3513
|
+
if (days) {
|
|
3514
|
+
const date = new Date();
|
|
3515
|
+
date.setTime(date.getTime() + days * 86_400_000);
|
|
3516
|
+
expires = `; expires=${date.toUTCString()}`;
|
|
3517
|
+
}
|
|
3518
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=${path}; SameSite=Lax`;
|
|
3519
|
+
}
|
|
3520
|
+
/** Returns the decoded cookie value, or null if not found. */
|
|
3521
|
+
function getCookie(name) {
|
|
3522
|
+
const key = `${encodeURIComponent(name)}=`;
|
|
3523
|
+
for (const part of document.cookie.split(';')) {
|
|
3524
|
+
const trimmed = part.trimStart();
|
|
3525
|
+
if (trimmed.startsWith(key)) {
|
|
3526
|
+
return decodeURIComponent(trimmed.slice(key.length));
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
return null;
|
|
3530
|
+
}
|
|
3531
|
+
/** Removes a cookie by setting its expiry to the past. */
|
|
3532
|
+
function removeCookie(name, path = '/') {
|
|
3533
|
+
document.cookie = `${encodeURIComponent(name)}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}`;
|
|
3534
|
+
}
|
|
3535
|
+
/** Returns all current cookies as a key-value map (both decoded). */
|
|
3536
|
+
function getAllCookies() {
|
|
3537
|
+
return document.cookie
|
|
3538
|
+
.split(';')
|
|
3539
|
+
.reduce((acc, part) => {
|
|
3540
|
+
const [k, ...rest] = part.trim().split('=');
|
|
3541
|
+
if (k)
|
|
3542
|
+
acc[decodeURIComponent(k)] = decodeURIComponent(rest.join('='));
|
|
3543
|
+
return acc;
|
|
3544
|
+
}, {});
|
|
3545
|
+
}
|
|
3546
|
+
/** Returns true if the cookie exists. */
|
|
3547
|
+
function hasCookie(name) {
|
|
3548
|
+
return getCookie(name) !== null;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
var storage_util = /*#__PURE__*/Object.freeze({
|
|
3552
|
+
__proto__: null,
|
|
3553
|
+
clearLocal: clearLocal,
|
|
3554
|
+
clearSession: clearSession,
|
|
3555
|
+
getAllCookies: getAllCookies,
|
|
3556
|
+
getCookie: getCookie,
|
|
3557
|
+
getLocal: getLocal,
|
|
3558
|
+
getSession: getSession,
|
|
3559
|
+
hasCookie: hasCookie,
|
|
3560
|
+
hasLocal: hasLocal,
|
|
3561
|
+
hasSession: hasSession,
|
|
3562
|
+
localKeys: localKeys,
|
|
3563
|
+
removeCookie: removeCookie,
|
|
3564
|
+
removeLocal: removeLocal,
|
|
3565
|
+
removeSession: removeSession,
|
|
3566
|
+
sessionKeys: sessionKeys,
|
|
3567
|
+
setCookie: setCookie,
|
|
3568
|
+
setLocal: setLocal,
|
|
3569
|
+
setSession: setSession
|
|
3570
|
+
});
|
|
3571
|
+
|
|
3572
|
+
// ─── String Utilities ────────────────────────────────────────────────────────
|
|
3573
|
+
/** Returns true if the value is null, undefined, or an empty string. */
|
|
3574
|
+
function isEmpty(str) {
|
|
3575
|
+
return str == null || str.length === 0;
|
|
3576
|
+
}
|
|
3577
|
+
/** Returns true if the value is null, undefined, empty, or whitespace only. */
|
|
3578
|
+
function isBlank(str) {
|
|
3579
|
+
return str == null || str.trim().length === 0;
|
|
3580
|
+
}
|
|
3581
|
+
/** Capitalizes the first letter; lowercases the rest. */
|
|
3582
|
+
function capitalize(str) {
|
|
3583
|
+
if (!str)
|
|
3584
|
+
return '';
|
|
3585
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
3586
|
+
}
|
|
3587
|
+
/** Converts each word's first letter to uppercase. */
|
|
3588
|
+
function titleCase(str) {
|
|
3589
|
+
return str.replace(/\w\S*/g, word => capitalize(word));
|
|
3590
|
+
}
|
|
3591
|
+
/** Converts a string to camelCase. */
|
|
3592
|
+
function camelCase(str) {
|
|
3593
|
+
return str
|
|
3594
|
+
.replace(/[^a-zA-Z0-9]+(.)/g, (_, char) => char.toUpperCase())
|
|
3595
|
+
.replace(/^[A-Z]/, c => c.toLowerCase());
|
|
3596
|
+
}
|
|
3597
|
+
/** Converts a string to PascalCase. */
|
|
3598
|
+
function pascalCase(str) {
|
|
3599
|
+
const cc = camelCase(str);
|
|
3600
|
+
return cc.charAt(0).toUpperCase() + cc.slice(1);
|
|
3601
|
+
}
|
|
3602
|
+
/** Converts a string to snake_case. */
|
|
3603
|
+
function snakeCase(str) {
|
|
3604
|
+
return str
|
|
3605
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
3606
|
+
.replace(/[\s\-]+/g, '_')
|
|
3607
|
+
.toLowerCase();
|
|
3608
|
+
}
|
|
3609
|
+
/** Converts a string to kebab-case. */
|
|
3610
|
+
function kebabCase(str) {
|
|
3611
|
+
return str
|
|
3612
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
3613
|
+
.replace(/[\s_]+/g, '-')
|
|
3614
|
+
.toLowerCase();
|
|
3615
|
+
}
|
|
3616
|
+
/** Truncates a string to the given length, appending a suffix if cut. */
|
|
3617
|
+
function truncate(str, length, suffix = '...') {
|
|
3618
|
+
if (str.length <= length)
|
|
3619
|
+
return str;
|
|
3620
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
3621
|
+
}
|
|
3622
|
+
/** Removes all HTML tags from a string. */
|
|
3623
|
+
function stripHtml(str) {
|
|
3624
|
+
return str.replace(/<[^>]*>/g, '');
|
|
3625
|
+
}
|
|
3626
|
+
/** Escapes HTML special characters. */
|
|
3627
|
+
function escapeHtml(str) {
|
|
3628
|
+
return str
|
|
3629
|
+
.replace(/&/g, '&')
|
|
3630
|
+
.replace(/</g, '<')
|
|
3631
|
+
.replace(/>/g, '>')
|
|
3632
|
+
.replace(/"/g, '"')
|
|
3633
|
+
.replace(/'/g, ''');
|
|
3634
|
+
}
|
|
3635
|
+
/** Unescapes HTML entities back to characters. */
|
|
3636
|
+
function unescapeHtml(str) {
|
|
3637
|
+
return str
|
|
3638
|
+
.replace(/&/g, '&')
|
|
3639
|
+
.replace(/</g, '<')
|
|
3640
|
+
.replace(/>/g, '>')
|
|
3641
|
+
.replace(/"/g, '"')
|
|
3642
|
+
.replace(/'/g, "'");
|
|
3643
|
+
}
|
|
3644
|
+
/** Converts a string to a URL-friendly slug. */
|
|
3645
|
+
function toSlug(str) {
|
|
3646
|
+
return str
|
|
3647
|
+
.toLowerCase()
|
|
3648
|
+
.normalize('NFD')
|
|
3649
|
+
.replace(/[̀-ͯ]/g, '')
|
|
3650
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
3651
|
+
.trim()
|
|
3652
|
+
.replace(/[\s_-]+/g, '-')
|
|
3653
|
+
.replace(/^-+|-+$/g, '');
|
|
3654
|
+
}
|
|
3655
|
+
/** Counts the number of words in a string. */
|
|
3656
|
+
function countWords(str) {
|
|
3657
|
+
return str.trim().split(/\s+/).filter(Boolean).length;
|
|
3658
|
+
}
|
|
3659
|
+
/** Extracts initials from a name (up to `maxLetters`). */
|
|
3660
|
+
function initials(name, maxLetters = 2) {
|
|
3661
|
+
return name
|
|
3662
|
+
.split(/\s+/)
|
|
3663
|
+
.slice(0, maxLetters)
|
|
3664
|
+
.map(w => w.charAt(0).toUpperCase())
|
|
3665
|
+
.join('');
|
|
3666
|
+
}
|
|
3667
|
+
/** Case-insensitive containment check. */
|
|
3668
|
+
function contains(str, search) {
|
|
3669
|
+
return str.toLowerCase().includes(search.toLowerCase());
|
|
3670
|
+
}
|
|
3671
|
+
/**
|
|
3672
|
+
* Replaces `{{key}}` placeholders in a template with values from the map.
|
|
3673
|
+
* @example format('Hello {{name}}', { name: 'World' }) → 'Hello World'
|
|
3674
|
+
*/
|
|
3675
|
+
function format(template, values) {
|
|
3676
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => key in values ? String(values[key]) : `{{${key}}}`);
|
|
3677
|
+
}
|
|
3678
|
+
/** Repeats a string `times` times. */
|
|
3679
|
+
function repeat(str, times) {
|
|
3680
|
+
return str.repeat(Math.max(0, times));
|
|
3681
|
+
}
|
|
3682
|
+
/** Reverses the characters of a string. */
|
|
3683
|
+
function reverseString(str) {
|
|
3684
|
+
return [...str].reverse().join('');
|
|
3685
|
+
}
|
|
3686
|
+
/** Counts non-overlapping occurrences of `search` in `str`. */
|
|
3687
|
+
function countOccurrences(str, search) {
|
|
3688
|
+
if (!search)
|
|
3689
|
+
return 0;
|
|
3690
|
+
return str.split(search).length - 1;
|
|
3691
|
+
}
|
|
3692
|
+
/** Removes characters not in `[a-zA-Z0-9]` plus anything in `preserve`. */
|
|
3693
|
+
function removeSpecialChars(str, preserve = '') {
|
|
3694
|
+
const escaped = preserve.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
3695
|
+
return str.replace(new RegExp(`[^a-zA-Z0-9${escaped}]`, 'g'), '');
|
|
3696
|
+
}
|
|
3697
|
+
/**
|
|
3698
|
+
* Masks part of a string, keeping `visibleChars` visible at the end.
|
|
3699
|
+
* @example mask('4111111111111234', 4) → '************1234'
|
|
3700
|
+
*/
|
|
3701
|
+
function mask(str, visibleChars = 4, maskChar = '*') {
|
|
3702
|
+
if (str.length <= visibleChars)
|
|
3703
|
+
return str;
|
|
3704
|
+
return maskChar.repeat(str.length - visibleChars) + str.slice(-visibleChars);
|
|
3705
|
+
}
|
|
3706
|
+
/** Parses common truthy strings ("true", "yes", "1", "on") to boolean. */
|
|
3707
|
+
function toBoolean(str) {
|
|
3708
|
+
return ['true', 'yes', '1', 'on'].includes(str.trim().toLowerCase());
|
|
3709
|
+
}
|
|
3710
|
+
/** Pads the start of a string. */
|
|
3711
|
+
function padStart(str, length, padChar = ' ') {
|
|
3712
|
+
return str.padStart(length, padChar);
|
|
3713
|
+
}
|
|
3714
|
+
/** Pads the end of a string. */
|
|
3715
|
+
function padEnd(str, length, padChar = ' ') {
|
|
3716
|
+
return str.padEnd(length, padChar);
|
|
3717
|
+
}
|
|
3718
|
+
/** Generates a random alphanumeric string of the given length. */
|
|
3719
|
+
function randomString(length) {
|
|
3720
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
3721
|
+
return Array.from({ length }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join('');
|
|
3722
|
+
}
|
|
3723
|
+
/** Removes all whitespace from a string. */
|
|
3724
|
+
function removeWhitespace(str) {
|
|
3725
|
+
return str.replace(/\s+/g, '');
|
|
3726
|
+
}
|
|
3727
|
+
/** Checks if a string is a palindrome (case- and space-insensitive). */
|
|
3728
|
+
function isPalindrome(str) {
|
|
3729
|
+
const normalized = str.toLowerCase().replace(/\s+/g, '');
|
|
3730
|
+
return normalized === [...normalized].reverse().join('');
|
|
3731
|
+
}
|
|
3732
|
+
/** Wraps a string at `width` characters, splitting on spaces where possible. */
|
|
3733
|
+
function wordWrap(str, width) {
|
|
3734
|
+
const words = str.split(' ');
|
|
3735
|
+
const lines = [];
|
|
3736
|
+
let current = '';
|
|
3737
|
+
for (const word of words) {
|
|
3738
|
+
if ((current + (current ? ' ' : '') + word).length <= width) {
|
|
3739
|
+
current += (current ? ' ' : '') + word;
|
|
3740
|
+
}
|
|
3741
|
+
else {
|
|
3742
|
+
if (current)
|
|
3743
|
+
lines.push(current);
|
|
3744
|
+
current = word;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
if (current)
|
|
3748
|
+
lines.push(current);
|
|
3749
|
+
return lines.join('\n');
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
var string_util = /*#__PURE__*/Object.freeze({
|
|
3753
|
+
__proto__: null,
|
|
3754
|
+
camelCase: camelCase,
|
|
3755
|
+
capitalize: capitalize,
|
|
3756
|
+
contains: contains,
|
|
3757
|
+
countOccurrences: countOccurrences,
|
|
3758
|
+
countWords: countWords,
|
|
3759
|
+
escapeHtml: escapeHtml,
|
|
3760
|
+
format: format,
|
|
3761
|
+
initials: initials,
|
|
3762
|
+
isBlank: isBlank,
|
|
3763
|
+
isEmpty: isEmpty,
|
|
3764
|
+
isPalindrome: isPalindrome,
|
|
3765
|
+
kebabCase: kebabCase,
|
|
3766
|
+
mask: mask,
|
|
3767
|
+
padEnd: padEnd,
|
|
3768
|
+
padStart: padStart,
|
|
3769
|
+
pascalCase: pascalCase,
|
|
3770
|
+
randomString: randomString,
|
|
3771
|
+
removeSpecialChars: removeSpecialChars,
|
|
3772
|
+
removeWhitespace: removeWhitespace,
|
|
3773
|
+
repeat: repeat,
|
|
3774
|
+
reverseString: reverseString,
|
|
3775
|
+
snakeCase: snakeCase,
|
|
3776
|
+
stripHtml: stripHtml,
|
|
3777
|
+
titleCase: titleCase,
|
|
3778
|
+
toBoolean: toBoolean,
|
|
3779
|
+
toSlug: toSlug,
|
|
3780
|
+
truncate: truncate,
|
|
3781
|
+
unescapeHtml: unescapeHtml,
|
|
3782
|
+
wordWrap: wordWrap
|
|
3783
|
+
});
|
|
3784
|
+
|
|
3785
|
+
// ─── Validation Utilities ────────────────────────────────────────────────────
|
|
3786
|
+
/** Returns true if value is not null, undefined, empty string, or whitespace. */
|
|
3787
|
+
function isRequired(value) {
|
|
3788
|
+
if (value == null)
|
|
3789
|
+
return false;
|
|
3790
|
+
if (typeof value === 'string')
|
|
3791
|
+
return value.trim().length > 0;
|
|
3792
|
+
if (Array.isArray(value))
|
|
3793
|
+
return value.length > 0;
|
|
3794
|
+
return true;
|
|
3795
|
+
}
|
|
3796
|
+
/** RFC 5322-compliant email validation. */
|
|
3797
|
+
function isEmail(value) {
|
|
3798
|
+
return /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/.test(value.trim());
|
|
3799
|
+
}
|
|
3800
|
+
/** Returns true if the string is a valid HTTP/HTTPS URL. */
|
|
3801
|
+
function isUrl(value) {
|
|
3802
|
+
try {
|
|
3803
|
+
const url = new URL(value);
|
|
3804
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
3805
|
+
}
|
|
3806
|
+
catch {
|
|
3807
|
+
return false;
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* Returns true if the string looks like a phone number.
|
|
3812
|
+
* Accepts formats: +1-800-555-5555, (800) 555 5555, 08001234567, etc.
|
|
3813
|
+
*/
|
|
3814
|
+
function isPhone(value) {
|
|
3815
|
+
return /^\+?[\d\s\-().]{7,20}$/.test(value.trim());
|
|
3816
|
+
}
|
|
3817
|
+
/**
|
|
3818
|
+
* Luhn algorithm — validates credit card numbers.
|
|
3819
|
+
* Works for Visa, MasterCard, Amex, Discover, etc.
|
|
3820
|
+
*/
|
|
3821
|
+
function isCreditCard(value) {
|
|
3822
|
+
const digits = value.replace(/\D/g, '');
|
|
3823
|
+
if (digits.length < 13 || digits.length > 19)
|
|
3824
|
+
return false;
|
|
3825
|
+
let sum = 0;
|
|
3826
|
+
let isEven = false;
|
|
3827
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
3828
|
+
let digit = parseInt(digits[i], 10);
|
|
3829
|
+
if (isEven) {
|
|
3830
|
+
digit *= 2;
|
|
3831
|
+
if (digit > 9)
|
|
3832
|
+
digit -= 9;
|
|
3833
|
+
}
|
|
3834
|
+
sum += digit;
|
|
3835
|
+
isEven = !isEven;
|
|
3836
|
+
}
|
|
3837
|
+
return sum % 10 === 0;
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* Validates postal / ZIP codes.
|
|
3841
|
+
* Supports: US (12345 / 12345-6789), UK (SW1A 1AA), CA (K1A 0B1),
|
|
3842
|
+
* DE/FR/AU/IN (5-digit numeric).
|
|
3843
|
+
*/
|
|
3844
|
+
function isPostalCode(value, countryCode = 'US') {
|
|
3845
|
+
const patterns = {
|
|
3846
|
+
US: /^\d{5}(-\d{4})?$/,
|
|
3847
|
+
UK: /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i,
|
|
3848
|
+
CA: /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i,
|
|
3849
|
+
DE: /^\d{5}$/,
|
|
3850
|
+
FR: /^\d{5}$/,
|
|
3851
|
+
AU: /^\d{4}$/,
|
|
3852
|
+
IN: /^\d{6}$/,
|
|
3853
|
+
};
|
|
3854
|
+
const pattern = patterns[countryCode.toUpperCase()] ?? /^\d{4,10}$/;
|
|
3855
|
+
return pattern.test(value.trim());
|
|
3856
|
+
}
|
|
3857
|
+
/** Returns true if the string contains only ASCII letters. */
|
|
3858
|
+
function isAlpha(value) {
|
|
3859
|
+
return /^[a-zA-Z]+$/.test(value);
|
|
3860
|
+
}
|
|
3861
|
+
/** Returns true if the string contains only ASCII letters and digits. */
|
|
3862
|
+
function isAlphanumeric(value) {
|
|
3863
|
+
return /^[a-zA-Z0-9]+$/.test(value);
|
|
3864
|
+
}
|
|
3865
|
+
/** Returns true if the string contains only digits (no decimal points). */
|
|
3866
|
+
function isNumeric(value) {
|
|
3867
|
+
return /^\d+$/.test(value.trim());
|
|
3868
|
+
}
|
|
3869
|
+
/** Returns true if the string represents a valid decimal number. */
|
|
3870
|
+
function isDecimal(value) {
|
|
3871
|
+
return /^-?\d+(\.\d+)?$/.test(value.trim());
|
|
3872
|
+
}
|
|
3873
|
+
/** Returns true if `value` is a positive number (> 0). */
|
|
3874
|
+
function isPositive(value) {
|
|
3875
|
+
return value > 0;
|
|
3876
|
+
}
|
|
3877
|
+
/** Returns true if `value` is a negative number (< 0). */
|
|
3878
|
+
function isNegative(value) {
|
|
3879
|
+
return value < 0;
|
|
3880
|
+
}
|
|
3881
|
+
/** Returns true if the string length is at least `min`. */
|
|
3882
|
+
function minLength(value, min) {
|
|
3883
|
+
return value.length >= min;
|
|
3884
|
+
}
|
|
3885
|
+
/** Returns true if the string length does not exceed `max`. */
|
|
3886
|
+
function maxLength(value, max) {
|
|
3887
|
+
return value.length <= max;
|
|
3888
|
+
}
|
|
3889
|
+
/** Returns true if the string length is within [min, max] (inclusive). */
|
|
3890
|
+
function lengthBetween(value, min, max) {
|
|
3891
|
+
return value.length >= min && value.length <= max;
|
|
3892
|
+
}
|
|
3893
|
+
/** Returns true if `value` is at least `min`. */
|
|
3894
|
+
function minValue(value, min) {
|
|
3895
|
+
return value >= min;
|
|
3896
|
+
}
|
|
3897
|
+
/** Returns true if `value` does not exceed `max`. */
|
|
3898
|
+
function maxValue(value, max) {
|
|
3899
|
+
return value <= max;
|
|
3900
|
+
}
|
|
3901
|
+
/** Returns true if `value` is within [min, max] (inclusive). */
|
|
3902
|
+
function inRange(value, min, max) {
|
|
3903
|
+
return value >= min && value <= max;
|
|
3904
|
+
}
|
|
3905
|
+
/** Returns true if the value is a valid Date or a parseable date string. */
|
|
3906
|
+
function isValidDate(value) {
|
|
3907
|
+
if (value == null)
|
|
3908
|
+
return false;
|
|
3909
|
+
const d = new Date(value);
|
|
3910
|
+
return !isNaN(d.getTime());
|
|
3911
|
+
}
|
|
3912
|
+
/** Returns true if the string is a valid dotted-decimal IPv4 address. */
|
|
3913
|
+
function isIPv4(value) {
|
|
3914
|
+
return /^(\d{1,3}\.){3}\d{1,3}$/.test(value) &&
|
|
3915
|
+
value.split('.').every(octet => parseInt(octet, 10) <= 255);
|
|
3916
|
+
}
|
|
3917
|
+
/** Returns true if the string is a valid IPv6 address. */
|
|
3918
|
+
function isIPv6(value) {
|
|
3919
|
+
return /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i.test(value) ||
|
|
3920
|
+
/^([\da-f]{1,4}:)*::([\da-f]{1,4}:)*[\da-f]{1,4}$/i.test(value) ||
|
|
3921
|
+
value === '::';
|
|
3922
|
+
}
|
|
3923
|
+
/** Returns true if the string matches the given pattern (string or RegExp). */
|
|
3924
|
+
function matchesPattern(value, pattern) {
|
|
3925
|
+
const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
|
|
3926
|
+
return regex.test(value);
|
|
3927
|
+
}
|
|
3928
|
+
/** Returns true if the string is a valid MAC address (with : or - separator). */
|
|
3929
|
+
function isMACAddress(value) {
|
|
3930
|
+
return /^([0-9A-Fa-f]{2}[:\-]){5}[0-9A-Fa-f]{2}$/.test(value);
|
|
3931
|
+
}
|
|
3932
|
+
/** Returns true if the string is a valid CSS hex color (#RGB or #RRGGBB). */
|
|
3933
|
+
function isHexColor(value) {
|
|
3934
|
+
return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value);
|
|
3935
|
+
}
|
|
3936
|
+
/**
|
|
3937
|
+
* Returns true if the password meets the given strength requirements.
|
|
3938
|
+
* Default: min 8 chars, at least one uppercase, lowercase, number, and special character.
|
|
3939
|
+
*/
|
|
3940
|
+
function isStrongPassword(value, options = {}) {
|
|
3941
|
+
const { minLength = 8, requireUppercase = true, requireLowercase = true, requireNumber = true, requireSpecial = true, } = options;
|
|
3942
|
+
if (value.length < minLength)
|
|
3943
|
+
return false;
|
|
3944
|
+
if (requireUppercase && !/[A-Z]/.test(value))
|
|
3945
|
+
return false;
|
|
3946
|
+
if (requireLowercase && !/[a-z]/.test(value))
|
|
3947
|
+
return false;
|
|
3948
|
+
if (requireNumber && !/\d/.test(value))
|
|
3949
|
+
return false;
|
|
3950
|
+
if (requireSpecial && !/[^a-zA-Z0-9]/.test(value))
|
|
3951
|
+
return false;
|
|
3952
|
+
return true;
|
|
3953
|
+
}
|
|
3954
|
+
/** Returns a password strength score: 0 (very weak) – 5 (very strong). */
|
|
3955
|
+
function passwordStrength(value) {
|
|
3956
|
+
let score = 0;
|
|
3957
|
+
if (value.length >= 8)
|
|
3958
|
+
score++;
|
|
3959
|
+
if (value.length >= 12)
|
|
3960
|
+
score++;
|
|
3961
|
+
if (/[A-Z]/.test(value))
|
|
3962
|
+
score++;
|
|
3963
|
+
if (/\d/.test(value))
|
|
3964
|
+
score++;
|
|
3965
|
+
if (/[^a-zA-Z0-9]/.test(value))
|
|
3966
|
+
score++;
|
|
3967
|
+
return score;
|
|
3968
|
+
}
|
|
3969
|
+
/** Returns true if the two values are strictly equal (useful for confirm-password fields). */
|
|
3970
|
+
function isMatch(a, b) {
|
|
3971
|
+
return a === b;
|
|
3972
|
+
}
|
|
3973
|
+
/** Returns true if the string is a valid JSON string. */
|
|
3974
|
+
function isJSON(value) {
|
|
3975
|
+
try {
|
|
3976
|
+
JSON.parse(value);
|
|
3977
|
+
return true;
|
|
3978
|
+
}
|
|
3979
|
+
catch {
|
|
3980
|
+
return false;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
/** Returns true if the string contains only printable ASCII characters. */
|
|
3984
|
+
function isASCII(value) {
|
|
3985
|
+
return /^[\x20-\x7E]*$/.test(value);
|
|
3986
|
+
}
|
|
3987
|
+
/** Returns true if the number is a whole integer (no fractional part). */
|
|
3988
|
+
function isInteger(value) {
|
|
3989
|
+
return Number.isInteger(value);
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
var validation_util = /*#__PURE__*/Object.freeze({
|
|
3993
|
+
__proto__: null,
|
|
3994
|
+
inRange: inRange,
|
|
3995
|
+
isASCII: isASCII,
|
|
3996
|
+
isAlpha: isAlpha,
|
|
3997
|
+
isAlphanumeric: isAlphanumeric,
|
|
3998
|
+
isCreditCard: isCreditCard,
|
|
3999
|
+
isDecimal: isDecimal,
|
|
4000
|
+
isEmail: isEmail,
|
|
4001
|
+
isHexColor: isHexColor,
|
|
4002
|
+
isIPv4: isIPv4,
|
|
4003
|
+
isIPv6: isIPv6,
|
|
4004
|
+
isInteger: isInteger,
|
|
4005
|
+
isJSON: isJSON,
|
|
4006
|
+
isMACAddress: isMACAddress,
|
|
4007
|
+
isMatch: isMatch,
|
|
4008
|
+
isNegative: isNegative,
|
|
4009
|
+
isNumeric: isNumeric,
|
|
4010
|
+
isPhone: isPhone,
|
|
4011
|
+
isPositive: isPositive,
|
|
4012
|
+
isPostalCode: isPostalCode,
|
|
4013
|
+
isRequired: isRequired,
|
|
4014
|
+
isStrongPassword: isStrongPassword,
|
|
4015
|
+
isUrl: isUrl,
|
|
4016
|
+
isValidDate: isValidDate,
|
|
4017
|
+
lengthBetween: lengthBetween,
|
|
4018
|
+
matchesPattern: matchesPattern,
|
|
4019
|
+
maxLength: maxLength,
|
|
4020
|
+
maxValue: maxValue,
|
|
4021
|
+
minLength: minLength,
|
|
4022
|
+
minValue: minValue,
|
|
4023
|
+
passwordStrength: passwordStrength
|
|
4024
|
+
});
|
|
4025
|
+
|
|
2481
4026
|
/*
|
|
2482
4027
|
* Public API Surface of osl-base-extended
|
|
2483
4028
|
*/
|
|
@@ -2486,5 +4031,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2486
4031
|
* Generated bundle index. Do not edit.
|
|
2487
4032
|
*/
|
|
2488
4033
|
|
|
2489
|
-
export { DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslFileUpload, OslFormGrid, OslGrid, OslRadio, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, Oslinput, Osltextarea, baseComponent };
|
|
4034
|
+
export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslFileUpload, OslFormGrid, OslGrid, OslRadio, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
|
|
2490
4035
|
//# sourceMappingURL=osl-base-extended.mjs.map
|