cssints 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{author-types-D9IjEXwn.d.mts → author-types-X4zGpNpU.d.mts} +11 -1
- package/dist/clay.mjs +2 -2
- package/dist/{contrast-B1QrUfME.mjs → contrast-BB0wimFI.mjs} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +18 -2
- package/dist/{node-CnkQisiV.d.mts → node-CWwn8PaX.d.mts} +1 -1
- package/dist/plugin-types.d.mts +29 -3
- package/dist/plugin.d.mts +3 -3
- package/dist/plugin.mjs +14 -1
- package/dist/{scope-CYtpn6RD.d.mts → scope-Oxd_stwQ.d.mts} +20 -4
- package/dist/{sheet-BVksF2ST.mjs → sheet-rwl57DmD.mjs} +6 -1
- package/dist/surface.d.mts +1 -1
- package/dist/{theme-Le0IAI8N.mjs → theme-ClgusumY.mjs} +1 -1
- package/dist/tui.mjs +3 -3
- package/dist/vite.d.mts +2 -2
- package/dist/vite.mjs +2 -2
- package/docs/API.md +190 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -223,7 +223,7 @@ cssints is MIT. It stands on other people's work; this is what we took, from who
|
|
|
223
223
|
|
|
224
224
|
No code is copied from these; each package README says which part follows which.
|
|
225
225
|
|
|
226
|
-
- **Plugin packages.** [Every Layout](https://every-layout.dev) and [smolcss](https://smolcss.dev) (layout), [nytimes/text-balancer](https://github.com/nytimes/text-balancer) and [react-wrap-balancer](https://github.com/shuding/react-wrap-balancer) (balance), [fluid-tailwind](https://github.com/barvian/fluid-tailwind) (fluid), [postcss-easing-gradients](https://github.com/larsenwork/postcss-easing-gradients) (gradient), [tailwindcss-motion](https://github.com/romboHQ/tailwindcss-motion) and [tailwindcss-animate](https://github.com/jamiebuilds/tailwindcss-animate) (motion), [the supersonic plugin](https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation) and [lax.js](https://github.com/alexfoxy/lax.js) (reveal), [sync-skeleton](https://github.com/crutchcorn/sync-skeleton) (skeleton), [bramus/sticky-observer](https://github.com/bramus/sticky-observer) (stuck), [unpic-placeholder](https://github.com/ascorbic/unpic-placeholder) and [ThumbHash](https://github.com/evanw/thumbhash) (placeholder), [nutelch](https://github.com/meodai/nutelch) and [Leonardo](https://github.com/adobe/leonardo) (ramp), the resets of Josh Comeau, Jake Lazaroff, Vale and Andy Bell (reset), [Mise en Mode](https://standard.mode.place) and its Mode Contract (mise-en-mode), [brendonovich/vite-plugin-opencode](https://github.com/Brendonovich/vite-plugin-opencode) (the prompt box of devtools).
|
|
226
|
+
- **Plugin packages.** [Every Layout](https://every-layout.dev) and [smolcss](https://smolcss.dev) (layout), [nytimes/text-balancer](https://github.com/nytimes/text-balancer) and [react-wrap-balancer](https://github.com/shuding/react-wrap-balancer) (balance), [fluid-tailwind](https://github.com/barvian/fluid-tailwind) (fluid), [postcss-easing-gradients](https://github.com/larsenwork/postcss-easing-gradients) (gradient), [tailwindcss-motion](https://github.com/romboHQ/tailwindcss-motion) and [tailwindcss-animate](https://github.com/jamiebuilds/tailwindcss-animate) (motion), [the supersonic plugin](https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation) and [lax.js](https://github.com/alexfoxy/lax.js) (reveal), [sync-skeleton](https://github.com/crutchcorn/sync-skeleton) (skeleton), [bramus/sticky-observer](https://github.com/bramus/sticky-observer) (stuck), [unpic-placeholder](https://github.com/ascorbic/unpic-placeholder) and [ThumbHash](https://github.com/evanw/thumbhash) (placeholder), [nutelch](https://github.com/meodai/nutelch) and [Leonardo](https://github.com/adobe/leonardo) (ramp), the resets of Josh Comeau, Jake Lazaroff, Vale and Andy Bell (reset), [Mise en Mode](https://standard.mode.place) and its Mode Contract (mise-en-mode), [Reactive CSS](https://reactivecss.com) (reactive), [brendonovich/vite-plugin-opencode](https://github.com/Brendonovich/vite-plugin-opencode) (the prompt box of devtools).
|
|
227
227
|
- **The engine.** The comparisons in [docs/research](docs/research) record what we took from [StyleX](https://stylexjs.com), [vanilla-extract](https://vanilla-extract.style), [Tailwind CSS](https://tailwindcss.com), [Tokenami](https://tokenami.com), [SugarCube](https://sugarcube.sh) and the [React Spectrum](https://react-spectrum.adobe.com) style macros; the first spark was [Haz's post](https://bsky.app/profile/haz.dev/post/3mebm2mx6jk27) on a CSS framework in TypeScript. Tokens follow the [Design Tokens Community Group](https://www.designtokens.org) format.
|
|
228
228
|
- **apps/csssky** follows the look of Mu and the hue slider of Witchsky.
|
|
229
229
|
|
|
@@ -70,9 +70,19 @@ interface Runtime {
|
|
|
70
70
|
/** The plugin's browser script, a module specifier the app resolves (`"@cssints/balance/runtime"`): imported, never evaluated at build time. */
|
|
71
71
|
script?: string;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* A value a site ships as a JavaScript object of its own: `ship({ owner, open })` (`@cssints/reactive`'s `state()`, whose
|
|
75
|
+
* owner is a style and whose sources are data). Each `Style` becomes its class string, an array or an object keeps its
|
|
76
|
+
* shape, and anything else is its JSON.
|
|
77
|
+
*/
|
|
78
|
+
interface Ship {
|
|
79
|
+
readonly "~ship": {
|
|
80
|
+
readonly [key: string]: unknown;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
73
83
|
/** A build error when one `cn()` holds the member's marker and a property listed here (a name, or a pattern). */
|
|
74
84
|
interface Conflict {
|
|
75
85
|
props: (string | RegExp)[];
|
|
76
86
|
message: string;
|
|
77
87
|
}
|
|
78
|
-
export { Kernel as a, GlobalRule as i, Conflict as n, Runtime as o, Decl as r,
|
|
88
|
+
export { Kernel as a, ThemeRule as c, GlobalRule as i, Conflict as n, Runtime as o, Decl as r, Ship as s, BcdKey as t };
|
package/dist/clay.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as registry } from "./registry-8sYvlzyn.mjs";
|
|
2
|
-
import { a as createTokens$1, i as createGlobalTheme$1, j as fail, s as sets } from "./theme-
|
|
3
|
-
import { n as valueOf, t as together } from "./contrast-
|
|
2
|
+
import { a as createTokens$1, i as createGlobalTheme$1, j as fail, s as sets } from "./theme-ClgusumY.mjs";
|
|
3
|
+
import { n as valueOf, t as together } from "./contrast-BB0wimFI.mjs";
|
|
4
4
|
import { n as parseColor } from "./color-BB8ipynT.mjs";
|
|
5
5
|
import { Formatter, Predicate } from "effect";
|
|
6
6
|
import { clampRgb, formatHex } from "culori/fn";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as registry, d as MacroError, o as warn } from "./registry-8sYvlzyn.mjs";
|
|
2
|
-
import { l as layerStatements } from "./sheet-
|
|
2
|
+
import { l as layerStatements } from "./sheet-rwl57DmD.mjs";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
4
|
// src/contrast.ts
|
|
5
5
|
const VAR = /var\((--[^\s,)]+)\)/g;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as Kernel } from "./author-types-
|
|
1
|
+
import { a as Kernel } from "./author-types-X4zGpNpU.mjs";
|
|
2
2
|
import { n as _cv, r as _cx } from "./runtime-Cv7hGrWf.mjs";
|
|
3
|
-
import { n as At, t as CssNode } from "./node-
|
|
3
|
+
import { n as At, t as CssNode } from "./node-CWwn8PaX.mjs";
|
|
4
4
|
import { CSS, CSSColorValue, CSSImageValue, CSSStyleValue, CSSUnitValue } from "@cssints/css-grammar/values";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
/** What firstThatWorks() builds: its values as text, the wanted one first. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as registry, d as MacroError, f as MemberError, o as warn, r as forgetKernels, t as Registry } from "./registry-8sYvlzyn.mjs";
|
|
2
|
-
import { A as camel, C as hashClass, D as settled, E as minWidth, F as probed, M as failIn, N as isNode, O as toDecls, P as node, S as edge, T as merged, _ as PSEUDOS, a as createTokens, b as checkVars, c as tokensOf, d as style, f as token, g as PROPS, h as LAYER, i as createGlobalTheme, j as fail, k as VAR, l as CSSTokenValue, m as FUNCTIONS, n as addTheme, o as isDone, p as CONTEXTS, r as check$1, t as DONE, u as isStyle, v as carried, w as isFallback, x as dropsOf, y as chainDecl } from "./theme-
|
|
3
|
-
import { D as relation, S as addRuntime, _ as addKernel, i as byOrder, n as addGlobal, r as addLayers, y as kernelWarnings } from "./sheet-
|
|
2
|
+
import { A as camel, C as hashClass, D as settled, E as minWidth, F as probed, M as failIn, N as isNode, O as toDecls, P as node, S as edge, T as merged, _ as PSEUDOS, a as createTokens, b as checkVars, c as tokensOf, d as style, f as token, g as PROPS, h as LAYER, i as createGlobalTheme, j as fail, k as VAR, l as CSSTokenValue, m as FUNCTIONS, n as addTheme, o as isDone, p as CONTEXTS, r as check$1, t as DONE, u as isStyle, v as carried, w as isFallback, x as dropsOf, y as chainDecl } from "./theme-ClgusumY.mjs";
|
|
3
|
+
import { D as relation, S as addRuntime, _ as addKernel, i as byOrder, n as addGlobal, r as addLayers, y as kernelWarnings } from "./sheet-rwl57DmD.mjs";
|
|
4
4
|
import { n as loweredOf } from "./lowered-DOJEDRFM.mjs";
|
|
5
5
|
import { i as SCV_CALL, l as groups$1, n as CX_CALL, t as CV_CALL } from "./instrument-QGVM1E1B.mjs";
|
|
6
6
|
import { checkDeclarationNow, checkQuery, checkSelector, checkValueNow, isDeclarationError } from "@cssints/css-grammar/check";
|
|
@@ -1285,11 +1285,27 @@ const inlineOf = Effect.fnUntraced(function* ({ __style: { sets, decls } }) {
|
|
|
1285
1285
|
});
|
|
1286
1286
|
});
|
|
1287
1287
|
const isPluginValue = Predicate.isObjectKeyword;
|
|
1288
|
+
const isShip = (v) => Predicate.hasProperty(v, "~ship");
|
|
1289
|
+
const shippedOf = Effect.fnUntraced(function* (value, at) {
|
|
1290
|
+
if (isNode(value)) return Formatter.formatJson(yield* classesOf(value, at));
|
|
1291
|
+
if (Array.isArray(value)) {
|
|
1292
|
+
const parts = [];
|
|
1293
|
+
for (const v of value) parts.push(yield* shippedOf(v, at));
|
|
1294
|
+
return `[${parts.join(",")}]`;
|
|
1295
|
+
}
|
|
1296
|
+
if (Predicate.isObject(value) && !(value instanceof CSSStyleValue)) {
|
|
1297
|
+
const parts = [];
|
|
1298
|
+
for (const [k, v] of Object.entries(value)) parts.push(`${Formatter.formatJson(k)}:${yield* shippedOf(v, at)}`);
|
|
1299
|
+
return `{${parts.join(",")}}`;
|
|
1300
|
+
}
|
|
1301
|
+
return Formatter.formatJson(value);
|
|
1302
|
+
});
|
|
1288
1303
|
const replacementOf = Effect.fnUntraced(function* (at, value) {
|
|
1289
1304
|
if (isNode(value)) return Formatter.formatJson(yield* classesOf(value, at));
|
|
1290
1305
|
if (isCv(value)) return `${CV_CALL}${yield* cvOf(value.__cv, at)})`;
|
|
1291
1306
|
if (isScv(value)) return `${SCV_CALL}${yield* scvOf(value.__scv, at)})`;
|
|
1292
1307
|
if (isGroup(value)) return yield* headOf(value, at);
|
|
1308
|
+
if (isShip(value)) return yield* shippedOf(value["~ship"], at);
|
|
1293
1309
|
if (isStyle(value)) return yield* inlineOf(value);
|
|
1294
1310
|
const tokens = tokensOf(value);
|
|
1295
1311
|
if (tokens !== void 0) return tokens;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as Kernel, i as GlobalRule, n as Conflict, o as Runtime
|
|
1
|
+
import { a as Kernel, c as ThemeRule, i as GlobalRule, n as Conflict, o as Runtime } from "./author-types-X4zGpNpU.mjs";
|
|
2
2
|
import { Warning } from "@cssints/css-grammar/support";
|
|
3
3
|
import { Context } from "effect";
|
|
4
4
|
import { TokenType } from "@cssints/css-grammar/token";
|
package/dist/plugin-types.d.mts
CHANGED
|
@@ -74,6 +74,16 @@ interface Runtime {
|
|
|
74
74
|
/** The plugin's browser script, a module specifier the app resolves (`"@cssints/balance/runtime"`): imported, never evaluated at build time. */
|
|
75
75
|
script?: string;
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* A value a site ships as a JavaScript object of its own: `ship({ owner, open })` (`@cssints/reactive`'s `state()`, whose
|
|
79
|
+
* owner is a style and whose sources are data). Each `Style` becomes its class string, an array or an object keeps its
|
|
80
|
+
* shape, and anything else is its JSON.
|
|
81
|
+
*/
|
|
82
|
+
interface Ship {
|
|
83
|
+
readonly "~ship": {
|
|
84
|
+
readonly [key: string]: unknown;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
77
87
|
/** A build error when one `cn()` holds the member's marker and a property listed here (a name, or a pattern). */
|
|
78
88
|
interface Conflict {
|
|
79
89
|
props: (string | RegExp)[];
|
|
@@ -129,12 +139,19 @@ interface ParentVar {
|
|
|
129
139
|
write: `--_${string}`;
|
|
130
140
|
watch?: `--_${string}`;
|
|
131
141
|
}
|
|
132
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* The browser half, delivered when a target lacks a polyfilled feature (`virtual:cssints/runtime/<scope>.<entry>`); `&` is
|
|
144
|
+
* the marker of the member's first kernel. A runtime with a `script` (or `css`) and no `polyfills` is the plugin's own
|
|
145
|
+
* face, not a fallback: every target gets it.
|
|
146
|
+
*/
|
|
133
147
|
interface RuntimeDef {
|
|
134
148
|
polyfills: BcdKey[];
|
|
135
149
|
parentVars?: ParentVar[];
|
|
136
150
|
css?: string;
|
|
137
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* A module specifier the app resolves (a package subpath, `"@cssints/balance/runtime"`): imported when a target lacks a
|
|
153
|
+
* polyfilled feature, and by every target when `polyfills` is empty.
|
|
154
|
+
*/
|
|
138
155
|
script?: string;
|
|
139
156
|
}
|
|
140
157
|
/** A build error when one `cn()` holds the member's marker and a property listed here (a name, or a pattern). */
|
|
@@ -377,4 +394,13 @@ export declare const dependsOn: (path: string) => void;
|
|
|
377
394
|
* `scope()`; an unknown group is an error.
|
|
378
395
|
*/
|
|
379
396
|
export declare const inGroup: (group: string, property: string, value: string) => boolean;
|
|
380
|
-
|
|
397
|
+
/**
|
|
398
|
+
* `ship({ owner, open })`: what a member returns when a site has more to give than a class string — `@cssints/reactive`'s
|
|
399
|
+
* `state()`, whose `owner` is a style and whose sources are data. The client module gets an object literal: each style is
|
|
400
|
+
* its class string, an array or an object keeps its shape, and anything else is its JSON. At build time the object is the
|
|
401
|
+
* entries themselves. Build time, like `scope()`.
|
|
402
|
+
*/
|
|
403
|
+
export declare const ship: (entries: {
|
|
404
|
+
readonly [key: string]: unknown;
|
|
405
|
+
}) => Ship;
|
|
406
|
+
export type { ArgsDef, Chain, Conflict, Css, Decl, Entry, GlobalRule, Impl, Kernel, KernelDef, Made, Members, Part, Pending, Runtime, Scope, Ship, Style, Table, ThemeRule, Typed };
|
package/dist/plugin.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as inGroup, a as Entry, b as
|
|
2
|
-
import { a as Kernel, i as GlobalRule, n as Conflict, o as Runtime, r as Decl, s as
|
|
3
|
-
export { type ArgsDef, type Chain, type Conflict, type Css, type Decl, type Entry, type GlobalRule, type Impl, type Kernel, type KernelDef, type Made, type Members, type Part, type Pending, type Runtime, type Scope, type Style, type Table, type ThemeRule, type Typed, arg, dependsOn, fn, inGroup, lacking, scope };
|
|
1
|
+
import { S as Style, _ as inGroup, a as Entry, b as ship, c as Made, d as Scope, f as Table, g as fn, h as dependsOn, i as Css, l as Members, m as arg, n as ArgsDef, o as Impl, p as Typed, r as Chain, s as KernelDef, u as Pending, v as lacking, x as Part, y as scope } from "./scope-Oxd_stwQ.mjs";
|
|
2
|
+
import { a as Kernel, c as ThemeRule, i as GlobalRule, n as Conflict, o as Runtime, r as Decl, s as Ship } from "./author-types-X4zGpNpU.mjs";
|
|
3
|
+
export { type ArgsDef, type Chain, type Conflict, type Css, type Decl, type Entry, type GlobalRule, type Impl, type Kernel, type KernelDef, type Made, type Members, type Part, type Pending, type Runtime, type Scope, type Ship, type Style, type Table, type ThemeRule, type Typed, arg, dependsOn, fn, inGroup, lacking, scope, ship };
|
package/dist/plugin.mjs
CHANGED
|
@@ -33,4 +33,17 @@ const dependsOn = (path) => engine().dependsOn(path);
|
|
|
33
33
|
* `scope()`; an unknown group is an error.
|
|
34
34
|
*/
|
|
35
35
|
const inGroup = (group, property, value) => engine().inGroup(group, property, value);
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* `ship({ owner, open })`: what a member returns when a site has more to give than a class string — `@cssints/reactive`'s
|
|
38
|
+
* `state()`, whose `owner` is a style and whose sources are data. The client module gets an object literal: each style is
|
|
39
|
+
* its class string, an array or an object keeps its shape, and anything else is its JSON. At build time the object is the
|
|
40
|
+
* entries themselves. Build time, like `scope()`.
|
|
41
|
+
*/
|
|
42
|
+
const ship = (entries) => {
|
|
43
|
+
Object.defineProperty(entries, "~ship", {
|
|
44
|
+
value: entries,
|
|
45
|
+
enumerable: false
|
|
46
|
+
});
|
|
47
|
+
return entries;
|
|
48
|
+
};
|
|
49
|
+
export { arg, dependsOn, fn, inGroup, lacking, scope, ship };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Decl, t as BcdKey } from "./author-types-
|
|
1
|
+
import { r as Decl, s as Ship, t as BcdKey } from "./author-types-X4zGpNpU.mjs";
|
|
2
2
|
import { t as ClassValue } from "./runtime-Cv7hGrWf.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { CSS, CSS as CSS$1, CSSColorValue, CSSColorValue as CSSColorValue$1, CSSImageValue, CSSImageValue as CSSImageValue$1, CSSStyleValue, CSSStyleValue as CSSStyleValue$1, CSSUnitValue, CSSUnitValue as CSSUnitValue$1 } from "@cssints/css-grammar/values";
|
|
@@ -3546,12 +3546,19 @@ interface ParentVar {
|
|
|
3546
3546
|
write: `--_${string}`;
|
|
3547
3547
|
watch?: `--_${string}`;
|
|
3548
3548
|
}
|
|
3549
|
-
/**
|
|
3549
|
+
/**
|
|
3550
|
+
* The browser half, delivered when a target lacks a polyfilled feature (`virtual:cssints/runtime/<scope>.<entry>`); `&` is
|
|
3551
|
+
* the marker of the member's first kernel. A runtime with a `script` (or `css`) and no `polyfills` is the plugin's own
|
|
3552
|
+
* face, not a fallback: every target gets it.
|
|
3553
|
+
*/
|
|
3550
3554
|
interface RuntimeDef {
|
|
3551
3555
|
polyfills: BcdKey[];
|
|
3552
3556
|
parentVars?: ParentVar[];
|
|
3553
3557
|
css?: string;
|
|
3554
|
-
/**
|
|
3558
|
+
/**
|
|
3559
|
+
* A module specifier the app resolves (a package subpath, `"@cssints/balance/runtime"`): imported when a target lacks a
|
|
3560
|
+
* polyfilled feature, and by every target when `polyfills` is empty.
|
|
3561
|
+
*/
|
|
3555
3562
|
script?: string;
|
|
3556
3563
|
}
|
|
3557
3564
|
/** A build error when one `cn()` holds the member's marker and a property listed here (a name, or a pattern). */
|
|
@@ -3794,4 +3801,13 @@ declare const dependsOn: (path: string) => void;
|
|
|
3794
3801
|
* `scope()`; an unknown group is an error.
|
|
3795
3802
|
*/
|
|
3796
3803
|
declare const inGroup: (group: string, property: string, value: string) => boolean;
|
|
3797
|
-
|
|
3804
|
+
/**
|
|
3805
|
+
* `ship({ owner, open })`: what a member returns when a site has more to give than a class string — `@cssints/reactive`'s
|
|
3806
|
+
* `state()`, whose `owner` is a style and whose sources are data. The client module gets an object literal: each style is
|
|
3807
|
+
* its class string, an array or an object keeps its shape, and anything else is its JSON. At build time the object is the
|
|
3808
|
+
* entries themselves. Build time, like `scope()`.
|
|
3809
|
+
*/
|
|
3810
|
+
declare const ship: (entries: {
|
|
3811
|
+
readonly [key: string]: unknown;
|
|
3812
|
+
}) => Ship;
|
|
3813
|
+
export { Style as S, inGroup as _, Entry as a, ship as b, Made as c, Scope as d, Table as f, fn as g, dependsOn as h, Css as i, Members as l, arg as m, ArgsDef as n, Impl as o, Typed as p, Chain as r, KernelDef as s, AliasesOf as t, Pending as u, lacking as v, Part as x, scope as y };
|
|
@@ -130,8 +130,13 @@ const uncovered = Effect.fnUntraced(function* (feature, off, runtimes) {
|
|
|
130
130
|
const beyond = yield* unreached(fallback);
|
|
131
131
|
return off.filter((t) => beyond.has(t));
|
|
132
132
|
});
|
|
133
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Whether a target needs the runtime: it lacks a feature in `polyfills` and the runtime reaches it (`unreached`). A
|
|
135
|
+
* runtime with a `script` (or `css`) and no `polyfills` is the plugin's own face, not a fallback: no target can decide it,
|
|
136
|
+
* so every target needs it.
|
|
137
|
+
*/
|
|
134
138
|
const needed = Effect.fnUntraced(function* (runtime, ts) {
|
|
139
|
+
if (!runtime.polyfills.length && (runtime.script || runtime.css)) return true;
|
|
135
140
|
const off = yield* unreached(runtime, ts);
|
|
136
141
|
for (const key of runtime.polyfills) for (const t of yield* lacking(key, ts)) if (!off.has(t.join(" "))) return true;
|
|
137
142
|
return false;
|
package/dist/surface.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as AliasesOf } from "./scope-
|
|
1
|
+
import { t as AliasesOf } from "./scope-Oxd_stwQ.mjs";
|
|
2
2
|
/**
|
|
3
3
|
* `surface().color(t.color.canvas).lighten(0.06).ink()`: a background that follows from the surface around it, however
|
|
4
4
|
* many plain wrappers sit between. `color(<color>)` is an absolute base (default: the parent surface); `lighten(step)`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as registry, d as MacroError, f as MemberError, o as warn } from "./registry-8sYvlzyn.mjs";
|
|
2
|
-
import { D as relation, E as covers, d as requireLayer, p as standIn } from "./sheet-
|
|
2
|
+
import { D as relation, E as covers, d as requireLayer, p as standIn } from "./sheet-rwl57DmD.mjs";
|
|
3
3
|
import { t as fallbackOf } from "./lowered-DOJEDRFM.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { checkSelector } from "@cssints/css-grammar/check";
|
package/dist/tui.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as registry } from "./registry-8sYvlzyn.mjs";
|
|
2
|
-
import { a as createTokens$1, i as createGlobalTheme$1, j as fail, s as sets } from "./theme-
|
|
3
|
-
import { g as evaluate, h as css } from "./sheet-
|
|
4
|
-
import { n as valueOf, t as together } from "./contrast-
|
|
2
|
+
import { a as createTokens$1, i as createGlobalTheme$1, j as fail, s as sets } from "./theme-ClgusumY.mjs";
|
|
3
|
+
import { g as evaluate, h as css } from "./sheet-rwl57DmD.mjs";
|
|
4
|
+
import { n as valueOf, t as together } from "./contrast-BB0wimFI.mjs";
|
|
5
5
|
import { n as parseColor } from "./color-BB8ipynT.mjs";
|
|
6
6
|
import { Formatter } from "effect";
|
|
7
7
|
import { clampRgb, formatHex, formatHex8 } from "culori/fn";
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as GlobalRule } from "./author-types-
|
|
1
|
+
import { i as GlobalRule } from "./author-types-X4zGpNpU.mjs";
|
|
2
2
|
import { n as ImportError, r as MacroError } from "./errors-jNEiB3ye.mjs";
|
|
3
|
-
import { i as RegistryState, n as At, r as LayersDecl } from "./node-
|
|
3
|
+
import { i as RegistryState, n as At, r as LayersDecl } from "./node-CWwn8PaX.mjs";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
5
|
import { ImportDeclaration } from "oxc-parser";
|
|
6
6
|
import MagicString from "magic-string";
|
package/dist/vite.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as EvalError, d as MacroError, f as MemberError, i as forgetWarnings, l as HookError, n as forget, p as SiteMissingError, t as Registry } from "./registry-8sYvlzyn.mjs";
|
|
2
|
-
import { C as fallbackEntries, T as runtimeNames, a as closeNames, b as DERIVED, c as layerEntries, f as themedEntries, l as layerStatements, m as usesDerived, n as addGlobal, o as globalEntries, r as addLayers, s as globalNames, t as LAYERS, u as renderSheet, v as kernelEntries, w as runtimeModule, x as RUNTIME_ID } from "./sheet-
|
|
2
|
+
import { C as fallbackEntries, T as runtimeNames, a as closeNames, b as DERIVED, c as layerEntries, f as themedEntries, l as layerStatements, m as usesDerived, n as addGlobal, o as globalEntries, r as addLayers, s as globalNames, t as LAYERS, u as renderSheet, v as kernelEntries, w as runtimeModule, x as RUNTIME_ID } from "./sheet-rwl57DmD.mjs";
|
|
3
3
|
import { a as instrument, c as writeSite, o as runtimeImport, s as tagged } from "./instrument-QGVM1E1B.mjs";
|
|
4
|
-
import { r as verifyChecks } from "./contrast-
|
|
4
|
+
import { r as verifyChecks } from "./contrast-BB0wimFI.mjs";
|
|
5
5
|
import { i as serverId, n as gitRoot, o as sessionOf, r as register } from "./devtools-link-DN91p7g5.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { targets as targets$1, useTargets } from "@cssints/css-grammar/support";
|
package/docs/API.md
CHANGED
|
@@ -954,8 +954,9 @@ export const a = cn(row().gap("2rem"), margin(0), flexGrow(1)); // fine: the mar
|
|
|
954
954
|
- **`conflicts: { props, message }`**: one `cn()` that holds the marker and one of the listed properties (a name, or a RegExp) is a build error with the plugin's message and the call site: `cssints: ring: ring() sets the outline: use its methods (this style also sets outline)`. Conditions count. The message is prefixed with the scope's name.
|
|
955
955
|
- **Derived conflicts.** A kernel written as styles needs no `conflicts` entry: a member that uses it conflicts with every property that meets one the kernel sets, as `cn()` decides overlap: the property itself, its longhands, a shorthand over it, and a logical or physical sibling of the same side. `core: { css: (css) => [css.outline(...), css.outlineOffset("2px")] }` stops `outline`, `outlineColor`, `outlineWidth` and `outlineOffset` in the same `cn()`, under any condition, and lets `color` through: `cssints: ring: kernel core sets outline, outline-offset (this style also sets outline-color)`. The message names the scope, the kernel and the property that was set. A `conflicts` entry among the member's `uses` **replaces** what its kernels derive (a list that allows `outline-color` while the kernel sets `outline`: `{ props: ["outline"], message }`; `props: []` allows everything), and so does a raw kernel, which has nothing to derive from (`frame()` and `surface()` are raw CSS with custom properties and `@container` rules, and `frame()` lets `border-color` through). The check reads every declaration of the style, those a member emits itself included: a member that emits a property its kernel sets lists its own `conflicts`.
|
|
956
956
|
|
|
957
|
-
- **`runtime: { polyfills, parentVars?, css?, script? }`** is the part for browsers that lack a feature. `polyfills` are BCD keys. A client module that holds a site with the member imports `virtual:cssints/runtime/<scope>.<entry>`, and the module is empty (`export {};`, nothing ships) unless a browserslist target lacks one of the keys and the runtime reaches it. A runtime with `parentVars` is a script that stands on `@property`, so it reaches only the targets that have it; `css` and `script` stand on nothing and reach every target (`@cssints/fonts`' capsize trim in Firefox 120). When it is needed, `parentVars` (`{ marker: "&", read, write, watch? }`, custom properties written `--_x`) configure `cssints/parent-vars`, one shared script and one `MutationObserver` for all plugins, `css` (the fallback rules) is added to the sheet in layer `_.k`, only then, and the module imports `script`.
|
|
958
|
-
- **`
|
|
957
|
+
- **`runtime: { polyfills, parentVars?, css?, script? }`** is the part for browsers that lack a feature. `polyfills` are BCD keys. A client module that holds a site with the member imports `virtual:cssints/runtime/<scope>.<entry>`, and the module is empty (`export {};`, nothing ships) unless a browserslist target lacks one of the keys and the runtime reaches it. A runtime with `parentVars` is a script that stands on `@property`, so it reaches only the targets that have it; `css` and `script` stand on nothing and reach every target (`@cssints/fonts`' capsize trim in Firefox 120). A runtime with `css` or `script` and **no** `polyfills` is not a fallback at all: it is the plugin's own face, so every target gets it (`@cssints/reactive`'s DOM runtime, 2026-10-31, cssints-zrt2). When it is needed, `parentVars` (`{ marker: "&", read, write, watch? }`, custom properties written `--_x`) configure `cssints/parent-vars`, one shared script and one `MutationObserver` for all plugins, `css` (the fallback rules) is added to the sheet in layer `_.k`, only then, and the module imports `script`.
|
|
958
|
+
- **`ship({ owner, open })`** (decided 2026-10-31, cssints-zrt2) is what a member returns when a site has more to give than a class string: `@cssints/reactive`'s `state()` returns its owner style and the sources beside it. The client module gets an object literal — each style is its class string, an array or an object keeps its shape, anything else its JSON — so the owner is a string in the browser and the data is there (`const ui = state({ open: false }); cn(ui.owner, p(4))`). At build time the object is the entries themselves, so `ui.owner` is the style and `ui.open` the source. A value a member returns at a site that is neither a style, a condition, a Typed OM value nor a `ship()` is a build error, unless the member is only ever called from a build-time module (nothing of an `icons()` definition module is in the client graph).
|
|
959
|
+
- **`script`** (decided 2026-10-08, cssints-kuki) is the plugin's own browser code, a module specifier: `"#io": { polyfills: ["css.properties.animation-timeline"], script: "@cssints/reveal/runtime", css: "..." }`. It is a string the engine never evaluates (the script stays out of the build-time graph) and writes into the runtime module as `import "@cssints/reveal/runtime";`, which Vite resolves as the app's own imports are, in dev and in `vite build`. Write a subpath of the plugin's package, with a `development` condition for the source and a default for `dist/` (`exports: { "./runtime": { development: "./src/runtime.ts", default: "./dist/runtime.mjs" } }`): the package's own tests resolve it by its name too. A script reaches every target, so it is not gated: it ships to every browser of a build whose targets lack a key — with no `polyfills` at all it ships to every build — and probes the feature itself (`CSS.supports`) to stay off where it is native; with `parentVars` beside it the two are decided apart. A script is a module, so the browser runs it once, however many sites, files or runtime entries import it. The plugin's subpath stays public: an app can still import it by hand, which is harmless. Derived tokens are not members; the engine registers their runtime (`cssints/derived`) on this same delivery (see "Themes").
|
|
959
960
|
- **Identity.** A scope name is unique in the process, and a scope is its content (the table, the source of its functions, the scopes it embeds), hashed. A module that runs again with the same content is nothing (every client module that uses a plugin runs it again). Another content under the same name replaces the old one, as an edit does in dev: the files that imported the edited module are forgotten when Vite reports it and run again. It is a build error, `scope "x" is defined twice with different content`, while a file that was not affected still uses the old one: two modules that define one name. `icons()` runs its scope once per collection, with the same table and the same code: one scope, and the collection is the closure of `build`, which the hash does not see.
|
|
960
961
|
- **Errors** are `MemberError` values (`Data.TaggedError`) with `pluginName` (the scope) and the file, site and offset of the call, so the Vite overlay points at it. What `build`, `value`, a chain's `emit` or a kernel's styles throw is a `MemberError` at the call. A plain function the plugin exports (one that wraps styles, say) may throw an `Error`: while a site runs, anything thrown is located at that site, with its message (2026-10-08, cssints-rf3k).
|
|
961
962
|
- **`lacking(...keys)`** gives the build's targets that lack one of the BCD keys, as browserslist names them, by the rules of the support warnings (`partial_implementation` lacks), and `[]` when every target has them all: a plugin emits fallback rules only when some target needs them (2026-10-08, cssints-rf3k). It runs at build time, in a member's functions or in the plugin's own:
|
|
@@ -1241,6 +1242,7 @@ More plugins on `scope()`, each a package of its own with its rules and limits i
|
|
|
1241
1242
|
| [`@cssints/ramp`](../packages/ramp/README.md) | `ramp(base, { mode, steps \| ratios, background })`, `overrides(ramp)` | An OKLCH palette as DTCG data for `createTokens`; the contrast mode meets each ratio against its background |
|
|
1242
1243
|
| [`@cssints/rybitten`](../packages/rybitten/README.md) | `color(hue, { cube })`, `wheel(steps, { cube })`, `shades(hue, lightness)`, `overrides(group)` | RYBitten's red-yellow-blue colours (36 historical cubes) as DTCG data for `createTokens`: a hue, a wheel of hues, a hue at several lightnesses |
|
|
1243
1244
|
| [`@cssints/skeleton`](../packages/skeleton/README.md) | `skeleton().radius().color().highlight()` | Loading placeholders whose shimmer is in phase across the page (one clock on `:root`), static under reduced motion |
|
|
1245
|
+
| [`@cssints/reactive`](../packages/reactive/README.md) | `state()`, `toggle()`, `set()`, `on()`, `bind()`, `when()`, `inside()` | State as custom properties, events as data on the element: writes only in events, conditions and DOM output in plain CSS, wired by a 0.9 kB runtime shipped to every target (2026-10-31, cssints-zrt2) |
|
|
1244
1246
|
|
|
1245
1247
|
### Headless: `createField` and `createConfirm` (`@cssints/headless`)
|
|
1246
1248
|
|
|
@@ -1469,6 +1471,8 @@ Selection is over ids, so it survives the host's re-sort. `rowProps` carries `ar
|
|
|
1469
1471
|
|
|
1470
1472
|
The APG tablist as state and attribute objects: roving tabindex, wrapping arrows, Home/End, automatic or manual activation, disabled tabs, and a selection that is a request (`value` + `onValueChange`) or uncontrolled (`defaultSelected`). `packages/apg-specs/specs/apg/tabs.qnt` is its model; `packages/headless/test/tabs.browser.test.tsx` replays it in Chromium.
|
|
1471
1473
|
|
|
1474
|
+
Its accessibility requirements are catalogued, gap by gap, in `packages/headless/docs/requirements/tabs.md` (the model is `packages/headless/docs/requirements/README.md`).
|
|
1475
|
+
|
|
1472
1476
|
Close and rename are an **extension** (bean `cssints-gdv2`, the docs playground's file tabs), off by default so the plain tabs are untouched. The primitive owns no list: it asks (`onClose`, `onRename`), the host removes or renames. `packages/headless/test/tabs-close.browser.test.tsx` is their oracle.
|
|
1473
1477
|
|
|
1474
1478
|
```ts skip
|
|
@@ -1546,6 +1550,48 @@ createScrollspy(options: {
|
|
|
1546
1550
|
|
|
1547
1551
|
f812d87e (headless: a data table with sortable headers and row selection)
|
|
1548
1552
|
|
|
1553
|
+
### Headless: `createCommandPalette` (`@cssints/headless`)
|
|
1554
|
+
|
|
1555
|
+
The first **composition**, not a new primitive and not a new spec: `createDialogModal` + `createTextInput` + `createStatus` wired into a palette. Its oracle is `packages/headless/test/command-palette.browser.test.tsx`. A palette is a dialog, **not a combobox**: choosing a row runs a command, so the field is `input[type=text][role=searchbox]` (no `aria-autocomplete`, no `aria-haspopup`), the container is `role=dialog` and the list is a `role=listbox`.
|
|
1556
|
+
|
|
1557
|
+
```ts skip
|
|
1558
|
+
createCommandPalette(options: {
|
|
1559
|
+
commands: ReadonlyArray<{
|
|
1560
|
+
id: string;
|
|
1561
|
+
label: string;
|
|
1562
|
+
group?: string | undefined; // the heading the host renders
|
|
1563
|
+
keywords?: ReadonlyArray<string> | undefined; // also matched
|
|
1564
|
+
run(): void | Promise<void>;
|
|
1565
|
+
}>;
|
|
1566
|
+
open?: boolean | undefined; // controlled: the host owns the open state
|
|
1567
|
+
onOpenChange?(open: boolean): void;
|
|
1568
|
+
hotkey?: string | undefined; // the chord attach() binds; "mod+k"
|
|
1569
|
+
labels?: Partial<{
|
|
1570
|
+
title: string; // "Command palette"
|
|
1571
|
+
placeholder: string; // "Type a command"
|
|
1572
|
+
empty: string; // "No commands found"
|
|
1573
|
+
results(count: number): string; // "4 commands"
|
|
1574
|
+
}> | undefined;
|
|
1575
|
+
}): {
|
|
1576
|
+
open(): boolean;
|
|
1577
|
+
openPalette(): void;
|
|
1578
|
+
closePalette(): void;
|
|
1579
|
+
query(): string;
|
|
1580
|
+
setQuery(text: string): void;
|
|
1581
|
+
active(): number; // index into results(); -1 = the field holds the highlight
|
|
1582
|
+
setActive(index: number): void;
|
|
1583
|
+
results(): ReadonlyArray<{ id: string; label: string; group: string | undefined }>;
|
|
1584
|
+
labels: { title: string; placeholder: string; empty: string; results(count: number): string };
|
|
1585
|
+
status(): string | undefined; // the count, in the polite region
|
|
1586
|
+
// inputProps, listProps, optionProps(id), emptyProps, statusProps,
|
|
1587
|
+
// dialogProps, backdropProps and titleProps: the attribute objects.
|
|
1588
|
+
runActive(): void | Promise<void>;
|
|
1589
|
+
attach(): () => void; // binds the hotkey on the document; returns the teardown
|
|
1590
|
+
};
|
|
1591
|
+
```
|
|
1592
|
+
|
|
1593
|
+
Focus stays in the field and `aria-activedescendant` names the highlighted option: ↓ on the last and ↑ on the first return the highlight to the field, and ↓/↑ from there re-enter at the first/last row. Home/End move the caret while the field has text and are list keys only while it is empty. Enter runs the highlight, Escape closes through the dialog, Tab is not intercepted. Filtering is a plain case-insensitive substring match over `label` and `keywords` — no fuzzy scoring, because a palette is short and ranking (recents, scopes) is the host's policy. `results()` keeps host order; `group` is what the host groups by; `emptyProps.hidden` and `labels.empty` are the empty state; the count is written to `statusProps` once per query, not on every arrow. The query and highlight reset on close, focus returns to the element that had it, and `runActive()` closes before `run()` so a command may open its own UI. Host's: rendering, grouping and CSS; async commands and their errors; every visible string via `labels`; any close other than Escape or a command. The filterable menu and the date picker are later compositions on the same research.
|
|
1594
|
+
|
|
1549
1595
|
### Headless: `createPanZoom` (`@cssints/headless`)
|
|
1550
1596
|
|
|
1551
1597
|
**No APG pattern exists for pan-zoom**, so there is no Quint spec and no ported scenarios: `packages/headless/test/panzoom.browser.test.tsx`, a real Chromium viewport, is its oracle. The accessible contract is the three parts it returns: a focusable viewport (`role=group`, `tabindex=0`, `aria-roledescription="zoomable image"`, `aria-describedby` the value), keyboard operations that stand in for every gesture, and a polite live value. The pointer — drag to pan, two pointers to pinch, a double click to reset — is an addition, never the only way in. It is not `createCanvas`: that is a camera over an infinite world, this is one image fitted to a box.
|
|
@@ -1592,6 +1638,103 @@ Wheel zooms **only with `ctrl` / `meta`** held: the browser's pinch gesture arri
|
|
|
1592
1638
|
|
|
1593
1639
|
The host owns the box and the look: the viewport's size and `overflow: hidden`, `touch-action: none` if a touch drag should pan, `transform-origin: 50% 50%` (the default this maths assumes), and any transition on the transform. Controlled is `scale` + `onScaleChange`; the offset has no controlled prop, so a host that ignores a scale request also gets the offset resolved for the scale it refused. Not modelled: inertia, rotation, and pinching on platforms without `PointerEvent` (the browser's own pinch is the `ctrl+wheel` path).
|
|
1594
1640
|
|
|
1641
|
+
### Headless: `createMenu` (`@cssints/headless`)
|
|
1642
|
+
|
|
1643
|
+
The APG menu family (`createMenu` for the menu button and the checkable menus, `createMenubar` for the roving bar) as state and attribute objects. Its behaviour is `specs/apg/menu-button.qnt` and `menu-checkable.qnt`, and `packages/headless/test/menu-button.browser.test.tsx` replays the generated models, ported scenarios and seeded differential walks against it.
|
|
1644
|
+
|
|
1645
|
+
```ts skip
|
|
1646
|
+
createMenu(options: {
|
|
1647
|
+
items: ReadonlyArray<string | { label: string; kind?: "item" | "checkbox" | "radio"; checked?: boolean; disabled?: boolean }>;
|
|
1648
|
+
variant?: "plain" | "checkbox-items" | "radio-items" | "mixed" | undefined; // "plain"
|
|
1649
|
+
open?: boolean | undefined; // controlled: the host owns the open state
|
|
1650
|
+
defaultOpen?: boolean; // uncontrolled starting state
|
|
1651
|
+
onOpenChange?(open: boolean): void; // a request the host may ignore
|
|
1652
|
+
value?: number | undefined; // controlled checked items, packed (checkboxItem0 * 5 + radioChoice, 1-based)
|
|
1653
|
+
defaultValue?: number;
|
|
1654
|
+
onCheckedValueChange?(value: number): void;
|
|
1655
|
+
triggerMode?: "outside" | "held" | undefined; // where the focus sits while the menu is closed
|
|
1656
|
+
disabled?: ReadonlyArray<number>; // focusable, never activated
|
|
1657
|
+
id?: string | undefined;
|
|
1658
|
+
onItemClick?(index: number): void;
|
|
1659
|
+
}): {
|
|
1660
|
+
open(): boolean;
|
|
1661
|
+
focusLevel(): 0 | 1 | 2; // 0 outside, 1 on the trigger, 2 on an item
|
|
1662
|
+
focusIndex(): number;
|
|
1663
|
+
openMenu(): void; // a request, like every intent
|
|
1664
|
+
closeMenu(): void;
|
|
1665
|
+
keyDown(key: string): void;
|
|
1666
|
+
// …items, triggerProps, menuProps, itemProps(index), type(), setFocusItem()
|
|
1667
|
+
};
|
|
1668
|
+
```
|
|
1669
|
+
|
|
1670
|
+
**The open state is a request/commit pair**, like every other controlled primitive: `open` (+ `onOpenChange`) makes the host own it, `defaultOpen` starts it. Every intent that opens or closes — the trigger's click and its keys, Escape, an item's activation, Tab, an outside click in a kit, the programmatic `openMenu` / `closeMenu` — is a request, so a host that holds `open` keeps the menu open and the focus where it was; `menu-button.qnt` reverts the whole transition of a rejected request, focus included. A write the host makes on its own is not a request: it takes the focus to the first item when it opens and to the trigger when it closes, and a write of the value it already has moves nothing (no widget can observe it).
|
|
1671
|
+
|
|
1672
|
+
### Headless: `createCombobox` (`@cssints/headless`)
|
|
1673
|
+
|
|
1674
|
+
The APG combobox as state and attribute objects: the select-only variant of `specs/apg/combobox-select-only.qnt`, with a listbox popup; `filter: true` turns typing into the autocomplete variant's prefix filter. The grid, tree and dialog popups the other combobox specs model are not this primitive (their models are walked against themselves). Its behaviour is `packages/headless/test/combobox.browser.test.tsx`: DOM focus never leaves the combobox, the popup's active option is announced through `aria-activedescendant`, and navigating never commits — Enter, Space, Tab, blur, Alt+ArrowUp, an option click or `setValue` do.
|
|
1675
|
+
|
|
1676
|
+
```ts skip
|
|
1677
|
+
createCombobox(options: {
|
|
1678
|
+
options: ReadonlyArray<ComboboxOption | string>; // `{ value, label }`, or a string that is both
|
|
1679
|
+
value?: string | null | undefined; // controlled selection
|
|
1680
|
+
defaultSelectedValue?: string | null; // uncontrolled start (`defaultValue` is its alias)
|
|
1681
|
+
open?: boolean | undefined; // controlled popup
|
|
1682
|
+
defaultOpen?: boolean;
|
|
1683
|
+
autoComplete?: "none" | "list" | "both"; // "list"
|
|
1684
|
+
filter?: boolean; // false: single-letter typeahead; true: a prefix filter
|
|
1685
|
+
disabled?: boolean; // aria-disabled on the combobox and the trigger; a key, click, typed character, filter input and option click change nothing, no popup opens
|
|
1686
|
+
placeholder?: string;
|
|
1687
|
+
id?: string; // base of `<id>-input`, `-trigger`, `-listbox`, `-option-<i>`
|
|
1688
|
+
onOpenChange?(open: boolean): void; // a request the host may ignore
|
|
1689
|
+
onValueChange?(value: string | null): void; // a request the host may ignore
|
|
1690
|
+
onFocusRelease?(): void; // Tab: the host may blur its own DOM
|
|
1691
|
+
}): {
|
|
1692
|
+
open(): boolean;
|
|
1693
|
+
activeIndex(): number; // index into `options()`; -1 = the visual focus on the combobox
|
|
1694
|
+
value(): string | null;
|
|
1695
|
+
focused(): boolean;
|
|
1696
|
+
query(): string; // the typeahead / filter text
|
|
1697
|
+
options(): ReadonlyArray<{ value; label; selected; id; props }>;
|
|
1698
|
+
setOpen(open: boolean): void; // a request
|
|
1699
|
+
setValue(value: string | null): void;
|
|
1700
|
+
comboboxProps: { id; role: "combobox"; value; "aria-autocomplete"; "aria-haspopup"; "aria-expanded"; "aria-controls"; "aria-activedescendant"; "aria-disabled"; onInput; onKeyDown; onFocus; onBlur; onClick };
|
|
1701
|
+
triggerProps: { id; type: "button"; tabindex: -1; "aria-haspopup"; "aria-expanded"; "aria-controls"; "aria-disabled"; onClick; onMouseDown };
|
|
1702
|
+
listboxProps: { id; role: "listbox"; hidden };
|
|
1703
|
+
};
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
**`disabled` is a widget-level extension** (`combobox-select-only.qnt` lists it under its deliberate omissions and has no `disabled` var, so the DOM test is its oracle): a key, a click, a typeahead character, a filter input and an option click are dropped before they become a request, and no popup opens. It announces `aria-disabled` — not the native `disabled` — because the pattern puts the state on a `role="combobox"` element that is a button in the select-only example; the combobox keeps its tab stop (the pattern's focus contract), as `createButton` and `createSlider`'s thumb do. The trigger announces `aria-disabled` too. `setOpen` / `setValue` stay host requests, so a programmatic write still applies.
|
|
1707
|
+
|
|
1708
|
+
### Headless: `createCheckboxGroup` (`@cssints/headless`)
|
|
1709
|
+
|
|
1710
|
+
The APG Checkbox pattern's tri-state group controller: exactly three child values, a controller whose `aria-checked` is derived from them (all / none / mixed) with the optional "remember the last mixed combination" cycle, checked against `specs/apg/checkbox-group.qnt`. Its name, description and error are `createField`'s contract on the `role="group"` element, which the model leaves out on purpose (its header omits labelling), so `packages/headless/test/checkbox-group.browser.test.tsx` is their oracle.
|
|
1711
|
+
|
|
1712
|
+
```ts skip
|
|
1713
|
+
createCheckboxGroup(options: {
|
|
1714
|
+
values: ReadonlyArray<T>; // exactly three
|
|
1715
|
+
value?: ReadonlyArray<T> | undefined; // controlled checked subset
|
|
1716
|
+
defaultValue?: ReadonlyArray<T>;
|
|
1717
|
+
parent?: boolean; // the "all" controller is a focus stop
|
|
1718
|
+
remember?: boolean; // the third activation restores the last mixed combination
|
|
1719
|
+
id?: string | undefined; // base of `<id>-control` (the group element), `<id>-0`..`-2` (the children) and `<id>-error`
|
|
1720
|
+
label?: string | undefined; // the group's name, as aria-label
|
|
1721
|
+
labelledby?: string | undefined; // the id of the host's own label element; wins over `label`
|
|
1722
|
+
describedby?: string | undefined; // host-owned description ids, after the error
|
|
1723
|
+
invalid?: boolean | string | undefined; // like createField: a string is the flag and the message
|
|
1724
|
+
error?: string | undefined; // the error shown; implies invalid, `invalid: false` silences it
|
|
1725
|
+
onValueChange?(checked: ReadonlyArray<T>): void;
|
|
1726
|
+
}): {
|
|
1727
|
+
invalid(): boolean;
|
|
1728
|
+
error(): string | undefined; // only while invalid
|
|
1729
|
+
describedIds(): readonly string[]; // the error id while shown, then describedby
|
|
1730
|
+
groupProps: { id; role: "group"; "aria-label"; "aria-labelledby"; "aria-describedby"; "aria-invalid"; onKeyDown };
|
|
1731
|
+
errorProps: { id; hidden; role }; // role "alert" while shown; render it always
|
|
1732
|
+
// …children, parentChecked, checkedValues(), click(target), keySpace(), groupProps' onKeyDown
|
|
1733
|
+
};
|
|
1734
|
+
```
|
|
1735
|
+
|
|
1736
|
+
The group is not a `<label for>` target: pass `labelledby` naming the host's own label element, or `label` without one. The error id is in `aria-describedby` only while the error is shown, `aria-invalid` only while the group is invalid, and the group element's own id is there for a controller that names it with `aria-controls`. The children's ids follow the same base (`<base>-0`..`-2`), so two groups on one page do not collide (the model omits ids). The checked set is unchanged by all of this: the request/commit split and the spec stay as they were.
|
|
1737
|
+
|
|
1595
1738
|
### Headless: `createSlider` and `createSpinbutton` (`@cssints/headless`)
|
|
1596
1739
|
|
|
1597
1740
|
The two number controls. Their behaviour is `packages/apg-specs/specs/apg/slider.qnt` and `spinbutton.qnt`, checked against the real DOM by the differential walk in `packages/headless/test/slider.browser.test.tsx` and `spinbutton.browser.test.tsx`. Options are plain and read lazily (a getter is fine); `value` + `onValueChange` make the widget controlled, `defaultValue` is the uncontrolled start.
|
|
@@ -1691,6 +1834,51 @@ createSpinbutton(options?: {
|
|
|
1691
1834
|
- a disabled spinbutton's field announces `aria-disabled` **and carries the native `disabled`** (the field is an `<input>`, so the platform's own form-control rule applies); the spin keys, the typed draft (Enter and blur included) and both steppers change nothing, and **both** steppers announce `aria-disabled="true"` — the at-bound rule of the spec stays, and a disabled widget adds the other side;
|
|
1692
1835
|
- both read the option on every attribute read, so a host may flip `disabled` on a mounted widget and the attributes and the guards follow in the same tick.
|
|
1693
1836
|
|
|
1837
|
+
### Headless: `createKnob` (`@cssints/headless`)
|
|
1838
|
+
|
|
1839
|
+
A virtual knob after GarageBand's ([How Apple Designs a Virtual Knob](https://jherrm.github.io/knobs/)): spin around the centre, or slide vertically / horizontally, with one gesture locked per drag. **No APG pattern and no ARIA role exist for a dial**, so there is no Quint spec and no ported scenarios: `packages/headless/test/knob.browser.test.tsx` (real Chromium) is the oracle. The knob is a `role=slider` — the closest role WAI-ARIA has — with a polite live value; a numeric field must sit next to it, because a dial is never the only control. Options are plain and read lazily; `value` + `onValueChange` make the widget controlled.
|
|
1840
|
+
|
|
1841
|
+
```ts skip
|
|
1842
|
+
createKnob(options?: {
|
|
1843
|
+
min?: number; // 0
|
|
1844
|
+
max?: number; // 100
|
|
1845
|
+
step?: number; // 1
|
|
1846
|
+
value?: number | undefined; // controlled; undefined = uncontrolled
|
|
1847
|
+
defaultValue?: number; // the middle of the range, quantised to step
|
|
1848
|
+
wrap?: boolean | undefined; // false: clamp at the bounds; true: continuous, past max wraps to min
|
|
1849
|
+
gesture?: "spin" | "slide" | "both" | undefined; // "both": locked per drag from the first movement
|
|
1850
|
+
labels?: { knob?: string | undefined } | undefined; // aria-label on the knob, "Knob" by default
|
|
1851
|
+
format?: ((value: number) => string) | undefined; // aria-valuetext, String by default
|
|
1852
|
+
id?: string | undefined; // base of the value region id
|
|
1853
|
+
onValueChange?(value: number): void; // a request the host may ignore
|
|
1854
|
+
}): {
|
|
1855
|
+
value(): number;
|
|
1856
|
+
setValue(value: number): void; // a request
|
|
1857
|
+
percentOf(value: number): number; // the rotation fraction, 0..1 (× 360 for degrees)
|
|
1858
|
+
stepBy(delta: number): void; // a request
|
|
1859
|
+
knobProps: {
|
|
1860
|
+
role: "slider";
|
|
1861
|
+
tabindex: 0;
|
|
1862
|
+
"aria-valuemin";
|
|
1863
|
+
"aria-valuemax";
|
|
1864
|
+
"aria-valuenow";
|
|
1865
|
+
"aria-valuetext";
|
|
1866
|
+
"aria-label";
|
|
1867
|
+
onKeyDown;
|
|
1868
|
+
onPointerDown;
|
|
1869
|
+
onPointerMove;
|
|
1870
|
+
onPointerUp;
|
|
1871
|
+
onPointerCancel;
|
|
1872
|
+
onWheel;
|
|
1873
|
+
};
|
|
1874
|
+
valueProps: { id; role: "status"; "aria-live": "polite" };
|
|
1875
|
+
};
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
**Gestures.** The first movement past 6 px picks the mode once: movement that stays on one axis (the other under 35% of it) is a slide, anything else is a turn around the centre, and `gesture: "spin"` / `"slide"` disables the other. The direction is the reference's "which side you turn": a vertical slide down turns clockwise (up in value) on the right half and counter-clockwise on the left half; a horizontal slide uses the same left/right side (the reference picks top/bottom for that one). A spin's value per degree shrinks with the pointer's distance from the centre — farther is finer. `percentOf` maps the value to the fraction of a full turn for the skin; `wrap` makes the dial continuous instead of clamped.
|
|
1879
|
+
|
|
1880
|
+
**Keyboard.** Arrows step, PageUp/PageDown move ten steps, Home/End jump to the bounds (no typing: the dial has no text entry). The wheel steps once per event and `preventDefault`s while the pointer is over the knob.
|
|
1881
|
+
|
|
1694
1882
|
### Headless: `createDropZone` (`@cssints/headless`)
|
|
1695
1883
|
|
|
1696
1884
|
A file drop target. **There is no APG pattern and no Quint spec for a drop zone** — the contract is HTML's file input plus WAI-ARIA's live region — so `packages/headless/test/dropzone.browser.test.tsx` (real Chromium) is its oracle. It is the replacement for the bare `<input type="file">` fields of cms-admin's image field and kocharin's dashboard upload.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cssints",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "CSS in TypeScript: typed css.* calls compiled to atomic CSS at build time (a Vite plugin)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@cssints/css-grammar": "^0.
|
|
75
|
+
"@cssints/css-grammar": "^0.21.0",
|
|
76
76
|
"culori": "^4.0.2",
|
|
77
77
|
"effect": "4.0.6",
|
|
78
78
|
"happy-dom": "^20.14.5",
|