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 +237 -93
- package/mergeClassNames.d.ts +5 -1
- package/mergeClassNames.js +123 -47
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
import { mergeClassNamesDebugger } from "simple-merge-class-names";
|
|
81
|
+
|
|
82
|
+
const MyComponent = () => {
|
|
79
83
|
return (
|
|
80
|
-
<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
|
+
>
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
### Workflow To Minimize Typing Strain
|
|
104
|
-
|
|
105
|
-

|
|
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>),
|
|
117
|
-
|
|
118
|
-
-
|
|
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
|
+

|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
#### After
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+

|
|
123
124
|
|
|
124
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
## Console Warning
|
|
133
162
|
|
|
134
|
-
Example
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
283
|
+
```
|
|
145
284
|
|
|
146
285
|
## Testing
|
|
147
286
|
|
|
148
|
-
This project uses `Vitest` as the test runner for fast
|
|
287
|
+
This project uses `Vitest` as the test runner for fast and modern testing.
|
|
149
288
|
|
|
150
|
-
#### Run All
|
|
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
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
isNonEmptyString
|
|
193
|
-
);
|
|
328
|
+
return className;
|
|
329
|
+
};
|
|
194
330
|
|
|
195
|
-
|
|
331
|
+
export const mergeClassNames = (...args) =>
|
|
332
|
+
mergeClassNamesCore({ args, activateDebugger: false });
|
|
196
333
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
/* ... */
|
|
334
|
+
export const mergeClassNamesDebugger = (...args) =>
|
|
335
|
+
mergeClassNamesCore({ args, activateDebugger: true });
|
|
200
336
|
```
|
|
201
337
|
|
|
202
338
|
## Misc.
|
|
203
339
|
|
|
204
|
-
###
|
|
340
|
+
### Motivation
|
|
205
341
|
|
|
206
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
356
|
+
### Why the Mismatch Between Exported Function and Package Name?
|
|
213
357
|
|
|
214
|
-
|
|
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
|
-
|
|
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
|
|
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 });
|