Initial commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
*.md
|
||||
node_modules
|
||||
dist
|
||||
public
|
||||
build
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"printWidth": 150,
|
||||
"tabWidth": 2,
|
||||
"endOfLine": "lf",
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"semi": true,
|
||||
"bracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"arrowParens": "always",
|
||||
"importOrderCaseSensitive": false
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# [←](../../docs/README.md) @crux-kit/utils
|
||||
|
||||
A collection of utility functions for type conversion, formatting, DOM, and browser detection.
|
||||
|
||||
## Available Modules
|
||||
|
||||
| Module | Description | Docs |
|
||||
| ------------ | ------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `array` | Array dedup and exclusion utilities | [Readme.md](./src/array/Readme.md) |
|
||||
| `object` | Typed object utilities — `keys`, `pick`, `omit` | [Readme.md](./src/object/Readme.md) |
|
||||
| `converters` | Type and format conversions (base64, booleans, strings, case, mask) | [Readme.md](./src/converters/README.md) |
|
||||
| `detectors` | Detect user locale, device type, date order, and time format | [Readme.md](./src/detectors/README.md) |
|
||||
| `formaters` | Format numbers and byte sizes into human-readable strings | [Readme.md](./src/formaters/Readme.md) |
|
||||
| `helpers` | File download, localStorage, sleep, throttle, debounce | [Readme.md](./src/helpers/Readme.md) |
|
||||
| `random` | Generate random numbers, string keys, and UIDs | [Readme.md](./src/random/Readme.md) |
|
||||
| `dom` | DOM element utilities (height with margins) | [Readme.md](./src/dom/Readme.md) |
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@crux-kit/utils",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"types": "src/index.ts",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@crux-kit/types": "*"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# [←](../../Readme.md) Огляд Array утиліт
|
||||
|
||||
### # arrayDedup\<T>(arr: T[]): T[]
|
||||
|
||||
Видаляє дублікати з масиву об'єктів або складних значень. Порівняння відбувається через детермінований stringify (ключі об'єктів сортуються).
|
||||
|
||||
```ts
|
||||
arrayDedup([{ a: 1 }, { a: 1 }, { a: 2 }]); // → [{ a: 1 }, { a: 2 }]
|
||||
arrayDedup([1, 'x', 1, null, null]); // → [1, 'x', null]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # arrayDedupPrimitives(array: Array\<number | string | null | undefined>): Array
|
||||
|
||||
Видаляє дублікати з масиву примітивних значень через `Set`. Швидший варіант для простих типів.
|
||||
|
||||
```ts
|
||||
arrayDedupPrimitives([1, 2, 2, 3]); // → [1, 2, 3]
|
||||
arrayDedupPrimitives(['a', 'b', 'a']); // → ['a', 'b']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # without\<T>(excludeValues: Iterable\<T>, values: T[]): T[]
|
||||
|
||||
Повертає масив без вказаних значень.
|
||||
|
||||
```ts
|
||||
without([2, 4], [1, 2, 3, 4, 5]); // → [1, 3, 5]
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
type Value = string | number | boolean | null | undefined | Value[] | { [key: string]: Value };
|
||||
|
||||
export function arrayDedup<T extends Value>(arr: T[]): T[] {
|
||||
const seen = new Map<string, T>();
|
||||
|
||||
for (const item of arr) {
|
||||
const key = stableStringify(item);
|
||||
if (!seen.has(key)) {
|
||||
seen.set(key, item);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(seen.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic stringify:
|
||||
* - sorts object keys
|
||||
* - preserves arrays order
|
||||
* - distinguishes undefined/null
|
||||
*/
|
||||
function stableStringify(value: Value): string {
|
||||
if (value === undefined) return 'undefined';
|
||||
if (value === null) return 'null';
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map(stableStringify).join(',')}]`;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
const keys = Object.keys(value).sort();
|
||||
return `{${keys.map((k) => `${k}:${stableStringify(value[k])}`).join(',')}}`;
|
||||
}
|
||||
|
||||
return `${typeof value}:${String(value)}`;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function arrayDedupPrimitives(array: Array<number | string | null | undefined>) {
|
||||
return Array.from(new Set(array));
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './arrayDedupPrimitives';
|
||||
export * from './arrayDedup';
|
||||
export * from './without';
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Return an array of values from values excluding the values present in excludeValues .
|
||||
*
|
||||
* @param excludeValues - An array of values to exclude from the result.
|
||||
* @param values - An array of values from which to exclude the excludeValues .
|
||||
* @returns An array of values from values excluding the values present in excludeValues .
|
||||
*
|
||||
* @example
|
||||
* const exclude = [2, 4, 6];
|
||||
* const values = [1, 2, 3, 4, 5, 6];
|
||||
* const result = without(exclude, values);
|
||||
* // result: [1, 3, 5]
|
||||
*/
|
||||
export function without<T>(excludeValues: Iterable<T>, values: T[]): T[] {
|
||||
const excludeSet = new Set<T>(excludeValues);
|
||||
const result: T[] = [];
|
||||
values.forEach((value) => {
|
||||
if (excludeSet.has(value)) return;
|
||||
result.push(value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# [←](../../Readme.md) Огляд Конвертерів
|
||||
|
||||
### # base64toFile(base64: string): File | null
|
||||
|
||||
Converts a base64 data URL (e.g. data:image/png;base64,...) into a browser File object
|
||||
|
||||
### # capitalizeEveryWord(str: string): string
|
||||
|
||||
Перетворює першу літеру кожного слова в рядку на велику
|
||||
`"hello world" → "Hello World"`
|
||||
|
||||
### # capitalize(str: string, lowerRest = false): string
|
||||
|
||||
Перетворює першу літеру першого слова в рядку на велику.
|
||||
Якщо `lowerRest = true`, решта рядка пишеться малими літерами.
|
||||
В іншому випадку зберігається початковий регістр.
|
||||
|
||||
`capitalize("hELLO", true) → "Hello"`
|
||||
|
||||
### # numToBool(value: string | number): boolean
|
||||
|
||||
Перетворює числові значення на логічні:
|
||||
`0` чи `"0"` → `false`
|
||||
`1` чи `"1"` → `true`
|
||||
|
||||
Будь-яке інше ТРУШНЕ(`true`) значення → `true`
|
||||
Будь-яке інше ПОЗЕРСЬКЕ(`falsy`) значення → `false`
|
||||
|
||||
### # snakeToCamel(snake: string): string
|
||||
|
||||
Перетворює `snake_case` на `camelCase`
|
||||
`"hello_world"` → `"helloWorld"`
|
||||
|
||||
### # boolToStr(value: boolean): 'true' | 'false'
|
||||
|
||||
`true` -> `"true"`
|
||||
`false` -> `"false"`
|
||||
|
||||
### # strBoolToBool(value: string): boolean
|
||||
|
||||
`"true"`-> `true`
|
||||
`"false"`-> `false`
|
||||
|
||||
### # mask(cc: number | string, num?: number, symbol?: string): string
|
||||
|
||||
Маскує всі символи, крім останніх `num` (за замовчуванням `4`). Символ маски за замовчуванням `*`.
|
||||
|
||||
`mask(1234567890)` → `'******7890'`
|
||||
`mask(1234567890123456, 4, 'X')` → `'XXXXXXXXXXXX3456'`
|
||||
@@ -0,0 +1,41 @@
|
||||
export function base64toFile(base64: string): File | null {
|
||||
if (typeof base64 !== 'string') return null;
|
||||
|
||||
try {
|
||||
if (!base64.startsWith('data:')) {
|
||||
throw new Error("Invalid data URL: must start with 'data:'.");
|
||||
}
|
||||
|
||||
const parts = base64.split(',');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error('Invalid data URL format: missing comma separator.');
|
||||
}
|
||||
|
||||
const header = parts[0];
|
||||
const base64Data = parts[1];
|
||||
|
||||
const mimeMatch = header.match(/data:(.*?);base64/);
|
||||
if (!mimeMatch) {
|
||||
throw new Error('Invalid data URL: MIME type not found or not base64 encoded.');
|
||||
}
|
||||
|
||||
const mime = mimeMatch[1];
|
||||
|
||||
let bstr: string;
|
||||
try {
|
||||
bstr = atob(base64Data);
|
||||
} catch (e) {
|
||||
throw new Error('Base64 decoding failed: data is not valid base64.');
|
||||
}
|
||||
|
||||
const u8arr = new Uint8Array(bstr.length);
|
||||
for (let i = 0; i < bstr.length; i++) {
|
||||
u8arr[i] = bstr.charCodeAt(i);
|
||||
}
|
||||
|
||||
return new File([u8arr], 'mileage-photo', { type: mime });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export const boolToStr = (value: boolean): 'true' | 'false' => {
|
||||
return value === true ? 'true' : 'false';
|
||||
};
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export function capitalize(str = '', lowerRest = false): string {
|
||||
return str.slice(0, 1).toUpperCase() + (lowerRest ? str.slice(1).toLowerCase() : str.slice(1));
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function capitalizeEveryWord(str = ''): string {
|
||||
return str.replace(/\b[a-z]/g, (char) => char.toUpperCase());
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from './capitalize';
|
||||
export * from './capitalizeEveryWord';
|
||||
export * from './numToBool';
|
||||
export * from './strToBool';
|
||||
export * from './boolToStr';
|
||||
export * from './base64ToFile';
|
||||
export * from './snakeToCamel';
|
||||
export * from './mask';
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Replaces all but the last num of characters with the specified mask character
|
||||
* @param cc - The number or string to mask.
|
||||
* @param num - The number of digits to keep at the end. Default is 4.
|
||||
* @param symbol - The symbol to use for masking. Default is "*".
|
||||
* @returns The masked string.
|
||||
* @example
|
||||
* // Example usage:
|
||||
* mask(1234567890123456, 4, "X"); // Output: "XXXXXXXXXXXX3456"
|
||||
* mask(1234567890); // '******7890'
|
||||
* mask(1234567890, 3); // '*******890'
|
||||
* mask(1234567890, -4, "$"); // '$$$$567890'
|
||||
*/
|
||||
export const mask = (cc: number | string, num = 4, symbol = '*'): string => String(cc).slice(-num).padStart(String(cc).length, symbol);
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
export const numToBool = (value: string | number): boolean => {
|
||||
if (value === 0 || value === '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value === 1 || value === '1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Boolean(value);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export function snakeToCamel(snake: string): string {
|
||||
if (typeof snake !== 'string') return '';
|
||||
|
||||
return snake.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
export const strBoolToBool = (value: string): boolean => {
|
||||
if (value === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
# [←](../../Readme.md) Огляд Детекторів
|
||||
|
||||
### # detectUserDeviceType(widthFallback?: UserDeviceTypeWidthFallback): UserDeviceType
|
||||
|
||||
Визначає тип пристрою користувача: `'mobile'` | `'tablet'` | `'desktop'`.
|
||||
|
||||
Пріоритет визначення:
|
||||
|
||||
1. Modern API (`navigator.userAgentData`) — Chrome, Edge, Android
|
||||
2. User-Agent — iOS (`iPhone`, `iPod`), iPadOS, Android
|
||||
3. Fallback за шириною екрану (якщо передано `widthFallback`)
|
||||
|
||||
```ts
|
||||
detectUserDeviceType(); // → 'desktop' | 'tablet' | 'mobile'
|
||||
|
||||
detectUserDeviceType({ mobile: 480, tablet: 1024 }); // fallback
|
||||
```
|
||||
|
||||
### # detectUserDateInputMode(): UserDateInputModeSegment[]
|
||||
|
||||
Повертає порядок сегментів дати (`day`, `month`, `year`) відповідно до локалі браузера (`navigator.language`).
|
||||
|
||||
```ts
|
||||
detectUserDateInputMode(); // → ['month', 'day', 'year'] (en-US)
|
||||
detectUserDateInputMode(); // → ['day', 'month', 'year'] (uk-UA)
|
||||
```
|
||||
|
||||
### # detectUserTimeMode(): TimeMode
|
||||
|
||||
Визначає формат часу користувача: `'12h'` або `'24h'` на основі локалі браузера.
|
||||
|
||||
```ts
|
||||
detectUserTimeMode(); // → '12h' (en-US)
|
||||
detectUserTimeMode(); // → '24h' (uk-UA)
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
import { UserDateInputModeSegment } from '@crux-kit/types';
|
||||
|
||||
export const detectUserDateInputMode = (): UserDateInputModeSegment[] => {
|
||||
const format = new Intl.DateTimeFormat(navigator.language)
|
||||
.formatToParts(new Date())
|
||||
.map((p) => p.type)
|
||||
.filter((t) => ['day', 'month', 'year'].includes(t));
|
||||
|
||||
return format as UserDateInputModeSegment[];
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import { UserDeviceType } from '@crux-kit/types';
|
||||
|
||||
export type UserDeviceTypeWidthFallback = {
|
||||
mobile: number;
|
||||
tablet: number;
|
||||
};
|
||||
|
||||
export function detectUserDeviceType(widthFallback?: UserDeviceTypeWidthFallback): UserDeviceType {
|
||||
const ua = navigator.userAgent || '';
|
||||
|
||||
// --- Modern API first (Chrome, Edge, Android, etc.)
|
||||
const uaData = (navigator as any).userAgentData;
|
||||
if (uaData && uaData.mobile !== undefined) {
|
||||
if (uaData.mobile) return 'mobile';
|
||||
}
|
||||
|
||||
// --- Apple devices (iOS & iPadOS)
|
||||
const isIOS = /iPhone|iPod/.test(ua);
|
||||
const isIPad = /iPad/.test(ua) || (/Macintosh/.test(ua) && 'ontouchend' in document); // iPadOS masquerading as Mac
|
||||
|
||||
if (isIOS) return 'mobile';
|
||||
if (isIPad) return 'tablet';
|
||||
|
||||
// --- Android
|
||||
if (/android/i.test(ua)) {
|
||||
return /mobile/i.test(ua) ? 'mobile' : 'tablet';
|
||||
}
|
||||
|
||||
// --- Fallback: screen size
|
||||
if (widthFallback) {
|
||||
const width = window.innerWidth;
|
||||
if (width <= widthFallback?.mobile) return 'mobile';
|
||||
if (width <= widthFallback?.tablet) return 'tablet';
|
||||
return 'desktop';
|
||||
}
|
||||
|
||||
return 'desktop';
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { TimeMode } from '@crux-kit/types';
|
||||
|
||||
export const detectUserTimeMode = (): TimeMode => {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
hour: 'numeric',
|
||||
}).resolvedOptions().hour12
|
||||
? '12h'
|
||||
: '24h';
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './detectUserDeviceType';
|
||||
export * from './detectUserDateInputMode';
|
||||
export * from './detectUserTimeMode';
|
||||
@@ -0,0 +1,11 @@
|
||||
# [←](../../Readme.md) Огляд DOM утиліт
|
||||
|
||||
### # getHeight(element: HTMLElement): number
|
||||
|
||||
Повертає повну висоту елемента в пікселях, включаючи `margin-top` та `margin-bottom`.
|
||||
|
||||
Якщо елемент не передано — повертає `0`.
|
||||
|
||||
```ts
|
||||
getHeight(document.querySelector('.my-element')); // → 124 (height + margins)
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
export function getHeight(element: HTMLElement): number {
|
||||
if (!element) return 0;
|
||||
|
||||
const styles = window.getComputedStyle(element);
|
||||
|
||||
const height = element.getBoundingClientRect().height;
|
||||
const marginTop = parseFloat(styles.marginTop);
|
||||
const marginBottom = parseFloat(styles.marginBottom);
|
||||
|
||||
const totalHeight = height + marginTop + marginBottom;
|
||||
|
||||
return totalHeight;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './getHeight';
|
||||
@@ -0,0 +1,32 @@
|
||||
# [←](../../Readme.md) Огляд Форматерів
|
||||
|
||||
### # bytesToHumanReadableFormat(bytes: number, units?: string[]): string
|
||||
|
||||
Перетворює кількість байт на читабельний рядок з одиницями виміру.
|
||||
|
||||
За замовчуванням використовує поріг `1024` та одиниці: `Bytes`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB`, `ZB`, `YB`.
|
||||
|
||||
```ts
|
||||
bytesToHumanReadableFormat(500); // → '500 Bytes'
|
||||
bytesToHumanReadableFormat(1536); // → '1.5 KB'
|
||||
bytesToHumanReadableFormat(1048576); // → '1.0 MB'
|
||||
```
|
||||
|
||||
Можна передати власні одиниці:
|
||||
|
||||
```ts
|
||||
bytesToHumanReadableFormat(2048, ['Б', 'КБ', 'МБ']); // → '2.0 КБ'
|
||||
```
|
||||
|
||||
### # numberToCompactFormat(num: number, digits?: number): string
|
||||
|
||||
Скорочує велике число до компактного формату з суфіксом (`K`, `M`, `G`, `T`, `P`, `E`).
|
||||
|
||||
`digits` — кількість знаків після коми (за замовчуванням `1`).
|
||||
|
||||
```ts
|
||||
numberToCompactFormat(999); // → '999'
|
||||
numberToCompactFormat(1500); // → '1.5K'
|
||||
numberToCompactFormat(1500, 2); // → '1.50K'
|
||||
numberToCompactFormat(2500000); // → '2.5M'
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
const thresh = 1024;
|
||||
|
||||
export function bytesToHumanReadableFormat(bytes: number, units: string[] = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']): string {
|
||||
let bytesCopy = bytes;
|
||||
if (bytesCopy < thresh) return `${bytesCopy} ${units[0]}`;
|
||||
let u = -1;
|
||||
do {
|
||||
bytesCopy /= thresh;
|
||||
++u;
|
||||
} while (bytesCopy >= thresh && u + 1 < units.length - 1);
|
||||
return `${bytesCopy.toFixed(1)} ${units[u + 1]}`;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './bytesToHumanReadableFormat';
|
||||
export * from './numberToCompactFormat';
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Converts a number to a compact format with symbols.
|
||||
* @param num - The number to convert.
|
||||
* @param digits - The number of decimal places to round to. Default is 1.
|
||||
* @returns The number in compact format with symbols.
|
||||
* @example
|
||||
* // Example usage:
|
||||
* const compactNumber = numberToCompactFormat(1500, 2);
|
||||
* console.log(compactNumber); // Output: "1.50K"
|
||||
*/
|
||||
export const numberToCompactFormat = (num: number, digits = 1): string => {
|
||||
const lookup = [
|
||||
{ value: 1, symbol: '' },
|
||||
{ value: 1e3, symbol: 'K' },
|
||||
{ value: 1e6, symbol: 'M' },
|
||||
{ value: 1e9, symbol: 'G' },
|
||||
{ value: 1e12, symbol: 'T' },
|
||||
{ value: 1e15, symbol: 'P' },
|
||||
{ value: 1e18, symbol: 'E' },
|
||||
];
|
||||
|
||||
const rx = /\.0+$|(?<temp1>\.[0-9]*[1-9])0+$/;
|
||||
|
||||
const item = lookup.reverse().find((entry) => {
|
||||
return num >= entry.value;
|
||||
});
|
||||
|
||||
return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0';
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
# [←](../../Readme.md) Огляд Хелперів
|
||||
|
||||
### # downloadFileByLink(href: string, name?: string, target?: string): void
|
||||
|
||||
Програмно завантажує файл за URL, створюючи тимчасовий `<a>` елемент і клікаючи по ньому.
|
||||
|
||||
```ts
|
||||
downloadFileByLink('https://example.com/file.pdf', 'report'); // завантажує як 'report'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # sleep(ms: number): Promise\<void>
|
||||
|
||||
Затримує виконання на задану кількість мілісекунд.
|
||||
|
||||
```ts
|
||||
await sleep(1000); // чекає 1 секунду
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # throttle(func, limit: number): Function
|
||||
|
||||
Обмежує частоту виклику функції — не частіше ніж раз на `limit` мс.
|
||||
|
||||
```ts
|
||||
const throttled = throttle(onScroll, 200);
|
||||
window.addEventListener('scroll', throttled);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # debounce\<T>(func: T, wait: number): Function
|
||||
|
||||
Відкладає виклик функції до закінчення `wait` мс після останнього виклику.
|
||||
|
||||
```ts
|
||||
const debounced = debounce(onSearch, 300);
|
||||
input.addEventListener('input', debounced);
|
||||
```
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/// <reference lib="dom" />
|
||||
|
||||
/**
|
||||
* Downloads a file by providing a link.
|
||||
* @param href - The URL of the file to download.
|
||||
* @param name - The name of the downloaded file. Default is "file".
|
||||
* @param target - The target window or frame for the downloaded file. Default is "_blank".
|
||||
* @example
|
||||
* // Example usage:
|
||||
* downloadFileByLink("https://example.com/file.pdf", "myFile", "_self");
|
||||
*/
|
||||
export const downloadFileByLink = (href: string, name = 'file', target = '_blank'): void => {
|
||||
const link = document.createElement('a');
|
||||
link.href = href;
|
||||
link.download = name;
|
||||
link.target = target;
|
||||
link.style.visibility = 'hidden';
|
||||
link.setAttribute('target', '_blank');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './downloadFileByLink';
|
||||
export * from './throttle-debounce';
|
||||
export * from './sleep';
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
export const sleep = (ms: number): Promise<void> =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
export const throttle = (func: (...args: unknown[]) => void, limit: number): ((...args: unknown[]) => void) => {
|
||||
let lastFunc: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastRan: number | null = null;
|
||||
|
||||
return function (this: unknown, ...args: unknown[]) {
|
||||
if (lastRan === null) {
|
||||
func.apply(this, args);
|
||||
lastRan = Date.now();
|
||||
} else {
|
||||
if (lastFunc !== null) {
|
||||
clearTimeout(lastFunc);
|
||||
}
|
||||
lastFunc = setTimeout(
|
||||
() => {
|
||||
if (Date.now() - (lastRan as number) >= limit) {
|
||||
func.apply(this, args);
|
||||
lastRan = Date.now();
|
||||
}
|
||||
},
|
||||
limit - (Date.now() - (lastRan as number)),
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function debounce<T extends (...args: unknown[]) => unknown>(func: T, wait: number): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
|
||||
return function (...args: Parameters<T>): void {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
func(...args);
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
export * from './helpers';
|
||||
export * from './random';
|
||||
export * from './converters';
|
||||
export * from './formaters';
|
||||
export * from './detectors';
|
||||
export * from './tailwind';
|
||||
export * from './dom';
|
||||
export * from './array';
|
||||
export * from './object';
|
||||
|
||||
export * from './is.ts';
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
export const isUndef = (o: unknown): o is undefined | void => typeof o === 'undefined' || o === undefined;
|
||||
|
||||
export const isNull = (o: unknown): o is null => o === null;
|
||||
|
||||
export const isBool = (o: unknown): o is boolean => typeof o === 'boolean';
|
||||
|
||||
export const isNil = (o: unknown): o is null | undefined | void => isNull(o) || isUndef(o);
|
||||
|
||||
export function isStr<T extends string = string>(o: unknown): o is T {
|
||||
return typeof o === 'string';
|
||||
}
|
||||
|
||||
export const isNum = (o: unknown, isStringAllowed = false): o is number => {
|
||||
const isNumType = typeof o === 'number';
|
||||
|
||||
if (!isStringAllowed) return isNumType;
|
||||
|
||||
return isNumType || (typeof o === 'string' && !isNaN(Number(o)) && o.trim() !== '');
|
||||
};
|
||||
|
||||
export const isNumStr = (o: unknown): o is number | string => isNum(o, true);
|
||||
|
||||
export const isEven = (num: number): boolean => num % 2 === 0;
|
||||
|
||||
export const isOdd = (num: number): boolean => num % 2 !== 0;
|
||||
|
||||
type Func = (...args: any[]) => any;
|
||||
|
||||
export function isFunc<T extends Func = Func>(o: unknown): o is T {
|
||||
return !(isNull(o) || isUndef(o)) && typeof o === 'function';
|
||||
}
|
||||
|
||||
export function isArr<T extends unknown[] = unknown[]>(value: unknown): value is T {
|
||||
return Array.isArray(value) as boolean;
|
||||
}
|
||||
|
||||
export function isPureObj<T extends Record<string, any> = Record<string, any>>(o: unknown): o is T {
|
||||
return !(isNull(o) || isUndef(o)) && typeof o === 'object' && !isArr(o) && !(o instanceof Map) && !(o instanceof Set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two objects are deeply equal.
|
||||
*
|
||||
* @param objA - The first object to compare.
|
||||
* @param objB - The second object to compare.
|
||||
* @returns A boolean indicating whether the objects are deeply equal.
|
||||
*
|
||||
* @example
|
||||
* const obj1 = { a: 1, b: { c: 2 } };
|
||||
* const obj2 = { a: 1, b: { c: 2 } };
|
||||
* const isEqual = isDeepEqual(obj1, obj2);
|
||||
* // isEqual: true
|
||||
*/
|
||||
export function isDeepEqual(objA: any, objB: any): boolean {
|
||||
if (Object.is(objA, objB)) return true;
|
||||
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) return false;
|
||||
|
||||
if (objA.constructor !== objB.constructor) return false;
|
||||
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
|
||||
const bHasOwnProperty = global.hasOwnProperty.bind(objB);
|
||||
while (keysA.length > 0) {
|
||||
const key: any = keysA.pop();
|
||||
|
||||
if (!bHasOwnProperty(key)) return false;
|
||||
|
||||
const a: unknown = objA[key];
|
||||
const b: unknown = objB[key];
|
||||
if (!Object.is(a, b)) {
|
||||
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
||||
|
||||
if (!isDeepEqual(a, b)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# [←](../../Readme.md) Огляд Object утиліт
|
||||
|
||||
### # keys\<T extends object>(object: T): (keyof T)[]
|
||||
|
||||
Типізована обгортка над `Object.keys` — повертає масив ключів зі збереженням типу.
|
||||
|
||||
```ts
|
||||
keys({ a: 1, b: 2 }); // → ['a', 'b']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # pick(names: string[], obj: Record\<string, unknown>): object
|
||||
|
||||
Повертає новий об'єкт лише з вказаними ключами.
|
||||
|
||||
```ts
|
||||
pick(['a', 'c'], { a: 1, b: 2, c: 3 }); // → { a: 1, c: 3 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # omit(excludeObjectKeys: string[], obj: Record\<string, unknown>): object
|
||||
|
||||
Повертає новий об'єкт без вказаних ключів.
|
||||
|
||||
```ts
|
||||
omit(['b', 'c'], { a: 1, b: 2, c: 3 }); // → { a: 1 }
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './keys';
|
||||
export * from './pick';
|
||||
export * from './omit';
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Return an array of keys from an object.
|
||||
*
|
||||
* @param object - The object from which to extract the keys.
|
||||
* @returns An array of keys from the object.
|
||||
*
|
||||
* @example
|
||||
* const obj = { a: 1, b: 2, c: 3 };
|
||||
* const result = keys(obj);
|
||||
* // result: ['a', 'b', 'c']
|
||||
*/
|
||||
export function keys<T extends object>(object: T): (keyof T)[] {
|
||||
return Object.keys(object) as (keyof T)[];
|
||||
}
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Omit specified keys from an object.
|
||||
*
|
||||
* @param excludeObjectKeys - An array of keys to exclude from the object.
|
||||
* @param obj - The object from which to omit the keys.
|
||||
* @returns The new object with the specified keys omitted.
|
||||
*
|
||||
* @example
|
||||
* const input = { a: 1, b: 2, c: 3 };
|
||||
* const excludedKeys = ['b', 'c'];
|
||||
* const output = omit(excludedKeys, input);
|
||||
* // output: { a: 1 }
|
||||
*/
|
||||
export function omit(excludeObjectKeys: string[], obj: Record<string, unknown>): object {
|
||||
const result: Record<string, unknown> = {};
|
||||
const index: Record<string, unknown> = {};
|
||||
let idx = 0;
|
||||
const length = excludeObjectKeys.length;
|
||||
|
||||
while (idx < length) {
|
||||
index[excludeObjectKeys[idx]] = 1;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
for (const prop in obj) {
|
||||
if (!Object.prototype.hasOwnProperty.call(index, prop)) result[prop] = obj[prop];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Pick specified keys from an object.
|
||||
*
|
||||
* @param names - An array of keys to pick from the object.
|
||||
* @param obj - The object from which to pick the keys.
|
||||
* @returns The new object with the specified keys picked.
|
||||
*
|
||||
* @example
|
||||
* const input = { a: 1, b: 2, c: 3 };
|
||||
* const pickedKeys = ['a', 'c'];
|
||||
* const output = pick(pickedKeys, input);
|
||||
* // output: { a: 1, c: 3 }
|
||||
*/
|
||||
export function pick(names: string[], obj: Record<string, unknown>): object {
|
||||
const result: Record<string, unknown> = {};
|
||||
let idx = 0;
|
||||
|
||||
while (idx < names.length) {
|
||||
if (names[idx] in obj) result[names[idx]] = obj[names[idx]];
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# [←](../../Readme.md) Огляд Random утиліт
|
||||
|
||||
### # randomNumber(min?: number, max?: number): number
|
||||
|
||||
Повертає випадкове число з діапазону `[min, max]`. За замовчуванням `[0, 1]`.
|
||||
|
||||
```ts
|
||||
randomNumber(1, 10); // → 7.42...
|
||||
randomNumber(); // → 0.38...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # randomKey(): string
|
||||
|
||||
Генерує випадковий рядковий ключ з префіксом `random-`.
|
||||
|
||||
```ts
|
||||
randomKey(); // → 'random-k7f2h9x1q'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### # uid(): string
|
||||
|
||||
Генерує простий унікальний ідентифікатор на основі `Date.now()` та випадкового числа.
|
||||
|
||||
```ts
|
||||
uid(); // → '17160394821573'
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './randomKey';
|
||||
export * from './randomNumber';
|
||||
export * from './uid';
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export const randomKey: () => string = () => {
|
||||
return `random-${Math.random().toString(36).slice(2, 11)}`;
|
||||
};
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export function randomNumber(min = 0, max = 1): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function uid(): string {
|
||||
return (Date.now() + Math.floor(Math.random() * 1000)).toString();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
/**
|
||||
* Merges Tailwind class names, resolving any conflicts.
|
||||
*
|
||||
* @param inputs - An array of class names to merge.
|
||||
* @returns A string of merged and optimized class names.
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './cn';
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2023"],
|
||||
"skipLibCheck": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"composite": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user