@structyl/utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/index.cjs +246 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.mjs +205 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 your-lib contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# @structyl/utils
|
|
2
|
+
|
|
3
|
+
> Small, tree-shakeable TypeScript utilities that power the structyl ecosystem.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
A focused collection of pure utility functions used across structyl's primitives, styled
|
|
9
|
+
components, and DataTable. No React imports, no side effects, fully tree-shakeable — import
|
|
10
|
+
only what you need. Useful on its own in any TypeScript or JavaScript project, browser or server.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# pnpm
|
|
16
|
+
pnpm add @structyl/utils
|
|
17
|
+
|
|
18
|
+
# npm
|
|
19
|
+
npm install @structyl/utils
|
|
20
|
+
|
|
21
|
+
# yarn
|
|
22
|
+
yarn add @structyl/utils
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> `cn` and `mergeProps` build on [`clsx`](https://github.com/lukeed/clsx) and
|
|
26
|
+
> [`tailwind-merge`](https://github.com/dcastil/tailwind-merge), which ship as dependencies.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
cn,
|
|
33
|
+
composeEventHandlers,
|
|
34
|
+
isString,
|
|
35
|
+
groupBy,
|
|
36
|
+
pick,
|
|
37
|
+
slugify,
|
|
38
|
+
formatCurrency,
|
|
39
|
+
getFocusableElements,
|
|
40
|
+
} from '@structyl/utils';
|
|
41
|
+
|
|
42
|
+
// Merge Tailwind classes — conflicting utilities are resolved.
|
|
43
|
+
cn('px-2 py-1', isActive && 'px-4'); // → 'py-1 px-4'
|
|
44
|
+
|
|
45
|
+
// Compose event handlers; the second is skipped if the first prevents default.
|
|
46
|
+
const onClick = composeEventHandlers(props.onClick, () => setOpen(true));
|
|
47
|
+
|
|
48
|
+
// Type guards narrow `unknown` safely.
|
|
49
|
+
if (isString(value)) {
|
|
50
|
+
value.toUpperCase();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Array helpers.
|
|
54
|
+
groupBy(users, (u) => u.role); // → { admin: [...], member: [...] }
|
|
55
|
+
|
|
56
|
+
// Object helpers.
|
|
57
|
+
pick(config, ['id', 'name']);
|
|
58
|
+
|
|
59
|
+
// String helpers.
|
|
60
|
+
slugify('Hello, World!'); // → 'hello-world'
|
|
61
|
+
|
|
62
|
+
// Number formatting via Intl.
|
|
63
|
+
formatCurrency(1999.9, 'USD'); // → '$1,999.90'
|
|
64
|
+
|
|
65
|
+
// DOM helpers (browser only).
|
|
66
|
+
getFocusableElements(containerEl); // → HTMLElement[]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **Tree-shakeable** — named exports with `"sideEffects": false`; unused helpers are dropped by your bundler.
|
|
72
|
+
- **No React dependency** — pure functions usable anywhere, including Node and edge runtimes.
|
|
73
|
+
- **Tailwind-aware class merging** — `cn` combines `clsx` + `tailwind-merge` to resolve conflicting utilities.
|
|
74
|
+
- **Type-safe guards** — `is*` helpers act as TypeScript type predicates to narrow `unknown`.
|
|
75
|
+
- **SSR-safe DOM helpers** — `isBrowser`-guarded utilities that won't crash on the server.
|
|
76
|
+
- **Ships ESM + CJS + types** — first-class TypeScript declarations out of the box.
|
|
77
|
+
|
|
78
|
+
## API
|
|
79
|
+
|
|
80
|
+
### Class names
|
|
81
|
+
|
|
82
|
+
| Export | Description |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `cn(...inputs)` | Merge class names with `clsx`, then resolve Tailwind conflicts via `tailwind-merge`. |
|
|
85
|
+
|
|
86
|
+
### Composition
|
|
87
|
+
|
|
88
|
+
| Export | Description |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `composeEventHandlers(original, ours, opts?)` | Chain two event handlers; skips `ours` when `original` calls `preventDefault` (configurable). |
|
|
91
|
+
| `mergeProps(base, override)` | Merge two prop objects, composing event handlers, `className`, and `style`. |
|
|
92
|
+
|
|
93
|
+
### Type guards
|
|
94
|
+
|
|
95
|
+
| Export | Description |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `isFunction`, `isObject`, `isString`, `isNumber`, `isArray`, `isBoolean`, `isNullish` | Type-predicate guards that narrow `unknown`. |
|
|
98
|
+
| `isEmpty(value)` | `true` for nullish values, empty strings/arrays/objects. |
|
|
99
|
+
| `isBrowser()` | `true` when `window` is defined. |
|
|
100
|
+
|
|
101
|
+
### Array
|
|
102
|
+
|
|
103
|
+
| Export | Description |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `chunk(array, size)` | Split an array into chunks of size `N`. |
|
|
106
|
+
| `groupBy(array, keyFn)` | Group items into a record keyed by `keyFn`. |
|
|
107
|
+
| `sortBy(array, keyFn)` | Non-mutating sort by a derived key. |
|
|
108
|
+
| `uniqueBy(array, keyFn)` | Dedupe items by a derived key. |
|
|
109
|
+
| `partition(array, predicate)` | Split into `[matching, notMatching]`. |
|
|
110
|
+
| `range(start, end?, step?)` | Generate a numeric range. |
|
|
111
|
+
|
|
112
|
+
### Object
|
|
113
|
+
|
|
114
|
+
| Export | Description |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `pick(obj, keys)` | Keep only the given keys. |
|
|
117
|
+
| `omit(obj, keys)` | Remove the given keys. |
|
|
118
|
+
| `get(obj, path, default?)` | Read a nested value by dot-path. |
|
|
119
|
+
| `deepEqual(a, b)` | Recursive structural equality check. |
|
|
120
|
+
|
|
121
|
+
### String
|
|
122
|
+
|
|
123
|
+
| Export | Description |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `capitalize`, `camelCase`, `kebabCase`, `snakeCase` | Case transforms. |
|
|
126
|
+
| `truncate(str, max, suffix?)` | Truncate with a trailing suffix (default `…`). |
|
|
127
|
+
| `slugify(str)` | URL-safe slug (lowercase, accent-stripped). |
|
|
128
|
+
|
|
129
|
+
### Number
|
|
130
|
+
|
|
131
|
+
| Export | Description |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `clamp(value, min, max)` | Constrain a number to a range. |
|
|
134
|
+
| `roundTo(value, decimals?)` | Round to `N` decimal places. |
|
|
135
|
+
| `lerp(start, end, t)` | Linear interpolation. |
|
|
136
|
+
| `formatNumber`, `formatCurrency`, `formatPercent` | `Intl`-based formatting helpers. |
|
|
137
|
+
|
|
138
|
+
### DOM
|
|
139
|
+
|
|
140
|
+
| Export | Description |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `getOwnerDocument(node)`, `getOwnerWindow(node)` | Resolve the owning `Document`/`Window`, SSR-safe. |
|
|
143
|
+
| `getActiveElement(doc?)` | The document's active element. |
|
|
144
|
+
| `getFocusableElements(container)` | All visible, focusable descendants. |
|
|
145
|
+
| `FOCUSABLE_SELECTOR` | Selector string for focusable elements. |
|
|
146
|
+
| `contains(parent, child)` | Whether `parent` contains `child`. |
|
|
147
|
+
|
|
148
|
+
## Part of structyl
|
|
149
|
+
|
|
150
|
+
This package is part of [structyl](https://github.com/imirfanul/structyl) — see the full
|
|
151
|
+
documentation at [structyl.dev](https://structyl.dev).
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clsx = require('clsx');
|
|
4
|
+
var tailwindMerge = require('tailwind-merge');
|
|
5
|
+
|
|
6
|
+
// src/class-names.ts
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/compose.ts
|
|
12
|
+
function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
|
|
13
|
+
return function handleEvent(event) {
|
|
14
|
+
originalEventHandler?.(event);
|
|
15
|
+
if (checkForDefaultPrevented === false || !event.defaultPrevented) {
|
|
16
|
+
ourEventHandler?.(event);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function mergeProps(base, override) {
|
|
21
|
+
const merged = { ...base, ...override };
|
|
22
|
+
for (const key in override) {
|
|
23
|
+
const baseVal = base[key];
|
|
24
|
+
const overrideVal = override[key];
|
|
25
|
+
if (/^on[A-Z]/.test(key) && typeof baseVal === "function" && typeof overrideVal === "function") {
|
|
26
|
+
merged[key] = (...args) => {
|
|
27
|
+
overrideVal(...args);
|
|
28
|
+
baseVal(...args);
|
|
29
|
+
};
|
|
30
|
+
} else if (key === "style" && typeof baseVal === "object" && typeof overrideVal === "object") {
|
|
31
|
+
merged[key] = { ...baseVal, ...overrideVal };
|
|
32
|
+
} else if (key === "className" && typeof baseVal === "string" && typeof overrideVal === "string") {
|
|
33
|
+
merged[key] = [baseVal, overrideVal].filter(Boolean).join(" ");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return merged;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/type-guards.ts
|
|
40
|
+
var isFunction = (value) => typeof value === "function";
|
|
41
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
var isString = (value) => typeof value === "string";
|
|
43
|
+
var isNumber = (value) => typeof value === "number" && !Number.isNaN(value);
|
|
44
|
+
var isArray = (value) => Array.isArray(value);
|
|
45
|
+
var isNullish = (value) => value == null;
|
|
46
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
47
|
+
var isEmpty = (value) => {
|
|
48
|
+
if (isNullish(value)) return true;
|
|
49
|
+
if (isString(value) || isArray(value)) return value.length === 0;
|
|
50
|
+
if (isObject(value)) return Object.keys(value).length === 0;
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
var isBrowser = () => typeof window !== "undefined";
|
|
54
|
+
|
|
55
|
+
// src/array.ts
|
|
56
|
+
function chunk(array, size) {
|
|
57
|
+
if (size <= 0) return [];
|
|
58
|
+
const result = [];
|
|
59
|
+
for (let i = 0; i < array.length; i += size) {
|
|
60
|
+
result.push(array.slice(i, i + size));
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
function groupBy(array, keyFn) {
|
|
65
|
+
return array.reduce(
|
|
66
|
+
(acc, item) => {
|
|
67
|
+
const key = keyFn(item);
|
|
68
|
+
(acc[key] ||= []).push(item);
|
|
69
|
+
return acc;
|
|
70
|
+
},
|
|
71
|
+
{}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
function sortBy(array, keyFn) {
|
|
75
|
+
return [...array].sort((a, b) => {
|
|
76
|
+
const aKey = keyFn(a);
|
|
77
|
+
const bKey = keyFn(b);
|
|
78
|
+
if (aKey < bKey) return -1;
|
|
79
|
+
if (aKey > bKey) return 1;
|
|
80
|
+
return 0;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function uniqueBy(array, keyFn) {
|
|
84
|
+
const seen = /* @__PURE__ */ new Set();
|
|
85
|
+
return array.filter((item) => {
|
|
86
|
+
const key = keyFn(item);
|
|
87
|
+
if (seen.has(key)) return false;
|
|
88
|
+
seen.add(key);
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function partition(array, predicate) {
|
|
93
|
+
const truthy = [];
|
|
94
|
+
const falsy = [];
|
|
95
|
+
for (const item of array) {
|
|
96
|
+
if (predicate(item)) truthy.push(item);
|
|
97
|
+
else falsy.push(item);
|
|
98
|
+
}
|
|
99
|
+
return [truthy, falsy];
|
|
100
|
+
}
|
|
101
|
+
function range(start, end, step = 1) {
|
|
102
|
+
if (end === void 0) {
|
|
103
|
+
end = start;
|
|
104
|
+
start = 0;
|
|
105
|
+
}
|
|
106
|
+
const result = [];
|
|
107
|
+
if (step > 0) {
|
|
108
|
+
for (let i = start; i < end; i += step) result.push(i);
|
|
109
|
+
} else if (step < 0) {
|
|
110
|
+
for (let i = start; i > end; i += step) result.push(i);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/object.ts
|
|
116
|
+
function pick(obj, keys) {
|
|
117
|
+
const result = {};
|
|
118
|
+
for (const key of keys) {
|
|
119
|
+
if (key in obj) result[key] = obj[key];
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function omit(obj, keys) {
|
|
124
|
+
const result = { ...obj };
|
|
125
|
+
for (const key of keys) {
|
|
126
|
+
delete result[key];
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function get(obj, path, defaultValue) {
|
|
131
|
+
const keys = path.split(".");
|
|
132
|
+
let result = obj;
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
if (result == null || typeof result !== "object") return defaultValue;
|
|
135
|
+
result = result[key];
|
|
136
|
+
}
|
|
137
|
+
return result ?? defaultValue;
|
|
138
|
+
}
|
|
139
|
+
function deepEqual(a, b) {
|
|
140
|
+
if (a === b) return true;
|
|
141
|
+
if (a == null || b == null) return false;
|
|
142
|
+
if (typeof a !== typeof b) return false;
|
|
143
|
+
if (typeof a !== "object") return false;
|
|
144
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
145
|
+
if (a.length !== b.length) return false;
|
|
146
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
147
|
+
}
|
|
148
|
+
const aKeys = Object.keys(a);
|
|
149
|
+
const bKeys = Object.keys(b);
|
|
150
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
151
|
+
return aKeys.every(
|
|
152
|
+
(key) => deepEqual(a[key], b[key])
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/string.ts
|
|
157
|
+
var capitalize = (str) => str.length === 0 ? str : str.charAt(0).toUpperCase() + str.slice(1);
|
|
158
|
+
var camelCase = (str) => str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^(.)/, (m) => m.toLowerCase());
|
|
159
|
+
var kebabCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
160
|
+
var snakeCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
161
|
+
var truncate = (str, maxLength, suffix = "\u2026") => str.length <= maxLength ? str : str.slice(0, maxLength - suffix.length) + suffix;
|
|
162
|
+
var slugify = (str) => str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
|
|
163
|
+
|
|
164
|
+
// src/number.ts
|
|
165
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
166
|
+
var roundTo = (value, decimals = 0) => {
|
|
167
|
+
const factor = 10 ** decimals;
|
|
168
|
+
return Math.round(value * factor) / factor;
|
|
169
|
+
};
|
|
170
|
+
var lerp = (start, end, t) => start + (end - start) * t;
|
|
171
|
+
var formatNumber = (value, options = {}, locale = "en-US") => new Intl.NumberFormat(locale, options).format(value);
|
|
172
|
+
var formatCurrency = (value, currency = "USD", locale = "en-US") => formatNumber(value, { style: "currency", currency }, locale);
|
|
173
|
+
var formatPercent = (value, decimals = 0, locale = "en-US") => formatNumber(
|
|
174
|
+
value,
|
|
175
|
+
{ style: "percent", minimumFractionDigits: decimals, maximumFractionDigits: decimals },
|
|
176
|
+
locale
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// src/dom.ts
|
|
180
|
+
var getOwnerDocument = (node) => node?.ownerDocument ?? (isBrowser() ? document : {});
|
|
181
|
+
var getOwnerWindow = (node) => getOwnerDocument(node).defaultView ?? (isBrowser() ? window : {});
|
|
182
|
+
var getActiveElement = (doc = document) => doc.activeElement;
|
|
183
|
+
var FOCUSABLE_SELECTOR = [
|
|
184
|
+
"a[href]",
|
|
185
|
+
"button:not([disabled])",
|
|
186
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
187
|
+
"select:not([disabled])",
|
|
188
|
+
"textarea:not([disabled])",
|
|
189
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
190
|
+
"audio[controls]",
|
|
191
|
+
"video[controls]",
|
|
192
|
+
'[contenteditable]:not([contenteditable="false"])'
|
|
193
|
+
].join(",");
|
|
194
|
+
var getFocusableElements = (container) => {
|
|
195
|
+
if (!container) return [];
|
|
196
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
197
|
+
(el) => !el.hasAttribute("disabled") && el.offsetParent !== null
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
var contains = (parent, child) => {
|
|
201
|
+
if (!parent || !child) return false;
|
|
202
|
+
return parent.contains(child);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
exports.FOCUSABLE_SELECTOR = FOCUSABLE_SELECTOR;
|
|
206
|
+
exports.camelCase = camelCase;
|
|
207
|
+
exports.capitalize = capitalize;
|
|
208
|
+
exports.chunk = chunk;
|
|
209
|
+
exports.clamp = clamp;
|
|
210
|
+
exports.cn = cn;
|
|
211
|
+
exports.composeEventHandlers = composeEventHandlers;
|
|
212
|
+
exports.contains = contains;
|
|
213
|
+
exports.deepEqual = deepEqual;
|
|
214
|
+
exports.formatCurrency = formatCurrency;
|
|
215
|
+
exports.formatNumber = formatNumber;
|
|
216
|
+
exports.formatPercent = formatPercent;
|
|
217
|
+
exports.get = get;
|
|
218
|
+
exports.getActiveElement = getActiveElement;
|
|
219
|
+
exports.getFocusableElements = getFocusableElements;
|
|
220
|
+
exports.getOwnerDocument = getOwnerDocument;
|
|
221
|
+
exports.getOwnerWindow = getOwnerWindow;
|
|
222
|
+
exports.groupBy = groupBy;
|
|
223
|
+
exports.isArray = isArray;
|
|
224
|
+
exports.isBoolean = isBoolean;
|
|
225
|
+
exports.isBrowser = isBrowser;
|
|
226
|
+
exports.isEmpty = isEmpty;
|
|
227
|
+
exports.isFunction = isFunction;
|
|
228
|
+
exports.isNullish = isNullish;
|
|
229
|
+
exports.isNumber = isNumber;
|
|
230
|
+
exports.isObject = isObject;
|
|
231
|
+
exports.isString = isString;
|
|
232
|
+
exports.kebabCase = kebabCase;
|
|
233
|
+
exports.lerp = lerp;
|
|
234
|
+
exports.mergeProps = mergeProps;
|
|
235
|
+
exports.omit = omit;
|
|
236
|
+
exports.partition = partition;
|
|
237
|
+
exports.pick = pick;
|
|
238
|
+
exports.range = range;
|
|
239
|
+
exports.roundTo = roundTo;
|
|
240
|
+
exports.slugify = slugify;
|
|
241
|
+
exports.snakeCase = snakeCase;
|
|
242
|
+
exports.sortBy = sortBy;
|
|
243
|
+
exports.truncate = truncate;
|
|
244
|
+
exports.uniqueBy = uniqueBy;
|
|
245
|
+
//# sourceMappingURL=index.cjs.map
|
|
246
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/class-names.ts","../src/compose.ts","../src/type-guards.ts","../src/array.ts","../src/object.ts","../src/string.ts","../src/number.ts","../src/dom.ts"],"names":["twMerge","clsx"],"mappings":";;;;;;AASO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;;;ACNO,SAAS,oBAAA,CACd,sBACA,eAAA,EACA,EAAE,2BAA2B,IAAA,EAAK,GAAI,EAAC,EACnB;AACpB,EAAA,OAAO,SAAS,YAAY,KAAA,EAAU;AACpC,IAAA,oBAAA,GAAuB,KAAK,CAAA;AAE5B,IAAA,IACE,wBAAA,KAA6B,KAAA,IAC7B,CAAE,KAAA,CAAmD,gBAAA,EACrD;AACA,MAAA,eAAA,GAAkB,KAAK,CAAA;AAAA,IACzB;AAAA,EACF,CAAA;AACF;AAQO,SAAS,UAAA,CACd,MACA,QAAA,EACO;AACP,EAAA,MAAM,MAAA,GAAmB,EAAE,GAAG,IAAA,EAAM,GAAG,QAAA,EAAS;AAEhD,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,MAAM,OAAA,GAAU,KAAK,GAAG,CAAA;AACxB,IAAA,MAAM,WAAA,GAAc,SAAS,GAAG,CAAA;AAGhC,IAAA,IAAI,UAAA,CAAW,KAAK,GAAG,CAAA,IAAK,OAAO,OAAA,KAAY,UAAA,IAAc,OAAO,WAAA,KAAgB,UAAA,EAAY;AAC9F,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,CAAA,GAAI,IAAA,KAAoB;AACpC,QAAC,WAAA,CAA6C,GAAG,IAAI,CAAA;AACrD,QAAC,OAAA,CAAyC,GAAG,IAAI,CAAA;AAAA,MACnD,CAAA;AAAA,IACF,CAAA,MAAA,IAES,QAAQ,OAAA,IAAW,OAAO,YAAY,QAAA,IAAY,OAAO,gBAAgB,QAAA,EAAU;AAC1F,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,EAAE,GAAI,OAAA,EAAoB,GAAI,WAAA,EAAuB;AAAA,IACrE,CAAA,MAAA,IAES,QAAQ,WAAA,IAAe,OAAO,YAAY,QAAA,IAAY,OAAO,gBAAgB,QAAA,EAAU;AAC9F,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,CAAC,OAAA,EAAS,WAAW,EAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAAA,IAC/D;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;ACxDO,IAAM,UAAA,GAAa,CAAC,KAAA,KACzB,OAAO,KAAA,KAAU;AAEZ,IAAM,QAAA,GAAW,CAAC,KAAA,KACvB,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK;AAE9D,IAAM,QAAA,GAAW,CAAC,KAAA,KAAoC,OAAO,KAAA,KAAU;AAEvE,IAAM,QAAA,GAAW,CAAC,KAAA,KACvB,OAAO,UAAU,QAAA,IAAY,CAAC,MAAA,CAAO,KAAA,CAAM,KAAK;AAE3C,IAAM,OAAA,GAAU,CAAc,KAAA,KAAiC,KAAA,CAAM,QAAQ,KAAK;AAElF,IAAM,SAAA,GAAY,CAAC,KAAA,KAA8C,KAAA,IAAS;AAE1E,IAAM,SAAA,GAAY,CAAC,KAAA,KAAqC,OAAO,KAAA,KAAU;AAEzE,IAAM,OAAA,GAAU,CAAC,KAAA,KAA4B;AAClD,EAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG,OAAO,IAAA;AAC7B,EAAA,IAAI,QAAA,CAAS,KAAK,CAAA,IAAK,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAO,MAAM,MAAA,KAAW,CAAA;AAC/D,EAAA,IAAI,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,OAAO,IAAA,CAAK,KAAK,EAAE,MAAA,KAAW,CAAA;AAC1D,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,SAAA,GAAY,MAAe,OAAO,MAAA,KAAW;;;ACvBnD,SAAS,KAAA,CAAS,OAAY,IAAA,EAAqB;AACxD,EAAA,IAAI,IAAA,IAAQ,CAAA,EAAG,OAAO,EAAC;AACvB,EAAA,MAAM,SAAgB,EAAC;AACvB,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,IAAA,EAAM;AAC3C,IAAA,MAAA,CAAO,KAAK,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,IAAI,CAAC,CAAA;AAAA,EACtC;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,OAAA,CACd,OACA,KAAA,EACgB;AAChB,EAAA,OAAO,KAAA,CAAM,MAAA;AAAA,IACX,CAAC,KAAK,IAAA,KAAS;AACb,MAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,MAAA,CAAC,IAAI,GAAG,CAAA,KAAM,EAAC,EAAG,KAAK,IAAI,CAAA;AAC3B,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IACA;AAAC,GACH;AACF;AAGO,SAAS,MAAA,CAAU,OAAY,KAAA,EAA0C;AAC9E,EAAA,OAAO,CAAC,GAAG,KAAK,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAC/B,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,EAAA;AACxB,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,CAAA;AACxB,IAAA,OAAO,CAAA;AAAA,EACT,CAAC,CAAA;AACH;AAGO,SAAS,QAAA,CAAY,OAAY,KAAA,EAAkC;AACxE,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAI;AACrB,EAAA,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,IAAA,KAAS;AAC5B,IAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,KAAA;AAC1B,IAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AACH;AAGO,SAAS,SAAA,CAAa,OAAY,SAAA,EAA6C;AACpF,EAAA,MAAM,SAAc,EAAC;AACrB,EAAA,MAAM,QAAa,EAAC;AACpB,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,SAAA,CAAU,IAAI,CAAA,EAAG,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,SAChC,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,EACtB;AACA,EAAA,OAAO,CAAC,QAAQ,KAAK,CAAA;AACvB;AAGO,SAAS,KAAA,CAAM,KAAA,EAAe,GAAA,EAAc,IAAA,GAAO,CAAA,EAAa;AACrE,EAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,IAAA,GAAA,GAAM,KAAA;AACN,IAAA,KAAA,GAAQ,CAAA;AAAA,EACV;AACA,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,IAAI,OAAO,CAAA,EAAG;AACZ,IAAA,KAAA,IAAS,CAAA,GAAI,OAAO,CAAA,GAAI,GAAA,EAAK,KAAK,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EACvD,CAAA,MAAA,IAAW,OAAO,CAAA,EAAG;AACnB,IAAA,KAAA,IAAS,CAAA,GAAI,OAAO,CAAA,GAAI,GAAA,EAAK,KAAK,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EACvD;AACA,EAAA,OAAO,MAAA;AACT;;;ACtEO,SAAS,IAAA,CAA0C,KAAQ,IAAA,EAAuB;AACvF,EAAA,MAAM,SAAS,EAAC;AAChB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,OAAO,GAAA,EAAK,MAAA,CAAO,GAAG,CAAA,GAAI,IAAI,GAAG,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,IAAA,CAA0C,KAAQ,IAAA,EAAuB;AACvF,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,GAAA,EAAI;AACxB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,OAAO,OAAO,GAAG,CAAA;AAAA,EACnB;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,GAAA,CAAiB,GAAA,EAAc,IAAA,EAAc,YAAA,EAAiC;AAC5F,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC3B,EAAA,IAAI,MAAA,GAAkB,GAAA;AACtB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,MAAA,IAAU,IAAA,IAAQ,OAAO,MAAA,KAAW,UAAU,OAAO,YAAA;AACzD,IAAA,MAAA,GAAU,OAAmC,GAAG,CAAA;AAAA,EAClD;AACA,EAAA,OAAQ,MAAA,IAAU,YAAA;AACpB;AAGO,SAAS,SAAA,CAAU,GAAY,CAAA,EAAqB;AACzD,EAAA,IAAI,CAAA,KAAM,GAAG,OAAO,IAAA;AACpB,EAAA,IAAI,CAAA,IAAK,IAAA,IAAQ,CAAA,IAAK,IAAA,EAAM,OAAO,KAAA;AACnC,EAAA,IAAI,OAAO,CAAA,KAAM,OAAO,CAAA,EAAG,OAAO,KAAA;AAClC,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,KAAA;AAElC,EAAA,IAAI,MAAM,OAAA,CAAQ,CAAC,KAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AACxC,IAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,IAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAC,IAAA,EAAM,CAAA,KAAM,UAAU,IAAA,EAAM,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AAAA,EACnD;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,CAAW,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,CAAW,CAAA;AACrC,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,KAAA,CAAM,MAAA,EAAQ,OAAO,KAAA;AAC1C,EAAA,OAAO,KAAA,CAAM,KAAA;AAAA,IAAM,CAAC,QAClB,SAAA,CAAW,CAAA,CAA8B,GAAG,CAAA,EAAI,CAAA,CAA8B,GAAG,CAAC;AAAA,GACpF;AACF;;;AC/CO,IAAM,UAAA,GAAa,CAAC,GAAA,KACzB,GAAA,CAAI,WAAW,CAAA,GAAI,GAAA,GAAM,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,GAAA,CAAI,MAAM,CAAC;AAE7D,IAAM,SAAA,GAAY,CAAC,GAAA,KACxB,GAAA,CACG,QAAQ,cAAA,EAAgB,CAAC,GAAG,IAAA,KAAU,IAAA,GAAO,KAAK,WAAA,EAAY,GAAI,EAAG,CAAA,CACrE,OAAA,CAAQ,QAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,EAAa;AAEpC,IAAM,SAAA,GAAY,CAAC,GAAA,KACxB,GAAA,CACG,OAAA,CAAQ,iBAAA,EAAmB,OAAO,CAAA,CAClC,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CACtB,WAAA;AAEE,IAAM,SAAA,GAAY,CAAC,GAAA,KACxB,GAAA,CACG,OAAA,CAAQ,iBAAA,EAAmB,OAAO,CAAA,CAClC,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CACtB,WAAA;AAEE,IAAM,WAAW,CAAC,GAAA,EAAa,SAAA,EAAmB,MAAA,GAAS,aAChE,GAAA,CAAI,MAAA,IAAU,SAAA,GAAY,GAAA,GAAM,IAAI,KAAA,CAAM,CAAA,EAAG,SAAA,GAAY,MAAA,CAAO,MAAM,CAAA,GAAI;AAErE,IAAM,OAAA,GAAU,CAAC,GAAA,KACtB,GAAA,CACG,WAAA,GACA,SAAA,CAAU,KAAK,CAAA,CACf,OAAA,CAAQ,kBAAA,EAAoB,EAAE,EAC9B,OAAA,CAAQ,eAAA,EAAiB,EAAE,CAAA,CAC3B,IAAA,EAAK,CACL,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CACnB,OAAA,CAAQ,KAAA,EAAO,GAAG;;;AC9BhB,IAAM,KAAA,GAAQ,CAAC,KAAA,EAAe,GAAA,EAAa,GAAA,KAChD,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAK,CAAC;AAG7B,IAAM,OAAA,GAAU,CAAC,KAAA,EAAe,QAAA,GAAW,CAAA,KAAc;AAC9D,EAAA,MAAM,SAAS,EAAA,IAAM,QAAA;AACrB,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,MAAM,CAAA,GAAI,MAAA;AACtC;AAGO,IAAM,OAAO,CAAC,KAAA,EAAe,KAAa,CAAA,KAAsB,KAAA,GAAA,CAAS,MAAM,KAAA,IAAS;AAGxF,IAAM,YAAA,GAAe,CAC1B,KAAA,EACA,OAAA,GAAoC,EAAC,EACrC,MAAA,GAAS,OAAA,KACE,IAAI,KAAK,YAAA,CAAa,MAAA,EAAQ,OAAO,CAAA,CAAE,OAAO,KAAK;AAEzD,IAAM,cAAA,GAAiB,CAC5B,KAAA,EACA,QAAA,GAAW,OACX,MAAA,GAAS,OAAA,KACE,YAAA,CAAa,KAAA,EAAO,EAAE,KAAA,EAAO,UAAA,EAAY,QAAA,IAAY,MAAM;AAEjE,IAAM,gBAAgB,CAC3B,KAAA,EACA,QAAA,GAAW,CAAA,EACX,SAAS,OAAA,KAET,YAAA;AAAA,EACE,KAAA;AAAA,EACA,EAAE,KAAA,EAAO,SAAA,EAAW,qBAAA,EAAuB,QAAA,EAAU,uBAAuB,QAAA,EAAS;AAAA,EACrF;AACF;;;ACjCK,IAAM,gBAAA,GAAmB,CAAC,IAAA,KAC/B,IAAA,EAAM,kBAAkB,SAAA,EAAU,GAAI,WAAY,EAAC;AAE9C,IAAM,cAAA,GAAiB,CAAC,IAAA,KAC5B,gBAAA,CAAiB,IAAI,EAAE,WAAA,KAAgB,SAAA,EAAU,GAAI,MAAA,GAAU,EAAC;AAE5D,IAAM,gBAAA,GAAmB,CAAC,GAAA,GAAgB,QAAA,KAA6B,GAAA,CAAI;AAG3E,IAAM,kBAAA,GAAqB;AAAA,EAChC,SAAA;AAAA,EACA,wBAAA;AAAA,EACA,4CAAA;AAAA,EACA,wBAAA;AAAA,EACA,0BAAA;AAAA,EACA,iCAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG;AAEH,IAAM,oBAAA,GAAuB,CAAC,SAAA,KAAiD;AACpF,EAAA,IAAI,CAAC,SAAA,EAAW,OAAO,EAAC;AACxB,EAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,gBAAA,CAA8B,kBAAkB,CAAC,CAAA,CAAE,MAAA;AAAA,IAC7E,CAAC,OAAO,CAAC,EAAA,CAAG,aAAa,UAAU,CAAA,IAAK,GAAG,YAAA,KAAiB;AAAA,GAC9D;AACF;AAEO,IAAM,QAAA,GAAW,CAAC,MAAA,EAAqB,KAAA,KAAgC;AAC5E,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,KAAA,EAAO,OAAO,KAAA;AAC9B,EAAA,OAAO,MAAA,CAAO,SAAS,KAAK,CAAA;AAC9B","file":"index.cjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names intelligently — conflicting utilities are resolved.\n *\n * @example\n * cn('px-2 py-1', condition && 'px-4') // → 'py-1 px-4'\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Compose two event handlers into one. The handlers are called in order.\n * If the original handler calls `event.preventDefault()`, the next one is skipped\n * unless `checkForDefaultPrevented` is `false`.\n */\nexport function composeEventHandlers<E>(\n originalEventHandler?: (event: E) => void,\n ourEventHandler?: (event: E) => void,\n { checkForDefaultPrevented = true } = {},\n): (event: E) => void {\n return function handleEvent(event: E) {\n originalEventHandler?.(event);\n\n if (\n checkForDefaultPrevented === false ||\n !(event as unknown as { defaultPrevented: boolean }).defaultPrevented\n ) {\n ourEventHandler?.(event);\n }\n };\n}\n\n/**\n * Merge two sets of React props, with the second set taking precedence\n * except for event handlers and `className`/`style`, which are composed.\n */\ntype AnyProps = Record<string, unknown>;\n\nexport function mergeProps<T extends AnyProps, U extends AnyProps>(\n base: T,\n override: U,\n): T & U {\n const merged: AnyProps = { ...base, ...override };\n\n for (const key in override) {\n const baseVal = base[key];\n const overrideVal = override[key];\n\n // Compose event handlers (onClick, onKeyDown, etc.)\n if (/^on[A-Z]/.test(key) && typeof baseVal === 'function' && typeof overrideVal === 'function') {\n merged[key] = (...args: unknown[]) => {\n (overrideVal as (...args: unknown[]) => void)(...args);\n (baseVal as (...args: unknown[]) => void)(...args);\n };\n }\n // Merge styles\n else if (key === 'style' && typeof baseVal === 'object' && typeof overrideVal === 'object') {\n merged[key] = { ...(baseVal as object), ...(overrideVal as object) };\n }\n // Merge classNames\n else if (key === 'className' && typeof baseVal === 'string' && typeof overrideVal === 'string') {\n merged[key] = [baseVal, overrideVal].filter(Boolean).join(' ');\n }\n }\n\n return merged as T & U;\n}\n","export const isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>\n typeof value === 'function';\n\nexport const isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const isString = (value: unknown): value is string => typeof value === 'string';\n\nexport const isNumber = (value: unknown): value is number =>\n typeof value === 'number' && !Number.isNaN(value);\n\nexport const isArray = <T = unknown>(value: unknown): value is T[] => Array.isArray(value);\n\nexport const isNullish = (value: unknown): value is null | undefined => value == null;\n\nexport const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';\n\nexport const isEmpty = (value: unknown): boolean => {\n if (isNullish(value)) return true;\n if (isString(value) || isArray(value)) return value.length === 0;\n if (isObject(value)) return Object.keys(value).length === 0;\n return false;\n};\n\nexport const isBrowser = (): boolean => typeof window !== 'undefined';\n","/** Split an array into chunks of size N. */\nexport function chunk<T>(array: T[], size: number): T[][] {\n if (size <= 0) return [];\n const result: T[][] = [];\n for (let i = 0; i < array.length; i += size) {\n result.push(array.slice(i, i + size));\n }\n return result;\n}\n\n/** Group array items by a key function. */\nexport function groupBy<T, K extends string | number>(\n array: T[],\n keyFn: (item: T) => K,\n): Record<K, T[]> {\n return array.reduce(\n (acc, item) => {\n const key = keyFn(item);\n (acc[key] ||= []).push(item);\n return acc;\n },\n {} as Record<K, T[]>,\n );\n}\n\n/** Sort array by a key function (non-mutating). */\nexport function sortBy<T>(array: T[], keyFn: (item: T) => number | string): T[] {\n return [...array].sort((a, b) => {\n const aKey = keyFn(a);\n const bKey = keyFn(b);\n if (aKey < bKey) return -1;\n if (aKey > bKey) return 1;\n return 0;\n });\n}\n\n/** Get unique items by a key function. */\nexport function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[] {\n const seen = new Set();\n return array.filter((item) => {\n const key = keyFn(item);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** Partition array into [matching, notMatching]. */\nexport function partition<T>(array: T[], predicate: (item: T) => boolean): [T[], T[]] {\n const truthy: T[] = [];\n const falsy: T[] = [];\n for (const item of array) {\n if (predicate(item)) truthy.push(item);\n else falsy.push(item);\n }\n return [truthy, falsy];\n}\n\n/** Generate a numeric range. */\nexport function range(start: number, end?: number, step = 1): number[] {\n if (end === undefined) {\n end = start;\n start = 0;\n }\n const result: number[] = [];\n if (step > 0) {\n for (let i = start; i < end; i += step) result.push(i);\n } else if (step < 0) {\n for (let i = start; i > end; i += step) result.push(i);\n }\n return result;\n}\n","/** Pick specified keys from an object. */\nexport function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n if (key in obj) result[key] = obj[key];\n }\n return result;\n}\n\n/** Omit specified keys from an object. */\nexport function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\n const result = { ...obj };\n for (const key of keys) {\n delete result[key];\n }\n return result;\n}\n\n/** Get a nested value by dot-path. */\nexport function get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined {\n const keys = path.split('.');\n let result: unknown = obj;\n for (const key of keys) {\n if (result == null || typeof result !== 'object') return defaultValue;\n result = (result as Record<string, unknown>)[key];\n }\n return (result ?? defaultValue) as T | undefined;\n}\n\n/** Deep equality check. */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n","export const capitalize = (str: string): string =>\n str.length === 0 ? str : str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const camelCase = (str: string): string =>\n str\n .replace(/[-_\\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))\n .replace(/^(.)/, (m) => m.toLowerCase());\n\nexport const kebabCase = (str: string): string =>\n str\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_]+/g, '-')\n .toLowerCase();\n\nexport const snakeCase = (str: string): string =>\n str\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .replace(/[\\s-]+/g, '_')\n .toLowerCase();\n\nexport const truncate = (str: string, maxLength: number, suffix = '…'): string =>\n str.length <= maxLength ? str : str.slice(0, maxLength - suffix.length) + suffix;\n\nexport const slugify = (str: string): string =>\n str\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-z0-9\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-');\n","/** Clamp a number between min and max. */\nexport const clamp = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, value));\n\n/** Round to N decimal places. */\nexport const roundTo = (value: number, decimals = 0): number => {\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n};\n\n/** Linear interpolation between two values. */\nexport const lerp = (start: number, end: number, t: number): number => start + (end - start) * t;\n\n/** Format a number using Intl. */\nexport const formatNumber = (\n value: number,\n options: Intl.NumberFormatOptions = {},\n locale = 'en-US',\n): string => new Intl.NumberFormat(locale, options).format(value);\n\nexport const formatCurrency = (\n value: number,\n currency = 'USD',\n locale = 'en-US',\n): string => formatNumber(value, { style: 'currency', currency }, locale);\n\nexport const formatPercent = (\n value: number,\n decimals = 0,\n locale = 'en-US',\n): string =>\n formatNumber(\n value,\n { style: 'percent', minimumFractionDigits: decimals, maximumFractionDigits: decimals },\n locale,\n );\n","import { isBrowser } from './type-guards';\n\nexport const getOwnerDocument = (node: Node | null | undefined): Document =>\n node?.ownerDocument ?? (isBrowser() ? document : ({} as Document));\n\nexport const getOwnerWindow = (node: Node | null | undefined): Window =>\n (getOwnerDocument(node).defaultView ?? (isBrowser() ? window : ({} as Window))) as Window;\n\nexport const getActiveElement = (doc: Document = document): Element | null => doc.activeElement;\n\n/** A list of all focusable element selectors. */\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n].join(',');\n\nexport const getFocusableElements = (container: HTMLElement | null): HTMLElement[] => {\n if (!container) return [];\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (el) => !el.hasAttribute('disabled') && el.offsetParent !== null,\n );\n};\n\nexport const contains = (parent: Node | null, child: Node | null): boolean => {\n if (!parent || !child) return false;\n return parent.contains(child);\n};\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merge Tailwind class names intelligently — conflicting utilities are resolved.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* cn('px-2 py-1', condition && 'px-4') // → 'py-1 px-4'
|
|
8
|
+
*/
|
|
9
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compose two event handlers into one. The handlers are called in order.
|
|
13
|
+
* If the original handler calls `event.preventDefault()`, the next one is skipped
|
|
14
|
+
* unless `checkForDefaultPrevented` is `false`.
|
|
15
|
+
*/
|
|
16
|
+
declare function composeEventHandlers<E>(originalEventHandler?: (event: E) => void, ourEventHandler?: (event: E) => void, { checkForDefaultPrevented }?: {
|
|
17
|
+
checkForDefaultPrevented?: boolean | undefined;
|
|
18
|
+
}): (event: E) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Merge two sets of React props, with the second set taking precedence
|
|
21
|
+
* except for event handlers and `className`/`style`, which are composed.
|
|
22
|
+
*/
|
|
23
|
+
type AnyProps = Record<string, unknown>;
|
|
24
|
+
declare function mergeProps<T extends AnyProps, U extends AnyProps>(base: T, override: U): T & U;
|
|
25
|
+
|
|
26
|
+
declare const isFunction: (value: unknown) => value is (...args: unknown[]) => unknown;
|
|
27
|
+
declare const isObject: (value: unknown) => value is Record<string, unknown>;
|
|
28
|
+
declare const isString: (value: unknown) => value is string;
|
|
29
|
+
declare const isNumber: (value: unknown) => value is number;
|
|
30
|
+
declare const isArray: <T = unknown>(value: unknown) => value is T[];
|
|
31
|
+
declare const isNullish: (value: unknown) => value is null | undefined;
|
|
32
|
+
declare const isBoolean: (value: unknown) => value is boolean;
|
|
33
|
+
declare const isEmpty: (value: unknown) => boolean;
|
|
34
|
+
declare const isBrowser: () => boolean;
|
|
35
|
+
|
|
36
|
+
/** Split an array into chunks of size N. */
|
|
37
|
+
declare function chunk<T>(array: T[], size: number): T[][];
|
|
38
|
+
/** Group array items by a key function. */
|
|
39
|
+
declare function groupBy<T, K extends string | number>(array: T[], keyFn: (item: T) => K): Record<K, T[]>;
|
|
40
|
+
/** Sort array by a key function (non-mutating). */
|
|
41
|
+
declare function sortBy<T>(array: T[], keyFn: (item: T) => number | string): T[];
|
|
42
|
+
/** Get unique items by a key function. */
|
|
43
|
+
declare function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[];
|
|
44
|
+
/** Partition array into [matching, notMatching]. */
|
|
45
|
+
declare function partition<T>(array: T[], predicate: (item: T) => boolean): [T[], T[]];
|
|
46
|
+
/** Generate a numeric range. */
|
|
47
|
+
declare function range(start: number, end?: number, step?: number): number[];
|
|
48
|
+
|
|
49
|
+
/** Pick specified keys from an object. */
|
|
50
|
+
declare function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
|
|
51
|
+
/** Omit specified keys from an object. */
|
|
52
|
+
declare function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
|
|
53
|
+
/** Get a nested value by dot-path. */
|
|
54
|
+
declare function get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined;
|
|
55
|
+
/** Deep equality check. */
|
|
56
|
+
declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
57
|
+
|
|
58
|
+
declare const capitalize: (str: string) => string;
|
|
59
|
+
declare const camelCase: (str: string) => string;
|
|
60
|
+
declare const kebabCase: (str: string) => string;
|
|
61
|
+
declare const snakeCase: (str: string) => string;
|
|
62
|
+
declare const truncate: (str: string, maxLength: number, suffix?: string) => string;
|
|
63
|
+
declare const slugify: (str: string) => string;
|
|
64
|
+
|
|
65
|
+
/** Clamp a number between min and max. */
|
|
66
|
+
declare const clamp: (value: number, min: number, max: number) => number;
|
|
67
|
+
/** Round to N decimal places. */
|
|
68
|
+
declare const roundTo: (value: number, decimals?: number) => number;
|
|
69
|
+
/** Linear interpolation between two values. */
|
|
70
|
+
declare const lerp: (start: number, end: number, t: number) => number;
|
|
71
|
+
/** Format a number using Intl. */
|
|
72
|
+
declare const formatNumber: (value: number, options?: Intl.NumberFormatOptions, locale?: string) => string;
|
|
73
|
+
declare const formatCurrency: (value: number, currency?: string, locale?: string) => string;
|
|
74
|
+
declare const formatPercent: (value: number, decimals?: number, locale?: string) => string;
|
|
75
|
+
|
|
76
|
+
declare const getOwnerDocument: (node: Node | null | undefined) => Document;
|
|
77
|
+
declare const getOwnerWindow: (node: Node | null | undefined) => Window;
|
|
78
|
+
declare const getActiveElement: (doc?: Document) => Element | null;
|
|
79
|
+
/** A list of all focusable element selectors. */
|
|
80
|
+
declare const FOCUSABLE_SELECTOR: string;
|
|
81
|
+
declare const getFocusableElements: (container: HTMLElement | null) => HTMLElement[];
|
|
82
|
+
declare const contains: (parent: Node | null, child: Node | null) => boolean;
|
|
83
|
+
|
|
84
|
+
export { FOCUSABLE_SELECTOR, camelCase, capitalize, chunk, clamp, cn, composeEventHandlers, contains, deepEqual, formatCurrency, formatNumber, formatPercent, get, getActiveElement, getFocusableElements, getOwnerDocument, getOwnerWindow, groupBy, isArray, isBoolean, isBrowser, isEmpty, isFunction, isNullish, isNumber, isObject, isString, kebabCase, lerp, mergeProps, omit, partition, pick, range, roundTo, slugify, snakeCase, sortBy, truncate, uniqueBy };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merge Tailwind class names intelligently — conflicting utilities are resolved.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* cn('px-2 py-1', condition && 'px-4') // → 'py-1 px-4'
|
|
8
|
+
*/
|
|
9
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compose two event handlers into one. The handlers are called in order.
|
|
13
|
+
* If the original handler calls `event.preventDefault()`, the next one is skipped
|
|
14
|
+
* unless `checkForDefaultPrevented` is `false`.
|
|
15
|
+
*/
|
|
16
|
+
declare function composeEventHandlers<E>(originalEventHandler?: (event: E) => void, ourEventHandler?: (event: E) => void, { checkForDefaultPrevented }?: {
|
|
17
|
+
checkForDefaultPrevented?: boolean | undefined;
|
|
18
|
+
}): (event: E) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Merge two sets of React props, with the second set taking precedence
|
|
21
|
+
* except for event handlers and `className`/`style`, which are composed.
|
|
22
|
+
*/
|
|
23
|
+
type AnyProps = Record<string, unknown>;
|
|
24
|
+
declare function mergeProps<T extends AnyProps, U extends AnyProps>(base: T, override: U): T & U;
|
|
25
|
+
|
|
26
|
+
declare const isFunction: (value: unknown) => value is (...args: unknown[]) => unknown;
|
|
27
|
+
declare const isObject: (value: unknown) => value is Record<string, unknown>;
|
|
28
|
+
declare const isString: (value: unknown) => value is string;
|
|
29
|
+
declare const isNumber: (value: unknown) => value is number;
|
|
30
|
+
declare const isArray: <T = unknown>(value: unknown) => value is T[];
|
|
31
|
+
declare const isNullish: (value: unknown) => value is null | undefined;
|
|
32
|
+
declare const isBoolean: (value: unknown) => value is boolean;
|
|
33
|
+
declare const isEmpty: (value: unknown) => boolean;
|
|
34
|
+
declare const isBrowser: () => boolean;
|
|
35
|
+
|
|
36
|
+
/** Split an array into chunks of size N. */
|
|
37
|
+
declare function chunk<T>(array: T[], size: number): T[][];
|
|
38
|
+
/** Group array items by a key function. */
|
|
39
|
+
declare function groupBy<T, K extends string | number>(array: T[], keyFn: (item: T) => K): Record<K, T[]>;
|
|
40
|
+
/** Sort array by a key function (non-mutating). */
|
|
41
|
+
declare function sortBy<T>(array: T[], keyFn: (item: T) => number | string): T[];
|
|
42
|
+
/** Get unique items by a key function. */
|
|
43
|
+
declare function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[];
|
|
44
|
+
/** Partition array into [matching, notMatching]. */
|
|
45
|
+
declare function partition<T>(array: T[], predicate: (item: T) => boolean): [T[], T[]];
|
|
46
|
+
/** Generate a numeric range. */
|
|
47
|
+
declare function range(start: number, end?: number, step?: number): number[];
|
|
48
|
+
|
|
49
|
+
/** Pick specified keys from an object. */
|
|
50
|
+
declare function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
|
|
51
|
+
/** Omit specified keys from an object. */
|
|
52
|
+
declare function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
|
|
53
|
+
/** Get a nested value by dot-path. */
|
|
54
|
+
declare function get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined;
|
|
55
|
+
/** Deep equality check. */
|
|
56
|
+
declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
57
|
+
|
|
58
|
+
declare const capitalize: (str: string) => string;
|
|
59
|
+
declare const camelCase: (str: string) => string;
|
|
60
|
+
declare const kebabCase: (str: string) => string;
|
|
61
|
+
declare const snakeCase: (str: string) => string;
|
|
62
|
+
declare const truncate: (str: string, maxLength: number, suffix?: string) => string;
|
|
63
|
+
declare const slugify: (str: string) => string;
|
|
64
|
+
|
|
65
|
+
/** Clamp a number between min and max. */
|
|
66
|
+
declare const clamp: (value: number, min: number, max: number) => number;
|
|
67
|
+
/** Round to N decimal places. */
|
|
68
|
+
declare const roundTo: (value: number, decimals?: number) => number;
|
|
69
|
+
/** Linear interpolation between two values. */
|
|
70
|
+
declare const lerp: (start: number, end: number, t: number) => number;
|
|
71
|
+
/** Format a number using Intl. */
|
|
72
|
+
declare const formatNumber: (value: number, options?: Intl.NumberFormatOptions, locale?: string) => string;
|
|
73
|
+
declare const formatCurrency: (value: number, currency?: string, locale?: string) => string;
|
|
74
|
+
declare const formatPercent: (value: number, decimals?: number, locale?: string) => string;
|
|
75
|
+
|
|
76
|
+
declare const getOwnerDocument: (node: Node | null | undefined) => Document;
|
|
77
|
+
declare const getOwnerWindow: (node: Node | null | undefined) => Window;
|
|
78
|
+
declare const getActiveElement: (doc?: Document) => Element | null;
|
|
79
|
+
/** A list of all focusable element selectors. */
|
|
80
|
+
declare const FOCUSABLE_SELECTOR: string;
|
|
81
|
+
declare const getFocusableElements: (container: HTMLElement | null) => HTMLElement[];
|
|
82
|
+
declare const contains: (parent: Node | null, child: Node | null) => boolean;
|
|
83
|
+
|
|
84
|
+
export { FOCUSABLE_SELECTOR, camelCase, capitalize, chunk, clamp, cn, composeEventHandlers, contains, deepEqual, formatCurrency, formatNumber, formatPercent, get, getActiveElement, getFocusableElements, getOwnerDocument, getOwnerWindow, groupBy, isArray, isBoolean, isBrowser, isEmpty, isFunction, isNullish, isNumber, isObject, isString, kebabCase, lerp, mergeProps, omit, partition, pick, range, roundTo, slugify, snakeCase, sortBy, truncate, uniqueBy };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
// src/class-names.ts
|
|
5
|
+
function cn(...inputs) {
|
|
6
|
+
return twMerge(clsx(inputs));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/compose.ts
|
|
10
|
+
function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
|
|
11
|
+
return function handleEvent(event) {
|
|
12
|
+
originalEventHandler?.(event);
|
|
13
|
+
if (checkForDefaultPrevented === false || !event.defaultPrevented) {
|
|
14
|
+
ourEventHandler?.(event);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function mergeProps(base, override) {
|
|
19
|
+
const merged = { ...base, ...override };
|
|
20
|
+
for (const key in override) {
|
|
21
|
+
const baseVal = base[key];
|
|
22
|
+
const overrideVal = override[key];
|
|
23
|
+
if (/^on[A-Z]/.test(key) && typeof baseVal === "function" && typeof overrideVal === "function") {
|
|
24
|
+
merged[key] = (...args) => {
|
|
25
|
+
overrideVal(...args);
|
|
26
|
+
baseVal(...args);
|
|
27
|
+
};
|
|
28
|
+
} else if (key === "style" && typeof baseVal === "object" && typeof overrideVal === "object") {
|
|
29
|
+
merged[key] = { ...baseVal, ...overrideVal };
|
|
30
|
+
} else if (key === "className" && typeof baseVal === "string" && typeof overrideVal === "string") {
|
|
31
|
+
merged[key] = [baseVal, overrideVal].filter(Boolean).join(" ");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/type-guards.ts
|
|
38
|
+
var isFunction = (value) => typeof value === "function";
|
|
39
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
var isString = (value) => typeof value === "string";
|
|
41
|
+
var isNumber = (value) => typeof value === "number" && !Number.isNaN(value);
|
|
42
|
+
var isArray = (value) => Array.isArray(value);
|
|
43
|
+
var isNullish = (value) => value == null;
|
|
44
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
45
|
+
var isEmpty = (value) => {
|
|
46
|
+
if (isNullish(value)) return true;
|
|
47
|
+
if (isString(value) || isArray(value)) return value.length === 0;
|
|
48
|
+
if (isObject(value)) return Object.keys(value).length === 0;
|
|
49
|
+
return false;
|
|
50
|
+
};
|
|
51
|
+
var isBrowser = () => typeof window !== "undefined";
|
|
52
|
+
|
|
53
|
+
// src/array.ts
|
|
54
|
+
function chunk(array, size) {
|
|
55
|
+
if (size <= 0) return [];
|
|
56
|
+
const result = [];
|
|
57
|
+
for (let i = 0; i < array.length; i += size) {
|
|
58
|
+
result.push(array.slice(i, i + size));
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function groupBy(array, keyFn) {
|
|
63
|
+
return array.reduce(
|
|
64
|
+
(acc, item) => {
|
|
65
|
+
const key = keyFn(item);
|
|
66
|
+
(acc[key] ||= []).push(item);
|
|
67
|
+
return acc;
|
|
68
|
+
},
|
|
69
|
+
{}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
function sortBy(array, keyFn) {
|
|
73
|
+
return [...array].sort((a, b) => {
|
|
74
|
+
const aKey = keyFn(a);
|
|
75
|
+
const bKey = keyFn(b);
|
|
76
|
+
if (aKey < bKey) return -1;
|
|
77
|
+
if (aKey > bKey) return 1;
|
|
78
|
+
return 0;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function uniqueBy(array, keyFn) {
|
|
82
|
+
const seen = /* @__PURE__ */ new Set();
|
|
83
|
+
return array.filter((item) => {
|
|
84
|
+
const key = keyFn(item);
|
|
85
|
+
if (seen.has(key)) return false;
|
|
86
|
+
seen.add(key);
|
|
87
|
+
return true;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function partition(array, predicate) {
|
|
91
|
+
const truthy = [];
|
|
92
|
+
const falsy = [];
|
|
93
|
+
for (const item of array) {
|
|
94
|
+
if (predicate(item)) truthy.push(item);
|
|
95
|
+
else falsy.push(item);
|
|
96
|
+
}
|
|
97
|
+
return [truthy, falsy];
|
|
98
|
+
}
|
|
99
|
+
function range(start, end, step = 1) {
|
|
100
|
+
if (end === void 0) {
|
|
101
|
+
end = start;
|
|
102
|
+
start = 0;
|
|
103
|
+
}
|
|
104
|
+
const result = [];
|
|
105
|
+
if (step > 0) {
|
|
106
|
+
for (let i = start; i < end; i += step) result.push(i);
|
|
107
|
+
} else if (step < 0) {
|
|
108
|
+
for (let i = start; i > end; i += step) result.push(i);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/object.ts
|
|
114
|
+
function pick(obj, keys) {
|
|
115
|
+
const result = {};
|
|
116
|
+
for (const key of keys) {
|
|
117
|
+
if (key in obj) result[key] = obj[key];
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
function omit(obj, keys) {
|
|
122
|
+
const result = { ...obj };
|
|
123
|
+
for (const key of keys) {
|
|
124
|
+
delete result[key];
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
function get(obj, path, defaultValue) {
|
|
129
|
+
const keys = path.split(".");
|
|
130
|
+
let result = obj;
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
if (result == null || typeof result !== "object") return defaultValue;
|
|
133
|
+
result = result[key];
|
|
134
|
+
}
|
|
135
|
+
return result ?? defaultValue;
|
|
136
|
+
}
|
|
137
|
+
function deepEqual(a, b) {
|
|
138
|
+
if (a === b) return true;
|
|
139
|
+
if (a == null || b == null) return false;
|
|
140
|
+
if (typeof a !== typeof b) return false;
|
|
141
|
+
if (typeof a !== "object") return false;
|
|
142
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
143
|
+
if (a.length !== b.length) return false;
|
|
144
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
145
|
+
}
|
|
146
|
+
const aKeys = Object.keys(a);
|
|
147
|
+
const bKeys = Object.keys(b);
|
|
148
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
149
|
+
return aKeys.every(
|
|
150
|
+
(key) => deepEqual(a[key], b[key])
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/string.ts
|
|
155
|
+
var capitalize = (str) => str.length === 0 ? str : str.charAt(0).toUpperCase() + str.slice(1);
|
|
156
|
+
var camelCase = (str) => str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^(.)/, (m) => m.toLowerCase());
|
|
157
|
+
var kebabCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
158
|
+
var snakeCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
159
|
+
var truncate = (str, maxLength, suffix = "\u2026") => str.length <= maxLength ? str : str.slice(0, maxLength - suffix.length) + suffix;
|
|
160
|
+
var slugify = (str) => str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
|
|
161
|
+
|
|
162
|
+
// src/number.ts
|
|
163
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
164
|
+
var roundTo = (value, decimals = 0) => {
|
|
165
|
+
const factor = 10 ** decimals;
|
|
166
|
+
return Math.round(value * factor) / factor;
|
|
167
|
+
};
|
|
168
|
+
var lerp = (start, end, t) => start + (end - start) * t;
|
|
169
|
+
var formatNumber = (value, options = {}, locale = "en-US") => new Intl.NumberFormat(locale, options).format(value);
|
|
170
|
+
var formatCurrency = (value, currency = "USD", locale = "en-US") => formatNumber(value, { style: "currency", currency }, locale);
|
|
171
|
+
var formatPercent = (value, decimals = 0, locale = "en-US") => formatNumber(
|
|
172
|
+
value,
|
|
173
|
+
{ style: "percent", minimumFractionDigits: decimals, maximumFractionDigits: decimals },
|
|
174
|
+
locale
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// src/dom.ts
|
|
178
|
+
var getOwnerDocument = (node) => node?.ownerDocument ?? (isBrowser() ? document : {});
|
|
179
|
+
var getOwnerWindow = (node) => getOwnerDocument(node).defaultView ?? (isBrowser() ? window : {});
|
|
180
|
+
var getActiveElement = (doc = document) => doc.activeElement;
|
|
181
|
+
var FOCUSABLE_SELECTOR = [
|
|
182
|
+
"a[href]",
|
|
183
|
+
"button:not([disabled])",
|
|
184
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
185
|
+
"select:not([disabled])",
|
|
186
|
+
"textarea:not([disabled])",
|
|
187
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
188
|
+
"audio[controls]",
|
|
189
|
+
"video[controls]",
|
|
190
|
+
'[contenteditable]:not([contenteditable="false"])'
|
|
191
|
+
].join(",");
|
|
192
|
+
var getFocusableElements = (container) => {
|
|
193
|
+
if (!container) return [];
|
|
194
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
195
|
+
(el) => !el.hasAttribute("disabled") && el.offsetParent !== null
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
var contains = (parent, child) => {
|
|
199
|
+
if (!parent || !child) return false;
|
|
200
|
+
return parent.contains(child);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export { FOCUSABLE_SELECTOR, camelCase, capitalize, chunk, clamp, cn, composeEventHandlers, contains, deepEqual, formatCurrency, formatNumber, formatPercent, get, getActiveElement, getFocusableElements, getOwnerDocument, getOwnerWindow, groupBy, isArray, isBoolean, isBrowser, isEmpty, isFunction, isNullish, isNumber, isObject, isString, kebabCase, lerp, mergeProps, omit, partition, pick, range, roundTo, slugify, snakeCase, sortBy, truncate, uniqueBy };
|
|
204
|
+
//# sourceMappingURL=index.mjs.map
|
|
205
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/class-names.ts","../src/compose.ts","../src/type-guards.ts","../src/array.ts","../src/object.ts","../src/string.ts","../src/number.ts","../src/dom.ts"],"names":[],"mappings":";;;;AASO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;;;ACNO,SAAS,oBAAA,CACd,sBACA,eAAA,EACA,EAAE,2BAA2B,IAAA,EAAK,GAAI,EAAC,EACnB;AACpB,EAAA,OAAO,SAAS,YAAY,KAAA,EAAU;AACpC,IAAA,oBAAA,GAAuB,KAAK,CAAA;AAE5B,IAAA,IACE,wBAAA,KAA6B,KAAA,IAC7B,CAAE,KAAA,CAAmD,gBAAA,EACrD;AACA,MAAA,eAAA,GAAkB,KAAK,CAAA;AAAA,IACzB;AAAA,EACF,CAAA;AACF;AAQO,SAAS,UAAA,CACd,MACA,QAAA,EACO;AACP,EAAA,MAAM,MAAA,GAAmB,EAAE,GAAG,IAAA,EAAM,GAAG,QAAA,EAAS;AAEhD,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,MAAM,OAAA,GAAU,KAAK,GAAG,CAAA;AACxB,IAAA,MAAM,WAAA,GAAc,SAAS,GAAG,CAAA;AAGhC,IAAA,IAAI,UAAA,CAAW,KAAK,GAAG,CAAA,IAAK,OAAO,OAAA,KAAY,UAAA,IAAc,OAAO,WAAA,KAAgB,UAAA,EAAY;AAC9F,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,CAAA,GAAI,IAAA,KAAoB;AACpC,QAAC,WAAA,CAA6C,GAAG,IAAI,CAAA;AACrD,QAAC,OAAA,CAAyC,GAAG,IAAI,CAAA;AAAA,MACnD,CAAA;AAAA,IACF,CAAA,MAAA,IAES,QAAQ,OAAA,IAAW,OAAO,YAAY,QAAA,IAAY,OAAO,gBAAgB,QAAA,EAAU;AAC1F,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,EAAE,GAAI,OAAA,EAAoB,GAAI,WAAA,EAAuB;AAAA,IACrE,CAAA,MAAA,IAES,QAAQ,WAAA,IAAe,OAAO,YAAY,QAAA,IAAY,OAAO,gBAAgB,QAAA,EAAU;AAC9F,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,CAAC,OAAA,EAAS,WAAW,EAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAAA,IAC/D;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;ACxDO,IAAM,UAAA,GAAa,CAAC,KAAA,KACzB,OAAO,KAAA,KAAU;AAEZ,IAAM,QAAA,GAAW,CAAC,KAAA,KACvB,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK;AAE9D,IAAM,QAAA,GAAW,CAAC,KAAA,KAAoC,OAAO,KAAA,KAAU;AAEvE,IAAM,QAAA,GAAW,CAAC,KAAA,KACvB,OAAO,UAAU,QAAA,IAAY,CAAC,MAAA,CAAO,KAAA,CAAM,KAAK;AAE3C,IAAM,OAAA,GAAU,CAAc,KAAA,KAAiC,KAAA,CAAM,QAAQ,KAAK;AAElF,IAAM,SAAA,GAAY,CAAC,KAAA,KAA8C,KAAA,IAAS;AAE1E,IAAM,SAAA,GAAY,CAAC,KAAA,KAAqC,OAAO,KAAA,KAAU;AAEzE,IAAM,OAAA,GAAU,CAAC,KAAA,KAA4B;AAClD,EAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG,OAAO,IAAA;AAC7B,EAAA,IAAI,QAAA,CAAS,KAAK,CAAA,IAAK,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAO,MAAM,MAAA,KAAW,CAAA;AAC/D,EAAA,IAAI,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,OAAO,IAAA,CAAK,KAAK,EAAE,MAAA,KAAW,CAAA;AAC1D,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,SAAA,GAAY,MAAe,OAAO,MAAA,KAAW;;;ACvBnD,SAAS,KAAA,CAAS,OAAY,IAAA,EAAqB;AACxD,EAAA,IAAI,IAAA,IAAQ,CAAA,EAAG,OAAO,EAAC;AACvB,EAAA,MAAM,SAAgB,EAAC;AACvB,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,IAAA,EAAM;AAC3C,IAAA,MAAA,CAAO,KAAK,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,IAAI,CAAC,CAAA;AAAA,EACtC;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,OAAA,CACd,OACA,KAAA,EACgB;AAChB,EAAA,OAAO,KAAA,CAAM,MAAA;AAAA,IACX,CAAC,KAAK,IAAA,KAAS;AACb,MAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,MAAA,CAAC,IAAI,GAAG,CAAA,KAAM,EAAC,EAAG,KAAK,IAAI,CAAA;AAC3B,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IACA;AAAC,GACH;AACF;AAGO,SAAS,MAAA,CAAU,OAAY,KAAA,EAA0C;AAC9E,EAAA,OAAO,CAAC,GAAG,KAAK,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAC/B,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,EAAA;AACxB,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,CAAA;AACxB,IAAA,OAAO,CAAA;AAAA,EACT,CAAC,CAAA;AACH;AAGO,SAAS,QAAA,CAAY,OAAY,KAAA,EAAkC;AACxE,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAI;AACrB,EAAA,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,IAAA,KAAS;AAC5B,IAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AACtB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,KAAA;AAC1B,IAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AACH;AAGO,SAAS,SAAA,CAAa,OAAY,SAAA,EAA6C;AACpF,EAAA,MAAM,SAAc,EAAC;AACrB,EAAA,MAAM,QAAa,EAAC;AACpB,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,SAAA,CAAU,IAAI,CAAA,EAAG,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,SAChC,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,EACtB;AACA,EAAA,OAAO,CAAC,QAAQ,KAAK,CAAA;AACvB;AAGO,SAAS,KAAA,CAAM,KAAA,EAAe,GAAA,EAAc,IAAA,GAAO,CAAA,EAAa;AACrE,EAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,IAAA,GAAA,GAAM,KAAA;AACN,IAAA,KAAA,GAAQ,CAAA;AAAA,EACV;AACA,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,IAAI,OAAO,CAAA,EAAG;AACZ,IAAA,KAAA,IAAS,CAAA,GAAI,OAAO,CAAA,GAAI,GAAA,EAAK,KAAK,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EACvD,CAAA,MAAA,IAAW,OAAO,CAAA,EAAG;AACnB,IAAA,KAAA,IAAS,CAAA,GAAI,OAAO,CAAA,GAAI,GAAA,EAAK,KAAK,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EACvD;AACA,EAAA,OAAO,MAAA;AACT;;;ACtEO,SAAS,IAAA,CAA0C,KAAQ,IAAA,EAAuB;AACvF,EAAA,MAAM,SAAS,EAAC;AAChB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,OAAO,GAAA,EAAK,MAAA,CAAO,GAAG,CAAA,GAAI,IAAI,GAAG,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,IAAA,CAA0C,KAAQ,IAAA,EAAuB;AACvF,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,GAAA,EAAI;AACxB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,OAAO,OAAO,GAAG,CAAA;AAAA,EACnB;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,GAAA,CAAiB,GAAA,EAAc,IAAA,EAAc,YAAA,EAAiC;AAC5F,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC3B,EAAA,IAAI,MAAA,GAAkB,GAAA;AACtB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,MAAA,IAAU,IAAA,IAAQ,OAAO,MAAA,KAAW,UAAU,OAAO,YAAA;AACzD,IAAA,MAAA,GAAU,OAAmC,GAAG,CAAA;AAAA,EAClD;AACA,EAAA,OAAQ,MAAA,IAAU,YAAA;AACpB;AAGO,SAAS,SAAA,CAAU,GAAY,CAAA,EAAqB;AACzD,EAAA,IAAI,CAAA,KAAM,GAAG,OAAO,IAAA;AACpB,EAAA,IAAI,CAAA,IAAK,IAAA,IAAQ,CAAA,IAAK,IAAA,EAAM,OAAO,KAAA;AACnC,EAAA,IAAI,OAAO,CAAA,KAAM,OAAO,CAAA,EAAG,OAAO,KAAA;AAClC,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,KAAA;AAElC,EAAA,IAAI,MAAM,OAAA,CAAQ,CAAC,KAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AACxC,IAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,IAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAC,IAAA,EAAM,CAAA,KAAM,UAAU,IAAA,EAAM,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AAAA,EACnD;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,CAAW,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,CAAW,CAAA;AACrC,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,KAAA,CAAM,MAAA,EAAQ,OAAO,KAAA;AAC1C,EAAA,OAAO,KAAA,CAAM,KAAA;AAAA,IAAM,CAAC,QAClB,SAAA,CAAW,CAAA,CAA8B,GAAG,CAAA,EAAI,CAAA,CAA8B,GAAG,CAAC;AAAA,GACpF;AACF;;;AC/CO,IAAM,UAAA,GAAa,CAAC,GAAA,KACzB,GAAA,CAAI,WAAW,CAAA,GAAI,GAAA,GAAM,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,GAAA,CAAI,MAAM,CAAC;AAE7D,IAAM,SAAA,GAAY,CAAC,GAAA,KACxB,GAAA,CACG,QAAQ,cAAA,EAAgB,CAAC,GAAG,IAAA,KAAU,IAAA,GAAO,KAAK,WAAA,EAAY,GAAI,EAAG,CAAA,CACrE,OAAA,CAAQ,QAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,EAAa;AAEpC,IAAM,SAAA,GAAY,CAAC,GAAA,KACxB,GAAA,CACG,OAAA,CAAQ,iBAAA,EAAmB,OAAO,CAAA,CAClC,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CACtB,WAAA;AAEE,IAAM,SAAA,GAAY,CAAC,GAAA,KACxB,GAAA,CACG,OAAA,CAAQ,iBAAA,EAAmB,OAAO,CAAA,CAClC,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CACtB,WAAA;AAEE,IAAM,WAAW,CAAC,GAAA,EAAa,SAAA,EAAmB,MAAA,GAAS,aAChE,GAAA,CAAI,MAAA,IAAU,SAAA,GAAY,GAAA,GAAM,IAAI,KAAA,CAAM,CAAA,EAAG,SAAA,GAAY,MAAA,CAAO,MAAM,CAAA,GAAI;AAErE,IAAM,OAAA,GAAU,CAAC,GAAA,KACtB,GAAA,CACG,WAAA,GACA,SAAA,CAAU,KAAK,CAAA,CACf,OAAA,CAAQ,kBAAA,EAAoB,EAAE,EAC9B,OAAA,CAAQ,eAAA,EAAiB,EAAE,CAAA,CAC3B,IAAA,EAAK,CACL,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CACnB,OAAA,CAAQ,KAAA,EAAO,GAAG;;;AC9BhB,IAAM,KAAA,GAAQ,CAAC,KAAA,EAAe,GAAA,EAAa,GAAA,KAChD,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAK,CAAC;AAG7B,IAAM,OAAA,GAAU,CAAC,KAAA,EAAe,QAAA,GAAW,CAAA,KAAc;AAC9D,EAAA,MAAM,SAAS,EAAA,IAAM,QAAA;AACrB,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,MAAM,CAAA,GAAI,MAAA;AACtC;AAGO,IAAM,OAAO,CAAC,KAAA,EAAe,KAAa,CAAA,KAAsB,KAAA,GAAA,CAAS,MAAM,KAAA,IAAS;AAGxF,IAAM,YAAA,GAAe,CAC1B,KAAA,EACA,OAAA,GAAoC,EAAC,EACrC,MAAA,GAAS,OAAA,KACE,IAAI,KAAK,YAAA,CAAa,MAAA,EAAQ,OAAO,CAAA,CAAE,OAAO,KAAK;AAEzD,IAAM,cAAA,GAAiB,CAC5B,KAAA,EACA,QAAA,GAAW,OACX,MAAA,GAAS,OAAA,KACE,YAAA,CAAa,KAAA,EAAO,EAAE,KAAA,EAAO,UAAA,EAAY,QAAA,IAAY,MAAM;AAEjE,IAAM,gBAAgB,CAC3B,KAAA,EACA,QAAA,GAAW,CAAA,EACX,SAAS,OAAA,KAET,YAAA;AAAA,EACE,KAAA;AAAA,EACA,EAAE,KAAA,EAAO,SAAA,EAAW,qBAAA,EAAuB,QAAA,EAAU,uBAAuB,QAAA,EAAS;AAAA,EACrF;AACF;;;ACjCK,IAAM,gBAAA,GAAmB,CAAC,IAAA,KAC/B,IAAA,EAAM,kBAAkB,SAAA,EAAU,GAAI,WAAY,EAAC;AAE9C,IAAM,cAAA,GAAiB,CAAC,IAAA,KAC5B,gBAAA,CAAiB,IAAI,EAAE,WAAA,KAAgB,SAAA,EAAU,GAAI,MAAA,GAAU,EAAC;AAE5D,IAAM,gBAAA,GAAmB,CAAC,GAAA,GAAgB,QAAA,KAA6B,GAAA,CAAI;AAG3E,IAAM,kBAAA,GAAqB;AAAA,EAChC,SAAA;AAAA,EACA,wBAAA;AAAA,EACA,4CAAA;AAAA,EACA,wBAAA;AAAA,EACA,0BAAA;AAAA,EACA,iCAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG;AAEH,IAAM,oBAAA,GAAuB,CAAC,SAAA,KAAiD;AACpF,EAAA,IAAI,CAAC,SAAA,EAAW,OAAO,EAAC;AACxB,EAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,gBAAA,CAA8B,kBAAkB,CAAC,CAAA,CAAE,MAAA;AAAA,IAC7E,CAAC,OAAO,CAAC,EAAA,CAAG,aAAa,UAAU,CAAA,IAAK,GAAG,YAAA,KAAiB;AAAA,GAC9D;AACF;AAEO,IAAM,QAAA,GAAW,CAAC,MAAA,EAAqB,KAAA,KAAgC;AAC5E,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,KAAA,EAAO,OAAO,KAAA;AAC9B,EAAA,OAAO,MAAA,CAAO,SAAS,KAAK,CAAA;AAC9B","file":"index.mjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names intelligently — conflicting utilities are resolved.\n *\n * @example\n * cn('px-2 py-1', condition && 'px-4') // → 'py-1 px-4'\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Compose two event handlers into one. The handlers are called in order.\n * If the original handler calls `event.preventDefault()`, the next one is skipped\n * unless `checkForDefaultPrevented` is `false`.\n */\nexport function composeEventHandlers<E>(\n originalEventHandler?: (event: E) => void,\n ourEventHandler?: (event: E) => void,\n { checkForDefaultPrevented = true } = {},\n): (event: E) => void {\n return function handleEvent(event: E) {\n originalEventHandler?.(event);\n\n if (\n checkForDefaultPrevented === false ||\n !(event as unknown as { defaultPrevented: boolean }).defaultPrevented\n ) {\n ourEventHandler?.(event);\n }\n };\n}\n\n/**\n * Merge two sets of React props, with the second set taking precedence\n * except for event handlers and `className`/`style`, which are composed.\n */\ntype AnyProps = Record<string, unknown>;\n\nexport function mergeProps<T extends AnyProps, U extends AnyProps>(\n base: T,\n override: U,\n): T & U {\n const merged: AnyProps = { ...base, ...override };\n\n for (const key in override) {\n const baseVal = base[key];\n const overrideVal = override[key];\n\n // Compose event handlers (onClick, onKeyDown, etc.)\n if (/^on[A-Z]/.test(key) && typeof baseVal === 'function' && typeof overrideVal === 'function') {\n merged[key] = (...args: unknown[]) => {\n (overrideVal as (...args: unknown[]) => void)(...args);\n (baseVal as (...args: unknown[]) => void)(...args);\n };\n }\n // Merge styles\n else if (key === 'style' && typeof baseVal === 'object' && typeof overrideVal === 'object') {\n merged[key] = { ...(baseVal as object), ...(overrideVal as object) };\n }\n // Merge classNames\n else if (key === 'className' && typeof baseVal === 'string' && typeof overrideVal === 'string') {\n merged[key] = [baseVal, overrideVal].filter(Boolean).join(' ');\n }\n }\n\n return merged as T & U;\n}\n","export const isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>\n typeof value === 'function';\n\nexport const isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const isString = (value: unknown): value is string => typeof value === 'string';\n\nexport const isNumber = (value: unknown): value is number =>\n typeof value === 'number' && !Number.isNaN(value);\n\nexport const isArray = <T = unknown>(value: unknown): value is T[] => Array.isArray(value);\n\nexport const isNullish = (value: unknown): value is null | undefined => value == null;\n\nexport const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';\n\nexport const isEmpty = (value: unknown): boolean => {\n if (isNullish(value)) return true;\n if (isString(value) || isArray(value)) return value.length === 0;\n if (isObject(value)) return Object.keys(value).length === 0;\n return false;\n};\n\nexport const isBrowser = (): boolean => typeof window !== 'undefined';\n","/** Split an array into chunks of size N. */\nexport function chunk<T>(array: T[], size: number): T[][] {\n if (size <= 0) return [];\n const result: T[][] = [];\n for (let i = 0; i < array.length; i += size) {\n result.push(array.slice(i, i + size));\n }\n return result;\n}\n\n/** Group array items by a key function. */\nexport function groupBy<T, K extends string | number>(\n array: T[],\n keyFn: (item: T) => K,\n): Record<K, T[]> {\n return array.reduce(\n (acc, item) => {\n const key = keyFn(item);\n (acc[key] ||= []).push(item);\n return acc;\n },\n {} as Record<K, T[]>,\n );\n}\n\n/** Sort array by a key function (non-mutating). */\nexport function sortBy<T>(array: T[], keyFn: (item: T) => number | string): T[] {\n return [...array].sort((a, b) => {\n const aKey = keyFn(a);\n const bKey = keyFn(b);\n if (aKey < bKey) return -1;\n if (aKey > bKey) return 1;\n return 0;\n });\n}\n\n/** Get unique items by a key function. */\nexport function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[] {\n const seen = new Set();\n return array.filter((item) => {\n const key = keyFn(item);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** Partition array into [matching, notMatching]. */\nexport function partition<T>(array: T[], predicate: (item: T) => boolean): [T[], T[]] {\n const truthy: T[] = [];\n const falsy: T[] = [];\n for (const item of array) {\n if (predicate(item)) truthy.push(item);\n else falsy.push(item);\n }\n return [truthy, falsy];\n}\n\n/** Generate a numeric range. */\nexport function range(start: number, end?: number, step = 1): number[] {\n if (end === undefined) {\n end = start;\n start = 0;\n }\n const result: number[] = [];\n if (step > 0) {\n for (let i = start; i < end; i += step) result.push(i);\n } else if (step < 0) {\n for (let i = start; i > end; i += step) result.push(i);\n }\n return result;\n}\n","/** Pick specified keys from an object. */\nexport function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n if (key in obj) result[key] = obj[key];\n }\n return result;\n}\n\n/** Omit specified keys from an object. */\nexport function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\n const result = { ...obj };\n for (const key of keys) {\n delete result[key];\n }\n return result;\n}\n\n/** Get a nested value by dot-path. */\nexport function get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined {\n const keys = path.split('.');\n let result: unknown = obj;\n for (const key of keys) {\n if (result == null || typeof result !== 'object') return defaultValue;\n result = (result as Record<string, unknown>)[key];\n }\n return (result ?? defaultValue) as T | undefined;\n}\n\n/** Deep equality check. */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n","export const capitalize = (str: string): string =>\n str.length === 0 ? str : str.charAt(0).toUpperCase() + str.slice(1);\n\nexport const camelCase = (str: string): string =>\n str\n .replace(/[-_\\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))\n .replace(/^(.)/, (m) => m.toLowerCase());\n\nexport const kebabCase = (str: string): string =>\n str\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_]+/g, '-')\n .toLowerCase();\n\nexport const snakeCase = (str: string): string =>\n str\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n .replace(/[\\s-]+/g, '_')\n .toLowerCase();\n\nexport const truncate = (str: string, maxLength: number, suffix = '…'): string =>\n str.length <= maxLength ? str : str.slice(0, maxLength - suffix.length) + suffix;\n\nexport const slugify = (str: string): string =>\n str\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-z0-9\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-');\n","/** Clamp a number between min and max. */\nexport const clamp = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, value));\n\n/** Round to N decimal places. */\nexport const roundTo = (value: number, decimals = 0): number => {\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n};\n\n/** Linear interpolation between two values. */\nexport const lerp = (start: number, end: number, t: number): number => start + (end - start) * t;\n\n/** Format a number using Intl. */\nexport const formatNumber = (\n value: number,\n options: Intl.NumberFormatOptions = {},\n locale = 'en-US',\n): string => new Intl.NumberFormat(locale, options).format(value);\n\nexport const formatCurrency = (\n value: number,\n currency = 'USD',\n locale = 'en-US',\n): string => formatNumber(value, { style: 'currency', currency }, locale);\n\nexport const formatPercent = (\n value: number,\n decimals = 0,\n locale = 'en-US',\n): string =>\n formatNumber(\n value,\n { style: 'percent', minimumFractionDigits: decimals, maximumFractionDigits: decimals },\n locale,\n );\n","import { isBrowser } from './type-guards';\n\nexport const getOwnerDocument = (node: Node | null | undefined): Document =>\n node?.ownerDocument ?? (isBrowser() ? document : ({} as Document));\n\nexport const getOwnerWindow = (node: Node | null | undefined): Window =>\n (getOwnerDocument(node).defaultView ?? (isBrowser() ? window : ({} as Window))) as Window;\n\nexport const getActiveElement = (doc: Document = document): Element | null => doc.activeElement;\n\n/** A list of all focusable element selectors. */\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n].join(',');\n\nexport const getFocusableElements = (container: HTMLElement | null): HTMLElement[] => {\n if (!container) return [];\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (el) => !el.hasAttribute('disabled') && el.offsetParent !== null,\n );\n};\n\nexport const contains = (parent: Node | null, child: Node | null): boolean => {\n if (!parent || !child) return false;\n return parent.contains(child);\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@structyl/utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Tree-shakeable TypeScript utilities for structyl: cn (Tailwind merge), prop/event composition, type guards, array/object/string/number/DOM helpers.",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"react",
|
|
10
|
+
"typescript",
|
|
11
|
+
"structyl",
|
|
12
|
+
"utils",
|
|
13
|
+
"cn",
|
|
14
|
+
"tailwind-merge",
|
|
15
|
+
"clsx",
|
|
16
|
+
"type-guards",
|
|
17
|
+
"tree-shakeable",
|
|
18
|
+
"merge-props",
|
|
19
|
+
"dom",
|
|
20
|
+
"helpers"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"clsx": "^2.1.1",
|
|
40
|
+
"tailwind-merge": "^2.5.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"tsup": "^8.3.0",
|
|
44
|
+
"typescript": "^5.6.3",
|
|
45
|
+
"vitest": "^2.1.2"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"lint": "eslint src",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"clean": "rm -rf dist .turbo"
|
|
55
|
+
}
|
|
56
|
+
}
|