@exmg/exm-theme 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/index.d.ts +18 -0
- package/index.js +19 -0
- package/package.json +43 -0
- package/src/config.d.ts +2 -0
- package/src/config.js +3 -0
- package/src/index.d.ts +11 -0
- package/src/index.js +54 -0
- package/src/material-color-helpers.d.ts +48 -0
- package/src/material-color-helpers.js +134 -0
- package/src/storage.d.ts +3 -0
- package/src/storage.js +12 -0
- package/src/theme-mixin.d.ts +8 -0
- package/src/theme-mixin.js +21 -0
- package/src/theme-signals.d.ts +2 -0
- package/src/theme-signals.js +3 -0
- package/src/theme.d.ts +75 -0
- package/src/theme.js +136 -0
- package/src/types.d.ts +32 -0
- package/src/types.js +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @exmg/exm-theme
|
|
2
|
+
|
|
3
|
+
This package is designed for the easy integration and management of material theming. It offers a streamlined process to implement dynamic themes in web components.
|
|
4
|
+
|
|
5
|
+
[Demo](https://exmg.github.io/exmachina-web-components/demo/?el=exm-tooltip)
|
|
6
|
+
|
|
7
|
+
## Getting Started: Installation
|
|
8
|
+
|
|
9
|
+
To incorporate @exmg/exm-theme into your project, use the following command:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @exmg/exm-theme
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Example Initialisation
|
|
16
|
+
|
|
17
|
+
In your main JavaScript file, add the following snippet to initialize the theme. This setup either loads existing theme variables from local storage or generates new ones based on the specified color settings.
|
|
18
|
+
|
|
19
|
+
Default Initialisation Parameters:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
import { initTheme } from '@exmg/exm-theme';
|
|
23
|
+
|
|
24
|
+
initTheme({ themeColor: 'blue', colorMode: 'dark' });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Modifying the Theme Color
|
|
28
|
+
|
|
29
|
+
To change the theme color dynamically, use the following code:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
this.dispatchEvent(new ThemeChangeDarkModeEvent('light'));
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Responsive Theme Adjustments
|
|
36
|
+
|
|
37
|
+
React to changes in theme color or mode using the following implementation:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
41
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
42
|
+
import { ColorMode, ThemeUtils, ThemeMixin } from '@exmg/exm-theme';
|
|
43
|
+
|
|
44
|
+
@customElement('customer-logo')
|
|
45
|
+
export class CustomerLogo extends ThemeMixin(LitElement) {
|
|
46
|
+
@state() selectedColorMode: ColorMode | null = null;
|
|
47
|
+
|
|
48
|
+
static styles = [
|
|
49
|
+
css`
|
|
50
|
+
:host {
|
|
51
|
+
display: block;
|
|
52
|
+
margin: 1rem 1rem 0rem;
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
async firstUpdated() {
|
|
58
|
+
if (!this.selectedColorMode) {
|
|
59
|
+
this.selectedColorMode = ThemeUtils.getCurrentMode();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
render() {
|
|
64
|
+
if (!this.selectedColorMode) return nothing;
|
|
65
|
+
return html`<img src="/demo/demos/exm-navigation/logo-small-${this.isDarkMode() ? 'dark' : 'light'}.svg" />`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { initTheme } from './src/index.js';
|
|
2
|
+
export { ThemeChangeColorEvent, ThemeChangeDarkModeEvent, ColorMode } from './src/types.js';
|
|
3
|
+
import { changeColor, changeColorAndMode, changeColorMode, getCurrentMode, getCurrentSeedColor, getCurrentThemeString, getLastSavedAutoColorMode, isModeDark } from './src/theme.js';
|
|
4
|
+
import { hctFromHex, hexFromHct } from './src/material-color-helpers.js';
|
|
5
|
+
export declare const ThemeUtils: {
|
|
6
|
+
changeColor: typeof changeColor;
|
|
7
|
+
changeColorAndMode: typeof changeColorAndMode;
|
|
8
|
+
changeColorMode: typeof changeColorMode;
|
|
9
|
+
getCurrentMode: typeof getCurrentMode;
|
|
10
|
+
getCurrentSeedColor: typeof getCurrentSeedColor;
|
|
11
|
+
getCurrentThemeString: typeof getCurrentThemeString;
|
|
12
|
+
getLastSavedAutoColorMode: typeof getLastSavedAutoColorMode;
|
|
13
|
+
isModeDark: typeof isModeDark;
|
|
14
|
+
hctFromHex: typeof hctFromHex;
|
|
15
|
+
hexFromHct: typeof hexFromHct;
|
|
16
|
+
};
|
|
17
|
+
export { ThemeMixin } from './src/theme-mixin.js';
|
|
18
|
+
export { themeCurrentMode } from './src/theme-signals.js';
|
package/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { initTheme } from './src/index.js';
|
|
2
|
+
export { ThemeChangeColorEvent, ThemeChangeDarkModeEvent } from './src/types.js';
|
|
3
|
+
import { changeColor, changeColorAndMode, changeColorMode, getCurrentMode, getCurrentSeedColor, getCurrentThemeString, getLastSavedAutoColorMode, isModeDark, } from './src/theme.js';
|
|
4
|
+
import { hctFromHex, hexFromHct } from './src/material-color-helpers.js';
|
|
5
|
+
export const ThemeUtils = {
|
|
6
|
+
changeColor,
|
|
7
|
+
changeColorAndMode,
|
|
8
|
+
changeColorMode,
|
|
9
|
+
getCurrentMode,
|
|
10
|
+
getCurrentSeedColor,
|
|
11
|
+
getCurrentThemeString,
|
|
12
|
+
getLastSavedAutoColorMode,
|
|
13
|
+
isModeDark,
|
|
14
|
+
hctFromHex,
|
|
15
|
+
hexFromHct,
|
|
16
|
+
};
|
|
17
|
+
export { ThemeMixin } from './src/theme-mixin.js';
|
|
18
|
+
export { themeCurrentMode } from './src/theme-signals.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exmg/exm-theme",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.js",
|
|
7
|
+
"description": "Helper to initialize theme for material components",
|
|
8
|
+
"contributors": [
|
|
9
|
+
"Ex Machina Group"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"web-components",
|
|
13
|
+
"lit",
|
|
14
|
+
"theme"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"**/*.js",
|
|
18
|
+
"**/*.d.ts"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@exmg/lit-base": "^2.0.1",
|
|
22
|
+
"@material/material-color-utilities": "^0.2.7",
|
|
23
|
+
"lit": "^3.0.0",
|
|
24
|
+
"tslib": "^2.6.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@exmg/lit-cli": "1.1.13"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/exmg/exmachina-web-components",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git@github.com:exmg/exm-web-components.git",
|
|
33
|
+
"directory": "packages/exm-theme"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"tsc": "tsc"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"gitHead": "0907b55c89325d59902b98a64c352bf6e1fc81ff"
|
|
43
|
+
}
|
package/src/config.d.ts
ADDED
package/src/config.js
ADDED
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ThemeChangeColorEvent, ThemeChangeDarkModeEvent, ColorMode } from './types.js';
|
|
2
|
+
export declare const initTheme: (config?: {
|
|
3
|
+
themeColor?: string;
|
|
4
|
+
colorMode?: ColorMode;
|
|
5
|
+
}) => void;
|
|
6
|
+
declare global {
|
|
7
|
+
interface HTMLElementEventMap {
|
|
8
|
+
'theme-change-color': ThemeChangeColorEvent;
|
|
9
|
+
'theme-change-mode': ThemeChangeDarkModeEvent;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { DEFAULT_THEME_COLOR } from './config.js';
|
|
2
|
+
import { applyTheme } from './material-color-helpers.js';
|
|
3
|
+
import { loadfromStorage } from './storage.js';
|
|
4
|
+
import { changeColor, changeColorAndMode, changeColorMode, getCurrentMode, getCurrentSeedColor, getCurrentThemeString, getLastSavedAutoColorMode, isModeDark, } from './theme.js';
|
|
5
|
+
/**
|
|
6
|
+
* Determines whether to update the theme on page navigation if the mode is
|
|
7
|
+
* 'auto'.
|
|
8
|
+
*
|
|
9
|
+
* This is necessary in the edge case where the user has set color mode to
|
|
10
|
+
* 'auto', and the system mode is A. They navigate away from the catalog, and
|
|
11
|
+
* over time the system mode changes to B. When they navigate back to the
|
|
12
|
+
* catalog, the mode may be 'auto', but color theme with mode A is saved instead
|
|
13
|
+
* of B.
|
|
14
|
+
*/
|
|
15
|
+
function determinePageNavigationAutoMode() {
|
|
16
|
+
if (getCurrentMode() !== 'auto') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const actualColorMode = isModeDark('auto', false) ? 'dark' : 'light';
|
|
20
|
+
const lastSavedAutoColorMode = getLastSavedAutoColorMode();
|
|
21
|
+
if (actualColorMode !== lastSavedAutoColorMode) {
|
|
22
|
+
// Recalculate auto mode with the same theme color.
|
|
23
|
+
changeColorMode('auto');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function setupListeners() {
|
|
27
|
+
document.body.addEventListener('theme-change-color', (event) => {
|
|
28
|
+
changeColor(event.color);
|
|
29
|
+
});
|
|
30
|
+
document.body.addEventListener('theme-change-mode', (event) => {
|
|
31
|
+
changeColorMode(event.mode);
|
|
32
|
+
});
|
|
33
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
34
|
+
if (getCurrentMode() !== 'auto') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
changeColor(getCurrentSeedColor());
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Initializes the theme by applying the theme string from localStorage.
|
|
41
|
+
export const initTheme = (config) => {
|
|
42
|
+
const { themeColor, colorMode } = config || {};
|
|
43
|
+
const theme = loadfromStorage();
|
|
44
|
+
// Applies the theme string to the document if available.
|
|
45
|
+
if (theme) {
|
|
46
|
+
applyTheme(document, theme);
|
|
47
|
+
}
|
|
48
|
+
setupListeners();
|
|
49
|
+
if (!getCurrentThemeString()) {
|
|
50
|
+
changeColorAndMode(themeColor || DEFAULT_THEME_COLOR, colorMode || 'auto');
|
|
51
|
+
}
|
|
52
|
+
determinePageNavigationAutoMode();
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { Hct } from '@material/material-color-utilities';
|
|
7
|
+
import type { Theme } from './types.js';
|
|
8
|
+
export declare const applyTheme: (doc: DocumentOrShadowRoot, theme: string) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Converts a hex value to a HCT tuple.
|
|
11
|
+
*
|
|
12
|
+
* @param value A stringified hex color e.g. '#C01075'
|
|
13
|
+
* @return Material Color Utilities HCT color tuple.
|
|
14
|
+
*/
|
|
15
|
+
export declare function hctFromHex(value: string): Hct;
|
|
16
|
+
/**
|
|
17
|
+
* Converts a hue chroma and tone to a hex color value clamped in the hex
|
|
18
|
+
* colorspace.
|
|
19
|
+
*
|
|
20
|
+
* @param hue The hue of the color of value [0,360]
|
|
21
|
+
* @param chroma The chroma of the color of value [0,150]
|
|
22
|
+
* @param tone The tone of the color of value [0,100]
|
|
23
|
+
* @return A clamped, stringified hex color value representing the HCT values.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hexFromHct(hue: number, chroma: number, tone: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* Generates a theme object mapping of kebab-system-color-token to stringified
|
|
28
|
+
* sRGB hex value in the Material SchemeContent color scheme given a single
|
|
29
|
+
* color.
|
|
30
|
+
*
|
|
31
|
+
* @param color A stringified hex color e.g. '#C01075'
|
|
32
|
+
* @param isDark Whether or not to generate a dark mode theme.
|
|
33
|
+
* @return A theme object that maps the sys color token to its value (not a
|
|
34
|
+
* custom property).
|
|
35
|
+
*/
|
|
36
|
+
export declare function themeFromSourceColor(color: string, isDark: boolean): Theme;
|
|
37
|
+
/**
|
|
38
|
+
* Generates a stylesheet string of custom properties from the given theme, and
|
|
39
|
+
* applies the styles to the given document or shadow root, and caches the value
|
|
40
|
+
* in memory and localstorage given an optional ssName.
|
|
41
|
+
*
|
|
42
|
+
* @param doc Document or ShadowRoot to apply theme.
|
|
43
|
+
* @param theme A theme object that maps the sys color token to its value
|
|
44
|
+
* (output of themeFromSourceColor).
|
|
45
|
+
* @param ssName Optional global identifier of the constructable stylesheet and
|
|
46
|
+
* used to generate the localstorage name.
|
|
47
|
+
*/
|
|
48
|
+
export declare function applyMaterialTheme(doc: DocumentOrShadowRoot, theme: Theme): void;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { argbFromHex, Hct, hexFromArgb, MaterialDynamicColors, SchemeContent, } from '@material/material-color-utilities';
|
|
7
|
+
import { STORAGE_KEY_THEME } from './config.js';
|
|
8
|
+
import { saveToStorage } from './storage.js';
|
|
9
|
+
// If supported by the browser, sets the theme color in the URL bar.
|
|
10
|
+
const setUrlBarColor = (themeString) => {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const surfaceContainer = (_a = themeString.match(/--md-sys-color-surface-container:(.+?);/)) === null || _a === void 0 ? void 0 : _a[1];
|
|
13
|
+
if (surfaceContainer) {
|
|
14
|
+
(_b = document.querySelector('meta[name="theme-color"]')) === null || _b === void 0 ? void 0 : _b.setAttribute('content', surfaceContainer);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
// eslint-disable-next-line
|
|
18
|
+
export const applyTheme = (doc, theme) => {
|
|
19
|
+
// eslint-disable-next-line
|
|
20
|
+
let sheet = globalThis[STORAGE_KEY_THEME];
|
|
21
|
+
if (!sheet) {
|
|
22
|
+
sheet = new CSSStyleSheet();
|
|
23
|
+
// eslint-disable-next-line
|
|
24
|
+
globalThis[STORAGE_KEY_THEME] = sheet;
|
|
25
|
+
doc.adoptedStyleSheets.push(sheet);
|
|
26
|
+
}
|
|
27
|
+
setUrlBarColor(theme);
|
|
28
|
+
sheet.replaceSync(theme);
|
|
29
|
+
saveToStorage(theme);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A Mapping of color token name to MCU HCT color function generator.
|
|
33
|
+
*/
|
|
34
|
+
const materialColors = {
|
|
35
|
+
background: MaterialDynamicColors.background,
|
|
36
|
+
'on-background': MaterialDynamicColors.onBackground,
|
|
37
|
+
surface: MaterialDynamicColors.surface,
|
|
38
|
+
'surface-dim': MaterialDynamicColors.surfaceDim,
|
|
39
|
+
'surface-bright': MaterialDynamicColors.surfaceBright,
|
|
40
|
+
'surface-container-lowest': MaterialDynamicColors.surfaceContainerLowest,
|
|
41
|
+
'surface-container-low': MaterialDynamicColors.surfaceContainerLow,
|
|
42
|
+
'surface-container': MaterialDynamicColors.surfaceContainer,
|
|
43
|
+
'surface-container-high': MaterialDynamicColors.surfaceContainerHigh,
|
|
44
|
+
'surface-container-highest': MaterialDynamicColors.surfaceContainerHighest,
|
|
45
|
+
'on-surface': MaterialDynamicColors.onSurface,
|
|
46
|
+
'surface-variant': MaterialDynamicColors.surfaceVariant,
|
|
47
|
+
'on-surface-variant': MaterialDynamicColors.onSurfaceVariant,
|
|
48
|
+
'inverse-surface': MaterialDynamicColors.inverseSurface,
|
|
49
|
+
'inverse-on-surface': MaterialDynamicColors.inverseOnSurface,
|
|
50
|
+
outline: MaterialDynamicColors.outline,
|
|
51
|
+
'outline-variant': MaterialDynamicColors.outlineVariant,
|
|
52
|
+
shadow: MaterialDynamicColors.shadow,
|
|
53
|
+
scrim: MaterialDynamicColors.scrim,
|
|
54
|
+
'surface-tint': MaterialDynamicColors.surfaceTint,
|
|
55
|
+
primary: MaterialDynamicColors.primary,
|
|
56
|
+
'on-primary': MaterialDynamicColors.onPrimary,
|
|
57
|
+
'primary-container': MaterialDynamicColors.primaryContainer,
|
|
58
|
+
'on-primary-container': MaterialDynamicColors.onPrimaryContainer,
|
|
59
|
+
'inverse-primary': MaterialDynamicColors.inversePrimary,
|
|
60
|
+
secondary: MaterialDynamicColors.secondary,
|
|
61
|
+
'on-secondary': MaterialDynamicColors.onSecondary,
|
|
62
|
+
'secondary-container': MaterialDynamicColors.secondaryContainer,
|
|
63
|
+
'on-secondary-container': MaterialDynamicColors.onSecondaryContainer,
|
|
64
|
+
tertiary: MaterialDynamicColors.tertiary,
|
|
65
|
+
'on-tertiary': MaterialDynamicColors.onTertiary,
|
|
66
|
+
'tertiary-container': MaterialDynamicColors.tertiaryContainer,
|
|
67
|
+
'on-tertiary-container': MaterialDynamicColors.onTertiaryContainer,
|
|
68
|
+
error: MaterialDynamicColors.error,
|
|
69
|
+
'on-error': MaterialDynamicColors.onError,
|
|
70
|
+
'error-container': MaterialDynamicColors.errorContainer,
|
|
71
|
+
'on-error-container': MaterialDynamicColors.onErrorContainer,
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Converts a hex value to a HCT tuple.
|
|
75
|
+
*
|
|
76
|
+
* @param value A stringified hex color e.g. '#C01075'
|
|
77
|
+
* @return Material Color Utilities HCT color tuple.
|
|
78
|
+
*/
|
|
79
|
+
export function hctFromHex(value) {
|
|
80
|
+
return Hct.fromInt(argbFromHex(value));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Converts a hue chroma and tone to a hex color value clamped in the hex
|
|
84
|
+
* colorspace.
|
|
85
|
+
*
|
|
86
|
+
* @param hue The hue of the color of value [0,360]
|
|
87
|
+
* @param chroma The chroma of the color of value [0,150]
|
|
88
|
+
* @param tone The tone of the color of value [0,100]
|
|
89
|
+
* @return A clamped, stringified hex color value representing the HCT values.
|
|
90
|
+
*/
|
|
91
|
+
export function hexFromHct(hue, chroma, tone) {
|
|
92
|
+
const hct = Hct.from(hue, chroma, tone);
|
|
93
|
+
const value = hct.toInt();
|
|
94
|
+
return hexFromArgb(value);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generates a theme object mapping of kebab-system-color-token to stringified
|
|
98
|
+
* sRGB hex value in the Material SchemeContent color scheme given a single
|
|
99
|
+
* color.
|
|
100
|
+
*
|
|
101
|
+
* @param color A stringified hex color e.g. '#C01075'
|
|
102
|
+
* @param isDark Whether or not to generate a dark mode theme.
|
|
103
|
+
* @return A theme object that maps the sys color token to its value (not a
|
|
104
|
+
* custom property).
|
|
105
|
+
*/
|
|
106
|
+
export function themeFromSourceColor(color, isDark) {
|
|
107
|
+
const scheme = new SchemeContent(Hct.fromInt(argbFromHex(color)), isDark, 0);
|
|
108
|
+
const theme = {};
|
|
109
|
+
for (const [key, value] of Object.entries(materialColors)) {
|
|
110
|
+
theme[key] = hexFromArgb(value.getArgb(scheme));
|
|
111
|
+
}
|
|
112
|
+
return theme;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generates a stylesheet string of custom properties from the given theme, and
|
|
116
|
+
* applies the styles to the given document or shadow root, and caches the value
|
|
117
|
+
* in memory and localstorage given an optional ssName.
|
|
118
|
+
*
|
|
119
|
+
* @param doc Document or ShadowRoot to apply theme.
|
|
120
|
+
* @param theme A theme object that maps the sys color token to its value
|
|
121
|
+
* (output of themeFromSourceColor).
|
|
122
|
+
* @param ssName Optional global identifier of the constructable stylesheet and
|
|
123
|
+
* used to generate the localstorage name.
|
|
124
|
+
*/
|
|
125
|
+
// eslint-disable-next-line
|
|
126
|
+
export function applyMaterialTheme(doc, theme) {
|
|
127
|
+
let styleString = ':root,:host{';
|
|
128
|
+
for (const [key, value] of Object.entries(theme)) {
|
|
129
|
+
styleString += `--md-sys-color-${key}:${value};`;
|
|
130
|
+
}
|
|
131
|
+
styleString += '}';
|
|
132
|
+
applyTheme(doc, styleString);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=material-color-helpers.js.map
|
package/src/storage.d.ts
ADDED
package/src/storage.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { STORAGE_KEY_THEME } from './config.js';
|
|
2
|
+
// Load the theme string from localStorage.
|
|
3
|
+
const loadfromStorage = () => {
|
|
4
|
+
const theme = localStorage.getItem(STORAGE_KEY_THEME);
|
|
5
|
+
return theme;
|
|
6
|
+
};
|
|
7
|
+
// Saves the theme string to localStorage.
|
|
8
|
+
const saveToStorage = (theme) => {
|
|
9
|
+
localStorage.setItem(STORAGE_KEY_THEME, theme);
|
|
10
|
+
};
|
|
11
|
+
export { loadfromStorage, saveToStorage };
|
|
12
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export type Constructor<T> = new (...args: any[]) => T;
|
|
3
|
+
export declare abstract class ThemeClass extends LitElement {
|
|
4
|
+
}
|
|
5
|
+
export declare class ThemeInterface {
|
|
6
|
+
isDarkMode(): boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const ThemeMixin: <T extends Constructor<LitElement & ThemeClass>>(base: T) => Constructor<ThemeInterface> & T;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { SignalWatcher } from '@lit-labs/preact-signals';
|
|
3
|
+
import { themeCurrentMode } from './theme-signals.js';
|
|
4
|
+
import { getCurrentMode, isModeDark } from './theme.js';
|
|
5
|
+
export class ThemeClass extends LitElement {
|
|
6
|
+
}
|
|
7
|
+
export const ThemeMixin = (base) => {
|
|
8
|
+
// eslint-disable-next-line
|
|
9
|
+
class ExmgTheme extends SignalWatcher(base) {
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
super.connectedCallback();
|
|
12
|
+
// Read current mode from local storage
|
|
13
|
+
themeCurrentMode.value = getCurrentMode() || 'auto';
|
|
14
|
+
}
|
|
15
|
+
isDarkMode() {
|
|
16
|
+
return isModeDark(themeCurrentMode.value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return ExmgTheme;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=theme-mixin.js.map
|
package/src/theme.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ColorMode } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determines whether or not the mode should be Dark. This also means
|
|
4
|
+
* calculating whether it should be dark if the current mode is 'auto'.
|
|
5
|
+
*
|
|
6
|
+
* @param mode The current color mode 'light', 'dark', or 'auto'.
|
|
7
|
+
* @param saveAutoMode (Optional) Whether or not to save last auto mode to
|
|
8
|
+
* localstorage. Set to false if you simply want to query whether auto mode
|
|
9
|
+
* is dark or not. Defaults to true.
|
|
10
|
+
* @return Whether or not the dark mode color tokens should apply.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isModeDark(mode: ColorMode, saveAutoMode?: boolean): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Gets the current stringified material theme css string from localstorage.
|
|
15
|
+
*
|
|
16
|
+
* @return The current stringified material theme css string.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getCurrentThemeString(): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Gets the current color mode from localstorage.
|
|
21
|
+
*
|
|
22
|
+
* @return The current color mode.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCurrentMode(): ColorMode | null;
|
|
25
|
+
/**
|
|
26
|
+
* Saves the given color mode to localstorage.
|
|
27
|
+
*
|
|
28
|
+
* @param mode The color mode to save to localstorage.
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveColorMode(mode: ColorMode): void;
|
|
31
|
+
/**
|
|
32
|
+
* Gets the current seed color from localstorage.
|
|
33
|
+
*
|
|
34
|
+
* @return The current seed color.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getCurrentSeedColor(): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Saves the given seed color to localstorage.
|
|
39
|
+
*
|
|
40
|
+
* @param color The seed color to save to local storage.
|
|
41
|
+
*/
|
|
42
|
+
export declare function saveSeedColor(color: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Gets last applied color mode while in "auto" from localstorage.
|
|
45
|
+
*
|
|
46
|
+
* @return The last applied color mode while in "auto".
|
|
47
|
+
*/
|
|
48
|
+
export declare function getLastSavedAutoColorMode(): "light" | "dark" | null;
|
|
49
|
+
/**
|
|
50
|
+
* Saves last applied color mode while in "auto" from localstorage.
|
|
51
|
+
*
|
|
52
|
+
* @param mode The last applied color mode while in "auto" to be saved to local
|
|
53
|
+
* storage.
|
|
54
|
+
*/
|
|
55
|
+
export declare function saveLastSavedAutoColorMode(mode: 'light' | 'dark'): void;
|
|
56
|
+
/**
|
|
57
|
+
* Generates and applies a new theme due to a change in source color.
|
|
58
|
+
*
|
|
59
|
+
* @param color The new source color from which to generate the new theme.
|
|
60
|
+
*/
|
|
61
|
+
export declare function changeColor(color: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Generates and applies a new theme due to a change in color mode.
|
|
64
|
+
*
|
|
65
|
+
* @param mode The new color mode from which to generate the new theme.
|
|
66
|
+
*/
|
|
67
|
+
export declare function changeColorMode(mode: ColorMode): void;
|
|
68
|
+
/**
|
|
69
|
+
* Generates and applies a new theme due to a change in both source color and
|
|
70
|
+
* color mode.
|
|
71
|
+
*
|
|
72
|
+
* @param color The new source color from which to generate the new theme.
|
|
73
|
+
* @param mode The new color mode from which to generate the new theme.
|
|
74
|
+
*/
|
|
75
|
+
export declare function changeColorAndMode(color: string, mode: ColorMode): void;
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { STORAGE_KEY_THEME } from './config.js';
|
|
2
|
+
import { themeCurrentMode } from './theme-signals.js';
|
|
3
|
+
import { applyMaterialTheme, themeFromSourceColor } from './material-color-helpers.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generates a Material Theme from a given color and dark mode boolean, and
|
|
6
|
+
* applies the theme to the document and lets the app know that the theme has
|
|
7
|
+
* changed.
|
|
8
|
+
*
|
|
9
|
+
* @param color The source color to generate the theme.
|
|
10
|
+
* @param isDark Whether or not the theme should be in dark mode.
|
|
11
|
+
*/
|
|
12
|
+
function applyThemeFromColor(color, isDark) {
|
|
13
|
+
const theme = themeFromSourceColor(color, isDark);
|
|
14
|
+
applyMaterialTheme(document, theme);
|
|
15
|
+
// Dispatches event to communicate with components pages' JS to update the
|
|
16
|
+
// theme in the playground preview.
|
|
17
|
+
window.dispatchEvent(new Event('theme-changed'));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Determines whether or not the mode should be Dark. This also means
|
|
21
|
+
* calculating whether it should be dark if the current mode is 'auto'.
|
|
22
|
+
*
|
|
23
|
+
* @param mode The current color mode 'light', 'dark', or 'auto'.
|
|
24
|
+
* @param saveAutoMode (Optional) Whether or not to save last auto mode to
|
|
25
|
+
* localstorage. Set to false if you simply want to query whether auto mode
|
|
26
|
+
* is dark or not. Defaults to true.
|
|
27
|
+
* @return Whether or not the dark mode color tokens should apply.
|
|
28
|
+
*/
|
|
29
|
+
export function isModeDark(mode, saveAutoMode = true) {
|
|
30
|
+
let isDark = mode === 'dark';
|
|
31
|
+
// Determines whether the auto mode should display light or dark.
|
|
32
|
+
if (mode === 'auto') {
|
|
33
|
+
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
34
|
+
if (saveAutoMode) {
|
|
35
|
+
// We have to save this because if the user closes the tab when it's light
|
|
36
|
+
// and reopens it when it's dark, we need to know whether the last applied
|
|
37
|
+
// 'auto' mode was correct.
|
|
38
|
+
saveLastSavedAutoColorMode(isDark ? 'dark' : 'light');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return isDark;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Gets the current stringified material theme css string from localstorage.
|
|
45
|
+
*
|
|
46
|
+
* @return The current stringified material theme css string.
|
|
47
|
+
*/
|
|
48
|
+
export function getCurrentThemeString() {
|
|
49
|
+
return localStorage.getItem(STORAGE_KEY_THEME);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets the current color mode from localstorage.
|
|
53
|
+
*
|
|
54
|
+
* @return The current color mode.
|
|
55
|
+
*/
|
|
56
|
+
export function getCurrentMode() {
|
|
57
|
+
return localStorage.getItem('color-mode');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Saves the given color mode to localstorage.
|
|
61
|
+
*
|
|
62
|
+
* @param mode The color mode to save to localstorage.
|
|
63
|
+
*/
|
|
64
|
+
export function saveColorMode(mode) {
|
|
65
|
+
themeCurrentMode.value = mode;
|
|
66
|
+
localStorage.setItem('color-mode', mode);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Gets the current seed color from localstorage.
|
|
70
|
+
*
|
|
71
|
+
* @return The current seed color.
|
|
72
|
+
*/
|
|
73
|
+
export function getCurrentSeedColor() {
|
|
74
|
+
return localStorage.getItem('seed-color');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Saves the given seed color to localstorage.
|
|
78
|
+
*
|
|
79
|
+
* @param color The seed color to save to local storage.
|
|
80
|
+
*/
|
|
81
|
+
export function saveSeedColor(color) {
|
|
82
|
+
localStorage.setItem('seed-color', color);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Gets last applied color mode while in "auto" from localstorage.
|
|
86
|
+
*
|
|
87
|
+
* @return The last applied color mode while in "auto".
|
|
88
|
+
*/
|
|
89
|
+
export function getLastSavedAutoColorMode() {
|
|
90
|
+
return localStorage.getItem('last-auto-color-mode');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Saves last applied color mode while in "auto" from localstorage.
|
|
94
|
+
*
|
|
95
|
+
* @param mode The last applied color mode while in "auto" to be saved to local
|
|
96
|
+
* storage.
|
|
97
|
+
*/
|
|
98
|
+
export function saveLastSavedAutoColorMode(mode) {
|
|
99
|
+
localStorage.setItem('last-auto-color-mode', mode);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Generates and applies a new theme due to a change in source color.
|
|
103
|
+
*
|
|
104
|
+
* @param color The new source color from which to generate the new theme.
|
|
105
|
+
*/
|
|
106
|
+
export function changeColor(color) {
|
|
107
|
+
const lastColorMode = getCurrentMode();
|
|
108
|
+
const isDark = isModeDark(lastColorMode);
|
|
109
|
+
applyThemeFromColor(color, isDark);
|
|
110
|
+
saveSeedColor(color);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generates and applies a new theme due to a change in color mode.
|
|
114
|
+
*
|
|
115
|
+
* @param mode The new color mode from which to generate the new theme.
|
|
116
|
+
*/
|
|
117
|
+
export function changeColorMode(mode) {
|
|
118
|
+
const color = getCurrentSeedColor();
|
|
119
|
+
const isDark = isModeDark(mode);
|
|
120
|
+
applyThemeFromColor(color, isDark);
|
|
121
|
+
saveColorMode(mode);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generates and applies a new theme due to a change in both source color and
|
|
125
|
+
* color mode.
|
|
126
|
+
*
|
|
127
|
+
* @param color The new source color from which to generate the new theme.
|
|
128
|
+
* @param mode The new color mode from which to generate the new theme.
|
|
129
|
+
*/
|
|
130
|
+
export function changeColorAndMode(color, mode) {
|
|
131
|
+
const isDark = isModeDark(mode);
|
|
132
|
+
applyThemeFromColor(color, isDark);
|
|
133
|
+
saveSeedColor(color);
|
|
134
|
+
saveColorMode(mode);
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=theme.js.map
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type WithStylesheet = typeof globalThis & {
|
|
2
|
+
[stylesheetName: string]: CSSStyleSheet | undefined;
|
|
3
|
+
};
|
|
4
|
+
export type ColorMode = 'light' | 'dark' | 'auto';
|
|
5
|
+
/**
|
|
6
|
+
* A theme mapping of token name (not custom property name) to stringified CSS
|
|
7
|
+
* value.
|
|
8
|
+
*/
|
|
9
|
+
export interface Theme {
|
|
10
|
+
[tokenName: string]: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Requests the global theme listener change the theme due to a color change.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ThemeChangeColorEvent extends Event {
|
|
16
|
+
color: string;
|
|
17
|
+
/**
|
|
18
|
+
* @param color The new source color to apply.
|
|
19
|
+
*/
|
|
20
|
+
constructor(color: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Requests the global theme listener change the theme due to a dark mode
|
|
24
|
+
* change.
|
|
25
|
+
*/
|
|
26
|
+
export declare class ThemeChangeDarkModeEvent extends Event {
|
|
27
|
+
mode: 'light' | 'dark' | 'auto';
|
|
28
|
+
/**
|
|
29
|
+
* @param mode The new color mode to apply.
|
|
30
|
+
*/
|
|
31
|
+
constructor(mode: 'light' | 'dark' | 'auto');
|
|
32
|
+
}
|
package/src/types.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requests the global theme listener change the theme due to a color change.
|
|
3
|
+
*/
|
|
4
|
+
export class ThemeChangeColorEvent extends Event {
|
|
5
|
+
/**
|
|
6
|
+
* @param color The new source color to apply.
|
|
7
|
+
*/
|
|
8
|
+
constructor(color) {
|
|
9
|
+
super('theme-change-color', { bubbles: true, composed: true });
|
|
10
|
+
this.color = color;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Requests the global theme listener change the theme due to a dark mode
|
|
15
|
+
* change.
|
|
16
|
+
*/
|
|
17
|
+
export class ThemeChangeDarkModeEvent extends Event {
|
|
18
|
+
/**
|
|
19
|
+
* @param mode The new color mode to apply.
|
|
20
|
+
*/
|
|
21
|
+
constructor(mode) {
|
|
22
|
+
super('theme-change-mode', { bubbles: true, composed: true });
|
|
23
|
+
this.mode = mode;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.js.map
|