colorsbymax 0.3.3 → 0.4.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/CHANGELOG.md +110 -94
- package/README.md +482 -476
- package/dist/{ThemeSwitcher-Bcf9w2Ce.js → ThemeSwitcher-DB_-2YB8.js} +1251 -264
- package/dist/auto.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -3
- package/types/index.d.ts +337 -321
package/types/index.d.ts
CHANGED
|
@@ -1,321 +1,337 @@
|
|
|
1
|
-
// Type declarations for colorsbymax. The package is written in JavaScript; these describe its
|
|
2
|
-
// public API and are checked against real usage by types/check.tsx (npm run typecheck).
|
|
3
|
-
|
|
4
|
-
import type { ReactElement, ReactNode } from 'react'
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------- tokens and themes
|
|
7
|
-
|
|
8
|
-
/** Every themeable colour. Each is exposed to CSS as `--color-<key>`. */
|
|
9
|
-
export type TokenKey =
|
|
10
|
-
| 'primary' | 'primary-dark' | 'primary-alt' | 'on-primary'
|
|
11
|
-
| 'background' | 'surface' | 'secondary' | 'on-secondary' | 'accent' | 'on-accent' | 'border' | 'shadow'
|
|
12
|
-
| 'ink' | 'ink-secondary' | 'ink-muted'
|
|
13
|
-
| 'success' | 'warning' | 'danger' | 'info'
|
|
14
|
-
| 'data-1' | 'data-2' | 'data-3' | 'data-4' | 'data-5' | 'data-6' | 'data-7' | 'data-8'
|
|
15
|
-
| 'app-background' | 'app-input' | 'app-border' | 'app-primary' | 'app-shadow-dark' | 'app-shadow-light' | 'app-ink' | 'app-ink-muted'
|
|
16
|
-
|
|
17
|
-
/** Token key → `"#rrggbb"`. */
|
|
18
|
-
export type ThemeTokens = Record<TokenKey, string>
|
|
19
|
-
|
|
20
|
-
export interface Theme {
|
|
21
|
-
id: string
|
|
22
|
-
name: string
|
|
23
|
-
tokens: ThemeTokens
|
|
24
|
-
/** A palette the visitor created or imported. */
|
|
25
|
-
custom?: boolean
|
|
26
|
-
/** From the generated library. */
|
|
27
|
-
library?: boolean
|
|
28
|
-
/** One of the host site's own themes (including scan suggestions). */
|
|
29
|
-
site?: boolean
|
|
30
|
-
/** Suggested by a site scan. */
|
|
31
|
-
scanned?: boolean
|
|
32
|
-
/** A generated dark twin of a light theme. */
|
|
33
|
-
derived?: boolean
|
|
34
|
-
/** Library categories, for library themes. */
|
|
35
|
-
tags?: string[]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface TokenGroup {
|
|
39
|
-
group: string
|
|
40
|
-
tokens: { key: TokenKey; label: string; usage: string }[]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const TOKEN_GROUPS: TokenGroup[]
|
|
44
|
-
export const TOKEN_KEYS: TokenKey[]
|
|
45
|
-
/** The neutral default theme's tokens (Slate). */
|
|
46
|
-
export const BASE_TOKENS: ThemeTokens
|
|
47
|
-
/** The hand-tuned themes shown as "Max's picks". */
|
|
48
|
-
export const PRESETS: Theme[]
|
|
49
|
-
|
|
50
|
-
/** Fills missing tokens from `base` (the neutral default unless given), deriving the secondary-area ones. */
|
|
51
|
-
export function completeTokens(partial: Partial<ThemeTokens>, base?: ThemeTokens): ThemeTokens
|
|
52
|
-
/** Derives the secondary-area (`app-*`) tokens from a theme's main palette. */
|
|
53
|
-
export function deriveAppTokens(tokens: ThemeTokens): Pick<ThemeTokens, Extract<TokenKey, `app-${string}`>>
|
|
54
|
-
|
|
55
|
-
// ---------------------------------------------------------------- React API
|
|
56
|
-
|
|
57
|
-
export interface ColorsByMaxConfig {
|
|
58
|
-
/** Name of the first theme group, e.g. "Tsungi". Detected from the page if left out. */
|
|
59
|
-
siteName?: string
|
|
60
|
-
/** localStorage key; must match the pre-paint script. Defaults to "colorsbymax". */
|
|
61
|
-
storageKey?: string
|
|
62
|
-
/** The site's own colours. Missing tokens are filled in. */
|
|
63
|
-
defaultTheme?: { name: string; tokens: Partial<ThemeTokens> }
|
|
64
|
-
/** Extra themes made for the site. */
|
|
65
|
-
themes?: { id: string; name: string; tokens: Partial<ThemeTokens> }[]
|
|
66
|
-
/** Where each token is used on the site, shown in the colour editors. */
|
|
67
|
-
usage?: Partial<Record<TokenKey, string>>
|
|
68
|
-
/** Colour the page's scrollbars from the theme. Default true. */
|
|
69
|
-
scrollbars?: boolean
|
|
70
|
-
/** Enables PDF uploads: pass `loadPdf` from 'colorsbymax/pdf' (needs pdfjs-dist installed). */
|
|
71
|
-
pdf?: () => Promise<unknown>
|
|
72
|
-
/**
|
|
73
|
-
* Re-colour a site that doesn't paint with the --color-* variables, by swapping the colours
|
|
74
|
-
* actually on the page. 'auto' (the default) does it only when the site doesn't define
|
|
75
|
-
* --color-primary itself; false never does.
|
|
76
|
-
*/
|
|
77
|
-
recolour?: boolean | 'auto'
|
|
78
|
-
/**
|
|
79
|
-
* Where the colour button starts. Default 'bottom-right', like chat and help widgets;
|
|
80
|
-
* 'top-right' sits just under a floating nav bar. Visitors can still drag it anywhere.
|
|
81
|
-
*/
|
|
82
|
-
position?: 'bottom-right' | 'bottom-left' | 'top-left' | 'top-right'
|
|
83
|
-
/**
|
|
84
|
-
* Hide the colour button and panel; the theme still applies. `hidden: import.meta.env.PROD`
|
|
85
|
-
* keeps it out of production once the colours are chosen, while it still shows in development.
|
|
86
|
-
*/
|
|
87
|
-
hidden?: boolean
|
|
88
|
-
/**
|
|
89
|
-
* Bring the colour button in with a short pop and a burst of the theme's colours, a moment after
|
|
90
|
-
* the page loads (default true). With reduced motion it fades in instead.
|
|
91
|
-
*/
|
|
92
|
-
intro?: boolean
|
|
93
|
-
/**
|
|
94
|
-
* Whether themes colour the site's logo for a first-time visitor (default false: the logo keeps
|
|
95
|
-
* its own colours). Visitors can still change it in the panel's settings.
|
|
96
|
-
*/
|
|
97
|
-
colourLogo?: boolean
|
|
98
|
-
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
export
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
export
|
|
269
|
-
|
|
270
|
-
export
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
export function
|
|
276
|
-
/**
|
|
277
|
-
export function
|
|
278
|
-
/**
|
|
279
|
-
export function
|
|
280
|
-
|
|
281
|
-
// ----------------------------------------------------------------
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
export
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
/** The
|
|
312
|
-
export function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
export function
|
|
1
|
+
// Type declarations for colorsbymax. The package is written in JavaScript; these describe its
|
|
2
|
+
// public API and are checked against real usage by types/check.tsx (npm run typecheck).
|
|
3
|
+
|
|
4
|
+
import type { ReactElement, ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------- tokens and themes
|
|
7
|
+
|
|
8
|
+
/** Every themeable colour. Each is exposed to CSS as `--color-<key>`. */
|
|
9
|
+
export type TokenKey =
|
|
10
|
+
| 'primary' | 'primary-dark' | 'primary-alt' | 'on-primary'
|
|
11
|
+
| 'background' | 'surface' | 'secondary' | 'on-secondary' | 'accent' | 'on-accent' | 'border' | 'shadow'
|
|
12
|
+
| 'ink' | 'ink-secondary' | 'ink-muted'
|
|
13
|
+
| 'success' | 'warning' | 'danger' | 'info'
|
|
14
|
+
| 'data-1' | 'data-2' | 'data-3' | 'data-4' | 'data-5' | 'data-6' | 'data-7' | 'data-8'
|
|
15
|
+
| 'app-background' | 'app-input' | 'app-border' | 'app-primary' | 'app-shadow-dark' | 'app-shadow-light' | 'app-ink' | 'app-ink-muted'
|
|
16
|
+
|
|
17
|
+
/** Token key → `"#rrggbb"`. */
|
|
18
|
+
export type ThemeTokens = Record<TokenKey, string>
|
|
19
|
+
|
|
20
|
+
export interface Theme {
|
|
21
|
+
id: string
|
|
22
|
+
name: string
|
|
23
|
+
tokens: ThemeTokens
|
|
24
|
+
/** A palette the visitor created or imported. */
|
|
25
|
+
custom?: boolean
|
|
26
|
+
/** From the generated library. */
|
|
27
|
+
library?: boolean
|
|
28
|
+
/** One of the host site's own themes (including scan suggestions). */
|
|
29
|
+
site?: boolean
|
|
30
|
+
/** Suggested by a site scan. */
|
|
31
|
+
scanned?: boolean
|
|
32
|
+
/** A generated dark twin of a light theme. */
|
|
33
|
+
derived?: boolean
|
|
34
|
+
/** Library categories, for library themes. */
|
|
35
|
+
tags?: string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TokenGroup {
|
|
39
|
+
group: string
|
|
40
|
+
tokens: { key: TokenKey; label: string; usage: string }[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const TOKEN_GROUPS: TokenGroup[]
|
|
44
|
+
export const TOKEN_KEYS: TokenKey[]
|
|
45
|
+
/** The neutral default theme's tokens (Slate). */
|
|
46
|
+
export const BASE_TOKENS: ThemeTokens
|
|
47
|
+
/** The hand-tuned themes shown as "Max's picks". */
|
|
48
|
+
export const PRESETS: Theme[]
|
|
49
|
+
|
|
50
|
+
/** Fills missing tokens from `base` (the neutral default unless given), deriving the secondary-area ones. */
|
|
51
|
+
export function completeTokens(partial: Partial<ThemeTokens>, base?: ThemeTokens): ThemeTokens
|
|
52
|
+
/** Derives the secondary-area (`app-*`) tokens from a theme's main palette. */
|
|
53
|
+
export function deriveAppTokens(tokens: ThemeTokens): Pick<ThemeTokens, Extract<TokenKey, `app-${string}`>>
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------- React API
|
|
56
|
+
|
|
57
|
+
export interface ColorsByMaxConfig {
|
|
58
|
+
/** Name of the first theme group, e.g. "Tsungi". Detected from the page if left out. */
|
|
59
|
+
siteName?: string
|
|
60
|
+
/** localStorage key; must match the pre-paint script. Defaults to "colorsbymax". */
|
|
61
|
+
storageKey?: string
|
|
62
|
+
/** The site's own colours. Missing tokens are filled in. */
|
|
63
|
+
defaultTheme?: { name: string; tokens: Partial<ThemeTokens> }
|
|
64
|
+
/** Extra themes made for the site. */
|
|
65
|
+
themes?: { id: string; name: string; tokens: Partial<ThemeTokens> }[]
|
|
66
|
+
/** Where each token is used on the site, shown in the colour editors. */
|
|
67
|
+
usage?: Partial<Record<TokenKey, string>>
|
|
68
|
+
/** Colour the page's scrollbars from the theme. Default true. */
|
|
69
|
+
scrollbars?: boolean
|
|
70
|
+
/** Enables PDF uploads: pass `loadPdf` from 'colorsbymax/pdf' (needs pdfjs-dist installed). */
|
|
71
|
+
pdf?: () => Promise<unknown>
|
|
72
|
+
/**
|
|
73
|
+
* Re-colour a site that doesn't paint with the --color-* variables, by swapping the colours
|
|
74
|
+
* actually on the page. 'auto' (the default) does it only when the site doesn't define
|
|
75
|
+
* --color-primary itself; false never does.
|
|
76
|
+
*/
|
|
77
|
+
recolour?: boolean | 'auto'
|
|
78
|
+
/**
|
|
79
|
+
* Where the colour button starts. Default 'bottom-right', like chat and help widgets;
|
|
80
|
+
* 'top-right' sits just under a floating nav bar. Visitors can still drag it anywhere.
|
|
81
|
+
*/
|
|
82
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-left' | 'top-right'
|
|
83
|
+
/**
|
|
84
|
+
* Hide the colour button and panel; the theme still applies. `hidden: import.meta.env.PROD`
|
|
85
|
+
* keeps it out of production once the colours are chosen, while it still shows in development.
|
|
86
|
+
*/
|
|
87
|
+
hidden?: boolean
|
|
88
|
+
/**
|
|
89
|
+
* Bring the colour button in with a short pop and a burst of the theme's colours, a moment after
|
|
90
|
+
* the page loads (default true). With reduced motion it fades in instead.
|
|
91
|
+
*/
|
|
92
|
+
intro?: boolean
|
|
93
|
+
/**
|
|
94
|
+
* Whether themes colour the site's logo for a first-time visitor (default false: the logo keeps
|
|
95
|
+
* its own colours). Visitors can still change it in the panel's settings.
|
|
96
|
+
*/
|
|
97
|
+
colourLogo?: boolean
|
|
98
|
+
/**
|
|
99
|
+
* How boldly a re-coloured site (one that doesn't paint with the --color-* variables) takes a
|
|
100
|
+
* theme, for a first-time visitor. 'colourful' (default) paints the page's parts by role, as a
|
|
101
|
+
* designer would: header, hero, alternating sections, cards, buttons, links, headings and footer.
|
|
102
|
+
* 'subtle' only swaps the colours the site already has. Visitors can switch in the panel.
|
|
103
|
+
*/
|
|
104
|
+
colourStyle?: 'colourful' | 'subtle'
|
|
105
|
+
/**
|
|
106
|
+
* Parts of the panel to switch off for everyone, e.g. `{ scan: false, audit: false }`. All are on
|
|
107
|
+
* by default. Unlike the rest of the config it's read live, so a site can change it after loading.
|
|
108
|
+
*/
|
|
109
|
+
features?: Partial<Record<'picks' | 'library' | 'search' | 'surprise' | 'scan' | 'custom' | 'overrides' | 'importExport' | 'audit' | 'addToSite' | 'colourStyle' | 'colourCount', boolean>>
|
|
110
|
+
/**
|
|
111
|
+
* The mode a first-time visitor starts in (default 'light'; 'system' follows their device). Any
|
|
112
|
+
* site can start dark: every theme has a dark twin. Visitors can still change it.
|
|
113
|
+
*/
|
|
114
|
+
defaultMode?: 'light' | 'dark' | 'system'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ThemeProviderProps {
|
|
118
|
+
config?: ColorsByMaxConfig
|
|
119
|
+
children?: ReactNode
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Applies the saved or chosen theme to the page and provides it to the switcher and useTheme(). */
|
|
123
|
+
export function ThemeProvider(props: ThemeProviderProps): ReactElement
|
|
124
|
+
|
|
125
|
+
/** The floating colour button and panel. Render it once, inside ThemeProvider. */
|
|
126
|
+
export function ThemeSwitcher(): ReactElement | null
|
|
127
|
+
|
|
128
|
+
export interface ScanResult {
|
|
129
|
+
at: number
|
|
130
|
+
palette: string[]
|
|
131
|
+
themes: Theme[]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ThemeState {
|
|
135
|
+
activeId: string
|
|
136
|
+
overrides: Partial<ThemeTokens>
|
|
137
|
+
customs: Theme[]
|
|
138
|
+
snapshot: Pick<Theme, 'id' | 'name' | 'tokens'> | null
|
|
139
|
+
scanned: ScanResult | null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface ThemeApi {
|
|
143
|
+
state: ThemeState
|
|
144
|
+
storageKey: string
|
|
145
|
+
siteName: string
|
|
146
|
+
usage: Partial<Record<TokenKey, string>>
|
|
147
|
+
loadPdf: (() => Promise<unknown>) | null
|
|
148
|
+
position: 'bottom-right' | 'bottom-left' | 'top-left' | 'top-right'
|
|
149
|
+
/** True when the config hides the switcher; the theme still applies. */
|
|
150
|
+
hidden: boolean
|
|
151
|
+
/** Whether the colour button makes an entrance when it first appears. */
|
|
152
|
+
intro: boolean
|
|
153
|
+
/** The mode in effect: every theme shows its light or dark version. */
|
|
154
|
+
mode: 'light' | 'dark'
|
|
155
|
+
/** The visitor's choice; 'system' follows the device. */
|
|
156
|
+
modeSetting: 'light' | 'dark' | 'system'
|
|
157
|
+
/** Sets the mode, for the whole site, and remembers it. */
|
|
158
|
+
setMode(mode: 'light' | 'dark' | 'system'): void
|
|
159
|
+
/** True when colorsbymax is swapping the page's own colours (the site isn't using the variables). */
|
|
160
|
+
recolouring: boolean
|
|
161
|
+
/** Turns re-colouring of the site's logo on or off. */
|
|
162
|
+
setLogoColouring(on: boolean): void
|
|
163
|
+
/** How boldly a re-coloured site takes the theme (the panel's Subtle / Colourful switch). */
|
|
164
|
+
setColourStyle(style: 'colourful' | 'subtle'): void
|
|
165
|
+
/** The site's own themes, then any scan suggestions. */
|
|
166
|
+
siteThemes: Theme[]
|
|
167
|
+
defaultTheme: Theme
|
|
168
|
+
presets: Theme[]
|
|
169
|
+
customs: Theme[]
|
|
170
|
+
/** Site themes, presets and custom palettes (not the library). */
|
|
171
|
+
themes: Theme[]
|
|
172
|
+
/** The applied theme. */
|
|
173
|
+
active: Theme
|
|
174
|
+
/** The applied colours: the active theme plus any overrides. */
|
|
175
|
+
tokens: ThemeTokens
|
|
176
|
+
/** Contrast problems in the applied colours. */
|
|
177
|
+
issues: ContrastIssue[]
|
|
178
|
+
|
|
179
|
+
/** Selects a theme by id; pass the theme itself for library themes and dark twins. */
|
|
180
|
+
selectTheme(id: string, theme?: Theme): void
|
|
181
|
+
/** Creates a custom palette from the current colours and applies it. Returns its id. */
|
|
182
|
+
createCustom(name: string): string
|
|
183
|
+
updateCustomToken(id: string, key: TokenKey, value: string): void
|
|
184
|
+
renameCustom(id: string, name: string): void
|
|
185
|
+
deleteCustom(id: string): void
|
|
186
|
+
/** Adds tokens as a new custom palette and applies it. Returns its id. */
|
|
187
|
+
addPalette(name: string, tokens: Partial<ThemeTokens>): string
|
|
188
|
+
|
|
189
|
+
setOverride(key: TokenKey, value: string): void
|
|
190
|
+
clearOverride(key: TokenKey): void
|
|
191
|
+
clearOverrides(): void
|
|
192
|
+
resetToDefault(): void
|
|
193
|
+
|
|
194
|
+
scanned: ScanResult | null
|
|
195
|
+
/** Reads the page's colours and adds themes built around them. Resolves to counts for the UI. */
|
|
196
|
+
runScan(): Promise<{ colours: number; themes: number }>
|
|
197
|
+
clearScan(): void
|
|
198
|
+
|
|
199
|
+
/** Fixes one issue by adjusting lightness. Returns false if no single change could. */
|
|
200
|
+
fixIssue(issue: ContrastIssue): boolean
|
|
201
|
+
fixAllIssues(): void
|
|
202
|
+
|
|
203
|
+
/** The applied theme as `{ name, tokens }` JSON. */
|
|
204
|
+
exportTheme(): string
|
|
205
|
+
/** Imports `{ name, tokens }` JSON as a custom palette. Returns an error message, or null. */
|
|
206
|
+
importTheme(json: string): string | null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The theme state and actions. Must be called inside ThemeProvider. */
|
|
210
|
+
export function useTheme(): ThemeApi
|
|
211
|
+
|
|
212
|
+
/** Writes each token to `--color-<key>` on `<html>`. */
|
|
213
|
+
export function applyTokens(tokens: ThemeTokens): void
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------- pre-paint and settings
|
|
216
|
+
|
|
217
|
+
export const DEFAULT_STORAGE_KEY: 'colorsbymax'
|
|
218
|
+
/** Source of an inline `<script>` for `<head>` that applies the saved theme before first paint. */
|
|
219
|
+
export function prePaintScript(storageKey?: string): string
|
|
220
|
+
|
|
221
|
+
export interface PanelSettings {
|
|
222
|
+
mode: 'light' | 'dark' | 'system'
|
|
223
|
+
showPicks: boolean
|
|
224
|
+
showLibrary: boolean
|
|
225
|
+
showCustom: boolean
|
|
226
|
+
showOverrides: boolean
|
|
227
|
+
showImportExport: boolean
|
|
228
|
+
draggable: boolean
|
|
229
|
+
animateDot: boolean
|
|
230
|
+
panelWidth: number | null
|
|
231
|
+
panelHeight: number | 'full' | null
|
|
232
|
+
libraryCollapsed: boolean
|
|
233
|
+
/** Whether themes re-colour the site's logo too. Off (the default) keeps its own colours. */
|
|
234
|
+
colourLogo: boolean
|
|
235
|
+
/** On a re-coloured site: 'colourful' paints its parts by role, 'subtle' only swaps its colours. */
|
|
236
|
+
colourStyle: 'colourful' | 'subtle'
|
|
237
|
+
/** Hidden on this device (from the finish screen); Alt+Shift+C or ?colorsbymax brings it back. */
|
|
238
|
+
hideButton: boolean
|
|
239
|
+
}
|
|
240
|
+
/** The visitor settings the panel starts with. */
|
|
241
|
+
export const DEFAULT_SETTINGS: PanelSettings
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------- contrast
|
|
244
|
+
|
|
245
|
+
export type PairingKind = 'text' | 'large-text' | 'non-text'
|
|
246
|
+
|
|
247
|
+
export interface Pairing {
|
|
248
|
+
id: string
|
|
249
|
+
label: string
|
|
250
|
+
kind: PairingKind
|
|
251
|
+
fg(tokens: ThemeTokens): string
|
|
252
|
+
bg(tokens: ThemeTokens): string
|
|
253
|
+
/** Tokens auto-fix may adjust, in preference order. */
|
|
254
|
+
fixable: TokenKey[]
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface ContrastIssue {
|
|
258
|
+
pairing: Pairing
|
|
259
|
+
ratio: number
|
|
260
|
+
required: number
|
|
261
|
+
/** e.g. "Primary text on background: 2.7:1 — text will be hard to read (needs 4.5:1)" */
|
|
262
|
+
message: string
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Every foreground/background pairing colorsbymax checks. */
|
|
266
|
+
export const PAIRINGS: Pairing[]
|
|
267
|
+
export const MIN_CONTRAST_TEXT: number
|
|
268
|
+
export const MIN_CONTRAST_LARGE_TEXT: number
|
|
269
|
+
export const MIN_CONTRAST_NON_TEXT: number
|
|
270
|
+
export const MIN_RAMP_STEP_DELTA_E: number
|
|
271
|
+
|
|
272
|
+
/** Every failing pairing in a theme. */
|
|
273
|
+
export function checkTheme(tokens: ThemeTokens): ContrastIssue[]
|
|
274
|
+
/** The smallest lightness change to one token that makes a pairing pass, or null. */
|
|
275
|
+
export function suggestFix(pairing: Pairing, tokens: ThemeTokens): { key: TokenKey; value: string } | null
|
|
276
|
+
/** Fixes failing pairings until the theme passes or nothing more helps. Returns the changed tokens. */
|
|
277
|
+
export function fixAll(tokens: ThemeTokens): Partial<ThemeTokens>
|
|
278
|
+
/** Problems with an ordered colour ramp drawn on `surface`, as sentences. */
|
|
279
|
+
export function checkRamp(colors: string[], surface: string): string[]
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------- colour helpers
|
|
282
|
+
|
|
283
|
+
/** WCAG 2 contrast ratio between two hex colours, 1–21. */
|
|
284
|
+
export function contrastRatio(a: string, b: string): number
|
|
285
|
+
/** "#abc", "abc" or "#AABBCC" → "#aabbcc"; null for anything else. */
|
|
286
|
+
export function normalizeHex(input: unknown): string | null
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------- light and dark
|
|
289
|
+
|
|
290
|
+
/** True when a theme's page background is dark. */
|
|
291
|
+
export function isDarkTheme(tokens: ThemeTokens): boolean
|
|
292
|
+
/** Dark versions of a light theme's tokens, adjusted to pass contrast. */
|
|
293
|
+
export function darkTokens(tokens: ThemeTokens): ThemeTokens
|
|
294
|
+
/** The dark twin of a light theme (themes that are already dark come back unchanged). */
|
|
295
|
+
export function toDark<T extends Theme>(theme: T): T
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------- site scan
|
|
298
|
+
|
|
299
|
+
export interface ColourTally {
|
|
300
|
+
hex: string
|
|
301
|
+
/** Area painted as a background. */
|
|
302
|
+
bg: number
|
|
303
|
+
/** Weighted amount of text in this colour. */
|
|
304
|
+
text: number
|
|
305
|
+
/** Border length in this colour. */
|
|
306
|
+
border: number
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export type Roles = Partial<Record<TokenKey, string>>
|
|
310
|
+
|
|
311
|
+
/** The page's painted colours, ignoring any applied theme. `exclude` is a selector to skip. */
|
|
312
|
+
export function collectColors(options?: { exclude?: string }): ColourTally[]
|
|
313
|
+
/** Works out token roles from collected colours. */
|
|
314
|
+
export function inferRoles(colors: ColourTally[]): { roles: Roles; palette: string[] }
|
|
315
|
+
/** A complete theme from detected roles, deriving whatever wasn't found. */
|
|
316
|
+
export function themeFromRoles(
|
|
317
|
+
roles: Roles,
|
|
318
|
+
variant?: { softness?: number; boldness?: number; complementary?: boolean },
|
|
319
|
+
): ThemeTokens
|
|
320
|
+
/** Themes suggested for a scanned site, named after it, plus the closest library themes. */
|
|
321
|
+
export function suggestThemes(scan: { roles: Roles }, siteName: string, library?: Theme[]): Theme[]
|
|
322
|
+
/** Best guess at the site's name: og:site_name, then the title, then the host. */
|
|
323
|
+
export function detectSiteName(doc?: Document): string
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------- palettes from files
|
|
326
|
+
|
|
327
|
+
/** The main colours in an image (or a PDF, with `loadPdf`), most important first. */
|
|
328
|
+
export function coloursFromFile(
|
|
329
|
+
file: File,
|
|
330
|
+
options?: { loadPdf?: (() => Promise<unknown>) | null },
|
|
331
|
+
): Promise<{ colours: string[]; from: 'image' | 'pdf-text' | 'pdf' }>
|
|
332
|
+
/** The dominant colours in sets of RGBA pixels, edge blends removed. */
|
|
333
|
+
export function dominantColours(pixelSets: Uint8ClampedArray[]): string[]
|
|
334
|
+
/** Assigns a palette's colours to theme roles. */
|
|
335
|
+
export function rolesFromPalette(colours: string[]): Roles
|
|
336
|
+
/** A complete, contrast-checked theme built around a palette. */
|
|
337
|
+
export function themeFromPalette(colours: string[]): ThemeTokens
|