@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 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
+ ![npm](https://img.shields.io/npm/v/@structyl/utils)
6
+ ![license](https://img.shields.io/npm/l/@structyl/utils)
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":"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","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"]}
@@ -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 };
@@ -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":"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","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
+ }