@theme-registry/refract 0.1.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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_createTheme-255ff48e.esm.js +2 -0
  3. package/dist/_createTheme-255ff48e.esm.js.map +1 -0
  4. package/dist/_createTheme-41c01033.cjs.js +2 -0
  5. package/dist/_createTheme-41c01033.cjs.js.map +1 -0
  6. package/dist/_errors-be768b25.cjs.js +2 -0
  7. package/dist/_errors-be768b25.cjs.js.map +1 -0
  8. package/dist/_errors-d75a5c74.esm.js +2 -0
  9. package/dist/_errors-d75a5c74.esm.js.map +1 -0
  10. package/dist/adapter-kit.cjs.js +2 -0
  11. package/dist/adapter-kit.cjs.js.map +1 -0
  12. package/dist/adapter-kit.d.ts +88 -0
  13. package/dist/adapter-kit.esm.js +2 -0
  14. package/dist/adapter-kit.esm.js.map +1 -0
  15. package/dist/build/auditCommand.d.ts +19 -0
  16. package/dist/build/buildCommand.d.ts +23 -0
  17. package/dist/build/cli.d.ts +2 -0
  18. package/dist/build/config.d.ts +58 -0
  19. package/dist/build/diff.d.ts +56 -0
  20. package/dist/build/diffCommand.d.ts +29 -0
  21. package/dist/build/emit.d.ts +20 -0
  22. package/dist/build/emitTheme.d.ts +39 -0
  23. package/dist/build/guide.d.ts +34 -0
  24. package/dist/build/importCommand.d.ts +42 -0
  25. package/dist/build/index.d.ts +33 -0
  26. package/dist/build/init.d.ts +32 -0
  27. package/dist/build/paths.d.ts +42 -0
  28. package/dist/build/skillsCommand.d.ts +53 -0
  29. package/dist/build/tokensCommand.d.ts +17 -0
  30. package/dist/build/vendor.d.ts +39 -0
  31. package/dist/build.cjs.js +2 -0
  32. package/dist/build.cjs.js.map +1 -0
  33. package/dist/build.esm.js +2 -0
  34. package/dist/build.esm.js.map +1 -0
  35. package/dist/cli.js +8250 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/color-math.cjs.js +2 -0
  38. package/dist/color-math.cjs.js.map +1 -0
  39. package/dist/color-math.esm.js +2 -0
  40. package/dist/color-math.esm.js.map +1 -0
  41. package/dist/core/ThemeAdapter.d.ts +234 -0
  42. package/dist/core/createTheme.d.ts +96 -0
  43. package/dist/core/cssProperties.d.ts +8 -0
  44. package/dist/core/defineAdapter.d.ts +25 -0
  45. package/dist/core/derive/crossProperty.d.ts +31 -0
  46. package/dist/core/derive/index.d.ts +11 -0
  47. package/dist/core/derive/resolveTokens.d.ts +27 -0
  48. package/dist/core/errors.d.ts +24 -0
  49. package/dist/core/index.d.ts +16 -0
  50. package/dist/core/media/containers.d.ts +20 -0
  51. package/dist/core/media/defaults.d.ts +3 -0
  52. package/dist/core/media/descriptors.d.ts +12 -0
  53. package/dist/core/media/index.d.ts +11 -0
  54. package/dist/core/media/queries.d.ts +20 -0
  55. package/dist/core/model/buildModel.d.ts +116 -0
  56. package/dist/core/model/index.d.ts +14 -0
  57. package/dist/core/model/interpreted.d.ts +37 -0
  58. package/dist/core/model/mergeRuleSet.d.ts +68 -0
  59. package/dist/core/model/model.d.ts +260 -0
  60. package/dist/core/noopAdapter.d.ts +26 -0
  61. package/dist/core/normalize/index.d.ts +15 -0
  62. package/dist/core/normalize/properties.d.ts +20 -0
  63. package/dist/core/normalize/recipeResolver.d.ts +19 -0
  64. package/dist/core/normalize/recipes.d.ts +38 -0
  65. package/dist/core/normalize/responsive.d.ts +40 -0
  66. package/dist/core/normalize/types.d.ts +261 -0
  67. package/dist/core/rawTheme.d.ts +53 -0
  68. package/dist/core/ref.d.ts +13 -0
  69. package/dist/core/subsystem.d.ts +115 -0
  70. package/dist/core/types.d.ts +31 -0
  71. package/dist/core/units.d.ts +93 -0
  72. package/dist/dtcg/export.d.ts +48 -0
  73. package/dist/dtcg/import.d.ts +43 -0
  74. package/dist/dtcg/index.d.ts +21 -0
  75. package/dist/dtcg/parse.d.ts +6 -0
  76. package/dist/dtcg/types.d.ts +69 -0
  77. package/dist/dtcg.cjs.js +2 -0
  78. package/dist/dtcg.cjs.js.map +1 -0
  79. package/dist/dtcg.esm.js +2 -0
  80. package/dist/dtcg.esm.js.map +1 -0
  81. package/dist/index.cjs.js +2 -0
  82. package/dist/index.cjs.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.esm.js +2 -0
  85. package/dist/index.esm.js.map +1 -0
  86. package/dist/subsystems/animation/index.d.ts +13 -0
  87. package/dist/subsystems/animation/recipes.d.ts +20 -0
  88. package/dist/subsystems/animation/types.d.ts +52 -0
  89. package/dist/subsystems/borders/index.d.ts +10 -0
  90. package/dist/subsystems/borders/recipes.d.ts +29 -0
  91. package/dist/subsystems/borders/types.d.ts +50 -0
  92. package/dist/subsystems/colors/audit.d.ts +67 -0
  93. package/dist/subsystems/colors/colorInput.d.ts +17 -0
  94. package/dist/subsystems/colors/index.d.ts +12 -0
  95. package/dist/subsystems/colors/keywords.d.ts +9 -0
  96. package/dist/subsystems/colors/normalize.d.ts +26 -0
  97. package/dist/subsystems/colors/recipes.d.ts +21 -0
  98. package/dist/subsystems/colors/types.d.ts +98 -0
  99. package/dist/subsystems/colors/utils.d.ts +104 -0
  100. package/dist/subsystems/components/index.d.ts +13 -0
  101. package/dist/subsystems/components/recipes.d.ts +22 -0
  102. package/dist/subsystems/components/types.d.ts +46 -0
  103. package/dist/subsystems/effects/index.d.ts +9 -0
  104. package/dist/subsystems/effects/recipes.d.ts +24 -0
  105. package/dist/subsystems/effects/types.d.ts +63 -0
  106. package/dist/subsystems/effects/utils.d.ts +57 -0
  107. package/dist/subsystems/globals/index.d.ts +21 -0
  108. package/dist/subsystems/globals/presets.d.ts +33 -0
  109. package/dist/subsystems/globals/types.d.ts +48 -0
  110. package/dist/subsystems/index.d.ts +21 -0
  111. package/dist/subsystems/layout/index.d.ts +14 -0
  112. package/dist/subsystems/layout/normalize.d.ts +35 -0
  113. package/dist/subsystems/layout/recipes.d.ts +16 -0
  114. package/dist/subsystems/layout/structural.d.ts +37 -0
  115. package/dist/subsystems/layout/types.d.ts +154 -0
  116. package/dist/subsystems/typography/index.d.ts +9 -0
  117. package/dist/subsystems/typography/normalize.d.ts +17 -0
  118. package/dist/subsystems/typography/recipes.d.ts +21 -0
  119. package/dist/subsystems/typography/types.d.ts +49 -0
  120. package/package.json +83 -0
  121. package/skills/adapter-scaffold/SKILL.md +85 -0
  122. package/skills/adapter-usage/SKILL.md +75 -0
  123. package/skills/build-config/SKILL.md +77 -0
  124. package/skills/colors/SKILL.md +134 -0
  125. package/skills/consuming-the-output/SKILL.md +62 -0
  126. package/skills/dtcg-import/SKILL.md +76 -0
  127. package/skills/layout/SKILL.md +65 -0
  128. package/skills/overrides-and-child-themes/SKILL.md +70 -0
  129. package/skills/recipes-and-composition/SKILL.md +110 -0
  130. package/skills/theme-authoring/SKILL.md +107 -0
  131. package/skills/theme-foundations/SKILL.md +127 -0
  132. package/skills/troubleshooting/SKILL.md +97 -0
  133. package/skills/typography/SKILL.md +66 -0
  134. package/skills/visual-effects/SKILL.md +96 -0
  135. package/src/subsystems/colors/utils.ts +299 -0
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Layout's `interpretRecipe` hook — resolves recipe declarations to token-path Refs.
3
+ *
4
+ * A layout recipe prop names a **spacing variant** (`paddingY: "xl"`, `gap: "relaxed"`),
5
+ * which this interpreter emits as a `layout.spacing.<variant>` token-path {@link Ref}
6
+ * (`layout.spacing` for the base). `background` passes through as a literal. One recipe prop
7
+ * can fan out to several CSS declarations (`paddingY` → `padding-top` + `padding-bottom`).
8
+ * Emits refs **directly** — no `var(--…)` strings; the CSS adapter maps each path to a
9
+ * `var(--…)` at render time. Ported + reshaped from the old `subsystems/layout/recipes.ts`.
10
+ */
11
+ import type { InterpretedRecipeVariant } from "../../core/model";
12
+ import type { NormalizedRecipeVariant } from "../../core/normalize";
13
+ import type { RecipeInterpretContext } from "../../core/subsystem";
14
+ import type { LayoutRecipeProps } from "./types";
15
+ /** Interpret one layout recipe variant into base + responsive/state override declarations. */
16
+ export declare const interpretLayoutRecipeVariant: (variantName: string, variant: NormalizedRecipeVariant<LayoutRecipeProps, string>, ctx: RecipeInterpretContext) => InterpretedRecipeVariant<string>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Layout structural generators → **Model rule-sets** (the clean-room payoff).
3
+ *
4
+ * `columns` / `grids` / `stacks` / `container` used to emit `CssNode[]` that a lossless
5
+ * node↔rule-set codec (`routeLayoutStructuralThroughModel`) round-tripped into the Model.
6
+ * Here the generators emit {@link RuleSetGroup}s **natively** — declarations carry token-path
7
+ * {@link Ref}s (`layout.spacing.relaxed`), never `var(--…)` — so the codec never exists. The CSS
8
+ * adapter lowers these rule-sets to `CssRuleNode[]` (columns/grids/stacks via `lowerRecipeGroup`,
9
+ * container via the two-pass `lowerContainerGroup`, matching the old unshift-bases/push-medias order).
10
+ *
11
+ * The columns/container config knobs (`--…-columns--size`, `--…-container--inset`, …) ride along as
12
+ * **layout property tokens** (`configProperties`, merged as `extraProperties` in the Model); the
13
+ * structural rule declarations reference those config tokens by path (`layout.container--inset`),
14
+ * exactly the two-level indirection the old generators produced. Math ported verbatim from the old
15
+ * `subsystems/layout/tokens.ts`; only the output shape (rule-sets, not CSS) differs.
16
+ *
17
+ * The math forms variant keys from raw breakpoint keys + span numbers (`col-sm-3`); the adapter
18
+ * prepends the class prefix + sanitizes. No CSS syntax is produced here — core stays format-neutral.
19
+ */
20
+ import type { PropertyModel, RuleSetGroup } from "../../core/model";
21
+ import { type MediaConfig } from "../../core/media";
22
+ import type { ColumnsValue, GridsDefinition, StacksDefinition } from "./types";
23
+ /** The structural output: rule-set groups (for the Model + CSS) + config `:root` tokens. */
24
+ export type LayoutStructuralOutput = {
25
+ ruleSetGroups: Record<string, RuleSetGroup>;
26
+ configProperties: Record<string, PropertyModel>;
27
+ };
28
+ export declare const buildColumns: (input: ColumnsValue | undefined, breakpoints: Record<string, number>) => LayoutStructuralOutput | undefined;
29
+ export declare const buildGrids: (grids: GridsDefinition | undefined) => LayoutStructuralOutput | undefined;
30
+ export declare const buildStacks: (stacks: StacksDefinition | undefined) => LayoutStructuralOutput | undefined;
31
+ export declare const buildContainer: (containerInput: unknown, breakpoints: Record<string, number>, sizeNames?: ReadonlySet<string>, mediaConfig?: MediaConfig) => LayoutStructuralOutput;
32
+ /**
33
+ * Run all four structural generators over the raw layout slice, merging their rule-set groups
34
+ * (structural order: columns → grids → stacks → container) and config tokens. Container always
35
+ * runs; the rest run only when their raw key is present.
36
+ */
37
+ export declare const buildLayoutStructural: (rawSlice: Record<string, unknown>, breakpoints: Record<string, number>, mediaConfig?: MediaConfig) => LayoutStructuralOutput;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Layout subsystem types (clean-room).
3
+ *
4
+ * Regular property keys (`spacing` / `gutters` / `aspectRatio`) normalize like any other
5
+ * subsystem; the structural keys (`columns` / `grids` / `stacks` / `container`) drive the
6
+ * structural generators (see `structural.ts`); `recipes` are ordinary token-path recipes.
7
+ */
8
+ import type { PropertyValue, RecipeBlock } from "../../core/normalize";
9
+ export type ResponsiveQuery = "min" | "max" | "exact";
10
+ /** The whitelisted "regular" layout property keys (the ones that become `PropertyModel`s). */
11
+ export declare const LAYOUT_PROPERTY_KEYS: readonly string[];
12
+ /**
13
+ * Scale-synthesis authoring keys (§10.6) for a length scale (`spacing`/`gutters`/`sizes`). Declare
14
+ * one curve — `ratio` (geometric `base × ratio^n`, `steps` an ordered name array) or `step` (linear
15
+ * `step × n`, `steps` a name→multiplier map) — to synthesize the variant ramp instead of hand-listing
16
+ * it. Opt-in: absent both, the property behaves exactly as before. The same keys inside a `responsive`
17
+ * entry regenerate the whole named scale at that breakpoint (D6). Authored `variants` still win.
18
+ */
19
+ export type LayoutScaleExtras = {
20
+ ratio?: number;
21
+ step?: number;
22
+ steps?: readonly string[] | Record<string, number>;
23
+ };
24
+ export type ColumnsConfig = {
25
+ size: number;
26
+ gutter?: string;
27
+ inset?: string;
28
+ };
29
+ export type ColumnsValue = number | ColumnsConfig;
30
+ export type GridDefinition = {
31
+ templateColumns?: string;
32
+ templateRows?: string;
33
+ autoRows?: string;
34
+ autoColumns?: string;
35
+ justifyItems?: string;
36
+ alignItems?: string;
37
+ justifyContent?: string;
38
+ alignContent?: string;
39
+ gap?: string;
40
+ responsive?: Array<{
41
+ breakpoint: string;
42
+ query?: ResponsiveQuery;
43
+ templateColumns?: string;
44
+ templateRows?: string;
45
+ autoRows?: string;
46
+ autoColumns?: string;
47
+ justifyItems?: string;
48
+ alignItems?: string;
49
+ justifyContent?: string;
50
+ alignContent?: string;
51
+ gap?: string;
52
+ }>;
53
+ };
54
+ export type GridsDefinition = Record<string, GridDefinition>;
55
+ export type StackDefinition = {
56
+ direction?: "row" | "column";
57
+ align?: string;
58
+ justify?: string;
59
+ wrap?: string;
60
+ inline?: boolean;
61
+ gap?: string;
62
+ responsive?: Array<{
63
+ breakpoint: string;
64
+ query?: ResponsiveQuery;
65
+ direction?: "row" | "column";
66
+ align?: string;
67
+ justify?: string;
68
+ wrap?: string;
69
+ inline?: boolean;
70
+ }>;
71
+ };
72
+ export type StacksDefinition = Record<string, StackDefinition>;
73
+ export type ContainerConfig = {
74
+ /** The resolved mode: `"fixed"` / `"fluid"` / a custom width string. */
75
+ mode: string;
76
+ inset?: string;
77
+ gutter?: string;
78
+ direction?: string;
79
+ align?: string;
80
+ justify?: string;
81
+ maxWidth?: string | number;
82
+ };
83
+ /**
84
+ * One authored container variant (§8a) — a mode string (`"fixed"` / `"fluid"` / a width) or a
85
+ * config object. Distinct from {@link ContainerConfig}, which is the resolved `{ mode }` shape the
86
+ * structural generator produces.
87
+ */
88
+ export type ContainerVariantRaw = string | {
89
+ base?: string;
90
+ inset?: string;
91
+ gutter?: string;
92
+ direction?: string;
93
+ align?: string;
94
+ justify?: string;
95
+ maxWidth?: string | number;
96
+ };
97
+ /** The authored `layout.container` value (§8a) — a mode string or a config with variants/responsive. */
98
+ export type ContainerRaw = string | {
99
+ base?: string;
100
+ inset?: string;
101
+ gutter?: string;
102
+ direction?: string;
103
+ align?: string;
104
+ justify?: string;
105
+ maxWidth?: string | number;
106
+ variants?: Record<string, ContainerVariantRaw>;
107
+ responsive?: Array<{
108
+ breakpoint: string;
109
+ query?: ResponsiveQuery;
110
+ target?: string;
111
+ direction?: string;
112
+ align?: string;
113
+ justify?: string;
114
+ inset?: string;
115
+ gutter?: string;
116
+ }>;
117
+ };
118
+ export type LayoutRecipeProps = {
119
+ paddingY?: string;
120
+ paddingX?: string;
121
+ marginY?: string;
122
+ marginX?: string;
123
+ gap?: string;
124
+ background?: string;
125
+ /**
126
+ * Sizing verbs (§22) — each names a `layout.sizes` variant and routes to its CSS longhand
127
+ * (`maxWidth` → `max-width`, no fan-out). A verb exists because it consumes a themed scale (`sizes`);
128
+ * dimensional CSS with no scale (`display`, `position`, …) stays in a component `css` delta.
129
+ */
130
+ width?: string;
131
+ minWidth?: string;
132
+ maxWidth?: string;
133
+ height?: string;
134
+ minHeight?: string;
135
+ maxHeight?: string;
136
+ [property: string]: string | undefined;
137
+ };
138
+ /**
139
+ * The authored `raw.layout` slice (§8a). The only **closed** subsystem — its keys are fixed:
140
+ * the regular property tokens (`spacing`/`gutters`/`aspectRatio`), the four structural generators
141
+ * (`columns`/`grids`/`stacks`/`container`), and the `recipes` block. Authoring type only.
142
+ */
143
+ export interface LayoutRaw {
144
+ spacing?: PropertyValue<string | number, LayoutScaleExtras>;
145
+ gutters?: PropertyValue<string | number, LayoutScaleExtras>;
146
+ aspectRatio?: PropertyValue<string | number>;
147
+ /** The sizing scale (§22) — one length scale for width/height/min/max, chosen at the recipe verb. */
148
+ sizes?: PropertyValue<string | number, LayoutScaleExtras>;
149
+ columns?: ColumnsValue;
150
+ grids?: GridsDefinition;
151
+ stacks?: StacksDefinition;
152
+ container?: ContainerRaw;
153
+ recipes?: RecipeBlock<LayoutRecipeProps>;
154
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The typography subsystem — a "regular" subsystem (no synthesized-ref derivations,
3
+ * unlike colors' tonal steps). Iterates `raw.typography` (skipping the reserved `recipes`
4
+ * key), runs the shared property normalize + the fontSize modular-scale synthesis per
5
+ * property, and interprets recipes into token-path refs. It grows a hook, never the spine.
6
+ */
7
+ import type { Subsystem } from "../../core/subsystem";
8
+ export declare const typographySubsystem: Subsystem;
9
+ export type { TypographyRaw, TypographyPropertyValue } from "./types";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Typography's `normalizeProperty` hook — modular font-size scale synthesis.
3
+ *
4
+ * When `fontSize` is authored with a `ratio`, this generates the scale steps
5
+ * (`xs`…`4xl`) as variants — each `base * ratio^step` (or a custom `algorithm`),
6
+ * rounded to `precision`. Author-declared steps are preserved and seed the chain.
7
+ * Non-`fontSize` properties and ratio-less `fontSize` pass through untouched.
8
+ */
9
+ import type { NormalizedPropertyValue } from "../../core/normalize";
10
+ /**
11
+ * The typography `normalizeProperty` hook. Synthesizes the modular scale for
12
+ * `fontSize`; a pass-through for every other typography property.
13
+ *
14
+ * @param propertyKey The property being normalized (only `"fontSize"` is transformed).
15
+ * @param normalized The property after the shared property normalize pass.
16
+ */
17
+ export declare const finalizeTypographyNormalization: (propertyKey: string, normalized: NormalizedPropertyValue<unknown>) => NormalizedPropertyValue<unknown>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Typography's `interpretRecipe` hook — resolves recipe declarations to token-path Refs.
3
+ *
4
+ * A typography recipe prop value names a **variant** of the matching property (`fontSize:
5
+ * "3xl"`, `fontWeight: "bold"`), or the base value (`fontSize: "base"`). This interpreter
6
+ * emits a format-neutral {@link Ref} carrying the canonical token path — `typography.fontSize.3xl`
7
+ * for a variant, `typography.fontSize` for the base. It emits refs **directly** (no `var(--…)`
8
+ * strings); the CSS adapter maps each path to a `var(--…)` at render time, so `theme.model`
9
+ * carries no CSS syntax. Ported + reshaped from the old `subsystems/typography/recipes.ts`
10
+ * (which produced `var(--…)` strings via a prefix resolver).
11
+ */
12
+ import type { InterpretedRecipeVariant } from "../../core/model";
13
+ import type { NormalizedRecipeVariant } from "../../core/normalize";
14
+ import type { RecipeInterpretContext } from "../../core/subsystem";
15
+ import type { TypographyRecipeProps } from "./types";
16
+ /**
17
+ * Interpret one typography recipe variant into base + responsive/state override declarations,
18
+ * each a token-path {@link Ref}. A responsive `variant:` swap inherits the sibling's base
19
+ * declarations (via `ctx.resolveRecipeVariant`).
20
+ */
21
+ export declare const interpretTypographyRecipeVariant: (variantName: string, variant: NormalizedRecipeVariant<TypographyRecipeProps, string>, ctx: RecipeInterpretContext) => InterpretedRecipeVariant<string>;
@@ -0,0 +1,49 @@
1
+ import type { PropertyValue, RecipeBlock } from "../../core/normalize";
2
+ /**
3
+ * Typography's fontSize-scale config. Only the normalize-relevant subset is ported
4
+ * for Step 0b (the recipe / token / source types grow in Step 2).
5
+ */
6
+ export type TypographyScaleKey = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
7
+ export type TypographyRatioKey = "minor-second" | "major-second" | "minor-third" | "major-third" | "perfect-fourth" | "augmented-fourth" | "perfect-fifth" | "golden";
8
+ export type FontSizeExtras = {
9
+ ratio?: TypographyRatioKey;
10
+ precision?: number;
11
+ unit?: "px" | "rem";
12
+ baseFontSize?: number;
13
+ algorithm?: (base: number, key: string, step: number, prev: number | null) => number;
14
+ };
15
+ export type FontSizeValue = PropertyValue<number, FontSizeExtras>;
16
+ /**
17
+ * A typography recipe declaration block. Each value names a **variant** of the
18
+ * matching typography property (`fontSize: "3xl"`, `fontWeight: "bold"`, `fontSize: "base"`
19
+ * for the base value) — the interpreter maps it to a token-path {@link import("../../core/model").Ref}.
20
+ * Only the keys in the property map are lowered; unknown keys are ignored.
21
+ */
22
+ export type TypographyRecipeProps = {
23
+ fontFamily?: string;
24
+ fontSize?: string;
25
+ fontWeight?: string;
26
+ lineHeight?: string;
27
+ letterSpacing?: string;
28
+ fontStyle?: string;
29
+ textTransform?: string;
30
+ textDecoration?: string;
31
+ textAlign?: string;
32
+ [property: string]: string | undefined;
33
+ };
34
+ /**
35
+ * A typography property value (§8a). Typography properties are heterogeneous —
36
+ * `fontFamily` is a string, `fontSize`/`fontWeight`/`lineHeight` numbers, `letterSpacing`
37
+ * a string — so the value unions `string | number`; the fontSize modular-scale extras
38
+ * (`ratio`/`precision`/…) ride along on every key (all optional, harmless elsewhere).
39
+ */
40
+ export type TypographyPropertyValue = PropertyValue<string | number, FontSizeExtras>;
41
+ /**
42
+ * The authored `raw.typography` slice (§8a). An **open** map: keys are typography property
43
+ * names (`fontFamily`, `fontSize`, …), each a {@link TypographyPropertyValue}; the reserved
44
+ * `recipes` key is a {@link RecipeBlock} of typography recipes. Authoring type only.
45
+ */
46
+ export interface TypographyRaw {
47
+ recipes?: RecipeBlock<TypographyRecipeProps>;
48
+ [property: string]: TypographyPropertyValue | RecipeBlock<TypographyRecipeProps> | undefined;
49
+ }
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@theme-registry/refract",
3
+ "version": "0.1.0",
4
+ "description": "The framework-agnostic, format-neutral theme compiler core — build a ThemeModel from a RawTheme, then lower it with any adapter package (CSS, SCSS, JSON, styled-components).",
5
+ "author": {
6
+ "name": "Petyo Stoyanov",
7
+ "email": "petyosv@gmail.com"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/theme-registry/refract.git",
12
+ "directory": "packages/refract"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/theme-registry/refract/issues"
16
+ },
17
+ "homepage": "https://github.com/theme-registry/refract#readme",
18
+ "license": "MIT",
19
+ "main": "./dist/index.cjs.js",
20
+ "module": "./dist/index.esm.js",
21
+ "types": "./dist/index.d.ts",
22
+ "bin": {
23
+ "refract": "./dist/cli.js"
24
+ },
25
+ "files": [
26
+ "/dist",
27
+ "/skills",
28
+ "/src/subsystems/colors/utils.ts"
29
+ ],
30
+ "sideEffects": false,
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.esm.js",
35
+ "require": "./dist/index.cjs.js"
36
+ },
37
+ "./dtcg": {
38
+ "types": "./dist/dtcg/index.d.ts",
39
+ "import": "./dist/dtcg.esm.js",
40
+ "require": "./dist/dtcg.cjs.js"
41
+ },
42
+ "./build": {
43
+ "types": "./dist/build/index.d.ts",
44
+ "import": "./dist/build.esm.js",
45
+ "require": "./dist/build.cjs.js"
46
+ },
47
+ "./adapter-kit": {
48
+ "types": "./dist/adapter-kit.d.ts",
49
+ "import": "./dist/adapter-kit.esm.js",
50
+ "require": "./dist/adapter-kit.cjs.js"
51
+ },
52
+ "./color-math": {
53
+ "types": "./dist/subsystems/colors/utils.d.ts",
54
+ "import": "./dist/color-math.esm.js",
55
+ "require": "./dist/color-math.cjs.js"
56
+ },
57
+ "./package.json": "./package.json"
58
+ },
59
+ "peerDependencies": {
60
+ "typescript": ">=5.0.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "typescript": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "devDependencies": {
68
+ "@rollup/plugin-terser": "^0.4.4",
69
+ "@rollup/plugin-typescript": "^11.1.6",
70
+ "@types/node": "^24",
71
+ "rimraf": "^5.0.5",
72
+ "rollup": "^3.29.4",
73
+ "rollup-plugin-peer-deps-external": "^2.2.4",
74
+ "typescript": "^5.4.2",
75
+ "vitest": "^4.1.10"
76
+ },
77
+ "scripts": {
78
+ "build": "rollup -c",
79
+ "typecheck": "tsc -p tsconfig.typecheck.json",
80
+ "clean": "rimraf dist",
81
+ "test": "vitest run"
82
+ }
83
+ }
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: adapter-scaffold
3
+ description: Write your own refract output adapter when no shipped adapter targets your format — a standalone package or a local module in your own project, against the public defineAdapter contract. Use when you need a format refract doesn't ship (React-Native, Flutter, a bespoke JSON shape, …). Triggers: "no adapter for X", "write my own adapter", "custom output format", "defineAdapter", "React Native adapter".
4
+ tier: optional
5
+ ---
6
+
7
+ # Write your own adapter
8
+
9
+ refract's adapter contract is public and stable, so if the shipped adapters (CSS, SCSS,
10
+ styled-components, JSON — see **adapter-usage**) don't target your format, you can write one **in
11
+ your own project** — a small standalone npm package or just a local module. You depend only on the
12
+ published `@theme-registry/refract`; you do **not** fork or clone the refract repo. Full contract
13
+ reference: `docs/extending.md`.
14
+
15
+ ## The contract
16
+
17
+ `createTheme` calls your adapter's `bind(model, ctx)` once, then curries the returned `BoundSpec`
18
+ onto the theme. You author an `AdapterSpec<TUnit>` and wrap it with `defineAdapter`.
19
+
20
+ - **`TUnit`** — your output unit: `string` for text formats, an object type for JSON / RN / style
21
+ objects. `recipeName` is always `string`; only `renderRecipe` / `renderVariables` / `join` are
22
+ `TUnit`.
23
+ - **Required primitives** (the only format-specific work):
24
+ - `recipeName(subsystem, group, variant): string` — the rule-set's identity (a class, a key).
25
+ - `renderRecipe(subsystem, group, variant): TUnit` — one rule-set (base + state/responsive rules).
26
+ - `renderVariables(subsystem): TUnit` — one subsystem's tokens.
27
+ - `join(parts: TUnit[]): TUnit` — combine units.
28
+ - **`allowedStates?`** — the states your format can render (`["hover","focus",…]`); omit to accept
29
+ any. Core validates recipe `state:` refs against it before `bind`.
30
+ - **Optional:** `renderToken`, aggregator overrides (`renderAllRecipes`/`…Variables`/`renderAll`),
31
+ `extend(theme)` (attach runtime glue), `emit(plan?)` (build-time files — switch on `plan.type`
32
+ and throw a clear error for modes you don't support).
33
+
34
+ `defineAdapter(spec)` supplies the default aggregators as pure Model walks — override only if the
35
+ full document isn't a flat concatenation.
36
+
37
+ ## The module
38
+
39
+ ```ts
40
+ import type { ThemeModel, AdapterSpec, RenderContext, ThemeAdapter } from "@theme-registry/refract";
41
+ import { defineAdapter } from "@theme-registry/refract";
42
+ // naming helpers if you need them: from "@theme-registry/refract/adapter-kit"
43
+ // OKLCH color math if you need it: from "@theme-registry/refract/color-math"
44
+
45
+ export type MyUnit = string; // ← or your object type
46
+ export type MyAdapterOptions = { /* prefix?, … */ };
47
+
48
+ export const createMyAdapter = (options: MyAdapterOptions = {}): ThemeAdapter<MyUnit> =>
49
+ defineAdapter({
50
+ name: "my-format",
51
+ version: 1,
52
+ // allowedStates: ["hover", "focus", "disabled"],
53
+ bind(model: ThemeModel, ctx: RenderContext) {
54
+ const { media, containers, resolve } = ctx; // resolve(ref) → value; containers = §10.5
55
+ void options; void media; void containers;
56
+ // Precompute per-subsystem lowering here (walk model.subsystems.<sub>.properties / .ruleSets).
57
+ return {
58
+ recipeName(subsystem, group, variant) { /* … */ return `${subsystem}-${group}-${variant}`; },
59
+ renderRecipe(subsystem, group, variant) { /* … */ return "" as MyUnit; },
60
+ renderVariables(subsystem) { /* … */ return "" as MyUnit; },
61
+ join(parts) { return parts.join("\n\n") as MyUnit; },
62
+ };
63
+ },
64
+ });
65
+ ```
66
+
67
+ Use it like any adapter: `createTheme(raw, { adapter: createMyAdapter() })`.
68
+
69
+ ## Packaging choices
70
+
71
+ - **Local module** — simplest: a file in your app, imported straight into your `theme.config` or a
72
+ `createTheme` call. No publishing.
73
+ - **Standalone package** — if you want to reuse or share it: a normal npm package with
74
+ `@theme-registry/refract` as a **peer dependency** (the consumer's single installed core is
75
+ shared), marked `external` in your bundler so you never ship a copy of core.
76
+
77
+ ## Notes
78
+
79
+ - Never import from a relative core path — always the published specifiers
80
+ (`@theme-registry/refract`, `/adapter-kit`, `/color-math`).
81
+ - Adapters are **not** subsystems; you don't register anything on core.
82
+ - Read `docs/extending.md` for the full `ThemeModel` walk and the `emit()` file contract.
83
+
84
+ *(Contributing an adapter back into the refract monorepo itself is a different, internal workflow —
85
+ that's the repo's own `adapter-scaffold` contributor skill, not this one.)*
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: adapter-usage
3
+ description: Choose a refract output adapter and use its call surface — CSS, SCSS, styled-components, or JSON, each a separate installable package. Use when deciding which format to emit, wiring createTheme with an adapter, or installing the adapter package. Triggers: "which adapter", "createTheme adapter", "CSS vs styled-components", "install refract-css", "adapter options / prefix / colorFormat / scheme".
4
+ tier: core
5
+ ---
6
+
7
+ # Adapter usage
8
+
9
+ Core (`@theme-registry/refract`) ships **zero adapters** — that's what keeps it format-neutral.
10
+ Each adapter is a **separate package** you install alongside core. Pick by target format, then
11
+ pass a constructed adapter to `createTheme`.
12
+
13
+ ## Which adapter
14
+
15
+ | Package | Factory | Emits | Choose it when |
16
+ |---|---|---|---|
17
+ | `@theme-registry/refract-css` | `createCssAdapter` | `:root` custom properties + recipe classes, `@media`/`@container`/`@keyframes` | Default. Framework-agnostic; runtime theming by swapping `:root`; MFE isolation via `prefix`. |
18
+ | `@theme-registry/refract-styled-components` | `createStyledComponentsAdapter` | TS/JS modules (literal `theme` object + tree-shakeable `css` recipes + `GlobalStyle`) | React apps on styled-components ^6. No `var()`; dark lives in each recipe so it tree-shakes. |
19
+ | `@theme-registry/refract-scss` | `createScssAdapter` | Sass `$variables` + `@use` partials + mixins/classes | A Sass build wanting compile-time `$variables`. |
20
+ | `@theme-registry/refract-json` | `createJsonAdapter` | The full model as address-keyed JSON (refs kept beside resolved values) | Data-interchange, tooling, or feeding a non-CSS consumer. |
21
+
22
+ If the format you need has no adapter, you can write one — see the opt-in **adapter-scaffold**
23
+ guide (build a standalone adapter against the public `defineAdapter` contract).
24
+
25
+ ## Install the package you picked
26
+
27
+ Add core **and** the chosen adapter (detect the package manager from the lockfile):
28
+
29
+ ```
30
+ npm i @theme-registry/refract @theme-registry/refract-css
31
+ # styled-components target also needs the peer:
32
+ npm i @theme-registry/refract-styled-components styled-components
33
+ ```
34
+
35
+ ## Call surface
36
+
37
+ `createTheme(raw, { adapter })` — `adapter` is **required**. Returns a `Theme` with the
38
+ format-neutral surface (`model`, `tokens`, `resolveToken(path)`, `override(partial)`), plus
39
+ whatever the adapter attaches via `extend()`:
40
+
41
+ ```ts
42
+ import { createTheme } from "@theme-registry/refract";
43
+ import { createCssAdapter } from "@theme-registry/refract-css";
44
+
45
+ const theme = createTheme(raw, { adapter: createCssAdapter({ prefix: "acme" }) });
46
+ theme.css; // full stylesheet
47
+ theme.classes; // recipe → class name map
48
+ theme.getClass("components", "buttons", "primary");
49
+ ```
50
+
51
+ - **CSS** adds `theme.css` / `variablesCss` / `recipesCss` / `classes` / `getClass()` /
52
+ `renderRecipe()` / `media`.
53
+ - **styled-components** adds `theme.theme` (for `<ThemeProvider>`) / `recipes` / `GlobalStyle` /
54
+ `media` / `scheme`.
55
+ - **JSON** adds `theme.json` / `jsonString`; **SCSS** adds `theme.scss` / `variablesScss` /
56
+ `rulesScss` / `classes`.
57
+
58
+ See **consuming-the-output** for wiring these into an app.
59
+
60
+ ## Adapter options (constructor)
61
+
62
+ Options are passed when constructing the adapter, never as CLI flags. The common ones:
63
+
64
+ - **CSS** — `prefix` (default `"dt"`, also the class prefix + MFE isolation), `classPrefix`,
65
+ `inline` (bake values, drop `var()`), `colorFormat: "rgb" | "hex" | "oklch"`, `naming` (§7B
66
+ override hook), `layer: string | boolean` (wrap output in `@layer` for deterministic precedence
67
+ under app CSS), `reducedMotion: true` (append a `prefers-reduced-motion` reduce block). The last
68
+ two are single-file-emit only and off by default (byte-identical).
69
+ - **styled-components** — `language: "ts" | "js"`, `emit: "single" | "split"`,
70
+ `scheme: "media" | "attribute" | "both"` (dark realization), `helpers: ["color-math"]`,
71
+ `naming`.
72
+ - **SCSS** — `prefix`, `inline`, `indent`, `layer` (same as CSS). **JSON** — minimal; see
73
+ `docs/css-adapter.md` and `docs/authoring.md` for the full tables.
74
+
75
+ Cross-format concerns — emit modes, dark-mode strategy, the CLI — live in **build-config**.
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: build-config
3
+ description: Wire the refract build — theme.config.ts (defineConfig, targets), the emit modes (single/split/subsystem/components), the refract CLI (init/build/import/tokens), and per-target dark-mode strategy. Use when emitting a theme to disk rather than building it at runtime. Triggers: "theme.config", "defineConfig", "refract build", "emit split/subsystem/components", "output multiple formats", "dark mode strategy", "refract init".
4
+ tier: core
5
+ ---
6
+
7
+ # Build config
8
+
9
+ Two ways to realize a theme: **at runtime** via `createTheme(raw, { adapter })` (see
10
+ **adapter-usage**), or **to disk** via a `theme.config` + the `refract` CLI. This guide is the
11
+ disk path.
12
+
13
+ ## `theme.config.(ts|mjs|js)`
14
+
15
+ The config is **your code** — it imports the adapter(s) and the raw theme, and that import *is*
16
+ the extensibility seam (like Rollup/Vite). Adapter options are passed at construction, not as CLI
17
+ flags. Multiple targets emit multiple formats from one theme. Keep the raw theme in a sibling
18
+ `theme.raw.ts` (the `.ts` config graph-compiles, so `import { raw } from "./theme.raw"` resolves).
19
+
20
+ ```ts
21
+ import { defineConfig } from "@theme-registry/refract/build";
22
+ import { createCssAdapter } from "@theme-registry/refract-css";
23
+ import { createStyledComponentsAdapter } from "@theme-registry/refract-styled-components";
24
+ import { raw } from "./theme.raw";
25
+
26
+ export default defineConfig({
27
+ raw,
28
+ targets: [
29
+ { name: "css", adapter: createCssAdapter(), outDir: "dist/theme" },
30
+ { name: "sc", adapter: createStyledComponentsAdapter({ language: "ts" }), outDir: "dist/sc" },
31
+ ],
32
+ // media / units / baseFontSize resolve once so every target emits identical units.
33
+ });
34
+ ```
35
+
36
+ `ThemeConfig` = `{ raw, targets, media?, units?, baseFontSize? }`. Each `EmitTarget` =
37
+ `{ adapter, outDir, name?, emit?, helpers? }`. Resolution order: `theme.config.ts` → `.mjs` →
38
+ `.js` (a `.ts` config lazy-loads the optional `typescript` peer).
39
+
40
+ ## Emit modes (`emit` on a target)
41
+
42
+ Core owns the vocabulary; **each adapter decides which modes it honors** and throws a clear error
43
+ otherwise.
44
+
45
+ | Mode | Output | Notes |
46
+ |---|---|---|
47
+ | `single` (default) | one file | `{ file }` renames it |
48
+ | `split` | rules file + variables file | load-order contract, **no `@import`** |
49
+ | `subsystem` | a styles+variables pair per subsystem | `filename(subsystem, kind)` |
50
+ | `components` | one self-contained rule-set per component variant | `inline` defaults **true** (bakes values); `inline: false` emits `var()` + a tree-shaken variables file |
51
+
52
+ Only `single` / `split` may omit `type`. Support matrix: **CSS** and **JSON** = all four; **SCSS**
53
+ = single/split/components (subsystem throws); **styled-components** = single/split only (ES modules
54
+ tree-shake, so the CSS-only modes are redundant).
55
+
56
+ ## The `refract` CLI
57
+
58
+ - `refract init [--js|--mjs] [--force]` — scaffold a runnable `theme.config`.
59
+ - `refract build [--config <path>] [--target <name|index>] [--out <dir>]` — emit each target.
60
+ - `refract import <tokens.json> […]` — seed a theme from a DTCG document (see **dtcg-import**).
61
+ - `refract tokens [--config <path>] [--out <file>]` — DTCG export (see **dtcg-import**).
62
+ - `refract help`.
63
+
64
+ Run via `npx refract <cmd>`. `outDir` resolves relative to the config file's directory.
65
+
66
+ ## Dark-mode strategy (differs by target)
67
+
68
+ - **CSS / SCSS / JSON** — dark rides the *referenced token's own `modes`* (see **theme-foundations**);
69
+ a token with a `dark` value emits `@media (prefers-color-scheme: dark)` overrides. No separate
70
+ global switch; `colorFormat` is the main CSS colour knob.
71
+ - **styled-components** — an explicit `scheme` option: `"media"` (OS), `"attribute"`
72
+ (`[data-theme="dark"]` manual toggle), or `"both"`.
73
+
74
+ ## Vendored helpers
75
+
76
+ `helpers: ["color-math"]` on a target materializes a shared helper module (live `lighten`/`darken`/
77
+ `alpha`) next to the output — opt-in, only when named.