@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,117 @@
|
|
|
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 { decomposeColor, hslToRgb, rgbToHex } from "../../vendor/mui.js";
|
|
17
|
+
/** WCAG 2.x AA contrast bar for normal-size text. */ export const AA_TEXT_CONTRAST = 4.5;
|
|
18
|
+
function srgbChannelToLinear(channel) {
|
|
19
|
+
const value = channel / 255;
|
|
20
|
+
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Exact WCAG relative luminance. MUI's `getLuminance` truncates to three
|
|
24
|
+
* digits, which is enough slop to misclassify a borderline shade either side
|
|
25
|
+
* of the 4.5:1 bar — the whole reason this module exists — so the math here
|
|
26
|
+
* keeps full precision. Alpha is ignored (this pipeline derives opaque
|
|
27
|
+
* tokens), matching MUI's behaviour for rgba inputs.
|
|
28
|
+
*/ export function relativeLuminance(color) {
|
|
29
|
+
const decomposed = decomposeColor(color);
|
|
30
|
+
if (decomposed.type === 'color') {
|
|
31
|
+
// `color(display-p3 …)` values are not 0–255 channels; a caller that
|
|
32
|
+
// feeds these must handle its own contrast.
|
|
33
|
+
throw new Error(`accessible-shade: unsupported color format "${color}"`);
|
|
34
|
+
}
|
|
35
|
+
const values = decomposed.type.startsWith('hsl') ? decomposeColor(hslToRgb(color)).values : decomposed.values;
|
|
36
|
+
return 0.2126 * srgbChannelToLinear(values[0]) + 0.7152 * srgbChannelToLinear(values[1]) + 0.0722 * srgbChannelToLinear(values[2]);
|
|
37
|
+
}
|
|
38
|
+
/** Exact WCAG contrast ratio between two colors, 1–21. */ export function contrastRatio(colorA, colorB) {
|
|
39
|
+
const luminanceA = relativeLuminance(colorA);
|
|
40
|
+
const luminanceB = relativeLuminance(colorB);
|
|
41
|
+
return (Math.max(luminanceA, luminanceB) + 0.05) / (Math.min(luminanceA, luminanceB) + 0.05);
|
|
42
|
+
}
|
|
43
|
+
/** True when `foreground` clears `minContrast` against EVERY background. */ export function meetsContrast(foreground, backgrounds, minContrast = AA_TEXT_CONTRAST) {
|
|
44
|
+
return backgrounds.every((background)=>contrastRatio(foreground, background) >= minContrast);
|
|
45
|
+
}
|
|
46
|
+
/** [hue 0–360, saturation 0–1, lightness 0–1] */ function toHslValues(color) {
|
|
47
|
+
const decomposed = decomposeColor(color);
|
|
48
|
+
if (decomposed.type.startsWith('hsl')) {
|
|
49
|
+
const [h, s, l] = decomposed.values;
|
|
50
|
+
return [
|
|
51
|
+
h,
|
|
52
|
+
s / 100,
|
|
53
|
+
l / 100
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
const r = decomposed.values[0] / 255;
|
|
57
|
+
const g = decomposed.values[1] / 255;
|
|
58
|
+
const b = decomposed.values[2] / 255;
|
|
59
|
+
const max = Math.max(r, g, b);
|
|
60
|
+
const min = Math.min(r, g, b);
|
|
61
|
+
const lightness = (max + min) / 2;
|
|
62
|
+
if (max === min) return [
|
|
63
|
+
0,
|
|
64
|
+
0,
|
|
65
|
+
lightness
|
|
66
|
+
];
|
|
67
|
+
const delta = max - min;
|
|
68
|
+
const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
69
|
+
let hue;
|
|
70
|
+
if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0);
|
|
71
|
+
else if (max === g) hue = (b - r) / delta + 2;
|
|
72
|
+
else hue = (r - g) / delta + 4;
|
|
73
|
+
return [
|
|
74
|
+
hue * 60,
|
|
75
|
+
saturation,
|
|
76
|
+
lightness
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
function hslToHex(hue, saturation, lightness) {
|
|
80
|
+
return rgbToHex(hslToRgb(`hsl(${hue}, ${saturation * 100}%, ${lightness * 100}%)`));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Nearest shade of `color`, walking HSL lightness in `direction` only, that
|
|
84
|
+
* clears `minContrast` against every background (AGL-1297). Hue and
|
|
85
|
+
* saturation are held fixed so the result stays the same colour, just
|
|
86
|
+
* deeper or lighter.
|
|
87
|
+
*
|
|
88
|
+
* - If `color` already clears the bar, it is returned UNCHANGED —
|
|
89
|
+
* byte-identical, so a value that was fine yesterday stays byte-stable.
|
|
90
|
+
* - The walk is one-directional: pass `'darken'` for a foreground on light
|
|
91
|
+
* backgrounds, `'lighten'` for a foreground on dark backgrounds.
|
|
92
|
+
* - If the bar is unreachable in that direction (e.g. lightening against a
|
|
93
|
+
* mid-grey), the walk stops at the pole (black/white) or the iteration
|
|
94
|
+
* cap and returns that best-effort value rather than looping or
|
|
95
|
+
* reversing direction.
|
|
96
|
+
*/ export function accessibleShade(color, backgrounds, direction, options = {}) {
|
|
97
|
+
const { minContrast = AA_TEXT_CONTRAST, step = 0.01, maxSteps = 120 } = options;
|
|
98
|
+
if (!backgrounds.length || meetsContrast(color, backgrounds, minContrast)) {
|
|
99
|
+
return color;
|
|
100
|
+
}
|
|
101
|
+
const [hue, saturation, initialLightness] = toHslValues(color);
|
|
102
|
+
const delta = direction === 'lighten' ? step : -step;
|
|
103
|
+
let lightness = initialLightness;
|
|
104
|
+
let candidate = color;
|
|
105
|
+
for(let i = 0; i < maxSteps; i += 1){
|
|
106
|
+
lightness = Math.min(1, Math.max(0, lightness + delta));
|
|
107
|
+
candidate = hslToHex(hue, saturation, lightness);
|
|
108
|
+
if (meetsContrast(candidate, backgrounds, minContrast)) return candidate;
|
|
109
|
+
// Pole reached without clearing: nothing further exists in this
|
|
110
|
+
// direction, so return the extreme as the best effort.
|
|
111
|
+
if (lightness === 0 || lightness === 1) return candidate;
|
|
112
|
+
}
|
|
113
|
+
return candidate;
|
|
114
|
+
}
|
|
115
|
+
export default accessibleShade;
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=accessible-shade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/accessible-shade.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 { decomposeColor, hslToRgb, rgbToHex } from '../../vendor/mui'\n\n/** Ramp direction for a shade walk. */\nexport type ShadeDirection = 'darken' | 'lighten'\n\n/** WCAG 2.x AA contrast bar for normal-size text. */\nexport const AA_TEXT_CONTRAST = 4.5\n\nexport type AccessibleShadeOptions = {\n /** Contrast every background must clear. Default {@link AA_TEXT_CONTRAST}. */\n minContrast?: number\n /** HSL lightness moved per iteration (0–1 scale). Default 0.01. */\n step?: number\n /**\n * Iteration cap. The default covers the full lightness range at the\n * default step with headroom, so the walk always terminates at a pole\n * before hitting it in practice.\n */\n maxSteps?: number\n}\n\nfunction srgbChannelToLinear(channel: number): number {\n const value = channel / 255\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\n/**\n * Exact WCAG relative luminance. MUI's `getLuminance` truncates to three\n * digits, which is enough slop to misclassify a borderline shade either side\n * of the 4.5:1 bar — the whole reason this module exists — so the math here\n * keeps full precision. Alpha is ignored (this pipeline derives opaque\n * tokens), matching MUI's behaviour for rgba inputs.\n */\nexport function relativeLuminance(color: string): number {\n const decomposed = decomposeColor(color)\n if (decomposed.type === 'color') {\n // `color(display-p3 …)` values are not 0–255 channels; a caller that\n // feeds these must handle its own contrast.\n throw new Error(`accessible-shade: unsupported color format \"${color}\"`)\n }\n const values = decomposed.type.startsWith('hsl')\n ? decomposeColor(hslToRgb(color)).values\n : decomposed.values\n return (\n 0.2126 * srgbChannelToLinear(values[0]) +\n 0.7152 * srgbChannelToLinear(values[1]) +\n 0.0722 * srgbChannelToLinear(values[2])\n )\n}\n\n/** Exact WCAG contrast ratio between two colors, 1–21. */\nexport function contrastRatio(colorA: string, colorB: string): number {\n const luminanceA = relativeLuminance(colorA)\n const luminanceB = relativeLuminance(colorB)\n return (\n (Math.max(luminanceA, luminanceB) + 0.05) /\n (Math.min(luminanceA, luminanceB) + 0.05)\n )\n}\n\n/** True when `foreground` clears `minContrast` against EVERY background. */\nexport function meetsContrast(\n foreground: string,\n backgrounds: ReadonlyArray<string>,\n minContrast: number = AA_TEXT_CONTRAST,\n): boolean {\n return backgrounds.every(\n (background) => contrastRatio(foreground, background) >= minContrast,\n )\n}\n\n/** [hue 0–360, saturation 0–1, lightness 0–1] */\nfunction toHslValues(color: string): [number, number, number] {\n const decomposed = decomposeColor(color)\n if (decomposed.type.startsWith('hsl')) {\n const [h, s, l] = decomposed.values\n return [h, s / 100, l / 100]\n }\n const r = decomposed.values[0] / 255\n const g = decomposed.values[1] / 255\n const b = decomposed.values[2] / 255\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const lightness = (max + min) / 2\n if (max === min) return [0, 0, lightness]\n const delta = max - min\n const saturation =\n lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n let hue: number\n if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0)\n else if (max === g) hue = (b - r) / delta + 2\n else hue = (r - g) / delta + 4\n return [hue * 60, saturation, lightness]\n}\n\nfunction hslToHex(hue: number, saturation: number, lightness: number): string {\n return rgbToHex(\n hslToRgb(`hsl(${hue}, ${saturation * 100}%, ${lightness * 100}%)`),\n )\n}\n\n/**\n * Nearest shade of `color`, walking HSL lightness in `direction` only, that\n * clears `minContrast` against every background (AGL-1297). Hue and\n * saturation are held fixed so the result stays the same colour, just\n * deeper or lighter.\n *\n * - If `color` already clears the bar, it is returned UNCHANGED —\n * byte-identical, so a value that was fine yesterday stays byte-stable.\n * - The walk is one-directional: pass `'darken'` for a foreground on light\n * backgrounds, `'lighten'` for a foreground on dark backgrounds.\n * - If the bar is unreachable in that direction (e.g. lightening against a\n * mid-grey), the walk stops at the pole (black/white) or the iteration\n * cap and returns that best-effort value rather than looping or\n * reversing direction.\n */\nexport function accessibleShade(\n color: string,\n backgrounds: ReadonlyArray<string>,\n direction: ShadeDirection,\n options: AccessibleShadeOptions = {},\n): string {\n const {\n minContrast = AA_TEXT_CONTRAST,\n step = 0.01,\n maxSteps = 120,\n } = options\n if (!backgrounds.length || meetsContrast(color, backgrounds, minContrast)) {\n return color\n }\n const [hue, saturation, initialLightness] = toHslValues(color)\n const delta = direction === 'lighten' ? step : -step\n let lightness = initialLightness\n let candidate = color\n for (let i = 0; i < maxSteps; i += 1) {\n lightness = Math.min(1, Math.max(0, lightness + delta))\n candidate = hslToHex(hue, saturation, lightness)\n if (meetsContrast(candidate, backgrounds, minContrast)) return candidate\n // Pole reached without clearing: nothing further exists in this\n // direction, so return the extreme as the best effort.\n if (lightness === 0 || lightness === 1) return candidate\n }\n return candidate\n}\n\nexport default accessibleShade\n"],"names":["decomposeColor","hslToRgb","rgbToHex","AA_TEXT_CONTRAST","srgbChannelToLinear","channel","value","relativeLuminance","color","decomposed","type","Error","values","startsWith","contrastRatio","colorA","colorB","luminanceA","luminanceB","Math","max","min","meetsContrast","foreground","backgrounds","minContrast","every","background","toHslValues","h","s","l","r","g","b","lightness","delta","saturation","hue","hslToHex","accessibleShade","direction","options","step","maxSteps","length","initialLightness","candidate","i"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,cAAc,EAAEC,QAAQ,EAAEC,QAAQ,QAAQ,sBAAkB;AAKrE,mDAAmD,GACnD,OAAO,MAAMC,mBAAmB,IAAG;AAenC,SAASC,oBAAoBC,OAAe;IAC1C,MAAMC,QAAQD,UAAU;IACxB,OAAOC,SAAS,UAAUA,QAAQ,QAAQ,AAAC,CAAA,AAACA,CAAAA,QAAQ,KAAI,IAAK,KAAI,KAAM;AACzE;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,kBAAkBC,KAAa;IAC7C,MAAMC,aAAaT,eAAeQ;IAClC,IAAIC,WAAWC,IAAI,KAAK,SAAS;QAC/B,qEAAqE;QACrE,4CAA4C;QAC5C,MAAM,IAAIC,MAAM,CAAC,4CAA4C,EAAEH,MAAM,CAAC,CAAC;IACzE;IACA,MAAMI,SAASH,WAAWC,IAAI,CAACG,UAAU,CAAC,SACtCb,eAAeC,SAASO,QAAQI,MAAM,GACtCH,WAAWG,MAAM;IACrB,OACE,SAASR,oBAAoBQ,MAAM,CAAC,EAAE,IACtC,SAASR,oBAAoBQ,MAAM,CAAC,EAAE,IACtC,SAASR,oBAAoBQ,MAAM,CAAC,EAAE;AAE1C;AAEA,wDAAwD,GACxD,OAAO,SAASE,cAAcC,MAAc,EAAEC,MAAc;IAC1D,MAAMC,aAAaV,kBAAkBQ;IACrC,MAAMG,aAAaX,kBAAkBS;IACrC,OACE,AAACG,CAAAA,KAAKC,GAAG,CAACH,YAAYC,cAAc,IAAG,IACtCC,CAAAA,KAAKE,GAAG,CAACJ,YAAYC,cAAc,IAAG;AAE3C;AAEA,0EAA0E,GAC1E,OAAO,SAASI,cACdC,UAAkB,EAClBC,WAAkC,EAClCC,cAAsBtB,gBAAgB;IAEtC,OAAOqB,YAAYE,KAAK,CACtB,CAACC,aAAeb,cAAcS,YAAYI,eAAeF;AAE7D;AAEA,+CAA+C,GAC/C,SAASG,YAAYpB,KAAa;IAChC,MAAMC,aAAaT,eAAeQ;IAClC,IAAIC,WAAWC,IAAI,CAACG,UAAU,CAAC,QAAQ;QACrC,MAAM,CAACgB,GAAGC,GAAGC,EAAE,GAAGtB,WAAWG,MAAM;QACnC,OAAO;YAACiB;YAAGC,IAAI;YAAKC,IAAI;SAAI;IAC9B;IACA,MAAMC,IAAIvB,WAAWG,MAAM,CAAC,EAAE,GAAG;IACjC,MAAMqB,IAAIxB,WAAWG,MAAM,CAAC,EAAE,GAAG;IACjC,MAAMsB,IAAIzB,WAAWG,MAAM,CAAC,EAAE,GAAG;IACjC,MAAMQ,MAAMD,KAAKC,GAAG,CAACY,GAAGC,GAAGC;IAC3B,MAAMb,MAAMF,KAAKE,GAAG,CAACW,GAAGC,GAAGC;IAC3B,MAAMC,YAAY,AAACf,CAAAA,MAAMC,GAAE,IAAK;IAChC,IAAID,QAAQC,KAAK,OAAO;QAAC;QAAG;QAAGc;KAAU;IACzC,MAAMC,QAAQhB,MAAMC;IACpB,MAAMgB,aACJF,YAAY,MAAMC,QAAS,CAAA,IAAIhB,MAAMC,GAAE,IAAKe,QAAShB,CAAAA,MAAMC,GAAE;IAC/D,IAAIiB;IACJ,IAAIlB,QAAQY,GAAGM,MAAM,AAACL,CAAAA,IAAIC,CAAAA,IAAKE,QAASH,CAAAA,IAAIC,IAAI,IAAI,CAAA;SAC/C,IAAId,QAAQa,GAAGK,MAAM,AAACJ,CAAAA,IAAIF,CAAAA,IAAKI,QAAQ;SACvCE,MAAM,AAACN,CAAAA,IAAIC,CAAAA,IAAKG,QAAQ;IAC7B,OAAO;QAACE,MAAM;QAAID;QAAYF;KAAU;AAC1C;AAEA,SAASI,SAASD,GAAW,EAAED,UAAkB,EAAEF,SAAiB;IAClE,OAAOjC,SACLD,SAAS,CAAC,IAAI,EAAEqC,IAAI,EAAE,EAAED,aAAa,IAAI,GAAG,EAAEF,YAAY,IAAI,EAAE,CAAC;AAErE;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASK,gBACdhC,KAAa,EACbgB,WAAkC,EAClCiB,SAAyB,EACzBC,UAAkC,CAAC,CAAC;IAEpC,MAAM,EACJjB,cAActB,gBAAgB,EAC9BwC,OAAO,IAAI,EACXC,WAAW,GAAG,EACf,GAAGF;IACJ,IAAI,CAAClB,YAAYqB,MAAM,IAAIvB,cAAcd,OAAOgB,aAAaC,cAAc;QACzE,OAAOjB;IACT;IACA,MAAM,CAAC8B,KAAKD,YAAYS,iBAAiB,GAAGlB,YAAYpB;IACxD,MAAM4B,QAAQK,cAAc,YAAYE,OAAO,CAACA;IAChD,IAAIR,YAAYW;IAChB,IAAIC,YAAYvC;IAChB,IAAK,IAAIwC,IAAI,GAAGA,IAAIJ,UAAUI,KAAK,EAAG;QACpCb,YAAYhB,KAAKE,GAAG,CAAC,GAAGF,KAAKC,GAAG,CAAC,GAAGe,YAAYC;QAChDW,YAAYR,SAASD,KAAKD,YAAYF;QACtC,IAAIb,cAAcyB,WAAWvB,aAAaC,cAAc,OAAOsB;QAC/D,gEAAgE;QAChE,uDAAuD;QACvD,IAAIZ,cAAc,KAAKA,cAAc,GAAG,OAAOY;IACjD;IACA,OAAOA;AACT;AAEA,eAAeP,gBAAe"}
|
|
@@ -0,0 +1,68 @@
|
|
|
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 { ThemeMode } from '../hocs/create-with-theme-provider';
|
|
18
|
+
/**
|
|
19
|
+
* The device's own light/dark preference, carried on the request.
|
|
20
|
+
*
|
|
21
|
+
* `prefers-color-scheme` is a media feature, and a media feature is a browser
|
|
22
|
+
* fact: a server render has no viewport to evaluate it against, so
|
|
23
|
+
* `useMediaQuery` reports light for every visitor whose device is dark. This
|
|
24
|
+
* user-agent client hint is the same preference in a form the server CAN read
|
|
25
|
+
* — the browser puts it on the request, so a visitor following their device
|
|
26
|
+
* gets their scheme in the first byte rather than a light document that
|
|
27
|
+
* repaints once React has hydrated the whole page.
|
|
28
|
+
*
|
|
29
|
+
* A browser sends it only where the origin asked for it, and the tenant
|
|
30
|
+
* middleware is what asks — it advertises this same token in `Accept-CH` and
|
|
31
|
+
* `Critical-CH` and splits its cache on it in `Vary`, from a copy of its own,
|
|
32
|
+
* since an edge bundle takes no library imports. Reading the request header by
|
|
33
|
+
* this constant is safe whatever the transport did to the casing: `Headers`
|
|
34
|
+
* matches case-insensitively.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ CHROMIUM ONLY. Firefox and Safari implement neither this hint nor the
|
|
37
|
+
* `Accept-CH` negotiation that asks for it, so on those browsers the header is
|
|
38
|
+
* simply absent and the scheme settles once the page has hydrated. Absence is
|
|
39
|
+
* therefore an ordinary answer, never an error: every reader must have a
|
|
40
|
+
* behavior for "the request did not say".
|
|
41
|
+
*
|
|
42
|
+
* Nothing in this module imports React, MUI or `js-cookie`, and its one import
|
|
43
|
+
* is a type erased at compile time. That is what lets a Server Component take
|
|
44
|
+
* it without pulling the theme library's context providers into the RSC graph
|
|
45
|
+
* — the same reason `util/theme-mode-cookie`, `util/emotion-cache` and
|
|
46
|
+
* `util/host-theme` are deep-imported rather than taken from the barrel.
|
|
47
|
+
*/
|
|
48
|
+
export declare const COLOR_SCHEME_HINT_HEADER = "Sec-CH-Prefers-Color-Scheme";
|
|
49
|
+
/**
|
|
50
|
+
* A `Sec-CH-Prefers-Color-Scheme` request-header value as a theme mode.
|
|
51
|
+
*
|
|
52
|
+
* Only `light` and `dark` are schemes; anything else — a header the browser
|
|
53
|
+
* never sent, a value from a future revision of the media feature — is `null`,
|
|
54
|
+
* meaning "the request named no scheme, fall back to the browser's own answer
|
|
55
|
+
* once there is one". A caller that mistook an unknown value for light would
|
|
56
|
+
* pin a dark device to the wrong scheme for the whole render.
|
|
57
|
+
*
|
|
58
|
+
* The value is a structured-field string, so it may arrive quoted (`"dark"`);
|
|
59
|
+
* the quotes are stripped and the token is compared case-insensitively so one
|
|
60
|
+
* parser reads every spelling a conforming user agent may send.
|
|
61
|
+
*
|
|
62
|
+
* This is the device-preference counterpart to `parseThemeModeCookie`, which
|
|
63
|
+
* reads the visitor's EXPLICIT choice off the same request. The two are
|
|
64
|
+
* separate answers on purpose and are never merged here: a stated preference
|
|
65
|
+
* outranks a device default, and the layering that enforces that lives in
|
|
66
|
+
* `useThemeModeState`, where the switcher can still tell them apart.
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseColorSchemeHint(value: string | undefined | null): ThemeMode;
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
* The device's own light/dark preference, carried on the request.
|
|
18
|
+
*
|
|
19
|
+
* `prefers-color-scheme` is a media feature, and a media feature is a browser
|
|
20
|
+
* fact: a server render has no viewport to evaluate it against, so
|
|
21
|
+
* `useMediaQuery` reports light for every visitor whose device is dark. This
|
|
22
|
+
* user-agent client hint is the same preference in a form the server CAN read
|
|
23
|
+
* — the browser puts it on the request, so a visitor following their device
|
|
24
|
+
* gets their scheme in the first byte rather than a light document that
|
|
25
|
+
* repaints once React has hydrated the whole page.
|
|
26
|
+
*
|
|
27
|
+
* A browser sends it only where the origin asked for it, and the tenant
|
|
28
|
+
* middleware is what asks — it advertises this same token in `Accept-CH` and
|
|
29
|
+
* `Critical-CH` and splits its cache on it in `Vary`, from a copy of its own,
|
|
30
|
+
* since an edge bundle takes no library imports. Reading the request header by
|
|
31
|
+
* this constant is safe whatever the transport did to the casing: `Headers`
|
|
32
|
+
* matches case-insensitively.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ CHROMIUM ONLY. Firefox and Safari implement neither this hint nor the
|
|
35
|
+
* `Accept-CH` negotiation that asks for it, so on those browsers the header is
|
|
36
|
+
* simply absent and the scheme settles once the page has hydrated. Absence is
|
|
37
|
+
* therefore an ordinary answer, never an error: every reader must have a
|
|
38
|
+
* behavior for "the request did not say".
|
|
39
|
+
*
|
|
40
|
+
* Nothing in this module imports React, MUI or `js-cookie`, and its one import
|
|
41
|
+
* is a type erased at compile time. That is what lets a Server Component take
|
|
42
|
+
* it without pulling the theme library's context providers into the RSC graph
|
|
43
|
+
* — the same reason `util/theme-mode-cookie`, `util/emotion-cache` and
|
|
44
|
+
* `util/host-theme` are deep-imported rather than taken from the barrel.
|
|
45
|
+
*/ export const COLOR_SCHEME_HINT_HEADER = 'Sec-CH-Prefers-Color-Scheme';
|
|
46
|
+
/**
|
|
47
|
+
* A `Sec-CH-Prefers-Color-Scheme` request-header value as a theme mode.
|
|
48
|
+
*
|
|
49
|
+
* Only `light` and `dark` are schemes; anything else — a header the browser
|
|
50
|
+
* never sent, a value from a future revision of the media feature — is `null`,
|
|
51
|
+
* meaning "the request named no scheme, fall back to the browser's own answer
|
|
52
|
+
* once there is one". A caller that mistook an unknown value for light would
|
|
53
|
+
* pin a dark device to the wrong scheme for the whole render.
|
|
54
|
+
*
|
|
55
|
+
* The value is a structured-field string, so it may arrive quoted (`"dark"`);
|
|
56
|
+
* the quotes are stripped and the token is compared case-insensitively so one
|
|
57
|
+
* parser reads every spelling a conforming user agent may send.
|
|
58
|
+
*
|
|
59
|
+
* This is the device-preference counterpart to `parseThemeModeCookie`, which
|
|
60
|
+
* reads the visitor's EXPLICIT choice off the same request. The two are
|
|
61
|
+
* separate answers on purpose and are never merged here: a stated preference
|
|
62
|
+
* outranks a device default, and the layering that enforces that lives in
|
|
63
|
+
* `useThemeModeState`, where the switcher can still tell them apart.
|
|
64
|
+
*/ export function parseColorSchemeHint(value) {
|
|
65
|
+
const scheme = value == null ? void 0 : value.trim().replace(/^"|"$/g, '').toLowerCase();
|
|
66
|
+
return scheme === 'dark' || scheme === 'light' ? scheme : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=color-scheme-hint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/theme/src/lib/util/color-scheme-hint.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 { ThemeMode } from '../hocs/create-with-theme-provider'\n\n/**\n * The device's own light/dark preference, carried on the request.\n *\n * `prefers-color-scheme` is a media feature, and a media feature is a browser\n * fact: a server render has no viewport to evaluate it against, so\n * `useMediaQuery` reports light for every visitor whose device is dark. This\n * user-agent client hint is the same preference in a form the server CAN read\n * — the browser puts it on the request, so a visitor following their device\n * gets their scheme in the first byte rather than a light document that\n * repaints once React has hydrated the whole page.\n *\n * A browser sends it only where the origin asked for it, and the tenant\n * middleware is what asks — it advertises this same token in `Accept-CH` and\n * `Critical-CH` and splits its cache on it in `Vary`, from a copy of its own,\n * since an edge bundle takes no library imports. Reading the request header by\n * this constant is safe whatever the transport did to the casing: `Headers`\n * matches case-insensitively.\n *\n * ⚠️ CHROMIUM ONLY. Firefox and Safari implement neither this hint nor the\n * `Accept-CH` negotiation that asks for it, so on those browsers the header is\n * simply absent and the scheme settles once the page has hydrated. Absence is\n * therefore an ordinary answer, never an error: every reader must have a\n * behavior for \"the request did not say\".\n *\n * Nothing in this module imports React, MUI or `js-cookie`, and its one import\n * is a type erased at compile time. That is what lets a Server Component take\n * it without pulling the theme library's context providers into the RSC graph\n * — the same reason `util/theme-mode-cookie`, `util/emotion-cache` and\n * `util/host-theme` are deep-imported rather than taken from the barrel.\n */\nexport const COLOR_SCHEME_HINT_HEADER = 'Sec-CH-Prefers-Color-Scheme'\n\n/**\n * A `Sec-CH-Prefers-Color-Scheme` request-header value as a theme mode.\n *\n * Only `light` and `dark` are schemes; anything else — a header the browser\n * never sent, a value from a future revision of the media feature — is `null`,\n * meaning \"the request named no scheme, fall back to the browser's own answer\n * once there is one\". A caller that mistook an unknown value for light would\n * pin a dark device to the wrong scheme for the whole render.\n *\n * The value is a structured-field string, so it may arrive quoted (`\"dark\"`);\n * the quotes are stripped and the token is compared case-insensitively so one\n * parser reads every spelling a conforming user agent may send.\n *\n * This is the device-preference counterpart to `parseThemeModeCookie`, which\n * reads the visitor's EXPLICIT choice off the same request. The two are\n * separate answers on purpose and are never merged here: a stated preference\n * outranks a device default, and the layering that enforces that lives in\n * `useThemeModeState`, where the switcher can still tell them apart.\n */\nexport function parseColorSchemeHint(\n value: string | undefined | null,\n): ThemeMode {\n const scheme = value?.trim().replace(/^\"|\"$/g, '').toLowerCase()\n return scheme === 'dark' || scheme === 'light' ? scheme : null\n}\n"],"names":["COLOR_SCHEME_HINT_HEADER","parseColorSchemeHint","value","scheme","trim","replace","toLowerCase"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,MAAMA,2BAA2B,8BAA6B;AAErE;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,qBACdC,KAAgC;IAEhC,MAAMC,SAASD,yBAAAA,MAAOE,IAAI,GAAGC,OAAO,CAAC,UAAU,IAAIC,WAAW;IAC9D,OAAOH,WAAW,UAAUA,WAAW,UAAUA,SAAS;AAC5D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2024 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 { CssVarsThemeOptions } from '@mui/material/styles';
|
|
18
|
+
import { responsiveFontSizes, type Theme, type ThemeOptions } from '../../vendor/mui';
|
|
19
|
+
export type CreateResponsiveThemeOptions = {
|
|
20
|
+
themeOptions?: ThemeOptions;
|
|
21
|
+
responsiveFontSizesOptions?: Parameters<typeof responsiveFontSizes>[1];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* START EXAMPLE – OVERRIDE DEFAULT PROPS ↓
|
|
25
|
+
* ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const theme = createMuiTheme({
|
|
28
|
+
* props: {
|
|
29
|
+
* // Name of the component ⚛️
|
|
30
|
+
* MuiButtonBase: {
|
|
31
|
+
* // The default props to change
|
|
32
|
+
* disableRipple: true, // No more ripple, on the whole application 💣!
|
|
33
|
+
* },
|
|
34
|
+
* },
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
38
|
+
* END EXAMPLE – OVERRIDE DEFAULT PROPS ↑
|
|
39
|
+
*
|
|
40
|
+
* START EXAMPLE – OVERRIDE DEFAULT STYLES ↓
|
|
41
|
+
* ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const theme = createMuiTheme({
|
|
44
|
+
* overrides: {
|
|
45
|
+
* // Style sheet name ⚛️
|
|
46
|
+
* MuiButton: {
|
|
47
|
+
* // Name of the rule
|
|
48
|
+
* text: {
|
|
49
|
+
* // Some CSS
|
|
50
|
+
* color: 'white',
|
|
51
|
+
* },
|
|
52
|
+
* },
|
|
53
|
+
* MuiCssBaseline: {
|
|
54
|
+
* '@global': {
|
|
55
|
+
* html: {
|
|
56
|
+
* WebkitFontSmoothing: 'auto',
|
|
57
|
+
* },
|
|
58
|
+
* },
|
|
59
|
+
* },
|
|
60
|
+
* },
|
|
61
|
+
* })
|
|
62
|
+
* ```
|
|
63
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
64
|
+
* END EXAMPLE – OVERRIDE DEFAULT STYLES ↑
|
|
65
|
+
*
|
|
66
|
+
* @param {ThemeOptions} options
|
|
67
|
+
* @returns {Theme}
|
|
68
|
+
*/
|
|
69
|
+
export declare function createResponsiveTheme(options: CreateResponsiveThemeOptions): Theme;
|
|
70
|
+
export declare function createResponsiveCssVarTheme(light: Theme, dark: Theme, options?: CssVarsThemeOptions): Omit<Theme, "applyStyles"> & import("@mui/material").CssVarsTheme;
|
|
71
|
+
export default createResponsiveTheme;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2024 Aglyn LLC
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/ import { extendTheme as muiExtendTheme } from "@mui/material/styles";
|
|
19
|
+
import { createTheme, darken, getContrastRatio, lighten, responsiveFontSizes } from "../../vendor/mui.js";
|
|
20
|
+
import { accessibleShade, contrastRatio, meetsContrast } from "./accessible-shade.js";
|
|
21
|
+
var ContrastText = /*#__PURE__*/ function(ContrastText) {
|
|
22
|
+
ContrastText["LIGHT"] = "rgba(0, 0, 0, 0.87)";
|
|
23
|
+
ContrastText["DARK"] = "#FFFFFF";
|
|
24
|
+
return ContrastText;
|
|
25
|
+
}(ContrastText || {});
|
|
26
|
+
function resolveTonalOffsets(tonalOffset) {
|
|
27
|
+
var _ref, _ref1, _ref2;
|
|
28
|
+
const offsetObj = typeof tonalOffset === 'number' ? undefined : tonalOffset;
|
|
29
|
+
const offsetNum = typeof tonalOffset === 'number' ? tonalOffset : undefined;
|
|
30
|
+
return {
|
|
31
|
+
light: (_ref = (_ref1 = offsetObj == null ? void 0 : offsetObj.light) != null ? _ref1 : offsetNum) != null ? _ref : 0.2,
|
|
32
|
+
dark: (_ref2 = offsetObj == null ? void 0 : offsetObj.dark) != null ? _ref2 : (offsetNum != null ? offsetNum : 0.2) * 1.5
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function getContrastTextColor(background, contrastThreshold) {
|
|
36
|
+
return getContrastRatio(background, "#FFFFFF") >= (contrastThreshold != null ? contrastThreshold : 3) ? "#FFFFFF" : "rgba(0, 0, 0, 0.87)";
|
|
37
|
+
}
|
|
38
|
+
function addShade(paletteColor, shade, variant, tonalOffset) {
|
|
39
|
+
// Custom palette colors (tertiary, surface) are optional in host themes.
|
|
40
|
+
if (!(paletteColor == null ? void 0 : paletteColor.main)) return;
|
|
41
|
+
const { light: tonalOffsetLight, dark: tonalOffsetDark } = resolveTonalOffsets(tonalOffset);
|
|
42
|
+
const indexed = paletteColor;
|
|
43
|
+
if (!indexed[shade]) {
|
|
44
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
45
|
+
if (paletteColor.hasOwnProperty(variant)) {
|
|
46
|
+
indexed[shade] = indexed[variant];
|
|
47
|
+
} else if (shade === 'light') {
|
|
48
|
+
paletteColor.light = lighten(paletteColor.main, tonalOffsetLight);
|
|
49
|
+
} else if (shade === 'dark') {
|
|
50
|
+
paletteColor.dark = darken(paletteColor.main, tonalOffsetDark);
|
|
51
|
+
} else if (shade === 'contrastText') {
|
|
52
|
+
paletteColor.contrastText = getContrastTextColor(paletteColor.main, tonalOffsetDark);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function addShadeVariants(paletteColor, tonalOffset) {
|
|
57
|
+
addShade(paletteColor, 'dark', undefined, tonalOffset);
|
|
58
|
+
addShade(paletteColor, 'light', undefined, tonalOffset);
|
|
59
|
+
addShade(paletteColor, 'contrastText', undefined, tonalOffset);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Colour families whose shades act as FOREGROUNDS — the `dark` slot carries
|
|
63
|
+
* "accessible accent text" (AGL-1293 put the marketing brand blue there), so
|
|
64
|
+
* its derivation must clear AA against the scheme's real backgrounds.
|
|
65
|
+
*
|
|
66
|
+
* `surface` is deliberately absent: its `light`/`dark` are surface STEPS
|
|
67
|
+
* (panel fills, borders — see the designer's box styler), and walking them to
|
|
68
|
+
* text contrast would repaint layout chrome, not fix any text.
|
|
69
|
+
*/ const FOREGROUND_COLOR_KEYS = [
|
|
70
|
+
'primary',
|
|
71
|
+
'secondary',
|
|
72
|
+
'tertiary',
|
|
73
|
+
'error',
|
|
74
|
+
'warning',
|
|
75
|
+
'info',
|
|
76
|
+
'success'
|
|
77
|
+
];
|
|
78
|
+
/** `contrastText` IS a text pairing wherever it lives, so `surface` joins here. */ const CONTRAST_TEXT_COLOR_KEYS = [
|
|
79
|
+
...FOREGROUND_COLOR_KEYS,
|
|
80
|
+
'surface'
|
|
81
|
+
];
|
|
82
|
+
/**
|
|
83
|
+
* Post-`createTheme` pass making DERIVED shades scheme- and AA-aware
|
|
84
|
+
* (AGL-1297). Fixed tonal offsets derive `dark = darken(main, 0.3)` no
|
|
85
|
+
* matter the scheme, which (a) misses 4.5:1 on real off-white surfaces and
|
|
86
|
+
* (b) points the wrong way entirely in dark mode, where the accessible
|
|
87
|
+
* direction for a foreground shade is LIGHTER.
|
|
88
|
+
*
|
|
89
|
+
* Invariants:
|
|
90
|
+
* - Only slots ABSENT from the caller's `palette` input are ever touched;
|
|
91
|
+
* an explicitly authored shade passes through byte-identical (the
|
|
92
|
+
* marketing host's hand-set `primary.dark` values depend on this).
|
|
93
|
+
* - A derived value that already clears 4.5:1 against both
|
|
94
|
+
* `background.default` and `background.paper` is kept byte-identical, so
|
|
95
|
+
* themes that were fine yesterday do not shift.
|
|
96
|
+
* - A failing derived `dark` is re-seeded at the tonal offset from `main`
|
|
97
|
+
* in the scheme's foreground direction (darken in light, lighten in
|
|
98
|
+
* dark), then walked in HSL lightness until it clears the bar.
|
|
99
|
+
* - A light-scheme `light` is a background TINT — its ramp direction points
|
|
100
|
+
* away from the text bar by design — so it keeps the tonal derivation.
|
|
101
|
+
* In dark schemes `light` is foreground-capable and gets the same walk.
|
|
102
|
+
* - Derived `contrastText` keeps the threshold choice unless that pairing
|
|
103
|
+
* is below 4.5:1 against `main`; then it walks toward the pole with more
|
|
104
|
+
* contrast headroom.
|
|
105
|
+
*/ function ensureAccessibleShades(theme, inputPalette) {
|
|
106
|
+
var _palette_background, _palette_background1;
|
|
107
|
+
const { palette } = theme;
|
|
108
|
+
const backgrounds = [
|
|
109
|
+
(_palette_background = palette.background) == null ? void 0 : _palette_background.default,
|
|
110
|
+
(_palette_background1 = palette.background) == null ? void 0 : _palette_background1.paper
|
|
111
|
+
].filter((background)=>typeof background === 'string');
|
|
112
|
+
if (!backgrounds.length) return;
|
|
113
|
+
const mode = palette.mode === 'dark' ? 'dark' : 'light';
|
|
114
|
+
const foregroundDirection = mode === 'dark' ? 'lighten' : 'darken';
|
|
115
|
+
const tonalOffsetDark = resolveTonalOffsets(palette.tonalOffset).dark;
|
|
116
|
+
const providedColors = inputPalette;
|
|
117
|
+
for (const key of CONTRAST_TEXT_COLOR_KEYS){
|
|
118
|
+
const color = palette[key];
|
|
119
|
+
if (!color || typeof color.main !== 'string') continue;
|
|
120
|
+
const provided = providedColors == null ? void 0 : providedColors[key];
|
|
121
|
+
// A ColorPartial ramp (`{ 50…900 }`) is an author-owned scale — MUI maps
|
|
122
|
+
// its shades straight across, so nothing in it counts as derived.
|
|
123
|
+
if (provided && typeof provided === 'object' && !('main' in provided)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Read with brackets, not dots: these keys come from an index signature,
|
|
127
|
+
// and `json-editor` compiles this file under
|
|
128
|
+
// `noPropertyAccessFromIndexSignature` (paths resolve to source, so a
|
|
129
|
+
// consumer's flags check its dependencies). Dot access reds that build
|
|
130
|
+
// while this project's own stays green — AGL-1323.
|
|
131
|
+
const explicit = provided != null ? provided : {};
|
|
132
|
+
const isForeground = FOREGROUND_COLOR_KEYS.includes(key);
|
|
133
|
+
try {
|
|
134
|
+
if (isForeground && !explicit['dark'] && typeof color.dark === 'string' && !meetsContrast(color.dark, backgrounds)) {
|
|
135
|
+
const seed = mode === 'dark' ? lighten(color.main, tonalOffsetDark) : color.dark;
|
|
136
|
+
color.dark = accessibleShade(seed, backgrounds, foregroundDirection);
|
|
137
|
+
}
|
|
138
|
+
if (isForeground && mode === 'dark' && !explicit['light'] && typeof color.light === 'string' && !meetsContrast(color.light, backgrounds)) {
|
|
139
|
+
color.light = accessibleShade(color.light, backgrounds, 'lighten');
|
|
140
|
+
}
|
|
141
|
+
if (!explicit['contrastText'] && typeof color.contrastText === 'string' && !meetsContrast(color.contrastText, [
|
|
142
|
+
color.main
|
|
143
|
+
])) {
|
|
144
|
+
const textDirection = contrastRatio('#fff', color.main) >= contrastRatio('#000', color.main) ? 'lighten' : 'darken';
|
|
145
|
+
color.contrastText = accessibleShade(color.contrastText, [
|
|
146
|
+
color.main
|
|
147
|
+
], textDirection);
|
|
148
|
+
}
|
|
149
|
+
} catch (unused) {
|
|
150
|
+
// Unparseable colour (CSS variable, color-mix()): leave the derived
|
|
151
|
+
// value alone rather than failing theme creation.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* MUI's own default `responsiveFontSizes` variant list, spelled out because
|
|
157
|
+
* passing `variants` replaces it rather than extending it.
|
|
158
|
+
*/ const RAMPED_MUI_VARIANTS = [
|
|
159
|
+
'h1',
|
|
160
|
+
'h2',
|
|
161
|
+
'h3',
|
|
162
|
+
'h4',
|
|
163
|
+
'h5',
|
|
164
|
+
'h6',
|
|
165
|
+
'subtitle1',
|
|
166
|
+
'subtitle2',
|
|
167
|
+
'body1',
|
|
168
|
+
'body2',
|
|
169
|
+
'caption',
|
|
170
|
+
'button',
|
|
171
|
+
'overline'
|
|
172
|
+
];
|
|
173
|
+
/**
|
|
174
|
+
* The brand's custom display rungs, which MUI cannot know about.
|
|
175
|
+
*
|
|
176
|
+
* Only variants whose size genuinely needs to shrink belong here. `lede`,
|
|
177
|
+
* `bodyCompact` and `micro` are body-scale text that reads the same at every
|
|
178
|
+
* width and are deliberately absent.
|
|
179
|
+
*/ const RAMPED_DISPLAY_VARIANTS = [
|
|
180
|
+
'displayXl'
|
|
181
|
+
];
|
|
182
|
+
/**
|
|
183
|
+
* START EXAMPLE – OVERRIDE DEFAULT PROPS ↓
|
|
184
|
+
* ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
|
|
185
|
+
* ```typescript
|
|
186
|
+
* const theme = createMuiTheme({
|
|
187
|
+
* props: {
|
|
188
|
+
* // Name of the component ⚛️
|
|
189
|
+
* MuiButtonBase: {
|
|
190
|
+
* // The default props to change
|
|
191
|
+
* disableRipple: true, // No more ripple, on the whole application 💣!
|
|
192
|
+
* },
|
|
193
|
+
* },
|
|
194
|
+
* })
|
|
195
|
+
* ```
|
|
196
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
197
|
+
* END EXAMPLE – OVERRIDE DEFAULT PROPS ↑
|
|
198
|
+
*
|
|
199
|
+
* START EXAMPLE – OVERRIDE DEFAULT STYLES ↓
|
|
200
|
+
* ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const theme = createMuiTheme({
|
|
203
|
+
* overrides: {
|
|
204
|
+
* // Style sheet name ⚛️
|
|
205
|
+
* MuiButton: {
|
|
206
|
+
* // Name of the rule
|
|
207
|
+
* text: {
|
|
208
|
+
* // Some CSS
|
|
209
|
+
* color: 'white',
|
|
210
|
+
* },
|
|
211
|
+
* },
|
|
212
|
+
* MuiCssBaseline: {
|
|
213
|
+
* '@global': {
|
|
214
|
+
* html: {
|
|
215
|
+
* WebkitFontSmoothing: 'auto',
|
|
216
|
+
* },
|
|
217
|
+
* },
|
|
218
|
+
* },
|
|
219
|
+
* },
|
|
220
|
+
* })
|
|
221
|
+
* ```
|
|
222
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
223
|
+
* END EXAMPLE – OVERRIDE DEFAULT STYLES ↑
|
|
224
|
+
*
|
|
225
|
+
* @param {ThemeOptions} options
|
|
226
|
+
* @returns {Theme}
|
|
227
|
+
*/ export function createResponsiveTheme(options) {
|
|
228
|
+
const { themeOptions, responsiveFontSizesOptions } = options;
|
|
229
|
+
let theme = createTheme(themeOptions);
|
|
230
|
+
addShadeVariants(theme.palette.tertiary, theme.palette.tonalOffset);
|
|
231
|
+
addShadeVariants(theme.palette.surface, theme.palette.tonalOffset);
|
|
232
|
+
// `tint` is deliberately absent from BOTH shade passes (AGL-1244). It is a
|
|
233
|
+
// group of string leaves rather than a PaletteColor — no `main` to ramp
|
|
234
|
+
// from, no `contrastText` to pair — so `addShadeVariants` would no-op and
|
|
235
|
+
// `ensureAccessibleShades` (which keys off `CONTRAST_TEXT_COLOR_KEYS`) never
|
|
236
|
+
// sees it. Both correct: a 90%-desaturated wash is authored, not derived,
|
|
237
|
+
// and walking it to a text-contrast bar would repaint the fill.
|
|
238
|
+
// Scheme- and AA-aware repair of DERIVED shades only (AGL-1297); explicit
|
|
239
|
+
// palette values pass through byte-identical.
|
|
240
|
+
ensureAccessibleShades(theme, themeOptions == null ? void 0 : themeOptions.palette);
|
|
241
|
+
theme = responsiveFontSizes(theme, _extends({
|
|
242
|
+
// Override to include `xs` and `xl` - default: ['sm', 'md', 'lg']
|
|
243
|
+
breakpoints: [
|
|
244
|
+
'xs',
|
|
245
|
+
'sm',
|
|
246
|
+
'md',
|
|
247
|
+
'lg',
|
|
248
|
+
'xl'
|
|
249
|
+
],
|
|
250
|
+
// MUI only ramps the variants it ships, so a CUSTOM display variant would
|
|
251
|
+
// keep its desktop size on a phone — `displayXl` at 72px on a 375px
|
|
252
|
+
// screen. Passing `variants` REPLACES MUI's default list, so the defaults
|
|
253
|
+
// are spelled out here and ours appended; drop one and that variant
|
|
254
|
+
// silently stops scaling.
|
|
255
|
+
variants: [
|
|
256
|
+
...RAMPED_MUI_VARIANTS,
|
|
257
|
+
...RAMPED_DISPLAY_VARIANTS
|
|
258
|
+
]
|
|
259
|
+
}, responsiveFontSizesOptions));
|
|
260
|
+
return theme;
|
|
261
|
+
}
|
|
262
|
+
export function createResponsiveCssVarTheme(light, dark, options) {
|
|
263
|
+
var _ref, _ref1;
|
|
264
|
+
var _options_colorSchemes, _options_colorSchemes1;
|
|
265
|
+
const { palette: lightPalette } = light, lightTheme = _object_without_properties_loose(light, [
|
|
266
|
+
"palette"
|
|
267
|
+
]);
|
|
268
|
+
const { palette: darkPalette } = dark;
|
|
269
|
+
return muiExtendTheme(_extends({}, lightTheme, {
|
|
270
|
+
// Allow setMode() to work by driving the color scheme via a CSS class on
|
|
271
|
+
// <html> instead of the OS-level @media query (the default 'media' selector
|
|
272
|
+
// makes setMode() a no-op because media queries can't be overridden in JS).
|
|
273
|
+
colorSchemeSelector: 'class'
|
|
274
|
+
}, options, {
|
|
275
|
+
colorSchemes: _extends({}, options == null ? void 0 : options.colorSchemes, {
|
|
276
|
+
light: _extends({
|
|
277
|
+
palette: lightPalette
|
|
278
|
+
}, (_ref = options == null ? void 0 : (_options_colorSchemes = options.colorSchemes) == null ? void 0 : _options_colorSchemes.light) != null ? _ref : {}),
|
|
279
|
+
dark: _extends({
|
|
280
|
+
palette: darkPalette
|
|
281
|
+
}, (_ref1 = options == null ? void 0 : (_options_colorSchemes1 = options.colorSchemes) == null ? void 0 : _options_colorSchemes1.dark) != null ? _ref1 : {})
|
|
282
|
+
})
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
export default createResponsiveTheme;
|
|
286
|
+
|
|
287
|
+
//# sourceMappingURL=create-responsive-theme.js.map
|