@aglyn/shared-ui-theme 1.0.0-beta.230 → 1.0.0-beta.232
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/package.json +4 -4
- package/src/lib/console-app.theme.d.ts +25 -0
- package/src/lib/console-app.theme.js +142 -0
- package/src/lib/console-app.theme.js.map +1 -0
- package/src/lib/console.theme.js +8 -2
- package/src/lib/console.theme.js.map +1 -1
- package/src/lib/constants.d.ts +2 -0
- package/src/lib/constants.js +1 -0
- package/src/lib/constants.js.map +1 -1
- package/src/lib/platform-brand.d.ts +25 -0
- package/src/lib/platform-brand.js +40 -0
- package/src/lib/platform-brand.js.map +1 -0
- package/src/lib/site-base-fonts.d.ts +54 -0
- package/src/lib/site-base-fonts.js +125 -0
- package/src/lib/site-base-fonts.js.map +1 -0
- package/src/lib/tenant.theme.d.ts +1 -2
- package/src/lib/tenant.theme.js +8 -30
- package/src/lib/tenant.theme.js.map +1 -1
- package/src/lib/util/font-stack.d.ts +44 -0
- package/src/lib/util/font-stack.js +79 -0
- package/src/lib/util/font-stack.js.map +1 -0
- package/src/lib/util/roboto-flex.d.ts +65 -0
- package/src/lib/util/roboto-flex.js +115 -0
- package/src/lib/util/roboto-flex.js.map +1 -0
- package/src/lib/util/self-hosted-fonts.d.ts +111 -1
- package/src/lib/util/self-hosted-fonts.js +349 -2
- package/src/lib/util/self-hosted-fonts.js.map +1 -1
- package/src/lib/util/theme-editor-edits.d.ts +105 -0
- package/src/lib/util/theme-editor-edits.js +168 -0
- package/src/lib/util/theme-editor-edits.js.map +1 -0
- package/src/lib/util/theme-editor-fields.d.ts +53 -2
- package/src/lib/util/theme-editor-fields.js +171 -20
- package/src/lib/util/theme-editor-fields.js.map +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { HostTheme, HostThemeScheme } from '@aglyn/shared-data-types';
|
|
7
|
+
import { type ThemeColorToken } from './theme-editor-fields';
|
|
8
|
+
/** One control's change; a null value clears it back to the theme's own. */
|
|
9
|
+
export type ThemeEditorEdit = {
|
|
10
|
+
control: 'color';
|
|
11
|
+
scheme: HostThemeScheme;
|
|
12
|
+
token: ThemeColorToken;
|
|
13
|
+
value: string | null;
|
|
14
|
+
} | {
|
|
15
|
+
control: 'darkScheme';
|
|
16
|
+
value: 'auto' | 'off';
|
|
17
|
+
} | {
|
|
18
|
+
control: 'fontFamily';
|
|
19
|
+
value: string;
|
|
20
|
+
} | {
|
|
21
|
+
control: 'borderRadius';
|
|
22
|
+
value: number | null;
|
|
23
|
+
} | {
|
|
24
|
+
control: 'spacing';
|
|
25
|
+
value: number | null;
|
|
26
|
+
} | {
|
|
27
|
+
control: 'navHeight';
|
|
28
|
+
breakpoint: 'xs' | 'sm';
|
|
29
|
+
value: number | null;
|
|
30
|
+
};
|
|
31
|
+
/** The most edits one save may carry: every color of both schemes and the rest. */
|
|
32
|
+
export declare const THEME_EDITOR_EDITS_MAX: number;
|
|
33
|
+
/** One edit from a request body, or why it was refused. */
|
|
34
|
+
export declare function readThemeEditorEdit(raw: unknown): ThemeEditorEdit | string;
|
|
35
|
+
/** Every edit of a request body, or the first refusal. */
|
|
36
|
+
export declare function readThemeEditorEdits(raw: unknown): ThemeEditorEdit[] | string;
|
|
37
|
+
/** The theme with [edits] applied, in order, by the editor's own writers. */
|
|
38
|
+
export declare function applyThemeEditorEdits(theme: HostTheme, edits: readonly ThemeEditorEdit[]): HostTheme;
|
|
39
|
+
/** What each control shows for [theme]; an absent value inherits the platform's. */
|
|
40
|
+
export interface ThemeEditorValues {
|
|
41
|
+
colors: Record<HostThemeScheme, Record<string, string | null>>;
|
|
42
|
+
darkScheme: 'auto' | 'off';
|
|
43
|
+
fontFamily: string;
|
|
44
|
+
borderRadius: number | null;
|
|
45
|
+
spacing: number | null;
|
|
46
|
+
navHeight: {
|
|
47
|
+
xs: number | null;
|
|
48
|
+
sm: number | null;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export declare function readThemeEditorValues(theme: HostTheme | undefined): ThemeEditorValues;
|
|
52
|
+
/**
|
|
53
|
+
* The controls themselves, for a client that draws the editor from data:
|
|
54
|
+
* every color field, the font list, and each number's range and label.
|
|
55
|
+
*/
|
|
56
|
+
export declare const THEME_EDITOR_CATALOG: {
|
|
57
|
+
readonly schemes: readonly HostThemeScheme[];
|
|
58
|
+
readonly colors: readonly {
|
|
59
|
+
token: ThemeColorToken;
|
|
60
|
+
label: string;
|
|
61
|
+
group: import("./theme-editor-fields").ThemeColorGroup;
|
|
62
|
+
}[];
|
|
63
|
+
readonly darkScheme: {
|
|
64
|
+
readonly label: "Dark scheme";
|
|
65
|
+
readonly options: readonly [{
|
|
66
|
+
readonly value: "auto";
|
|
67
|
+
readonly label: "Follows the visitor";
|
|
68
|
+
}, {
|
|
69
|
+
readonly value: "off";
|
|
70
|
+
readonly label: "Off — always light";
|
|
71
|
+
}];
|
|
72
|
+
};
|
|
73
|
+
readonly systemFont: "__system__";
|
|
74
|
+
readonly fonts: {
|
|
75
|
+
family: string;
|
|
76
|
+
category: "sans-serif" | "serif" | "monospace" | "display";
|
|
77
|
+
weights: Array<number>;
|
|
78
|
+
}[];
|
|
79
|
+
readonly borderRadius: {
|
|
80
|
+
readonly label: "Border radius";
|
|
81
|
+
readonly min: 0;
|
|
82
|
+
readonly max: 24;
|
|
83
|
+
readonly step: 1;
|
|
84
|
+
};
|
|
85
|
+
readonly spacing: {
|
|
86
|
+
readonly label: "Spacing unit (px)";
|
|
87
|
+
readonly min: 2;
|
|
88
|
+
readonly max: 16;
|
|
89
|
+
readonly step: 1;
|
|
90
|
+
};
|
|
91
|
+
readonly navHeight: {
|
|
92
|
+
readonly xs: {
|
|
93
|
+
readonly label: "Nav height, mobile (px)";
|
|
94
|
+
readonly min: 40;
|
|
95
|
+
readonly max: 160;
|
|
96
|
+
readonly step: 1;
|
|
97
|
+
};
|
|
98
|
+
readonly sm: {
|
|
99
|
+
readonly label: "Nav height, desktop (px)";
|
|
100
|
+
readonly min: 40;
|
|
101
|
+
readonly max: 160;
|
|
102
|
+
readonly step: 1;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/ import { BORDER_RADIUS_FIELD, DARK_SCHEME_FIELD, GOOGLE_FONT_OPTIONS, SPACING_FIELD, SYSTEM_FONT_VALUE, THEME_COLOR_FIELDS, THEME_EDITOR_SCHEMES, TOOLBAR_HEIGHT_FIELDS, readDarkScheme, readFontFamily, readThemeColor, readToolbarHeight, writeBorderRadius, writeDarkScheme, writeFontFamily, writeSpacing, writeThemeColor, writeToolbarHeight } from "./theme-editor-fields.js";
|
|
6
|
+
/** The most edits one save may carry: every color of both schemes and the rest. */ export const THEME_EDITOR_EDITS_MAX = THEME_COLOR_FIELDS.length * 2 + 8;
|
|
7
|
+
const HEX_COLOR = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
8
|
+
function readNumber(value, range) {
|
|
9
|
+
if (value === null || value === undefined || value === '') return null;
|
|
10
|
+
const parsed = typeof value === 'number' ? value : Number(value);
|
|
11
|
+
if (!Number.isFinite(parsed)) return 'Use a number.';
|
|
12
|
+
if (parsed < range.min || parsed > range.max) {
|
|
13
|
+
return `Use a number from ${range.min} to ${range.max}.`;
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
/** One edit from a request body, or why it was refused. */ export function readThemeEditorEdit(raw) {
|
|
18
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return 'That change could not be read.';
|
|
19
|
+
const edit = raw;
|
|
20
|
+
switch(edit['control']){
|
|
21
|
+
case 'color':
|
|
22
|
+
{
|
|
23
|
+
const scheme = edit['scheme'];
|
|
24
|
+
if (!THEME_EDITOR_SCHEMES.includes(scheme)) return 'Pick light or dark.';
|
|
25
|
+
const token = edit['token'];
|
|
26
|
+
if (!THEME_COLOR_FIELDS.some((field)=>field.token === token)) return 'That is not a theme color.';
|
|
27
|
+
const value = edit['value'];
|
|
28
|
+
if (value !== null && value !== undefined && value !== '' && (typeof value !== 'string' || !HEX_COLOR.test(value))) {
|
|
29
|
+
return 'Use a hex color such as #1A73E8.';
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
control: 'color',
|
|
33
|
+
scheme: scheme,
|
|
34
|
+
token: token,
|
|
35
|
+
value: typeof value === 'string' && value ? value : null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
case 'darkScheme':
|
|
39
|
+
return edit['value'] === 'off' ? {
|
|
40
|
+
control: 'darkScheme',
|
|
41
|
+
value: 'off'
|
|
42
|
+
} : {
|
|
43
|
+
control: 'darkScheme',
|
|
44
|
+
value: 'auto'
|
|
45
|
+
};
|
|
46
|
+
case 'fontFamily':
|
|
47
|
+
{
|
|
48
|
+
const value = edit['value'];
|
|
49
|
+
if (value !== SYSTEM_FONT_VALUE && !GOOGLE_FONT_OPTIONS.some((option)=>option.family === value)) {
|
|
50
|
+
return 'Pick a font from the list.';
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
control: 'fontFamily',
|
|
54
|
+
value: value
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
case 'borderRadius':
|
|
58
|
+
{
|
|
59
|
+
const value = readNumber(edit['value'], BORDER_RADIUS_FIELD);
|
|
60
|
+
return typeof value === 'string' ? value : {
|
|
61
|
+
control: 'borderRadius',
|
|
62
|
+
value
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
case 'spacing':
|
|
66
|
+
{
|
|
67
|
+
const value = readNumber(edit['value'], SPACING_FIELD);
|
|
68
|
+
return typeof value === 'string' ? value : {
|
|
69
|
+
control: 'spacing',
|
|
70
|
+
value
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
case 'navHeight':
|
|
74
|
+
{
|
|
75
|
+
const breakpoint = edit['breakpoint'];
|
|
76
|
+
if (breakpoint !== 'xs' && breakpoint !== 'sm') return 'Pick mobile or desktop.';
|
|
77
|
+
const value = readNumber(edit['value'], TOOLBAR_HEIGHT_FIELDS[breakpoint]);
|
|
78
|
+
return typeof value === 'string' ? value : {
|
|
79
|
+
control: 'navHeight',
|
|
80
|
+
breakpoint,
|
|
81
|
+
value
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
default:
|
|
85
|
+
return 'That is not a theme control.';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Every edit of a request body, or the first refusal. */ export function readThemeEditorEdits(raw) {
|
|
89
|
+
if (!Array.isArray(raw) || raw.length === 0) return 'Nothing to change.';
|
|
90
|
+
if (raw.length > THEME_EDITOR_EDITS_MAX) return 'Too many changes at once.';
|
|
91
|
+
const edits = [];
|
|
92
|
+
for (const entry of raw){
|
|
93
|
+
const edit = readThemeEditorEdit(entry);
|
|
94
|
+
if (typeof edit === 'string') return edit;
|
|
95
|
+
edits.push(edit);
|
|
96
|
+
}
|
|
97
|
+
return edits;
|
|
98
|
+
}
|
|
99
|
+
/** The theme with [edits] applied, in order, by the editor's own writers. */ export function applyThemeEditorEdits(theme, edits) {
|
|
100
|
+
let next = theme;
|
|
101
|
+
for (const edit of edits){
|
|
102
|
+
switch(edit.control){
|
|
103
|
+
case 'color':
|
|
104
|
+
var _edit_value;
|
|
105
|
+
next = writeThemeColor(next, edit.scheme, edit.token, (_edit_value = edit.value) != null ? _edit_value : undefined);
|
|
106
|
+
break;
|
|
107
|
+
case 'darkScheme':
|
|
108
|
+
next = writeDarkScheme(next, edit.value);
|
|
109
|
+
break;
|
|
110
|
+
case 'fontFamily':
|
|
111
|
+
next = writeFontFamily(next, edit.value);
|
|
112
|
+
break;
|
|
113
|
+
case 'borderRadius':
|
|
114
|
+
var _edit_value1;
|
|
115
|
+
next = writeBorderRadius(next, (_edit_value1 = edit.value) != null ? _edit_value1 : undefined);
|
|
116
|
+
break;
|
|
117
|
+
case 'spacing':
|
|
118
|
+
var _edit_value2;
|
|
119
|
+
next = writeSpacing(next, (_edit_value2 = edit.value) != null ? _edit_value2 : undefined);
|
|
120
|
+
break;
|
|
121
|
+
case 'navHeight':
|
|
122
|
+
var _edit_value3;
|
|
123
|
+
next = writeToolbarHeight(next, edit.breakpoint, (_edit_value3 = edit.value) != null ? _edit_value3 : undefined);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return next;
|
|
128
|
+
}
|
|
129
|
+
export function readThemeEditorValues(theme) {
|
|
130
|
+
var _readToolbarHeight, _readToolbarHeight1;
|
|
131
|
+
var _safe_shape;
|
|
132
|
+
const safe = theme != null ? theme : {};
|
|
133
|
+
const colors = {};
|
|
134
|
+
for (const scheme of THEME_EDITOR_SCHEMES){
|
|
135
|
+
colors[scheme] = {};
|
|
136
|
+
for (const field of THEME_COLOR_FIELDS){
|
|
137
|
+
var _readThemeColor;
|
|
138
|
+
colors[scheme][field.token] = (_readThemeColor = readThemeColor(safe, scheme, field.token)) != null ? _readThemeColor : null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const radius = (_safe_shape = safe.shape) == null ? void 0 : _safe_shape.borderRadius;
|
|
142
|
+
return {
|
|
143
|
+
colors,
|
|
144
|
+
darkScheme: readDarkScheme(safe),
|
|
145
|
+
fontFamily: readFontFamily(safe),
|
|
146
|
+
borderRadius: typeof radius === 'number' && Number.isFinite(radius) ? radius : null,
|
|
147
|
+
spacing: typeof safe.spacing === 'number' ? safe.spacing : null,
|
|
148
|
+
navHeight: {
|
|
149
|
+
xs: (_readToolbarHeight = readToolbarHeight(safe, 'xs')) != null ? _readToolbarHeight : null,
|
|
150
|
+
sm: (_readToolbarHeight1 = readToolbarHeight(safe, 'sm')) != null ? _readToolbarHeight1 : null
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The controls themselves, for a client that draws the editor from data:
|
|
156
|
+
* every color field, the font list, and each number's range and label.
|
|
157
|
+
*/ export const THEME_EDITOR_CATALOG = {
|
|
158
|
+
schemes: THEME_EDITOR_SCHEMES,
|
|
159
|
+
colors: THEME_COLOR_FIELDS,
|
|
160
|
+
darkScheme: DARK_SCHEME_FIELD,
|
|
161
|
+
systemFont: SYSTEM_FONT_VALUE,
|
|
162
|
+
fonts: GOOGLE_FONT_OPTIONS,
|
|
163
|
+
borderRadius: BORDER_RADIUS_FIELD,
|
|
164
|
+
spacing: SPACING_FIELD,
|
|
165
|
+
navHeight: TOOLBAR_HEIGHT_FIELDS
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=theme-editor-edits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/theme-editor-edits.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { HostTheme, HostThemeScheme } from '@aglyn/shared-data-types'\nimport {\n BORDER_RADIUS_FIELD,\n DARK_SCHEME_FIELD,\n GOOGLE_FONT_OPTIONS,\n SPACING_FIELD,\n SYSTEM_FONT_VALUE,\n THEME_COLOR_FIELDS,\n THEME_EDITOR_SCHEMES,\n TOOLBAR_HEIGHT_FIELDS,\n readDarkScheme,\n readFontFamily,\n readThemeColor,\n readToolbarHeight,\n writeBorderRadius,\n writeDarkScheme,\n writeFontFamily,\n writeSpacing,\n writeThemeColor,\n writeToolbarHeight,\n type ThemeColorToken,\n} from './theme-editor-fields'\n\n/*\n * THE THEME EDITOR'S CONTROLS AS EDITS (AGL-3668).\n *\n * The console's theme section edits the resolved theme in the browser with\n * the writers in `theme-editor-fields.ts` and saves the difference from the\n * picked theme as the site's override. A native app sends the same controls\n * as named edits instead — one color for one scheme, the font, the radius,\n * the spacing unit, a nav height, the dark scheme — and `/api/hosts/theme`\n * applies them here with those same writers, so both clients make exactly the\n * same theme from the same choice. `readThemeEditorValues` is the other half:\n * what each control currently shows.\n */\n\n/** One control's change; a null value clears it back to the theme's own. */\nexport type ThemeEditorEdit =\n | { control: 'color'; scheme: HostThemeScheme; token: ThemeColorToken; value: string | null }\n | { control: 'darkScheme'; value: 'auto' | 'off' }\n | { control: 'fontFamily'; value: string }\n | { control: 'borderRadius'; value: number | null }\n | { control: 'spacing'; value: number | null }\n | { control: 'navHeight'; breakpoint: 'xs' | 'sm'; value: number | null }\n\n/** The most edits one save may carry: every color of both schemes and the rest. */\nexport const THEME_EDITOR_EDITS_MAX = THEME_COLOR_FIELDS.length * 2 + 8\n\nconst HEX_COLOR = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/\n\nfunction readNumber(\n value: unknown,\n range: { min: number; max: number },\n): number | null | string {\n if (value === null || value === undefined || value === '') return null\n const parsed = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(parsed)) return 'Use a number.'\n if (parsed < range.min || parsed > range.max) {\n return `Use a number from ${range.min} to ${range.max}.`\n }\n return parsed\n}\n\n/** One edit from a request body, or why it was refused. */\nexport function readThemeEditorEdit(raw: unknown): ThemeEditorEdit | string {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return 'That change could not be read.'\n const edit = raw as Record<string, unknown>\n switch (edit['control']) {\n case 'color': {\n const scheme = edit['scheme']\n if (!THEME_EDITOR_SCHEMES.includes(scheme as HostThemeScheme)) return 'Pick light or dark.'\n const token = edit['token']\n if (!THEME_COLOR_FIELDS.some((field) => field.token === token)) return 'That is not a theme color.'\n const value = edit['value']\n if (value !== null && value !== undefined && value !== '' && (typeof value !== 'string' || !HEX_COLOR.test(value))) {\n return 'Use a hex color such as #1A73E8.'\n }\n return {\n control: 'color',\n scheme: scheme as HostThemeScheme,\n token: token as ThemeColorToken,\n value: typeof value === 'string' && value ? value : null,\n }\n }\n case 'darkScheme':\n return edit['value'] === 'off' ? { control: 'darkScheme', value: 'off' } : { control: 'darkScheme', value: 'auto' }\n case 'fontFamily': {\n const value = edit['value']\n if (value !== SYSTEM_FONT_VALUE && !GOOGLE_FONT_OPTIONS.some((option) => option.family === value)) {\n return 'Pick a font from the list.'\n }\n return { control: 'fontFamily', value: value as string }\n }\n case 'borderRadius': {\n const value = readNumber(edit['value'], BORDER_RADIUS_FIELD)\n return typeof value === 'string' ? value : { control: 'borderRadius', value }\n }\n case 'spacing': {\n const value = readNumber(edit['value'], SPACING_FIELD)\n return typeof value === 'string' ? value : { control: 'spacing', value }\n }\n case 'navHeight': {\n const breakpoint = edit['breakpoint']\n if (breakpoint !== 'xs' && breakpoint !== 'sm') return 'Pick mobile or desktop.'\n const value = readNumber(edit['value'], TOOLBAR_HEIGHT_FIELDS[breakpoint])\n return typeof value === 'string' ? value : { control: 'navHeight', breakpoint, value }\n }\n default:\n return 'That is not a theme control.'\n }\n}\n\n/** Every edit of a request body, or the first refusal. */\nexport function readThemeEditorEdits(raw: unknown): ThemeEditorEdit[] | string {\n if (!Array.isArray(raw) || raw.length === 0) return 'Nothing to change.'\n if (raw.length > THEME_EDITOR_EDITS_MAX) return 'Too many changes at once.'\n const edits: ThemeEditorEdit[] = []\n for (const entry of raw) {\n const edit = readThemeEditorEdit(entry)\n if (typeof edit === 'string') return edit\n edits.push(edit)\n }\n return edits\n}\n\n/** The theme with [edits] applied, in order, by the editor's own writers. */\nexport function applyThemeEditorEdits(theme: HostTheme, edits: readonly ThemeEditorEdit[]): HostTheme {\n let next = theme\n for (const edit of edits) {\n switch (edit.control) {\n case 'color':\n next = writeThemeColor(next, edit.scheme, edit.token, edit.value ?? undefined)\n break\n case 'darkScheme':\n next = writeDarkScheme(next, edit.value)\n break\n case 'fontFamily':\n next = writeFontFamily(next, edit.value)\n break\n case 'borderRadius':\n next = writeBorderRadius(next, edit.value ?? undefined)\n break\n case 'spacing':\n next = writeSpacing(next, edit.value ?? undefined)\n break\n case 'navHeight':\n next = writeToolbarHeight(next, edit.breakpoint, edit.value ?? undefined)\n break\n }\n }\n return next\n}\n\n/** What each control shows for [theme]; an absent value inherits the platform's. */\nexport interface ThemeEditorValues {\n colors: Record<HostThemeScheme, Record<string, string | null>>\n darkScheme: 'auto' | 'off'\n fontFamily: string\n borderRadius: number | null\n spacing: number | null\n navHeight: { xs: number | null; sm: number | null }\n}\n\nexport function readThemeEditorValues(theme: HostTheme | undefined): ThemeEditorValues {\n const safe = theme ?? {}\n const colors = {} as Record<HostThemeScheme, Record<string, string | null>>\n for (const scheme of THEME_EDITOR_SCHEMES) {\n colors[scheme] = {}\n for (const field of THEME_COLOR_FIELDS) {\n colors[scheme][field.token] = readThemeColor(safe, scheme, field.token) ?? null\n }\n }\n const radius = safe.shape?.borderRadius\n return {\n colors,\n darkScheme: readDarkScheme(safe),\n fontFamily: readFontFamily(safe),\n borderRadius: typeof radius === 'number' && Number.isFinite(radius) ? radius : null,\n spacing: typeof safe.spacing === 'number' ? safe.spacing : null,\n navHeight: {\n xs: readToolbarHeight(safe, 'xs') ?? null,\n sm: readToolbarHeight(safe, 'sm') ?? null,\n },\n }\n}\n\n/**\n * The controls themselves, for a client that draws the editor from data:\n * every color field, the font list, and each number's range and label.\n */\nexport const THEME_EDITOR_CATALOG = {\n schemes: THEME_EDITOR_SCHEMES,\n colors: THEME_COLOR_FIELDS,\n darkScheme: DARK_SCHEME_FIELD,\n systemFont: SYSTEM_FONT_VALUE,\n fonts: GOOGLE_FONT_OPTIONS,\n borderRadius: BORDER_RADIUS_FIELD,\n spacing: SPACING_FIELD,\n navHeight: TOOLBAR_HEIGHT_FIELDS,\n} as const\n"],"names":["BORDER_RADIUS_FIELD","DARK_SCHEME_FIELD","GOOGLE_FONT_OPTIONS","SPACING_FIELD","SYSTEM_FONT_VALUE","THEME_COLOR_FIELDS","THEME_EDITOR_SCHEMES","TOOLBAR_HEIGHT_FIELDS","readDarkScheme","readFontFamily","readThemeColor","readToolbarHeight","writeBorderRadius","writeDarkScheme","writeFontFamily","writeSpacing","writeThemeColor","writeToolbarHeight","THEME_EDITOR_EDITS_MAX","length","HEX_COLOR","readNumber","value","range","undefined","parsed","Number","isFinite","min","max","readThemeEditorEdit","raw","Array","isArray","edit","scheme","includes","token","some","field","test","control","option","family","breakpoint","readThemeEditorEdits","edits","entry","push","applyThemeEditorEdits","theme","next","readThemeEditorValues","safe","colors","radius","shape","borderRadius","darkScheme","fontFamily","spacing","navHeight","xs","sm","THEME_EDITOR_CATALOG","schemes","systemFont","fonts"],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import type { HostTheme, HostThemeScheme, HostThemeSchemeColors } from '@aglyn/shared-data-types';
|
|
17
|
+
import type { HostTheme, HostThemeFontCategory, HostThemeTypographyVariantKey, HostThemeScheme, HostThemeSchemeColors } from '@aglyn/shared-data-types';
|
|
18
18
|
import { type HostThemeComponentKey } from './host-theme';
|
|
19
19
|
/**
|
|
20
20
|
* The host theme editor's field catalog (AGL-2938): every control the editor
|
|
@@ -140,7 +140,7 @@ export declare const GOOGLE_FONT_OPTIONS: Array<{
|
|
|
140
140
|
category: 'sans-serif' | 'serif' | 'monospace' | 'display';
|
|
141
141
|
weights: Array<number>;
|
|
142
142
|
}>;
|
|
143
|
-
export declare function fontFamilyStack(family: string, category:
|
|
143
|
+
export declare function fontFamilyStack(family: string, category: HostThemeFontCategory): string;
|
|
144
144
|
export declare function getSchemeColor(colors: HostThemeSchemeColors | undefined, path: SurfaceColorPath): string | undefined;
|
|
145
145
|
/** The two color schemes the editor edits, in tab order. */
|
|
146
146
|
export declare const THEME_EDITOR_SCHEMES: readonly HostThemeScheme[];
|
|
@@ -277,6 +277,57 @@ export declare function readFontFamily(theme: HostTheme | undefined): string;
|
|
|
277
277
|
* theme default. A family outside the curated list changes nothing.
|
|
278
278
|
*/
|
|
279
279
|
export declare function writeFontFamily(theme: HostTheme, value: string): HostTheme;
|
|
280
|
+
/**
|
|
281
|
+
* The text styles a separate heading font is set on: `h1`…`h6`, and
|
|
282
|
+
* `displayXl`, the platform's rung above `h1`, which a heading font left off
|
|
283
|
+
* would draw in the body font — and load the body font's heaviest weight for.
|
|
284
|
+
*/
|
|
285
|
+
export declare const THEME_HEADING_VARIANTS: readonly HostThemeTypographyVariantKey[];
|
|
286
|
+
/**
|
|
287
|
+
* One family a theme sets text in, as the font picker shows and writes it
|
|
288
|
+
* (AGL-3656): a `fonts` entry and the role it plays.
|
|
289
|
+
*/
|
|
290
|
+
export interface ThemeFontChoice {
|
|
291
|
+
family: string;
|
|
292
|
+
category: HostThemeFontCategory;
|
|
293
|
+
/** Upright weights to load, ascending. */
|
|
294
|
+
weights: number[];
|
|
295
|
+
/** Italic weights to load, ascending; absent or empty for none. */
|
|
296
|
+
italics?: number[];
|
|
297
|
+
source: 'google' | 'custom';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The theme's body font and, when it differs, its heading font. `null` is the
|
|
301
|
+
* theme default: the base stack, nothing loaded.
|
|
302
|
+
*/
|
|
303
|
+
export interface ThemeFontSelection {
|
|
304
|
+
body: ThemeFontChoice | null;
|
|
305
|
+
heading: ThemeFontChoice | null;
|
|
306
|
+
}
|
|
307
|
+
/** The first family a CSS font-family stack names, unquoted. */
|
|
308
|
+
export declare function firstStackFamily(stack: unknown): string | undefined;
|
|
309
|
+
/**
|
|
310
|
+
* The body and heading fonts a theme sets text in (AGL-3656).
|
|
311
|
+
*
|
|
312
|
+
* The body font is the family the theme-wide stack names first, when the
|
|
313
|
+
* theme loads it: a font the theme lists that no stack names draws nothing,
|
|
314
|
+
* which is what the theme default is. The heading font is the family `h1`
|
|
315
|
+
* names, when that is a loaded font other than the body's.
|
|
316
|
+
*/
|
|
317
|
+
export declare function readThemeFonts(theme: HostTheme | undefined): ThemeFontSelection;
|
|
318
|
+
/**
|
|
319
|
+
* Sets the theme's body and heading fonts (AGL-3656): the `fonts` entries the
|
|
320
|
+
* page loads, the theme-wide stack, and the stack of each style in
|
|
321
|
+
* {@link THEME_HEADING_VARIANTS} for a heading font of its own. `null` goes
|
|
322
|
+
* back to the theme default for that role.
|
|
323
|
+
*
|
|
324
|
+
* An uploaded (`custom`) font the theme lists but no longer sets text in is
|
|
325
|
+
* kept, because it is the site's own and is picked again from the list; a
|
|
326
|
+
* Google font nothing sets text in any more is dropped, so the page stops
|
|
327
|
+
* loading it. Everything else in the typography — sizes, weights, a variant's own
|
|
328
|
+
* family that is not a heading — is left as it was.
|
|
329
|
+
*/
|
|
330
|
+
export declare function writeThemeFonts(theme: HostTheme, selection: ThemeFontSelection): HostTheme;
|
|
280
331
|
/** Sets the corner radius, or clears it with `undefined` so it inherits. */
|
|
281
332
|
export declare function writeBorderRadius(theme: HostTheme, value: number | undefined): HostTheme;
|
|
282
333
|
/** Sets the spacing unit; anything but a positive number clears it. */
|
|
@@ -596,38 +596,189 @@ export const FONT_FAMILY_FIELD = {
|
|
|
596
596
|
}
|
|
597
597
|
/** The font select's value: the site's font, or {@link SYSTEM_FONT_VALUE}. */ export function readFontFamily(theme) {
|
|
598
598
|
var _ref;
|
|
599
|
-
var
|
|
600
|
-
return (_ref =
|
|
599
|
+
var _readThemeFonts_body;
|
|
600
|
+
return (_ref = (_readThemeFonts_body = readThemeFonts(theme).body) == null ? void 0 : _readThemeFonts_body.family) != null ? _ref : SYSTEM_FONT_VALUE;
|
|
601
601
|
}
|
|
602
602
|
/**
|
|
603
603
|
* Picks a font from {@link GOOGLE_FONT_OPTIONS}, loading it and naming it as
|
|
604
604
|
* the typography's stack, or {@link SYSTEM_FONT_VALUE} to go back to the
|
|
605
605
|
* theme default. A family outside the curated list changes nothing.
|
|
606
606
|
*/ export function writeFontFamily(theme, value) {
|
|
607
|
+
// Through writeThemeFonts (AGL-3656), so a heading font and the site's own
|
|
608
|
+
// uploaded fonts survive a body font picked from the short list or by the
|
|
609
|
+
// AI theme step; replacing `fonts` wholesale dropped both.
|
|
610
|
+
const current = readThemeFonts(theme);
|
|
607
611
|
if (value === SYSTEM_FONT_VALUE) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (Object.keys(typography).length) next.typography = typography;
|
|
613
|
-
else delete next.typography;
|
|
614
|
-
return next;
|
|
612
|
+
return writeThemeFonts(theme, {
|
|
613
|
+
body: null,
|
|
614
|
+
heading: current.heading
|
|
615
|
+
});
|
|
615
616
|
}
|
|
616
617
|
const option = GOOGLE_FONT_OPTIONS.find((entry)=>entry.family === value);
|
|
617
618
|
if (!option) return theme;
|
|
618
|
-
return
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
619
|
+
return writeThemeFonts(theme, {
|
|
620
|
+
body: {
|
|
621
|
+
family: option.family,
|
|
622
|
+
category: option.category,
|
|
623
|
+
weights: option.weights,
|
|
624
|
+
source: 'google'
|
|
625
|
+
},
|
|
626
|
+
heading: current.heading
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* The text styles a separate heading font is set on: `h1`…`h6`, and
|
|
631
|
+
* `displayXl`, the platform's rung above `h1`, which a heading font left off
|
|
632
|
+
* would draw in the body font — and load the body font's heaviest weight for.
|
|
633
|
+
*/ export const THEME_HEADING_VARIANTS = [
|
|
634
|
+
'displayXl',
|
|
635
|
+
'h1',
|
|
636
|
+
'h2',
|
|
637
|
+
'h3',
|
|
638
|
+
'h4',
|
|
639
|
+
'h5',
|
|
640
|
+
'h6'
|
|
641
|
+
];
|
|
642
|
+
/** The first family a CSS font-family stack names, unquoted. */ export function firstStackFamily(stack) {
|
|
643
|
+
var _stack_split_;
|
|
644
|
+
if (typeof stack !== 'string') return undefined;
|
|
645
|
+
const first = (_stack_split_ = stack.split(',')[0]) == null ? void 0 : _stack_split_.trim().replace(/^["']|["']$/g, '').trim();
|
|
646
|
+
return first || undefined;
|
|
647
|
+
}
|
|
648
|
+
/** The category a stack's generic keyword names, for a font that records none. */ function stackCategory(stack) {
|
|
649
|
+
if (typeof stack !== 'string') return 'sans-serif';
|
|
650
|
+
if (/\bmonospace\b/i.test(stack)) return 'monospace';
|
|
651
|
+
if (/(^|,)\s*serif\b/i.test(stack)) return 'serif';
|
|
652
|
+
return 'sans-serif';
|
|
653
|
+
}
|
|
654
|
+
function sortedWeights(values) {
|
|
655
|
+
return [
|
|
656
|
+
...new Set((values != null ? values : []).map((value)=>Math.round(Number(value))).filter((value)=>Number.isFinite(value) && value >= 1 && value <= 1000))
|
|
657
|
+
].sort((a, b)=>a - b);
|
|
658
|
+
}
|
|
659
|
+
function choiceOf(font, stack) {
|
|
660
|
+
var _font_category;
|
|
661
|
+
const italics = sortedWeights(font.italics);
|
|
662
|
+
return _extends({
|
|
663
|
+
family: font.family.trim(),
|
|
664
|
+
category: (_font_category = font.category) != null ? _font_category : stackCategory(stack),
|
|
665
|
+
weights: sortedWeights(font.weights).length ? sortedWeights(font.weights) : [
|
|
666
|
+
400
|
|
667
|
+
]
|
|
668
|
+
}, italics.length ? {
|
|
669
|
+
italics
|
|
670
|
+
} : {}, {
|
|
671
|
+
source: font.source === 'custom' ? 'custom' : 'google'
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
function findFont(theme, family) {
|
|
675
|
+
var _theme_fonts;
|
|
676
|
+
if (!family) return undefined;
|
|
677
|
+
const wanted = family.toLowerCase();
|
|
678
|
+
return theme == null ? void 0 : (_theme_fonts = theme.fonts) == null ? void 0 : _theme_fonts.find((font)=>{
|
|
679
|
+
var _font_source;
|
|
680
|
+
var _font_family;
|
|
681
|
+
return ((_font_family = font.family) == null ? void 0 : _font_family.trim().toLowerCase()) === wanted && ((_font_source = font.source) != null ? _font_source : 'google') !== 'system';
|
|
629
682
|
});
|
|
630
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* The body and heading fonts a theme sets text in (AGL-3656).
|
|
686
|
+
*
|
|
687
|
+
* The body font is the family the theme-wide stack names first, when the
|
|
688
|
+
* theme loads it: a font the theme lists that no stack names draws nothing,
|
|
689
|
+
* which is what the theme default is. The heading font is the family `h1`
|
|
690
|
+
* names, when that is a loaded font other than the body's.
|
|
691
|
+
*/ export function readThemeFonts(theme) {
|
|
692
|
+
var _theme_typography, _theme_typography_variants_h1, _theme_typography_variants, _theme_typography1;
|
|
693
|
+
const stack = theme == null ? void 0 : (_theme_typography = theme.typography) == null ? void 0 : _theme_typography.fontFamily;
|
|
694
|
+
const bodyFont = findFont(theme, firstStackFamily(stack));
|
|
695
|
+
const headingStack = theme == null ? void 0 : (_theme_typography1 = theme.typography) == null ? void 0 : (_theme_typography_variants = _theme_typography1.variants) == null ? void 0 : (_theme_typography_variants_h1 = _theme_typography_variants.h1) == null ? void 0 : _theme_typography_variants_h1.fontFamily;
|
|
696
|
+
const headingFont = findFont(theme, firstStackFamily(headingStack));
|
|
697
|
+
return {
|
|
698
|
+
body: bodyFont ? choiceOf(bodyFont, stack) : null,
|
|
699
|
+
heading: headingFont && headingFont !== bodyFont ? choiceOf(headingFont, headingStack) : null
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function fontEntry(choice) {
|
|
703
|
+
const italics = sortedWeights(choice.italics);
|
|
704
|
+
return _extends({
|
|
705
|
+
family: choice.family.trim(),
|
|
706
|
+
weights: sortedWeights(choice.weights).length ? sortedWeights(choice.weights) : [
|
|
707
|
+
400
|
|
708
|
+
]
|
|
709
|
+
}, italics.length ? {
|
|
710
|
+
italics
|
|
711
|
+
} : {}, {
|
|
712
|
+
source: choice.source,
|
|
713
|
+
category: choice.category
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Sets the theme's body and heading fonts (AGL-3656): the `fonts` entries the
|
|
718
|
+
* page loads, the theme-wide stack, and the stack of each style in
|
|
719
|
+
* {@link THEME_HEADING_VARIANTS} for a heading font of its own. `null` goes
|
|
720
|
+
* back to the theme default for that role.
|
|
721
|
+
*
|
|
722
|
+
* An uploaded (`custom`) font the theme lists but no longer sets text in is
|
|
723
|
+
* kept, because it is the site's own and is picked again from the list; a
|
|
724
|
+
* Google font nothing sets text in any more is dropped, so the page stops
|
|
725
|
+
* loading it. Everything else in the typography — sizes, weights, a variant's own
|
|
726
|
+
* family that is not a heading — is left as it was.
|
|
727
|
+
*/ export function writeThemeFonts(theme, selection) {
|
|
728
|
+
var _ref, _theme_fonts;
|
|
729
|
+
var _theme_typography_variants_h1, _theme_typography_variants, _theme_typography, _theme_typography1;
|
|
730
|
+
const body = selection.body;
|
|
731
|
+
const heading = selection.heading && (!body || selection.heading.family.toLowerCase() !== body.family.toLowerCase()) ? selection.heading : null;
|
|
732
|
+
const previous = readThemeFonts(theme);
|
|
733
|
+
const previousHeadingStack = previous.heading ? (_theme_typography = theme.typography) == null ? void 0 : (_theme_typography_variants = _theme_typography.variants) == null ? void 0 : (_theme_typography_variants_h1 = _theme_typography_variants.h1) == null ? void 0 : _theme_typography_variants_h1.fontFamily : undefined;
|
|
734
|
+
const chosen = [
|
|
735
|
+
body,
|
|
736
|
+
heading
|
|
737
|
+
].filter((entry)=>!!entry);
|
|
738
|
+
const chosenKeys = new Set(chosen.map((entry)=>entry.family.trim().toLowerCase()));
|
|
739
|
+
// Families a text style other than a heading still names, by hand or by
|
|
740
|
+
// an earlier editor: they keep loading, as their style keeps drawing them.
|
|
741
|
+
const otherStyles = new Set(Object.entries((_ref = (_theme_typography1 = theme.typography) == null ? void 0 : _theme_typography1.variants) != null ? _ref : {}).filter(([key])=>!THEME_HEADING_VARIANTS.includes(key)).map(([, variant])=>{
|
|
742
|
+
var _firstStackFamily;
|
|
743
|
+
return (_firstStackFamily = firstStackFamily(variant == null ? void 0 : variant.fontFamily)) == null ? void 0 : _firstStackFamily.toLowerCase();
|
|
744
|
+
}).filter((family)=>!!family));
|
|
745
|
+
const kept = ((_theme_fonts = theme.fonts) != null ? _theme_fonts : []).filter((font)=>{
|
|
746
|
+
var _ref;
|
|
747
|
+
var _font_family;
|
|
748
|
+
const key = (_ref = (_font_family = font.family) == null ? void 0 : _font_family.trim().toLowerCase()) != null ? _ref : '';
|
|
749
|
+
if (!key || chosenKeys.has(key)) return false;
|
|
750
|
+
return font.source === 'custom' || otherStyles.has(key);
|
|
751
|
+
});
|
|
752
|
+
const fonts = [
|
|
753
|
+
...chosen.map(fontEntry),
|
|
754
|
+
...kept
|
|
755
|
+
];
|
|
756
|
+
const typography = _extends({}, theme.typography);
|
|
757
|
+
if (body) typography.fontFamily = fontFamilyStack(body.family.trim(), body.category);
|
|
758
|
+
else delete typography.fontFamily;
|
|
759
|
+
const variants = _extends({}, typography.variants);
|
|
760
|
+
for (const key of THEME_HEADING_VARIANTS){
|
|
761
|
+
const variant = _extends({}, variants[key]);
|
|
762
|
+
if (heading) {
|
|
763
|
+
variant.fontFamily = fontFamilyStack(heading.family.trim(), heading.category);
|
|
764
|
+
} else if (variant.fontFamily !== undefined && (variant.fontFamily === previousHeadingStack || !findFont({
|
|
765
|
+
fonts
|
|
766
|
+
}, firstStackFamily(variant.fontFamily)))) {
|
|
767
|
+
// The heading font this theme had, or a family it no longer loads.
|
|
768
|
+
delete variant.fontFamily;
|
|
769
|
+
}
|
|
770
|
+
if (Object.keys(variant).length) variants[key] = variant;
|
|
771
|
+
else delete variants[key];
|
|
772
|
+
}
|
|
773
|
+
if (Object.keys(variants).length) typography.variants = variants;
|
|
774
|
+
else delete typography.variants;
|
|
775
|
+
const next = _extends({}, theme);
|
|
776
|
+
if (fonts.length) next.fonts = fonts;
|
|
777
|
+
else delete next.fonts;
|
|
778
|
+
if (Object.keys(typography).length) next.typography = typography;
|
|
779
|
+
else delete next.typography;
|
|
780
|
+
return next;
|
|
781
|
+
}
|
|
631
782
|
/** Sets the corner radius, or clears it with `undefined` so it inherits. */ export function writeBorderRadius(theme, value) {
|
|
632
783
|
if (value !== undefined && Number.isFinite(value)) {
|
|
633
784
|
return _extends({}, theme, {
|