@navecss/core 0.3.0 → 0.5.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/CONSUMER-ATOMS.md +15 -11
- package/LICENSE +22 -0
- package/README.md +285 -30
- package/dist/atom-names-B8Ud-7zD.d.ts +28 -0
- package/dist/atoms.d.ts +4 -13
- package/dist/atoms.js +2 -1
- package/dist/bin.js +101 -16
- package/dist/check.d.ts +2 -1
- package/dist/check.js +4 -3
- package/dist/{chunk-NFS2REPQ.js → chunk-2K5EBBF2.js} +1 -191
- package/dist/chunk-2TNTYI4L.js +7 -0
- package/dist/{chunk-R6JJZGXC.js → chunk-63H2FLGU.js} +3 -2
- package/dist/chunk-BCUK4M4P.js +2765 -0
- package/dist/{chunk-XQQCDUV6.js → chunk-CTFEYJBE.js} +1 -1
- package/dist/{chunk-LIUB4EES.js → chunk-CUL5PLPI.js} +1 -5
- package/dist/chunk-GZL2VW7C.js +57 -0
- package/dist/{chunk-IZQLFUQU.js → chunk-KKUYU75D.js} +9 -8
- package/dist/chunk-MWHZ26S4.js +4059 -0
- package/dist/chunk-SYNQJNVB.js +302 -0
- package/dist/chunk-UVHGEVMV.js +204 -0
- package/dist/{chunk-UANF3FGO.js → chunk-ZNTNLGEC.js} +2 -1
- package/dist/collect.d.ts +317 -0
- package/dist/collect.js +5 -0
- package/dist/css.d.ts +1 -0
- package/dist/cx.d.ts +1 -1
- package/dist/cx.js +14 -1
- package/dist/lightningcss.d.ts +3 -1
- package/dist/lightningcss.js +10 -5
- package/dist/postcss.d.ts +29 -2
- package/dist/postcss.js +334 -82
- package/dist/standalone.css +4 -0
- package/dist/vite-options-sOCeGn9B.d.ts +87 -0
- package/dist/vite.d.ts +27 -68
- package/dist/vite.js +1344 -3176
- package/package.json +58 -24
- package/skills/navecss/SKILL.md +16 -0
- package/dist/chunk-S7DZUVV3.js +0 -1386
package/CONSUMER-ATOMS.md
CHANGED
|
@@ -122,20 +122,22 @@ Compiles to:
|
|
|
122
122
|
user-select: none;
|
|
123
123
|
-webkit-tap-highlight-color: transparent;
|
|
124
124
|
outline: none;
|
|
125
|
-
transition-property: color, background-color, border-color, opacity, box-shadow;
|
|
126
|
-
transition-duration: var(--nave-motion-duration-base);
|
|
127
|
-
transition-timing-function: var(--nave-motion-easing-standard);
|
|
128
|
-
background: var(--nave-color-action-primary);
|
|
129
|
-
color: var(--nave-color-on-action-primary);
|
|
130
|
-
padding: var(--nave-spacing-control-md) var(--nave-spacing-control-lg);
|
|
131
|
-
border-radius: var(--nave-radius-control);
|
|
132
|
-
font-weight: var(--nave-font-weight-medium);
|
|
133
125
|
&:focus-visible {
|
|
134
126
|
outline-style: solid;
|
|
135
127
|
outline-width: var(--nave-border-width-focus, 2px);
|
|
136
128
|
outline-color: var(--nave-color-border-focus, currentColor);
|
|
137
129
|
outline-offset: 2px;
|
|
138
130
|
}
|
|
131
|
+
& {
|
|
132
|
+
transition-property: color, background-color, border-color, opacity, box-shadow;
|
|
133
|
+
transition-duration: var(--nave-motion-duration-base);
|
|
134
|
+
transition-timing-function: var(--nave-motion-easing-standard);
|
|
135
|
+
background: var(--nave-color-action-primary);
|
|
136
|
+
color: var(--nave-color-on-action-primary);
|
|
137
|
+
padding: var(--nave-spacing-control-md) var(--nave-spacing-control-lg);
|
|
138
|
+
border-radius: var(--nave-radius-control);
|
|
139
|
+
font-weight: var(--nave-font-weight-medium);
|
|
140
|
+
}
|
|
139
141
|
&:hover {
|
|
140
142
|
background: var(--nave-color-action-primary-hover);
|
|
141
143
|
}
|
|
@@ -293,15 +295,17 @@ Compiles to:
|
|
|
293
295
|
display: flex;
|
|
294
296
|
flex-direction: column;
|
|
295
297
|
gap: var(--nave-spacing-content-md);
|
|
296
|
-
background: var(--nave-color-surface-raised);
|
|
297
|
-
border-radius: var(--nave-radius-card);
|
|
298
|
-
padding: var(--nave-spacing-content-md);
|
|
299
298
|
@container (width >= 28rem) {
|
|
300
299
|
& {
|
|
301
300
|
flex-direction: row;
|
|
302
301
|
align-items: center;
|
|
303
302
|
}
|
|
304
303
|
}
|
|
304
|
+
& {
|
|
305
|
+
background: var(--nave-color-surface-raised);
|
|
306
|
+
border-radius: var(--nave-radius-card);
|
|
307
|
+
padding: var(--nave-spacing-content-md);
|
|
308
|
+
}
|
|
305
309
|
}
|
|
306
310
|
}
|
|
307
311
|
```
|
package/LICENSE
CHANGED
|
@@ -55,3 +55,25 @@ JavaScript under dist/: the member names of a version 3 source map, the
|
|
|
55
55
|
layout of its mappings string, the base64 VLQ encoding of each field in it,
|
|
56
56
|
and the base64 alphabet. The code implementing them is this package's own, and
|
|
57
57
|
everything in this package is offered under the MIT licence above.
|
|
58
|
+
|
|
59
|
+
This package also includes material copied from or derived from mdn-data
|
|
60
|
+
(css/properties.json in the mdn/data repository, version 2.37.2,
|
|
61
|
+
https://github.com/mdn/data), which its authors dedicated to the public domain
|
|
62
|
+
under CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/),
|
|
63
|
+
and from CSS Logical Properties and Values Module Level 1, CSS Overflow Module
|
|
64
|
+
Level 3, CSS Overscroll Behavior Module Level 1, CSS Scroll Snap Module Level 1,
|
|
65
|
+
CSS Box Sizing Module Level 4, CSS Fonts Module Level 4, CSS Text Module Level
|
|
66
|
+
4, CSS Gaps Module Level 1, CSS Fragmentation Module Level 4, CSS Backgrounds
|
|
67
|
+
and Borders Module Level 3, and CSS Cascading and Inheritance Module Level 5 (in
|
|
68
|
+
the w3c/csswg-drafts repository, at commit
|
|
69
|
+
40527902c4d0840cdb7a808c8cd42302be1c3bc8). Copyright (c) 2026 World Wide Web
|
|
70
|
+
Consortium. Those specifications, and the repository carrying them, are
|
|
71
|
+
published under the W3C Software and Document License
|
|
72
|
+
(https://www.w3.org/copyright/software-license-2023/).
|
|
73
|
+
|
|
74
|
+
What is copied or derived is implemented in
|
|
75
|
+
src/directive/property-table.generated.ts, built into the JavaScript under
|
|
76
|
+
dist/: the names of CSS properties, the properties each shorthand sets, which
|
|
77
|
+
names are legacy name aliases of another property, and which logical and
|
|
78
|
+
physical properties belong to one group. The code using them is this package's
|
|
79
|
+
own, and everything in this package is offered under the MIT licence above.
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ pnpm add @navecss/core
|
|
|
21
21
|
included, is reachable only through the package's `exports` map: there is no
|
|
22
22
|
`main` field to fall back on.
|
|
23
23
|
- **ES modules.** `@navecss/core/cx`, `@navecss/core/atoms`,
|
|
24
|
-
`@navecss/core/vite`, `@navecss/core/lightningcss` and `@navecss/core/
|
|
24
|
+
`@navecss/core/vite`, `@navecss/core/lightningcss`, `@navecss/core/check` and `@navecss/core/collect` load through `import` only. A `require()` of any of
|
|
25
25
|
them fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`, which reads as though the
|
|
26
26
|
entry point did not exist. In TypeScript, set `moduleResolution` to `bundler`,
|
|
27
27
|
`node16` or `nodenext`; under `node10` their types are not found. A CommonJS
|
|
@@ -107,10 +107,141 @@ Requires the Vite plugin or the PostCSS plugin. Everything stays in CSS files.
|
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
Declarations are inlined at build time, and nothing of `@nave` is left at run time.
|
|
110
|
-
Pseudo-class rules (`:focus-visible` from `focusRing`), `@media` and
|
|
111
|
-
|
|
112
|
-
apply to every selector in a selector list. Native nesting is inside the browser
|
|
113
|
-
|
|
110
|
+
Pseudo-class rules (`:focus-visible` from `focusRing`), `@media` and `@container` blocks are
|
|
111
|
+
emitted as native CSS nesting inside your rule, each right after its own atom's declarations, so
|
|
112
|
+
they apply to every selector in a selector list. Native nesting is inside the browser floor under
|
|
113
|
+
[Requirements](#requirements), and [Order inside a rule](#order-inside-a-rule) says what wins when
|
|
114
|
+
your rule sets the same property.
|
|
115
|
+
|
|
116
|
+
#### Order inside a rule
|
|
117
|
+
|
|
118
|
+
`@nave a b;` expands where you write it, in the order CSS mixins will apply
|
|
119
|
+
`@apply --a; @apply --b;`: the declarations of `a`, then the pseudo-class,
|
|
120
|
+
`@media` and `@container` blocks of `a`, then the same for `b`. Whatever your rule
|
|
121
|
+
says after the directive comes after all of it. So of two declarations of one
|
|
122
|
+
property that both apply, the later one wins, unless the earlier one has the more
|
|
123
|
+
specific selector (an atom's pseudo-class block, such as the `:focus-visible` of
|
|
124
|
+
`focusRing`, does), as anywhere else in CSS.
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
.panel {
|
|
128
|
+
@nave flex hideDesktopUp;
|
|
129
|
+
gap: var(--nave-spacing-content-md);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
expands to:
|
|
134
|
+
|
|
135
|
+
```css
|
|
136
|
+
.panel {
|
|
137
|
+
display: flex;
|
|
138
|
+
@media (width >= 75em) {
|
|
139
|
+
& {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
& {
|
|
144
|
+
gap: var(--nave-spacing-content-md);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A declaration that follows a nested block is written inside `& { }`, the wrapper
|
|
150
|
+
Nave already uses for a directive written after a nested rule. Under a selector
|
|
151
|
+
list, `&` matches with the specificity of the list's most specific selector, so a
|
|
152
|
+
wrapped declaration can win over a rule elsewhere that a bare one would lose to;
|
|
153
|
+
write it above the directive if that matters.
|
|
154
|
+
|
|
155
|
+
The order you write is the order that wins:
|
|
156
|
+
|
|
157
|
+
| You write | From 75em up |
|
|
158
|
+
| ------------------------------------- | ---------------- |
|
|
159
|
+
| `@nave flex hideDesktopUp;` | hidden |
|
|
160
|
+
| `@nave hideDesktopUp flex;` | shown, as flex |
|
|
161
|
+
| `display: grid; @nave hideDesktopUp;` | hidden |
|
|
162
|
+
| `@nave hideDesktopUp; display: grid;` | shown, as a grid |
|
|
163
|
+
|
|
164
|
+
Name a responsive or state atom after the atoms it adjusts, and write a declaration
|
|
165
|
+
that should give way to an atom's variants above the directive. A declaration or a
|
|
166
|
+
nested block below the directive is how you customize an atom inside your own rule.
|
|
167
|
+
|
|
168
|
+
The same order applies to the focus atoms. After `@nave focusRing;`, a later
|
|
169
|
+
`&:focus-visible { outline-color: var(--brand) }` recolours the ring, and a later
|
|
170
|
+
`&:focus { outline: none }` or `&:focus-visible { outline: none }` removes it,
|
|
171
|
+
which leaves the element with no focus indicator from `focusRing` under keyboard
|
|
172
|
+
focus. After `@nave srOnlyFocusable;`, a later `&:focus-visible { position: fixed }`
|
|
173
|
+
places the revealed element, and a later `clip-path: inset(50%)` in the same state
|
|
174
|
+
hides it again, so a skip link stays hidden when it has focus. To keep the atom's
|
|
175
|
+
result, write the block above the directive. The order warning below reports each
|
|
176
|
+
of these, and says so when the ring or the reveal is lost.
|
|
177
|
+
|
|
178
|
+
**The order warning.** Where something later in the rule overrides one of an atom's
|
|
179
|
+
pseudo-class, `@media` or `@container` blocks with a different value, the build
|
|
180
|
+
prints a warning at the later content. Where moving something lets the atom win
|
|
181
|
+
without a new warning, it says what to move, and it always shows how to mark the
|
|
182
|
+
directive if the later value is meant to win:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
src/panel.css:3:3: @nave: display: grid comes after "hideDesktopUp", so under @media (width >= 75em) display is grid, not none. Before @navecss/core 0.5.0 "hideDesktopUp" won, because an atom's blocks were placed last in the rule. To let "hideDesktopUp" win, move display: grid above the directive. If it is meant to win, mark the directive that names "hideDesktopUp": `@nave /* nave-allow-override: display */ hideDesktopUp;`
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Where no move clears the warning, as when the later value comes from another atom's
|
|
189
|
+
block and moving either only turns the pair round, it prints only the marker. Write
|
|
190
|
+
the atom that should win last, and mark the directive the warning then names.
|
|
191
|
+
|
|
192
|
+
It never fails the build, and `onUnknown` does not change it. The Vite plugin prints
|
|
193
|
+
it through Vite's logger, the PostCSS plugin as a PostCSS warning (and with `console.warn` on
|
|
194
|
+
Next.js under Turbopack, which does not print PostCSS warnings), the Lightning CSS
|
|
195
|
+
adapter with `console.warn`, and `navecss-core expand` on standard error, without
|
|
196
|
+
changing its exit code. No option turns it off for the whole project.
|
|
197
|
+
|
|
198
|
+
A directive written inside a nested rule that applies to the same element, such as
|
|
199
|
+
`.card { & { @nave hideDesktopUp } display: flex; }`, is checked the same way: its atoms' blocks
|
|
200
|
+
are compared with what follows the directive inside that nested rule, and with what each
|
|
201
|
+
enclosing rule holds after the nested rule. A warning of the second kind says nothing about
|
|
202
|
+
earlier releases, because that later content already won in them.
|
|
203
|
+
|
|
204
|
+
**Marking an override you mean.** Name the property in a comment inside the
|
|
205
|
+
directive:
|
|
206
|
+
|
|
207
|
+
```css
|
|
208
|
+
.panel {
|
|
209
|
+
@nave /* nave-allow-override: display */ hideDesktopUp;
|
|
210
|
+
display: grid;
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The comment says that what comes after this directive's atoms (a later atom in it,
|
|
215
|
+
or your rule's own content) is meant to win for the properties it names. It
|
|
216
|
+
silences the warning for those properties on this directive and nothing else: an
|
|
217
|
+
override of another property, or of another directive's atoms, still warns. A
|
|
218
|
+
shorthand covers its longhands (`outline` covers `outline-color`). You can add a
|
|
219
|
+
reason after `--`: `/* nave-allow-override: display -- a grid on wide screens */`.
|
|
220
|
+
The head `nave-allow-override:` is matched in lower case only, as `eslint-disable`
|
|
221
|
+
is, so a comment that writes it in another case is not a marker: it allows nothing,
|
|
222
|
+
and the warning it was meant to silence still prints.
|
|
223
|
+
The comment never reaches your CSS. Only a comment inside the directive is read,
|
|
224
|
+
because some build steps remove the others before Nave sees the stylesheet (Vite
|
|
225
|
+
under `css.transformer: 'lightningcss'`, for one). A marker naming a property that
|
|
226
|
+
nothing overrides any more is reported, so remove it when you reorder. The Vite plugin under
|
|
227
|
+
`css.transformer: 'lightningcss'` does not report one, at any targets, because it cannot tell
|
|
228
|
+
whether Lightning CSS has already moved what follows the directive.
|
|
229
|
+
|
|
230
|
+
#### Output size
|
|
231
|
+
|
|
232
|
+
`@nave` copies an atom's declarations into every rule that uses it. That is the design: the directive is a build-time stand-in for native CSS mixins, whose `@apply` puts a mixin's declarations into the rule that applies it ([why](https://github.com/navecss/navecss/blob/main/docs/04-adr/0006-nave-directive-stands-in-for-css-mixins.md)). A browser that supports native mixins does that expansion itself, so such a stylesheet carries each mixin's body once, plus a short `@apply` where it is used; a build step has to write the copies out.
|
|
233
|
+
|
|
234
|
+
What the copies cost, in a model of a large app (10,000 component rules using a 25-atom design system and class names of about 20 characters, minified): about 70 percent of the raw CSS, 40 percent after gzip and 30 percent after brotli at level 11. Your app will differ, so measure your own bundle. What reduces it:
|
|
235
|
+
|
|
236
|
+
- **Compress CSS ahead of time with brotli at its highest level (11)**, instead of compressing on the fly at a lower one. In the model, level 11 sent 260 KB where level 5 sent 330 KB, which saves more than any way of removing the copies that was tried (below).
|
|
237
|
+
- **Split CSS by route**, so a page loads a shared part and its own rules rather than the whole app's. Split into one shared part and 40 routes, a page in the model loaded 65 KB at level 11, about a quarter, though all the parts together came to 348 KB, a third more than the single bundle.
|
|
238
|
+
|
|
239
|
+
What Nave does not do, and why:
|
|
240
|
+
|
|
241
|
+
- **It does not merge the copies into shared rules with combined selector lists.** In the model that saved 0.3 percent under brotli at level 11 and made gzip output 19 percent larger, because each selector then repeats once per atom, far apart. With 7-character class names the same merge saved 2 percent under gzip, 4 under brotli at level 11 and 12 under brotli at level 5, still less than level 11 saves over level 5. It also changed the computed style of 4 to 6 percent of the elements sampled: two atoms that set the same property stop resolving in the order you wrote them, and an atom's `@media` blocks move relative to your own declarations.
|
|
242
|
+
- **It does not turn your `@nave` atoms into shared classes for your markup to carry** (`cx()` does that for Nave's built-in atoms, where you choose it). That would move them into the `atomic` layer, below your component CSS, so your other rules and Nave's own component styles start beating them, and the class names it would add to your JavaScript and HTML cost about as much as the CSS it would remove under brotli, and about half as much under gzip.
|
|
243
|
+
|
|
244
|
+
The lasting fix is native CSS mixins. Once they ship in a browser and the specification settles, Nave adds a flag that writes `@mixin` and `@apply`, and once all three engines support them that becomes the default.
|
|
114
245
|
|
|
115
246
|
### Escape hatch — cx() utility
|
|
116
247
|
|
|
@@ -193,7 +324,8 @@ Every custom property Nave declares is in the stylesheet you already installed,
|
|
|
193
324
|
|
|
194
325
|
## Setting up `@nave`
|
|
195
326
|
|
|
196
|
-
On Vite, use the Vite plugin: it is the one route this package teaches for Vite
|
|
327
|
+
On Vite, use the Vite plugin: it is the one route this package teaches for Vite, and for Astro,
|
|
328
|
+
which builds with Vite ([Astro](#astro) has its config). On
|
|
197
329
|
Next.js, webpack, or any other pipeline that runs PostCSS plugins, use the PostCSS plugin. A host
|
|
198
330
|
that runs Lightning CSS directly uses [the Lightning CSS adapter](#lightning-css-adapter-setup),
|
|
199
331
|
and a project with no bundler uses [`navecss-core expand`](#navecss-core-expand).
|
|
@@ -218,8 +350,8 @@ It has no dependency and no peer. It expands `@nave` after Vite's own CSS step h
|
|
|
218
350
|
through `@import`, a Sass file (including a `@mixin` that holds a directive), a CSS Module, an
|
|
219
351
|
`?inline` or `?url` import, a Vue or Svelte style block and a `.css` inside `node_modules` are all
|
|
220
352
|
read as the CSS they compile to, in `vite build` and in the dev server, under either
|
|
221
|
-
`css.transformer`. A `?raw` import returns the file's text as it is. Astro
|
|
222
|
-
|
|
353
|
+
`css.transformer`. A `?raw` import returns the file's text as it is. Astro has its own setup and
|
|
354
|
+
its own measured list: [Astro](#astro).
|
|
223
355
|
|
|
224
356
|
**It also chooses which atoms ship.** A `cx()` call in your code whose atoms the build cannot read,
|
|
225
357
|
such as `cx(variant)` with a prop, is an error in the dev server and fails `vite build`.
|
|
@@ -242,10 +374,15 @@ Nave class it cannot see has no rule unless its atom is listed in `keep`.
|
|
|
242
374
|
|
|
243
375
|
**Under `css.transformer: 'lightningcss'`** the plugin expands exactly as it does under the default
|
|
244
376
|
transformer once both floor keys below are set. At Vite's default targets, which sit below the
|
|
245
|
-
floor, Lightning CSS lowers CSS nesting before any plugin runs, and
|
|
377
|
+
floor, Lightning CSS lowers CSS nesting before any plugin runs, and three things change.
|
|
246
378
|
A directive written after a declaration that follows a nested rule can be moved ahead of that
|
|
247
379
|
declaration: in `.a { &:hover { color: red; } display: grid; @nave block; }`, `grid` wins with no
|
|
248
|
-
message, where the default transformer makes `block` win.
|
|
380
|
+
message, where the default transformer makes `block` win.
|
|
381
|
+
What a rule says after a directive becomes a rule of its own, after the directive's, so it
|
|
382
|
+
already resolves in the order [Order inside a rule](#order-inside-a-rule) describes, but no order
|
|
383
|
+
warning is printed for it: a later `&:focus { outline: none }` after `focusRing`, for one, removes
|
|
384
|
+
the ring there with no message.
|
|
385
|
+
A directive in a group rule nested in a
|
|
249
386
|
style rule (`.a { @media (...) { @nave flex; } }`) still fails the build, but the message no longer
|
|
250
387
|
names the `& { @nave ...; }` workaround, and its position is in the CSS as Lightning CSS rewrote
|
|
251
388
|
it, not in your file. The plugin also drops one message and only that one: Lightning CSS's
|
|
@@ -310,12 +447,28 @@ newest 8.x at the time of each release.
|
|
|
310
447
|
The Vite plugin also chooses which of Nave's atoms reach your stylesheet. It reads the `cx()` calls
|
|
311
448
|
in the code the build compiles, dependencies included, and ships the rules for the atoms those calls
|
|
312
449
|
name, for the atom of each Nave class written as text (`nave-grid` in `index.html` or in a string in
|
|
313
|
-
your code, say)
|
|
314
|
-
|
|
450
|
+
your code, say) and each Nave class a CSS Module composes from global (`composes: nave-flex from global`),
|
|
451
|
+
and for the atoms you list in `keep` and `keepFor`; `srOnlyFocusable` also ships whenever `srOnly`
|
|
452
|
+
does. The dev server serves the same atoms, from the code it has read. This is
|
|
315
453
|
what `navePlugin()` does with no options. On the CSS-first path, where `@nave` copies an atom's
|
|
316
454
|
declarations into your own rule, a project that never calls `cx()` and writes no Nave class ships no
|
|
317
455
|
rule from the atom layer.
|
|
318
456
|
|
|
457
|
+
In a build, the plugin also turns each `cx()` call it read into the class string the call returns, so
|
|
458
|
+
the map from atom names to classes does not ship: `cx('flex', 'grid')` becomes `"nave-flex nave-grid"`,
|
|
459
|
+
and `cx('flex', on && 'grid')` becomes `["nave-flex", on && "nave-grid"].filter(Boolean).join(" ")`,
|
|
460
|
+
with each argument evaluated once, in the order you wrote it. A direct `cx.raw()` call becomes the
|
|
461
|
+
same join, and a direct `cx.dynamic()` call a lookup in a map of the atoms in `keep` and `keepFor`.
|
|
462
|
+
The dev server keeps the run-time functions, which return the same strings. A bundle holds neither
|
|
463
|
+
the name map nor the atoms' declarations when every use of `cx` in it is one of those direct calls
|
|
464
|
+
and no module that imports `cx` calls `eval` directly. Two forms keep the map, since the build
|
|
465
|
+
cannot follow the alias: `const r = cx.raw` and `const { raw } = cx`. So does a package listed in
|
|
466
|
+
`keepFor` whose calls the build cannot read, because those calls stay calls to the run-time `cx()`.
|
|
467
|
+
A module that calls `eval` directly keeps its import of `cx`, and with it the map, whatever text it
|
|
468
|
+
runs (`eval('1')` too), because that text can read the module's names and the build cannot read the
|
|
469
|
+
text. An indirect call, `(0, eval)(text)` or `globalThis.eval(text)`, runs its text in the global
|
|
470
|
+
scope, where `cx` is not visible, and does not keep the import.
|
|
471
|
+
|
|
319
472
|
So that nothing is dropped on a guess, a `cx()` call whose atoms the build cannot read fails the
|
|
320
473
|
build. `vite build` reads every module first and fails once, at the end, with a report that lists
|
|
321
474
|
each such use with its file, and its line and column when the source maps lead back to them, then
|
|
@@ -514,9 +667,10 @@ build, that list is the set of atoms its CSS holds, so you can diff it between b
|
|
|
514
667
|
**Shipping every atom.** `navePlugin({ atomic: 'all' })` reads no `cx()` call and ships every atom,
|
|
515
668
|
as the PostCSS plugin does. It suits a project whose Nave classes are mostly written where no build
|
|
516
669
|
reads them, such as a backend's own templates or HTML from a CMS, where listing them in `keep` is
|
|
517
|
-
not practical. It gives up what this section describes: no `cx()` call is read
|
|
518
|
-
that is no atom passes through `cx()` as text,
|
|
519
|
-
maps through every atom. The dev server serves
|
|
670
|
+
not practical. It gives up what this section describes: no `cx()` call is read, checked or turned
|
|
671
|
+
into its class string, so the name map ships, a name that is no atom passes through `cx()` as text,
|
|
672
|
+
as it does without the plugin, and `cx.dynamic()` maps through every atom. The dev server serves
|
|
673
|
+
every atom too, the plugin sets neither
|
|
520
674
|
`optimizeDeps.exclude` nor `resolve.noExternal`, no build writes `nave-used-atoms.json`, and
|
|
521
675
|
`keep`, `keepFor` and `cxModules` are still checked for well-formed values and otherwise do nothing.
|
|
522
676
|
It is also the way out that the plugin's own check names when a build's atom layer does not hold
|
|
@@ -546,6 +700,82 @@ their own code, and `keepFor` does not reach it.
|
|
|
546
700
|
|
|
547
701
|
List every atom your components can apply at run time, `focusRing`, `srOnly`, `srOnlyFocusable`, `hidden` and the `hide*` atoms included where you use them: a consumer passes them to `keepFor` under your package's name, and one left out renders as if the class were absent in their production build.
|
|
548
702
|
|
|
703
|
+
#### Astro
|
|
704
|
+
|
|
705
|
+
Astro builds with Vite, so the Vite plugin is the route there too. Add it under `vite` in your
|
|
706
|
+
Astro config, which is where Astro takes Vite plugins, and put the floor key under `vite` as well:
|
|
707
|
+
Astro's own `build` has no `cssTarget`, and drops one written there without an error.
|
|
708
|
+
|
|
709
|
+
```js
|
|
710
|
+
// astro.config.mjs
|
|
711
|
+
import { defineConfig } from 'astro/config'
|
|
712
|
+
import { navePlugin } from '@navecss/core/vite'
|
|
713
|
+
|
|
714
|
+
export default defineConfig({
|
|
715
|
+
vite: {
|
|
716
|
+
plugins: [navePlugin()],
|
|
717
|
+
build: { cssTarget: ['chrome125', 'edge125', 'firefox128', 'safari18', 'ios18'] },
|
|
718
|
+
},
|
|
719
|
+
})
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
The build chooses which atoms ship, so run your checks, keyboard, screen-reader and automated
|
|
723
|
+
accessibility checks included, against a production build as well as in dev: `astro build`, then
|
|
724
|
+
`astro preview` or however you serve production.
|
|
725
|
+
|
|
726
|
+
Put Nave's order statement first in the `<head>` of every layout, as an inline style, ahead of
|
|
727
|
+
anything that can bring a style, `<ClientRouter />` included:
|
|
728
|
+
|
|
729
|
+
```astro
|
|
730
|
+
---
|
|
731
|
+
// src/layouts/Layout.astro
|
|
732
|
+
import '../styles/app.css'
|
|
733
|
+
---
|
|
734
|
+
<html lang="en">
|
|
735
|
+
<head>
|
|
736
|
+
<style is:inline>@layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;</style>
|
|
737
|
+
<meta charset="utf-8" />
|
|
738
|
+
<!-- the rest of your head, <ClientRouter /> included -->
|
|
739
|
+
</head>
|
|
740
|
+
<body><slot /></body>
|
|
741
|
+
</html>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
Astro can place a page's or a component's own style ahead of your layout's stylesheet, and with
|
|
745
|
+
`<ClientRouter />` it does so whatever the page imports first. A `@layer` in that style then fixes
|
|
746
|
+
its layer's place before Nave's order statement does, and the order inverts with no error. Astro
|
|
747
|
+
leaves an `is:inline` style where you write it, so this one is the first `@layer` the page sees.
|
|
748
|
+
`astro dev` does not show the inversion a build makes, so check the order in a build. The statement
|
|
749
|
+
is the one `@navecss/core/layers` holds, and the two must stay identical. Write the rules of a
|
|
750
|
+
`<style>` block inside `@layer components.consumer`, as for any CSS of yours: Astro leaves them
|
|
751
|
+
outside every layer otherwise, where they beat all of them.
|
|
752
|
+
|
|
753
|
+
The build chooses atoms as [Which atoms the build ships](#which-atoms-the-build-ships) says, with
|
|
754
|
+
three things particular to Astro. Astro writes its CSS in its server build, before the browser's
|
|
755
|
+
build runs, so an atom named only by code the browser runs, such as a `client:only` component or a
|
|
756
|
+
`<script>` in an `.astro` file, fails the build: list it in `keep`. A dependency the server build
|
|
757
|
+
leaves external is read just as late: add it to `resolve.noExternal` under `vite`, which reaches
|
|
758
|
+
every environment Astro builds, where `ssr.noExternal` does not. And in `output: 'server'` with
|
|
759
|
+
prerendered pages, the stylesheet the on-demand pages share ships every atom.
|
|
760
|
+
|
|
761
|
+
Both end-of-build checks, the `@nave` scan and the check that the atom layer holds exactly the atoms
|
|
762
|
+
the build chose, also read the stylesheets Astro writes into the page instead of as files (those
|
|
763
|
+
under 4 kB, by default). The file a message names for one of those is the name the stylesheet had
|
|
764
|
+
before Astro inlined it, so no file of that name is in `dist`. In an `.astro` file, the line of a
|
|
765
|
+
`@nave` error counts from the start of the `<style>` block that holds it, not from the top of the
|
|
766
|
+
file, and its column can differ from your file's, because Astro rewrites the block's whitespace.
|
|
767
|
+
|
|
768
|
+
In `astro dev`, the first paint holds the stylesheet as Vite's own CSS step left it, every atom and
|
|
769
|
+
every `@nave` unexpanded, until the page's scripts load; after that the dev server serves what the
|
|
770
|
+
build would, as far as it has read. The build is unaffected. As on any Vite project, run your
|
|
771
|
+
checks, keyboard, screen-reader and automated accessibility checks included, against a production
|
|
772
|
+
build as well as in dev: `astro build`, then `astro preview` or however you serve production.
|
|
773
|
+
|
|
774
|
+
A fixture measures this on Astro 7.4.0, with Vite 8.3.5 underneath: `@nave` expanded in `.css`
|
|
775
|
+
files and in `<style>` and `<style is:global>` blocks, and `cx()` read in frontmatter, in templates
|
|
776
|
+
and in React islands, in `astro build` for a static site and for `output: 'server'`, and in
|
|
777
|
+
`astro dev`. Islands in Vue, Svelte or Solid were not measured.
|
|
778
|
+
|
|
549
779
|
### PostCSS plugin setup
|
|
550
780
|
|
|
551
781
|
For Next.js, webpack under `postcss-loader`, and any other pipeline that runs PostCSS plugins.
|
|
@@ -602,6 +832,11 @@ Without it, Turbopack compiles for older browsers and rewrites every `light-dark
|
|
|
602
832
|
Nave's colours included, into `--lightningcss-light` and `--lightningcss-dark` variables. Spell
|
|
603
833
|
the floor out as above: Next 15 rejects the shorter `baseline 2024` with `Unknown browser baseline`.
|
|
604
834
|
|
|
835
|
+
**Where Nave's warnings show.** With webpack (`next build --webpack`), `next build` prints them under `Compiled with warnings`, once for each compilation that reads the stylesheet, so a CSS Module imported from a Server Component can show the same warning twice; `next dev --webpack` may not print a global stylesheet's, so read them from `next build`. Turbopack does not print PostCSS warnings, so under Turbopack the plugin prints them itself on standard error, with the file, line and column, in `next build` and `next dev` alike. Neither bundler shows them in the browser console or Next's error overlay.
|
|
836
|
+
A problem that [`onUnknown`](#options) covers is a warning only under `'warn'`; under the
|
|
837
|
+
default, `'error'`, it fails the build on either bundler. Where the warnings show was measured
|
|
838
|
+
on Next 16.4.1, not on Next 15.
|
|
839
|
+
|
|
605
840
|
**If your Vite config sets `css.transformer: 'lightningcss'`, this plugin never
|
|
606
841
|
runs.** That option replaces Vite's CSS pipeline with Lightning CSS, which does
|
|
607
842
|
not run PostCSS plugins at all: the build stays green, `@nave` reaches the
|
|
@@ -651,7 +886,8 @@ Nave's own messages, with the file's path and the line and column of each proble
|
|
|
651
886
|
and the map as a string, ready for `transform()`'s `code` and `inputSourceMap`. `resolver.read` reads each file
|
|
652
887
|
`bundleAsync()` asks for and returns its expanded text, so a file reached through `@import` is
|
|
653
888
|
covered too. It takes `extend` (an object of your own atoms) and `onUnknown` as the other plugins
|
|
654
|
-
do ([Options](#options)); under `'warn'` it prints each problem with `console.warn
|
|
889
|
+
do ([Options](#options)); under `'warn'` it prints each problem with `console.warn`, and prints
|
|
890
|
+
the [order warning](#order-inside-a-rule) the same way under every `onUnknown`.
|
|
655
891
|
|
|
656
892
|
The adapter imports nothing from `lightningcss`, types included, and declares no peer: you bring
|
|
657
893
|
your own copy. The supported range is documented, not declared: `lightningcss` 1.24.1 and later.
|
|
@@ -680,6 +916,21 @@ in one error, positioned at the first: its message, then
|
|
|
680
916
|
others. The report covers one stylesheet, so a bundler that stops at the first
|
|
681
917
|
failing file reports one file per build. Under `'warn'`, each problem is a
|
|
682
918
|
warning of its own.
|
|
919
|
+
The [order warning](#order-inside-a-rule) is not one of these problems: it is printed as a
|
|
920
|
+
warning whatever `onUnknown` says. On Turbopack, Next 16's default bundler, which shows no
|
|
921
|
+
PostCSS warning, the PostCSS plugin prints each problem under `'warn'` and each order warning
|
|
922
|
+
to the terminal instead, as `file:line:column:` and the message
|
|
923
|
+
([where Next.js shows them](#nextjs)). It knows Turbopack by the `TURBOPACK` environment
|
|
924
|
+
variable, which Next.js sets for it, so wherever that variable is set and not empty, under Vite
|
|
925
|
+
or the PostCSS CLI as well, it does the same and raises no PostCSS warning. The Vite plugin and
|
|
926
|
+
the Lightning CSS adapter do not read the variable. The PostCSS plugin also takes
|
|
927
|
+
`printWarnings` (default `true`): with `printWarnings: false` it raises each problem under
|
|
928
|
+
`'warn'` and each order warning as a PostCSS warning and prints none of them, whatever
|
|
929
|
+
`TURBOPACK` holds, for a tool that runs the plugin itself and reports the warnings it raises.
|
|
930
|
+
Such a tool can import `isOrderWarning(warning)` from `@navecss/core/postcss` to tell them
|
|
931
|
+
apart: it is `true` for an order warning and `false` for each of these problems, so the tool
|
|
932
|
+
can report an order warning as the plugin does, whatever `onUnknown` says, and never fail a
|
|
933
|
+
build on one.
|
|
683
934
|
|
|
684
935
|
To add atoms of your own to `@nave`, pass them as `extend`: see
|
|
685
936
|
[CONSUMER-ATOMS.md](./CONSUMER-ATOMS.md). An `extend` object written inline in
|
|
@@ -779,6 +1030,9 @@ across every file is printed in one run, one report per stylesheet. Exit codes:
|
|
|
779
1030
|
`--watch`, a directory that could not be watched. Nothing is written, except that files written
|
|
780
1031
|
before an `--out` that failed stay written.
|
|
781
1032
|
|
|
1033
|
+
An order warning is printed and is not a problem here: it changes neither the exit code nor what is
|
|
1034
|
+
written.
|
|
1035
|
+
|
|
782
1036
|
It does not resolve or inline `@import`; that would make it a CSS bundler. It
|
|
783
1037
|
expands only the files you name with `--source`, so a stylesheet you import that
|
|
784
1038
|
holds `@nave` needs a pair of its own. An `@import` of a relative or absolute URL
|
|
@@ -865,17 +1119,18 @@ Consumers of `@navecss/tokens` alone get no guide: it ships only in this package
|
|
|
865
1119
|
|
|
866
1120
|
## Exports
|
|
867
1121
|
|
|
868
|
-
| Export | Description
|
|
869
|
-
| ---------------------------- |
|
|
870
|
-
| `@navecss/core` | CSS entry point — `@import url('@navecss/core')`
|
|
871
|
-
| `@navecss/core/layers` | The `@layer` order statement alone. Import it first, before any other stylesheet
|
|
872
|
-
| `@navecss/core/no-tokens` | Entry point for projects that generate their own Nave token layer; see the header comment in the file
|
|
873
|
-
| `@navecss/core/reset` | Reset stylesheet only. It reads Nave's custom properties, so it needs a token layer beside it
|
|
874
|
-
| `@navecss/core/atomic` | Generated atomic CSS (global `nave-` classes)
|
|
875
|
-
| `@navecss/core/cx` | `cx()` / `cx.raw()` utilities + `AtomName` type
|
|
876
|
-
| `@navecss/core/atoms` | Atom definitions + `atomClassMap`
|
|
877
|
-
| `@navecss/core/vite` | Vite plugin — `navePlugin()`
|
|
878
|
-
| `@navecss/core/lightningcss` | Lightning CSS adapter — `navePlugin()`, returning `expand` and `resolver`
|
|
879
|
-
| `@navecss/core/standalone` | The whole stylesheet in one file, with no `@import`, for projects with no bundler
|
|
880
|
-
| `@navecss/core/postcss` | PostCSS plugin — `navePlugin()`
|
|
881
|
-
| `@navecss/core/check` | The survival check as a function — `check({ source })`
|
|
1122
|
+
| Export | Description |
|
|
1123
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
1124
|
+
| `@navecss/core` | CSS entry point — `@import url('@navecss/core')` |
|
|
1125
|
+
| `@navecss/core/layers` | The `@layer` order statement alone. Import it first, before any other stylesheet |
|
|
1126
|
+
| `@navecss/core/no-tokens` | Entry point for projects that generate their own Nave token layer; see the header comment in the file |
|
|
1127
|
+
| `@navecss/core/reset` | Reset stylesheet only. It reads Nave's custom properties, so it needs a token layer beside it |
|
|
1128
|
+
| `@navecss/core/atomic` | Generated atomic CSS (global `nave-` classes) |
|
|
1129
|
+
| `@navecss/core/cx` | `cx()` / `cx.raw()` utilities + `AtomName` type |
|
|
1130
|
+
| `@navecss/core/atoms` | Atom definitions + `atomClassMap` |
|
|
1131
|
+
| `@navecss/core/vite` | Vite plugin — `navePlugin()` |
|
|
1132
|
+
| `@navecss/core/lightningcss` | Lightning CSS adapter — `navePlugin()`, returning `expand` and `resolver` |
|
|
1133
|
+
| `@navecss/core/standalone` | The whole stylesheet in one file, with no `@import`, for projects with no bundler |
|
|
1134
|
+
| `@navecss/core/postcss` | PostCSS plugin — `navePlugin()` |
|
|
1135
|
+
| `@navecss/core/check` | The survival check as a function — `check({ source })` |
|
|
1136
|
+
| `@navecss/core/collect` | Reads which atoms a module uses and trims the atom layer to them, for build tools; not meant for application code |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The names of Nave's built-in atoms, and the class each one gets.
|
|
3
|
+
*
|
|
4
|
+
* cx() needs this and nothing else, so it lives apart from the declaration data in atoms.ts:
|
|
5
|
+
* a module that imports atoms.ts hands a consumer's bundler every atom's CSS declarations,
|
|
6
|
+
* not every bundler can drop them, and cx() would pay that for a name map.
|
|
7
|
+
*
|
|
8
|
+
* atoms.ts is checked against this list (`satisfies Record<AtomName, AtomDefinition>`), so
|
|
9
|
+
* an atom added there and missing here, or the other way round, fails the typecheck. Keep the
|
|
10
|
+
* two in the same order as well: atomic.css is written in the order of atoms.ts, the Vite
|
|
11
|
+
* plugin lists the atoms in the order of this list, and a test holds the two together.
|
|
12
|
+
*/
|
|
13
|
+
declare const atomNames: readonly ["flex", "inlineFlex", "grid", "block", "inlineBlock", "hidden", "flexCol", "flexWrap", "itemsCenter", "itemsStart", "itemsEnd", "justifyCenter", "justifyBetween", "justifyEnd", "flexGrow", "flexShrink0", "gap", "relative", "absolute", "insetFull", "wFull", "hFull", "minW0", "truncate", "srOnly", "srOnlyFocusable", "noWrap", "breakWord", "textLeft", "textCenter", "textRight", "interactive", "focusRing", "disabledState", "rounded", "roundedCard", "roundedFull", "border", "overflowHidden", "overflowAuto", "transition", "container", "hidePhoneOnly", "hideTabletPortraitUp", "hideTabletLandscapeUp", "hideDesktopUp", "stackPhoneOnly", "wFullPhoneOnly"];
|
|
14
|
+
type AtomName = (typeof atomNames)[number];
|
|
15
|
+
/**
|
|
16
|
+
* Converts a camelCase atom name to its nave-kebab-case CSS class name.
|
|
17
|
+
* focusRing → nave-focus-ring
|
|
18
|
+
*/
|
|
19
|
+
declare const toClassName: (name: AtomName) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Map of atom name → global CSS class name.
|
|
22
|
+
* Used by cx() and the directive core's diagnostic hints.
|
|
23
|
+
* Only covers Nave built-in atoms — consumer atoms are @nave-directive only.
|
|
24
|
+
*/
|
|
25
|
+
declare const atomClassMap: Record<AtomName, string>;
|
|
26
|
+
|
|
27
|
+
export { atomClassMap as a, toClassName as t };
|
|
28
|
+
export type { AtomName as A };
|
package/dist/atoms.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { A as AtomName, a as atomClassMap, t as toClassName } from './atom-names-B8Ud-7zD.js';
|
|
2
|
+
|
|
1
3
|
type CSSDeclarations = Record<string, string>;
|
|
2
4
|
type PseudoMap = Record<string, CSSDeclarations>;
|
|
3
5
|
interface MediaBlock {
|
|
@@ -403,17 +405,6 @@ declare const atoms: {
|
|
|
403
405
|
};
|
|
404
406
|
};
|
|
405
407
|
};
|
|
406
|
-
type AtomName = keyof typeof atoms;
|
|
407
|
-
/**
|
|
408
|
-
* Converts a camelCase atom name to its nave-kebab-case CSS class name.
|
|
409
|
-
* focusRing → nave-focus-ring
|
|
410
|
-
*/
|
|
411
|
-
declare const toClassName: (name: AtomName) => string;
|
|
412
|
-
/**
|
|
413
|
-
* Map of atom name → global CSS class name.
|
|
414
|
-
* Used by cx() and the directive core's diagnostic hints.
|
|
415
|
-
* Only covers Nave built-in atoms — consumer atoms are @nave-directive only.
|
|
416
|
-
*/
|
|
417
|
-
declare const atomClassMap: Record<AtomName, string>;
|
|
418
408
|
|
|
419
|
-
export {
|
|
409
|
+
export { atoms };
|
|
410
|
+
export type { AtomDefinition };
|
package/dist/atoms.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { atoms } from './chunk-CUL5PLPI.js';
|
|
2
|
+
export { atomClassMap, toClassName } from './chunk-GZL2VW7C.js';
|