@jsdevlab/classnames 0.0.0-stage → 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.txt +21 -0
- package/README.md +360 -2
- package/README.zh-CN.md +353 -0
- package/dist/index.cjs +91 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +46 -0
- package/package.json +61 -4
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jsdevlab
|
|
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
CHANGED
|
@@ -1,3 +1,361 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @jsdevlab/classnames
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@jsdevlab/classnames)
|
|
4
|
+
[](https://github.com/jsdevlab/classnames/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE.txt)
|
|
6
|
+
|
|
7
|
+
**English** | [简体中文](./README.zh-CN.md)
|
|
8
|
+
|
|
9
|
+
Type-safe conditional class names for JavaScript and TypeScript — framework-agnostic,
|
|
10
|
+
classnames-compatible, with lazy evaluation and deduplication by default.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Familiar API** — the same `classNames(...)` call shape you already know.
|
|
15
|
+
- **Deduplication by default** — `'a b a'` becomes `'a b'`; first-occurrence order is preserved.
|
|
16
|
+
- **Lazy function values** — `{ active: () => condition }` is evaluated when the class string is built.
|
|
17
|
+
- **Rich inputs** — strings, numbers, booleans, objects, nested arrays, and `Set`s.
|
|
18
|
+
- **Dedup opt-out factory** — `createClassNames({ unique: false })` keeps duplicates when you need them.
|
|
19
|
+
- **`cx` alias** — a short alias with identical behavior.
|
|
20
|
+
- **TypeScript-first** — complete declarations and exported types.
|
|
21
|
+
- **Zero runtime dependencies** — ESM + CJS builds, works in any modern JS runtime.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @jsdevlab/classnames
|
|
27
|
+
# or
|
|
28
|
+
npm install @jsdevlab/classnames
|
|
29
|
+
# or
|
|
30
|
+
yarn add @jsdevlab/classnames
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { classNames, cx, createClassNames } from '@jsdevlab/classnames';
|
|
37
|
+
|
|
38
|
+
classNames('btn', 'btn-primary'); // 'btn btn-primary'
|
|
39
|
+
classNames('btn', { 'btn-primary': true, 'is-loading': false }); // 'btn btn-primary'
|
|
40
|
+
classNames('btn', ['btn-lg', ['rounded']]); // 'btn btn-lg rounded'
|
|
41
|
+
classNames('btn', new Set(['btn-lg', 'rounded'])); // 'btn btn-lg rounded'
|
|
42
|
+
classNames('btn', 'btn'); // 'btn'
|
|
43
|
+
|
|
44
|
+
cx('btn', { disabled: true }); // 'btn disabled'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
There is **no default export** — use named imports:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { classNames } from '@jsdevlab/classnames'; // ✅
|
|
51
|
+
// import classNames from '@jsdevlab/classnames'; // ❌ not supported
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Conditional class names
|
|
57
|
+
|
|
58
|
+
Object keys are emitted only when the corresponding value is truthy:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const isActive = true;
|
|
62
|
+
|
|
63
|
+
classNames('tab', {
|
|
64
|
+
'is-active': isActive,
|
|
65
|
+
'is-disabled': !isActive,
|
|
66
|
+
});
|
|
67
|
+
// 'tab is-active'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Strings and numbers
|
|
71
|
+
|
|
72
|
+
Numbers are stringified; top-level `0` is kept:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
classNames('z', 0, 1, 2); // 'z 0 1 2'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Note that object values use truthiness, so `0` is dropped there:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
classNames({ zero: 0, one: 1 }); // 'one'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Nested arrays and Sets
|
|
85
|
+
|
|
86
|
+
Arrays are flattened recursively, and `Set`s are supported directly:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
classNames(['a', ['b', ['c']]]); // 'a b c'
|
|
90
|
+
classNames(new Set(['a', 'b'])); // 'a b'
|
|
91
|
+
classNames([new Set(['a']), { b: true }]); // 'a b'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Lazy function values
|
|
95
|
+
|
|
96
|
+
A function used as an **object value** is invoked once at call time. Its return
|
|
97
|
+
value only decides whether the key is included — it is not converted to a class
|
|
98
|
+
name:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const flags = {
|
|
102
|
+
'is-active': () => user.active,
|
|
103
|
+
'is-admin': () => user.roles.includes('admin'),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
classNames('user', flags);
|
|
107
|
+
// Both functions are evaluated now; only truthy keys are emitted.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This keeps call sites clean and defers expensive checks until the class string is
|
|
111
|
+
actually built. Each function runs once per `classNames` call; reusing the same
|
|
112
|
+
object across calls re-evaluates it.
|
|
113
|
+
|
|
114
|
+
### Deduplication
|
|
115
|
+
|
|
116
|
+
Duplicates are removed by default, preserving the order of first appearance:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
classNames('a', 'b', 'a', ['b', { a: true }]); // 'a b'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Keeping duplicates
|
|
123
|
+
|
|
124
|
+
Create a factory with deduplication turned off:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createClassNames } from '@jsdevlab/classnames';
|
|
128
|
+
|
|
129
|
+
const join = createClassNames({ unique: false });
|
|
130
|
+
|
|
131
|
+
join('a', 'a'); // 'a a'
|
|
132
|
+
classNames('a', 'a'); // 'a'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Options are resolved when the factory is created, so the returned function is
|
|
136
|
+
bound to them:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const cls = createClassNames(); // unique defaults to true
|
|
140
|
+
cls('a', 'a'); // 'a'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### React
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { cx } from '@jsdevlab/classnames';
|
|
147
|
+
import type { ReactNode } from 'react';
|
|
148
|
+
|
|
149
|
+
interface ButtonProps {
|
|
150
|
+
primary?: boolean;
|
|
151
|
+
loading?: boolean;
|
|
152
|
+
className?: string;
|
|
153
|
+
children: ReactNode;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function Button({
|
|
157
|
+
primary,
|
|
158
|
+
loading,
|
|
159
|
+
className,
|
|
160
|
+
children,
|
|
161
|
+
}: ButtonProps) {
|
|
162
|
+
return (
|
|
163
|
+
<button
|
|
164
|
+
className={cx(
|
|
165
|
+
'btn',
|
|
166
|
+
{ 'btn-primary': primary, 'is-loading': loading },
|
|
167
|
+
className,
|
|
168
|
+
)}
|
|
169
|
+
disabled={loading}
|
|
170
|
+
>
|
|
171
|
+
{children}
|
|
172
|
+
</button>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Vue
|
|
178
|
+
|
|
179
|
+
```vue
|
|
180
|
+
<script setup lang="ts">
|
|
181
|
+
import { classNames } from '@jsdevlab/classnames';
|
|
182
|
+
|
|
183
|
+
defineProps<{ active?: boolean; disabled?: boolean }>();
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<template>
|
|
187
|
+
<button
|
|
188
|
+
:class="classNames('tab', { 'is-active': active, 'is-disabled': disabled })"
|
|
189
|
+
>
|
|
190
|
+
<slot />
|
|
191
|
+
</button>
|
|
192
|
+
</template>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Because the library is framework-agnostic, the same functions work in Svelte,
|
|
196
|
+
Solid, Angular, plain DOM code, or anywhere else JavaScript runs.
|
|
197
|
+
|
|
198
|
+
## API
|
|
199
|
+
|
|
200
|
+
### `classNames(...values: ClassValue[]): string`
|
|
201
|
+
|
|
202
|
+
Accepts any mix of supported values and returns a single space-separated class
|
|
203
|
+
name string. Returns `''` when nothing matches:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
classNames(); // ''
|
|
207
|
+
classNames(null, false, undefined); // ''
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### `cx`
|
|
211
|
+
|
|
212
|
+
An alias of `classNames` with the identical signature and behavior:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
cx('a', { b: true }); // 'a b'
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `createClassNames(options?: ClassNamesOptions): typeof classNames`
|
|
219
|
+
|
|
220
|
+
Returns a `classNames`-style function bound to the given options.
|
|
221
|
+
|
|
222
|
+
#### `ClassNamesOptions`
|
|
223
|
+
|
|
224
|
+
| Option | Type | Default | Description |
|
|
225
|
+
| -------- | --------- | ------- | ------------------------------------------------- |
|
|
226
|
+
| `unique` | `boolean` | `true` | Deduplicate class names, preserving first-occurrence order. |
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const defaultJoin = createClassNames(); // unique: true
|
|
230
|
+
const keepAll = createClassNames({ unique: false }); // keeps duplicates
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Types
|
|
234
|
+
|
|
235
|
+
All types are exported:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
export type ClassValue =
|
|
239
|
+
| string
|
|
240
|
+
| number
|
|
241
|
+
| boolean
|
|
242
|
+
| null
|
|
243
|
+
| undefined
|
|
244
|
+
| ClassObject
|
|
245
|
+
| ClassValue[]
|
|
246
|
+
| Set<ClassValue>;
|
|
247
|
+
|
|
248
|
+
export interface ClassObject {
|
|
249
|
+
[key: string]: ClassValue | (() => ClassValue);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface ClassNamesOptions {
|
|
253
|
+
unique?: boolean;
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Behavior reference
|
|
258
|
+
|
|
259
|
+
| Input | Output |
|
|
260
|
+
| -------------------------------------------------------------- | ---------- |
|
|
261
|
+
| `classNames('a', 'b')` | `'a b'` |
|
|
262
|
+
| `classNames('a', '', null, undefined, false, true)` | `'a'` |
|
|
263
|
+
| `classNames(0, 1)` | `'0 1'` |
|
|
264
|
+
| `classNames({ a: true, b: false, c: 1, d: 0 })` | `'a c'` |
|
|
265
|
+
| `classNames(['a', [{ b: true }]])` | `'a b'` |
|
|
266
|
+
| `classNames(new Set(['a', 'b']))` | `'a b'` |
|
|
267
|
+
| `classNames({ active: () => true, hidden: () => false })` | `'active'` |
|
|
268
|
+
| `classNames('a', 'b', 'a')` | `'a b'` |
|
|
269
|
+
| `createClassNames({ unique: false })('a', 'a')` | `'a a'` |
|
|
270
|
+
| `classNames()` | `''` |
|
|
271
|
+
|
|
272
|
+
## Notes and edge cases
|
|
273
|
+
|
|
274
|
+
- Class names are always joined with a **single space**; there is no custom
|
|
275
|
+
separator option.
|
|
276
|
+
- Deduplication compares exact, case-sensitive strings and does **not** split
|
|
277
|
+
strings: `classNames('a b', 'b')` returns `'a b b'`.
|
|
278
|
+
- Top-level `0` is kept (`classNames(0)` → `'0'`), but `0` as an object value is
|
|
279
|
+
falsy and dropped (`{ a: 0 }` → `''`).
|
|
280
|
+
- `true` and `false` are ignored as values; only object keys produce output.
|
|
281
|
+
- Functions are supported **only as object values**. A function passed as a
|
|
282
|
+
top-level argument is ignored.
|
|
283
|
+
- A function's return value is tested for truthiness; it does not become the
|
|
284
|
+
class name (`{ a: () => 'foo' }` → `'a'`).
|
|
285
|
+
- Output order follows input order; object keys follow `Object.entries` order.
|
|
286
|
+
Deduplication keeps the first occurrence.
|
|
287
|
+
- `null`, `undefined`, and empty strings are ignored.
|
|
288
|
+
|
|
289
|
+
## classnames compatibility
|
|
290
|
+
|
|
291
|
+
`@jsdevlab/classnames` is a drop-in replacement for `classnames` in most cases:
|
|
292
|
+
|
|
293
|
+
```diff
|
|
294
|
+
-import classNames from 'classnames';
|
|
295
|
+
+import { classNames } from '@jsdevlab/classnames';
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
| Capability | `classnames` | `@jsdevlab/classnames` |
|
|
299
|
+
| ----------------------------------- | ------------------------------- | ----------------------------------- |
|
|
300
|
+
| Strings / numbers | ✅ | ✅ |
|
|
301
|
+
| Objects with truthy keys | ✅ | ✅ |
|
|
302
|
+
| Nested arrays | ✅ | ✅ |
|
|
303
|
+
| Skips `null` / `undefined` / `false` / `''` | ✅ | ✅ |
|
|
304
|
+
| `Set` input | — | ✅ |
|
|
305
|
+
| Function values in objects | Not evaluated (functions are truthy) | Evaluated at call time |
|
|
306
|
+
| Duplicate removal | via `classnames/dedupe` | Default |
|
|
307
|
+
| Keep duplicates | Default | `createClassNames({ unique: false })` |
|
|
308
|
+
| Custom separator | — | — |
|
|
309
|
+
| Default export | ✅ | — (named exports only) |
|
|
310
|
+
|
|
311
|
+
If you relied on the default duplicate-preserving behavior of `classnames`, bind
|
|
312
|
+
your own instance:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { createClassNames } from '@jsdevlab/classnames';
|
|
316
|
+
|
|
317
|
+
const classNames = createClassNames({ unique: false });
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Module formats
|
|
321
|
+
|
|
322
|
+
| Format | File |
|
|
323
|
+
| ---------- | ------------------ |
|
|
324
|
+
| ESM | `dist/index.js` |
|
|
325
|
+
| CommonJS | `dist/index.cjs` |
|
|
326
|
+
| TypeScript | `dist/index.d.ts` |
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
// ESM
|
|
330
|
+
import { classNames } from '@jsdevlab/classnames';
|
|
331
|
+
|
|
332
|
+
// CommonJS
|
|
333
|
+
const { classNames } = require('@jsdevlab/classnames');
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The package has no runtime dependencies and works in modern browsers, Node.js,
|
|
337
|
+
Bun, and Deno.
|
|
338
|
+
|
|
339
|
+
## Development
|
|
340
|
+
|
|
341
|
+
Requires Node.js ≥ 24.19 and pnpm 12.18.
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
pnpm install
|
|
345
|
+
pnpm test # tsc --noEmit + rstest
|
|
346
|
+
pnpm build # rslib build → dist/
|
|
347
|
+
pnpm check # Biome lint + fix
|
|
348
|
+
pnpm format # Biome format + fix
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Releases are managed with [changesets](https://github.com/changesets/changesets):
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
pnpm changeset # describe the change
|
|
355
|
+
pnpm version # bump versions and changelog
|
|
356
|
+
pnpm release # publish to npm
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
[MIT](./LICENSE.txt) © 2026 jsdevlab
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# @jsdevlab/classnames
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@jsdevlab/classnames)
|
|
4
|
+
[](https://github.com/jsdevlab/classnames/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE.txt)
|
|
6
|
+
|
|
7
|
+
[English](./README.md) | **简体中文**
|
|
8
|
+
|
|
9
|
+
面向 JavaScript 与 TypeScript 的类型安全条件类名工具 —— 框架无关、兼容 classnames,
|
|
10
|
+
默认去重,并支持惰性求值。
|
|
11
|
+
|
|
12
|
+
## 特性
|
|
13
|
+
|
|
14
|
+
- **熟悉的 API** —— 与 `classnames` 相同的 `classNames(...)` 调用方式。
|
|
15
|
+
- **默认去重** —— `'a b a'` 会变成 `'a b'`,并保留首次出现的顺序。
|
|
16
|
+
- **惰性函数值** —— `{ active: () => condition }` 在构建类名字符串时才求值。
|
|
17
|
+
- **输入类型丰富** —— 字符串、数字、布尔值、对象、嵌套数组与 `Set`。
|
|
18
|
+
- **可关闭去重的工厂** —— 需要保留重复项时使用 `createClassNames({ unique: false })`。
|
|
19
|
+
- **`cx` 别名** —— 行为完全一致的简短别名。
|
|
20
|
+
- **TypeScript 优先** —— 完整的类型声明与导出的类型。
|
|
21
|
+
- **零运行时依赖** —— 同时提供 ESM 与 CJS 构建,可在任何现代 JS 运行时中使用。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @jsdevlab/classnames
|
|
27
|
+
# 或
|
|
28
|
+
npm install @jsdevlab/classnames
|
|
29
|
+
# 或
|
|
30
|
+
yarn add @jsdevlab/classnames
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 快速开始
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { classNames, cx, createClassNames } from '@jsdevlab/classnames';
|
|
37
|
+
|
|
38
|
+
classNames('btn', 'btn-primary'); // 'btn btn-primary'
|
|
39
|
+
classNames('btn', { 'btn-primary': true, 'is-loading': false }); // 'btn btn-primary'
|
|
40
|
+
classNames('btn', ['btn-lg', ['rounded']]); // 'btn btn-lg rounded'
|
|
41
|
+
classNames('btn', new Set(['btn-lg', 'rounded'])); // 'btn btn-lg rounded'
|
|
42
|
+
classNames('btn', 'btn'); // 'btn'
|
|
43
|
+
|
|
44
|
+
cx('btn', { disabled: true }); // 'btn disabled'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
本包**没有默认导出**,请使用具名导入:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { classNames } from '@jsdevlab/classnames'; // ✅
|
|
51
|
+
// import classNames from '@jsdevlab/classnames'; // ❌ 不支持
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 用法
|
|
55
|
+
|
|
56
|
+
### 条件类名
|
|
57
|
+
|
|
58
|
+
只有当对象的值为真值时,对应的键才会被输出:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const isActive = true;
|
|
62
|
+
|
|
63
|
+
classNames('tab', {
|
|
64
|
+
'is-active': isActive,
|
|
65
|
+
'is-disabled': !isActive,
|
|
66
|
+
});
|
|
67
|
+
// 'tab is-active'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 字符串与数字
|
|
71
|
+
|
|
72
|
+
数字会被转换为字符串;顶层的 `0` 会被保留:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
classNames('z', 0, 1, 2); // 'z 0 1 2'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
注意,对象值按真值判断,因此其中的 `0` 会被丢弃:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
classNames({ zero: 0, one: 1 }); // 'one'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 嵌套数组与 Set
|
|
85
|
+
|
|
86
|
+
数组会被递归展平,同时也直接支持 `Set`:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
classNames(['a', ['b', ['c']]]); // 'a b c'
|
|
90
|
+
classNames(new Set(['a', 'b'])); // 'a b'
|
|
91
|
+
classNames([new Set(['a']), { b: true }]); // 'a b'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 惰性函数值
|
|
95
|
+
|
|
96
|
+
作为**对象值**传入的函数会在调用时执行一次。它的返回值只用于判断是否输出对应
|
|
97
|
+
的键 —— 返回值本身不会作为类名:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const flags = {
|
|
101
|
+
'is-active': () => user.active,
|
|
102
|
+
'is-admin': () => user.roles.includes('admin'),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
classNames('user', flags);
|
|
106
|
+
// 两个函数会在此时求值,只有结果为真值的键会被输出。
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
这样可以让调用处保持简洁,并把开销较大的判断推迟到真正构建类名字符串时。每个
|
|
110
|
+
函数在每次 `classNames` 调用中执行一次;在多次调用间复用同一个对象会重新求值。
|
|
111
|
+
|
|
112
|
+
### 去重
|
|
113
|
+
|
|
114
|
+
默认会移除重复类名,并保留首次出现的顺序:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
classNames('a', 'b', 'a', ['b', { a: true }]); // 'a b'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 保留重复项
|
|
121
|
+
|
|
122
|
+
创建一个关闭去重的工厂函数:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { createClassNames } from '@jsdevlab/classnames';
|
|
126
|
+
|
|
127
|
+
const join = createClassNames({ unique: false });
|
|
128
|
+
|
|
129
|
+
join('a', 'a'); // 'a a'
|
|
130
|
+
classNames('a', 'a'); // 'a'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
选项在创建工厂时解析,返回的函数会绑定这些选项:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const cls = createClassNames(); // unique 默认为 true
|
|
137
|
+
cls('a', 'a'); // 'a'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### React
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { cx } from '@jsdevlab/classnames';
|
|
144
|
+
import type { ReactNode } from 'react';
|
|
145
|
+
|
|
146
|
+
interface ButtonProps {
|
|
147
|
+
primary?: boolean;
|
|
148
|
+
loading?: boolean;
|
|
149
|
+
className?: string;
|
|
150
|
+
children: ReactNode;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function Button({
|
|
154
|
+
primary,
|
|
155
|
+
loading,
|
|
156
|
+
className,
|
|
157
|
+
children,
|
|
158
|
+
}: ButtonProps) {
|
|
159
|
+
return (
|
|
160
|
+
<button
|
|
161
|
+
className={cx(
|
|
162
|
+
'btn',
|
|
163
|
+
{ 'btn-primary': primary, 'is-loading': loading },
|
|
164
|
+
className,
|
|
165
|
+
)}
|
|
166
|
+
disabled={loading}
|
|
167
|
+
>
|
|
168
|
+
{children}
|
|
169
|
+
</button>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Vue
|
|
175
|
+
|
|
176
|
+
```vue
|
|
177
|
+
<script setup lang="ts">
|
|
178
|
+
import { classNames } from '@jsdevlab/classnames';
|
|
179
|
+
|
|
180
|
+
defineProps<{ active?: boolean; disabled?: boolean }>();
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
<template>
|
|
184
|
+
<button
|
|
185
|
+
:class="classNames('tab', { 'is-active': active, 'is-disabled': disabled })"
|
|
186
|
+
>
|
|
187
|
+
<slot />
|
|
188
|
+
</button>
|
|
189
|
+
</template>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
由于本库与框架无关,同样的函数也可用于 Svelte、Solid、Angular、原生 DOM 代码,
|
|
193
|
+
或任何其他 JavaScript 运行环境。
|
|
194
|
+
|
|
195
|
+
## API
|
|
196
|
+
|
|
197
|
+
### `classNames(...values: ClassValue[]): string`
|
|
198
|
+
|
|
199
|
+
接收任意组合的受支持值,返回以单个空格连接的类名字符串。没有有效类名时返回
|
|
200
|
+
`''`:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
classNames(); // ''
|
|
204
|
+
classNames(null, false, undefined); // ''
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `cx`
|
|
208
|
+
|
|
209
|
+
`classNames` 的别名,签名与行为完全一致:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
cx('a', { b: true }); // 'a b'
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### `createClassNames(options?: ClassNamesOptions): typeof classNames`
|
|
216
|
+
|
|
217
|
+
返回一个绑定指定选项的 `classNames` 风格函数。
|
|
218
|
+
|
|
219
|
+
#### `ClassNamesOptions`
|
|
220
|
+
|
|
221
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
222
|
+
| -------- | --------- | ------ | -------------------------------------- |
|
|
223
|
+
| `unique` | `boolean` | `true` | 对类名去重,保留首次出现的顺序。 |
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const defaultJoin = createClassNames(); // unique: true
|
|
227
|
+
const keepAll = createClassNames({ unique: false }); // 保留重复项
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## 类型
|
|
231
|
+
|
|
232
|
+
所有类型均已导出:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
export type ClassValue =
|
|
236
|
+
| string
|
|
237
|
+
| number
|
|
238
|
+
| boolean
|
|
239
|
+
| null
|
|
240
|
+
| undefined
|
|
241
|
+
| ClassObject
|
|
242
|
+
| ClassValue[]
|
|
243
|
+
| Set<ClassValue>;
|
|
244
|
+
|
|
245
|
+
export interface ClassObject {
|
|
246
|
+
[key: string]: ClassValue | (() => ClassValue);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface ClassNamesOptions {
|
|
250
|
+
unique?: boolean;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 行为参考
|
|
255
|
+
|
|
256
|
+
| 输入 | 输出 |
|
|
257
|
+
| -------------------------------------------------------------- | ---------- |
|
|
258
|
+
| `classNames('a', 'b')` | `'a b'` |
|
|
259
|
+
| `classNames('a', '', null, undefined, false, true)` | `'a'` |
|
|
260
|
+
| `classNames(0, 1)` | `'0 1'` |
|
|
261
|
+
| `classNames({ a: true, b: false, c: 1, d: 0 })` | `'a c'` |
|
|
262
|
+
| `classNames(['a', [{ b: true }]])` | `'a b'` |
|
|
263
|
+
| `classNames(new Set(['a', 'b']))` | `'a b'` |
|
|
264
|
+
| `classNames({ active: () => true, hidden: () => false })` | `'active'` |
|
|
265
|
+
| `classNames('a', 'b', 'a')` | `'a b'` |
|
|
266
|
+
| `createClassNames({ unique: false })('a', 'a')` | `'a a'` |
|
|
267
|
+
| `classNames()` | `''` |
|
|
268
|
+
|
|
269
|
+
## 注意事项与边界情况
|
|
270
|
+
|
|
271
|
+
- 类名始终使用**单个空格**连接,不提供自定义分隔符选项。
|
|
272
|
+
- 去重按完全相同的字符串比较,区分大小写,且**不会**拆分字符串:
|
|
273
|
+
`classNames('a b', 'b')` 返回 `'a b b'`。
|
|
274
|
+
- 顶层 `0` 会被保留(`classNames(0)` → `'0'`),但对象值中的 `0` 为假值,会被
|
|
275
|
+
丢弃(`{ a: 0 }` → `''`)。
|
|
276
|
+
- `true` 和 `false` 作为值会被忽略;只有对象的键会产生输出。
|
|
277
|
+
- 函数**只能作为对象值**使用;直接作为顶层参数传入的函数会被忽略。
|
|
278
|
+
- 函数的返回值只用于判断真值,不会成为类名(`{ a: () => 'foo' }` → `'a'`)。
|
|
279
|
+
- 输出顺序与输入顺序一致;对象的键遵循 `Object.entries` 的顺序。去重保留首次
|
|
280
|
+
出现的位置。
|
|
281
|
+
- `null`、`undefined` 和空字符串会被忽略。
|
|
282
|
+
|
|
283
|
+
## 与 classnames 的兼容性
|
|
284
|
+
|
|
285
|
+
在大多数场景下,`@jsdevlab/classnames` 可以直接替代 `classnames`:
|
|
286
|
+
|
|
287
|
+
```diff
|
|
288
|
+
-import classNames from 'classnames';
|
|
289
|
+
+import { classNames } from '@jsdevlab/classnames';
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
| 能力 | `classnames` | `@jsdevlab/classnames` |
|
|
293
|
+
| ----------------------------------- | ------------------------------------ | ----------------------------------------- |
|
|
294
|
+
| 字符串 / 数字 | ✅ | ✅ |
|
|
295
|
+
| 值为真值的对象 | ✅ | ✅ |
|
|
296
|
+
| 嵌套数组 | ✅ | ✅ |
|
|
297
|
+
| 跳过 `null` / `undefined` / `false` / `''` | ✅ | ✅ |
|
|
298
|
+
| `Set` 输入 | — | ✅ |
|
|
299
|
+
| 对象中的函数值 | 不求值(函数本身为真值) | 在调用时求值 |
|
|
300
|
+
| 移除重复项 | 通过 `classnames/dedupe` | 默认行为 |
|
|
301
|
+
| 保留重复项 | 默认行为 | `createClassNames({ unique: false })` |
|
|
302
|
+
| 自定义分隔符 | — | — |
|
|
303
|
+
| 默认导出 | ✅ | —(仅具名导出) |
|
|
304
|
+
|
|
305
|
+
如果你的代码依赖 `classnames` 默认保留重复项的行为,可以绑定自己的实例:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import { createClassNames } from '@jsdevlab/classnames';
|
|
309
|
+
|
|
310
|
+
const classNames = createClassNames({ unique: false });
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 模块格式
|
|
314
|
+
|
|
315
|
+
| 格式 | 文件 |
|
|
316
|
+
| ---------- | ------------------ |
|
|
317
|
+
| ESM | `dist/index.js` |
|
|
318
|
+
| CommonJS | `dist/index.cjs` |
|
|
319
|
+
| TypeScript | `dist/index.d.ts` |
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
// ESM
|
|
323
|
+
import { classNames } from '@jsdevlab/classnames';
|
|
324
|
+
|
|
325
|
+
// CommonJS
|
|
326
|
+
const { classNames } = require('@jsdevlab/classnames');
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
本包没有运行时依赖,可在现代浏览器、Node.js、Bun 与 Deno 中使用。
|
|
330
|
+
|
|
331
|
+
## 开发
|
|
332
|
+
|
|
333
|
+
需要 Node.js ≥ 24.19 与 pnpm 12.18。
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
pnpm install
|
|
337
|
+
pnpm test # tsc --noEmit + rstest
|
|
338
|
+
pnpm build # rslib 构建 → dist/
|
|
339
|
+
pnpm check # Biome 检查并修复
|
|
340
|
+
pnpm format # Biome 格式化并修复
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
发布流程使用 [changesets](https://github.com/changesets/changesets):
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
pnpm changeset # 描述本次变更
|
|
347
|
+
pnpm version # 更新版本号与 CHANGELOG
|
|
348
|
+
pnpm release # 发布到 npm
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## 许可证
|
|
352
|
+
|
|
353
|
+
[MIT](./LICENSE.txt) © 2026 jsdevlab
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
+
})();
|
|
18
|
+
(()=>{
|
|
19
|
+
__webpack_require__.r = (exports1)=>{
|
|
20
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
+
value: 'Module'
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
+
value: true
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
})();
|
|
28
|
+
var __webpack_exports__ = {};
|
|
29
|
+
__webpack_require__.r(__webpack_exports__);
|
|
30
|
+
const DEFAULT_OPTIONS = {
|
|
31
|
+
unique: true
|
|
32
|
+
};
|
|
33
|
+
const resolveOptions = (options)=>({
|
|
34
|
+
unique: options?.unique ?? DEFAULT_OPTIONS.unique
|
|
35
|
+
});
|
|
36
|
+
const collect = (value, parts)=>{
|
|
37
|
+
if (null == value || false === value || true === value) return;
|
|
38
|
+
if ('string' == typeof value || 'number' == typeof value) {
|
|
39
|
+
if ('' !== value) parts.push(String(value));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
for (const item of value)collect(item, parts);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (value instanceof Set) {
|
|
47
|
+
for (const item of value)collect(item, parts);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const [key, raw] of Object.entries(value)){
|
|
51
|
+
const v = 'function' == typeof raw ? raw() : raw;
|
|
52
|
+
if (v) parts.push(key);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const classNamesImpl = (values, options)=>{
|
|
56
|
+
const parts = [];
|
|
57
|
+
for (const value of values)collect(value, parts);
|
|
58
|
+
if (!options.unique) return parts.join(' ');
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const unique = [];
|
|
61
|
+
for (const part of parts)if (!seen.has(part)) {
|
|
62
|
+
seen.add(part);
|
|
63
|
+
unique.push(part);
|
|
64
|
+
}
|
|
65
|
+
return unique.join(' ');
|
|
66
|
+
};
|
|
67
|
+
function classNames(...values) {
|
|
68
|
+
return classNamesImpl(values, resolveOptions());
|
|
69
|
+
}
|
|
70
|
+
const cx = classNames;
|
|
71
|
+
function createClassNames(options) {
|
|
72
|
+
const resolved = resolveOptions(options);
|
|
73
|
+
return (...values)=>classNamesImpl(values, resolved);
|
|
74
|
+
}
|
|
75
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
76
|
+
classNames: ()=>classNames,
|
|
77
|
+
createClassNames: ()=>createClassNames
|
|
78
|
+
}, {
|
|
79
|
+
cx: cx
|
|
80
|
+
});
|
|
81
|
+
exports.classNames = __webpack_exports__.classNames;
|
|
82
|
+
exports.createClassNames = __webpack_exports__.createClassNames;
|
|
83
|
+
exports.cx = __webpack_exports__.cx;
|
|
84
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
85
|
+
"classNames",
|
|
86
|
+
"createClassNames",
|
|
87
|
+
"cx"
|
|
88
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
89
|
+
Object.defineProperty(exports, '__esModule', {
|
|
90
|
+
value: true
|
|
91
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsdevlab/classnames — a type-safe conditional class name utility (framework-agnostic).
|
|
3
|
+
*
|
|
4
|
+
* classnames-compatible, plus enhancements:
|
|
5
|
+
* - Basics: strings/numbers/objects (truthy keys)/nested arrays/Sets
|
|
6
|
+
* - Enhancements: lazily evaluated function values (`{ active: () => cond }`),
|
|
7
|
+
* deduplication by default (order preserved), dedup toggle factory (createClassNames)
|
|
8
|
+
*/
|
|
9
|
+
/** Object value: a plain value or a lazily evaluated function. */
|
|
10
|
+
export type ClassValue = string | number | boolean | null | undefined | ClassObject | ClassValue[] | Set<ClassValue>;
|
|
11
|
+
export interface ClassObject {
|
|
12
|
+
[key: string]: ClassValue | (() => ClassValue);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Options for the class name factory.
|
|
16
|
+
*
|
|
17
|
+
* Class names always join with a space (the only separator CSS accepts),
|
|
18
|
+
* so only deduplication is configurable.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* createClassNames({ unique: false })
|
|
22
|
+
*/
|
|
23
|
+
export interface ClassNamesOptions {
|
|
24
|
+
/** Whether to deduplicate (order preserved), defaults to true. */
|
|
25
|
+
unique?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Conditional class names (classnames-compatible + enhanced); `cx` is an alias.
|
|
29
|
+
*
|
|
30
|
+
* @param values Class values: strings/numbers, objects with truthy keys, nested arrays,
|
|
31
|
+
* Sets, and lazily evaluated function values.
|
|
32
|
+
* @returns The joined class name string.
|
|
33
|
+
* @example
|
|
34
|
+
* classNames('a', 'b', 'c') // 'a b c'
|
|
35
|
+
* classNames('a', { b: true, c: false }, ['d']) // 'a b d'
|
|
36
|
+
* classNames({ active: () => isActive }) // 'active' when isActive is truthy
|
|
37
|
+
* classNames('a', 'b', 'a') // 'a b' (deduplicated, order preserved)
|
|
38
|
+
*/
|
|
39
|
+
export declare function classNames(...values: ClassValue[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Short alias of `classNames` (identical signature and behavior).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* cx('a', { b: true }) // 'a b'
|
|
45
|
+
*/
|
|
46
|
+
export declare const cx: typeof classNames;
|
|
47
|
+
/**
|
|
48
|
+
* Factory with a dedup toggle.
|
|
49
|
+
*
|
|
50
|
+
* @param options Dedup options.
|
|
51
|
+
* @returns A `classNames`-like function bound to the given options.
|
|
52
|
+
* @example
|
|
53
|
+
* const keep = createClassNames({ unique: false })
|
|
54
|
+
* keep('a', 'a') // 'a a'
|
|
55
|
+
*/
|
|
56
|
+
export declare function createClassNames(options?: ClassNamesOptions): typeof classNames;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const DEFAULT_OPTIONS = {
|
|
2
|
+
unique: true
|
|
3
|
+
};
|
|
4
|
+
const resolveOptions = (options)=>({
|
|
5
|
+
unique: options?.unique ?? DEFAULT_OPTIONS.unique
|
|
6
|
+
});
|
|
7
|
+
const collect = (value, parts)=>{
|
|
8
|
+
if (null == value || false === value || true === value) return;
|
|
9
|
+
if ('string' == typeof value || 'number' == typeof value) {
|
|
10
|
+
if ('' !== value) parts.push(String(value));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
for (const item of value)collect(item, parts);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (value instanceof Set) {
|
|
18
|
+
for (const item of value)collect(item, parts);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const [key, raw] of Object.entries(value)){
|
|
22
|
+
const v = 'function' == typeof raw ? raw() : raw;
|
|
23
|
+
if (v) parts.push(key);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const classNamesImpl = (values, options)=>{
|
|
27
|
+
const parts = [];
|
|
28
|
+
for (const value of values)collect(value, parts);
|
|
29
|
+
if (!options.unique) return parts.join(' ');
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const unique = [];
|
|
32
|
+
for (const part of parts)if (!seen.has(part)) {
|
|
33
|
+
seen.add(part);
|
|
34
|
+
unique.push(part);
|
|
35
|
+
}
|
|
36
|
+
return unique.join(' ');
|
|
37
|
+
};
|
|
38
|
+
function classNames(...values) {
|
|
39
|
+
return classNamesImpl(values, resolveOptions());
|
|
40
|
+
}
|
|
41
|
+
const cx = classNames;
|
|
42
|
+
function createClassNames(options) {
|
|
43
|
+
const resolved = resolveOptions(options);
|
|
44
|
+
return (...values)=>classNamesImpl(values, resolved);
|
|
45
|
+
}
|
|
46
|
+
export { classNames, createClassNames, cx };
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsdevlab/classnames",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Type-safe classnames utility — conditional classes with lazy evaluation, dedup, and separator factory. Framework-agnostic.",
|
|
6
|
+
"keywords": [],
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/jsdevlab/classnames.git"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": ["dist", "README.md", "README.zh-CN.md"],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "rslib",
|
|
25
|
+
"changeset": "changeset",
|
|
26
|
+
"check": "biome check --write",
|
|
27
|
+
"dev": "rslib --watch",
|
|
28
|
+
"format": "biome format --write",
|
|
29
|
+
"prepare": "simple-git-hooks",
|
|
30
|
+
"release": "changeset publish",
|
|
31
|
+
"test": "tsc --noEmit && rstest",
|
|
32
|
+
"test:watch": "rstest --watch",
|
|
33
|
+
"version": "changeset version"
|
|
34
|
+
},
|
|
35
|
+
"simple-git-hooks": {
|
|
36
|
+
"pre-commit": "pnpm check",
|
|
37
|
+
"pre-push": "pnpm format"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@biomejs/biome": "^2.6.6",
|
|
41
|
+
"@changesets/changelog-github": "^1.0.1",
|
|
42
|
+
"@changesets/cli": "^3.0.1",
|
|
43
|
+
"@rslib/core": "^0.23.2",
|
|
44
|
+
"@rstest/adapter-rslib": "^0.11.9",
|
|
45
|
+
"@rstest/core": "^0.11.9",
|
|
46
|
+
"@rstest/coverage-v8": "^0.11.9",
|
|
47
|
+
"@types/node": "^24.13.3",
|
|
48
|
+
"simple-git-hooks": "^2.14.0",
|
|
49
|
+
"typescript": "^6.0.3"
|
|
50
|
+
},
|
|
51
|
+
"packageManager": "pnpm@12.18.0+sha512.9d7882d5dc82147ecfc4e062aa34834e0ad0296710a1837a1115ae012127d36b7109e321ac1598a1dd0a65597681b1e99917e12ed0d749eae91187943363beb3",
|
|
52
|
+
"devEngines": {
|
|
53
|
+
"runtime": {
|
|
54
|
+
"name": "node",
|
|
55
|
+
"version": ">=24.19.0 <27.0.0",
|
|
56
|
+
"onFail": "download"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public",
|
|
61
|
+
"registry": "https://registry.npmjs.org/"
|
|
62
|
+
}
|
|
63
|
+
}
|