@kbach/react 0.6.17 → 0.6.20
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 +55 -15
- package/dist/vite-plugin.js +91 -4
- package/dist/vite-plugin.mjs +91 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,9 @@ npm install @kbach/react
|
|
|
20
20
|
|
|
21
21
|
## Setup
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
One step is always required, then pick **one** of the two setups below — they're independent, don't mix them.
|
|
24
24
|
|
|
25
|
-
### Step 1 — JSX runtime
|
|
25
|
+
### Step 1 — JSX runtime (always required)
|
|
26
26
|
|
|
27
27
|
**tsconfig.json:**
|
|
28
28
|
|
|
@@ -32,17 +32,17 @@ Two steps: configure the JSX runtime (always), then pick one styling method.
|
|
|
32
32
|
|
|
33
33
|
That's the only setting needed — Vite, Next.js, and React Router all read it. Don't *also* set `jsxImportSource` on a bundler plugin (e.g. `@vitejs/plugin-react`); one source of truth avoids conflicts.
|
|
34
34
|
|
|
35
|
-
###
|
|
36
|
-
|
|
37
|
-
Not sure which? Check your framework first:
|
|
35
|
+
### Which setup do I need?
|
|
38
36
|
|
|
39
37
|
| Framework | Use |
|
|
40
38
|
|---|---|
|
|
41
|
-
| Next.js | **
|
|
42
|
-
| React Router, framework mode | **Static CSS setup** — and skip `@vitejs/plugin-react`, see note
|
|
43
|
-
| Vite, React Router library mode, CRA, other | Either —
|
|
39
|
+
| Next.js | **[Runtime setup](#runtime-setup)** — Static CSS doesn't apply (webpack/Turbopack, not Vite) |
|
|
40
|
+
| React Router, framework mode | **[Static CSS setup](#static-css-setup)** — and skip `@vitejs/plugin-react`, see note in that section |
|
|
41
|
+
| Vite, React Router library mode, CRA, other | Either — Runtime is faster to try, Static CSS is zero-cost for production |
|
|
42
|
+
|
|
43
|
+
## Runtime setup
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Client-side CSS injection — works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …), no build plugin. This is the whole setup:
|
|
46
46
|
|
|
47
47
|
```jsx
|
|
48
48
|
import { ThemeProvider, KbachReset } from '@kbach/react';
|
|
@@ -57,11 +57,17 @@ export default function Root() {
|
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
That's it — done. `<KbachReset />` renders the base reset (see [CSS resets](#css-resets)) as real markup instead of waiting on client JS — matters most for SSR, where it avoids a flash of unstyled browser defaults before hydration. `@kbach/react` ships its own `"use client"` directive, so this works in a Next.js Server Component tree with no wrapper needed — in Next.js, render it once in the root `layout.tsx`.
|
|
61
|
+
|
|
62
|
+
Using a custom `kbach.config.js`? Pass it to `ThemeProvider` too — see [Wiring the config in](#wiring-the-config-in--required-for-both-setups).
|
|
63
|
+
|
|
64
|
+
Don't also set up Static CSS below in the same app — pick one.
|
|
65
|
+
|
|
66
|
+
## Static CSS setup
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
Vite only. A build-time plugin writes real CSS into a file you import at build time — nothing generated client-side, zero runtime cost. Three pieces, all required:
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
**1. Add the plugin:**
|
|
65
71
|
|
|
66
72
|
```ts
|
|
67
73
|
// vite.config.ts
|
|
@@ -71,6 +77,8 @@ import { kbach } from '@kbach/react/vite';
|
|
|
71
77
|
export default defineConfig({ plugins: [kbach()] });
|
|
72
78
|
```
|
|
73
79
|
|
|
80
|
+
**2. Create an empty stylesheet with the markers, and import it once:**
|
|
81
|
+
|
|
74
82
|
```css
|
|
75
83
|
/* src/kbach.css */
|
|
76
84
|
/* kbach:start */
|
|
@@ -82,16 +90,19 @@ export default defineConfig({ plugins: [kbach()] });
|
|
|
82
90
|
import './kbach.css';
|
|
83
91
|
```
|
|
84
92
|
|
|
93
|
+
**3. Wrap your app — no `<KbachReset />` here, `kbach.css` already includes the reset:**
|
|
94
|
+
|
|
85
95
|
```jsx
|
|
86
96
|
import { ThemeProvider } from '@kbach/react';
|
|
87
|
-
// No <KbachReset /> — kbach.css already includes the reset.
|
|
88
97
|
|
|
89
98
|
export default function Root() {
|
|
90
99
|
return <ThemeProvider defaultMode="system"><App /></ThemeProvider>;
|
|
91
100
|
}
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection. It also warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize as a real utility or an existing CSS rule elsewhere in the project — usually a typo.
|
|
103
|
+
Done. The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection, so there's no double-styling between this and the Runtime setup above. It also warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize as a real utility or an existing CSS rule elsewhere in the project — usually a typo.
|
|
104
|
+
|
|
105
|
+
Using a custom `kbach.config.js`? It needs to be wired in **twice** here — once to `kbach()` above (step 1) so the generated CSS reflects it, and once to `ThemeProvider` (step 3) so dark mode/`useColors()`/animations do too. See [Wiring the config in](#wiring-the-config-in--required-for-both-setups) — easy to only do one and have the other silently fall back to defaults.
|
|
95
106
|
|
|
96
107
|
**React Router framework mode:** don't add `@vitejs/plugin-react` — `reactRouter()` already provides JSX handling, and both together crash the page (`Identifier 'RefreshRuntime' has already been declared`).
|
|
97
108
|
|
|
@@ -99,7 +110,7 @@ The plugin scans your source at build time and writes CSS between the markers
|
|
|
99
110
|
// vite.config.ts
|
|
100
111
|
import { reactRouter } from '@react-router/dev/vite';
|
|
101
112
|
import { defineConfig } from 'vite';
|
|
102
|
-
import { kbach } from '@kbach/react/vite'; // omit if using
|
|
113
|
+
import { kbach } from '@kbach/react/vite'; // omit if using Runtime setup instead
|
|
103
114
|
|
|
104
115
|
export default defineConfig({ plugins: [kbach(), reactRouter()] });
|
|
105
116
|
```
|
|
@@ -296,6 +307,35 @@ module.exports = {
|
|
|
296
307
|
- Colors can alias each other: `primary: 'blue-6'`, `brand: { 6: 'primary' }`
|
|
297
308
|
- Runtime update: `updateConfig({ extend: { ... } }); clearCache();`
|
|
298
309
|
|
|
310
|
+
### Wiring the config in — required for both setups
|
|
311
|
+
|
|
312
|
+
`kbach.config.js` isn't picked up automatically. It has to be imported and passed in explicitly, and **where** depends on which of the two things it affects:
|
|
313
|
+
|
|
314
|
+
- **Runtime** — dark mode strategy, `useColors()`, custom `@keyframes`/`animation`, and (for SSR) the default-font fallback rendered before your stylesheet takes over. Needed by **both** [Runtime setup](#runtime-setup) and [Static CSS setup](#static-css-setup) — pass it to `ThemeProvider`:
|
|
315
|
+
|
|
316
|
+
```jsx
|
|
317
|
+
import { ThemeProvider } from '@kbach/react';
|
|
318
|
+
import kbachConfig from '../kbach.config';
|
|
319
|
+
|
|
320
|
+
<ThemeProvider defaultMode="system" config={kbachConfig}>
|
|
321
|
+
<App />
|
|
322
|
+
</ThemeProvider>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
(Equivalent to calling `updateConfig(kbachConfig)` once before anything renders — `ThemeProvider`'s `config` prop does this for you and keeps the global store in sync if it ever changes.)
|
|
326
|
+
|
|
327
|
+
- **Build-time** — what the Vite plugin actually scans your source against and generates CSS for. Only relevant to [Static CSS setup](#static-css-setup) — pass it to the plugin itself:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
// vite.config.ts
|
|
331
|
+
import { kbach } from '@kbach/react/vite';
|
|
332
|
+
import kbachConfig from './kbach.config';
|
|
333
|
+
|
|
334
|
+
export default defineConfig({ plugins: [kbach(kbachConfig)] });
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Skipping the runtime one is an easy mistake under Static CSS setup specifically — the generated `kbach.css` will look correct (it *does* have your customizations baked in), while dark mode, `useColors()`, and custom animations silently fall back to Kbach's defaults instead of your config, since nothing ever told the running app what you'd customized.
|
|
338
|
+
|
|
299
339
|
## Full reference
|
|
300
340
|
|
|
301
341
|
[kbach-react.md](./kbach-react.md) — complete utility list, every modifier, all config options.
|
package/dist/vite-plugin.js
CHANGED
|
@@ -3176,9 +3176,96 @@ function buildColorVarMap(theme, usedCSS) {
|
|
|
3176
3176
|
}
|
|
3177
3177
|
return { replacements, declarations };
|
|
3178
3178
|
}
|
|
3179
|
+
var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
3180
|
+
"padding",
|
|
3181
|
+
"padding-top",
|
|
3182
|
+
"padding-right",
|
|
3183
|
+
"padding-bottom",
|
|
3184
|
+
"padding-left",
|
|
3185
|
+
"margin",
|
|
3186
|
+
"margin-top",
|
|
3187
|
+
"margin-right",
|
|
3188
|
+
"margin-bottom",
|
|
3189
|
+
"margin-left",
|
|
3190
|
+
"gap",
|
|
3191
|
+
"column-gap",
|
|
3192
|
+
"row-gap",
|
|
3193
|
+
"width",
|
|
3194
|
+
"height",
|
|
3195
|
+
"min-width",
|
|
3196
|
+
"min-height",
|
|
3197
|
+
"max-width",
|
|
3198
|
+
"max-height",
|
|
3199
|
+
"top",
|
|
3200
|
+
"right",
|
|
3201
|
+
"bottom",
|
|
3202
|
+
"left",
|
|
3203
|
+
"flex-basis"
|
|
3204
|
+
]);
|
|
3205
|
+
function buildSpacingVarMap(theme, usedCSS) {
|
|
3206
|
+
const pxToKey = /* @__PURE__ */ new Map();
|
|
3207
|
+
for (const [key, val] of Object.entries(theme.spacing ?? {})) {
|
|
3208
|
+
if (typeof val === "number" && !pxToKey.has(val)) pxToKey.set(val, key);
|
|
3209
|
+
}
|
|
3210
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3211
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3212
|
+
const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
|
|
3213
|
+
const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
|
|
3214
|
+
let m;
|
|
3215
|
+
while ((m = declRe.exec(usedCSS)) !== null) {
|
|
3216
|
+
const [, prop, numStr] = m;
|
|
3217
|
+
if (!SPACING_PROPS.has(prop)) continue;
|
|
3218
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3219
|
+
if (key === void 0) continue;
|
|
3220
|
+
const varName = varNameFor(key);
|
|
3221
|
+
replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
|
|
3222
|
+
declarations.set(varName, `${numStr}px`);
|
|
3223
|
+
}
|
|
3224
|
+
const transformRe = /(translateX|translateY)\((\d+(?:\.\d+)?)px\)/g;
|
|
3225
|
+
while ((m = transformRe.exec(usedCSS)) !== null) {
|
|
3226
|
+
const [, fn, numStr] = m;
|
|
3227
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3228
|
+
if (key === void 0) continue;
|
|
3229
|
+
const varName = varNameFor(key);
|
|
3230
|
+
replacements.set(`${fn}(${numStr}px)`, `${fn}(var(${varName}))`);
|
|
3231
|
+
declarations.set(varName, `${numStr}px`);
|
|
3232
|
+
}
|
|
3233
|
+
return { replacements, declarations };
|
|
3234
|
+
}
|
|
3235
|
+
function prettifyCSS(css) {
|
|
3236
|
+
const out = [];
|
|
3237
|
+
let depth = 0;
|
|
3238
|
+
let current = "";
|
|
3239
|
+
for (const ch of css) {
|
|
3240
|
+
if (ch === "{") {
|
|
3241
|
+
const selector = current.trim();
|
|
3242
|
+
if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
|
|
3243
|
+
depth++;
|
|
3244
|
+
current = "";
|
|
3245
|
+
} else if (ch === "}") {
|
|
3246
|
+
const decl = current.trim();
|
|
3247
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3248
|
+
depth = Math.max(0, depth - 1);
|
|
3249
|
+
out.push(`${" ".repeat(depth)}}`);
|
|
3250
|
+
current = "";
|
|
3251
|
+
} else if (ch === ";") {
|
|
3252
|
+
const decl = current.trim();
|
|
3253
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3254
|
+
current = "";
|
|
3255
|
+
} else {
|
|
3256
|
+
current += ch;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
const trailing = current.trim();
|
|
3260
|
+
if (trailing) out.push(trailing);
|
|
3261
|
+
return out.join("\n");
|
|
3262
|
+
}
|
|
3179
3263
|
function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
3180
3264
|
const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
|
|
3181
|
-
const
|
|
3265
|
+
const colorVars = buildColorVarMap(theme, allRaw);
|
|
3266
|
+
const spacingVars = buildSpacingVarMap(theme, allRaw);
|
|
3267
|
+
const replacements = new Map([...colorVars.replacements, ...spacingVars.replacements]);
|
|
3268
|
+
const declarations = new Map([...colorVars.declarations, ...spacingVars.declarations]);
|
|
3182
3269
|
const sortedReplacements = [...replacements].sort(([a], [b]) => b.length - a.length);
|
|
3183
3270
|
function applyVars(css) {
|
|
3184
3271
|
for (const [pattern, replacement] of sortedReplacements)
|
|
@@ -3190,7 +3277,7 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
|
3190
3277
|
if (!css) continue;
|
|
3191
3278
|
const label = classifyToken(token, responsiveRe);
|
|
3192
3279
|
if (!groups.has(label)) groups.set(label, []);
|
|
3193
|
-
groups.get(label).push(applyVars(css));
|
|
3280
|
+
groups.get(label).push(prettifyCSS(applyVars(css)));
|
|
3194
3281
|
}
|
|
3195
3282
|
const out = ["/* Generated by Kbach \u2014 do not edit */"];
|
|
3196
3283
|
if (declarations.size > 0) {
|
|
@@ -3201,13 +3288,13 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
|
3201
3288
|
const globalCSS = buildThemeGlobal(theme);
|
|
3202
3289
|
if (globalCSS) out.push(`
|
|
3203
3290
|
/* Global */
|
|
3204
|
-
${globalCSS}`);
|
|
3291
|
+
${prettifyCSS(globalCSS)}`);
|
|
3205
3292
|
for (const label of GROUP_ORDER) {
|
|
3206
3293
|
const items = groups.get(label) ?? [];
|
|
3207
3294
|
if (!items.length) continue;
|
|
3208
3295
|
out.push(`
|
|
3209
3296
|
/* ${label} */`);
|
|
3210
|
-
out.push(items.join("\n"));
|
|
3297
|
+
out.push(items.join("\n\n"));
|
|
3211
3298
|
}
|
|
3212
3299
|
return out.join("\n");
|
|
3213
3300
|
}
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -3142,9 +3142,96 @@ function buildColorVarMap(theme, usedCSS) {
|
|
|
3142
3142
|
}
|
|
3143
3143
|
return { replacements, declarations };
|
|
3144
3144
|
}
|
|
3145
|
+
var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
3146
|
+
"padding",
|
|
3147
|
+
"padding-top",
|
|
3148
|
+
"padding-right",
|
|
3149
|
+
"padding-bottom",
|
|
3150
|
+
"padding-left",
|
|
3151
|
+
"margin",
|
|
3152
|
+
"margin-top",
|
|
3153
|
+
"margin-right",
|
|
3154
|
+
"margin-bottom",
|
|
3155
|
+
"margin-left",
|
|
3156
|
+
"gap",
|
|
3157
|
+
"column-gap",
|
|
3158
|
+
"row-gap",
|
|
3159
|
+
"width",
|
|
3160
|
+
"height",
|
|
3161
|
+
"min-width",
|
|
3162
|
+
"min-height",
|
|
3163
|
+
"max-width",
|
|
3164
|
+
"max-height",
|
|
3165
|
+
"top",
|
|
3166
|
+
"right",
|
|
3167
|
+
"bottom",
|
|
3168
|
+
"left",
|
|
3169
|
+
"flex-basis"
|
|
3170
|
+
]);
|
|
3171
|
+
function buildSpacingVarMap(theme, usedCSS) {
|
|
3172
|
+
const pxToKey = /* @__PURE__ */ new Map();
|
|
3173
|
+
for (const [key, val] of Object.entries(theme.spacing ?? {})) {
|
|
3174
|
+
if (typeof val === "number" && !pxToKey.has(val)) pxToKey.set(val, key);
|
|
3175
|
+
}
|
|
3176
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3177
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3178
|
+
const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
|
|
3179
|
+
const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
|
|
3180
|
+
let m;
|
|
3181
|
+
while ((m = declRe.exec(usedCSS)) !== null) {
|
|
3182
|
+
const [, prop, numStr] = m;
|
|
3183
|
+
if (!SPACING_PROPS.has(prop)) continue;
|
|
3184
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3185
|
+
if (key === void 0) continue;
|
|
3186
|
+
const varName = varNameFor(key);
|
|
3187
|
+
replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
|
|
3188
|
+
declarations.set(varName, `${numStr}px`);
|
|
3189
|
+
}
|
|
3190
|
+
const transformRe = /(translateX|translateY)\((\d+(?:\.\d+)?)px\)/g;
|
|
3191
|
+
while ((m = transformRe.exec(usedCSS)) !== null) {
|
|
3192
|
+
const [, fn, numStr] = m;
|
|
3193
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3194
|
+
if (key === void 0) continue;
|
|
3195
|
+
const varName = varNameFor(key);
|
|
3196
|
+
replacements.set(`${fn}(${numStr}px)`, `${fn}(var(${varName}))`);
|
|
3197
|
+
declarations.set(varName, `${numStr}px`);
|
|
3198
|
+
}
|
|
3199
|
+
return { replacements, declarations };
|
|
3200
|
+
}
|
|
3201
|
+
function prettifyCSS(css) {
|
|
3202
|
+
const out = [];
|
|
3203
|
+
let depth = 0;
|
|
3204
|
+
let current = "";
|
|
3205
|
+
for (const ch of css) {
|
|
3206
|
+
if (ch === "{") {
|
|
3207
|
+
const selector = current.trim();
|
|
3208
|
+
if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
|
|
3209
|
+
depth++;
|
|
3210
|
+
current = "";
|
|
3211
|
+
} else if (ch === "}") {
|
|
3212
|
+
const decl = current.trim();
|
|
3213
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3214
|
+
depth = Math.max(0, depth - 1);
|
|
3215
|
+
out.push(`${" ".repeat(depth)}}`);
|
|
3216
|
+
current = "";
|
|
3217
|
+
} else if (ch === ";") {
|
|
3218
|
+
const decl = current.trim();
|
|
3219
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3220
|
+
current = "";
|
|
3221
|
+
} else {
|
|
3222
|
+
current += ch;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
const trailing = current.trim();
|
|
3226
|
+
if (trailing) out.push(trailing);
|
|
3227
|
+
return out.join("\n");
|
|
3228
|
+
}
|
|
3145
3229
|
function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
3146
3230
|
const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
|
|
3147
|
-
const
|
|
3231
|
+
const colorVars = buildColorVarMap(theme, allRaw);
|
|
3232
|
+
const spacingVars = buildSpacingVarMap(theme, allRaw);
|
|
3233
|
+
const replacements = new Map([...colorVars.replacements, ...spacingVars.replacements]);
|
|
3234
|
+
const declarations = new Map([...colorVars.declarations, ...spacingVars.declarations]);
|
|
3148
3235
|
const sortedReplacements = [...replacements].sort(([a], [b]) => b.length - a.length);
|
|
3149
3236
|
function applyVars(css) {
|
|
3150
3237
|
for (const [pattern, replacement] of sortedReplacements)
|
|
@@ -3156,7 +3243,7 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
|
3156
3243
|
if (!css) continue;
|
|
3157
3244
|
const label = classifyToken(token, responsiveRe);
|
|
3158
3245
|
if (!groups.has(label)) groups.set(label, []);
|
|
3159
|
-
groups.get(label).push(applyVars(css));
|
|
3246
|
+
groups.get(label).push(prettifyCSS(applyVars(css)));
|
|
3160
3247
|
}
|
|
3161
3248
|
const out = ["/* Generated by Kbach \u2014 do not edit */"];
|
|
3162
3249
|
if (declarations.size > 0) {
|
|
@@ -3167,13 +3254,13 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
|
3167
3254
|
const globalCSS = buildThemeGlobal(theme);
|
|
3168
3255
|
if (globalCSS) out.push(`
|
|
3169
3256
|
/* Global */
|
|
3170
|
-
${globalCSS}`);
|
|
3257
|
+
${prettifyCSS(globalCSS)}`);
|
|
3171
3258
|
for (const label of GROUP_ORDER) {
|
|
3172
3259
|
const items = groups.get(label) ?? [];
|
|
3173
3260
|
if (!items.length) continue;
|
|
3174
3261
|
out.push(`
|
|
3175
3262
|
/* ${label} */`);
|
|
3176
|
-
out.push(items.join("\n"));
|
|
3263
|
+
out.push(items.join("\n\n"));
|
|
3177
3264
|
}
|
|
3178
3265
|
return out.join("\n");
|
|
3179
3266
|
}
|