simple-merge-class-names 4.0.1 → 5.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,32 +1,46 @@
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
+ - [Support Palestine](#support-palestine)
8
+ - [Production Considerations](#production-considerations)
7
9
  - [Installation](#installation)
8
10
  - [Install Prettier With VSCode (Most Recommended)](#install-prettier-with-vscode-most-recommended)
9
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)
10
26
 
11
- - [Conditionally Include Class Names](#conditionally-include-class-names)
27
+ ## Support Palestine
12
28
 
13
- - [Workflow To Minimize Typing Strain](#workflow-to-minimize-typing-strain)
29
+ We Stand with Palestine <img
30
+ src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEyMDAiIGhlaWdodD0iNjAwIiB2aWV3Qm94PSIwIDAgNiAzIj48cmVjdCBmaWxsPSIjMDA5NjM5IiB3aWR0aD0iNiIgaGVpZ2h0PSIzIi8+PHJlY3QgZmlsbD0iI0ZGRiIgd2lkdGg9IjYiIGhlaWdodD0iMiIvPjxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjEiLz48cGF0aCBmaWxsPSIjRUQyRTM4IiBkPSJNMCwwbDIsMS41TDAsM1oiLz48L3N2Zz4="
31
+ alt="Flag of Palestine"
32
+ style="height: 0.6rem;"
33
+ />
14
34
 
15
- - [Argument Handling](#argument-handling)
35
+ [Donate](https://gazafunds.com/)
16
36
 
17
- - [Console Warning for Invalid Arguments](#console-warning-for-invalid-arguments)
37
+ [Boycott Brands Supporting Genocide](https://www.uplift.ie/bds/)
18
38
 
19
- - [Breaking Changes From Version 1.X.X](#breaking-changes-from-version-1xx)
39
+ [Legal Action](https://www.hindrajabfoundation.org/perpetrators)
20
40
 
21
- - [Testing](#testing)
22
- - [Source Code (Partial)](#source-code-partial)
23
- - [Misc.](#misc)
41
+ ## Production Considerations
24
42
 
25
- - [Why the Mismatch Between Exported Function and Package Name?](#why-the-mismatch-between-exported-function-and-package-name)
26
- - [Where This Package Excels](#where-this-package-excels)
27
-
28
- - [Production Considerations](#production-considerations)
29
- - [License](#license)
43
+ 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)
30
44
 
31
45
  ## Installation
32
46
 
@@ -44,18 +58,18 @@ npm install simple-merge-class-names
44
58
 
45
59
  ### Install `Prettier` With VSCode (Most Recommended)
46
60
 
47
- [https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
61
+ - [https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
48
62
 
49
- Or install an equivalent auto code formatter for your IDE.
63
+ - Or use an equivalent auto code formatter for your IDE.
50
64
 
51
65
  ## Usage
52
66
 
53
- Import `mergeClassNames(...args)` from the package, and use it in your JSX or JavaScript code.
67
+ Import `mergeClassNames(...args)` from the package, and use it in your _JSX_ or _JavaScript_ code.
54
68
 
55
69
  ```jsx
56
70
  import { mergeClassNames } from "simple-merge-class-names";
57
71
 
58
- function MyComponent() {
72
+ const MyComponent = () => {
59
73
  return (
60
74
  <div
61
75
  className={mergeClassNames(
@@ -69,40 +83,35 @@ function MyComponent() {
69
83
  Hello, world!
70
84
  </div>
71
85
  );
72
- }
86
+ };
73
87
  ```
74
88
 
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:
89
+ Or for debugging purposes to fix [console warnings](#console-warning):
90
+
91
+ - Open the _Browser's Developer Tools_ and
92
+ - Use `mergeClassNamesDebugger`
76
93
 
77
94
  ```jsx
78
- function MyComponent() {
95
+ import { mergeClassNamesDebugger } from "simple-merge-class-names";
96
+
97
+ const MyComponent = () => {
79
98
  return (
80
- <div className="app min-h-dvh grid grid-rows-[auto_1fr_auto] outline">
99
+ <div
100
+ className={mergeClassNamesDebugger(
101
+ "app",
102
+ condition ? "min-h-dvh" : false,
103
+ "grid",
104
+ "grid-rows-[auto_1fr_auto]",
105
+ "outline"
106
+ )}
107
+ >
81
108
  Hello, world!
82
109
  </div>
83
110
  );
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
- );
111
+ };
99
112
  ```
100
113
 
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)
114
+ ## Workflow To Minimize Typing Strain
106
115
 
107
116
  - Have `Prettier` installed
108
117
  - Have `Editor: Word Wrap` enabled in VS Code:
@@ -112,44 +121,192 @@ mergeClassNames(
112
121
 
113
122
  `"editor.wordWrap": "on"`
114
123
 
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.
124
+ - **Use single quotes** (<kbd>'</kbd>) for class names, often a single key press on many keyboards.
125
+ - **Save the file** (<kbd>Ctrl+S</kbd>), which activates `Prettier` to auto-format the file, it will:
126
+
127
+ - Replace single quotes with double quotes.
128
+ - Neatly arrange each class name on a new line.
119
129
 
120
- ## Argument Handling
130
+ ### Result
121
131
 
122
- `mergeClassNames(...args)` accepts only the following arguments:
132
+ #### Before
123
133
 
124
- - **Non-empty strings** (for example: `"app"`, `"min-h-dvh"`)
134
+ ![Screenshot of code before Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/before.png)
135
+
136
+ #### After
137
+
138
+ ![Screenshot of code after Prettier neatly formats code](https://raw.githubusercontent.com/new-AF/simple-merge-class-names/main/.github/images/after.png)
139
+
140
+ ## Type Definitions (of Exported Functions)
141
+
142
+ ```jsx
143
+ mergeClassNames: (...args: (string | false)[]) => string;
144
+ ```
145
+
146
+ ```jsx
147
+ mergeClassNamesDebugger: (...args: (string | false)[]) => string;
148
+ ```
149
+
150
+ ```jsx
151
+ isEmptyString: (argument: string) => boolean;
152
+ ```
153
+
154
+ For both `mergeClassNames` and `mergeClassNamesDebugger` they always return a string.
155
+
156
+ - If no inputs were provided e.g. `mergeClassNames()` or were invalid `mergeClassNames(undefined, " ")` then an _empty string_ is returned `""`
157
+
158
+ ## Accepted Arguments
159
+
160
+ `mergeClassNames(...args)` and `mergeClassNamesDebugger(...args)` only accept the following arguments:
161
+
162
+ - **Strings that are not empty, and are not whitespace** (for example: `"app"`, `"min-h-dvh"`, `" grid "`)
125
163
 
126
164
  - The boolean value **`false`**
127
165
 
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.
166
+ Everything else is an _invalid argument_ that will be _ignored_, and cause a _warning_ to be logged, these argument types include:
129
167
 
130
- ### Console Warning for Invalid Arguments
168
+ - _empty strings_ (`""`),
169
+ - _whitespace combinations_ (e.g. `"\n"`, `" \n\t "`, etc...),
170
+ - `null`,
171
+ - `undefined`,
172
+ - _numbers_,
173
+ - _objects_,
174
+ - _arrays_
131
175
 
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.
176
+ ## Console Warning
133
177
 
134
- Example output:
178
+ 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
179
 
136
180
  ```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.
181
+ [mergeClassNames] Warning: invalid arguments were provided and ignored:
182
+
183
+ * Replace "mergeClassNames" with "mergeClassNamesDebugger" without changing any arguments, and open the Developer Console, or attach Debugger (see README.md).
184
+
185
+ * Expected all arguments to be either strings or value `false`, but got 5 invalid value(s):
186
+ [
187
+ (1/5): >undefined< of type "undefined",
188
+ (2/5): > test < of type "object",
189
+ (3/5): >[object Object]< of type "object",
190
+ (4/5): >true< of type "boolean",
191
+ (5/5): >null< of type "object"
192
+ ]
193
+
194
+ * Expected 0 empty strings, but got 2 invalid value(s):
195
+ [
196
+ (1/2): ><,
197
+ (2/2): > <
198
+ ]
199
+ ```
200
+
201
+ ## Conditionally Include Class Names
202
+
203
+ To conditionally include a class name, use the
204
+
205
+ - _Conditional operator_ `condition ? "class-name" : false` with `false` as the fallback value to maintain a clear and warning-free code.
206
+
207
+ This is because `false` will never cause the function to print a warning.
208
+
209
+ **Important**: Avoid using the
210
+
211
+ - _Short-circuit implicit syntax_ `condition && "class-name"` because it can produce falsy values like `0`, `""`, `undefined`, `null`, which will cause warnings to be logged.
212
+
213
+ ```jsx
214
+ import { mergeClassNames } from "simple-merge-class-names";
215
+
216
+ const MyComponent = () => {
217
+ return (
218
+ <div
219
+ className={mergeClassNames(
220
+ "app",
221
+ condition ? "min-h-dvh" : false,
222
+ "grid",
223
+ "grid-rows-[auto_1fr_auto]",
224
+ "outline"
225
+ )}
226
+ >
227
+ Hello, world!
228
+ </div>
229
+ );
230
+ };
140
231
  ```
141
232
 
142
- ### Breaking Changes From Version 1.X.X
233
+ ## Using `mergeClassNamesDebugger` And The Built-in Browser Debugger To Find And Fix Warnings
234
+
235
+ `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.
236
+
237
+ 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:
238
+
239
+ 1. Simply **_Open the Browser's Developer Tools_**, this tells the JavaScript engine that the Debugger is _enabled_.
240
+ 2. **_Replace_** `mergeClassNames` with **`mergeClassNamesDebugger`** _without_ changing any of the argument provided.
241
+
242
+ _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._
243
+
244
+ When the Debugger is active, it should look like this screenshot (in _FireFox_):
245
+
246
+ ![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)
247
+
248
+ ## Strategies To Ensure Correct Arguments Are Sent to `mergeClassNames`
249
+
250
+ - Use the _conditional operator_ to conditionally including class names:
251
+
252
+ ```jsx
253
+ import { mergeClassNames } from "simple-merge-class-names";
254
+
255
+ const MyComponent = () => {
256
+ return (
257
+ <div
258
+ className={mergeClassNames(
259
+ "app",
260
+ condition ? "min-h-dvh" : false,
261
+ "grid",
262
+ "grid-rows-[auto_1fr_auto]",
263
+ "outline"
264
+ )}
265
+ >
266
+ Hello, world!
267
+ </div>
268
+ );
269
+ };
270
+ ```
271
+
272
+ - If chaining the result of another `mergeClassNames` then use `isEmptyString` to check if the result is not the _empty string_ (`""`):
273
+
274
+ ```jsx
275
+ import { mergeClassNames, isEmptyString } from "simple-merge-class-names";
276
+
277
+ const MyComponent = ({ condition, resultOfAnotherMergeClassNames }) => {
278
+ return (
279
+ <div
280
+ className={mergeClassNames(
281
+ "app",
282
+
283
+ condition ? "min-h-dvh" : false,
143
284
 
144
- In pervious versions, arguments that were not strings were implicitly converted to strings by the JavaScript engine.
285
+ isEmptyString(resultOfAnotherMergeClassNames)
286
+ ? false
287
+ : resultOfAnotherMergeClassNames,
288
+
289
+ "grid",
290
+ "grid-rows-[auto_1fr_auto]",
291
+ "outline"
292
+ )}
293
+ >
294
+ Hello, world!
295
+ </div>
296
+ );
297
+ };
298
+ ```
145
299
 
146
300
  ## Testing
147
301
 
148
- This project uses `Vitest` as the test runner for fast, modern testing.
302
+ This project uses `Vitest` as the test runner for fast and modern testing.
149
303
 
150
- #### Run All Testing Once
304
+ #### Run All Tests Once
151
305
 
152
306
  ```bash
307
+ git clone https://github.com/new-AF/simple-merge-class-names
308
+ cd simple-merge-class-names
309
+ pnpm install
153
310
  pnpm test
154
311
  ```
155
312
 
@@ -164,56 +321,58 @@ pnpm test:watch
164
321
  ```javascript
165
322
  /**
166
323
  * mergeClassNames - A straightforward utility for merging CSS class names in React + Tailwind, and other JavaScript projects.
167
- ...
324
+ *
325
+ * @license AGPL-3.0
326
+ * Copyright (C) 2025 Abdullah Fatota
327
+ *
328
+ * ...
168
329
  */
169
330
 
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) => {
331
+ const mergeClassNamesCore = ({ args, activateDebugger }) => {
184
332
  const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
185
333
 
186
- const [strings, nonStrings] = partition(args, isTypeString);
334
+ const [_, nonFalseValues] = partition(args, isValueFalse); // ignore all false values used for conditional class inclusion
335
+ const [strings, nonStrings] = partition(nonFalseValues, isTypeString);
336
+ const [emptyStrings, nonEmptyStrings] = partition(strings, isEmptyString);
337
+ const trimmed = nonEmptyStrings.map((val) => val.trim());
338
+ const className = trimmed.join(space);
187
339
 
188
- const trimmed = strings.map((val) => val.trim());
340
+ /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
341
+ warnInvalidArguments({ nonStrings, emptyStrings, activateDebugger });
189
342
 
190
- const [nonEmptyStrings, emptyStrings] = partition(
191
- trimmed,
192
- isNonEmptyString
193
- );
343
+ return className;
344
+ };
194
345
 
195
- const className = nonEmptyStrings.join(space);
346
+ export const mergeClassNames = (...args) =>
347
+ mergeClassNamesCore({ args, activateDebugger: false });
196
348
 
197
- /* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
198
- const warn = [];
199
- /* ... */
349
+ export const mergeClassNamesDebugger = (...args) =>
350
+ mergeClassNamesCore({ args, activateDebugger: true });
200
351
  ```
201
352
 
202
353
  ## Misc.
203
354
 
204
- ### Why the Mismatch Between Exported Function and Package Name?
355
+ ### Motivation
205
356
 
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.
357
+ 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
358
 
208
- In addition there was already a package named `merge-class-names` but it is no longer maintained (and the developer recommends `clsx` instead).
359
+ 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
360
 
210
- ### Where This Package Excels
361
+ ```jsx
362
+ const MyComponent = () => {
363
+ return (
364
+ <div className="app min-h-dvh grid grid-rows-[auto_1fr_auto] outline">
365
+ Hello, world!
366
+ </div>
367
+ );
368
+ };
369
+ ```
211
370
 
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.
371
+ ### Why the Mismatch Between Exported Function and Package Name?
213
372
 
214
- ## Production Considerations
373
+ 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
374
 
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)
375
+ In addition there was already a package named `merge-class-names` but it is no longer maintained (and the developer recommends `clsx` instead).
217
376
 
218
377
  ## License
219
378
 
@@ -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.1",
4
4
  "description": "A straightforward utility for merging CSS class names in React + Tailwind and JavaScript projects.",
5
5
  "exports": {
6
6
  ".": {