simple-merge-class-names 4.0.0 → 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 +242 -74
- package/mergeClassNames.d.ts +5 -1
- package/mergeClassNames.js +123 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
# simple-merge-class-names
|
|
2
2
|
|
|
3
|
-
A straightforward utility for merging CSS class names in `React + Tailwind` and other
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
- [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [Console Warning for Invalid Arguments](#console-warning-for-invalid-arguments)
|
|
18
|
-
|
|
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)
|
|
19
18
|
- [Testing](#testing)
|
|
20
19
|
- [Source Code (Partial)](#source-code-partial)
|
|
21
20
|
- [Misc.](#misc)
|
|
22
|
-
|
|
21
|
+
- [Motivation](#motivation)
|
|
23
22
|
- [Why the Mismatch Between Exported Function and Package Name?](#why-the-mismatch-between-exported-function-and-package-name)
|
|
24
23
|
- [Where This Package Excels](#where-this-package-excels)
|
|
25
|
-
|
|
26
|
-
- [Production Considerations](#production-considerations)
|
|
27
24
|
- [License](#license)
|
|
28
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
|
+
|
|
29
30
|
## Installation
|
|
30
31
|
|
|
31
32
|
```bash
|
|
@@ -42,23 +43,23 @@ npm install simple-merge-class-names
|
|
|
42
43
|
|
|
43
44
|
### Install `Prettier` With VSCode (Most Recommended)
|
|
44
45
|
|
|
45
|
-
[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)
|
|
46
47
|
|
|
47
|
-
Or
|
|
48
|
+
- Or use an equivalent auto code formatter for your IDE.
|
|
48
49
|
|
|
49
50
|
## Usage
|
|
50
51
|
|
|
51
|
-
Import `mergeClassNames(...args)` from the package, and use it in your
|
|
52
|
+
Import `mergeClassNames(...args)` from the package, and use it in your _JSX_ or _JavaScript_ code.
|
|
52
53
|
|
|
53
54
|
```jsx
|
|
54
55
|
import { mergeClassNames } from "simple-merge-class-names";
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
const MyComponent = () => {
|
|
57
58
|
return (
|
|
58
59
|
<div
|
|
59
60
|
className={mergeClassNames(
|
|
60
61
|
"app",
|
|
61
|
-
"min-h-dvh",
|
|
62
|
+
condition ? "min-h-dvh" : false,
|
|
62
63
|
"grid",
|
|
63
64
|
"grid-rows-[auto_1fr_auto]",
|
|
64
65
|
"outline"
|
|
@@ -67,24 +68,35 @@ function MyComponent() {
|
|
|
67
68
|
Hello, world!
|
|
68
69
|
</div>
|
|
69
70
|
);
|
|
70
|
-
}
|
|
71
|
+
};
|
|
71
72
|
```
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
Or for debugging purposes to fix [console warnings](#console-warning):
|
|
75
|
+
|
|
76
|
+
- Open the _Browser's Developer Tools_ and
|
|
77
|
+
- Use `mergeClassNamesDebugger`
|
|
74
78
|
|
|
75
79
|
```jsx
|
|
76
|
-
|
|
80
|
+
import { mergeClassNamesDebugger } from "simple-merge-class-names";
|
|
81
|
+
|
|
82
|
+
const MyComponent = () => {
|
|
77
83
|
return (
|
|
78
|
-
<div
|
|
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
|
+
>
|
|
79
93
|
Hello, world!
|
|
80
94
|
</div>
|
|
81
95
|
);
|
|
82
|
-
}
|
|
96
|
+
};
|
|
83
97
|
```
|
|
84
98
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-

|
|
99
|
+
## Workflow To Minimize Typing Strain
|
|
88
100
|
|
|
89
101
|
- Have `Prettier` installed
|
|
90
102
|
- Have `Editor: Word Wrap` enabled in VS Code:
|
|
@@ -94,38 +106,192 @@ function MyComponent() {
|
|
|
94
106
|
|
|
95
107
|
`"editor.wordWrap": "on"`
|
|
96
108
|
|
|
97
|
-
- Use single quotes (<kbd>'</kbd>) for class names, often a single key press on many keyboards.
|
|
98
|
-
- Save the file (<kbd>Ctrl+S</kbd>),
|
|
99
|
-
|
|
100
|
-
-
|
|
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
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
#### Before
|
|
103
118
|
|
|
104
|
-
|
|
119
|
+

|
|
105
120
|
|
|
106
|
-
|
|
121
|
+
#### After
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+

|
|
109
124
|
|
|
110
|
-
|
|
125
|
+
## Type Definitions (of Exported Functions)
|
|
111
126
|
|
|
112
|
-
|
|
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
|
+
```
|
|
113
138
|
|
|
114
|
-
|
|
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 "`)
|
|
148
|
+
|
|
149
|
+
- The boolean value **`false`**
|
|
150
|
+
|
|
151
|
+
Everything else is an _invalid argument_ that will be _ignored_, and cause a _warning_ to be logged, these argument types include:
|
|
152
|
+
|
|
153
|
+
- _empty strings_ (`""`),
|
|
154
|
+
- _whitespace combinations_ (e.g. `"\n"`, `" \n\t "`, etc...),
|
|
155
|
+
- `null`,
|
|
156
|
+
- `undefined`,
|
|
157
|
+
- _numbers_,
|
|
158
|
+
- _objects_,
|
|
159
|
+
- _arrays_
|
|
160
|
+
|
|
161
|
+
## Console Warning
|
|
162
|
+
|
|
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:
|
|
115
164
|
|
|
116
165
|
```plaintext
|
|
117
|
-
[mergeClassNames] Warning: invalid arguments were provided and
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
]
|
|
184
|
+
```
|
|
185
|
+
|
|
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
|
|
195
|
+
|
|
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
|
+

|
|
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
|
+
};
|
|
120
283
|
```
|
|
121
284
|
|
|
122
285
|
## Testing
|
|
123
286
|
|
|
124
|
-
This project uses `Vitest` as the test runner for fast
|
|
287
|
+
This project uses `Vitest` as the test runner for fast and modern testing.
|
|
125
288
|
|
|
126
|
-
#### Run All
|
|
289
|
+
#### Run All Tests Once
|
|
127
290
|
|
|
128
291
|
```bash
|
|
292
|
+
git clone https://github.com/new-AF/simple-merge-class-names
|
|
293
|
+
cd simple-merge-class-names
|
|
294
|
+
pnpm install
|
|
129
295
|
pnpm test
|
|
130
296
|
```
|
|
131
297
|
|
|
@@ -140,56 +306,58 @@ pnpm test:watch
|
|
|
140
306
|
```javascript
|
|
141
307
|
/**
|
|
142
308
|
* mergeClassNames - A straightforward utility for merging CSS class names in React + Tailwind, and other JavaScript projects.
|
|
143
|
-
|
|
309
|
+
*
|
|
310
|
+
* @license AGPL-3.0
|
|
311
|
+
* Copyright (C) 2025 Abdullah Fatota
|
|
312
|
+
*
|
|
313
|
+
* ...
|
|
144
314
|
*/
|
|
145
315
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const isNonEmptyString = (val) => val !== "";
|
|
149
|
-
|
|
150
|
-
const partition = (array, keepPredicate) => {
|
|
151
|
-
const keep = [];
|
|
152
|
-
const ignore = [];
|
|
153
|
-
for (const element of array) {
|
|
154
|
-
(keepPredicate(element) ? keep : ignore).push(element);
|
|
155
|
-
}
|
|
156
|
-
return [keep, ignore];
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export const mergeClassNames = (...args) => {
|
|
316
|
+
const mergeClassNamesCore = ({ args, activateDebugger }) => {
|
|
160
317
|
const space = "\x20"; // ASCII code for a single space character (" "), decimal 32
|
|
161
318
|
|
|
162
|
-
const [
|
|
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);
|
|
163
324
|
|
|
164
|
-
|
|
325
|
+
/* Don't silently ignore invalid input, explicitly disclose them as it may indicate a bigger problem */
|
|
326
|
+
warnInvalidArguments({ nonStrings, emptyStrings, activateDebugger });
|
|
165
327
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
isNonEmptyString
|
|
169
|
-
);
|
|
328
|
+
return className;
|
|
329
|
+
};
|
|
170
330
|
|
|
171
|
-
|
|
331
|
+
export const mergeClassNames = (...args) =>
|
|
332
|
+
mergeClassNamesCore({ args, activateDebugger: false });
|
|
172
333
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/* ... */
|
|
334
|
+
export const mergeClassNamesDebugger = (...args) =>
|
|
335
|
+
mergeClassNamesCore({ args, activateDebugger: true });
|
|
176
336
|
```
|
|
177
337
|
|
|
178
338
|
## Misc.
|
|
179
339
|
|
|
180
|
-
###
|
|
340
|
+
### Motivation
|
|
181
341
|
|
|
182
|
-
|
|
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_.
|
|
183
343
|
|
|
184
|
-
In addition
|
|
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:
|
|
185
345
|
|
|
186
|
-
|
|
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
|
+
```
|
|
187
355
|
|
|
188
|
-
|
|
356
|
+
### Why the Mismatch Between Exported Function and Package Name?
|
|
189
357
|
|
|
190
|
-
|
|
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.
|
|
191
359
|
|
|
192
|
-
|
|
360
|
+
In addition there was already a package named `merge-class-names` but it is no longer maintained (and the developer recommends `clsx` instead).
|
|
193
361
|
|
|
194
362
|
## License
|
|
195
363
|
|
package/mergeClassNames.d.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export declare const mergeClassNames: (...args: (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;
|
package/mergeClassNames.js
CHANGED
|
@@ -41,7 +41,10 @@ const isValueFalse = (val) => val === false;
|
|
|
41
41
|
|
|
42
42
|
const isTypeString = (val) => typeof val === "string";
|
|
43
43
|
|
|
44
|
-
const
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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 (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
156
|
+
/* Full Warn makeMessage */
|
|
157
|
+
const functionName =
|
|
158
|
+
activateDebugger === false
|
|
159
|
+
? "mergeClassNames"
|
|
160
|
+
: "mergeClassNamesDebugger";
|
|
104
161
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 });
|