@evinvest/uikit 0.17.1 → 0.19.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 CHANGED
@@ -47,22 +47,35 @@ your Tailwind v4 entrypoint — this is the load-bearing part of the kit:
47
47
 
48
48
  | group | names |
49
49
  |---|---|
50
- | scope | `:root` · `.dark` · `.light` — a consumer defines one or both |
50
+ | scope | `:root` · `[data-brand="…"]` — a theme root; `.light` · `.dark` — a polarity beneath it |
51
51
  | surfaces | `background` `card` `popover` `muted` `hover` |
52
52
  | ink | `ink` `ink-mid` `ink-soft` — hierarchy, loudest first |
53
53
  | lines | `border` `input` `ring` |
54
- | roles | `brand` `primary` `secondary` `positive` `accent-trace` `accent-debug` `accent-info` `accent-warn` `accent-error`, each with `on-*` where it gets filled |
54
+ | roles | `brand` `primary` `secondary` `positive` `accent-trace` `accent-debug` `accent-info` `accent-warn` `accent-error`, each with `on-*` where it gets filled; `primary-ink` — the primary role as ink on a surface (`#128377` fill under a white `on-primary`, `#2a9d8f` ink) |
55
55
  | scalars | `radius` `control-radius` `control-py` `display-scale` `band-py` `page-max` `page-px` `shadow-*` `font-*` |
56
56
  | charts | `chart-1` … `chart-5` |
57
57
 
58
58
  Custom properties inherit, so a scope class on a `<section>` re-themes its
59
59
  subtree — `bg-card text-ink` is correct on both polarities with no prop. That is
60
- what `<Section polarity="dark">` does.
60
+ what `<Section polarity="dark">` does. Without `polarity` a `Section` sets no
61
+ scope and inherits its parent's, so a dark-first brand stays dark; pass
62
+ `"light"` or `"dark"` only where a band flips.
61
63
 
62
64
  A surface takes its ink from the scope, so it carries no `-foreground`. A
63
65
  **filled role** does not — gold wants black and navy wants white regardless of
64
66
  polarity — so `on-{role}` exists exactly there.
65
67
 
68
+ A fill and an ink are different jobs, and `primary` is the one role that
69
+ names them separately: `primary` is the fill (`bg-primary text-on-primary` on
70
+ a button, a checked switch or checkbox — `#128377`, white reads on it at
71
+ 4.63:1 and it clears 3:1 against `background`, `secondary`, `card` and
72
+ `popover`) and `primary-ink` is the same hue as ink (`text-primary-ink` on a
73
+ link or eyebrow, a radio dot, a slider range, `border-primary-ink` on a checked
74
+ outline, the focus `ring` — `#2a9d8f`, 5.85:1 on `background`, 4.72:1 on
75
+ `card`). Every other role holds its own label and reads as ink with one value.
76
+ A consumer sheet must define `--primary-ink`; see
77
+ [`docs/spec/accents.md`](../../docs/spec/accents.md).
78
+
66
79
  Accents are decorative and **ordered by significance**, quiet to loud. Pick a
67
80
  rung by how loud the thing should be, never by what it means; see
68
81
  [`docs/spec/accents.md`](../../docs/spec/accents.md). Every consumer defines all
@@ -74,14 +87,185 @@ state, never its appearance (`Red`) and never its selection (`Default`); see
74
87
  [`docs/spec/variants.md`](../../docs/spec/variants.md).
75
88
 
76
89
  `styles/tokens.css` is generated by `nix run .#gen` from the repo-root
77
- `tokens.css` shared with the Rust feature — flattened, so it is self-contained —
78
- and carries EV's values: a single dark palette on `:root, .dark`. A consumer
79
- with two polarities writes its own sheet against the same names.
90
+ `tokens.css` shared with the Rust feature — flattened, so it is self-contained.
91
+ It has two halves, each also shipped as a flat sheet of its own —
92
+ `styles/theme.css` (the contract) and `styles/ev.css` (EV's palette):
93
+
94
+ - **the contract** — the `@theme inline` mapping, the geometry, and the tokens
95
+ *derived* from a palette (`hover`, `ink-mid`, `ink-soft`, `border`, `input`
96
+ from `ink`; `ring` from `primary-ink`). The derived ones are redeclared on
97
+ `:root`, `[data-brand]`, `.light` and `.dark` at zero specificity, so a brand
98
+ scope gets its own borders, hover and focus ring rather than inheriting the
99
+ root's, and a palette may still pin any of them outright. The geometry
100
+ (`radius`, `page-px`, `band-py`, …) and its responsive scale-ups restart on
101
+ every `[data-brand]` the same way.
102
+ - **EV's palette** — a single dark palette on `:where(:root, [data-brand="ev"])`.
103
+ It binds neither polarity class, so a `.dark` section inside another brand
104
+ never picks EV's values up, and `<Section polarity="light">` under EV is a
105
+ no-op (EV is single-polarity).
106
+
107
+ ### Brand palettes
108
+
109
+ A second brand is a palette against the same names, scoped to
110
+ `[data-brand="<slug>"]`; light is its default and `.light` / `.dark` beneath it
111
+ pick a polarity, so a `<Section polarity="dark">` inside the brand takes the
112
+ brand's dark values. The package ships a generator for it, reading the
113
+ `brand.toml` shape (`[colors.light]` and `[colors.dark]`, keyed by token name;
114
+ optional top-level `mark` and `mark-aspect` become `--brand-mark` and
115
+ `--brand-aspect`; the family names in `[fonts]` — `sans` (or aquafix's `text`),
116
+ `display`, `mono` — become the [font parameters](#fonts); other keys and tables
117
+ are ignored). Each polarity block also declares `--scheme: light | dark`, which
118
+ the platform-drawn parts of the kit read (a `NativeSelect`'s popup):
119
+
120
+ ```sh
121
+ npx evinvest-palette --brand aquafix --out app/brand.css assets/brand.toml
122
+ ```
123
+
124
+ It fails — naming every hole — unless **both** polarities declare every name the
125
+ contract requires: Tailwind answers an undefined token with no rule at all, so a
126
+ hole would be silent on the page. Values must be `#hex` or `var(--token)`. The
127
+ list of required names is read from the installed kit's own `styles/theme.css`,
128
+ never restated. The same functions are importable for a build script or a Vite
129
+ plugin from `@evinvest/uikit/palette` (Node-only; see `example/vite.config.ts`).
130
+ The generator checks completeness, not contrast: measure a new palette against
131
+ the floors in `test/tokens.test.ts`, which runs them over every palette the kit
132
+ knows.
133
+
134
+ ```css
135
+ @import "tailwindcss";
136
+ @import "@evinvest/uikit/styles/tokens.css";
137
+ @import "./brand.css"; /* order does not matter: EV's palette has zero specificity */
138
+ ```
139
+
140
+ ```html
141
+ <html data-brand="aquafix">
142
+ ```
143
+
144
+ An app that never shows EV's palette imports the contract alone, so a name its
145
+ palette misses stays a visible hole instead of falling back to EV's value:
146
+
147
+ ```css
148
+ @import "tailwindcss";
149
+ @import "@evinvest/uikit/styles/theme.css";
150
+ @import "./brand.css";
151
+ ```
152
+
153
+ Overlays (`Dialog`, `Select`, `Popover`, `Tooltip`, menus, …) portal to
154
+ `document.body` and would leave the brand scope there. Wrap the scope's content
155
+ in `PortalProvider` with an element **inside** the scope — and inside its
156
+ polarity, or a dialog opened from a dark band comes up light:
157
+
158
+ ```tsx
159
+ const [overlays, setOverlays] = useState<HTMLDivElement | null>(null);
160
+
161
+ <section data-brand="aquafix" className="dark">
162
+ <PortalProvider container={overlays}>{children}</PortalProvider>
163
+ <div ref={setOverlays} />
164
+ </section>
165
+ ```
166
+
167
+ Put the container as a direct child of the `[data-brand]` (or polarity)
168
+ element, and never under an ancestor with `overflow: hidden`, a `transform`,
169
+ `filter` or `perspective`, or anything else that makes a containing block or
170
+ an isolated stacking context (`contain`, `isolation: isolate`, a `z-index`ed
171
+ positioned box): overlays are positioned `fixed`/`absolute` against the
172
+ viewport and stacked by `z-index`, so any of those clips them, offsets them, or
173
+ buries them under the page.
174
+
175
+ A brand on `<html>` needs no provider: `document.body` is already inside it.
176
+ Brand scopes do not nest — CSS has no "nearest ancestor", so a polarity class
177
+ under a brand under another brand may match either.
178
+
179
+ ### Fonts
180
+
181
+ The families are parameters, one per role; unset, each is EV's:
182
+
183
+ | parameter | utilities | default (EV) |
184
+ |---|---|---|
185
+ | `--brand-font-sans` | `font-sans`, the page's base family | `--font-inter`, else `"Inter"` |
186
+ | `--brand-font-display` | `font-display`, `font-serif` | `--font-playfair`, else `"Playfair Display"` |
187
+ | `--brand-font-mono` | `font-mono` | `--font-inter`, else `"Inter"` |
188
+
189
+ Declare them on the brand scope (the palette generator writes them from
190
+ `[fonts]`) and load the files yourself — `next/font`, or your own `@font-face`;
191
+ the kit ships no font files:
192
+
193
+ ```css
194
+ [data-brand="aquafix"] {
195
+ --brand-font-display: "Archivo";
196
+ }
197
+ ```
198
+
199
+ The utilities resolve the chain on the element, so a scope re-fonts everything
200
+ under it that carries one. The page's base family resolves once, on `<html>`: a
201
+ brand scoped lower than `<html>` puts `font-sans` on its own element so plain
202
+ text picks the brand's family up.
203
+
204
+ ## Forms that submit before hydration
205
+
206
+ A landing page's form has to work with scripting off and keep its labels wired
207
+ through hydration. Three parts of the kit make that hold:
208
+
209
+ - **`NativeSelect`** is a real `<select name>` wearing the kit's control — the
210
+ kit's arrow instead of the OS one, token colours, the popup following the
211
+ palette's `--scheme`. It posts with the form without any script, and the
212
+ popup is the platform's (keyboard, type-ahead, the mobile picker).
213
+ `NativeSelectOption` / `NativeSelectGroup` are `<option>` / `<optgroup>`.
214
+ `placeholder` adds an empty, unpickable first option, selected until a value
215
+ is chosen and styled `text-ink-soft`; with `required`, the browser refuses to
216
+ submit it. `className` styles the `<select>`, `wrapperClassName` the box that
217
+ holds it and the arrow. That box is `w-full`, like `Input`, so the two line
218
+ up in a form column; narrow it with `wrapperClassName`, not `className`.
219
+
220
+ It is a component of its own rather than a mode of `Select`: none of
221
+ `SelectTrigger` / `SelectValue` / `SelectContent` has a native counterpart.
222
+ `Select` stays the scripted combobox for what a platform popup cannot draw
223
+ (rich rows, a check mark, brand-styled menus everywhere).
224
+ - **`Field` mints the id** with `React.useId` — positional, so the server and the
225
+ hydrating client agree — and hands it to its `FieldLabel` as `for` and to the
226
+ kit's `Input`, `Textarea`, `NativeSelect`, `SelectTrigger`, `Checkbox` and
227
+ `Switch` as `id`, each only when the caller passed none. No `FormControl`
228
+ wrapper is needed. Want your own id — pass `controlId` to the `Field`, and
229
+ both ends take it. One control per `Field`: when it holds more, give the
230
+ others an `id` of their own (a development warning names the collision).
231
+ A `FieldLabel` that wraps its control (`<FieldLabel><input type="checkbox" />
232
+ Accept</FieldLabel>`, or a nested `Field` — the choice card) gets no `for`:
233
+ the wrapping already labels it. A control hidden inside a component of yours
234
+ is invisible to that check; pass `htmlFor={null}` there to drop the `for`.
235
+ (`FormItem` / `FormControl` keep their own `useId` wiring for forms that
236
+ want `aria-describedby` too.)
237
+ - **`SelectValue` shows the label**, not the stored value: the matching
238
+ `SelectItem`'s `textValue`, else the text of its children — text, so an id in
239
+ the item's markup is not rendered a second time in the trigger. It is read off
240
+ the element tree, so it is right on the server and before the popover first
241
+ opens. An item rendered by a component of yours is learnt when it mounts (on
242
+ the first open), and the trigger updates then; pass `SelectValue` children to
243
+ show something else outright.
244
+
245
+ ```tsx
246
+ <form action="/lead" method="post">
247
+ <Field>
248
+ <FieldLabel>Service</FieldLabel>
249
+ <NativeSelect name="service" placeholder="Pick one" required>
250
+ <NativeSelectOption value="leak">Leak</NativeSelectOption>
251
+ <NativeSelectOption value="boiler">Boiler</NativeSelectOption>
252
+ </NativeSelect>
253
+ </Field>
254
+ <Field>
255
+ <FieldLabel>Phone</FieldLabel>
256
+ <Input name="phone" type="tel" />
257
+ </Field>
258
+ <Button type="submit">Request a call</Button>
259
+ </form>
260
+ ```
80
261
 
81
- `text-main-mist`, `bg-sidebar`, `text-muted-foreground` and the rest keep
82
- working, and the current tokens are re-pointed at the old values so rendered
83
- colour is unchanged. Import it instead while migrating; it goes away once every
84
- consumer has moved.
262
+ **Focus.** A control that paints a fill — `Button` `primary` / `secondary` /
263
+ `destructive` or any accent fill, a filled `Badge`, `Switch`, `Checkbox` —
264
+ shows focus as the solid `ring` standing 2px off it, so both edges of the
265
+ indicator are against the surface: the ring reads on every surface at ≥ 3:1
266
+ and so does the fill, on every palette and polarity `test/tokens.test.ts`
267
+ measures. Controls that sit on the surface (`outline`, `ghost`, inputs) keep
268
+ the `ring` border plus its halo.
85
269
 
86
270
  ## Usage
87
271
 
@@ -129,11 +313,12 @@ element across the two ports.
129
313
  | overlay placement | inline `position:fixed` + `data-side` | `Portal` + `useFloating` |
130
314
  | dismiss / focus trap | full-screen backdrop / native order | `useDismissableLayer` / `useFocusScope` |
131
315
 
132
- ### Component inventory (all 62 bricks)
316
+ ### Component inventory (all 63 bricks)
133
317
 
134
- - **Tier A — static (22):** badge, button, button-group, card, input, textarea,
318
+ - **Tier A — static (23):** badge, button, button-group, card, input, textarea,
135
319
  label, field, separator, skeleton, spinner, kbd, table, container, alert,
136
- breadcrumb, empty, item, input-group, avatar, progress, pagination.
320
+ breadcrumb, empty, item, input-group, avatar, progress, pagination,
321
+ native-select (TS-only).
137
322
  - **Tier B — interactive (13):** accordion, collapsible, tabs, toggle,
138
323
  toggle-group, switch, checkbox, radio-group, slider, sidebar, scroll-area,
139
324
  carousel, input-otp.
@@ -236,6 +421,12 @@ measuring needs host-only `web-sys`). Known gaps:
236
421
  `max-h-*` on `DrawerContent` and let the body scroll by itself.
237
422
  - **sidebar:** the mobile-sheet integration, cookie persistence, and keyboard
238
423
  shortcut are omitted.
424
+ - **footer:** brings no content of its own — every string, the mark and the
425
+ lock-up are the caller's, and each column renders only when it has content.
426
+ The lock-up is composed from `mark` + `brand` (display family) + `tagline`,
427
+ or passed whole as `lockup`. Brand-coloured text reads `primary-ink`.
428
+ - **TS-only for now:** `NativeSelect`, the `Field` id hand-off and
429
+ `SelectValue` labels.
239
430
  - **brand chrome (header / footer / status pages):** TS routes links through an
240
431
  optional `linkComponent` (e.g. `next/link`) for soft navigation; Rust renders
241
432
  plain `<a>` (a full document load). The Dioxus header drives its scroll state,
@@ -0,0 +1,306 @@
1
+ // src/palette/contract.ts
2
+ function references(value) {
3
+ return [...value.matchAll(/var\(--([A-Za-z0-9-]+)/g)].flatMap((m) => m[1] ? [m[1]] : []);
4
+ }
5
+ var DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand])";
6
+ function readRules(css) {
7
+ const rules = [];
8
+ const src = css.replace(/\/\*[\s\S]*?\*\//g, "");
9
+ const stack = [];
10
+ let start = 0;
11
+ for (let i = 0; i < src.length; i++) {
12
+ const c = src[i];
13
+ if (c === "{") {
14
+ stack.push(src.slice(start, i).trim());
15
+ start = i + 1;
16
+ } else if (c === "}") {
17
+ const selector = stack.pop();
18
+ if (selector === void 0) throw new Error("unbalanced `}` in token sheet");
19
+ const body = src.slice(start, i);
20
+ if (!body.includes("{")) rules.push({ selector: normalise(selector), declarations: declarations(body) });
21
+ start = i + 1;
22
+ } else if (c === ";" && stack.length === 0) {
23
+ start = i + 1;
24
+ }
25
+ }
26
+ if (stack.length > 0) throw new Error("unbalanced `{` in token sheet");
27
+ return rules;
28
+ }
29
+ function declarations(body) {
30
+ const out = /* @__PURE__ */ new Map();
31
+ for (const decl of body.split(";")) {
32
+ const match = /^\s*--([A-Za-z0-9-]+)\s*:\s*([\s\S]+?)\s*$/.exec(decl);
33
+ if (match?.[1] && match[2]) out.set(match[1], match[2]);
34
+ }
35
+ return out;
36
+ }
37
+ function normalise(selector) {
38
+ return selector.replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").trim();
39
+ }
40
+ function readContract(css) {
41
+ const rules = readRules(css);
42
+ const theme = rules.find((r) => r.selector === "@theme inline");
43
+ if (!theme) throw new Error("token sheet has no `@theme inline` block \u2014 not the kit's contract");
44
+ const painted = [];
45
+ for (const [name, value] of theme.declarations) {
46
+ const match = /^color-(.+)$/.exec(name);
47
+ if (match?.[1] && value === `var(--${match[1]})`) painted.push(match[1]);
48
+ }
49
+ const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);
50
+ if (!derivedRule) throw new Error(`token sheet has no \`${DERIVED_SCOPE}\` rule \u2014 not the kit's contract`);
51
+ const derived = [...derivedRule.declarations.keys()];
52
+ const stray = derived.filter((d) => !painted.includes(d));
53
+ if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(", ")}`);
54
+ const derivedFrom = Object.fromEntries([...derivedRule.declarations].map(([name, value]) => [name, references(value)]));
55
+ return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };
56
+ }
57
+
58
+ // src/palette/toml.ts
59
+ var BARE_KEY = /^[A-Za-z0-9_-]+$/;
60
+ function parseToml(text) {
61
+ const root = {};
62
+ let table2 = root;
63
+ const headers = /* @__PURE__ */ new Set();
64
+ text.split(/\r?\n/).forEach((raw, index) => {
65
+ const line = stripComment(raw).trim();
66
+ if (line === "") return;
67
+ const fail = (why) => {
68
+ throw new Error(`TOML line ${index + 1}: ${why}: ${raw.trim()}`);
69
+ };
70
+ if (line.startsWith("[")) {
71
+ if (!line.endsWith("]") || line.startsWith("[[")) fail("only plain [table] headers are supported");
72
+ const path = line.slice(1, -1).trim().split(".").map((part) => part.trim());
73
+ if (headers.has(path.join("."))) fail("duplicate table header");
74
+ headers.add(path.join("."));
75
+ table2 = descend(root, path, fail);
76
+ return;
77
+ }
78
+ const eq = line.indexOf("=");
79
+ if (eq < 0) fail("expected `key = value`");
80
+ const key = line.slice(0, eq).trim();
81
+ if (!BARE_KEY.test(key)) fail("only bare keys are supported");
82
+ if (key in table2) fail(`duplicate key \`${key}\``);
83
+ table2[key] = parseValue(line.slice(eq + 1).trim(), fail);
84
+ });
85
+ return root;
86
+ }
87
+ function descend(root, path, fail) {
88
+ let table2 = root;
89
+ for (const part of path) {
90
+ const key = part.trim();
91
+ if (!BARE_KEY.test(key)) fail("only bare keys are supported in a header");
92
+ const next = table2[key] ?? {};
93
+ if (typeof next !== "object" || Array.isArray(next)) fail(`\`${key}\` is already a value`);
94
+ table2[key] = next;
95
+ table2 = next;
96
+ }
97
+ return table2;
98
+ }
99
+ function stripComment(line) {
100
+ let quoted = false;
101
+ for (let i = 0; i < line.length; i++) {
102
+ const c = line[i];
103
+ if (c === "\\" && quoted) i++;
104
+ else if (c === '"') quoted = !quoted;
105
+ else if (c === "#" && !quoted) return line.slice(0, i);
106
+ }
107
+ return line;
108
+ }
109
+ function parseValue(src, fail) {
110
+ if (src.startsWith('"')) {
111
+ if (src.length < 2 || !src.endsWith('"')) fail("unterminated string");
112
+ const body = src.slice(1, -1);
113
+ if (/(^|[^\\])(\\\\)*"/.test(body)) fail("one string per value");
114
+ if (/\\(?!["\\nt])/.test(body.replace(/\\\\/g, ""))) fail("unsupported escape");
115
+ return body.replace(/\\(["\\nt])/g, (_, c) => ({ n: "\n", t: " " })[c] ?? c);
116
+ }
117
+ if (src.startsWith("[")) {
118
+ if (!src.endsWith("]")) fail("arrays must close on the same line");
119
+ const inner = src.slice(1, -1).trim();
120
+ if (inner === "") return [];
121
+ return splitTopLevel(inner).map((item) => parseValue(item.trim(), fail));
122
+ }
123
+ if (src === "true" || src === "false") return src === "true";
124
+ if (/^[+-]?\d+(\.\d+)?$/.test(src)) return Number(src);
125
+ return fail("unsupported value");
126
+ }
127
+ function splitTopLevel(inner) {
128
+ const items = [];
129
+ let quoted = false;
130
+ let start = 0;
131
+ for (let i = 0; i < inner.length; i++) {
132
+ const c = inner[i];
133
+ if (c === "\\" && quoted) i++;
134
+ else if (c === '"') quoted = !quoted;
135
+ else if (c === "," && !quoted) {
136
+ items.push(inner.slice(start, i));
137
+ start = i + 1;
138
+ }
139
+ }
140
+ const last = inner.slice(start);
141
+ if (last.trim() !== "") items.push(last);
142
+ return items;
143
+ }
144
+
145
+ // src/palette/brand.ts
146
+ var POLARITIES = ["light", "dark"];
147
+ var FONT_ROLES = ["sans", "display", "mono"];
148
+ var FONT_KEYS = { sans: "sans", text: "sans", display: "display", mono: "mono" };
149
+ var PaletteError = class extends Error {
150
+ constructor(problems) {
151
+ super(`invalid brand palette:
152
+ - ${problems.join("\n - ")}`);
153
+ this.problems = problems;
154
+ this.name = "PaletteError";
155
+ }
156
+ problems;
157
+ };
158
+ var COLOUR = /^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|var\(--[a-z0-9-]+\))$/;
159
+ var SLUG = /^[a-z0-9][a-z0-9-]*$/;
160
+ var ASPECT = /^\d+(\.\d+)?\s*\/\s*\d+(\.\d+)?$/;
161
+ var MARK_URL = /^[^"'`\\\s()<>]+$/;
162
+ var FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;
163
+ function brandFromToml(text) {
164
+ const doc = parseToml(text);
165
+ const problems = [];
166
+ const colors = table(doc["colors"]);
167
+ if (!colors) problems.push("no [colors] table");
168
+ const scope = (name) => {
169
+ const t = colors ? table(colors[name]) : void 0;
170
+ if (colors && !t) problems.push(`no [colors.${name}] table`);
171
+ const out = {};
172
+ for (const [key, value] of Object.entries(t ?? {})) {
173
+ if (typeof value === "string") out[key] = value;
174
+ else problems.push(`colors.${name}.${key} is not a string`);
175
+ }
176
+ return out;
177
+ };
178
+ for (const key of Object.keys(colors ?? {})) {
179
+ if (!POLARITIES.includes(key)) problems.push(`[colors.${key}] is not a polarity (light, dark)`);
180
+ }
181
+ const config = { colors: { light: scope("light"), dark: scope("dark") } };
182
+ const mark = optionalString(doc["mark"], "mark", problems);
183
+ const markAspect = optionalString(doc["mark-aspect"], "mark-aspect", problems);
184
+ if (mark !== void 0) config.mark = mark;
185
+ if (markAspect !== void 0) config.markAspect = markAspect;
186
+ const fonts = table(doc["fonts"]);
187
+ for (const [key, role] of Object.entries(FONT_KEYS)) {
188
+ const family = optionalString(fonts?.[key], `fonts.${key}`, problems);
189
+ if (family !== void 0) config.fonts = { ...config.fonts, [role]: family };
190
+ }
191
+ if (problems.length > 0) throw new PaletteError(problems);
192
+ return config;
193
+ }
194
+ function table(value) {
195
+ return typeof value === "object" && !Array.isArray(value) ? value : void 0;
196
+ }
197
+ function optionalString(value, key, problems) {
198
+ if (value === void 0 || typeof value === "string") return value;
199
+ problems.push(`${key} is not a string`);
200
+ return void 0;
201
+ }
202
+ function validateBrand(config, contract) {
203
+ const problems = [];
204
+ const known = /* @__PURE__ */ new Set([...contract.required, ...contract.derived]);
205
+ for (const polarity of POLARITIES) {
206
+ const values = config.colors[polarity];
207
+ const missing = contract.required.filter((t) => !(t in values));
208
+ if (missing.length > 0) problems.push(`${polarity} is missing ${missing.join(", ")}`);
209
+ for (const [key, value] of Object.entries(values)) {
210
+ if (!known.has(key)) problems.push(`${polarity}.${key} is not a token of the contract`);
211
+ else if (!COLOUR.test(value)) problems.push(`${polarity}.${key} = ${JSON.stringify(value)} is not #hex or var(--token)`);
212
+ else {
213
+ for (const ref of references(value)) {
214
+ if (!known.has(ref)) problems.push(`${polarity}.${key} refers to --${ref}, which is not a token of the contract`);
215
+ }
216
+ }
217
+ }
218
+ const cycle = findCycle(values, contract, known);
219
+ if (cycle) problems.push(`${polarity} has a reference cycle: ${cycle.map((t) => `--${t}`).join(" \u2192 ")}`);
220
+ }
221
+ if (config.mark !== void 0 && !MARK_URL.test(config.mark)) problems.push(`mark ${JSON.stringify(config.mark)} is not a plain URL`);
222
+ if (config.markAspect !== void 0 && !ASPECT.test(config.markAspect)) {
223
+ problems.push(`mark-aspect ${JSON.stringify(config.markAspect)} is not "width / height"`);
224
+ }
225
+ for (const [role, family] of Object.entries(config.fonts ?? {})) {
226
+ if (!FAMILY.test(family)) problems.push(`fonts.${role} ${JSON.stringify(family)} is not a plain family name`);
227
+ }
228
+ return problems;
229
+ }
230
+ function findCycle(values, contract, known) {
231
+ const edges = (name) => {
232
+ const value = values[name];
233
+ if (value !== void 0) return references(value).filter((r) => known.has(r));
234
+ return contract.derivedFrom[name] ?? [];
235
+ };
236
+ const done = /* @__PURE__ */ new Set();
237
+ const path = [];
238
+ const visit = (name) => {
239
+ const at = path.indexOf(name);
240
+ if (at >= 0) return [...path.slice(at), name];
241
+ if (done.has(name)) return void 0;
242
+ path.push(name);
243
+ for (const next of edges(name)) {
244
+ const cycle = visit(next);
245
+ if (cycle) return cycle;
246
+ }
247
+ path.pop();
248
+ done.add(name);
249
+ return void 0;
250
+ };
251
+ for (const name of known) {
252
+ const cycle = visit(name);
253
+ if (cycle) return cycle;
254
+ }
255
+ return void 0;
256
+ }
257
+ function renderPalette(brand, config, contract, source = "a brand file") {
258
+ const problems = SLUG.test(brand) ? [] : [`brand ${JSON.stringify(brand)} is not a lowercase slug`];
259
+ problems.push(...validateBrand(config, contract));
260
+ if (problems.length > 0) throw new PaletteError(problems);
261
+ const at = `[data-brand="${brand}"]`;
262
+ const order = [...contract.required, ...contract.derived];
263
+ const block = (selectors, polarity) => {
264
+ const values = config.colors[polarity];
265
+ const body = order.filter((t) => t in values).map((t) => ` --${t}: ${values[t]};
266
+ `);
267
+ return `${selectors.join(",\n")} {
268
+ --scheme: ${polarity};
269
+ ${body.join("")}}
270
+ `;
271
+ };
272
+ const extras = [];
273
+ if (config.mark !== void 0) extras.push(` --brand-mark: url("${config.mark}");
274
+ `);
275
+ if (config.markAspect !== void 0) extras.push(` --brand-aspect: ${config.markAspect};
276
+ `);
277
+ for (const role of FONT_ROLES) {
278
+ const family = config.fonts?.[role];
279
+ if (family !== void 0) extras.push(` --brand-font-${role}: "${family}";
280
+ `);
281
+ }
282
+ return [
283
+ // `source` is a file name, free text as far as CSS is concerned: it must not close the comment.
284
+ `/* GENERATED by evinvest-palette from ${source.replaceAll("*/", "*\\/")} \u2014 do not edit. */
285
+ `,
286
+ block([at, `${at}.light`, `${at} .light`], "light"),
287
+ block([`${at}.dark`, `${at} .dark`], "dark"),
288
+ ...extras.length > 0 ? [`${at} {
289
+ ${extras.join("")}}
290
+ `] : []
291
+ ].join("\n");
292
+ }
293
+
294
+ export {
295
+ DERIVED_SCOPE,
296
+ readRules,
297
+ readContract,
298
+ parseToml,
299
+ POLARITIES,
300
+ FONT_ROLES,
301
+ PaletteError,
302
+ brandFromToml,
303
+ validateBrand,
304
+ renderPalette
305
+ };
306
+ //# sourceMappingURL=chunk-3IPB4QQJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/palette/contract.ts","../src/palette/toml.ts","../src/palette/brand.ts"],"sourcesContent":["/**\n * The colour contract, read out of the kit's own token sheet rather than\n * restated here: `@theme inline` maps every name the class tables can paint\n * (`--color-X: var(--X)`), and the derived rule lists the ones the contract\n * computes from the rest. A palette owes the difference.\n */\nexport interface Contract {\n /** Names every palette scope must declare. */\n required: readonly string[];\n /** Names the contract derives from the palette; a palette may override them. */\n derived: readonly string[];\n /** The tokens each derived default reads, so a palette's references can be checked for cycles through them. */\n derivedFrom: Readonly<Record<string, readonly string[]>>;\n}\n\n/** The `--name`s a value reads through `var()`. */\nexport function references(value: string): string[] {\n return [...value.matchAll(/var\\(--([A-Za-z0-9-]+)/g)].flatMap((m) => (m[1] ? [m[1]] : []));\n}\n\n/** The rule the derived tokens live on — see `theme.css`, the contract half of `tokens.css`. */\nexport const DERIVED_SCOPE = \":where(:root, .dark, .light, [data-brand])\";\n\nexport interface CssRule {\n selector: string;\n /** Custom properties declared directly in the rule, in source order. */\n declarations: Map<string, string>;\n}\n\n/**\n * Top-level and `@media`-nested rules of a flat sheet, comments stripped. Only\n * as much CSS as a token sheet is made of: no strings containing braces.\n */\nexport function readRules(css: string): CssRule[] {\n const rules: CssRule[] = [];\n const src = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const stack: string[] = [];\n let start = 0;\n for (let i = 0; i < src.length; i++) {\n const c = src[i];\n if (c === \"{\") {\n stack.push(src.slice(start, i).trim());\n start = i + 1;\n } else if (c === \"}\") {\n const selector = stack.pop();\n if (selector === undefined) throw new Error(\"unbalanced `}` in token sheet\");\n const body = src.slice(start, i);\n if (!body.includes(\"{\")) rules.push({ selector: normalise(selector), declarations: declarations(body) });\n start = i + 1;\n } else if (c === \";\" && stack.length === 0) {\n start = i + 1;\n }\n }\n if (stack.length > 0) throw new Error(\"unbalanced `{` in token sheet\");\n return rules;\n}\n\nfunction declarations(body: string): Map<string, string> {\n const out = new Map<string, string>();\n for (const decl of body.split(\";\")) {\n const match = /^\\s*--([A-Za-z0-9-]+)\\s*:\\s*([\\s\\S]+?)\\s*$/.exec(decl);\n if (match?.[1] && match[2]) out.set(match[1], match[2]);\n }\n return out;\n}\n\nexport function normalise(selector: string): string {\n return selector.replace(/\\s+/g, \" \").replace(/\\s*,\\s*/g, \", \").trim();\n}\n\nexport function readContract(css: string): Contract {\n const rules = readRules(css);\n const theme = rules.find((r) => r.selector === \"@theme inline\");\n if (!theme) throw new Error(\"token sheet has no `@theme inline` block — not the kit's contract\");\n const painted: string[] = [];\n for (const [name, value] of theme.declarations) {\n const match = /^color-(.+)$/.exec(name);\n if (match?.[1] && value === `var(--${match[1]})`) painted.push(match[1]);\n }\n const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);\n if (!derivedRule) throw new Error(`token sheet has no \\`${DERIVED_SCOPE}\\` rule — not the kit's contract`);\n const derived = [...derivedRule.declarations.keys()];\n const stray = derived.filter((d) => !painted.includes(d));\n if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(\", \")}`);\n const derivedFrom = Object.fromEntries([...derivedRule.declarations].map(([name, value]) => [name, references(value)]));\n return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };\n}\n","/**\n * The subset of TOML a brand file is written in: `[table.headers]`, bare keys,\n * and single-line values that are a basic string, a number, a boolean, or a\n * flat array of those. Anything else is an error naming the line, rather than a\n * silent misread — the kit stays dependency-free, and a brand file has no use\n * for the rest of the language.\n */\n\nexport type TomlValue = string | number | boolean | TomlValue[];\nexport interface TomlTable {\n [key: string]: TomlValue | TomlTable;\n}\n\nconst BARE_KEY = /^[A-Za-z0-9_-]+$/;\n\nexport function parseToml(text: string): TomlTable {\n const root: TomlTable = {};\n let table = root;\n const headers = new Set<string>();\n text.split(/\\r?\\n/).forEach((raw, index) => {\n const line = stripComment(raw).trim();\n if (line === \"\") return;\n const fail = (why: string): never => {\n throw new Error(`TOML line ${index + 1}: ${why}: ${raw.trim()}`);\n };\n\n if (line.startsWith(\"[\")) {\n if (!line.endsWith(\"]\") || line.startsWith(\"[[\")) fail(\"only plain [table] headers are supported\");\n const path = line.slice(1, -1).trim().split(\".\").map((part) => part.trim());\n // Re-opening a table would merge two halves of it silently.\n if (headers.has(path.join(\".\"))) fail(\"duplicate table header\");\n headers.add(path.join(\".\"));\n table = descend(root, path, fail);\n return;\n }\n\n const eq = line.indexOf(\"=\");\n if (eq < 0) fail(\"expected `key = value`\");\n const key = line.slice(0, eq).trim();\n if (!BARE_KEY.test(key)) fail(\"only bare keys are supported\");\n if (key in table) fail(`duplicate key \\`${key}\\``);\n table[key] = parseValue(line.slice(eq + 1).trim(), fail);\n });\n return root;\n}\n\nfunction descend(root: TomlTable, path: string[], fail: (why: string) => never): TomlTable {\n let table = root;\n for (const part of path) {\n const key = part.trim();\n if (!BARE_KEY.test(key)) fail(\"only bare keys are supported in a header\");\n const next = table[key] ?? {};\n if (typeof next !== \"object\" || Array.isArray(next)) fail(`\\`${key}\\` is already a value`);\n table[key] = next;\n table = next as TomlTable;\n }\n return table;\n}\n\n// A `#` inside a string is not a comment; the brand files carry `\"#rrggbb\"`.\nfunction stripComment(line: string): string {\n let quoted = false;\n for (let i = 0; i < line.length; i++) {\n const c = line[i];\n if (c === \"\\\\\" && quoted) i++;\n else if (c === '\"') quoted = !quoted;\n else if (c === \"#\" && !quoted) return line.slice(0, i);\n }\n return line;\n}\n\nfunction parseValue(src: string, fail: (why: string) => never): TomlValue {\n if (src.startsWith('\"')) {\n if (src.length < 2 || !src.endsWith('\"')) fail(\"unterminated string\");\n const body = src.slice(1, -1);\n if (/(^|[^\\\\])(\\\\\\\\)*\"/.test(body)) fail(\"one string per value\");\n // Only the escapes decoded below; `\\u…` and friends would otherwise pass through as literal text.\n if (/\\\\(?![\"\\\\nt])/.test(body.replace(/\\\\\\\\/g, \"\"))) fail(\"unsupported escape\");\n return body.replace(/\\\\([\"\\\\nt])/g, (_, c: string) => ({ n: \"\\n\", t: \"\\t\" })[c] ?? c);\n }\n if (src.startsWith(\"[\")) {\n if (!src.endsWith(\"]\")) fail(\"arrays must close on the same line\");\n const inner = src.slice(1, -1).trim();\n if (inner === \"\") return [];\n return splitTopLevel(inner).map((item) => parseValue(item.trim(), fail));\n }\n if (src === \"true\" || src === \"false\") return src === \"true\";\n if (/^[+-]?\\d+(\\.\\d+)?$/.test(src)) return Number(src);\n return fail(\"unsupported value\");\n}\n\nfunction splitTopLevel(inner: string): string[] {\n const items: string[] = [];\n let quoted = false;\n let start = 0;\n for (let i = 0; i < inner.length; i++) {\n const c = inner[i];\n if (c === \"\\\\\" && quoted) i++;\n else if (c === '\"') quoted = !quoted;\n else if (c === \",\" && !quoted) {\n items.push(inner.slice(start, i));\n start = i + 1;\n }\n }\n const last = inner.slice(start);\n if (last.trim() !== \"\") items.push(last);\n return items;\n}\n","import { references, type Contract } from \"./contract\";\nimport { parseToml, type TomlTable, type TomlValue } from \"./toml\";\n\nexport type PolarityName = \"light\" | \"dark\";\nexport const POLARITIES: readonly PolarityName[] = [\"light\", \"dark\"];\n\n/** A brand's palette: one value per contract name, for each polarity. */\nexport interface BrandConfig {\n colors: Record<PolarityName, Record<string, string>>;\n /** `--brand-mark`: the URL `<Logo>` masks. */\n mark?: string;\n /** `--brand-aspect`: the mark's width / height, e.g. `\"387 / 335\"`. */\n markAspect?: string;\n /** `--brand-font-<role>`: a family name per role; unset roles keep the kit's. */\n fonts?: Partial<Record<FontRole, string>>;\n}\n\nexport type FontRole = \"sans\" | \"display\" | \"mono\";\nexport const FONT_ROLES: readonly FontRole[] = [\"sans\", \"display\", \"mono\"];\n// aquafix's file calls the body family `text`.\nconst FONT_KEYS: Record<string, FontRole> = { sans: \"sans\", text: \"sans\", display: \"display\", mono: \"mono\" };\n\n/** Every problem with a brand file at once, so one run fixes them all. */\nexport class PaletteError extends Error {\n constructor(readonly problems: readonly string[]) {\n super(`invalid brand palette:\\n - ${problems.join(\"\\n - \")}`);\n this.name = \"PaletteError\";\n }\n}\n\n// Written into CSS verbatim, so a value is held to a shape that cannot close\n// the declaration: a hex colour, or a reference to another token.\nconst COLOUR = /^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|var\\(--[a-z0-9-]+\\))$/;\nconst SLUG = /^[a-z0-9][a-z0-9-]*$/;\nconst ASPECT = /^\\d+(\\.\\d+)?\\s*\\/\\s*\\d+(\\.\\d+)?$/;\n// Written inside `url(\"…\")`: nothing that closes the string or the function,\n// and no markup characters for a sheet that ends up inlined in HTML.\nconst MARK_URL = /^[^\"'`\\\\\\s()<>]+$/;\nconst FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;\n\n/**\n * Reads the aquafix `brand.toml` shape: `[colors.light]` and `[colors.dark]`\n * keyed by contract name, optional top-level `mark` / `mark-aspect`, and the\n * family names in `[fonts]` (`sans` or `text`, `display`, `mono`). The rest of\n * `[fonts]` and every other table (`[print]`, …) belong to other tools and are\n * ignored — loading the font files is the site's job, not the palette's.\n */\nexport function brandFromToml(text: string): BrandConfig {\n const doc = parseToml(text);\n const problems: string[] = [];\n const colors = table(doc[\"colors\"]);\n if (!colors) problems.push(\"no [colors] table\");\n const scope = (name: PolarityName): Record<string, string> => {\n const t = colors ? table(colors[name]) : undefined;\n if (colors && !t) problems.push(`no [colors.${name}] table`);\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(t ?? {})) {\n if (typeof value === \"string\") out[key] = value;\n else problems.push(`colors.${name}.${key} is not a string`);\n }\n return out;\n };\n for (const key of Object.keys(colors ?? {})) {\n if (!(POLARITIES as readonly string[]).includes(key)) problems.push(`[colors.${key}] is not a polarity (light, dark)`);\n }\n const config: BrandConfig = { colors: { light: scope(\"light\"), dark: scope(\"dark\") } };\n const mark = optionalString(doc[\"mark\"], \"mark\", problems);\n const markAspect = optionalString(doc[\"mark-aspect\"], \"mark-aspect\", problems);\n if (mark !== undefined) config.mark = mark;\n if (markAspect !== undefined) config.markAspect = markAspect;\n const fonts = table(doc[\"fonts\"]);\n for (const [key, role] of Object.entries(FONT_KEYS)) {\n const family = optionalString(fonts?.[key], `fonts.${key}`, problems);\n if (family !== undefined) config.fonts = { ...config.fonts, [role]: family };\n }\n if (problems.length > 0) throw new PaletteError(problems);\n return config;\n}\n\nfunction table(value: TomlValue | TomlTable | undefined): TomlTable | undefined {\n return typeof value === \"object\" && !Array.isArray(value) ? value : undefined;\n}\n\nfunction optionalString(value: TomlValue | TomlTable | undefined, key: string, problems: string[]) {\n if (value === undefined || typeof value === \"string\") return value;\n problems.push(`${key} is not a string`);\n return undefined;\n}\n\n/** The problems with `config` against `contract`; empty when it is complete. */\nexport function validateBrand(config: BrandConfig, contract: Contract): string[] {\n const problems: string[] = [];\n const known = new Set([...contract.required, ...contract.derived]);\n for (const polarity of POLARITIES) {\n const values = config.colors[polarity];\n const missing = contract.required.filter((t) => !(t in values));\n if (missing.length > 0) problems.push(`${polarity} is missing ${missing.join(\", \")}`);\n for (const [key, value] of Object.entries(values)) {\n if (!known.has(key)) problems.push(`${polarity}.${key} is not a token of the contract`);\n else if (!COLOUR.test(value)) problems.push(`${polarity}.${key} = ${JSON.stringify(value)} is not #hex or var(--token)`);\n else {\n for (const ref of references(value)) {\n if (!known.has(ref)) problems.push(`${polarity}.${key} refers to --${ref}, which is not a token of the contract`);\n }\n }\n }\n const cycle = findCycle(values, contract, known);\n if (cycle) problems.push(`${polarity} has a reference cycle: ${cycle.map((t) => `--${t}`).join(\" → \")}`);\n }\n if (config.mark !== undefined && !MARK_URL.test(config.mark)) problems.push(`mark ${JSON.stringify(config.mark)} is not a plain URL`);\n if (config.markAspect !== undefined && !ASPECT.test(config.markAspect)) {\n problems.push(`mark-aspect ${JSON.stringify(config.markAspect)} is not \"width / height\"`);\n }\n for (const [role, family] of Object.entries(config.fonts ?? {})) {\n if (!FAMILY.test(family)) problems.push(`fonts.${role} ${JSON.stringify(family)} is not a plain family name`);\n }\n return problems;\n}\n\n/**\n * A `var()` cycle makes every token on it invalid at computed-value time — the\n * same silent hole as a missing token. Edges are the palette's own references,\n * and for a derived token it leaves to the contract, the contract's formula\n * (`border` reads `ink`), so `ink = \"var(--border)\"` is caught too.\n */\nfunction findCycle(values: Record<string, string>, contract: Contract, known: Set<string>): string[] | undefined {\n const edges = (name: string): readonly string[] => {\n const value = values[name];\n if (value !== undefined) return references(value).filter((r) => known.has(r));\n return contract.derivedFrom[name] ?? [];\n };\n const done = new Set<string>();\n const path: string[] = [];\n const visit = (name: string): string[] | undefined => {\n const at = path.indexOf(name);\n if (at >= 0) return [...path.slice(at), name];\n if (done.has(name)) return undefined;\n path.push(name);\n for (const next of edges(name)) {\n const cycle = visit(next);\n if (cycle) return cycle;\n }\n path.pop();\n done.add(name);\n return undefined;\n };\n for (const name of known) {\n const cycle = visit(name);\n if (cycle) return cycle;\n }\n return undefined;\n}\n\n/**\n * A flat sheet scoping `config` under `[data-brand=\"<brand>\"]`: light is the\n * brand's default and `.light` / `.dark` beneath it pick a polarity. Selectors\n * outrank the zero-specificity EV palette, so import order does not matter.\n * Throws a {@link PaletteError} when a scope is incomplete — Tailwind answers an\n * undefined token with no rule at all, so a hole would be silent on the page.\n */\nexport function renderPalette(brand: string, config: BrandConfig, contract: Contract, source = \"a brand file\"): string {\n const problems = SLUG.test(brand) ? [] : [`brand ${JSON.stringify(brand)} is not a lowercase slug`];\n problems.push(...validateBrand(config, contract));\n if (problems.length > 0) throw new PaletteError(problems);\n\n const at = `[data-brand=\"${brand}\"]`;\n const order = [...contract.required, ...contract.derived];\n const block = (selectors: string[], polarity: PolarityName) => {\n const values = config.colors[polarity];\n const body = order.filter((t) => t in values).map((t) => ` --${t}: ${values[t]};\\n`);\n return `${selectors.join(\",\\n\")} {\\n --scheme: ${polarity};\\n${body.join(\"\")}}\\n`;\n };\n const extras: string[] = [];\n if (config.mark !== undefined) extras.push(` --brand-mark: url(\"${config.mark}\");\\n`);\n if (config.markAspect !== undefined) extras.push(` --brand-aspect: ${config.markAspect};\\n`);\n for (const role of FONT_ROLES) {\n const family = config.fonts?.[role];\n if (family !== undefined) extras.push(` --brand-font-${role}: \"${family}\";\\n`);\n }\n\n return [\n // `source` is a file name, free text as far as CSS is concerned: it must not close the comment.\n `/* GENERATED by evinvest-palette from ${source.replaceAll(\"*/\", \"*\\\\/\")} — do not edit. */\\n`,\n block([at, `${at}.light`, `${at} .light`], \"light\"),\n block([`${at}.dark`, `${at} .dark`], \"dark\"),\n ...(extras.length > 0 ? [`${at} {\\n${extras.join(\"\")}}\\n`] : []),\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":["table"]}