@evolve.ui/theme-system 1.1.0 → 1.2.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/{allThemes-CFS_9-tk.cjs → allThemes-3d8nhymo.cjs} +12 -2
- package/allThemes-3d8nhymo.cjs.map +1 -0
- package/{allThemes-OV-ayn4C.mjs → allThemes-Abn6KDm7.mjs} +7 -3
- package/allThemes-Abn6KDm7.mjs.map +1 -0
- package/allThemes.cjs +1 -1
- package/allThemes.d.cts +1 -0
- package/allThemes.d.cts.map +1 -1
- package/allThemes.d.mts +1 -0
- package/allThemes.d.mts.map +1 -1
- package/allThemes.mjs +1 -1
- package/brands-CE0wIkKX.d.cts +58 -0
- package/brands-CE0wIkKX.d.cts.map +1 -0
- package/brands-CE0wIkKX.d.mts +58 -0
- package/brands-CE0wIkKX.d.mts.map +1 -0
- package/brands.cjs +16 -13
- package/brands.cjs.map +1 -1
- package/brands.d.cts +2 -2
- package/brands.d.mts +2 -2
- package/brands.mjs +16 -13
- package/brands.mjs.map +1 -1
- package/css/EverydayHarbor.css +377 -0
- package/css/EverydayHarbor.effects.css +650 -0
- package/{default-b-tgfyxR.cjs → default-B6VWF0Js.cjs} +237 -36
- package/default-B6VWF0Js.cjs.map +1 -0
- package/{default-B2n4PnsJ.mjs → default-BogJDhKE.mjs} +238 -37
- package/default-BogJDhKE.mjs.map +1 -0
- package/effects.cjs +26 -0
- package/effects.cjs.map +1 -0
- package/effects.d.cts +22 -0
- package/effects.d.cts.map +1 -0
- package/effects.d.mts +22 -0
- package/effects.d.mts.map +1 -0
- package/effects.mjs +22 -0
- package/effects.mjs.map +1 -0
- package/evolve-ui.entries.json +5 -0
- package/index.cjs +12 -5
- package/index.cjs.map +1 -1
- package/index.d.cts +108 -4
- package/index.d.cts.map +1 -1
- package/index.d.mts +108 -4
- package/index.d.mts.map +1 -1
- package/index.mjs +9 -6
- package/index.mjs.map +1 -1
- package/package.json +5 -1
- package/{plugin-DPtvp4lp.cjs → plugin-B3PF4RMI.cjs} +299 -110
- package/plugin-B3PF4RMI.cjs.map +1 -0
- package/{plugin-D4eCyUJe.mjs → plugin-D6aLCeNB.mjs} +258 -111
- package/plugin-D6aLCeNB.mjs.map +1 -0
- package/src/effects/__fixtures__/fixtureLayer.ts +88 -0
- package/src/effects/brands/EverydayHarbor.ts +392 -0
- package/src/effects/classes.ts +19 -0
- package/src/effects/emit.ts +314 -0
- package/src/effects/hooks.ts +493 -0
- package/src/effects/index.ts +2 -0
- package/src/effects/layers.ts +18 -0
- package/src/effects/rules.ts +90 -0
- package/src/effects/rules.typecheck.ts +50 -0
- package/src/effects/tokens.ts +148 -0
- package/src/effects/validate.ts +683 -0
- package/src/effects/vocabulary.ts +32 -0
- package/src/index.ts +3 -0
- package/src/tailwind/animation.ts +30 -0
- package/src/tailwind/borderWidth.ts +14 -0
- package/src/tailwind/plugin.ts +230 -239
- package/src/tailwind/reducedMotion.ts +164 -0
- package/src/tailwind/text.ts +4 -2
- package/src/themeValues.snapshot.ts +6735 -0
- package/src/themes/EverydayHarbor.ts +147 -0
- package/src/themes/allThemes.ts +2 -0
- package/src/themes/brandSources.ts +37 -0
- package/src/themes/brands.ts +41 -15
- package/src/themes/contrastFloors.ts +26 -0
- package/src/themes/inkRamps.ts +43 -0
- package/src/themes/runtime/buildTheme.ts +6 -19
- package/src/themes/runtime/everydayHarbor.ts +3 -0
- package/src/themes/semanticColorTokenSteps.ts +5 -2
- package/src/themes/themeValues.ts +374 -0
- package/src/themes/wcag.ts +48 -0
- package/tailwind/plugin.cjs +5 -1
- package/tailwind/plugin.d.cts +458 -1
- package/tailwind/plugin.d.cts.map +1 -1
- package/tailwind/plugin.d.mts +458 -1
- package/tailwind/plugin.d.mts.map +1 -1
- package/tailwind/plugin.mjs +2 -2
- package/themes/runtime/default.cjs +1 -1
- package/themes/runtime/default.mjs +1 -1
- package/themes/themeType.d.cts +1 -1
- package/themes/themeType.d.mts +1 -1
- package/allThemes-CFS_9-tk.cjs.map +0 -1
- package/allThemes-OV-ayn4C.mjs.map +0 -1
- package/brands-n30rolHQ.d.cts +0 -33
- package/brands-n30rolHQ.d.cts.map +0 -1
- package/brands-n30rolHQ.d.mts +0 -33
- package/brands-n30rolHQ.d.mts.map +0 -1
- package/default-B2n4PnsJ.mjs.map +0 -1
- package/default-b-tgfyxR.cjs.map +0 -1
- package/plugin-D4eCyUJe.mjs.map +0 -1
- package/plugin-DPtvp4lp.cjs.map +0 -1
- package/src/themes/generate.ts +0 -46
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import type { ColorAliasesObject } from './generateCSSVars';
|
|
2
|
+
|
|
3
|
+
import { borderRadius } from '../tailwind/borderRadius';
|
|
4
|
+
import { fontFamily } from '../tailwind/fontFamily';
|
|
5
|
+
import { tailwindTheme, themePlugin } from '../tailwind/plugin';
|
|
6
|
+
import { screens } from '../tailwind/screens';
|
|
7
|
+
import { spacing } from '../tailwind/spacing';
|
|
8
|
+
import { textComponents } from '../tailwind/text';
|
|
9
|
+
import { generateNormalColorSteps, generateSurfaceColorSteps, type Steps } from '../utils/color';
|
|
10
|
+
import { brandSources } from './brandSources';
|
|
11
|
+
import {
|
|
12
|
+
FILLED_BUTTON_FAMILIES,
|
|
13
|
+
FILLED_BUTTON_STEPS,
|
|
14
|
+
TEXT_AA,
|
|
15
|
+
TEXT_AAA_BODY,
|
|
16
|
+
TEXT_CONTRAST_TOKENS,
|
|
17
|
+
UI_AA,
|
|
18
|
+
UI_CONTRAST_TOKENS,
|
|
19
|
+
} from './contrastFloors';
|
|
20
|
+
|
|
21
|
+
export const LOCKED_BRANDS = ['BasketballTraining', 'Default', 'ProfessionalBrand'] as const;
|
|
22
|
+
|
|
23
|
+
export const COLOR_GENERATOR_SEEDS = ['#112233', '#2563EB', '#D92525'] as const;
|
|
24
|
+
|
|
25
|
+
export type BrandSource = {
|
|
26
|
+
colorAliases: ColorAliasesObject;
|
|
27
|
+
nonColorVariables: Record<string, string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type TokenValue = {
|
|
31
|
+
binding: string;
|
|
32
|
+
resolved: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type BrandValues = {
|
|
36
|
+
palette: Record<string, string>;
|
|
37
|
+
tokens: Record<string, TokenValue>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type OrderedEntries<T> = readonly (readonly [string, T])[];
|
|
41
|
+
|
|
42
|
+
export type OrderedValue = string | number | boolean | null | readonly OrderedValue[];
|
|
43
|
+
|
|
44
|
+
export type ContrastValues = {
|
|
45
|
+
filledButtonFamilies: readonly string[];
|
|
46
|
+
filledButtonSteps: readonly string[];
|
|
47
|
+
textAa: number;
|
|
48
|
+
textAaaBody: number;
|
|
49
|
+
textTokens: readonly (readonly [string, number])[];
|
|
50
|
+
uiAa: number;
|
|
51
|
+
uiTokens: readonly string[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ThemeValueInput = {
|
|
55
|
+
borderRadius?: Record<string, string>;
|
|
56
|
+
brands?: Record<string, BrandSource>;
|
|
57
|
+
contrast?: ContrastValues;
|
|
58
|
+
fontFamily?: Record<string, string>;
|
|
59
|
+
generateNormalColorSteps?: (color: string) => string[];
|
|
60
|
+
generateSurfaceColorSteps?: (color: string, isNeutral?: boolean) => string[];
|
|
61
|
+
screens?: Record<string, Record<string, string>>;
|
|
62
|
+
spacing?: Record<string, string>;
|
|
63
|
+
tailwind?: typeof tailwindTheme;
|
|
64
|
+
textComponents?: Record<string, unknown>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const brandSource = (colorAliases: ColorAliasesObject, nonColorVariables: Record<string, string>): BrandSource => ({
|
|
68
|
+
colorAliases,
|
|
69
|
+
nonColorVariables,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const themeBrandSources = (): Record<string, BrandSource> =>
|
|
73
|
+
Object.fromEntries(
|
|
74
|
+
Object.entries(brandSources).map(([brand, source]) => [
|
|
75
|
+
brand,
|
|
76
|
+
brandSource(source.colorAliases, source.nonColorVariables as Record<string, string>),
|
|
77
|
+
])
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const COLOR_STEPS = new Set<string>(['50', '100', '200', '300', '400', '500', '600', '700', '800', '900']);
|
|
81
|
+
|
|
82
|
+
const isColorAlias = (alias: string, aliases: ColorAliasesObject): alias is keyof ColorAliasesObject =>
|
|
83
|
+
Object.prototype.hasOwnProperty.call(aliases, alias);
|
|
84
|
+
|
|
85
|
+
const isStep = (step: string): step is Steps => COLOR_STEPS.has(step);
|
|
86
|
+
|
|
87
|
+
const resolveRefs = (value: string, aliases: BrandSource['colorAliases']): string =>
|
|
88
|
+
value.replace(/var\(--color-([a-z]+)-(\d+)\)/g, (full, alias: string, step: string) => {
|
|
89
|
+
if (!isColorAlias(alias, aliases) || !isStep(step)) {
|
|
90
|
+
return full;
|
|
91
|
+
}
|
|
92
|
+
return aliases[alias][step];
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const paletteOf = (aliases: BrandSource['colorAliases']): Record<string, string> => {
|
|
96
|
+
const palette: Record<string, string> = {};
|
|
97
|
+
for (const [alias, steps] of Object.entries(aliases)) {
|
|
98
|
+
for (const [step, channels] of Object.entries(steps)) {
|
|
99
|
+
palette[`${alias}.${step}`] = channels;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return palette;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const tokensOf = (brand: BrandSource): Record<string, TokenValue> => {
|
|
106
|
+
const tokens: Record<string, TokenValue> = {};
|
|
107
|
+
for (const [name, binding] of Object.entries(brand.nonColorVariables)) {
|
|
108
|
+
tokens[name] = { binding, resolved: resolveRefs(binding, brand.colorAliases) };
|
|
109
|
+
}
|
|
110
|
+
return tokens;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const isTailwindTheme = (value: unknown): value is typeof tailwindTheme =>
|
|
114
|
+
value !== null &&
|
|
115
|
+
typeof value === 'object' &&
|
|
116
|
+
'borderWidth' in value &&
|
|
117
|
+
'colors' in value &&
|
|
118
|
+
'fontFamily' in value &&
|
|
119
|
+
'screens' in value;
|
|
120
|
+
|
|
121
|
+
const pluginTailwindTheme = (): typeof tailwindTheme => {
|
|
122
|
+
const theme = themePlugin.config?.theme;
|
|
123
|
+
if (!isTailwindTheme(theme)) {
|
|
124
|
+
throw new Error('theme plugin is not passing a theme object');
|
|
125
|
+
}
|
|
126
|
+
return theme;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const isSerializable = (value: unknown): value is string | number | boolean | null =>
|
|
130
|
+
typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null;
|
|
131
|
+
|
|
132
|
+
// Identity only. A lookalike null-parent object, and another realm's Object.prototype, are not these.
|
|
133
|
+
const hasAllowedPrototype = (value: object): boolean => {
|
|
134
|
+
const proto = Object.getPrototypeOf(value);
|
|
135
|
+
return proto === Object.prototype || proto === Array.prototype || proto === null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const assertRecordableObject = (value: object): void => {
|
|
139
|
+
if (!hasAllowedPrototype(value)) {
|
|
140
|
+
throw new Error('theme value has an unexpected prototype');
|
|
141
|
+
}
|
|
142
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
143
|
+
throw new Error('theme value has a symbol key');
|
|
144
|
+
}
|
|
145
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
146
|
+
if (Array.isArray(value) && key === 'length') {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
150
|
+
if (!descriptor) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
154
|
+
throw new Error('theme value has an accessor property');
|
|
155
|
+
}
|
|
156
|
+
if (!descriptor.enumerable) {
|
|
157
|
+
throw new Error('theme value has a non-enumerable property');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const orderValue = (value: unknown): OrderedValue => {
|
|
163
|
+
if (value !== null && typeof value === 'object') {
|
|
164
|
+
assertRecordableObject(value);
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
return ['array', value.map(orderValue)] as const;
|
|
168
|
+
}
|
|
169
|
+
if (value !== null && typeof value === 'object') {
|
|
170
|
+
return [
|
|
171
|
+
'object',
|
|
172
|
+
Object.keys(value).map((key) => {
|
|
173
|
+
const child = Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
174
|
+
return [key, orderValue(child)] as const;
|
|
175
|
+
}),
|
|
176
|
+
] as const;
|
|
177
|
+
}
|
|
178
|
+
if (isSerializable(value)) {
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
throw new Error('theme value is not serializable');
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const MATCH_UTILITY_PROBE = 'probe';
|
|
185
|
+
|
|
186
|
+
type UtilityCallback = (value: unknown, extra: { modifier: string | null }) => unknown;
|
|
187
|
+
|
|
188
|
+
const isUtilityCallback = (value: unknown): value is UtilityCallback => typeof value === 'function';
|
|
189
|
+
|
|
190
|
+
const valuesOf = (options: unknown): object | undefined => {
|
|
191
|
+
if (options === null || typeof options !== 'object') {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
assertRecordableObject(options);
|
|
195
|
+
const values = Object.getOwnPropertyDescriptor(options, 'values')?.value;
|
|
196
|
+
if (values === undefined) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
if (values === null || typeof values !== 'object') {
|
|
200
|
+
throw new Error('theme value is not serializable');
|
|
201
|
+
}
|
|
202
|
+
assertRecordableObject(values);
|
|
203
|
+
return values;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const callUtility = (callback: UtilityCallback, value: unknown): unknown => callback(value, { modifier: null });
|
|
207
|
+
|
|
208
|
+
const recordCallbackOutputs = (callback: UtilityCallback, values: object | undefined): object => {
|
|
209
|
+
const recorded: Record<string, unknown> = {
|
|
210
|
+
probe: callUtility(callback, MATCH_UTILITY_PROBE),
|
|
211
|
+
};
|
|
212
|
+
if (values) {
|
|
213
|
+
const outputs: Record<string, unknown> = {};
|
|
214
|
+
for (const key of Object.keys(values)) {
|
|
215
|
+
outputs[key] = callUtility(callback, Object.getOwnPropertyDescriptor(values, key)?.value);
|
|
216
|
+
}
|
|
217
|
+
recorded.values = outputs;
|
|
218
|
+
}
|
|
219
|
+
return recorded;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const probeUtilities = (utilities: object, options: unknown): object => {
|
|
223
|
+
assertRecordableObject(utilities);
|
|
224
|
+
const values = valuesOf(options);
|
|
225
|
+
const probed: object = Object.create(Object.getPrototypeOf(utilities));
|
|
226
|
+
for (const key of Object.keys(utilities)) {
|
|
227
|
+
const child = Object.getOwnPropertyDescriptor(utilities, key)?.value;
|
|
228
|
+
const recorded = isUtilityCallback(child) ? recordCallbackOutputs(child, values) : child;
|
|
229
|
+
Object.defineProperty(probed, key, {
|
|
230
|
+
configurable: true,
|
|
231
|
+
enumerable: true,
|
|
232
|
+
value: recorded,
|
|
233
|
+
writable: true,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return probed;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const pluginConfig = (): object => {
|
|
240
|
+
const config = themePlugin.config;
|
|
241
|
+
if (!config) {
|
|
242
|
+
throw new Error('theme plugin is not passing a config');
|
|
243
|
+
}
|
|
244
|
+
return config;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const PLUGIN_API_METHODS = new Set([
|
|
248
|
+
'addBase',
|
|
249
|
+
'addComponents',
|
|
250
|
+
'addDefaults',
|
|
251
|
+
'addUtilities',
|
|
252
|
+
'addVariant',
|
|
253
|
+
'config',
|
|
254
|
+
'corePlugins',
|
|
255
|
+
'e',
|
|
256
|
+
'matchComponents',
|
|
257
|
+
'matchUtilities',
|
|
258
|
+
'matchVariant',
|
|
259
|
+
'postcss',
|
|
260
|
+
'prefix',
|
|
261
|
+
'theme',
|
|
262
|
+
'variants',
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
const recordPluginCalls = (): readonly unknown[] => {
|
|
266
|
+
const calls: unknown[] = [];
|
|
267
|
+
const record =
|
|
268
|
+
(method: string) =>
|
|
269
|
+
(...args: readonly unknown[]) => {
|
|
270
|
+
if (method === 'matchUtilities' || method === 'matchComponents') {
|
|
271
|
+
const [utilities, options] = args;
|
|
272
|
+
const recorded =
|
|
273
|
+
utilities !== null && typeof utilities === 'object'
|
|
274
|
+
? probeUtilities(utilities, options)
|
|
275
|
+
: utilities;
|
|
276
|
+
calls.push([method, recorded, ...args.slice(1)]);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (method === 'matchVariant') {
|
|
280
|
+
const [name, callback, options] = args;
|
|
281
|
+
const recorded = isUtilityCallback(callback)
|
|
282
|
+
? recordCallbackOutputs(callback, valuesOf(options))
|
|
283
|
+
: callback;
|
|
284
|
+
calls.push([method, name, recorded, ...args.slice(2)]);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
calls.push([method, ...args]);
|
|
288
|
+
};
|
|
289
|
+
const api: Record<string, (...args: readonly unknown[]) => void> = new Proxy(
|
|
290
|
+
{},
|
|
291
|
+
{
|
|
292
|
+
get: (_target, property) => {
|
|
293
|
+
if (typeof property !== 'string' || !PLUGIN_API_METHODS.has(property)) {
|
|
294
|
+
throw new Error(`theme plugin API method is not recorded: ${String(property)}`);
|
|
295
|
+
}
|
|
296
|
+
return record(property);
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
themePlugin.handler(api as unknown as Parameters<typeof themePlugin.handler>[0]);
|
|
301
|
+
return calls;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const isObjectShape = (value: unknown): value is readonly ['object', OrderedEntries<OrderedValue>] =>
|
|
305
|
+
Array.isArray(value) &&
|
|
306
|
+
value.length === 2 &&
|
|
307
|
+
value[0] === 'object' &&
|
|
308
|
+
Array.isArray(value[1]) &&
|
|
309
|
+
value[1].every(
|
|
310
|
+
(entry): entry is readonly [string, OrderedValue] =>
|
|
311
|
+
Array.isArray(entry) && entry.length === 2 && typeof entry[0] === 'string'
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const LOCKED_BRAND_NAMES: ReadonlySet<string> = new Set(LOCKED_BRANDS);
|
|
315
|
+
|
|
316
|
+
export const themeContrastValues = (): ContrastValues => ({
|
|
317
|
+
filledButtonFamilies: FILLED_BUTTON_FAMILIES,
|
|
318
|
+
filledButtonSteps: FILLED_BUTTON_STEPS,
|
|
319
|
+
textAa: TEXT_AA,
|
|
320
|
+
textAaaBody: TEXT_AAA_BODY,
|
|
321
|
+
textTokens: TEXT_CONTRAST_TOKENS,
|
|
322
|
+
uiAa: UI_AA,
|
|
323
|
+
uiTokens: UI_CONTRAST_TOKENS,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
export const collectThemeValues = (input: ThemeValueInput = {}): OrderedValue => {
|
|
327
|
+
const brands = input.brands ?? themeBrandSources();
|
|
328
|
+
const normal = input.generateNormalColorSteps ?? generateNormalColorSteps;
|
|
329
|
+
const surface = input.generateSurfaceColorSteps ?? generateSurfaceColorSteps;
|
|
330
|
+
const recordedBrands: Record<string, BrandValues> = {};
|
|
331
|
+
for (const [name, brand] of Object.entries(brands)) {
|
|
332
|
+
recordedBrands[name] = { palette: paletteOf(brand.colorAliases), tokens: tokensOf(brand) };
|
|
333
|
+
}
|
|
334
|
+
const generatorFor = (run: (seed: string) => string[]) => {
|
|
335
|
+
const out: Record<string, string[]> = {};
|
|
336
|
+
for (const seed of COLOR_GENERATOR_SEEDS) {
|
|
337
|
+
out[seed] = run(seed);
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
};
|
|
341
|
+
const tailwind = input.tailwind ?? pluginTailwindTheme();
|
|
342
|
+
return orderValue({
|
|
343
|
+
brands: recordedBrands,
|
|
344
|
+
colorGenerator: {
|
|
345
|
+
normal: generatorFor(normal),
|
|
346
|
+
surface: generatorFor((seed) => surface(seed)),
|
|
347
|
+
surfaceNeutral: generatorFor((seed) => surface(seed, true)),
|
|
348
|
+
},
|
|
349
|
+
contrast: input.contrast ?? themeContrastValues(),
|
|
350
|
+
fontFamilies: input.fontFamily ?? (fontFamily as Record<string, string>),
|
|
351
|
+
radius: input.borderRadius ?? (borderRadius as Record<string, string>),
|
|
352
|
+
pluginCalls: recordPluginCalls(),
|
|
353
|
+
pluginConfig: pluginConfig(),
|
|
354
|
+
screens: input.screens ?? screens,
|
|
355
|
+
spacing: input.spacing ?? (spacing as Record<string, string>),
|
|
356
|
+
tailwind,
|
|
357
|
+
typeScale: input.textComponents ?? (textComponents as Record<string, unknown>),
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export const lockThemeValues = (values: OrderedValue): OrderedValue => {
|
|
362
|
+
if (!isObjectShape(values)) {
|
|
363
|
+
throw new Error('theme values are not an object');
|
|
364
|
+
}
|
|
365
|
+
return [
|
|
366
|
+
'object',
|
|
367
|
+
values[1].map(([key, child]) => {
|
|
368
|
+
if (key !== 'brands' || !isObjectShape(child)) {
|
|
369
|
+
return [key, child] as const;
|
|
370
|
+
}
|
|
371
|
+
return [key, ['object', child[1].filter(([name]) => LOCKED_BRAND_NAMES.has(name))] as const] as const;
|
|
372
|
+
}),
|
|
373
|
+
] as const;
|
|
374
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WCAG 2.1 relative luminance and contrast ratio, independent of the colour generator (no tinycolor).
|
|
3
|
+
* Shared by contrast.spec.ts and EverydayHarbor.spec.ts so every contrast gate uses ONE formula.
|
|
4
|
+
* Inputs are the generator's channel strings ("R G B", as `removeRGB` in utils/color.ts emits).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Parse a channel string such as "51 65 92". Throws on anything that is not three 0-255 numbers. */
|
|
8
|
+
export const parseChannels = (value: string): [number, number, number] => {
|
|
9
|
+
const parts = value
|
|
10
|
+
.trim()
|
|
11
|
+
.split(/[\s,]+/)
|
|
12
|
+
.map(Number);
|
|
13
|
+
if (parts.length !== 3 || parts.some((c) => !Number.isFinite(c) || c < 0 || c > 255)) {
|
|
14
|
+
throw new Error(`Not an "R G B" channel string: ${JSON.stringify(value)}`);
|
|
15
|
+
}
|
|
16
|
+
return parts as [number, number, number];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** `#RRGGBB` to the generator's channel string. Throws on a malformed literal. */
|
|
20
|
+
export const hexToChannels = (hex: string): string => {
|
|
21
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex)) {
|
|
22
|
+
throw new Error(`Not a #RRGGBB literal: ${JSON.stringify(hex)}`);
|
|
23
|
+
}
|
|
24
|
+
return [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(' ');
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Generator channel string to `#RRGGBB` (uppercase). */
|
|
28
|
+
export const channelsToHex = (channels: string): string =>
|
|
29
|
+
`#${parseChannels(channels)
|
|
30
|
+
.map((c) => c.toString(16).padStart(2, '0'))
|
|
31
|
+
.join('')
|
|
32
|
+
.toUpperCase()}`;
|
|
33
|
+
|
|
34
|
+
/** WCAG 2.1 relative luminance. */
|
|
35
|
+
export const luminance = ([r, g, b]: [number, number, number]): number => {
|
|
36
|
+
const [lr, lg, lb] = [r, g, b].map((channel) => {
|
|
37
|
+
const c = channel / 255;
|
|
38
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
39
|
+
});
|
|
40
|
+
return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** WCAG 2.1 contrast ratio between two channel strings. */
|
|
44
|
+
export const contrast = (a: string, b: string): number => {
|
|
45
|
+
const [la, lb] = [luminance(parseChannels(a)), luminance(parseChannels(b))];
|
|
46
|
+
const [lighter, darker] = la >= lb ? [la, lb] : [lb, la];
|
|
47
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
48
|
+
};
|
package/tailwind/plugin.cjs
CHANGED
|
@@ -2,5 +2,9 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_plugin = require("../plugin-
|
|
5
|
+
const require_plugin = require("../plugin-B3PF4RMI.cjs");
|
|
6
|
+
exports.autofillStyles = require_plugin.autofillStyles;
|
|
7
|
+
exports.pluginBaseStyles = require_plugin.pluginBaseStyles;
|
|
8
|
+
exports.pluginComponentStyles = require_plugin.pluginComponentStyles;
|
|
9
|
+
exports.tailwindTheme = require_plugin.tailwindTheme;
|
|
6
10
|
exports.themePlugin = require_plugin.themePlugin;
|