@mlola-ui/engine 1.0.13 → 1.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.
- package/generated/agents.md +2 -0
- package/generated/contract.json +4 -0
- package/generated/recipes.css +18 -3
- package/generated/theme-spec.schema.json +8 -0
- package/generated/themes.json +5 -0
- package/generated/tokens.css +5 -5
- package/generated/tokens.json +5 -0
- package/package.json +6 -2
- package/src/declarations.mjs +3 -1
- package/src/palette.mjs +23 -15
- package/src/spec.mjs +12 -0
- package/src/theme.d.ts +17 -0
package/generated/agents.md
CHANGED
|
@@ -159,6 +159,7 @@ correct. This table is read out of the stylesheet, so it is never stale.
|
|
|
159
159
|
| `.ml-button` | `data-size` | `icon`, `lg`, `sm` |
|
|
160
160
|
| `.ml-button` | `data-state` | `success` |
|
|
161
161
|
| `.ml-button` | `data-variant` | `danger`, `link`, `outline`, `primary`, `secondary`, `subtle` |
|
|
162
|
+
| `.ml-button` | `data-width` | `full` |
|
|
162
163
|
| `.ml-calendar-cell` | `data-preview` | _presence only_ |
|
|
163
164
|
| `.ml-calendar-cell` | `data-range` | `end`, `start` |
|
|
164
165
|
| `.ml-calendar-day` | `aria-disabled` | `true` |
|
|
@@ -212,6 +213,7 @@ correct. This table is read out of the stylesheet, so it is never stale.
|
|
|
212
213
|
| `.ml-heatmap-cell` | `data-level` | `1`, `2`, `3`, `4` |
|
|
213
214
|
| `.ml-heatmap-cell` | `data-outside` | _presence only_ |
|
|
214
215
|
| `.ml-input` | `aria-invalid` | `true` |
|
|
216
|
+
| `.ml-input` | `data-invalid` | _presence only_ |
|
|
215
217
|
| `.ml-input` | `data-size` | `lg`, `sm` |
|
|
216
218
|
| `.ml-input` | `data-variant` | `filled`, `subtle` |
|
|
217
219
|
| `.ml-input-control` | `data-leading` | _presence only_ |
|
package/generated/contract.json
CHANGED
|
@@ -87,6 +87,9 @@
|
|
|
87
87
|
"primary",
|
|
88
88
|
"secondary",
|
|
89
89
|
"subtle"
|
|
90
|
+
],
|
|
91
|
+
"data-width": [
|
|
92
|
+
"full"
|
|
90
93
|
]
|
|
91
94
|
},
|
|
92
95
|
"ml-calendar-cell": {
|
|
@@ -283,6 +286,7 @@
|
|
|
283
286
|
"aria-invalid": [
|
|
284
287
|
"true"
|
|
285
288
|
],
|
|
289
|
+
"data-invalid": [],
|
|
286
290
|
"data-size": [
|
|
287
291
|
"lg",
|
|
288
292
|
"sm"
|
package/generated/recipes.css
CHANGED
|
@@ -1513,6 +1513,7 @@ button.ml-breadcrumb-link {
|
|
|
1513
1513
|
.ml-button {
|
|
1514
1514
|
position: relative;
|
|
1515
1515
|
display: inline-flex;
|
|
1516
|
+
max-width: 100%;
|
|
1516
1517
|
min-height: var(--ml-control-md);
|
|
1517
1518
|
align-items: center;
|
|
1518
1519
|
justify-content: center;
|
|
@@ -1528,7 +1529,6 @@ button.ml-breadcrumb-link {
|
|
|
1528
1529
|
line-height: 1;
|
|
1529
1530
|
letter-spacing: -.006em;
|
|
1530
1531
|
text-decoration: none;
|
|
1531
|
-
white-space: nowrap;
|
|
1532
1532
|
cursor: pointer;
|
|
1533
1533
|
user-select: none;
|
|
1534
1534
|
-webkit-tap-highlight-color: transparent;
|
|
@@ -1649,10 +1649,22 @@ button.ml-breadcrumb-link {
|
|
|
1649
1649
|
padding: 0;
|
|
1650
1650
|
}
|
|
1651
1651
|
|
|
1652
|
+
/* A label too long for the space wraps onto balanced lines instead of
|
|
1653
|
+
spilling out. The taller line height is taken back by the margins, so a
|
|
1654
|
+
one-line button is exactly as tall as before. */
|
|
1652
1655
|
.ml-button-label {
|
|
1653
1656
|
display: inline-flex;
|
|
1654
1657
|
align-items: center;
|
|
1655
1658
|
gap: var(--ml-space-1-5);
|
|
1659
|
+
margin-block: -.1em;
|
|
1660
|
+
line-height: 1.2;
|
|
1661
|
+
text-align: center;
|
|
1662
|
+
text-wrap: balance;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
.ml-button[data-width="full"] {
|
|
1666
|
+
display: flex;
|
|
1667
|
+
width: 100%;
|
|
1656
1668
|
}
|
|
1657
1669
|
|
|
1658
1670
|
.ml-button[data-state="success"] {
|
|
@@ -3493,13 +3505,16 @@ button.ml-breadcrumb-link {
|
|
|
3493
3505
|
font-size: var(--ml-type-md);
|
|
3494
3506
|
}
|
|
3495
3507
|
|
|
3508
|
+
/* A field that is a button or a group says it is invalid with data-invalid: aria-invalid belongs to inputs. */
|
|
3496
3509
|
.ml-input-field[data-invalid] .ml-input,
|
|
3497
|
-
.ml-input[aria-invalid="true"]
|
|
3510
|
+
.ml-input[aria-invalid="true"],
|
|
3511
|
+
.ml-input[data-invalid] {
|
|
3498
3512
|
border-color: var(--ml-danger);
|
|
3499
3513
|
}
|
|
3500
3514
|
|
|
3501
3515
|
.ml-input-field[data-invalid] .ml-input:focus,
|
|
3502
|
-
.ml-input[aria-invalid="true"]:focus
|
|
3516
|
+
.ml-input[aria-invalid="true"]:focus,
|
|
3517
|
+
.ml-input[data-invalid]:focus {
|
|
3503
3518
|
box-shadow: 0 0 0 3px color-mix(in oklab,var(--ml-danger) 20%,transparent);
|
|
3504
3519
|
}
|
|
3505
3520
|
|
|
@@ -105,6 +105,14 @@
|
|
|
105
105
|
],
|
|
106
106
|
"description": "Optional different seed for dark mode."
|
|
107
107
|
},
|
|
108
|
+
"primaryForeground": {
|
|
109
|
+
"enum": [
|
|
110
|
+
"auto",
|
|
111
|
+
"light",
|
|
112
|
+
"dark"
|
|
113
|
+
],
|
|
114
|
+
"description": "The label on primary fills in light mode. \"auto\" lets the engine choose; \"light\" or \"dark\" moves the fill as far as it must for that label to meet contrast."
|
|
115
|
+
},
|
|
108
116
|
"neutral": {
|
|
109
117
|
"type": "object",
|
|
110
118
|
"properties": {
|
package/generated/themes.json
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"color": {
|
|
36
36
|
"primary": "oklch(0.21 0.006 286)",
|
|
37
37
|
"primaryDark": null,
|
|
38
|
+
"primaryForeground": "auto",
|
|
38
39
|
"neutral": {
|
|
39
40
|
"hue": 286,
|
|
40
41
|
"chroma": 0.004
|
|
@@ -83,6 +84,7 @@
|
|
|
83
84
|
"color": {
|
|
84
85
|
"primary": "oklch(0.55 0.16 58)",
|
|
85
86
|
"primaryDark": "oklch(0.75 0.16 68)",
|
|
87
|
+
"primaryForeground": "auto",
|
|
86
88
|
"neutral": {
|
|
87
89
|
"hue": 80,
|
|
88
90
|
"chroma": 0.016
|
|
@@ -134,6 +136,7 @@
|
|
|
134
136
|
"color": {
|
|
135
137
|
"primary": "oklch(0.22 0.01 240)",
|
|
136
138
|
"primaryDark": "oklch(0.78 0.15 210)",
|
|
139
|
+
"primaryForeground": "auto",
|
|
137
140
|
"neutral": {
|
|
138
141
|
"hue": 240,
|
|
139
142
|
"chroma": 0.004
|
|
@@ -185,6 +188,7 @@
|
|
|
185
188
|
"color": {
|
|
186
189
|
"primary": "oklch(0.55 0.24 285)",
|
|
187
190
|
"primaryDark": "oklch(0.75 0.2 285)",
|
|
191
|
+
"primaryForeground": "auto",
|
|
188
192
|
"neutral": {
|
|
189
193
|
"hue": 260,
|
|
190
194
|
"chroma": 0.018
|
|
@@ -236,6 +240,7 @@
|
|
|
236
240
|
"color": {
|
|
237
241
|
"primary": "oklch(0.5 0.14 150)",
|
|
238
242
|
"primaryDark": "oklch(0.72 0.15 150)",
|
|
243
|
+
"primaryForeground": "auto",
|
|
239
244
|
"neutral": {
|
|
240
245
|
"hue": 135,
|
|
241
246
|
"chroma": 0.018
|
package/generated/tokens.css
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
--ml-font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
70
70
|
--ml-font-display: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
71
71
|
--ml-font-mono: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
72
|
-
--ml-display-weight:
|
|
72
|
+
--ml-display-weight: 700;
|
|
73
73
|
--ml-body-leading: 1.495;
|
|
74
74
|
--ml-tracking: -0.0088em;
|
|
75
75
|
--ml-icon-stroke: 1.974;
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
--ml-font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
236
236
|
--ml-font-display: var(--font-newsreader, "Newsreader"), "Iowan Old Style", Georgia, serif;
|
|
237
237
|
--ml-font-mono: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
238
|
-
--ml-display-weight:
|
|
238
|
+
--ml-display-weight: 800;
|
|
239
239
|
--ml-body-leading: 1.536;
|
|
240
240
|
--ml-tracking: -0.0133em;
|
|
241
241
|
--ml-icon-stroke: 1.687;
|
|
@@ -399,7 +399,7 @@
|
|
|
399
399
|
--ml-font-sans: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
400
400
|
--ml-font-display: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
401
401
|
--ml-font-mono: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
402
|
-
--ml-display-weight:
|
|
402
|
+
--ml-display-weight: 700;
|
|
403
403
|
--ml-body-leading: 1.47;
|
|
404
404
|
--ml-tracking: -0.0076em;
|
|
405
405
|
--ml-icon-stroke: 2.049;
|
|
@@ -563,7 +563,7 @@
|
|
|
563
563
|
--ml-font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
564
564
|
--ml-font-display: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
565
565
|
--ml-font-mono: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
566
|
-
--ml-display-weight:
|
|
566
|
+
--ml-display-weight: 800;
|
|
567
567
|
--ml-body-leading: 1.558;
|
|
568
568
|
--ml-tracking: -0.009em;
|
|
569
569
|
--ml-icon-stroke: 1.736;
|
|
@@ -727,7 +727,7 @@
|
|
|
727
727
|
--ml-font-sans: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
728
728
|
--ml-font-display: var(--font-inter, "Inter"), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
729
729
|
--ml-font-mono: var(--font-jetbrains, "JetBrains Mono"), ui-monospace, "SFMono-Regular", Consolas, monospace;
|
|
730
|
-
--ml-display-weight:
|
|
730
|
+
--ml-display-weight: 800;
|
|
731
731
|
--ml-body-leading: 1.541;
|
|
732
732
|
--ml-tracking: -0.011em;
|
|
733
733
|
--ml-icon-stroke: 1.834;
|
package/generated/tokens.json
CHANGED
|
@@ -145,6 +145,7 @@
|
|
|
145
145
|
"color": {
|
|
146
146
|
"primary": "oklch(0.21 0.006 286)",
|
|
147
147
|
"primaryDark": null,
|
|
148
|
+
"primaryForeground": "auto",
|
|
148
149
|
"neutral": {
|
|
149
150
|
"hue": 286,
|
|
150
151
|
"chroma": 0.004
|
|
@@ -1187,6 +1188,7 @@
|
|
|
1187
1188
|
"color": {
|
|
1188
1189
|
"primary": "oklch(0.55 0.16 58)",
|
|
1189
1190
|
"primaryDark": "oklch(0.75 0.16 68)",
|
|
1191
|
+
"primaryForeground": "auto",
|
|
1190
1192
|
"neutral": {
|
|
1191
1193
|
"hue": 80,
|
|
1192
1194
|
"chroma": 0.016
|
|
@@ -2232,6 +2234,7 @@
|
|
|
2232
2234
|
"color": {
|
|
2233
2235
|
"primary": "oklch(0.22 0.01 240)",
|
|
2234
2236
|
"primaryDark": "oklch(0.78 0.15 210)",
|
|
2237
|
+
"primaryForeground": "auto",
|
|
2235
2238
|
"neutral": {
|
|
2236
2239
|
"hue": 240,
|
|
2237
2240
|
"chroma": 0.004
|
|
@@ -3277,6 +3280,7 @@
|
|
|
3277
3280
|
"color": {
|
|
3278
3281
|
"primary": "oklch(0.55 0.24 285)",
|
|
3279
3282
|
"primaryDark": "oklch(0.75 0.2 285)",
|
|
3283
|
+
"primaryForeground": "auto",
|
|
3280
3284
|
"neutral": {
|
|
3281
3285
|
"hue": 260,
|
|
3282
3286
|
"chroma": 0.018
|
|
@@ -4322,6 +4326,7 @@
|
|
|
4322
4326
|
"color": {
|
|
4323
4327
|
"primary": "oklch(0.5 0.14 150)",
|
|
4324
4328
|
"primaryDark": "oklch(0.72 0.15 150)",
|
|
4329
|
+
"primaryForeground": "auto",
|
|
4325
4330
|
"neutral": {
|
|
4326
4331
|
"hue": 135,
|
|
4327
4332
|
"chroma": 0.018
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlola-ui/engine",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Framework-free CSS, spring easings, and the machine-readable contract for Mlola UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
"./motion.css": "./generated/motion.css",
|
|
22
22
|
"./accessibility.css": "./generated/accessibility.css",
|
|
23
23
|
"./tokens.json": "./generated/tokens.json",
|
|
24
|
-
"./manifest.json": "./generated/manifest.json"
|
|
24
|
+
"./manifest.json": "./generated/manifest.json",
|
|
25
|
+
"./theme": {
|
|
26
|
+
"types": "./src/theme.d.ts",
|
|
27
|
+
"default": "./src/theme.mjs"
|
|
28
|
+
}
|
|
25
29
|
},
|
|
26
30
|
"scripts": {
|
|
27
31
|
"build": "node build.mjs",
|
package/src/declarations.mjs
CHANGED
|
@@ -92,7 +92,9 @@ export function themeDeclarations(spec) {
|
|
|
92
92
|
`--ml-font-sans: ${fonts.sans}`,
|
|
93
93
|
`--ml-font-display: ${fonts.display}`,
|
|
94
94
|
`--ml-font-mono: ${fonts.mono}`,
|
|
95
|
-
|
|
95
|
+
// Written as a whole hundred: a font that ships only static weights
|
|
96
|
+
// (400, 700, 900) would render 737 as the next weight up, often a heavy 900.
|
|
97
|
+
`--ml-display-weight: ${Math.round(derived.displayWeight / 100) * 100}`,
|
|
96
98
|
`--ml-body-leading: ${round(derived.bodyLeading)}`,
|
|
97
99
|
`--ml-tracking: ${round(derived.tracking, 4)}em`,
|
|
98
100
|
`--ml-icon-stroke: ${round(derived.iconStroke)}`,
|
package/src/palette.mjs
CHANGED
|
@@ -63,22 +63,28 @@ const inkOn = (hue) => ({ L: 0.18, C: 0.02, H: hue });
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Pick whichever of white or dark ink reads better on `fill`. If neither
|
|
66
|
-
* reaches the target, move the fill until the better one does
|
|
66
|
+
* reaches the target, move the fill until the better one does: it is the
|
|
67
|
+
* closer of the two, so the fill moves least. `preferDark` settles a tie.
|
|
67
68
|
*/
|
|
68
|
-
function fillPair(fill, target,
|
|
69
|
-
let base = fill;
|
|
69
|
+
function fillPair(fill, target, preferDark) {
|
|
70
70
|
const dark = inkOn(fill.H);
|
|
71
|
-
const light = contrast(WHITE,
|
|
72
|
-
const deep = contrast(dark,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
const light = contrast(WHITE, fill);
|
|
72
|
+
const deep = contrast(dark, fill);
|
|
73
|
+
const useDark = deep > light || (deep === light && preferDark);
|
|
74
|
+
if (Math.max(light, deep) >= target) return { fill, foreground: useDark ? dark : WHITE };
|
|
75
|
+
if (useDark) return { fill: solveLightness(fill, dark, target, "lighter"), foreground: dark };
|
|
76
|
+
return { fill: solveLightness(fill, WHITE, target, "darker"), foreground: WHITE };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The label a theme asked for, and the fill moved (darker for white, lighter
|
|
81
|
+
* for dark ink) only as far as that label needs to meet the target. Light
|
|
82
|
+
* mode only: at night a fill dark enough for white would sink into the page.
|
|
83
|
+
*/
|
|
84
|
+
function fillFor(fill, foreground, target) {
|
|
85
|
+
const ink = foreground === "light" ? WHITE : inkOn(fill.H);
|
|
86
|
+
const moved = contrast(ink, fill) >= target ? fill : solveLightness(fill, ink, target, foreground === "light" ? "darker" : "lighter");
|
|
87
|
+
return { fill: moved, foreground: ink };
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
const tint = (hue, chroma, L) => ({ L, C: chroma, H: hue });
|
|
@@ -156,7 +162,9 @@ function primaryFor(spec, mode, neutrals) {
|
|
|
156
162
|
fill = solveLightness(fill, neutrals.background, TARGETS.separation, mode === "light" ? "darker" : "lighter");
|
|
157
163
|
}
|
|
158
164
|
fill = deepenForWhite(fill, mode);
|
|
159
|
-
const pair =
|
|
165
|
+
const pair = mode === "light" && spec.color.primaryForeground && spec.color.primaryForeground !== "auto"
|
|
166
|
+
? fillFor(fill, spec.color.primaryForeground, TARGETS.body)
|
|
167
|
+
: fillPair(fill, TARGETS.body, mode === "dark");
|
|
160
168
|
// Links and focus rings follow the brand. A monochrome brand inverted for
|
|
161
169
|
// the night reads as the inverted ink, not a mid gray lifted from the day's.
|
|
162
170
|
const textSeed = mode === "dark" && !spec.color.primaryDark && isAchromatic(seed) ? fill : seed;
|
package/src/spec.mjs
CHANGED
|
@@ -133,6 +133,9 @@ export const PALETTE_TOKENS = [
|
|
|
133
133
|
|
|
134
134
|
const ID_PATTERN = /^[a-z][a-z0-9-]{0,47}$/;
|
|
135
135
|
|
|
136
|
+
/** What a theme may ask for on primary fills: the engine's choice, white, or dark ink. */
|
|
137
|
+
const FOREGROUNDS = ["auto", "light", "dark"];
|
|
138
|
+
|
|
136
139
|
const DEFAULT_SPEC = {
|
|
137
140
|
specVersion: SPEC_VERSION,
|
|
138
141
|
id: "custom",
|
|
@@ -141,6 +144,7 @@ const DEFAULT_SPEC = {
|
|
|
141
144
|
color: {
|
|
142
145
|
primary: "oklch(0.21 0.006 286)",
|
|
143
146
|
primaryDark: null,
|
|
147
|
+
primaryForeground: "auto",
|
|
144
148
|
neutral: { hue: 286, chroma: 0.004 },
|
|
145
149
|
ink: null,
|
|
146
150
|
},
|
|
@@ -227,6 +231,7 @@ export function normalizeSpec(input = {}, base = DEFAULT_SPEC) {
|
|
|
227
231
|
color: {
|
|
228
232
|
primary: readColor(color.primary ?? legacySignal, base.color.primary),
|
|
229
233
|
primaryDark: readColor(color.primaryDark ?? legacySignalDark, base.color.primaryDark ?? null),
|
|
234
|
+
primaryForeground: FOREGROUNDS.includes(color.primaryForeground) ? color.primaryForeground : (base.color.primaryForeground ?? "auto"),
|
|
230
235
|
neutral: readTint(color.neutral, base.color.neutral),
|
|
231
236
|
ink: color.ink === null ? null : readTint(color.ink, base.color.ink ?? null),
|
|
232
237
|
},
|
|
@@ -255,6 +260,9 @@ export function validateSpec(input) {
|
|
|
255
260
|
if (input.color?.primaryDark != null && !parseColor(input.color.primaryDark)) {
|
|
256
261
|
problems.push("color.primaryDark must be an oklch() or hex color, or null.");
|
|
257
262
|
}
|
|
263
|
+
if (input.color?.primaryForeground !== undefined && !FOREGROUNDS.includes(input.color.primaryForeground)) {
|
|
264
|
+
problems.push(`color.primaryForeground must be one of ${FOREGROUNDS.join(", ")}.`);
|
|
265
|
+
}
|
|
258
266
|
const neutral = input.color?.neutral;
|
|
259
267
|
if (!neutral || typeof neutral.hue !== "number" || typeof neutral.chroma !== "number" || neutral.chroma < 0 || neutral.chroma > 0.06) {
|
|
260
268
|
problems.push("color.neutral needs a hue and a chroma from 0 to 0.06.");
|
|
@@ -302,6 +310,10 @@ export function renderSpecSchema() {
|
|
|
302
310
|
properties: {
|
|
303
311
|
primary: { type: "string", description: "Seed for the primary color, oklch() or hex. Lightness is adjusted to meet contrast." },
|
|
304
312
|
primaryDark: { type: ["string", "null"], description: "Optional different seed for dark mode." },
|
|
313
|
+
primaryForeground: {
|
|
314
|
+
enum: FOREGROUNDS,
|
|
315
|
+
description: "The label on primary fills in light mode. \"auto\" lets the engine choose; \"light\" or \"dark\" moves the fill as far as it must for that label to meet contrast.",
|
|
316
|
+
},
|
|
305
317
|
neutral: { ...tint, description: "Tint of the page and surfaces." },
|
|
306
318
|
ink: { anyOf: [tint, { type: "null" }], description: "Tint of text and dark-mode surfaces. Defaults to neutral." },
|
|
307
319
|
},
|
package/src/theme.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A project theme: a theme spec plus `inherit`, `scale` and `extend`. See https://ui.mlola.com/docs/theming#custom. */
|
|
2
|
+
export type ThemeInput = Record<string, unknown>;
|
|
3
|
+
|
|
4
|
+
export interface ProjectTheme extends Record<string, unknown> {
|
|
5
|
+
/** The theme's id: its `data-theme` value. */
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
/** The canonical theme whose decisions fill what the file leaves out. */
|
|
9
|
+
inherit: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const THEME_CHANNELS: readonly string[];
|
|
13
|
+
/** The spec with every default filled in. */
|
|
14
|
+
export function defineTheme(input?: ThemeInput): ProjectTheme;
|
|
15
|
+
/** The theme's CSS, on the same `data-theme` contract as every canonical theme. */
|
|
16
|
+
export function renderThemeCss(input: ThemeInput): string;
|
|
17
|
+
export function renderThemeManifest(projectThemeInput?: ThemeInput | null): Array<Record<string, unknown>>;
|