@slotted/theme 0.0.0-stage → 0.1.0-beta.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 +10 -2
- package/cli/build-theme.mjs +17 -0
- package/cli/build.mjs +53 -0
- package/cli/validate-rules.mjs +36 -0
- package/conformance/theme-conformance.mjs +452 -0
- package/dist/index.js +6135 -0
- package/dist/layer-order.js +10 -0
- package/dist/seeds-x76IGbQO.js +99 -0
- package/dist/seeds.js +2 -0
- package/dist/styles.css +1 -0
- package/dist/types/apply.d.ts +17 -0
- package/dist/types/bound.d.ts +38 -0
- package/dist/types/color.d.ts +30 -0
- package/dist/types/contract-types.d.ts +18 -0
- package/dist/types/css-scan.d.ts +29 -0
- package/dist/types/define.d.ts +56 -0
- package/dist/types/format.d.ts +6 -0
- package/dist/types/generated/contract.d.ts +9 -0
- package/dist/types/index.d.ts +24 -0
- package/dist/types/layer-order.d.ts +9 -0
- package/dist/types/recipe-checks.d.ts +11 -0
- package/dist/types/recipe.d.ts +41 -0
- package/dist/types/recipes/index.d.ts +4 -0
- package/dist/types/recipes/standard.d.ts +2 -0
- package/dist/types/render.d.ts +6 -0
- package/dist/types/resolve.d.ts +5 -0
- package/dist/types/rule-policy.d.ts +41 -0
- package/dist/types/rules.d.ts +25 -0
- package/dist/types/seeds.d.ts +9 -0
- package/dist/types/validate.d.ts +26 -0
- package/package.json +51 -3
- package/src/contract.json +1963 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { contrast, deltaE, fromSrgb, parseSeed, relativeLuminance, toHex, toLinearSrgb, } from './color';
|
|
2
|
+
export type { Luminance, Oklch } from './color';
|
|
3
|
+
export { LAYER_NAMES, LAYER_ORDER_STATEMENT } from './layer-order';
|
|
4
|
+
export { applyBounds, boundCss, boundValue, fitBound } from './bound';
|
|
5
|
+
export type { Bound } from './bound';
|
|
6
|
+
export { derivationCss, evaluateRecipe, SCHEMES, SEED_NAMES } from './recipe';
|
|
7
|
+
export type { AliasDerivation, Derivation, MixDerivation, Recipe, Scheme, SeedDerivation, SeedName, } from './recipe';
|
|
8
|
+
export { checkScheme, CONTRAST_REQUIREMENT, seedGrid } from './recipe-checks';
|
|
9
|
+
export { recipes } from './recipes';
|
|
10
|
+
export type { TokenContract, TokenDefinition } from './contract-types';
|
|
11
|
+
export { DENSITIES, defineTheme, extendTheme, flattenTheme } from './define';
|
|
12
|
+
export type { Density, FontFaceDefinition, Theme, ThemeDefinition, ThemeOverrides } from './define';
|
|
13
|
+
export { COMPONENT_TOKENS, contract } from './generated/contract';
|
|
14
|
+
export type { BaseToken, ColorToken, ComponentToken, DensityToken, MotionToken, SchemeToken, } from './generated/contract';
|
|
15
|
+
export { resolveTokens } from './resolve';
|
|
16
|
+
export { validateContract, validateTheme } from './validate';
|
|
17
|
+
export type { FlatTheme } from './validate';
|
|
18
|
+
export { renderTheme } from './render';
|
|
19
|
+
export { splitTopLevel } from './rules';
|
|
20
|
+
export { ruleErrors } from './rule-policy';
|
|
21
|
+
export type { RuleError } from './rule-policy';
|
|
22
|
+
export { applyTheme, THEME_CHANGE_EVENT } from './apply';
|
|
23
|
+
export { seedStyle } from './seeds';
|
|
24
|
+
export type { ThemeChangeDetail } from './apply';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slotted` is the only top-level layer. `theme-rules` holds the rules a theme
|
|
3
|
+
* carries, after the family stylesheets they adjust, so a theme rule wins by
|
|
4
|
+
* layer order and never by specificity. `reset` and `tokens` hold no rule;
|
|
5
|
+
* they are declared because a layer takes its position from the first
|
|
6
|
+
* mention of its name.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LAYER_NAMES: readonly ["reset", "tokens", "theme", "components", "theme-rules"];
|
|
9
|
+
export declare const LAYER_ORDER_STATEMENT: string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Oklch } from './color';
|
|
2
|
+
import type { Scheme } from './recipe';
|
|
3
|
+
/** The text ratio each scheme promises. `high-contrast` earns its name at AAA. */
|
|
4
|
+
export declare const CONTRAST_REQUIREMENT: Readonly<Record<Scheme, number>>;
|
|
5
|
+
/**
|
|
6
|
+
* The ratios of `theme-conformance` over evaluated colours, as messages. A
|
|
7
|
+
* recipe passes when every seed of `seedGrid()` leaves this list empty.
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkScheme(values: Readonly<Record<string, Oklch>>, scheme: Scheme): string[];
|
|
10
|
+
/** Hue 0–345 by 15, chroma 0–0.35 by 0.05, lightness 0.2–0.95 by 0.05: 3072 seeds. */
|
|
11
|
+
export declare function seedGrid(): Oklch[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Bound } from './bound';
|
|
2
|
+
import type { Oklch } from './color';
|
|
3
|
+
import { SEED_NAMES, type SeedName } from './seeds';
|
|
4
|
+
export { SEED_NAMES, type SeedName };
|
|
5
|
+
export declare const SCHEMES: readonly ["light", "dark", "high-contrast"];
|
|
6
|
+
export type Scheme = (typeof SCHEMES)[number];
|
|
7
|
+
/** A colour taken from a seed: fixed or offset lightness, scaled chroma, shifted hue. */
|
|
8
|
+
export interface SeedDerivation {
|
|
9
|
+
readonly seed: SeedName;
|
|
10
|
+
readonly l: number | {
|
|
11
|
+
readonly offset: number;
|
|
12
|
+
readonly min?: number;
|
|
13
|
+
readonly max?: number;
|
|
14
|
+
};
|
|
15
|
+
readonly c: {
|
|
16
|
+
readonly scale: number;
|
|
17
|
+
readonly max: number;
|
|
18
|
+
};
|
|
19
|
+
readonly h?: number;
|
|
20
|
+
readonly bounds?: readonly Bound[];
|
|
21
|
+
}
|
|
22
|
+
/** A colour moved `share` toward white, or toward black when its lightness is above 0.9. */
|
|
23
|
+
export interface MixDerivation {
|
|
24
|
+
readonly mix: string;
|
|
25
|
+
readonly share: number;
|
|
26
|
+
readonly bounds?: readonly Bound[];
|
|
27
|
+
}
|
|
28
|
+
export interface AliasDerivation {
|
|
29
|
+
readonly alias: string;
|
|
30
|
+
}
|
|
31
|
+
export type Derivation = SeedDerivation | MixDerivation | AliasDerivation;
|
|
32
|
+
/** Per scheme, the derivation of every colour token the recipe owns. */
|
|
33
|
+
export type Recipe = Readonly<Record<Scheme, Readonly<Record<string, Derivation>>>>;
|
|
34
|
+
/**
|
|
35
|
+
* Every token of one scheme, computed with the arithmetic `derivationCss`
|
|
36
|
+
* writes: the browser's `oklch(from …)` and `color-mix(in oklch, …)` on the
|
|
37
|
+
* same constants.
|
|
38
|
+
*/
|
|
39
|
+
export declare function evaluateRecipe(recipe: Recipe, seeds: Readonly<Record<SeedName, Oklch>>, scheme: Scheme): Record<string, Oklch>;
|
|
40
|
+
/** The CSS value of one derivation. */
|
|
41
|
+
export declare function derivationCss(rule: Derivation): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a theme rule may select and read (design §10.2, ADR 0004). A rule
|
|
3
|
+
* selects only what a binding renders on purpose — a family by `data-ui`, its
|
|
4
|
+
* parts by `data-part`, and the `data-*` attributes a family or the theme
|
|
5
|
+
* axes set — and reads only tokens. Everything else is an implementation
|
|
6
|
+
* detail of a family stylesheet, which changes without notice.
|
|
7
|
+
*
|
|
8
|
+
* `defineTheme` (every rule given as text) and `slotted-build-theme` (every
|
|
9
|
+
* rule, after lightningcss has parsed it) both call `ruleErrors`, so the two
|
|
10
|
+
* paths hold one policy.
|
|
11
|
+
*/
|
|
12
|
+
/** One violation: the 1-based line in the rule text, the selector of the rule it sits in, and why. */
|
|
13
|
+
export interface RuleError {
|
|
14
|
+
readonly line: number;
|
|
15
|
+
readonly selector: string;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The reasons a selector list breaks the policy, each naming the part of the
|
|
20
|
+
* selector at fault. `nested` is true for a rule inside a style rule, where
|
|
21
|
+
* `&` is that rule's element.
|
|
22
|
+
*/
|
|
23
|
+
export declare function selectorErrors(selector: string, nested?: boolean): string[];
|
|
24
|
+
/** The reason a declaration writes a property no theme may write, or `undefined`. */
|
|
25
|
+
export declare function propertyWriteError(name: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* The reason a `var()`, or a `style()` query in a value or in `@container`,
|
|
28
|
+
* reads a property no theme may read, or `undefined`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function tokenReadError(name: string, reader?: 'var' | 'style'): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Every policy violation in a theme's CSS text: each style rule's selector,
|
|
33
|
+
* nested rules and rules in conditional groups included; every `@scope`,
|
|
34
|
+
* `@import` and `@layer`, at any depth; every `@property` naming a private
|
|
35
|
+
* property or a token; every declaration that writes a private property or
|
|
36
|
+
* stands outside a style rule; and every `var()` and `style()` query the text
|
|
37
|
+
* reads, outside strings and comments, with escapes in the function and
|
|
38
|
+
* property names decoded. The
|
|
39
|
+
* text is taken to be balanced; `splitTopLevel` refuses one that is not.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ruleErrors(css: string): RuleError[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits CSS into its top-level statements: a block ends at the brace that
|
|
3
|
+
* returns to depth 0, an at-statement at the semicolon. Strings and comments
|
|
4
|
+
* are skipped, so a brace inside `content: "}"` does not end a rule.
|
|
5
|
+
*
|
|
6
|
+
* A theme rule is text a theme author wrote, concatenated with every other
|
|
7
|
+
* rule into one `@layer`, so an unbalanced `}` here would close that layer
|
|
8
|
+
* (or the `@scope` around it) early and let the rest of the input escape its
|
|
9
|
+
* boundary. The five malformed shapes below are refused rather than silently
|
|
10
|
+
* reinterpreted: a closing brace with nothing open, braces still open at the
|
|
11
|
+
* end, a string never closed (or ended by a newline), a comment never closed,
|
|
12
|
+
* and a trailing `\` that would escape whatever the compiler writes next.
|
|
13
|
+
*/
|
|
14
|
+
export declare function splitTopLevel(css: string): string[];
|
|
15
|
+
/**
|
|
16
|
+
* The rules of one theme, ready for the compiler: `@keyframes`, `@font-face`
|
|
17
|
+
* and `@property` leave the scope they cannot live in, and every keyframes
|
|
18
|
+
* name takes the theme's prefix so two themes on one page never collide.
|
|
19
|
+
* `renderTheme` calls it only on rules `ruleErrors` accepted, which refuses
|
|
20
|
+
* `@scope`, `@import` and `@layer` at any depth.
|
|
21
|
+
*/
|
|
22
|
+
export declare function processRules(themeName: string, sources: readonly string[]): {
|
|
23
|
+
hoisted: string[];
|
|
24
|
+
body: string;
|
|
25
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seeds: the colours every derived colour token is computed from. This
|
|
3
|
+
* module is the entry `@slotted/theme/seeds`, which writes a reader's seeds
|
|
4
|
+
* without loading the contract, the validator or the renderer.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SEED_NAMES: readonly ["accent", "neutral", "success", "warning", "danger"];
|
|
7
|
+
export type SeedName = (typeof SEED_NAMES)[number];
|
|
8
|
+
/** The `style` object that gives an element its own seeds, for React `style` and Angular `[style]`. */
|
|
9
|
+
export declare function seedStyle(seeds: Readonly<Partial<Record<SeedName, string>>>): Record<string, string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TokenContract } from './contract-types';
|
|
2
|
+
type Values = Record<string, string>;
|
|
3
|
+
/** The resolved shape of a theme: what `flattenTheme` returns and what the contract is checked on. */
|
|
4
|
+
export interface FlatTheme {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly base: Values;
|
|
7
|
+
readonly schemes: Readonly<Record<string, Values>>;
|
|
8
|
+
readonly densities: Readonly<Record<string, Values>>;
|
|
9
|
+
readonly reducedMotion: Values;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A token value, a component value or a font field is interpolated into CSS
|
|
13
|
+
* text verbatim (`render.ts` writes `${name}: ${value};` and the like). An
|
|
14
|
+
* unquoted, uncommented `;`, `{` or `}` inside it would close the
|
|
15
|
+
* declaration, the rule or the `@layer`/`@scope` it is written into, and a
|
|
16
|
+
* string or comment the value leaves open, or a trailing `\` escaping the
|
|
17
|
+
* character written after it, would do the same to whatever follows it in
|
|
18
|
+
* the stylesheet — so all of them are rejected here, and so is a value that
|
|
19
|
+
* is not a string, which `String()` could turn into any text. `url("a;b.woff2")`
|
|
20
|
+
* and `"1px /* comment *\/"` stay legitimate: the scan this shares with
|
|
21
|
+
* `splitTopLevel` skips a structural character inside either.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasUnsafeStructuralChar(value: unknown): boolean;
|
|
24
|
+
export declare function validateContract(contract: TokenContract): string[];
|
|
25
|
+
export declare function validateTheme(contract: TokenContract, theme: Partial<FlatTheme>): string[];
|
|
26
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slotted/theme",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/andrelopes-code/slotted.git",
|
|
8
|
+
"directory": "packages/theme"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src/contract.json",
|
|
14
|
+
"cli",
|
|
15
|
+
"conformance"
|
|
16
|
+
],
|
|
17
|
+
"bin": {
|
|
18
|
+
"slotted-build-theme": "./cli/build-theme.mjs"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/types/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./layer-order": {
|
|
26
|
+
"types": "./dist/types/layer-order.d.ts",
|
|
27
|
+
"import": "./dist/layer-order.js"
|
|
28
|
+
},
|
|
29
|
+
"./seeds": {
|
|
30
|
+
"types": "./dist/types/seeds.d.ts",
|
|
31
|
+
"import": "./dist/seeds.js"
|
|
32
|
+
},
|
|
33
|
+
"./contract.json": "./src/contract.json",
|
|
34
|
+
"./conformance": "./conformance/theme-conformance.mjs",
|
|
35
|
+
"./styles.css": "./dist/styles.css"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"lightningcss": "1.33.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "24.13.3",
|
|
42
|
+
"jsdom": "30.0.1",
|
|
43
|
+
"typescript": "6.0.3",
|
|
44
|
+
"vite": "8.2.2",
|
|
45
|
+
"vitest": "4.1.11"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"generate": "node scripts/generate-sources.mjs",
|
|
49
|
+
"build": "vite build && tsc -p tsconfig.build.json && node scripts/write-styles.mjs",
|
|
50
|
+
"test": "vitest run && node --test test/*.test.mjs",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit --pretty false",
|
|
52
|
+
"verify": "pnpm build && pnpm test && pnpm typecheck"
|
|
53
|
+
}
|
|
6
54
|
}
|