simple-merge-class-names 6.0.9 → 7.0.1

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/README.md CHANGED
@@ -1,40 +1,42 @@
1
1
  # simple-merge-class-names
2
2
 
3
- A straightforward utility for merging CSS class names in _React & Tailwind_ and other _JavaScript_ projects.
3
+ A straightforward utility for merging CSS class names in _React (JSX)_ and other _JavaScript_ projects.
4
+
5
+ For Production look into [https://www.npmjs.com/package/clsx](https://www.npmjs.com/package/clsx)
4
6
 
5
7
  ## Table of Contents
6
8
 
7
- - [We Stand with Palestine](#we-stand-with-palestine)
8
- - [Production Considerations](#production-considerations)
9
- - [Installation](#installation)
10
- - [Install Prettier With VSCode (Most Recommended)](#install-prettier-with-vscode-most-recommended)
11
- - [Usage](#usage)
12
- - [Workflow To Minimize Typing Strain](#workflow-to-minimize-typing-strain)
13
- - [Type Definitions (of Exported Functions)](#type-definitions-of-exported-functions)
14
- - [Accepted Arguments](#accepted-arguments)
15
- - [Console Warning](#console-warning)
16
- - [Conditionally Include Class Names](#conditionally-include-class-names)
17
- - [Using `mergeClassNamesDebugger` And The Built-in Browser Debugger To Find And Fix Warnings](#using-mergeclassnamesdebugger-and-the-built-in-browser-debugger-to-find-and-fix-warnings)
18
- - [Strategies To Ensure Correct Arguments Are Sent to `mergeClassNames`](#strategies-to-ensure-correct-arguments-are-sent-to-mergeclassnames)
19
- - [Testing](#testing)
20
- - [Source Code (Partial)](#source-code-partial)
21
- - [Misc.](#misc)
22
- - [Motivation](#motivation)
23
- - [Why the Mismatch Between Exported Function and Package Name?](#why-the-mismatch-between-exported-function-and-package-name)
24
- - [Where This Package Excels](#where-this-package-excels)
25
- - [License](#license)
26
-
27
- ## We Stand with Palestine <img src="https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/ps.svg" alt="Flag of Palestine" height="11" />
9
+ - [simple-merge-class-names](#simple-merge-class-names)
10
+ - [Table of Contents](#table-of-contents)
11
+ - [The Genocidal Occupation Is Starving Gaza](#the-genocidal-occupation-is-starving-gaza)
12
+ - [Installation](#installation)
13
+ - [_Recommended If Using VSCode: Install `Prettier` Extension_](#recommended-if-using-vscode-install-prettier-extension)
14
+ - [Usage](#usage)
15
+ - [TypeScript Definitions](#typescript-definitions)
16
+ - [Valid Arguments](#valid-arguments)
17
+ - [Invalid Arguments](#invalid-arguments)
18
+ - [Reason for warnings](#reason-for-warnings)
19
+ - [Conditional class inclusion](#conditional-class-inclusion)
20
+ - [Return Result](#return-result)
21
+ - [Chaining](#chaining)
22
+ - [Usage of Browser Debugger](#usage-of-browser-debugger)
23
+ - [VSCode Workflow To Minimize Typing Strain](#vscode-workflow-to-minimize-typing-strain)
24
+ - [Testing Source Code](#testing-source-code)
25
+ - [Run Once](#run-once)
26
+ - [Run Watch Mode](#run-watch-mode)
27
+ - [License](#license)
28
+
29
+ ## The Genocidal Occupation Is Starving Gaza
28
30
 
29
31
  - [Donate](https://gazafunds.com/)
30
32
 
31
- - [Boycott Brands Supporting Genocide](https://www.uplift.ie/bds/)
33
+ - [(US) Demand Immediate Opening of ALL Gaza Border Crossings](https://act.uscpr.org/a/letaidin)
32
34
 
33
- - [Legal Action](https://www.hindrajabfoundation.org/perpetrators)
35
+ - [Boycott Brands Supporting Gaza Holocaust](https://www.uplift.ie/bds/)
34
36
 
35
- ## Production Considerations
37
+ - [Legal Action](https://www.hindrajabfoundation.org/perpetrators)
36
38
 
37
- In developing this package, I prioritized _code readability_, _strict input handling_, and _enhanced developer experience_. Consequently, **performance** and **_features_** were not the primary focus. If you are considering this package for production, you may also want to explore `clsx`: [https://www.npmjs.com/package/clsx](https://www.npmjs.com/package/clsx).
39
+ _Palestine is about fundamental non-negotiable human rights. End all financial and diplomatic ties with i\*rael_.
38
40
 
39
41
  ## Installation
40
42
 
@@ -50,20 +52,25 @@ yarn add simple-merge-class-names
50
52
  npm install simple-merge-class-names
51
53
  ```
52
54
 
53
- ### Install `Prettier` With VSCode (Most Recommended)
55
+ ### _Recommended If Using VSCode: Install `Prettier` Extension_
56
+
57
+ - _[https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)_
54
58
 
55
- - [https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
59
+ _It will nicely format your code especially when you have lots of classes, and will significantly improve your visual experience._
56
60
 
57
- - Or use an equivalent auto code formatter for your IDE.
61
+ _If you have a different IDE use an equivalent auto code formatter tool/extension_
58
62
 
59
63
  ## Usage
60
64
 
61
- Import `mergeClassNames(...args)` from the package, and use it in your _JSX_ or _JavaScript_ code.
65
+ | Function | Prints console warnings | Activates debugger |
66
+ | ------------------------- | ----------------------- | ------------------ |
67
+ | `mergeClassNames` | ✅ | ❌ |
68
+ | `mergeClassNamesDebugger` | ✅ | ✅ |
62
69
 
63
70
  ```jsx
64
71
  import { mergeClassNames } from "simple-merge-class-names";
65
72
 
66
- const MyComponent = () => {
73
+ const Component = ({ condition }) => {
67
74
  return (
68
75
  <div
69
76
  className={mergeClassNames(
@@ -80,130 +87,110 @@ const MyComponent = () => {
80
87
  };
81
88
  ```
82
89
 
83
- Or for [debugging purposes to fix console warnings](#using-mergeclassnamesdebugger-and-the-built-in-browser-debugger-to-find-and-fix-warnings):
90
+ ### TypeScript Definitions
84
91
 
85
- - Open the _Browser's Developer Tools_ and
86
- - Use `mergeClassNamesDebugger`
92
+ ```ts
93
+ export declare const mergeClassNames: (
94
+ ...args: (string | false)[]
95
+ ) => string | false;
87
96
 
88
- ```jsx
89
- import { mergeClassNamesDebugger } from "simple-merge-class-names";
90
-
91
- const MyComponent = () => {
92
- return (
93
- <div
94
- className={mergeClassNamesDebugger(
95
- "app",
96
- condition ? "min-h-dvh" : false,
97
- "grid",
98
- "grid-rows-[auto_1fr_auto]",
99
- "outline"
100
- )}
101
- >
102
- Hello, world!
103
- </div>
104
- );
105
- };
97
+ export declare const mergeClassNamesDebugger: (
98
+ ...args: (string | false)[]
99
+ ) => string | false;
106
100
  ```
107
101
 
108
- ## Workflow To Minimize Typing Strain
109
-
110
- - Have `Prettier` installed
111
- - Have `Editor: Word Wrap` enabled in VS Code:
102
+ ### Valid Arguments
112
103
 
113
- - `Open Settings (UI)` → `Editor: Word Wrap` → `on`
114
- - Or `Open User Settings (JSON)` and add this entry:
104
+ Only 2:
115
105
 
116
- `"editor.wordWrap": "on"`
106
+ 1. **Valid strings, not whitespace, of length >= 1**
117
107
 
118
- - **Use single quotes** (<kbd>'</kbd>) for class names, often a single key press on many keyboards.
119
- - **Save the file** (<kbd>Ctrl+S</kbd>), which activates `Prettier` to auto-format the file, it will:
108
+ _(As long as you have content in the string you're OK)_
120
109
 
121
- - Replace single quotes with double quotes.
122
- - Neatly arrange each class name on a new line.
110
+ 2. **`false`**
123
111
 
124
- ### Result
112
+ _Example, This is OK:_
125
113
 
126
- #### Before
127
-
128
- ![Screenshot of code before Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/before.png)
129
-
130
- #### After
131
-
132
- ![Screenshot of code after Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/after.png)
133
-
134
- ## Type Definitions (of Exported Functions)
135
-
136
- ```jsx
137
- mergeClassNames: (...args: (string | false)[]) => string;
114
+ ```js
115
+ mergeClassNames(
116
+ "mx-auto",
117
+ "min-dvh ",
118
+ " flex",
119
+ " grid ",
120
+ "italic font-bold ",
121
+ `
122
+ gap-y-4
123
+ `,
124
+ false,
125
+ condition ? "daisy-btn-active" : false
126
+ );
138
127
  ```
139
128
 
140
- ```jsx
141
- mergeClassNamesDebugger: (...args: (string | false)[]) => string;
129
+ ### Invalid Arguments
130
+
131
+ Each below argument will be **ignored**, and cause a Dev Console **warning to be printed** to alert you:
132
+
133
+ - **Empty strings**: _(e.g. `""`)_
134
+ - **Whitespace** any consecutive combination of the following:
135
+ - new lines,
136
+ - spaces,
137
+ - tabs
138
+ - _(e.g._ `" "`, `"\n "`, `" \t \n "`, _etc.)_
139
+ - **`true`**
140
+ - **`undefined`**
141
+ - **`null`**
142
+ - **Objects**
143
+ - **Numbers**
144
+ - **Big Int**
145
+ - **Symbols**
146
+
147
+ ```js
148
+ // Example: These arguments will be **ignored**, and a console.warn will be printed
149
+
150
+ const someVariable = "";
151
+
152
+ mergeClassNames(
153
+ someVariable, // empty string
154
+ " ", // whitespace
155
+ "\n ", // whitespace
156
+ " \t \n ", // whitespace
157
+ ` // whitespace
158
+ \n
159
+ `,
160
+ true, // true
161
+ undefined, // undefined
162
+ null, // null
163
+ {
164
+ // object
165
+ name: "value",
166
+ email: "email@example.com",
167
+ },
168
+ 123, // number
169
+ 123.45 // number
170
+ );
142
171
  ```
143
172
 
144
- Both `mergeClassNames` and `mergeClassNamesDebugger` always return a string.
145
-
146
- - If no inputs are provided (e.g. `mergeClassNames()`) or if invalid inputs are given (e.g. `mergeClassNames(undefined, " ")`), an _empty string_ is returned: `""`.
147
-
148
- ## Acceptable Arguments
149
-
150
- `mergeClassNames(...args)` and `mergeClassNamesDebugger(...args)` only accept the following arguments:
151
-
152
- - **Strings that are not empty, and are not whitespace** (e.g. `"app"`, `"min-h-dvh"`, `" grid "`)
153
-
154
- - The boolean value **`false`**
155
-
156
- Any other input is considered an invalid argument and will be ignored, resulting in a matching warning being logged. Invalid argument types include:
157
-
158
- - _Empty strings_ (`""`),
159
- - _Whitespace combinations_ (e.g. `" "`, `"\n"`, `" \n\t "`, etc...),,
160
- - `null`,
161
- - `undefined`,
162
- - _Numbers_,
163
- - _Objects_,
164
- - _Arrays_
165
-
166
- ## Console Warning
167
-
168
- Whenever invalid arguments are passed to `mergeClassNames`, they are not silently ignored, as this can lead to subtle bugs and increase technical debt. Instead, a `console.warn` is shown in the _Developer Console_ to notify the developer of a potentially deeper issue that requires attention. For example:
169
-
170
- ```plaintext
171
- [mergeClassNames] Warning: invalid arguments were provided and ignored:
172
-
173
- * Replace "mergeClassNames" with "mergeClassNamesDebugger" without changing any arguments, and open the Developer Console, or attach Debugger (see README.md).
174
-
175
- * Expected all arguments to be either strings or value `false`, but got 5 invalid value(s):
176
- [
177
- (1/5): >undefined< of type "undefined",
178
- (2/5): > test < of type "object",
179
- (3/5): >[object Object]< of type "object",
180
- (4/5): >true< of type "boolean",
181
- (5/5): >null< of type "object"
182
- ]
173
+ ![screenshot of console.warn warnings because invalid arguments were provided and ignored, so no silent failing](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/warnings.PNG)
183
174
 
184
- * Expected 0 empty strings, but got 2 invalid value(s):
185
- [
186
- (1/2): ><,
187
- (2/2): > <
188
- ]
189
- ```
175
+ ### Reason for warnings
190
176
 
191
- ## Conditionally Include Class Names
177
+ - To avoid silent failures, because you will be pulling your hair asking why a Tailwind class isn't working only to figure out you passed an _object_, _array_ or an _empty string_ instead of a valid string. _(It could also be because of an unsupported class name or typo but this is beyond the scope of this package)_
192
178
 
193
- To conditionally include a class name, use the _conditional operator_ as follows:
179
+ ### Conditional class inclusion
194
180
 
195
- - `condition ? "class-name" : false`, with `false` serving as the fallback value to ensure clear and warning-free code.
181
+ Use this pattern:
196
182
 
197
- This approach is effective because `false` will never result in a warning from the function.
183
+ - `condition ? "class-name" : false` with `false` serving as the valid fallback.
184
+ - _or_ `condition === true ? "class-name" : false` if you want to be specific.
198
185
 
199
- **Important**: Avoid using the _short-circuit implicit syntax_ like this:
186
+ _Note:_ Avoid using the _short-circuit implicit syntax_ like this:
200
187
 
201
- - `condition && "class-name"`, as it can produce falsy values such as `0`, `""`, `undefined`, and `null`, which will lead to warnings being logged.
188
+ - `condition && "class-name"`, beside less readable code, it can produce _falsy_ values which will be **_ignored_** _(e.g. `0`, `""`, `undefined`, and `null`)_.
202
189
 
203
190
  ```jsx
204
191
  import { mergeClassNames } from "simple-merge-class-names";
205
192
 
206
- const MyComponent = () => {
193
+ const Component = () => {
207
194
  return (
208
195
  <div
209
196
  className={mergeClassNames(
@@ -220,141 +207,129 @@ const MyComponent = () => {
220
207
  };
221
208
  ```
222
209
 
223
- ## Using `mergeClassNamesDebugger` And The Built-in Browser Debugger To Find And Fix Warnings
210
+ ### Return Result
224
211
 
225
- ```jsx
226
- import { mergeClassNamesDebugger } from "simple-merge-class-names";
212
+ Either:
227
213
 
228
- const MyComponent = () => {
229
- return (
230
- <div
231
- className={mergeClassNamesDebugger(
232
- "app",
233
- condition ? "min-h-dvh" : false,
234
- "grid",
235
- "grid-rows-[auto_1fr_auto]",
236
- "outline"
237
- )}
238
- >
239
- Hello, world!
240
- </div>
241
- );
242
- };
243
- ```
214
+ 1. **Valid `string`, never whitespace, always length >= 1**
244
215
 
245
- `mergeClassNamesDebugger` is a drop-in replacement for `mergeClassNames` but with the added _benefit_ that it will activate the built-in **_debugger_** inside browsers like _FireFox_, _Chrome_, _Safari_ and even _VS Code_ if configured properly.
216
+ 2. _or_ **`false`** _(if all input arguments were invalid)_
246
217
 
247
- This built-in JavaScript feature gained wide-spread support from major browsers around 2012, allowing you to access it with minimal effort. To utilize it, simply follow these two steps:
218
+ ### Chaining
248
219
 
249
- 1. **_Open the Browser's Developer Tools_** to inform the JavaScript engine that the debugger is _enabled_.
250
- 2. **_Replace_** `mergeClassNames` with **`mergeClassNamesDebugger`** _without_ altering any of the provided arguments.
220
+ Because of safe return types you can chain calls safely without worrying about warnings or arguments being ignored.
251
221
 
252
- _When the debugger is enabled (i.e. *Browser's Developer Tools* is open) and an invalid argument such as `undefined` or `" "` is passed to `mergeClassNamesDebugger`, the JavaScript engine will automatically pause execution and highlight the invalid argument. You simply need to select the offending component (e.g. `Container.jsx`) from the Call Stack._
222
+ _So this is OK:_
253
223
 
254
- When the debugger is active, it will appear as shown in this screenshot (in _Firefox_):
224
+ ```js
225
+ mergeClassNames(condition ? "disabled" : mergeClassNames(...) )
226
+ ```
255
227
 
256
- ![Firefox Debugger automatically paused execution when undefined was passed to mergeClassNamesDebugger](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/debugger.png)
228
+ ## Usage of Browser Debugger
257
229
 
258
- ## Strategies To Ensure Correct Arguments Are Sent to `mergeClassNames`
230
+ **Once you see warnings in the console, the next step is to use `mergeClassNamesDebugger`**
259
231
 
260
- - Use the _conditional operator_ to conditionally including class names:
232
+ 1. Enable Debugger
233
+ - _For chromium-based browsers it's On by default and you don't need to do anything AFAIK._
234
+ - _For Firefox:_ Open **_Developer Tools_:**
235
+ - _Make Sure_ **_Debugger_** _(tab)_ -> **`Pause on debugger statement`** is ticked.
236
+ - Keep Dev Tools open.
237
+ ![screenshot of Firefox Debugger section with Pause on debugger statement ticked on](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/debugger-enabled.png)
261
238
 
262
- ```jsx
263
- import { mergeClassNames } from "simple-merge-class-names";
239
+ - Use **`import {mergeClassNamesDebugger as mergeClassNames}`** to debug the entire file, and keep the rest intact.
264
240
 
265
- const MyComponent = () => {
266
- return (
267
- <div
268
- className={mergeClassNames(
269
- "app",
270
- condition ? "min-h-dvh" : false,
271
- "grid",
272
- "grid-rows-[auto_1fr_auto]",
273
- "outline"
274
- )}
275
- >
276
- Hello, world!
277
- </div>
278
- );
279
- };
280
- ```
241
+ ```jsx
242
+ import { mergeClassNamesDebugger as mergeClassNames } from "simple-merge-class-names";
281
243
 
282
- ## Testing
244
+ const Component = ({ condition }) => {
245
+ return (
246
+ <div
247
+ className={mergeClassNames(
248
+ "app",
249
+ condition ? "min-h-dvh" : false,
250
+ "grid",
251
+ "grid-rows-[auto_1fr_auto]",
252
+ "outline"
253
+ )}
254
+ >
255
+ Hello, world!
256
+ </div>
257
+ );
258
+ };
259
+ ```
283
260
 
284
- This project uses `Vitest` as the test runner for fast and modern testing.
261
+ - or call `mergeClassNamesDebugger` directly.
285
262
 
286
- #### Run All Tests Once
263
+ ```jsx
264
+ import { mergeClassNamesDebugger } from "simple-merge-class-names";
287
265
 
288
- ```bash
289
- git clone https://github.com/new-AF/simple-merge-class-names
290
- cd simple-merge-class-names
291
- pnpm install
292
- pnpm test
293
- ```
266
+ const Component = ({ condition }) => {
267
+ return (
268
+ <div
269
+ className={mergeClassNamesDebugger(
270
+ "app",
271
+ condition ? "min-h-dvh" : false,
272
+ "grid",
273
+ "grid-rows-[auto_1fr_auto]",
274
+ "outline"
275
+ )}
276
+ >
277
+ Hello, world!
278
+ </div>
279
+ );
280
+ };
281
+ ```
294
282
 
295
- #### Run Tests In Watch Mode
283
+ - Refresh the page, the debugger should connect:
296
284
 
297
- ```bash
298
- pnpm test:watch
299
- ```
285
+ - Navigate to the **_Call stack_**
286
+ - Click the function/component right before _`mergeClassNamesDebugger`_
300
287
 
301
- ## Source Code (Partial)
288
+ ![screenshot of Firefox debugger active because of `undefined` invalid class name argument](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/debugger-active.png)
302
289
 
303
- ```javascript
304
- /**
305
- * mergeClassNames - A straightforward utility for merging CSS class names in React + Tailwind, and other JavaScript projects.
306
- *
307
- * @license AGPL-3.0
308
- * Copyright (C) 2025 Abdullah Fatota
309
- *
310
- * ...
311
- */
290
+ - Hover over the arguments, one or several should be invalid:
291
+ ![screenshot of Firefox debugger active because of `undefined` invalid class name argument](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/debugger-3.png)
312
292
 
313
- const mergeClassNamesCore = ({ args, activateDebugger }) => {
314
- const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
293
+ ## VSCode Workflow To Minimize Typing Strain
315
294
 
316
- const [_, nonFalseValues] = partition(args, isValueFalse); // ignore all false values used for conditional class inclusion
317
- const [strings, nonStrings] = partition(nonFalseValues, isTypeString);
318
- const [emptyStrings, nonEmptyStrings] = partition(strings, isEmptyString);
319
- const trimmed = nonEmptyStrings.map((val) => val.trim());
320
- const className = trimmed.join(space);
295
+ Use single quotes around class names, and activate `Prettier` which will neatly format and arrange the classes.
321
296
 
322
- /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
323
- warnInvalidArguments({ nonStrings, emptyStrings, activateDebugger });
297
+ - Install `Prettier`
298
+ - Enable `Editor: Word Wrap`:
324
299
 
325
- return className;
326
- };
300
+ - Open `Settings (UI)` → `Editor: Word Wrap` → `on`
301
+ - _or_ `User Settings (JSON)` and add this entry `"editor.wordWrap": "on"`
327
302
 
328
- export const mergeClassNames = (...args) =>
329
- mergeClassNamesCore({ args, activateDebugger: false });
303
+ - Use single quotes (<kbd>'</kbd>) around class names
304
+ - Save the file
330
305
 
331
- export const mergeClassNamesDebugger = (...args) =>
332
- mergeClassNamesCore({ args, activateDebugger: true });
333
- ```
306
+ Example:
334
307
 
335
- ## Misc.
308
+ - Before
309
+ ![Screenshot of code before Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/before.png)
336
310
 
337
- ### Motivation
311
+ - After
338
312
 
339
- This package aims to improve code readability and developer experience in _React & Tailwind_ projects by enforcing strict input handling. It logs warnings for invalid arguments, helping developers catch and fix underlying issues _early_.
313
+ ![Screenshot of code after Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/after.png)
340
314
 
341
- While writing class names as separate strings may seem tedious, the [workflow](#workflow-to-minimize-typing-strain) reduces friction and the overall process results in more readable and maintainable code in contrast to using single long strings:
315
+ ## Testing Source Code
342
316
 
343
- ```jsx
344
- const MyComponent = () => {
345
- return (
346
- <div className="app min-h-dvh grid grid-rows-[auto_1fr_auto] outline">
347
- Hello, world!
348
- </div>
349
- );
350
- };
351
- ```
317
+ This project uses `Vitest` as the test runner for fast and modern testing.
318
+
319
+ ### Run Once
352
320
 
353
- ### Why the Mismatch Between Exported Function and Package Name?
321
+ ```bash
322
+ git clone https://github.com/new-AF/simple-merge-class-names
323
+ cd simple-merge-class-names
324
+ pnpm install
325
+ pnpm test
326
+ ```
354
327
 
355
- I wanted to name the package `mergeClassNames` to reflect the exported function, but the NPM Package Registry doesn't allow capital letters, only lower case and dash characters.
328
+ ### Run Watch Mode
356
329
 
357
- In addition there was already a package named `merge-class-names` but it is no longer maintained (and the developer recommends `clsx` instead).
330
+ ```bash
331
+ pnpm test:watch
332
+ ```
358
333
 
359
334
  ## License
360
335
 
@@ -1,4 +1,7 @@
1
- export declare const mergeClassNames: (...args: (string | false)[]) => string;
1
+ export declare const mergeClassNames: (
2
+ ...args: (string | false)[]
3
+ ) => string | false;
4
+
2
5
  export declare const mergeClassNamesDebugger: (
3
6
  ...args: (string | false)[]
4
- ) => string;
7
+ ) => string | false;
@@ -37,153 +37,139 @@
37
37
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
38
38
  */
39
39
 
40
- const isValueFalse = (val) => val === false;
41
-
42
- const isTypeString = (val) => typeof val === "string";
43
-
44
- const isEmptyString = (val) => {
45
- const trimmed = val.trim();
46
- return trimmed === "";
47
- };
48
-
49
40
  const partition = (array, keepPredicate) => {
50
41
  const keep = [];
51
42
  const ignore = [];
52
43
  for (const element of array) {
53
- (keepPredicate(element) ? keep : ignore).push(element);
44
+ const arrayRef = keepPredicate(element) ? keep : ignore;
45
+ arrayRef.push(element);
54
46
  }
55
47
  return [keep, ignore];
56
48
  };
57
49
 
58
- const warnInvalidArguments = ({
59
- nonStrings,
60
- emptyStrings,
61
- activateDebugger = false,
62
- }) => {
63
- const hasInvalidTypes = nonStrings.length > 0;
64
- const hasEmptyStrings = emptyStrings.length > 0;
65
- const doPrint = hasInvalidTypes || hasEmptyStrings;
66
-
67
- if (doPrint === false) return;
68
-
69
- const messages = [];
70
- const newline = "\n";
71
- const doubleNewline = newline.repeat(2);
72
-
73
- const tabString = (string, tabCount = 0) => {
74
- const tab = "\t".repeat(tabCount);
75
- return `${tab}${string}`;
76
- };
77
-
78
- const tabArray = (array, tabCount) =>
79
- array.map((element) => tabString(element, tabCount));
80
-
81
- /* convert array to nice string because console.warn(array) squashes it on 1 line */
82
- const makeMessage = ({
83
- array,
84
- expected,
85
- tabCount = 1,
86
- includeElementType = false,
87
- arrayMaxLength = 5,
88
- }) => {
89
- const count = array.length;
90
- const addEllipsis = count > arrayMaxLength;
91
- const slice = addEllipsis ? array.slice(0, arrayMaxLength) : array;
92
-
93
- const formatElementWithType = (element, index) =>
94
- `(${index + 1}/${count}): >${element}< of type "${typeof element}"`;
95
-
96
- const formatElement = (element, index) =>
97
- `(${index + 1}/${count}): >${element}<`;
98
-
99
- const initialMapped = slice.map(
100
- includeElementType ? formatElementWithType : formatElement
101
- );
102
-
103
- const mapped = addEllipsis ? [...initialMapped, "..."] : initialMapped;
104
-
105
- const string = [
106
- tabString(
107
- `${expected}, but got ${count} invalid value(s):`,
108
- tabCount
109
- ),
110
- tabString("[", tabCount),
111
- tabArray(mapped, tabCount).join("," + newline),
112
- tabString("]", tabCount),
113
- ].join(newline);
114
-
115
- return string;
116
- };
117
-
118
- /* "Replace "mergeClassNames" with "mergeClassNamesDebugger" ... " */
119
- if (doPrint && activateDebugger === false) {
120
- messages.push(
121
- tabString(
122
- '* Replace "mergeClassNames" with "mergeClassNamesDebugger" without changing any arguments, and open the Developer Console, or attach Debugger (see README.md).',
123
- 1
124
- )
125
- );
50
+ const filterArguments = (argumentsArray) => {
51
+ /*
52
+ returns {
53
+ valid: bool,
54
+ value: any
55
+ (optional) isString: bool,
56
+ (optional) valueType: any
57
+ (optional) isValueFalse
58
+ (optional) isValidString: bool
126
59
  }
60
+ */
61
+ const isValidArgument = (value) => {
62
+ const valueType = typeof value;
127
63
 
128
- /* "Expected all arguments to be either ..." */
129
- if (hasInvalidTypes) {
130
- if (activateDebugger) {
131
- debugger;
64
+ if (value === false) {
65
+ return { valid: true, value };
132
66
  }
133
- messages.push(
134
- makeMessage({
135
- array: nonStrings,
136
- expected:
137
- "* Expected all arguments to be either strings or value `false`",
138
- includeElementType: true,
139
- })
140
- );
141
- }
142
67
 
143
- /* "Expected 0 empty strings ..." */
144
- if (hasEmptyStrings) {
145
- if (activateDebugger) {
146
- debugger;
68
+ if (valueType === "string") {
69
+ const trimmed = value.trim();
70
+
71
+ // invalid string
72
+ if (trimmed === "") {
73
+ return {
74
+ valid: false,
75
+ value,
76
+ isString: true,
77
+ isValidString: false,
78
+ };
79
+ }
80
+ // valid string
81
+ return {
82
+ valid: true,
83
+ value: trimmed,
84
+ isString: true,
85
+ isValidString: true,
86
+ };
147
87
  }
148
- messages.push(
149
- makeMessage({
150
- array: emptyStrings,
151
- expected: "* Expected 0 empty strings",
152
- })
153
- );
154
- }
155
88
 
156
- /* Full Warn makeMessage */
157
- const functionName =
158
- activateDebugger === false
159
- ? "mergeClassNames"
160
- : "mergeClassNamesDebugger";
89
+ // everything else
90
+ return { valid: false, value, isString: false, valueType };
91
+ };
161
92
 
162
- const string = [
163
- `[${functionName}] Warning: invalid arguments were provided and ignored:`,
164
- ...messages,
165
- ].join(doubleNewline);
93
+ // array of objects
94
+ const newArguments = argumentsArray.map((element) =>
95
+ isValidArgument(element)
96
+ );
166
97
 
167
- console.warn(string);
168
- };
98
+ // includes `false`
99
+ const [validArgumentsWithFalse, invalidArguments] = partition(
100
+ newArguments,
101
+ ({ valid }) => valid === true
102
+ );
103
+
104
+ // ignore `false`
105
+ const [validArguments, _] = partition(
106
+ validArgumentsWithFalse,
107
+ ({ value }) => value !== false
108
+ );
169
109
 
170
- const mergeClassNamesCore = ({ args, activateDebugger }) => {
171
- const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
110
+ // console.log({ validArgumentsWithFalse, validArguments, invalidArguments });
172
111
 
173
- const [_, nonFalseValues] = partition(args, isValueFalse); // ignore all false values used for conditional class inclusion
174
- const [strings, nonStrings] = partition(nonFalseValues, isTypeString);
175
- const [emptyStrings, nonEmptyStrings] = partition(strings, isEmptyString);
176
- const trimmed = nonEmptyStrings.map((val) => val.trim());
177
- const className = trimmed.join(space);
112
+ return { validArguments, invalidArguments };
113
+ };
178
114
 
179
- /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
180
- warnInvalidArguments({ nonStrings, emptyStrings, activateDebugger });
115
+ const warnMessage = (
116
+ { value, isString, valueType, isValidString },
117
+ callerFunctionName
118
+ ) => {
119
+ if (isString === false) {
120
+ return `[${callerFunctionName}] Ignored non-string >${value}< (${valueType})`;
121
+ } else if (isValidString === false) {
122
+ return `[${callerFunctionName}] Ignored empty string "${value}"`;
123
+ }
124
+ };
181
125
 
182
- return className;
126
+ const warnOnly = (
127
+ invalidArgumentsArray,
128
+ callerFunctionName,
129
+ debuggerVariantName
130
+ ) =>
131
+ invalidArgumentsArray.forEach((obj) => {
132
+ const message = warnMessage(obj, callerFunctionName);
133
+ console.warn(`${message}`);
134
+ });
135
+
136
+ const warnDebug = (invalidArgumentsArray, callerFunctionName) =>
137
+ invalidArgumentsArray.forEach((obj) => {
138
+ console.warn(warnMessage(obj, callerFunctionName));
139
+ debugger;
140
+ });
141
+
142
+ const join = (argumentsArray) => {
143
+ const space = "\x20"; // ASCII for single space (" "), decimal 32
144
+ const classNames = argumentsArray.map(({ value }) => value).join(space);
145
+ return classNames;
183
146
  };
184
147
 
185
- export const mergeClassNames = (...args) =>
186
- mergeClassNamesCore({ args, activateDebugger: false });
148
+ const finalResult = (joinedClassNames) =>
149
+ joinedClassNames === "" ? false : joinedClassNames;
150
+
151
+ // exported
152
+ // export const mergeClassNamesNoWarning = (...argumentsArray) => {
153
+ // const { validArguments, _ } = filterArguments(argumentsArray);
154
+ // const classNames = join(validArguments);
155
+ // const result = finalResult(classNames);
156
+ // return result;
157
+ // };
158
+
159
+ export const mergeClassNames = (...argumentsArray) => {
160
+ const { validArguments, invalidArguments } =
161
+ filterArguments(argumentsArray);
162
+ warnOnly(invalidArguments, "mergeClassNames", "mergeClassNamesDebugger");
163
+ const classNames = join(validArguments);
164
+ const result = finalResult(classNames);
165
+ return result;
166
+ };
187
167
 
188
- export const mergeClassNamesDebugger = (...args) =>
189
- mergeClassNamesCore({ args, activateDebugger: true });
168
+ export const mergeClassNamesDebugger = (...argumentsArray) => {
169
+ const { validArguments, invalidArguments } =
170
+ filterArguments(argumentsArray);
171
+ warnDebug(invalidArguments, "mergeClassNamesDebugger");
172
+ const classNames = join(validArguments);
173
+ const result = finalResult(classNames);
174
+ return result;
175
+ };
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "simple-merge-class-names",
3
- "version": "6.0.9",
4
- "description": "A straightforward utility for merging CSS class names in React + Tailwind and JavaScript projects.",
5
- "exports": {
6
- ".": {
7
- "import": "./mergeClassNames.js",
8
- "types": "./mergeClassNames.d.ts"
9
- }
10
- },
11
- "files": [
12
- "mergeClassNames.js",
13
- "mergeClassNames.d.ts",
14
- "LICENSE.txt",
15
- "README.md"
16
- ],
17
- "sideEffects": false,
18
- "scripts": {
19
- "test": "vitest",
20
- "test:watch": "vitest --watch",
21
- "test:ui": "vitest --ui"
22
- },
23
- "keywords": [
24
- "mergeClassNames",
25
- "merge",
26
- "class-names-merger",
27
- "utility",
28
- "react",
29
- "tailwindcss"
30
- ],
31
- "author": "Abdullah Fatota",
32
- "license": "AGPL-3.0",
33
- "repository": {
34
- "type": "git",
35
- "url": "git+https://github.com/new-AF/simple-merge-class-names.git"
36
- },
37
- "bugs": {
38
- "url": "https://github.com/new-AF/simple-merge-class-names/issues"
39
- },
40
- "homepage": "https://github.com/new-AF/simple-merge-class-names",
41
- "devDependencies": {
42
- "vitest": "^3.2.3"
43
- }
44
- }
1
+ {
2
+ "name": "simple-merge-class-names",
3
+ "version": "7.0.1",
4
+ "description": "A straightforward utility for merging CSS class names in React + Tailwind and JavaScript projects.",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./mergeClassNames.js",
8
+ "types": "./mergeClassNames.d.ts"
9
+ }
10
+ },
11
+ "files": [
12
+ "mergeClassNames.js",
13
+ "mergeClassNames.d.ts",
14
+ "LICENSE.txt",
15
+ "README.md"
16
+ ],
17
+ "sideEffects": false,
18
+ "keywords": [
19
+ "mergeClassNames",
20
+ "merge",
21
+ "class-names-merger",
22
+ "utility",
23
+ "react",
24
+ "tailwindcss"
25
+ ],
26
+ "author": "Abdullah Fatota",
27
+ "license": "AGPL-3.0",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/new-AF/simple-merge-class-names.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/new-AF/simple-merge-class-names/issues"
34
+ },
35
+ "homepage": "https://github.com/new-AF/simple-merge-class-names",
36
+ "devDependencies": {
37
+ "vitest": "^3.2.3"
38
+ },
39
+ "scripts": {
40
+ "test": "vitest",
41
+ "test:watch": "vitest --watch",
42
+ "test:ui": "vitest --ui"
43
+ }
44
+ }