@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 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
+ }
@@ -0,0 +1,2 @@
1
+ export declare const STORAGE_KEY_THEME = "exm-theme";
2
+ export declare const DEFAULT_THEME_COLOR = "#3284FF";
package/src/config.js ADDED
@@ -0,0 +1,3 @@
1
+ export const STORAGE_KEY_THEME = 'exm-theme';
2
+ export const DEFAULT_THEME_COLOR = '#3284FF';
3
+ //# sourceMappingURL=config.js.map
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
@@ -0,0 +1,3 @@
1
+ declare const loadfromStorage: () => string | null;
2
+ declare const saveToStorage: (theme: string) => void;
3
+ export { loadfromStorage, saveToStorage };
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
@@ -0,0 +1,2 @@
1
+ import { ColorMode } from './types.js';
2
+ export declare const themeCurrentMode: import("@preact/signals-core").Signal<ColorMode>;
@@ -0,0 +1,3 @@
1
+ import { signal } from '@lit-labs/preact-signals';
2
+ export const themeCurrentMode = signal('light');
3
+ //# sourceMappingURL=theme-signals.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