@aglyn/shared-ui-theme 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +51 -0
- package/src/index.d.ts +34 -0
- package/src/index.js +50 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/host-theme-provider.d.ts +84 -0
- package/src/lib/components/host-theme-provider.js +146 -0
- package/src/lib/components/host-theme-provider.js.map +1 -0
- package/src/lib/components/theme-css-var-provider.d.ts +44 -0
- package/src/lib/components/theme-css-var-provider.js +79 -0
- package/src/lib/components/theme-css-var-provider.js.map +1 -0
- package/src/lib/console.theme.d.ts +34 -0
- package/src/lib/console.theme.js +598 -0
- package/src/lib/console.theme.js.map +1 -0
- package/src/lib/constants.d.ts +72 -0
- package/src/lib/constants.js +85 -0
- package/src/lib/constants.js.map +1 -0
- package/src/lib/hocs/create-with-emotion-client-cache.d.ts +30 -0
- package/src/lib/hocs/create-with-emotion-client-cache.js +53 -0
- package/src/lib/hocs/create-with-emotion-client-cache.js.map +1 -0
- package/src/lib/hocs/create-with-theme-provider.d.ts +86 -0
- package/src/lib/hocs/create-with-theme-provider.js +231 -0
- package/src/lib/hocs/create-with-theme-provider.js.map +1 -0
- package/src/lib/tenant.theme.d.ts +58 -0
- package/src/lib/tenant.theme.js +282 -0
- package/src/lib/tenant.theme.js.map +1 -0
- package/src/lib/theme.types.d.ts +26 -0
- package/src/lib/theme.types.js +18 -0
- package/src/lib/theme.types.js.map +1 -0
- package/src/lib/util/accent-text.d.ts +187 -0
- package/src/lib/util/accent-text.js +277 -0
- package/src/lib/util/accent-text.js.map +1 -0
- package/src/lib/util/accessible-shade.d.ts +61 -0
- package/src/lib/util/accessible-shade.js +117 -0
- package/src/lib/util/accessible-shade.js.map +1 -0
- package/src/lib/util/color-scheme-hint.d.ts +68 -0
- package/src/lib/util/color-scheme-hint.js +69 -0
- package/src/lib/util/color-scheme-hint.js.map +1 -0
- package/src/lib/util/create-responsive-theme.d.ts +71 -0
- package/src/lib/util/create-responsive-theme.js +287 -0
- package/src/lib/util/create-responsive-theme.js.map +1 -0
- package/src/lib/util/emotion-cache.d.ts +81 -0
- package/src/lib/util/emotion-cache.js +79 -0
- package/src/lib/util/emotion-cache.js.map +1 -0
- package/src/lib/util/generate-component-class-keys.d.ts +41 -0
- package/src/lib/util/generate-component-class-keys.js +53 -0
- package/src/lib/util/generate-component-class-keys.js.map +1 -0
- package/src/lib/util/host-theme.d.ts +92 -0
- package/src/lib/util/host-theme.js +340 -0
- package/src/lib/util/host-theme.js.map +1 -0
- package/src/lib/util/layered-emotion-cache.d.ts +78 -0
- package/src/lib/util/layered-emotion-cache.js +90 -0
- package/src/lib/util/layered-emotion-cache.js.map +1 -0
- package/src/lib/util/merge-sx-props.d.ts +22 -0
- package/src/lib/util/merge-sx-props.js +27 -0
- package/src/lib/util/merge-sx-props.js.map +1 -0
- package/src/lib/util/scheme-route-segment.d.ts +95 -0
- package/src/lib/util/scheme-route-segment.js +101 -0
- package/src/lib/util/scheme-route-segment.js.map +1 -0
- package/src/lib/util/theme-editor-defaults.d.ts +41 -0
- package/src/lib/util/theme-editor-defaults.js +49 -0
- package/src/lib/util/theme-editor-defaults.js.map +1 -0
- package/src/lib/util/theme-editor-fields.d.ts +315 -0
- package/src/lib/util/theme-editor-fields.js +714 -0
- package/src/lib/util/theme-editor-fields.js.map +1 -0
- package/src/lib/util/theme-mode-cookie.d.ts +48 -0
- package/src/lib/util/theme-mode-cookie.js +48 -0
- package/src/lib/util/theme-mode-cookie.js.map +1 -0
- package/src/vendor/emotion.d.ts +18 -0
- package/src/vendor/emotion.js +22 -0
- package/src/vendor/emotion.js.map +1 -0
- package/src/vendor/jss.d.ts +18 -0
- package/src/vendor/jss.js +20 -0
- package/src/vendor/jss.js.map +1 -0
- package/src/vendor/mui.d.ts +282 -0
- package/src/vendor/mui.js +20 -0
- package/src/vendor/mui.js.map +1 -0
|
@@ -0,0 +1,340 @@
|
|
|
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 { objectDeepMergeReplaceArrays } from "@aglyn/shared-util-vendor";
|
|
18
|
+
/**
|
|
19
|
+
* Components a host theme may override. Persisted overrides are plain JSON;
|
|
20
|
+
* anything outside this list is dropped by {@link sanitizeHostTheme} so a
|
|
21
|
+
* tampered document can't restyle console-internal or portal-critical
|
|
22
|
+
* components.
|
|
23
|
+
*/ export const HOST_THEME_COMPONENT_WHITELIST = [
|
|
24
|
+
'MuiAppBar',
|
|
25
|
+
'MuiAvatar',
|
|
26
|
+
'MuiBadge',
|
|
27
|
+
'MuiButton',
|
|
28
|
+
'MuiButtonBase',
|
|
29
|
+
'MuiCard',
|
|
30
|
+
'MuiCardContent',
|
|
31
|
+
'MuiCheckbox',
|
|
32
|
+
'MuiChip',
|
|
33
|
+
'MuiCircularProgress',
|
|
34
|
+
'MuiDivider',
|
|
35
|
+
'MuiIconButton',
|
|
36
|
+
'MuiLinearProgress',
|
|
37
|
+
'MuiLink',
|
|
38
|
+
'MuiList',
|
|
39
|
+
'MuiListItem',
|
|
40
|
+
'MuiMenu',
|
|
41
|
+
'MuiPaper',
|
|
42
|
+
'MuiRadio',
|
|
43
|
+
'MuiSlider',
|
|
44
|
+
'MuiSwitch',
|
|
45
|
+
'MuiTab',
|
|
46
|
+
'MuiTabs',
|
|
47
|
+
'MuiTextField',
|
|
48
|
+
'MuiToolbar',
|
|
49
|
+
'MuiTooltip',
|
|
50
|
+
'MuiTypography'
|
|
51
|
+
];
|
|
52
|
+
const componentWhitelist = new Set(HOST_THEME_COMPONENT_WHITELIST);
|
|
53
|
+
function pickPaletteColor(color) {
|
|
54
|
+
if (!(color == null ? void 0 : color.main)) return undefined;
|
|
55
|
+
const picked = {
|
|
56
|
+
main: color.main
|
|
57
|
+
};
|
|
58
|
+
if (color.light) picked.light = color.light;
|
|
59
|
+
if (color.dark) picked.dark = color.dark;
|
|
60
|
+
if (color.contrastText) picked.contrastText = color.contrastText;
|
|
61
|
+
return picked;
|
|
62
|
+
}
|
|
63
|
+
function schemeColorsToPaletteOptions(scheme, colors) {
|
|
64
|
+
var _colors_background, _colors_background1, _colors_text, _colors_text1, _colors_text2, _colors_tint, _colors_tint1, _colors_tint2;
|
|
65
|
+
const palette = {
|
|
66
|
+
mode: scheme
|
|
67
|
+
};
|
|
68
|
+
if (!colors) return palette;
|
|
69
|
+
const colorKeys = [
|
|
70
|
+
'primary',
|
|
71
|
+
'secondary',
|
|
72
|
+
'tertiary',
|
|
73
|
+
'surface',
|
|
74
|
+
'error',
|
|
75
|
+
'warning',
|
|
76
|
+
'info',
|
|
77
|
+
'success'
|
|
78
|
+
];
|
|
79
|
+
for (const key of colorKeys){
|
|
80
|
+
const color = pickPaletteColor(colors[key]);
|
|
81
|
+
if (color) palette[key] = color;
|
|
82
|
+
}
|
|
83
|
+
if (((_colors_background = colors.background) == null ? void 0 : _colors_background.default) || ((_colors_background1 = colors.background) == null ? void 0 : _colors_background1.paper)) {
|
|
84
|
+
palette.background = _extends({}, colors.background.default && {
|
|
85
|
+
default: colors.background.default
|
|
86
|
+
}, colors.background.paper && {
|
|
87
|
+
paper: colors.background.paper
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (((_colors_text = colors.text) == null ? void 0 : _colors_text.primary) || ((_colors_text1 = colors.text) == null ? void 0 : _colors_text1.secondary) || ((_colors_text2 = colors.text) == null ? void 0 : _colors_text2.disabled)) {
|
|
91
|
+
palette.text = _extends({}, colors.text.primary && {
|
|
92
|
+
primary: colors.text.primary
|
|
93
|
+
}, colors.text.secondary && {
|
|
94
|
+
secondary: colors.text.secondary
|
|
95
|
+
}, colors.text.disabled && {
|
|
96
|
+
disabled: colors.text.disabled
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Tints are string leaves, not a PaletteColor, so they pass through the
|
|
100
|
+
// same "copy what was set" path as `background`/`text` rather than
|
|
101
|
+
// `pickPaletteColor` — which requires a `main` a tint does not have
|
|
102
|
+
// (AGL-1244).
|
|
103
|
+
if (((_colors_tint = colors.tint) == null ? void 0 : _colors_tint.primary) || ((_colors_tint1 = colors.tint) == null ? void 0 : _colors_tint1.secondary) || ((_colors_tint2 = colors.tint) == null ? void 0 : _colors_tint2.tertiary)) {
|
|
104
|
+
palette.tint = _extends({}, colors.tint.primary && {
|
|
105
|
+
primary: colors.tint.primary
|
|
106
|
+
}, colors.tint.secondary && {
|
|
107
|
+
secondary: colors.tint.secondary
|
|
108
|
+
}, colors.tint.tertiary && {
|
|
109
|
+
tertiary: colors.tint.tertiary
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (colors.divider) palette.divider = colors.divider;
|
|
113
|
+
return palette;
|
|
114
|
+
}
|
|
115
|
+
function sanitizeComponents(components) {
|
|
116
|
+
if (!components) return undefined;
|
|
117
|
+
const sanitized = {};
|
|
118
|
+
for (const [key, override] of Object.entries(components)){
|
|
119
|
+
if (!componentWhitelist.has(key) || !override) continue;
|
|
120
|
+
const entry = {};
|
|
121
|
+
if (override.defaultProps) entry.defaultProps = override.defaultProps;
|
|
122
|
+
if (override.styleOverrides) entry.styleOverrides = override.styleOverrides;
|
|
123
|
+
if (Object.keys(entry).length) sanitized[key] = entry;
|
|
124
|
+
}
|
|
125
|
+
return Object.keys(sanitized).length ? sanitized : undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Strips unknown component overrides and empty branches from a persisted
|
|
129
|
+
* host theme. Returns a new object; the input is never mutated.
|
|
130
|
+
*/ export function sanitizeHostTheme(theme) {
|
|
131
|
+
var _theme_mixins;
|
|
132
|
+
if (!theme) return {};
|
|
133
|
+
const sanitized = _extends({}, theme);
|
|
134
|
+
const components = sanitizeComponents(theme.components);
|
|
135
|
+
if (components) sanitized.components = components;
|
|
136
|
+
else delete sanitized.components;
|
|
137
|
+
// `mixins.toolbar` must be a CSS object; a scalar would reach
|
|
138
|
+
// `createTheme` and throw while building the Toolbar variant.
|
|
139
|
+
if (isPlainObject((_theme_mixins = theme.mixins) == null ? void 0 : _theme_mixins.toolbar)) {
|
|
140
|
+
sanitized.mixins = {
|
|
141
|
+
toolbar: theme.mixins.toolbar
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
delete sanitized.mixins;
|
|
145
|
+
}
|
|
146
|
+
// Absent already means "follows the visitor", so only the opt-out is worth
|
|
147
|
+
// persisting; anything else (a stale `'auto'`, junk) is dropped.
|
|
148
|
+
if (theme.darkScheme !== 'off') delete sanitized.darkScheme;
|
|
149
|
+
return sanitized;
|
|
150
|
+
}
|
|
151
|
+
/** An `@media` key that names a `min-width`, and the width it names. */ const MIN_WIDTH_AT_RULE = /^@media\b[^{]*\bmin-width\s*:\s*(\d+(?:\.\d+)?)\s*px/;
|
|
152
|
+
/**
|
|
153
|
+
* Re-orders breakpoint at-rules ascending, so STORED key order cannot decide
|
|
154
|
+
* which rule wins (AGL-3146).
|
|
155
|
+
*
|
|
156
|
+
* Two `min-width` rules of equal specificity both match a wide window, so the
|
|
157
|
+
* later one wins — which makes key order a rendering decision. Firestore does
|
|
158
|
+
* not preserve it: one stored toolbar mixin came back sm, landscape, base to
|
|
159
|
+
* the server reader and landscape, base, sm to the browser, so aglyn.com's
|
|
160
|
+
* nav drew 48px live and 72px on the besigner canvas from a single saved
|
|
161
|
+
* document. The editor and the page disagreed about a value neither of them
|
|
162
|
+
* had changed.
|
|
163
|
+
*
|
|
164
|
+
* Ascending is the order MUI writes its own breakpoint styles in and the
|
|
165
|
+
* order the theme editor writes a toolbar mixin in: the wider query is the
|
|
166
|
+
* more specific answer, so it belongs last.
|
|
167
|
+
*
|
|
168
|
+
* Only two things move. Plain declarations are hoisted ahead of the blocks,
|
|
169
|
+
* keeping their order among themselves — which is where the style engine
|
|
170
|
+
* emits them anyway, since a nested block becomes a rule of its own after the
|
|
171
|
+
* base one, and where the shorthand/longhand hazard lives. Breakpoint rules
|
|
172
|
+
* are sorted into the slots the blocks already occupy, so a nested selector
|
|
173
|
+
* or a condition naming no width never changes position against one another.
|
|
174
|
+
*
|
|
175
|
+
* Returns its input by identity when no order changes, which is every theme
|
|
176
|
+
* with at most one breakpoint rule per object.
|
|
177
|
+
*/ export function orderMediaWidths(value) {
|
|
178
|
+
if (Array.isArray(value)) {
|
|
179
|
+
let moved = false;
|
|
180
|
+
const next = value.map((entry)=>{
|
|
181
|
+
const ordered = orderMediaWidths(entry);
|
|
182
|
+
if (ordered !== entry) moved = true;
|
|
183
|
+
return ordered;
|
|
184
|
+
});
|
|
185
|
+
return moved ? next : value;
|
|
186
|
+
}
|
|
187
|
+
if (!isPlainObject(value)) return value;
|
|
188
|
+
const source = value;
|
|
189
|
+
const keys = Object.keys(source);
|
|
190
|
+
const declarations = keys.filter((key)=>!isPlainObject(source[key]));
|
|
191
|
+
const blocks = keys.filter((key)=>isPlainObject(source[key]));
|
|
192
|
+
const widthSlots = [];
|
|
193
|
+
const widths = [];
|
|
194
|
+
blocks.forEach((key, index)=>{
|
|
195
|
+
const match = MIN_WIDTH_AT_RULE.exec(key);
|
|
196
|
+
if (!match) return;
|
|
197
|
+
widthSlots.push(index);
|
|
198
|
+
widths.push({
|
|
199
|
+
key,
|
|
200
|
+
width: Number.parseFloat(match[1])
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
// A stable sort, so two rules at the same width keep the order they were
|
|
204
|
+
// stored in — there is nothing to prefer between them.
|
|
205
|
+
const sorted = [
|
|
206
|
+
...widths
|
|
207
|
+
].sort((a, b)=>a.width - b.width);
|
|
208
|
+
widthSlots.forEach((slot, position)=>{
|
|
209
|
+
blocks[slot] = sorted[position].key;
|
|
210
|
+
});
|
|
211
|
+
const order = [
|
|
212
|
+
...declarations,
|
|
213
|
+
...blocks
|
|
214
|
+
];
|
|
215
|
+
let moved = order.some((key, index)=>key !== keys[index]);
|
|
216
|
+
const next = {};
|
|
217
|
+
for (const key of order){
|
|
218
|
+
const child = orderMediaWidths(source[key]);
|
|
219
|
+
if (child !== source[key]) moved = true;
|
|
220
|
+
next[key] = child;
|
|
221
|
+
}
|
|
222
|
+
return moved ? next : value;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Converts a persisted {@link HostTheme} document into MUI `ThemeOptions`
|
|
226
|
+
* for one color scheme. The result is meant to be passed through
|
|
227
|
+
* `createResponsiveTheme` (or `createTheme`) by the consumer; shade and
|
|
228
|
+
* contrast-text derivation for partial palettes is MUI's job, so only
|
|
229
|
+
* explicitly set values are forwarded.
|
|
230
|
+
*/ export function hostThemeToThemeOptions(theme, scheme) {
|
|
231
|
+
var _sanitized_colorSchemes, _sanitized_shape, _sanitized_mixins;
|
|
232
|
+
const sanitized = sanitizeHostTheme(theme);
|
|
233
|
+
const options = {
|
|
234
|
+
palette: schemeColorsToPaletteOptions(scheme, (_sanitized_colorSchemes = sanitized.colorSchemes) == null ? void 0 : _sanitized_colorSchemes[scheme])
|
|
235
|
+
};
|
|
236
|
+
const { typography } = sanitized;
|
|
237
|
+
if ((typography == null ? void 0 : typography.fontFamily) || (typography == null ? void 0 : typography.variants)) {
|
|
238
|
+
// HostThemeTypographyVariant is a sanitized subset of MUI's
|
|
239
|
+
// TypographyStyleOptions; the missing index signature is by design.
|
|
240
|
+
options.typography = _extends({}, typography.fontFamily && {
|
|
241
|
+
fontFamily: typography.fontFamily
|
|
242
|
+
}, typography.variants);
|
|
243
|
+
}
|
|
244
|
+
if (typeof ((_sanitized_shape = sanitized.shape) == null ? void 0 : _sanitized_shape.borderRadius) === 'number') {
|
|
245
|
+
options.shape = {
|
|
246
|
+
borderRadius: sanitized.shape.borderRadius
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (typeof sanitized.spacing === 'number') {
|
|
250
|
+
options.spacing = sanitized.spacing;
|
|
251
|
+
}
|
|
252
|
+
if (sanitized.components) {
|
|
253
|
+
options.components = sanitized.components;
|
|
254
|
+
}
|
|
255
|
+
// Toolbar height is only reachable here (AGL-1242). MUI derives the
|
|
256
|
+
// Toolbar's `regular` variant style from `mixins.toolbar` and applies it
|
|
257
|
+
// AFTER `components.MuiToolbar.styleOverrides`, so the slot override loses
|
|
258
|
+
// every time — its nested media queries do not even emit.
|
|
259
|
+
if ((_sanitized_mixins = sanitized.mixins) == null ? void 0 : _sanitized_mixins.toolbar) {
|
|
260
|
+
options.mixins = {
|
|
261
|
+
toolbar: sanitized.mixins.toolbar
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// Every surface that renders a site's theme arrives here — the tenant's
|
|
265
|
+
// provider, the besigner canvas, Preview, the theme editor — so this is
|
|
266
|
+
// where a stored document stops being able to resolve differently for
|
|
267
|
+
// different readers (AGL-3146).
|
|
268
|
+
return orderMediaWidths(options);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Layers a host's overrides ONTO a base set of theme options (AGL-1180).
|
|
272
|
+
*
|
|
273
|
+
* `hostThemeToThemeOptions` deliberately emits only what the host explicitly
|
|
274
|
+
* set, so building a theme from it alone leaves every other slot to MUI's
|
|
275
|
+
* stock palette. Consumers used to switch — console theme when the document
|
|
276
|
+
* was empty, host document when it was not — which meant setting a single
|
|
277
|
+
* value (the spec's own example is `{ spacing: 8 }`) silently repainted
|
|
278
|
+
* secondary, tertiary, surface, info, success, warning, error, background
|
|
279
|
+
* and paper in MUI blue/purple. Merging instead of switching keeps the brand
|
|
280
|
+
* as the floor no matter how much the host customizes.
|
|
281
|
+
*
|
|
282
|
+
* `palette` merges one level deep so overriding `primary` cannot drop
|
|
283
|
+
* `secondary`. Within a single colour the override replaces the whole record
|
|
284
|
+
* — MUI derives shades and contrast text from `main`, which is exactly the
|
|
285
|
+
* partial-palette behaviour the converter is written for.
|
|
286
|
+
*/ export function mergeThemeOptions(base, overrides) {
|
|
287
|
+
var _base_components, _overrides_components;
|
|
288
|
+
const merged = _extends({}, base, overrides);
|
|
289
|
+
// Palette merges ONE level: overriding a colour replaces its whole record
|
|
290
|
+
// so MUI re-derives light/dark/contrastText from the new `main`, which is
|
|
291
|
+
// the partial-palette behaviour the converter is written for. Overriding
|
|
292
|
+
// `primary` still must not disturb `secondary`, hence the level.
|
|
293
|
+
merged.palette = _extends({}, base.palette, overrides.palette);
|
|
294
|
+
merged.shape = _extends({}, base.shape, overrides.shape);
|
|
295
|
+
// Same reasoning as `palette`: setting `toolbar` must not drop any other
|
|
296
|
+
// mixin the base defines.
|
|
297
|
+
merged.mixins = _extends({}, base.mixins, overrides.mixins);
|
|
298
|
+
// `typography` is an object in every base we ship, but MUI's type also
|
|
299
|
+
// allows a function of the palette — merging into that would silently drop
|
|
300
|
+
// the base, so prefer the override wholesale in that case.
|
|
301
|
+
if (isPlainObject(base.typography) && isPlainObject(overrides.typography)) {
|
|
302
|
+
merged.typography = objectDeepMergeReplaceArrays(base.typography, overrides.typography);
|
|
303
|
+
}
|
|
304
|
+
// Components merge DEEPLY. A host override names one component, and often
|
|
305
|
+
// one property inside it — `MuiButton.defaultProps.color`. A shallow merge
|
|
306
|
+
// would swap out the entire `MuiButton` entry and take the brand's
|
|
307
|
+
// `styleOverrides` with it, and those styles are frequently FUNCTIONS of
|
|
308
|
+
// the theme that JSON cannot express, so the editor could not put them
|
|
309
|
+
// back even in principle. Deep-merging means you override the leaf you
|
|
310
|
+
// named and inherit everything else, functions included.
|
|
311
|
+
merged.components = objectDeepMergeReplaceArrays((_base_components = base.components) != null ? _base_components : {}, (_overrides_components = overrides.components) != null ? _overrides_components : {});
|
|
312
|
+
return merged;
|
|
313
|
+
}
|
|
314
|
+
/** Plain data object — not an array, function, or class instance. */ function isPlainObject(value) {
|
|
315
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
316
|
+
}
|
|
317
|
+
/** True when the document customizes anything, i.e. consumers should build a theme from it rather than using their default. */ export function hasHostTheme(theme) {
|
|
318
|
+
return !!theme && Object.keys(theme).length > 0;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Builds a Google Fonts CSS2 stylesheet URL for the theme's loadable fonts.
|
|
322
|
+
* Returns undefined when nothing needs loading (system/absent fonts).
|
|
323
|
+
*/ export function getGoogleFontsUrl(fonts) {
|
|
324
|
+
const families = (fonts != null ? fonts : []).filter((font)=>{
|
|
325
|
+
var _font_source;
|
|
326
|
+
return font.family && ((_font_source = font.source) != null ? _font_source : 'google') === 'google';
|
|
327
|
+
}).map((font)=>{
|
|
328
|
+
var _font_weights;
|
|
329
|
+
const family = font.family.trim().replace(/\s+/g, '+');
|
|
330
|
+
const weights = ((_font_weights = font.weights) == null ? void 0 : _font_weights.length) ? `:wght@${[
|
|
331
|
+
...font.weights
|
|
332
|
+
].sort((a, b)=>a - b).join(';')}` : '';
|
|
333
|
+
return `family=${family}${weights}`;
|
|
334
|
+
});
|
|
335
|
+
if (!families.length) return undefined;
|
|
336
|
+
return `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`;
|
|
337
|
+
}
|
|
338
|
+
export default hostThemeToThemeOptions;
|
|
339
|
+
|
|
340
|
+
//# sourceMappingURL=host-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/host-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 */\n\nimport type {\n HostTheme,\n HostThemeComponentOverride,\n HostThemeFont,\n HostThemePaletteColor,\n HostThemeScheme,\n HostThemeSchemeColors,\n} from '@aglyn/shared-data-types'\nimport { objectDeepMergeReplaceArrays } from '@aglyn/shared-util-vendor'\nimport type { PaletteOptions, ThemeOptions } from '../../vendor/mui'\n\n/**\n * Components a host theme may override. Persisted overrides are plain JSON;\n * anything outside this list is dropped by {@link sanitizeHostTheme} so a\n * tampered document can't restyle console-internal or portal-critical\n * components.\n */\nexport const HOST_THEME_COMPONENT_WHITELIST = [\n 'MuiAppBar',\n 'MuiAvatar',\n 'MuiBadge',\n 'MuiButton',\n 'MuiButtonBase',\n 'MuiCard',\n 'MuiCardContent',\n 'MuiCheckbox',\n 'MuiChip',\n 'MuiCircularProgress',\n 'MuiDivider',\n 'MuiIconButton',\n 'MuiLinearProgress',\n 'MuiLink',\n 'MuiList',\n 'MuiListItem',\n 'MuiMenu',\n 'MuiPaper',\n 'MuiRadio',\n 'MuiSlider',\n 'MuiSwitch',\n 'MuiTab',\n 'MuiTabs',\n 'MuiTextField',\n 'MuiToolbar',\n 'MuiTooltip',\n 'MuiTypography',\n] as const\n\nexport type HostThemeComponentKey =\n (typeof HOST_THEME_COMPONENT_WHITELIST)[number]\n\nconst componentWhitelist: ReadonlySet<string> = new Set(\n HOST_THEME_COMPONENT_WHITELIST,\n)\n\nfunction pickPaletteColor(color: HostThemePaletteColor | undefined) {\n if (!color?.main) return undefined\n const picked: HostThemePaletteColor = { main: color.main }\n if (color.light) picked.light = color.light\n if (color.dark) picked.dark = color.dark\n if (color.contrastText) picked.contrastText = color.contrastText\n return picked\n}\n\nfunction schemeColorsToPaletteOptions(\n scheme: HostThemeScheme,\n colors: HostThemeSchemeColors | undefined,\n): PaletteOptions {\n const palette: PaletteOptions = { mode: scheme }\n if (!colors) return palette\n\n const colorKeys = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'surface',\n 'error',\n 'warning',\n 'info',\n 'success',\n ] as const\n for (const key of colorKeys) {\n const color = pickPaletteColor(colors[key])\n if (color) (palette as Record<string, unknown>)[key] = color\n }\n\n if (colors.background?.default || colors.background?.paper) {\n palette.background = {\n ...(colors.background.default && { default: colors.background.default }),\n ...(colors.background.paper && { paper: colors.background.paper }),\n }\n }\n if (\n colors.text?.primary ||\n colors.text?.secondary ||\n colors.text?.disabled\n ) {\n palette.text = {\n ...(colors.text.primary && { primary: colors.text.primary }),\n ...(colors.text.secondary && { secondary: colors.text.secondary }),\n ...(colors.text.disabled && { disabled: colors.text.disabled }),\n }\n }\n // Tints are string leaves, not a PaletteColor, so they pass through the\n // same \"copy what was set\" path as `background`/`text` rather than\n // `pickPaletteColor` — which requires a `main` a tint does not have\n // (AGL-1244).\n if (colors.tint?.primary || colors.tint?.secondary || colors.tint?.tertiary) {\n palette.tint = {\n ...(colors.tint.primary && { primary: colors.tint.primary }),\n ...(colors.tint.secondary && { secondary: colors.tint.secondary }),\n ...(colors.tint.tertiary && { tertiary: colors.tint.tertiary }),\n }\n }\n if (colors.divider) palette.divider = colors.divider\n\n return palette\n}\n\nfunction sanitizeComponents(\n components: Record<string, HostThemeComponentOverride> | undefined,\n) {\n if (!components) return undefined\n const sanitized: Record<string, HostThemeComponentOverride> = {}\n for (const [key, override] of Object.entries(components)) {\n if (!componentWhitelist.has(key) || !override) continue\n const entry: HostThemeComponentOverride = {}\n if (override.defaultProps) entry.defaultProps = override.defaultProps\n if (override.styleOverrides) entry.styleOverrides = override.styleOverrides\n if (Object.keys(entry).length) sanitized[key] = entry\n }\n return Object.keys(sanitized).length ? sanitized : undefined\n}\n\n/**\n * Strips unknown component overrides and empty branches from a persisted\n * host theme. Returns a new object; the input is never mutated.\n */\nexport function sanitizeHostTheme(theme: HostTheme | undefined): HostTheme {\n if (!theme) return {}\n const sanitized: HostTheme = { ...theme }\n const components = sanitizeComponents(theme.components)\n if (components) sanitized.components = components\n else delete sanitized.components\n // `mixins.toolbar` must be a CSS object; a scalar would reach\n // `createTheme` and throw while building the Toolbar variant.\n if (isPlainObject(theme.mixins?.toolbar)) {\n sanitized.mixins = { toolbar: theme.mixins.toolbar }\n } else {\n delete sanitized.mixins\n }\n // Absent already means \"follows the visitor\", so only the opt-out is worth\n // persisting; anything else (a stale `'auto'`, junk) is dropped.\n if (theme.darkScheme !== 'off') delete sanitized.darkScheme\n return sanitized\n}\n\n/** An `@media` key that names a `min-width`, and the width it names. */\nconst MIN_WIDTH_AT_RULE = /^@media\\b[^{]*\\bmin-width\\s*:\\s*(\\d+(?:\\.\\d+)?)\\s*px/\n\n/**\n * Re-orders breakpoint at-rules ascending, so STORED key order cannot decide\n * which rule wins (AGL-3146).\n *\n * Two `min-width` rules of equal specificity both match a wide window, so the\n * later one wins — which makes key order a rendering decision. Firestore does\n * not preserve it: one stored toolbar mixin came back sm, landscape, base to\n * the server reader and landscape, base, sm to the browser, so aglyn.com's\n * nav drew 48px live and 72px on the besigner canvas from a single saved\n * document. The editor and the page disagreed about a value neither of them\n * had changed.\n *\n * Ascending is the order MUI writes its own breakpoint styles in and the\n * order the theme editor writes a toolbar mixin in: the wider query is the\n * more specific answer, so it belongs last.\n *\n * Only two things move. Plain declarations are hoisted ahead of the blocks,\n * keeping their order among themselves — which is where the style engine\n * emits them anyway, since a nested block becomes a rule of its own after the\n * base one, and where the shorthand/longhand hazard lives. Breakpoint rules\n * are sorted into the slots the blocks already occupy, so a nested selector\n * or a condition naming no width never changes position against one another.\n *\n * Returns its input by identity when no order changes, which is every theme\n * with at most one breakpoint rule per object.\n */\nexport function orderMediaWidths<T>(value: T): T {\n if (Array.isArray(value)) {\n let moved = false\n const next = value.map((entry) => {\n const ordered = orderMediaWidths(entry)\n if (ordered !== entry) moved = true\n return ordered\n })\n return (moved ? next : value) as T\n }\n if (!isPlainObject(value)) return value\n\n const source = value as Record<string, unknown>\n const keys = Object.keys(source)\n const declarations = keys.filter((key) => !isPlainObject(source[key]))\n const blocks = keys.filter((key) => isPlainObject(source[key]))\n const widthSlots: number[] = []\n const widths: Array<{ key: string; width: number }> = []\n blocks.forEach((key, index) => {\n const match = MIN_WIDTH_AT_RULE.exec(key)\n if (!match) return\n widthSlots.push(index)\n widths.push({ key, width: Number.parseFloat(match[1]) })\n })\n // A stable sort, so two rules at the same width keep the order they were\n // stored in — there is nothing to prefer between them.\n const sorted = [...widths].sort((a, b) => a.width - b.width)\n widthSlots.forEach((slot, position) => {\n blocks[slot] = sorted[position].key\n })\n\n const order = [...declarations, ...blocks]\n let moved = order.some((key, index) => key !== keys[index])\n const next: Record<string, unknown> = {}\n for (const key of order) {\n const child = orderMediaWidths(source[key])\n if (child !== source[key]) moved = true\n next[key] = child\n }\n return (moved ? next : value) as T\n}\n\n/**\n * Converts a persisted {@link HostTheme} document into MUI `ThemeOptions`\n * for one color scheme. The result is meant to be passed through\n * `createResponsiveTheme` (or `createTheme`) by the consumer; shade and\n * contrast-text derivation for partial palettes is MUI's job, so only\n * explicitly set values are forwarded.\n */\nexport function hostThemeToThemeOptions(\n theme: HostTheme | undefined,\n scheme: HostThemeScheme,\n): ThemeOptions {\n const sanitized = sanitizeHostTheme(theme)\n const options: ThemeOptions = {\n palette: schemeColorsToPaletteOptions(\n scheme,\n sanitized.colorSchemes?.[scheme],\n ),\n }\n\n const { typography } = sanitized\n if (typography?.fontFamily || typography?.variants) {\n // HostThemeTypographyVariant is a sanitized subset of MUI's\n // TypographyStyleOptions; the missing index signature is by design.\n options.typography = {\n ...(typography.fontFamily && { fontFamily: typography.fontFamily }),\n ...typography.variants,\n } as ThemeOptions['typography']\n }\n\n if (typeof sanitized.shape?.borderRadius === 'number') {\n options.shape = { borderRadius: sanitized.shape.borderRadius }\n }\n if (typeof sanitized.spacing === 'number') {\n options.spacing = sanitized.spacing\n }\n if (sanitized.components) {\n options.components = sanitized.components as ThemeOptions['components']\n }\n // Toolbar height is only reachable here (AGL-1242). MUI derives the\n // Toolbar's `regular` variant style from `mixins.toolbar` and applies it\n // AFTER `components.MuiToolbar.styleOverrides`, so the slot override loses\n // every time — its nested media queries do not even emit.\n if (sanitized.mixins?.toolbar) {\n options.mixins = { toolbar: sanitized.mixins.toolbar }\n }\n\n // Every surface that renders a site's theme arrives here — the tenant's\n // provider, the besigner canvas, Preview, the theme editor — so this is\n // where a stored document stops being able to resolve differently for\n // different readers (AGL-3146).\n return orderMediaWidths(options)\n}\n\n/**\n * Layers a host's overrides ONTO a base set of theme options (AGL-1180).\n *\n * `hostThemeToThemeOptions` deliberately emits only what the host explicitly\n * set, so building a theme from it alone leaves every other slot to MUI's\n * stock palette. Consumers used to switch — console theme when the document\n * was empty, host document when it was not — which meant setting a single\n * value (the spec's own example is `{ spacing: 8 }`) silently repainted\n * secondary, tertiary, surface, info, success, warning, error, background\n * and paper in MUI blue/purple. Merging instead of switching keeps the brand\n * as the floor no matter how much the host customizes.\n *\n * `palette` merges one level deep so overriding `primary` cannot drop\n * `secondary`. Within a single colour the override replaces the whole record\n * — MUI derives shades and contrast text from `main`, which is exactly the\n * partial-palette behaviour the converter is written for.\n */\nexport function mergeThemeOptions(\n base: ThemeOptions,\n overrides: ThemeOptions,\n): ThemeOptions {\n const merged: ThemeOptions = { ...base, ...overrides }\n\n // Palette merges ONE level: overriding a colour replaces its whole record\n // so MUI re-derives light/dark/contrastText from the new `main`, which is\n // the partial-palette behaviour the converter is written for. Overriding\n // `primary` still must not disturb `secondary`, hence the level.\n merged.palette = { ...base.palette, ...overrides.palette }\n merged.shape = { ...base.shape, ...overrides.shape }\n // Same reasoning as `palette`: setting `toolbar` must not drop any other\n // mixin the base defines.\n merged.mixins = { ...base.mixins, ...overrides.mixins }\n\n // `typography` is an object in every base we ship, but MUI's type also\n // allows a function of the palette — merging into that would silently drop\n // the base, so prefer the override wholesale in that case.\n if (\n isPlainObject(base.typography) &&\n isPlainObject(overrides.typography)\n ) {\n merged.typography = objectDeepMergeReplaceArrays(\n base.typography,\n overrides.typography,\n ) as ThemeOptions['typography']\n }\n\n // Components merge DEEPLY. A host override names one component, and often\n // one property inside it — `MuiButton.defaultProps.color`. A shallow merge\n // would swap out the entire `MuiButton` entry and take the brand's\n // `styleOverrides` with it, and those styles are frequently FUNCTIONS of\n // the theme that JSON cannot express, so the editor could not put them\n // back even in principle. Deep-merging means you override the leaf you\n // named and inherit everything else, functions included.\n merged.components = objectDeepMergeReplaceArrays(\n base.components ?? {},\n overrides.components ?? {},\n ) as ThemeOptions['components']\n\n return merged\n}\n\n/** Plain data object — not an array, function, or class instance. */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === 'object' && value !== null && !Array.isArray(value)\n )\n}\n\n/** True when the document customizes anything, i.e. consumers should build a theme from it rather than using their default. */\nexport function hasHostTheme(theme: HostTheme | undefined): theme is HostTheme {\n return !!theme && Object.keys(theme).length > 0\n}\n\n/**\n * Builds a Google Fonts CSS2 stylesheet URL for the theme's loadable fonts.\n * Returns undefined when nothing needs loading (system/absent fonts).\n */\nexport function getGoogleFontsUrl(fonts: Array<HostThemeFont> | undefined) {\n const families = (fonts ?? [])\n .filter((font) => font.family && (font.source ?? 'google') === 'google')\n .map((font) => {\n const family = font.family.trim().replace(/\\s+/g, '+')\n const weights = font.weights?.length\n ? `:wght@${[...font.weights].sort((a, b) => a - b).join(';')}`\n : ''\n return `family=${family}${weights}`\n })\n if (!families.length) return undefined\n return `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`\n}\n\nexport default hostThemeToThemeOptions\n"],"names":["objectDeepMergeReplaceArrays","HOST_THEME_COMPONENT_WHITELIST","componentWhitelist","Set","pickPaletteColor","color","main","undefined","picked","light","dark","contrastText","schemeColorsToPaletteOptions","scheme","colors","palette","mode","colorKeys","key","background","default","paper","text","primary","secondary","disabled","tint","tertiary","divider","sanitizeComponents","components","sanitized","override","Object","entries","has","entry","defaultProps","styleOverrides","keys","length","sanitizeHostTheme","theme","isPlainObject","mixins","toolbar","darkScheme","MIN_WIDTH_AT_RULE","orderMediaWidths","value","Array","isArray","moved","next","map","ordered","source","declarations","filter","blocks","widthSlots","widths","forEach","index","match","exec","push","width","Number","parseFloat","sorted","sort","a","b","slot","position","order","some","child","hostThemeToThemeOptions","options","colorSchemes","typography","fontFamily","variants","shape","borderRadius","spacing","mergeThemeOptions","base","overrides","merged","hasHostTheme","getGoogleFontsUrl","fonts","families","font","family","trim","replace","weights","join"],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 CreateEmotionCacheOptions, type EmotionCache } from '../../vendor/emotion';
|
|
18
|
+
/**
|
|
19
|
+
* An emotion cache whose output is wrapped in `@layer mui`, for surfaces that
|
|
20
|
+
* cannot use `AppRouterCacheProvider` (AGL-2486).
|
|
21
|
+
*
|
|
22
|
+
* ## Why this exists: "what you see is not what you publish"
|
|
23
|
+
*
|
|
24
|
+
* `enableCssLayer` is NOT an emotion option. It is implemented by
|
|
25
|
+
* `AppRouterCacheProvider` monkey-patching `cache.insert` to wrap the
|
|
26
|
+
* serialized styles string (`appRouterV13.mjs`), and that patch is the only
|
|
27
|
+
* code in the dependency tree that emits `@layer mui`. So every surface that
|
|
28
|
+
* builds its own cache with plain `createEmotionCache` is UNLAYERED, however
|
|
29
|
+
* carefully its options were copied.
|
|
30
|
+
*
|
|
31
|
+
* The besigner canvas was exactly that surface. Measured 2026-08-23 against
|
|
32
|
+
* rendered CSS on both halves:
|
|
33
|
+
*
|
|
34
|
+
* - published tenant (`northwind-coffee.aglyn.app`): 72 `@layer mui` blocks,
|
|
35
|
+
* ZERO unlayered `.mui-*` rules;
|
|
36
|
+
* - besigner canvas shadow root: ZERO layer blocks, 183 unlayered `.msd-*`
|
|
37
|
+
* rules.
|
|
38
|
+
*
|
|
39
|
+
* Component defaults and author `sx` share one cache, so they enter or skip
|
|
40
|
+
* the layer TOGETHER and their relative order survives either way — that part
|
|
41
|
+
* was never broken. What diverged is CSS that never passes through the
|
|
42
|
+
* emotion cache at all, principally a Custom HTML component's `css` attribute
|
|
43
|
+
* (`custom-html.tsx` renders it as a raw `<style>`). Against a fully layered
|
|
44
|
+
* document that CSS wins UNCONDITIONALLY, regardless of specificity, because
|
|
45
|
+
* an unlayered normal declaration beats every layered one. Against the
|
|
46
|
+
* unlayered canvas it merely competes on specificity. Measured on the same
|
|
47
|
+
* two surfaces with a rule of specificity 0-0-1 pitched against an emotion
|
|
48
|
+
* rule of 0-1-0: it won on the published page and lost on the canvas.
|
|
49
|
+
*
|
|
50
|
+
* That is a divergence in the product's central claim — "publish exactly what
|
|
51
|
+
* you designed" — so the canvas is layered to match the published document
|
|
52
|
+
* rather than the published document unlayered to match the canvas. Layering
|
|
53
|
+
* the tenant is what lets site and plugin CSS override MUI without a
|
|
54
|
+
* specificity war (see `emotion-cache.ts`); that behaviour is the intended
|
|
55
|
+
* one and it is the editor that has to agree with it.
|
|
56
|
+
*
|
|
57
|
+
* ## Notes for whoever touches this next
|
|
58
|
+
*
|
|
59
|
+
* - Cascade layers are scoped to a TREE CONTEXT. A `@layer mui` inside a
|
|
60
|
+
* shadow root is a different layer from the document's, with its own
|
|
61
|
+
* ordering, so reusing the name across the boundary is safe — and it has to
|
|
62
|
+
* be the same name anyway, since the point is to reproduce the published
|
|
63
|
+
* cascade exactly.
|
|
64
|
+
* - The `^@layer\s+[^{]*$` guard is MUI's, kept verbatim: a bare `@layer a, b;`
|
|
65
|
+
* ORDERING statement must not be wrapped in a layer block, or it stops
|
|
66
|
+
* declaring an order and starts declaring an empty nested layer.
|
|
67
|
+
* - `!important` inverts layer precedence (a layered important declaration
|
|
68
|
+
* beats an unlayered one). The canvas's `.aglyn-hidden` override in
|
|
69
|
+
* `viewport-frame.component.tsx` is an important declaration that goes
|
|
70
|
+
* through this cache, whereas the tenant ships its `.aglyn-hidden` rule from
|
|
71
|
+
* a non-emotion `<style>`. Those two were already deliberately different
|
|
72
|
+
* rules (AGL-592, the canvas reveals hidden panels while selected), so this
|
|
73
|
+
* does not make an agreeing pair disagree.
|
|
74
|
+
* - `cache.compat` is deliberately NOT set here. `AppRouterCacheProvider` sets
|
|
75
|
+
* it for its streaming-SSR `flush()` registry, which this client-only
|
|
76
|
+
* shadow-root cache has no equivalent of.
|
|
77
|
+
*/
|
|
78
|
+
export declare function createLayeredEmotionCache(options: CreateEmotionCacheOptions): EmotionCache;
|
|
@@ -0,0 +1,90 @@
|
|
|
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 { createEmotionCache } from "../../vendor/emotion.js";
|
|
17
|
+
import { MUI_CSS_LAYER_NAME } from "./emotion-cache.js";
|
|
18
|
+
/**
|
|
19
|
+
* An emotion cache whose output is wrapped in `@layer mui`, for surfaces that
|
|
20
|
+
* cannot use `AppRouterCacheProvider` (AGL-2486).
|
|
21
|
+
*
|
|
22
|
+
* ## Why this exists: "what you see is not what you publish"
|
|
23
|
+
*
|
|
24
|
+
* `enableCssLayer` is NOT an emotion option. It is implemented by
|
|
25
|
+
* `AppRouterCacheProvider` monkey-patching `cache.insert` to wrap the
|
|
26
|
+
* serialized styles string (`appRouterV13.mjs`), and that patch is the only
|
|
27
|
+
* code in the dependency tree that emits `@layer mui`. So every surface that
|
|
28
|
+
* builds its own cache with plain `createEmotionCache` is UNLAYERED, however
|
|
29
|
+
* carefully its options were copied.
|
|
30
|
+
*
|
|
31
|
+
* The besigner canvas was exactly that surface. Measured 2026-08-23 against
|
|
32
|
+
* rendered CSS on both halves:
|
|
33
|
+
*
|
|
34
|
+
* - published tenant (`northwind-coffee.aglyn.app`): 72 `@layer mui` blocks,
|
|
35
|
+
* ZERO unlayered `.mui-*` rules;
|
|
36
|
+
* - besigner canvas shadow root: ZERO layer blocks, 183 unlayered `.msd-*`
|
|
37
|
+
* rules.
|
|
38
|
+
*
|
|
39
|
+
* Component defaults and author `sx` share one cache, so they enter or skip
|
|
40
|
+
* the layer TOGETHER and their relative order survives either way — that part
|
|
41
|
+
* was never broken. What diverged is CSS that never passes through the
|
|
42
|
+
* emotion cache at all, principally a Custom HTML component's `css` attribute
|
|
43
|
+
* (`custom-html.tsx` renders it as a raw `<style>`). Against a fully layered
|
|
44
|
+
* document that CSS wins UNCONDITIONALLY, regardless of specificity, because
|
|
45
|
+
* an unlayered normal declaration beats every layered one. Against the
|
|
46
|
+
* unlayered canvas it merely competes on specificity. Measured on the same
|
|
47
|
+
* two surfaces with a rule of specificity 0-0-1 pitched against an emotion
|
|
48
|
+
* rule of 0-1-0: it won on the published page and lost on the canvas.
|
|
49
|
+
*
|
|
50
|
+
* That is a divergence in the product's central claim — "publish exactly what
|
|
51
|
+
* you designed" — so the canvas is layered to match the published document
|
|
52
|
+
* rather than the published document unlayered to match the canvas. Layering
|
|
53
|
+
* the tenant is what lets site and plugin CSS override MUI without a
|
|
54
|
+
* specificity war (see `emotion-cache.ts`); that behaviour is the intended
|
|
55
|
+
* one and it is the editor that has to agree with it.
|
|
56
|
+
*
|
|
57
|
+
* ## Notes for whoever touches this next
|
|
58
|
+
*
|
|
59
|
+
* - Cascade layers are scoped to a TREE CONTEXT. A `@layer mui` inside a
|
|
60
|
+
* shadow root is a different layer from the document's, with its own
|
|
61
|
+
* ordering, so reusing the name across the boundary is safe — and it has to
|
|
62
|
+
* be the same name anyway, since the point is to reproduce the published
|
|
63
|
+
* cascade exactly.
|
|
64
|
+
* - The `^@layer\s+[^{]*$` guard is MUI's, kept verbatim: a bare `@layer a, b;`
|
|
65
|
+
* ORDERING statement must not be wrapped in a layer block, or it stops
|
|
66
|
+
* declaring an order and starts declaring an empty nested layer.
|
|
67
|
+
* - `!important` inverts layer precedence (a layered important declaration
|
|
68
|
+
* beats an unlayered one). The canvas's `.aglyn-hidden` override in
|
|
69
|
+
* `viewport-frame.component.tsx` is an important declaration that goes
|
|
70
|
+
* through this cache, whereas the tenant ships its `.aglyn-hidden` rule from
|
|
71
|
+
* a non-emotion `<style>`. Those two were already deliberately different
|
|
72
|
+
* rules (AGL-592, the canvas reveals hidden panels while selected), so this
|
|
73
|
+
* does not make an agreeing pair disagree.
|
|
74
|
+
* - `cache.compat` is deliberately NOT set here. `AppRouterCacheProvider` sets
|
|
75
|
+
* it for its streaming-SSR `flush()` registry, which this client-only
|
|
76
|
+
* shadow-root cache has no equivalent of.
|
|
77
|
+
*/ export function createLayeredEmotionCache(options) {
|
|
78
|
+
const cache = createEmotionCache(options);
|
|
79
|
+
const prevInsert = cache.insert;
|
|
80
|
+
cache.insert = (...args)=>{
|
|
81
|
+
const serialized = args[1];
|
|
82
|
+
if (!serialized.styles.match(/^@layer\s+[^{]*$/)) {
|
|
83
|
+
serialized.styles = `@layer ${MUI_CSS_LAYER_NAME} {${serialized.styles}}`;
|
|
84
|
+
}
|
|
85
|
+
return prevInsert(...args);
|
|
86
|
+
};
|
|
87
|
+
return cache;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=layered-emotion-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/layered-emotion-cache.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 */\n\nimport {\n createEmotionCache,\n type CreateEmotionCacheOptions,\n type EmotionCache,\n} from '../../vendor/emotion'\nimport { MUI_CSS_LAYER_NAME } from './emotion-cache'\n\n/**\n * An emotion cache whose output is wrapped in `@layer mui`, for surfaces that\n * cannot use `AppRouterCacheProvider` (AGL-2486).\n *\n * ## Why this exists: \"what you see is not what you publish\"\n *\n * `enableCssLayer` is NOT an emotion option. It is implemented by\n * `AppRouterCacheProvider` monkey-patching `cache.insert` to wrap the\n * serialized styles string (`appRouterV13.mjs`), and that patch is the only\n * code in the dependency tree that emits `@layer mui`. So every surface that\n * builds its own cache with plain `createEmotionCache` is UNLAYERED, however\n * carefully its options were copied.\n *\n * The besigner canvas was exactly that surface. Measured 2026-08-23 against\n * rendered CSS on both halves:\n *\n * - published tenant (`northwind-coffee.aglyn.app`): 72 `@layer mui` blocks,\n * ZERO unlayered `.mui-*` rules;\n * - besigner canvas shadow root: ZERO layer blocks, 183 unlayered `.msd-*`\n * rules.\n *\n * Component defaults and author `sx` share one cache, so they enter or skip\n * the layer TOGETHER and their relative order survives either way — that part\n * was never broken. What diverged is CSS that never passes through the\n * emotion cache at all, principally a Custom HTML component's `css` attribute\n * (`custom-html.tsx` renders it as a raw `<style>`). Against a fully layered\n * document that CSS wins UNCONDITIONALLY, regardless of specificity, because\n * an unlayered normal declaration beats every layered one. Against the\n * unlayered canvas it merely competes on specificity. Measured on the same\n * two surfaces with a rule of specificity 0-0-1 pitched against an emotion\n * rule of 0-1-0: it won on the published page and lost on the canvas.\n *\n * That is a divergence in the product's central claim — \"publish exactly what\n * you designed\" — so the canvas is layered to match the published document\n * rather than the published document unlayered to match the canvas. Layering\n * the tenant is what lets site and plugin CSS override MUI without a\n * specificity war (see `emotion-cache.ts`); that behaviour is the intended\n * one and it is the editor that has to agree with it.\n *\n * ## Notes for whoever touches this next\n *\n * - Cascade layers are scoped to a TREE CONTEXT. A `@layer mui` inside a\n * shadow root is a different layer from the document's, with its own\n * ordering, so reusing the name across the boundary is safe — and it has to\n * be the same name anyway, since the point is to reproduce the published\n * cascade exactly.\n * - The `^@layer\\s+[^{]*$` guard is MUI's, kept verbatim: a bare `@layer a, b;`\n * ORDERING statement must not be wrapped in a layer block, or it stops\n * declaring an order and starts declaring an empty nested layer.\n * - `!important` inverts layer precedence (a layered important declaration\n * beats an unlayered one). The canvas's `.aglyn-hidden` override in\n * `viewport-frame.component.tsx` is an important declaration that goes\n * through this cache, whereas the tenant ships its `.aglyn-hidden` rule from\n * a non-emotion `<style>`. Those two were already deliberately different\n * rules (AGL-592, the canvas reveals hidden panels while selected), so this\n * does not make an agreeing pair disagree.\n * - `cache.compat` is deliberately NOT set here. `AppRouterCacheProvider` sets\n * it for its streaming-SSR `flush()` registry, which this client-only\n * shadow-root cache has no equivalent of.\n */\nexport function createLayeredEmotionCache(\n options: CreateEmotionCacheOptions,\n): EmotionCache {\n const cache = createEmotionCache(options)\n const prevInsert = cache.insert\n cache.insert = (...args: Parameters<EmotionCache['insert']>) => {\n const serialized = args[1]\n if (!serialized.styles.match(/^@layer\\s+[^{]*$/)) {\n serialized.styles = `@layer ${MUI_CSS_LAYER_NAME} {${serialized.styles}}`\n }\n return prevInsert(...args)\n }\n return cache\n}\n"],"names":["createEmotionCache","MUI_CSS_LAYER_NAME","createLayeredEmotionCache","options","cache","prevInsert","insert","args","serialized","styles","match"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,kBAAkB,QAGb,0BAAsB;AAC7B,SAASC,kBAAkB,QAAQ,qBAAiB;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2DC,GACD,OAAO,SAASC,0BACdC,OAAkC;IAElC,MAAMC,QAAQJ,mBAAmBG;IACjC,MAAME,aAAaD,MAAME,MAAM;IAC/BF,MAAME,MAAM,GAAG,CAAC,GAAGC;QACjB,MAAMC,aAAaD,IAAI,CAAC,EAAE;QAC1B,IAAI,CAACC,WAAWC,MAAM,CAACC,KAAK,CAAC,qBAAqB;YAChDF,WAAWC,MAAM,GAAG,CAAC,OAAO,EAAER,mBAAmB,EAAE,EAAEO,WAAWC,MAAM,CAAC,CAAC,CAAC;QAC3E;QACA,OAAOJ,cAAcE;IACvB;IACA,OAAOH;AACT"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 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 { SxProps } from '@mui/system';
|
|
18
|
+
import type { Theme as DefaultTheme } from '../../vendor/mui';
|
|
19
|
+
type MaybeSxProps<Theme extends object> = SxProps<Theme> | boolean | null | undefined;
|
|
20
|
+
export declare function useMergeSxProps<Theme extends DefaultTheme>(...sxProps: MaybeSxProps<Theme>[]): SxProps<Theme>;
|
|
21
|
+
export declare function mergeSxProps<Theme extends DefaultTheme>(...sxProps: MaybeSxProps<Theme>[]): SxProps<Theme>;
|
|
22
|
+
export default mergeSxProps;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 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 { useMemo } from "react";
|
|
17
|
+
export function useMergeSxProps(...sxProps) {
|
|
18
|
+
return useMemo(()=>mergeSxProps(...sxProps), [
|
|
19
|
+
sxProps
|
|
20
|
+
]);
|
|
21
|
+
}
|
|
22
|
+
export function mergeSxProps(...sxProps) {
|
|
23
|
+
return sxProps.flat(2);
|
|
24
|
+
}
|
|
25
|
+
export default mergeSxProps;
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=merge-sx-props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/merge-sx-props.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2023 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 */\n\nimport type { SxProps } from '@mui/system'\nimport { useMemo } from 'react'\nimport type { Theme as DefaultTheme } from '../../vendor/mui'\n\ntype MaybeSxProps<Theme extends object> = SxProps<Theme> | boolean | null | undefined\n\nexport function useMergeSxProps<Theme extends DefaultTheme>(\n ...sxProps: MaybeSxProps<Theme>[]\n): SxProps<Theme> {\n return useMemo(() => mergeSxProps(...sxProps), [sxProps])\n}\n\nexport function mergeSxProps<Theme extends DefaultTheme>(\n ...sxProps: MaybeSxProps<Theme>[]\n): SxProps<Theme> {\n return sxProps.flat(2) as SxProps<Theme>\n}\n\nexport default mergeSxProps\n"],"names":["useMemo","useMergeSxProps","sxProps","mergeSxProps","flat"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,OAAO,QAAQ,QAAO;AAK/B,OAAO,SAASC,gBACd,GAAGC,OAA8B;IAEjC,OAAOF,QAAQ,IAAMG,gBAAgBD,UAAU;QAACA;KAAQ;AAC1D;AAEA,OAAO,SAASC,aACd,GAAGD,OAA8B;IAEjC,OAAOA,QAAQE,IAAI,CAAC;AACtB;AAEA,eAAeD,aAAY"}
|