simple-merge-class-names 4.0.1 → 5.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/README.md CHANGED
@@ -1,33 +1,32 @@
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 + Tailwind` and other _JavaScript_ projects.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
+ - [Production Considerations](#production-considerations)
7
8
  - [Installation](#installation)
8
9
  - [Install Prettier With VSCode (Most Recommended)](#install-prettier-with-vscode-most-recommended)
9
10
  - [Usage](#usage)
10
-
11
- - [Conditionally Include Class Names](#conditionally-include-class-names)
12
-
13
- - [Workflow To Minimize Typing Strain](#workflow-to-minimize-typing-strain)
14
-
15
- - [Argument Handling](#argument-handling)
16
-
17
- - [Console Warning for Invalid Arguments](#console-warning-for-invalid-arguments)
18
-
19
- - [Breaking Changes From Version 1.X.X](#breaking-changes-from-version-1xx)
20
-
11
+ - [Workflow To Minimize Typing Strain](#workflow-to-minimize-typing-strain)
12
+ - [Type Definitions (of Exported Functions)](#type-definitions-of-exported-functions)
13
+ - [Accepted Arguments](#accepted-arguments)
14
+ - [Console Warning](#console-warning)
15
+ - [Conditionally Include Class Names](#conditionally-include-class-names)
16
+ - [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)
17
+ - [Strategies To Ensure Correct Arguments Are Sent to `mergeClassNames`](#strategies-to-ensure-correct-arguments-are-sent-to-mergeclassnames)
21
18
  - [Testing](#testing)
22
19
  - [Source Code (Partial)](#source-code-partial)
23
20
  - [Misc.](#misc)
24
-
21
+ - [Motivation](#motivation)
25
22
  - [Why the Mismatch Between Exported Function and Package Name?](#why-the-mismatch-between-exported-function-and-package-name)
26
23
  - [Where This Package Excels](#where-this-package-excels)
27
-
28
- - [Production Considerations](#production-considerations)
29
24
  - [License](#license)
30
25
 
26
+ ## Production Considerations
27
+
28
+ When developing this package I prioritized _code readability_, _strict input handling_, and _improved developer experience_, as such **_performance_** and **_features_** were not the guiding factor. Therefore if you are considering this package for production, you might also want to look into `clsx`: [https://www.npmjs.com/package/clsx](https://www.npmjs.com/package/clsx)
29
+
31
30
  ## Installation
32
31
 
33
32
  ```bash
@@ -44,18 +43,18 @@ npm install simple-merge-class-names
44
43
 
45
44
  ### Install `Prettier` With VSCode (Most Recommended)
46
45
 
47
- [https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
46
+ - [https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
48
47
 
49
- Or install an equivalent auto code formatter for your IDE.
48
+ - Or use an equivalent auto code formatter for your IDE.
50
49
 
51
50
  ## Usage
52
51
 
53
- Import `mergeClassNames(...args)` from the package, and use it in your JSX or JavaScript code.
52
+ Import `mergeClassNames(...args)` from the package, and use it in your _JSX_ or _JavaScript_ code.
54
53
 
55
54
  ```jsx
56
55
  import { mergeClassNames } from "simple-merge-class-names";
57
56
 
58
- function MyComponent() {
57
+ const MyComponent = () => {
59
58
  return (
60
59
  <div
61
60
  className={mergeClassNames(
@@ -69,40 +68,35 @@ function MyComponent() {
69
68
  Hello, world!
70
69
  </div>
71
70
  );
72
- }
71
+ };
73
72
  ```
74
73
 
75
- While using separate strings for each class name can be tedious, see [Workflow To Minimize Typing Strain](#workflow-to-minimize-typing-strain), it significantly enhances code readability and Developer Experience (DX). This is in contrast to hard-to-read strings like:
74
+ Or for debugging purposes to fix [console warnings](#console-warning):
75
+
76
+ - Open the _Browser's Developer Tools_ and
77
+ - Use `mergeClassNamesDebugger`
76
78
 
77
79
  ```jsx
78
- function MyComponent() {
80
+ import { mergeClassNamesDebugger } from "simple-merge-class-names";
81
+
82
+ const MyComponent = () => {
79
83
  return (
80
- <div className="app min-h-dvh grid grid-rows-[auto_1fr_auto] outline">
84
+ <div
85
+ className={mergeClassNamesDebugger(
86
+ "app",
87
+ condition ? "min-h-dvh" : false,
88
+ "grid",
89
+ "grid-rows-[auto_1fr_auto]",
90
+ "outline"
91
+ )}
92
+ >
81
93
  Hello, world!
82
94
  </div>
83
95
  );
84
- }
85
- ```
86
-
87
- ### Conditionally Include Class Names
88
-
89
- To conditionally include a class, use the ternary operator like this: `condition ? 'class-name' : false` to maintain clear and warning-free code.
90
-
91
- ```jsx
92
- mergeClassNames(
93
- "app",
94
- condition ? "min-h-dvh" : false,
95
- "grid",
96
- "grid-rows-[auto_1fr_auto]",
97
- "outline"
98
- );
96
+ };
99
97
  ```
100
98
 
101
- **Important**: Avoid using the short-circuit implicit syntax `condition && "class-name"` because it can produce falsy values like `0`, `""`, `undefined`, `null`, which will cause warnings to be logged.
102
-
103
- ### Workflow To Minimize Typing Strain
104
-
105
- ![Screen recording of optimal DX in action: using this package with Prettier as it neatly arranges each class name on a new line](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/Reduce%20typing%20strain.gif)
99
+ ## Workflow To Minimize Typing Strain
106
100
 
107
101
  - Have `Prettier` installed
108
102
  - Have `Editor: Word Wrap` enabled in VS Code:
@@ -112,44 +106,192 @@ mergeClassNames(
112
106
 
113
107
  `"editor.wordWrap": "on"`
114
108
 
115
- - Use single quotes (<kbd>'</kbd>) for class names, often a single key press on many keyboards.
116
- - Save the file (<kbd>Ctrl+S</kbd>), and Prettier does the formatting heavy-lifting, it automatically:
117
- - Replaces single quotes with double quotes.
118
- - Neatly arranges each class name on a new line.
109
+ - **Use single quotes** (<kbd>'</kbd>) for class names, often a single key press on many keyboards.
110
+ - **Save the file** (<kbd>Ctrl+S</kbd>), which activates `Prettier` to auto-format the file, it will:
111
+
112
+ - Replace single quotes with double quotes.
113
+ - Neatly arrange each class name on a new line.
114
+
115
+ ### Result
116
+
117
+ #### Before
118
+
119
+ ![Screenshot of code before Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/before.png)
119
120
 
120
- ## Argument Handling
121
+ #### After
121
122
 
122
- `mergeClassNames(...args)` accepts only the following arguments:
123
+ ![Screenshot of code after Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/after.png)
123
124
 
124
- - **Non-empty strings** (for example: `"app"`, `"min-h-dvh"`)
125
+ ## Type Definitions (of Exported Functions)
126
+
127
+ ```jsx
128
+ mergeClassNames: (...args: (string | false)[]) => string;
129
+ ```
130
+
131
+ ```jsx
132
+ mergeClassNamesDebugger: (...args: (string | false)[]) => string;
133
+ ```
134
+
135
+ ```jsx
136
+ isEmptyString: (argument: string) => boolean;
137
+ ```
138
+
139
+ For both `mergeClassNames` and `mergeClassNamesDebugger` they always return a string.
140
+
141
+ - If no inputs were provided e.g. `mergeClassNames()` or were invalid `mergeClassNames(undefined, " ")` then an _empty string_ is returned `""`
142
+
143
+ ## Accepted Arguments
144
+
145
+ `mergeClassNames(...args)` and `mergeClassNamesDebugger(...args)` only accept the following arguments:
146
+
147
+ - **Strings that are not empty, and are not whitespace** (for example: `"app"`, `"min-h-dvh"`, `" grid "`)
125
148
 
126
149
  - The boolean value **`false`**
127
150
 
128
- Everything else like empty strings (`""`), `null`, `undefined`, numbers, objects and arrays is _ignored_ and logged via `console.warn` to alert the user of a potentially deeper issue.
151
+ Everything else is an _invalid argument_ that will be _ignored_, and cause a _warning_ to be logged, these argument types include:
129
152
 
130
- ### Console Warning for Invalid Arguments
153
+ - _empty strings_ (`""`),
154
+ - _whitespace combinations_ (e.g. `"\n"`, `" \n\t "`, etc...),
155
+ - `null`,
156
+ - `undefined`,
157
+ - _numbers_,
158
+ - _objects_,
159
+ - _arrays_
131
160
 
132
- Invalid arguments are not silently ignored, as they may indicate a deeper issue. A single warning is logged to the developer console whenever these arguments are passed and ignored.
161
+ ## Console Warning
133
162
 
134
- Example output:
163
+ Whenever invalid arguments are passed to `mergeClassNames` they are _not silently ignored_ because this can cause a lot of subtle bugs in the future and compound technical debt. Therefore a `console.warn` is printed in the _Developer Console_ to alert of the potentially deeper issue that requires rectifying. Example:
135
164
 
136
165
  ```plaintext
137
- [mergeClassNames] Warning: invalid arguments were provided and were ignored:
138
- - Expected all arguments to be strings, but got 4 non-string values: [(1/4): (undefined) of type "undefined", (2/4): (test) of type "object", (3/4): ([object Object]) of type "object", (4/4): (null) of type "object"].
139
- - Expected 0 empty strings, but got 2.
166
+ [mergeClassNames] Warning: invalid arguments were provided and ignored:
167
+
168
+ * Replace "mergeClassNames" with "mergeClassNamesDebugger" without changing any arguments, and open the Developer Console, or attach Debugger (see README.md).
169
+
170
+ * Expected all arguments to be either strings or value `false`, but got 5 invalid value(s):
171
+ [
172
+ (1/5): >undefined< of type "undefined",
173
+ (2/5): > test < of type "object",
174
+ (3/5): >[object Object]< of type "object",
175
+ (4/5): >true< of type "boolean",
176
+ (5/5): >null< of type "object"
177
+ ]
178
+
179
+ * Expected 0 empty strings, but got 2 invalid value(s):
180
+ [
181
+ (1/2): ><,
182
+ (2/2): > <
183
+ ]
140
184
  ```
141
185
 
142
- ### Breaking Changes From Version 1.X.X
186
+ ## Conditionally Include Class Names
187
+
188
+ To conditionally include a class name, use the
189
+
190
+ - _Conditional operator_ `condition ? "class-name" : false` with `false` as the fallback value to maintain a clear and warning-free code.
191
+
192
+ This is because `false` will never cause the function to print a warning.
193
+
194
+ **Important**: Avoid using the
143
195
 
144
- In pervious versions, arguments that were not strings were implicitly converted to strings by the JavaScript engine.
196
+ - _Short-circuit implicit syntax_ `condition && "class-name"` because it can produce falsy values like `0`, `""`, `undefined`, `null`, which will cause warnings to be logged.
197
+
198
+ ```jsx
199
+ import { mergeClassNames } from "simple-merge-class-names";
200
+
201
+ const MyComponent = () => {
202
+ return (
203
+ <div
204
+ className={mergeClassNames(
205
+ "app",
206
+ condition ? "min-h-dvh" : false,
207
+ "grid",
208
+ "grid-rows-[auto_1fr_auto]",
209
+ "outline"
210
+ )}
211
+ >
212
+ Hello, world!
213
+ </div>
214
+ );
215
+ };
216
+ ```
217
+
218
+ ## Using `mergeClassNamesDebugger` And The Built-in Browser Debugger To Find And Fix Warnings
219
+
220
+ `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.
221
+
222
+ This built-in JavaScript feature gained wide-spread support from major browsers around 2012, so you are getting this feature for free with minimal effort, all you have to do are 2 things:
223
+
224
+ 1. Simply **_Open the Browser's Developer Tools_**, this tells the JavaScript engine that the Debugger is _enabled_.
225
+ 2. **_Replace_** `mergeClassNames` with **`mergeClassNamesDebugger`** _without_ changing any of the argument provided.
226
+
227
+ _When the Debugger is enabled (i.e. *Browser's Developer Tools* is open) and an invalid argument like `undefined` or `" "` is passed to `mergeClassNamesDebugger`, then the JavaScript engine will automatically pause execution and highlight the invalid argument. You simply have to select the offending component (`Container.jsx` in this case) from the Call Stack._
228
+
229
+ When the Debugger is active, it should look like this screenshot (in _FireFox_):
230
+
231
+ ![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)
232
+
233
+ ## Strategies To Ensure Correct Arguments Are Sent to `mergeClassNames`
234
+
235
+ - Use the _conditional operator_ to conditionally including class names:
236
+
237
+ ```jsx
238
+ import { mergeClassNames } from "simple-merge-class-names";
239
+
240
+ const MyComponent = () => {
241
+ return (
242
+ <div
243
+ className={mergeClassNames(
244
+ "app",
245
+ condition ? "min-h-dvh" : false,
246
+ "grid",
247
+ "grid-rows-[auto_1fr_auto]",
248
+ "outline"
249
+ )}
250
+ >
251
+ Hello, world!
252
+ </div>
253
+ );
254
+ };
255
+ ```
256
+
257
+ - If chaining the result of another `mergeClassNames` then use `isEmptyString` to check if the result is not the _empty string_ (`""`):
258
+
259
+ ```jsx
260
+ import { mergeClassNames, isEmptyString } from "simple-merge-class-names";
261
+
262
+ const MyComponent = ({ condition, resultOfAnotherMergeClassNames }) => {
263
+ return (
264
+ <div
265
+ className={mergeClassNames(
266
+ "app",
267
+
268
+ condition ? "min-h-dvh" : false,
269
+
270
+ isEmptyString(resultOfAnotherMergeClassNames)
271
+ ? false
272
+ : resultOfAnotherMergeClassNames,
273
+
274
+ "grid",
275
+ "grid-rows-[auto_1fr_auto]",
276
+ "outline"
277
+ )}
278
+ >
279
+ Hello, world!
280
+ </div>
281
+ );
282
+ };
283
+ ```
145
284
 
146
285
  ## Testing
147
286
 
148
- This project uses `Vitest` as the test runner for fast, modern testing.
287
+ This project uses `Vitest` as the test runner for fast and modern testing.
149
288
 
150
- #### Run All Testing Once
289
+ #### Run All Tests Once
151
290
 
152
291
  ```bash
292
+ git clone https://github.com/new-AF/simple-merge-class-names
293
+ cd simple-merge-class-names
294
+ pnpm install
153
295
  pnpm test
154
296
  ```
155
297
 
@@ -164,56 +306,58 @@ pnpm test:watch
164
306
  ```javascript
165
307
  /**
166
308
  * mergeClassNames - A straightforward utility for merging CSS class names in React + Tailwind, and other JavaScript projects.
167
- ...
309
+ *
310
+ * @license AGPL-3.0
311
+ * Copyright (C) 2025 Abdullah Fatota
312
+ *
313
+ * ...
168
314
  */
169
315
 
170
- const isTypeString = (val) => typeof val === "string";
171
-
172
- const isNonEmptyString = (val) => val !== "";
173
-
174
- const partition = (array, keepPredicate) => {
175
- const keep = [];
176
- const ignore = [];
177
- for (const element of array) {
178
- (keepPredicate(element) ? keep : ignore).push(element);
179
- }
180
- return [keep, ignore];
181
- };
182
-
183
- export const mergeClassNames = (...args) => {
316
+ const mergeClassNamesCore = ({ args, activateDebugger }) => {
184
317
  const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
185
318
 
186
- const [strings, nonStrings] = partition(args, isTypeString);
319
+ const [_, nonFalseValues] = partition(args, isValueFalse); // ignore all false values used for conditional class inclusion
320
+ const [strings, nonStrings] = partition(nonFalseValues, isTypeString);
321
+ const [emptyStrings, nonEmptyStrings] = partition(strings, isEmptyString);
322
+ const trimmed = nonEmptyStrings.map((val) => val.trim());
323
+ const className = trimmed.join(space);
187
324
 
188
- const trimmed = strings.map((val) => val.trim());
325
+ /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
326
+ warnInvalidArguments({ nonStrings, emptyStrings, activateDebugger });
189
327
 
190
- const [nonEmptyStrings, emptyStrings] = partition(
191
- trimmed,
192
- isNonEmptyString
193
- );
328
+ return className;
329
+ };
194
330
 
195
- const className = nonEmptyStrings.join(space);
331
+ export const mergeClassNames = (...args) =>
332
+ mergeClassNamesCore({ args, activateDebugger: false });
196
333
 
197
- /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
198
- const warn = [];
199
- /* ... */
334
+ export const mergeClassNamesDebugger = (...args) =>
335
+ mergeClassNamesCore({ args, activateDebugger: true });
200
336
  ```
201
337
 
202
338
  ## Misc.
203
339
 
204
- ### Why the Mismatch Between Exported Function and Package Name?
340
+ ### Motivation
205
341
 
206
- I wanted to name the package as `mergeClassNames` to reflect the single exported function, but the NPM Package Registry does not allow capital letters, only lower case and dash characters.
342
+ 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_.
207
343
 
208
- In addition there was already a package named `merge-class-names` but it is no longer maintained (and the developer recommends `clsx` instead).
344
+ In addition 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 than using single long strings:
209
345
 
210
- ### Where This Package Excels
346
+ ```jsx
347
+ const MyComponent = () => {
348
+ return (
349
+ <div className="app min-h-dvh grid grid-rows-[auto_1fr_auto] outline">
350
+ Hello, world!
351
+ </div>
352
+ );
353
+ };
354
+ ```
211
355
 
212
- While similar packages exist (`clsx`) with better features and potentially improved performance, `simple-merge-class-names` focuses on being very straightforward and easy to reason about, as defined in its source code.
356
+ ### Why the Mismatch Between Exported Function and Package Name?
213
357
 
214
- ## Production Considerations
358
+ 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.
215
359
 
216
- If you are considering this package for production, you might also want to look into `clsx`: [https://www.npmjs.com/package/clsx](https://www.npmjs.com/package/clsx)
360
+ In addition there was already a package named `merge-class-names` but it is no longer maintained (and the developer recommends `clsx` instead).
217
361
 
218
362
  ## License
219
363
 
@@ -1 +1,5 @@
1
- export declare const mergeClassNames: (...args: (string | boolean)[]) => string;
1
+ export declare const mergeClassNames: (...args: (string | false)[]) => string;
2
+ export declare const mergeClassNamesDebugger: (
3
+ ...args: (string | false)[]
4
+ ) => string;
5
+ export declare const isEmptyString: (argument: string) => boolean;
@@ -41,7 +41,10 @@ const isValueFalse = (val) => val === false;
41
41
 
42
42
  const isTypeString = (val) => typeof val === "string";
43
43
 
44
- const isNonEmptyString = (val) => val !== "";
44
+ export const isEmptyString = (val) => {
45
+ const trimmed = val.trim();
46
+ return trimmed === "";
47
+ };
45
48
 
46
49
  const partition = (array, keepPredicate) => {
47
50
  const keep = [];
@@ -52,62 +55,135 @@ const partition = (array, keepPredicate) => {
52
55
  return [keep, ignore];
53
56
  };
54
57
 
55
- export const mergeClassNames = (...args) => {
56
- const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
57
-
58
- const [_, nonFalseValues] = partition(args, isValueFalse); // ignore all false values used for conditional class inclusion
59
-
60
- const [strings, nonStrings] = partition(nonFalseValues, isTypeString);
61
-
62
- const trimmed = strings.map((val) => val.trim());
63
-
64
- const [nonEmptyStrings, emptyStrings] = partition(
65
- trimmed,
66
- isNonEmptyString
67
- );
68
-
69
- const className = nonEmptyStrings.join(space);
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
+ );
70
102
 
71
- /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
72
- const warn = [];
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
+ );
126
+ }
73
127
 
74
128
  /* "Expected all arguments to be either ..." */
75
- if (nonStrings.length > 0) {
76
- const join = ", ";
77
- const count = nonStrings.length;
78
- const maxPrint = 10;
79
- const formatGotArray = (element, index) =>
80
- `(${index + 1}/${count}): (${element}) of type "${typeof element}"`;
81
- const message = (
82
- count > maxPrint ? nonStrings.slice(0, maxPrint) : nonStrings
83
- )
84
- .map(formatGotArray)
85
- .join(join);
86
- const suffix = count > maxPrint ? ", ... ]" : "]";
87
-
88
- warn.push(
89
- `Expected all arguments to be either strings or value "false", but got ${count} invalid values: [${message}${suffix}.`
129
+ if (hasInvalidTypes) {
130
+ if (activateDebugger) {
131
+ debugger;
132
+ }
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
+ })
90
140
  );
91
141
  }
92
142
 
93
143
  /* "Expected 0 empty strings ..." */
94
- if (emptyStrings.length > 0) {
95
- const count = emptyStrings.length;
96
- warn.push(`Expected 0 empty strings, but got ${count}.`);
144
+ if (hasEmptyStrings) {
145
+ if (activateDebugger) {
146
+ debugger;
147
+ }
148
+ messages.push(
149
+ makeMessage({
150
+ array: emptyStrings,
151
+ expected: "* Expected 0 empty strings",
152
+ })
153
+ );
97
154
  }
98
155
 
99
- /* Full Warn Message */
100
- if (warn.length > 0) {
101
- const newline = "\n";
102
- const prefix = "\t" + "- ";
103
- const message = warn.map((text) => `${prefix}${text}`).join(newline);
156
+ /* Full Warn makeMessage */
157
+ const functionName =
158
+ activateDebugger === false
159
+ ? "mergeClassNames"
160
+ : "mergeClassNamesDebugger";
104
161
 
105
- console.warn(
106
- "[mergeClassNames] Warning: invalid arguments were provided and were ignored:",
107
- newline,
108
- message
109
- );
110
- }
162
+ const string = [
163
+ `[${functionName}] Warning: invalid arguments were provided and ignored:`,
164
+ ...messages,
165
+ ].join(doubleNewline);
166
+
167
+ console.warn(string);
168
+ };
169
+
170
+ const mergeClassNamesCore = ({ args, activateDebugger }) => {
171
+ const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
172
+
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);
178
+
179
+ /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
180
+ warnInvalidArguments({ nonStrings, emptyStrings, activateDebugger });
111
181
 
112
182
  return className;
113
183
  };
184
+
185
+ export const mergeClassNames = (...args) =>
186
+ mergeClassNamesCore({ args, activateDebugger: false });
187
+
188
+ export const mergeClassNamesDebugger = (...args) =>
189
+ mergeClassNamesCore({ args, activateDebugger: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-merge-class-names",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "A straightforward utility for merging CSS class names in React + Tailwind and JavaScript projects.",
5
5
  "exports": {
6
6
  ".": {