@aglyn/shared-ui-theme 1.0.0-beta.231 → 1.0.0-beta.233

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-theme",
3
- "version": "1.0.0-beta.231",
3
+ "version": "1.0.0-beta.233",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,9 +25,9 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-types": "1.0.0-beta.231",
29
- "@aglyn/shared-util-tools": "1.0.0-beta.231",
30
- "@aglyn/shared-util-vendor": "1.0.0-beta.231",
28
+ "@aglyn/shared-data-types": "1.0.0-beta.233",
29
+ "@aglyn/shared-util-tools": "1.0.0-beta.233",
30
+ "@aglyn/shared-util-vendor": "1.0.0-beta.233",
31
31
  "@emotion/cache": "^11.14.0",
32
32
  "@emotion/react": "11.14.0",
33
33
  "@swc/helpers": "0.5.23",
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { Theme } from '../vendor/mui';
18
+ /**
19
+ * The console app's own themes: `consoleThemeLight`/`consoleThemeDark` plus
20
+ * the phone chrome above. A module of its own, outside the package barrel, so
21
+ * a published page — which reaches `console.theme` for the brand themes —
22
+ * never carries it. Import it as `@aglyn/shared-ui-theme/console-app.theme`.
23
+ */
24
+ export declare const consoleAppThemeLight: Theme;
25
+ export declare const consoleAppThemeDark: Theme;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { consoleOptions, consoleOptionsDark } from "./console.theme.js";
18
+ import createResponsiveTheme from "./util/create-responsive-theme.js";
19
+ /**
20
+ * Dialogs on a phone, for the console's own chrome only.
21
+ *
22
+ * MUI keeps 32px around every dialog paper, which leaves a 375px phone a
23
+ * 311px column — and a `md`/`lg` dialog, laid out for a table or an editor,
24
+ * squeezed into it. Below `sm` the margin halves, and the wide dialogs take
25
+ * the whole screen: the same paper `fullScreen` draws, which is MUI's
26
+ * responsive full-screen dialog arrived at once instead of per call site.
27
+ * A dialog that asked for `fullScreen` is left alone.
28
+ *
29
+ * Kept off `consoleOptions`, which customer sites inherit for component
30
+ * behavior: a customer site's dialogs are its author's to lay out.
31
+ */ const consoleChromeComponents = {
32
+ /*
33
+ * An alert carrying an action button — an upsell's "View add-ons", a
34
+ * notice's "Open" — on a phone. MUI lays message and action in one row,
35
+ * so the button squeezed to a word per line beside a paragraph. Below
36
+ * `sm` the action takes its own line under the message instead. A bare
37
+ * close button (`onClose` with no `action`) keeps its corner.
38
+ */ MuiAlert: {
39
+ styleOverrides: {
40
+ root: ({ theme, ownerState })=>(ownerState == null ? void 0 : ownerState.action) ? {
41
+ [theme.breakpoints.down('sm')]: {
42
+ flexWrap: 'wrap'
43
+ }
44
+ } : {},
45
+ action: ({ theme, ownerState })=>(ownerState == null ? void 0 : ownerState.action) ? {
46
+ [theme.breakpoints.down('sm')]: {
47
+ flexBasis: '100%',
48
+ marginLeft: 0,
49
+ marginRight: 0,
50
+ paddingLeft: 0,
51
+ paddingTop: 0,
52
+ justifyContent: 'flex-end'
53
+ }
54
+ } : {}
55
+ }
56
+ },
57
+ /*
58
+ * Every list footer on a phone — the DataGrid's, `ListPagination` and the
59
+ * bare ones. "Rows per page", its select and the range are one ~420px
60
+ * line, so on a 375px screen the footer scrolled sideways inside its own
61
+ * card. Below `sm` the label and the spacer before it go; the select and
62
+ * the range stay, and the select keeps the label as its accessible name.
63
+ */ MuiTablePagination: {
64
+ styleOverrides: {
65
+ toolbar: ({ theme })=>({
66
+ [theme.breakpoints.down('sm')]: {
67
+ paddingLeft: theme.spacing(1)
68
+ }
69
+ }),
70
+ spacer: ({ theme })=>({
71
+ [theme.breakpoints.down('sm')]: {
72
+ display: 'none'
73
+ }
74
+ }),
75
+ selectLabel: ({ theme })=>({
76
+ [theme.breakpoints.down('sm')]: {
77
+ display: 'none'
78
+ }
79
+ }),
80
+ input: ({ theme })=>({
81
+ [theme.breakpoints.down('sm')]: {
82
+ marginLeft: 0,
83
+ marginRight: theme.spacing(1)
84
+ }
85
+ }),
86
+ actions: ({ theme })=>({
87
+ [theme.breakpoints.down('sm')]: {
88
+ marginLeft: theme.spacing(0.5)
89
+ }
90
+ })
91
+ }
92
+ },
93
+ MuiDialog: {
94
+ styleOverrides: {
95
+ paper: ({ theme, ownerState })=>{
96
+ if (ownerState == null ? void 0 : ownerState.fullScreen) return {};
97
+ const wide = ![
98
+ 'xs',
99
+ 'sm'
100
+ ].includes(ownerState == null ? void 0 : ownerState.maxWidth);
101
+ return {
102
+ [theme.breakpoints.down('sm')]: wide ? {
103
+ margin: 0,
104
+ width: '100%',
105
+ maxWidth: '100%',
106
+ height: '100%',
107
+ maxHeight: 'none',
108
+ borderRadius: 0
109
+ } : {
110
+ margin: theme.spacing(2),
111
+ maxWidth: `calc(100% - ${theme.spacing(4)})`,
112
+ maxHeight: `calc(100% - ${theme.spacing(4)})`
113
+ }
114
+ };
115
+ },
116
+ paperFullWidth: ({ theme, ownerState })=>(ownerState == null ? void 0 : ownerState.fullScreen) || ![
117
+ 'xs',
118
+ 'sm'
119
+ ].includes(ownerState == null ? void 0 : ownerState.maxWidth) ? {} : {
120
+ [theme.breakpoints.down('sm')]: {
121
+ width: `calc(100% - ${theme.spacing(4)})`
122
+ }
123
+ }
124
+ }
125
+ }
126
+ };
127
+ const withConsoleChrome = (options)=>_extends({}, options, {
128
+ components: _extends({}, options.components, consoleChromeComponents)
129
+ });
130
+ /**
131
+ * The console app's own themes: `consoleThemeLight`/`consoleThemeDark` plus
132
+ * the phone chrome above. A module of its own, outside the package barrel, so
133
+ * a published page — which reaches `console.theme` for the brand themes —
134
+ * never carries it. Import it as `@aglyn/shared-ui-theme/console-app.theme`.
135
+ */ export const consoleAppThemeLight = createResponsiveTheme({
136
+ themeOptions: withConsoleChrome(consoleOptions)
137
+ });
138
+ export const consoleAppThemeDark = createResponsiveTheme({
139
+ themeOptions: withConsoleChrome(consoleOptionsDark)
140
+ });
141
+
142
+ //# sourceMappingURL=console-app.theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/shared/ui/theme/src/lib/console-app.theme.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Theme, ThemeOptions } from '../vendor/mui'\nimport { consoleOptions, consoleOptionsDark } from './console.theme'\nimport createResponsiveTheme from './util/create-responsive-theme'\n\n/**\n * Dialogs on a phone, for the console's own chrome only.\n *\n * MUI keeps 32px around every dialog paper, which leaves a 375px phone a\n * 311px column — and a `md`/`lg` dialog, laid out for a table or an editor,\n * squeezed into it. Below `sm` the margin halves, and the wide dialogs take\n * the whole screen: the same paper `fullScreen` draws, which is MUI's\n * responsive full-screen dialog arrived at once instead of per call site.\n * A dialog that asked for `fullScreen` is left alone.\n *\n * Kept off `consoleOptions`, which customer sites inherit for component\n * behavior: a customer site's dialogs are its author's to lay out.\n */\nconst consoleChromeComponents: ThemeOptions['components'] = {\n /*\n * An alert carrying an action button — an upsell's \"View add-ons\", a\n * notice's \"Open\" — on a phone. MUI lays message and action in one row,\n * so the button squeezed to a word per line beside a paragraph. Below\n * `sm` the action takes its own line under the message instead. A bare\n * close button (`onClose` with no `action`) keeps its corner.\n */\n MuiAlert: {\n styleOverrides: {\n root: ({ theme, ownerState }: any) =>\n ownerState?.action\n ? { [theme.breakpoints.down('sm')]: { flexWrap: 'wrap' } }\n : {},\n action: ({ theme, ownerState }: any) =>\n ownerState?.action\n ? {\n [theme.breakpoints.down('sm')]: {\n flexBasis: '100%',\n marginLeft: 0,\n marginRight: 0,\n paddingLeft: 0,\n paddingTop: 0,\n justifyContent: 'flex-end',\n },\n }\n : {},\n },\n },\n /*\n * Every list footer on a phone — the DataGrid's, `ListPagination` and the\n * bare ones. \"Rows per page\", its select and the range are one ~420px\n * line, so on a 375px screen the footer scrolled sideways inside its own\n * card. Below `sm` the label and the spacer before it go; the select and\n * the range stay, and the select keeps the label as its accessible name.\n */\n MuiTablePagination: {\n styleOverrides: {\n toolbar: ({ theme }: any) => ({\n [theme.breakpoints.down('sm')]: { paddingLeft: theme.spacing(1) },\n }),\n spacer: ({ theme }: any) => ({\n [theme.breakpoints.down('sm')]: { display: 'none' },\n }),\n selectLabel: ({ theme }: any) => ({\n [theme.breakpoints.down('sm')]: { display: 'none' },\n }),\n input: ({ theme }: any) => ({\n [theme.breakpoints.down('sm')]: {\n marginLeft: 0,\n marginRight: theme.spacing(1),\n },\n }),\n actions: ({ theme }: any) => ({\n [theme.breakpoints.down('sm')]: { marginLeft: theme.spacing(0.5) },\n }),\n },\n },\n MuiDialog: {\n styleOverrides: {\n paper: ({ theme, ownerState }: any) => {\n if (ownerState?.fullScreen) return {}\n const wide = !['xs', 'sm'].includes(ownerState?.maxWidth)\n return {\n [theme.breakpoints.down('sm')]: wide\n ? {\n margin: 0,\n width: '100%',\n maxWidth: '100%',\n height: '100%',\n maxHeight: 'none',\n borderRadius: 0,\n }\n : {\n margin: theme.spacing(2),\n maxWidth: `calc(100% - ${theme.spacing(4)})`,\n maxHeight: `calc(100% - ${theme.spacing(4)})`,\n },\n }\n },\n paperFullWidth: ({ theme, ownerState }: any) =>\n ownerState?.fullScreen || !['xs', 'sm'].includes(ownerState?.maxWidth)\n ? {}\n : {\n [theme.breakpoints.down('sm')]: {\n width: `calc(100% - ${theme.spacing(4)})`,\n },\n },\n },\n },\n}\n\nconst withConsoleChrome = (options: ThemeOptions): ThemeOptions => ({\n ...options,\n components: { ...options.components, ...consoleChromeComponents },\n})\n\n/**\n * The console app's own themes: `consoleThemeLight`/`consoleThemeDark` plus\n * the phone chrome above. A module of its own, outside the package barrel, so\n * a published page — which reaches `console.theme` for the brand themes —\n * never carries it. Import it as `@aglyn/shared-ui-theme/console-app.theme`.\n */\nexport const consoleAppThemeLight: Theme = createResponsiveTheme({\n themeOptions: withConsoleChrome(consoleOptions),\n})\nexport const consoleAppThemeDark: Theme = createResponsiveTheme({\n themeOptions: withConsoleChrome(consoleOptionsDark),\n})\n"],"names":["consoleOptions","consoleOptionsDark","createResponsiveTheme","consoleChromeComponents","MuiAlert","styleOverrides","root","theme","ownerState","action","breakpoints","down","flexWrap","flexBasis","marginLeft","marginRight","paddingLeft","paddingTop","justifyContent","MuiTablePagination","toolbar","spacing","spacer","display","selectLabel","input","actions","MuiDialog","paper","fullScreen","wide","includes","maxWidth","margin","width","height","maxHeight","borderRadius","paperFullWidth","withConsoleChrome","options","components","consoleAppThemeLight","themeOptions","consoleAppThemeDark"],"mappings":"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"}
@@ -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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"}