cssints 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +188 -2
- package/dist/author-types-C1AhsnrT.d.mts +80 -0
- package/dist/check.d.mts +22 -0
- package/dist/check.mjs +37 -0
- package/dist/color-BYIxVLlS.mjs +29 -0
- package/dist/color-C8OzjAeg.mjs +2 -0
- package/dist/contrast-DhK69Xak.mjs +83 -0
- package/dist/derived.d.mts +14 -0
- package/dist/derived.mjs +391 -0
- package/dist/errors-D4ZbcwRo.d.mts +32 -0
- package/dist/frame-BoBKjgBB.mjs +92 -0
- package/dist/frame.d.mts +72 -0
- package/dist/frame.mjs +2 -0
- package/dist/group-common-9zpBxfUF.d.mts +1 -0
- package/dist/group-types-9ttphdku.d.mts +55 -0
- package/dist/groups/compositing-2.d.mts +20 -0
- package/dist/groups/css-align-3.d.mts +29 -0
- package/dist/groups/css-anchor-position-1.d.mts +49 -0
- package/dist/groups/css-anchor-position-2.d.mts +50 -0
- package/dist/groups/css-animations-1.d.mts +28 -0
- package/dist/groups/css-animations-2.d.mts +29 -0
- package/dist/groups/css-backgrounds-3.d.mts +27 -0
- package/dist/groups/css-backgrounds-4.d.mts +29 -0
- package/dist/groups/css-borders-4.d.mts +95 -0
- package/dist/groups/css-box-4.d.mts +38 -0
- package/dist/groups/css-break-4.d.mts +23 -0
- package/dist/groups/css-cascade-5.d.mts +18 -0
- package/dist/groups/css-cascade-6.d.mts +18 -0
- package/dist/groups/css-color-4.d.mts +29 -0
- package/dist/groups/css-color-5.d.mts +32 -0
- package/dist/groups/css-color-6.d.mts +32 -0
- package/dist/groups/css-color-adjust-1.d.mts +20 -0
- package/dist/groups/css-color-hdr-1.d.mts +19 -0
- package/dist/groups/css-conditional-3.d.mts +17 -0
- package/dist/groups/css-conditional-4.d.mts +17 -0
- package/dist/groups/css-conditional-5.d.mts +22 -0
- package/dist/groups/css-contain-2.d.mts +19 -0
- package/dist/groups/css-content-3.d.mts +23 -0
- package/dist/groups/css-counter-styles-3.d.mts +16 -0
- package/dist/groups/css-display-4.d.mts +22 -0
- package/dist/groups/css-easing-2.d.mts +18 -0
- package/dist/groups/css-env-1.d.mts +16 -0
- package/dist/groups/css-flexbox-1.d.mts +24 -0
- package/dist/groups/css-fonts-4.d.mts +46 -0
- package/dist/groups/css-fonts-5.d.mts +44 -0
- package/dist/groups/css-forms-1.d.mts +19 -0
- package/dist/groups/css-gaps-1.d.mts +21 -0
- package/dist/groups/css-gcpm-3.d.mts +20 -0
- package/dist/groups/css-gcpm-4.d.mts +20 -0
- package/dist/groups/css-grid-2.d.mts +34 -0
- package/dist/groups/css-grid-3.d.mts +34 -0
- package/dist/groups/css-images-3.d.mts +25 -0
- package/dist/groups/css-images-4.d.mts +32 -0
- package/dist/groups/css-images-5.d.mts +33 -0
- package/dist/groups/css-inline-3.d.mts +28 -0
- package/dist/groups/css-lists-3.d.mts +26 -0
- package/dist/groups/css-logical-1.d.mts +35 -0
- package/dist/groups/css-masking-1.d.mts +37 -0
- package/dist/groups/css-multicol-2.d.mts +24 -0
- package/dist/groups/css-overflow-3.d.mts +28 -0
- package/dist/groups/css-overflow-4.d.mts +29 -0
- package/dist/groups/css-overflow-5.d.mts +31 -0
- package/dist/groups/css-overscroll-1.d.mts +22 -0
- package/dist/groups/css-page-3.d.mts +18 -0
- package/dist/groups/css-page-floats-3.d.mts +19 -0
- package/dist/groups/css-position-3.d.mts +29 -0
- package/dist/groups/css-position-4.d.mts +30 -0
- package/dist/groups/css-rhythm-1.d.mts +18 -0
- package/dist/groups/css-round-display-1.d.mts +16 -0
- package/dist/groups/css-ruby-1.d.mts +21 -0
- package/dist/groups/css-scroll-anchoring-1.d.mts +18 -0
- package/dist/groups/css-scroll-snap-1.d.mts +42 -0
- package/dist/groups/css-scroll-snap-2.d.mts +43 -0
- package/dist/groups/css-scrollbars-1.d.mts +19 -0
- package/dist/groups/css-shapes-1.d.mts +27 -0
- package/dist/groups/css-shapes-2.d.mts +27 -0
- package/dist/groups/css-size-adjust-1.d.mts +18 -0
- package/dist/groups/css-sizing-3.d.mts +34 -0
- package/dist/groups/css-sizing-4.d.mts +33 -0
- package/dist/groups/css-speech-1.d.mts +18 -0
- package/dist/groups/css-tables-3.d.mts +22 -0
- package/dist/groups/css-text-3.d.mts +36 -0
- package/dist/groups/css-text-4.d.mts +40 -0
- package/dist/groups/css-text-decor-3.d.mts +30 -0
- package/dist/groups/css-text-decor-4.d.mts +31 -0
- package/dist/groups/css-transforms-1.d.mts +31 -0
- package/dist/groups/css-transforms-2.d.mts +48 -0
- package/dist/groups/css-transitions-1.d.mts +22 -0
- package/dist/groups/css-transitions-2.d.mts +23 -0
- package/dist/groups/css-ui-4.d.mts +35 -0
- package/dist/groups/css-values-4.d.mts +38 -0
- package/dist/groups/css-values-5.d.mts +43 -0
- package/dist/groups/css-variables-2.d.mts +6 -0
- package/dist/groups/css-view-transitions-2.d.mts +20 -0
- package/dist/groups/css-viewport.d.mts +18 -0
- package/dist/groups/css-will-change-1.d.mts +18 -0
- package/dist/groups/css-writing-modes-4.d.mts +22 -0
- package/dist/groups/css2.d.mts +19 -0
- package/dist/groups/fill-stroke-3.d.mts +27 -0
- package/dist/groups/filter-effects-1.d.mts +32 -0
- package/dist/groups/filter-effects-2.d.mts +33 -0
- package/dist/groups/limited-availability.d.mts +2 -0
- package/dist/groups/mediaqueries-4.d.mts +16 -0
- package/dist/groups/mediaqueries-5.d.mts +16 -0
- package/dist/groups/motion-1.d.mts +24 -0
- package/dist/groups/newly-available.d.mts +631 -0
- package/dist/groups/not-discouraged.d.mts +727 -0
- package/dist/groups/scroll-animations-1.d.mts +30 -0
- package/dist/groups/selectors-4.d.mts +54 -0
- package/dist/groups/selectors-5.d.mts +54 -0
- package/dist/groups/widely-available.d.mts +616 -0
- package/dist/index.d.mts +48 -0
- package/dist/index.mjs +1269 -0
- package/dist/instrument-gCazujmb.mjs +1624 -0
- package/dist/lowered-D2KusPLp.mjs +28 -0
- package/dist/node-OJ_w_Usj.d.mts +233 -0
- package/dist/parent-vars.d.mts +13 -0
- package/dist/parent-vars.mjs +65 -0
- package/dist/plugin-types.d.mts +377 -0
- package/dist/plugin.d.mts +3 -0
- package/dist/plugin.mjs +30 -0
- package/dist/registry-CTumHB8p.mjs +73 -0
- package/dist/runtime-Dt4Tm0Q7.d.mts +19 -0
- package/dist/runtime.d.mts +18 -0
- package/dist/runtime.mjs +25 -0
- package/dist/scope-ZVhUxQH1.d.mts +2101 -0
- package/dist/sheet-BKsMTa0c.mjs +757 -0
- package/dist/surface.d.mts +228 -0
- package/dist/surface.mjs +101 -0
- package/dist/theme-BsP2E4LB.mjs +929 -0
- package/dist/theme-types-Bqv0Ggz_.d.mts +1808 -0
- package/dist/trace-otlp-CbKFDsn9.mjs +14 -0
- package/dist/tui-types.d.mts +181 -0
- package/dist/tui.d.mts +23 -0
- package/dist/tui.mjs +427 -0
- package/dist/types.d.mts +2 -0
- package/dist/vite.d.mts +134 -0
- package/dist/vite.mjs +977 -0
- package/docs/API.md +1193 -0
- package/package.json +85 -4
- package/skills/cssints/SKILL.md +203 -0
- package/skills/cssints/references/errors.md +86 -0
- package/skills/cssints/references/plugins.md +91 -0
- package/skills/cssints/references/tokens.md +91 -0
- package/skills/cssints-plugin/SKILL.md +177 -0
- package/skills/cssints-plugin/references/table.md +124 -0
- package/skills/cssints-plugin/references/testing.md +122 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cssints-plugin
|
|
3
|
+
description: >-
|
|
4
|
+
Write or fix a cssints plugin: a module or package that exports members of scope() from "cssints/plugin" (kernels,
|
|
5
|
+
chain/call/value members, conflicts, runtimes with polyfills/css/script/parentVars, lacking(), group markers, token
|
|
6
|
+
themes). Load it when code imports scope, fn, arg or lacking from "cssints/plugin", when you design a new
|
|
7
|
+
@cssints/* package or a member like stack(), motion() or fluid(), when a build says "kernel ... sets",
|
|
8
|
+
"wraps ... the marker of kernel", "scope ... is defined twice", "share a name", "kernels ... both set", or a
|
|
9
|
+
declaration file fails with TS7056/TS2742, and when you write its fixtures, type tests or browser tests.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# cssints plugins on `scope()`
|
|
13
|
+
|
|
14
|
+
A plugin is a module that exports the members of `scope(name, table).build(impl).export()` from `cssints/plugin`.
|
|
15
|
+
The table says what the plugin has; the members are typed from it (no types file). User code imports the plugin with
|
|
16
|
+
`with { type: "cssints" }`; there is no registration. In user code a member is a class string (or a CSS value), so it
|
|
17
|
+
goes into `cn()` and conditions. Everything runs at build time in Vite's Module Runner. Read the user skill
|
|
18
|
+
`cssints` first for the styles themselves; the full reference is the "Plugins" section of `docs/API.md`.
|
|
19
|
+
|
|
20
|
+
If the cssints MCP server is configured, prefer `compile` to see a fixture's classes, sheet, errors and warnings,
|
|
21
|
+
`check_declaration` for a kernel's declarations, `check_support` for the BCD keys of `requires`/`polyfills`, and
|
|
22
|
+
`search_docs` for the API text.
|
|
23
|
+
|
|
24
|
+
## The shape
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { scope } from "cssints/plugin";
|
|
28
|
+
|
|
29
|
+
export const { ring } = scope("ring", {
|
|
30
|
+
"#core": {
|
|
31
|
+
css: (css) => [css.outline("var(--_w)", "solid", "var(--_c)"), css.outlineOffset("2px")],
|
|
32
|
+
},
|
|
33
|
+
ring: {
|
|
34
|
+
uses: ["core"],
|
|
35
|
+
chain: {
|
|
36
|
+
width: { args: "<length [0,∞]> | thin | thick", spacing: true, emit: (v) => [["--_w", v]] },
|
|
37
|
+
color: { args: "<color>", emit: (v) => [["--_c", v]] },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}).export();
|
|
41
|
+
// user code: cn(ring().width(1).color("red")) -> "_ring-core _a _b"; ring().width("red") is a type error
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **Names.** The scope name is lowercase letters, digits and `-`, unique in the process (`cssints`, `derived` are
|
|
45
|
+
taken). `--_x` anywhere in the scope is `--<scope>-x`; `&` in raw CSS is the kernel's class `_<scope>-<entry>`;
|
|
46
|
+
`&(mod.key)` is the class of a kernel of an embedded scope. Your CSS never writes the long names.
|
|
47
|
+
- **Private keys** start with `#`: used by other entries, not exported. `uses` names them without the `#`, so `#trim`
|
|
48
|
+
beside a public `trim` is an error (`#trim and trim share a name: rename one`).
|
|
49
|
+
- **A key is a name** (no dots). A member shown as `fluid.container` is a key `fluidContainer` with
|
|
50
|
+
`label: "fluid.container"`, attached by hand (`Object.assign`).
|
|
51
|
+
- `.build({ member: fn })` holds the functions of call members; a scope with only chains and values skips it.
|
|
52
|
+
|
|
53
|
+
The table entries by shape (syntax names, typed `-props`, kernels, globals, layers, runtimes, conflicts, token sets,
|
|
54
|
+
embedded scopes, chain, call and value members): [references/table.md](references/table.md).
|
|
55
|
+
|
|
56
|
+
## Kernels: styles or raw CSS
|
|
57
|
+
|
|
58
|
+
A kernel is global CSS printed once in layer `_.k`, keyed to its marker class. Write it **as styles** by default:
|
|
59
|
+
`css: (css) => [...]` gets the `css.*` members, checked by the grammar like a user's declarations, plus:
|
|
60
|
+
|
|
61
|
+
- conditions: `css.hover(...)`, `css.media("(...)")(...)`, `attr()`, `within()`, `container()`, `supports()`;
|
|
62
|
+
- `css.nest(selector)(...)` for other elements: one complex selector with `&` (the kernel's class) anywhere:
|
|
63
|
+
`"& > *"`, `"& > * + *"`, `".dark &"`, `":root:has(&)"` (a list is an error);
|
|
64
|
+
- `css.vars({ "--_gap": "1rem" })` for custom properties (values not checked);
|
|
65
|
+
- `keyframes()`/`positionTry()` names as values (`css.vars({ "--_k": fade })`): their rules go with the kernel; a
|
|
66
|
+
`css.keyframes({ from: css.vars({ "--_p": "100%" }) })` made in the kernel animates the scope's `--<scope>-p`.
|
|
67
|
+
|
|
68
|
+
A styles kernel **derives its conflicts** (every property that meets one it sets, as `cn()` decides overlap) and its
|
|
69
|
+
support warnings are **covered** by the `polyfills` of a runtime in the same `uses`. Raw CSS (`css: "&{...}"`) is still
|
|
70
|
+
needed for:
|
|
71
|
+
|
|
72
|
+
- at-rules the styles cannot write: `@container style(...)` alternation (frame, surface), `&&` specificity tricks;
|
|
73
|
+
- a value the grammar does not know yet (write it raw, with a test that fails once the grammar has it);
|
|
74
|
+
- a declaration whose support warning nothing should cover and nothing should print (balance's `pretty()`).
|
|
75
|
+
|
|
76
|
+
Runtime fallback CSS (`@supports not (...) { & {...} }`) is a raw string in the runtime entry, not a kernel.
|
|
77
|
+
|
|
78
|
+
A raw kernel derives no conflicts and is not compared with others: list a `conflicts` entry. Two styles kernels of
|
|
79
|
+
one member that set one element property are an error (`kernels zap and view both set animation, ...: write those in
|
|
80
|
+
one kernel`). `atProperty` holds `@property` rules; `requires` holds BCD keys (warnings, never errors);
|
|
81
|
+
`{ key, exact: true }` counts a partial implementation as lacking.
|
|
82
|
+
|
|
83
|
+
A kernel cannot be conditional per site. A member whose style is only markers (no declarations: no method, no
|
|
84
|
+
arguments) under `hover()` or `media()` is a build error (`:hover wraps _x-core, the marker of kernel x.core, with no
|
|
85
|
+
declaration of its own`). Give the member methods that set custom properties; the kernel reads them, and a condition
|
|
86
|
+
can wrap a method.
|
|
87
|
+
|
|
88
|
+
## Members
|
|
89
|
+
|
|
90
|
+
- **Chain** `{ uses, chain: { m: { args, spacing?, excludes?, emit } } }`: `x().m(v)`, each method once. `args` is a
|
|
91
|
+
grammar of the parts (`""` for no value); `spacing: true` makes a bare number a 0.25rem step; `emit(v)` returns
|
|
92
|
+
`[prop, value][]` (custom properties unchecked, others checked). Write grammars as literals (`as const` on a
|
|
93
|
+
`const` string).
|
|
94
|
+
- **Call** `{ args, uses? }` plus `build({ x: (...args) => ({ decls?, uses?, globals?, themes?, layers? }) })`. `args`:
|
|
95
|
+
a grammar, `arg<T>()` (a TypeScript value, as is), or a tuple of positions: grammars, `"$tokens"`, `"$overrides"`
|
|
96
|
+
(checked against the `$tokens` before it), `"$selector"`, `arg<T>()`, and at the end one object of named leaves
|
|
97
|
+
(`{ "size?": "<length>" }`). Use `args: []` for a member with no arguments.
|
|
98
|
+
- **Value** `{ args, value, label? }`: returns a CSS value, not a class. `value` gets the parts as text (a token as
|
|
99
|
+
its `var(--name)`). With a tuple or `arg<T>()` write `fn({ args, value })`. Return a Typed OM value to keep the
|
|
100
|
+
type (`new CSSColorValue(...)`, `CSS.rem()`, `class X extends CSSStyleValue<"length">`, `CSSImageValue`, all from
|
|
101
|
+
`"cssints"` without the attribute in a plugin module); a string (a template literal too) is opaque to the types
|
|
102
|
+
and checked where it lands.
|
|
103
|
+
- **`group: true`** on a chain or call member adds the group marker `_g-<scope>`, so
|
|
104
|
+
`within(attr("data-stuck", "top", "~="), "<scope>")(...)` reads that element. One group per scope.
|
|
105
|
+
- **Typed properties**: a key that starts with `-` and a grammar (`"-webkit-font-smoothing": "auto | antialiased"`)
|
|
106
|
+
is exported camel-cased (`webkitFontSmoothing`). A property mdn-data knows is an error: use `css.<name>`.
|
|
107
|
+
- **Plain functions** the plugin exports (wrapping the user's styles, as `stuck(...styles)`) are fine; anything they
|
|
108
|
+
throw while a site runs is located at that site.
|
|
109
|
+
|
|
110
|
+
## Conflicts
|
|
111
|
+
|
|
112
|
+
- **Derived** from styles kernels: `cn(stack(), display("grid"))` gives
|
|
113
|
+
`layout: kernel stack-core sets display, flex-direction, gap (this style also sets display)`. Custom properties and
|
|
114
|
+
`nest()` rules on another subject are left out (a child's margin is not the element's; `.dark &` and `&.on` are
|
|
115
|
+
the element and count).
|
|
116
|
+
- **Explicit** `{ props: ["outline", /^outline-/], message: "ring() sets the outline: use its methods" }` in `uses`
|
|
117
|
+
**replaces** what the kernels derive; `props: []` allows everything. Needed for raw kernels, and when a member
|
|
118
|
+
emits a property its own kernel sets (the check reads the member's emits too).
|
|
119
|
+
|
|
120
|
+
## Runtimes: fallbacks for targets that lack a feature
|
|
121
|
+
|
|
122
|
+
`"#fb": { polyfills: [bcdKeys], css?, script?, parentVars? }` in a member's `uses`. A client module that uses the
|
|
123
|
+
member imports `virtual:cssints/runtime/<scope>.<entry>`; it is empty (`export {};`) unless a browserslist target
|
|
124
|
+
lacks a key.
|
|
125
|
+
|
|
126
|
+
- `css`: fallback rules, added to `_.k` only then (usually `@supports not (...) { & {...} }`); reaches every target.
|
|
127
|
+
- `script`: a module specifier the runtime module imports (`"@cssints/reveal/runtime"`), never evaluated at build
|
|
128
|
+
time. Ship it as a package subpath with a `development` condition for `src/`; it probes the feature itself
|
|
129
|
+
(`CSS.supports`) and does nothing where it is native. Reaches every target.
|
|
130
|
+
- `parentVars: { marker: "&", read, write, watch? }`: the shared `cssints/parent-vars` script copies custom
|
|
131
|
+
properties from parent to child; it stands on `@property`, so it reaches only targets that have it.
|
|
132
|
+
- Coverage: a `requires` key or a styles-kernel warning whose BCD key is in `polyfills` warns only for targets the
|
|
133
|
+
runtime does not reach. Match keys exactly (the key is in parentheses at the end of the warning).
|
|
134
|
+
- `lacking(...keys)` returns the build's targets that lack a key (`["firefox 140"]`, `[]` when none): emit fallback
|
|
135
|
+
rules or styles only when needed (`stuck()` adds its attribute rules only then).
|
|
136
|
+
- `dependsOn(path)`: call it for every file the plugin reads (an image, an SVG, a font). In dev an edit of the file
|
|
137
|
+
runs the modules that read it again; in `vite build --watch` it is a watched file.
|
|
138
|
+
|
|
139
|
+
## Errors
|
|
140
|
+
|
|
141
|
+
What `build`, `value`, `emit` or a kernel's styles throw is a located `MemberError` at the call, prefixed with the
|
|
142
|
+
scope. Throw `new Error("cssints: <scope>: <what>, <what to do>")`; name the value and the fix. The grammar errors of
|
|
143
|
+
`args` come for free (`ring.width: unexpected "red" ...`, a tuple's `tint.shade(2).by: ...`).
|
|
144
|
+
|
|
145
|
+
## Exporting, packaging
|
|
146
|
+
|
|
147
|
+
When the inferred type is too long to print (TS7056) or names an internal file (TS2742) in `vp pack`/`dts`:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const balanceScope = scope("balance", { ... }).build({ balance: () => ({}) });
|
|
151
|
+
type Members = ReturnType<typeof balanceScope.export>;
|
|
152
|
+
const members: Members = balanceScope.export();
|
|
153
|
+
export const balance: Members["balance"] = members.balance;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A package: `cssints` in `peerDependencies`; `exports` with `development` (src) before `types`/`default` (dist); a
|
|
157
|
+
`./runtime` subpath for a script (with `sideEffects` listing it); no `.css` file. Export the scope itself
|
|
158
|
+
(`export const motionScope = ...`) when another plugin should embed it.
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
Fixtures through the engine (output, errors, a re-run per `BROWSERSLIST`), a type test with `@ts-expect-error`, and
|
|
163
|
+
`test:browsers` with a control that must fail: [references/testing.md](references/testing.md).
|
|
164
|
+
|
|
165
|
+
## Checklist
|
|
166
|
+
|
|
167
|
+
- [ ] Scope name unique and lowercase; private keys for everything that is not a member.
|
|
168
|
+
- [ ] Kernels as styles unless one of the raw cases above; raw kernels have a `conflicts` entry.
|
|
169
|
+
- [ ] Every member that may go under a condition has a method or argument that emits a declaration.
|
|
170
|
+
- [ ] Defaults of custom properties set on the kernel (`vars()`), so a nested member does not inherit its parent's.
|
|
171
|
+
- [ ] `requires` lists the BCD keys; each has a runtime in `polyfills`, or the warning is intended.
|
|
172
|
+
- [ ] Fallback CSS and scripts only when `lacking()` or a runtime says so; a script probes and stays off natively.
|
|
173
|
+
- [ ] Errors name the scope, the value and the fix; grammars are literals.
|
|
174
|
+
- [ ] Fixtures: output, each error, `BROWSERSLIST` modern and old, the runtime module empty or not.
|
|
175
|
+
- [ ] Type test: valid calls typed `string` (or the value type), invalid ones under `@ts-expect-error`.
|
|
176
|
+
- [ ] Browser test (if there is a runtime or a visual claim): native, fallback forced, and a control that fails.
|
|
177
|
+
- [ ] README: example, the CSS it prints, rules, browsers, limits; `docs/API.md` table row if in the cssints repo.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# The entries of a `scope()` table
|
|
2
|
+
|
|
3
|
+
Entries are told apart by their shape. A key that starts with `#` is private (not exported, invisible to an embedding
|
|
4
|
+
scope); `uses` names it without the `#`. `--_x` is `--<scope>-x` everywhere in the scope; in raw CSS `&` is the
|
|
5
|
+
kernel's class and `&(mod.key)` the class of a kernel of an embedded scope.
|
|
6
|
+
|
|
7
|
+
| Shape | What it is |
|
|
8
|
+
| ------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
9
|
+
| `width: "<length [0,∞]> \| thin"` | A local syntax name: `<width>` in this scope's grammars |
|
|
10
|
+
| `"-webkit-font-smoothing": "auto \| antialiased"` | A typed property not in mdn-data, exported camel-cased (`webkitFontSmoothing`) |
|
|
11
|
+
| `{ css, atProperty?, requires? }` | A kernel: `css` a string (raw) or `(css) => style \| style[]` |
|
|
12
|
+
| `{ rule, layer? }` | A global rule (no class), identified by its content |
|
|
13
|
+
| `{ layers: ["app", "viewer"], before? }` | Top-level layers, after the engine's (or before with `before: true`) |
|
|
14
|
+
| `{ polyfills, css?, script?, parentVars? }` | A runtime: the fallback for targets that lack a BCD key |
|
|
15
|
+
| `{ props, message }` | Conflicts: a `cn()` with the member and one of `props` (names or RegExps) is an error |
|
|
16
|
+
| `{ tokens }` | A token set from `createTokens`: `<tokens.color.accent>` in grammars is that token's type |
|
|
17
|
+
| `mod: otherScope` | An embedded scope: `uses: ["mod.key"]`, `<mod.name>` in grammars, `&(mod.key)` in raw CSS |
|
|
18
|
+
| `{ uses?, group?, chain: { ... } }` | A chain member: `x().m(v).n(w)` |
|
|
19
|
+
| `{ args, uses?, group? }` | A call member: its function is the entry of the same name in `.build({ ... })` |
|
|
20
|
+
| `{ args, value, label? }` / `fn({ ... })` | A value member: returns a CSS value (`fn()` when `args` is a tuple or `arg<T>()`) |
|
|
21
|
+
|
|
22
|
+
## Kernels
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
"#core": {
|
|
26
|
+
css: (css) => [
|
|
27
|
+
css.vars({ "--_gap": "1rem", "--_fade": fade }), // defaults on the element; a keyframes() name carries its rule
|
|
28
|
+
css.display("flex"),
|
|
29
|
+
css.animation("var(--_fade) 200ms"),
|
|
30
|
+
css.hover(css.opacity(0.8)),
|
|
31
|
+
css.media("(width >= 40rem)")(css.padding("1rem")),
|
|
32
|
+
css.nest("& > * + *")(css.marginInlineStart("var(--_gap)")), // another subject: & anywhere
|
|
33
|
+
],
|
|
34
|
+
requires: ["css.at-rules.property", { key: "css.types.color.oklch.relative_syntax", exact: true }],
|
|
35
|
+
},
|
|
36
|
+
"#raw": {
|
|
37
|
+
css: "& { --_x: 1 } &(base.mark) > & { --_y: 2 }",
|
|
38
|
+
atProperty: '@property --_w { syntax: "<length>"; inherits: false; initial-value: 2px; }',
|
|
39
|
+
},
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- Printed once in `_.k`, sorted by key (`<scope>.<entry>`), whatever the order of `uses`; at-rule kernels
|
|
43
|
+
(`keyframes()`) before the kernel that names them. A styles kernel prints in the sheet's order (a shorthand before
|
|
44
|
+
its longhands; fewer conditions first), so the order you write does not matter.
|
|
45
|
+
- Not allowed in a styles kernel: `firstThatWorks()`, a member's marker (another plugin member), `nest()` without an
|
|
46
|
+
`&` or with a selector list.
|
|
47
|
+
- Build errors: two keys that declare one custom property, two keys that print one marker, two styles kernels of one
|
|
48
|
+
member that set one element property.
|
|
49
|
+
|
|
50
|
+
## Chain members
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
ring: {
|
|
54
|
+
uses: ["core", "fallback"],
|
|
55
|
+
group: true, // adds _g-<scope> after the kernel markers
|
|
56
|
+
chain: {
|
|
57
|
+
width: { args: "<width>", spacing: true, emit: (v) => [["--_w", v]] },
|
|
58
|
+
color: { args: "<color>", emit: (v) => [["--_c", v]] },
|
|
59
|
+
plain: { args: "", excludes: ["color"], emit: () => [["--_c", "currentColor"]] }, // no value; color() no longer offered
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`emit` returns `[property, value][]`: a custom property's value is not checked, any other property is checked like a
|
|
65
|
+
`css.*` call (and counts for conflicts).
|
|
66
|
+
|
|
67
|
+
## Call members
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
look: { args: ["<color>", "$tokens", "$overrides", { "size?": "<base.gap>", "in?": "app | viewer" }], uses: ["order"] },
|
|
71
|
+
icon: { args: arg<"home" | "star">() },
|
|
72
|
+
reset: { args: [{ "media?": arg<boolean>(), "prose?": arg<boolean>() }] },
|
|
73
|
+
// ...
|
|
74
|
+
.build({
|
|
75
|
+
look: (color, tokens, overrides, { size, in: source }) => ({
|
|
76
|
+
decls: [["--_c", color], ["--_s", size ?? "0"]],
|
|
77
|
+
uses: ["base.mark"],
|
|
78
|
+
themes: [{ tokens, overrides, selector: ":root", layer: source ?? "app" }],
|
|
79
|
+
}),
|
|
80
|
+
icon: (name) => ({ decls: [["--_icon", name]] }),
|
|
81
|
+
reset: ({ media }) => ({
|
|
82
|
+
layers: { layers: ["reset"], before: true },
|
|
83
|
+
globals: media ? [{ css: "img { display: block }", layer: "reset" }] : [],
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`build` returns `{ decls?, uses?, globals?, themes?, layers? }`:
|
|
89
|
+
|
|
90
|
+
- `decls`: declarations of the member's own class (in `_.a`, or under the user's condition).
|
|
91
|
+
- `uses`: more kernels, globals, layers or runtimes, by key or `mod.key`.
|
|
92
|
+
- `globals: { css, layer? }[]`: rules with no class, identified by content; may set one custom property in several
|
|
93
|
+
layers (looks).
|
|
94
|
+
- `themes: { tokens, overrides, selector, layer?, scope? }[]`: `createGlobalTheme` made by a member, checked against
|
|
95
|
+
the token set and read by `check.contrast`.
|
|
96
|
+
- `layers`: a list, or `{ layers, before: true }`.
|
|
97
|
+
|
|
98
|
+
The positional form puts a type error on the call, with the position in the message (`look(2): ...`), not on the
|
|
99
|
+
argument. A grammar part arrives as checked text; a token as its `var(--name)`.
|
|
100
|
+
|
|
101
|
+
## Value members
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
tint: { args: "<color> <percentage>", value: ([color, share]) => new CSSColorValue(`color-mix(in oklab, ${color}, white ${share})`) },
|
|
105
|
+
shade: fn({
|
|
106
|
+
args: ["<color>", { "by?": "<percentage>", "dark?": arg<boolean>() }],
|
|
107
|
+
label: "tint.shade",
|
|
108
|
+
value: ([c, { by = "10%", dark }]): string => `color-mix(in oklab, ${c}, ${dark ? "black" : "white"} ${by})`,
|
|
109
|
+
}),
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- With a grammar `args`, the parts are joined by a space, so `"<color> <color>"` cannot tell `"red", "blue"` from
|
|
113
|
+
`"red blue"`: check `parts.length` or use a tuple.
|
|
114
|
+
- `value` runs at build time, once per call; the call is replaced by its CSS text. It throws for values the grammar
|
|
115
|
+
cannot reject (units it cannot compute, a failed accessibility rule): `fluid` rejects `em` and fails WCAG 1.4.4.
|
|
116
|
+
- What `value` returns must be a string or a Typed OM value (`cssints: tint() value must return a string or a Typed OM
|
|
117
|
+
value`). A result that does not fit the property is that property's error at the same site.
|
|
118
|
+
|
|
119
|
+
## Identity and HMR
|
|
120
|
+
|
|
121
|
+
A scope is its content (the table, the source of its functions, the scopes it embeds), hashed. Running the module
|
|
122
|
+
again with the same content is nothing; another content under the same name replaces it (an edit in dev). Two
|
|
123
|
+
modules that define one name with different content: `scope "x" is defined twice with different content`. A closure
|
|
124
|
+
the hash does not see (icons' collection) is fine as long as the table and code are the same.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Testing a plugin
|
|
2
|
+
|
|
3
|
+
Three layers, as every `@cssints/*` package does them (`packages/balance`, `packages/stuck`, `packages/squircle` in
|
|
4
|
+
the cssints repo are the models): fixtures through the engine, a type test, and a browser test when there is a
|
|
5
|
+
runtime or a visual claim. Tests are plain `node` scripts with `node:assert`; no test runner.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
packages/<name>/
|
|
9
|
+
src/index.ts the scope
|
|
10
|
+
src/runtime.ts a browser script, if any (no imports)
|
|
11
|
+
test/<name>.mjs fixtures through the engine
|
|
12
|
+
test/fixtures/*.ts modules that use the members (some invalid on purpose)
|
|
13
|
+
test/types/<name>.ts the type test
|
|
14
|
+
test/browsers.mjs Chrome, Firefox and WebKit (optional)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Fixtures through the engine
|
|
18
|
+
|
|
19
|
+
A Vite dev server in middleware mode with the plugin, a client transform per fixture, and the sheet of a module:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import assert from "node:assert/strict";
|
|
23
|
+
import { execFileSync } from "node:child_process";
|
|
24
|
+
import cssints from "cssints/vite";
|
|
25
|
+
import { createLogger, createServer } from "vite";
|
|
26
|
+
|
|
27
|
+
const root = `${import.meta.dirname}/fixtures`;
|
|
28
|
+
const mode = process.argv[2];
|
|
29
|
+
const warnings = [];
|
|
30
|
+
const plugins = cssints();
|
|
31
|
+
const server = await createServer({
|
|
32
|
+
root,
|
|
33
|
+
configFile: false,
|
|
34
|
+
customLogger: { ...createLogger("warn"), warn: (m) => warnings.push(m) },
|
|
35
|
+
server: { middlewareMode: true, watch: null, ws: false },
|
|
36
|
+
plugins,
|
|
37
|
+
});
|
|
38
|
+
const client = server.environments.client;
|
|
39
|
+
const code = async (f) => (await client.transformRequest(`/${f}`)).code;
|
|
40
|
+
const sheet = (f) => plugins[0].api.sheet([`${root}/${f}`]);
|
|
41
|
+
const flat = (css) => css.replace(/\s+/g, " ").trim();
|
|
42
|
+
|
|
43
|
+
const use = await code("use.ts"); // class strings in place of the calls
|
|
44
|
+
const out = flat(sheet("use.ts")); // the CSS of that module
|
|
45
|
+
const runtime = (await client.transformRequest("virtual:cssints/runtime/x.fb")).code;
|
|
46
|
+
|
|
47
|
+
assert.match(use, /export const a = "_x-core _\w+";/);
|
|
48
|
+
assert.ok(out.includes("._x-core{display:flex}"), out);
|
|
49
|
+
assert.deepEqual(warnings, []); // no support warning left uncovered
|
|
50
|
+
await assert.rejects(
|
|
51
|
+
code("conflict.ts"),
|
|
52
|
+
/x: kernel core sets display \(this style also sets display\)/,
|
|
53
|
+
);
|
|
54
|
+
await assert.rejects(code("condition.ts"), /:hover wraps _x-core, the marker of kernel x\.core/);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then the targets: run the same file again per browserslist, in a child process (`BROWSERSLIST` is read once per
|
|
58
|
+
process; a Vite root with its own `.browserslistrc` works in one process too):
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
if (mode) {
|
|
62
|
+
assert.equal(runtime === "export {};", mode === "native"); // empty where every target has the feature
|
|
63
|
+
assert.equal(out.includes("@supports not"), mode === "old"); // fallback CSS only where needed
|
|
64
|
+
await server.close();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
await server.close();
|
|
68
|
+
for (const [m, targets] of [
|
|
69
|
+
["native", "chrome 130, safari 18, firefox 130"],
|
|
70
|
+
["old", "chrome 110, safari 17.3, firefox 120"],
|
|
71
|
+
])
|
|
72
|
+
execFileSync(process.execPath, [import.meta.filename, m], {
|
|
73
|
+
env: { ...process.env, BROWSERSLIST: targets },
|
|
74
|
+
stdio: "inherit",
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
What to cover: the output of each member and method; each build error (`assert.rejects` with the message); the
|
|
79
|
+
warnings for the default targets (none, or the intended ones); the runtime module and the fallback CSS for a modern
|
|
80
|
+
and an old target set; a member under a condition (a method: fine; marker only: the error); nesting (a member inside
|
|
81
|
+
another one does not inherit its custom properties).
|
|
82
|
+
|
|
83
|
+
To look at one fixture by hand inside the cssints repo: `cd packages/cssints/test && node run.mjs fixtures/x.ts`
|
|
84
|
+
prints the code and the CSS (a plugin fixture must live where `cssints` resolves). With the MCP server, `compile`.
|
|
85
|
+
|
|
86
|
+
## The type test
|
|
87
|
+
|
|
88
|
+
`test/types/<name>.ts`, checked by the package's `typecheck` (`tsgo -p tsconfig.json --noEmit`, with
|
|
89
|
+
`test/types/*.ts` in `include`):
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { ring } from "../../src/index.ts";
|
|
93
|
+
|
|
94
|
+
export const a: string = ring().width("2px").color("red");
|
|
95
|
+
// @ts-expect-error not a width
|
|
96
|
+
export const e1 = ring().width("red");
|
|
97
|
+
// @ts-expect-error a method is used once
|
|
98
|
+
export const e2 = ring().width("1px").width("2px");
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A value member's result goes into a property in the type test (`color(tint("red", "20%"))`), to catch a result type
|
|
102
|
+
the grammar rejects. Some errors only the build sees (a range in some positions, `em` where px is computed): test
|
|
103
|
+
those in the fixtures, not with `@ts-expect-error`.
|
|
104
|
+
|
|
105
|
+
## Browser tests
|
|
106
|
+
|
|
107
|
+
`test/browsers.mjs`, run by `bun run test:browsers` (not in `test`: it needs the browsers). The shape:
|
|
108
|
+
|
|
109
|
+
1. Build a fixture app with `vite build` (`write: false`) and a `BROWSERSLIST` that makes the fallback ship; set
|
|
110
|
+
`NODE_ENV=development` so the runtime resolves to `src/`.
|
|
111
|
+
2. Assert on the built CSS and JS first (the `@supports not` rule is there, the script is in the bundle once).
|
|
112
|
+
3. Open it in Chrome (`chromium.launch({ channel: "chrome" })`), Firefox and WebKit from `playwright-core`.
|
|
113
|
+
4. For each browser, three runs:
|
|
114
|
+
- **native**: the feature works by itself, the fallback stays off;
|
|
115
|
+
- **fallback forced**: the native feature switched off (an `!important` override, or the declaration cut out of
|
|
116
|
+
the CSS), the runtime or fallback CSS must produce the same result;
|
|
117
|
+
- **control**: the same, with the fallback disabled (its observers replaced by no-ops, its rules removed). This
|
|
118
|
+
run **must fail** the check: it proves that the test can see the difference.
|
|
119
|
+
5. Measure something visible: line widths, pixels of a screenshot, computed styles, the phase of an animation.
|
|
120
|
+
|
|
121
|
+
A check without a control that fails is not a test of the fallback. `BROWSERS=chrome,webkit node test/browsers.mjs`
|
|
122
|
+
runs some.
|