vira 31.19.0 → 31.20.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/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +1 -0
- package/dist/elements/vira-theme-switcher.element.d.ts +17 -0
- package/dist/elements/vira-theme-switcher.element.js +104 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/overflow-observer.d.ts +1 -0
- package/dist/util/overflow-observer.js +1 -0
- package/dist/util/vira-theme-client.d.ts +83 -0
- package/dist/util/vira-theme-client.js +93 -0
- package/package.json +3 -1
package/dist/elements/index.d.ts
CHANGED
package/dist/elements/index.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { ViraThemeClient, ViraThemeSelection } from '../util/vira-theme-client.js';
|
|
3
|
+
/**
|
|
4
|
+
* A row of buttons for selecting a {@link ViraThemeSelection} (light, dark, or auto). Fires a
|
|
5
|
+
* `themeSelect` event when the user picks an option; the consumer is responsible for applying the
|
|
6
|
+
* resulting theme.
|
|
7
|
+
*
|
|
8
|
+
* @category Elements
|
|
9
|
+
*/
|
|
10
|
+
export declare const ViraThemeSwitcher: import("element-vir").DeclarativeElementDefinition<"vira-theme-switcher", PartialWithUndefined<{
|
|
11
|
+
themeClient: Readonly<ViraThemeClient>;
|
|
12
|
+
/** Override the default English button titles (used as `title` attributes for tooltips). */
|
|
13
|
+
labels: Readonly<Record<ViraThemeSelection, string>>;
|
|
14
|
+
}>, {
|
|
15
|
+
internalThemeClient: undefined | Readonly<ViraThemeClient>;
|
|
16
|
+
currentTheme: ViraThemeSelection;
|
|
17
|
+
}, {}, "vira-theme-switcher-", "vira-theme-switcher-", readonly [], readonly []>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mapEnumToObject } from '@augment-vir/common';
|
|
2
|
+
import { classMap, css, html, listen } from 'element-vir';
|
|
3
|
+
import { AutoTheme24Icon, Moon24Icon, Sun24Icon } from '../icons/index.js';
|
|
4
|
+
import { viraFormCssVars } from '../styles/form-styles.js';
|
|
5
|
+
import { noNativeFormStyles, noNativeSpacing, viraTheme } from '../styles/index.js';
|
|
6
|
+
import { defineViraElement } from '../util/define-vira-element.js';
|
|
7
|
+
import { ViraThemeClient, ViraThemeSelection } from '../util/vira-theme-client.js';
|
|
8
|
+
import { ViraIcon } from './vira-icon.element.js';
|
|
9
|
+
const themeIcons = mapEnumToObject(ViraThemeSelection, (theme) => {
|
|
10
|
+
const map = {
|
|
11
|
+
[ViraThemeSelection.Light]: Sun24Icon,
|
|
12
|
+
[ViraThemeSelection.Dark]: Moon24Icon,
|
|
13
|
+
[ViraThemeSelection.Auto]: AutoTheme24Icon,
|
|
14
|
+
};
|
|
15
|
+
return map[theme];
|
|
16
|
+
});
|
|
17
|
+
const defaultThemeLabels = {
|
|
18
|
+
[ViraThemeSelection.Light]: 'Light',
|
|
19
|
+
[ViraThemeSelection.Dark]: 'Dark',
|
|
20
|
+
[ViraThemeSelection.Auto]: 'Auto',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A row of buttons for selecting a {@link ViraThemeSelection} (light, dark, or auto). Fires a
|
|
24
|
+
* `themeSelect` event when the user picks an option; the consumer is responsible for applying the
|
|
25
|
+
* resulting theme.
|
|
26
|
+
*
|
|
27
|
+
* @category Elements
|
|
28
|
+
*/
|
|
29
|
+
export const ViraThemeSwitcher = defineViraElement()({
|
|
30
|
+
tagName: 'vira-theme-switcher',
|
|
31
|
+
styles: css `
|
|
32
|
+
:host {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
gap: 4px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
button {
|
|
40
|
+
${noNativeSpacing};
|
|
41
|
+
${noNativeFormStyles};
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
border: 1px solid transparent;
|
|
46
|
+
border-radius: ${viraFormCssVars['vira-form-radius'].value};
|
|
47
|
+
padding: 2px;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
color: ${viraTheme.colors['vira-grey-foreground-placeholder'].foreground.value};
|
|
50
|
+
|
|
51
|
+
&:hover {
|
|
52
|
+
color: ${viraFormCssVars['vira-form-accent-primary-hover-color'].value};
|
|
53
|
+
border-color: currentColor;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&.selected {
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
color: ${viraFormCssVars['vira-form-accent-primary-color'].value};
|
|
59
|
+
border-color: ${viraFormCssVars['vira-form-accent-primary-color'].value};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
${ViraIcon} {
|
|
64
|
+
width: 20px;
|
|
65
|
+
aspect-ratio: 1;
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
state() {
|
|
69
|
+
return {
|
|
70
|
+
internalThemeClient: undefined,
|
|
71
|
+
currentTheme: ViraThemeSelection.Auto,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
render({ inputs, state, updateState }) {
|
|
75
|
+
const themeClient = inputs.themeClient || state.internalThemeClient || new ViraThemeClient();
|
|
76
|
+
updateState({
|
|
77
|
+
internalThemeClient: themeClient,
|
|
78
|
+
currentTheme: themeClient.currentTheme,
|
|
79
|
+
});
|
|
80
|
+
const labels = inputs.labels || defaultThemeLabels;
|
|
81
|
+
return Object.values(ViraThemeSelection).map((theme) => {
|
|
82
|
+
return html `
|
|
83
|
+
<button
|
|
84
|
+
class=${classMap({
|
|
85
|
+
selected: themeClient.currentTheme === theme,
|
|
86
|
+
})}
|
|
87
|
+
title=${labels[theme]}
|
|
88
|
+
${listen('click', (event) => {
|
|
89
|
+
event.stopPropagation();
|
|
90
|
+
themeClient.setSelectedTheme(theme);
|
|
91
|
+
updateState({
|
|
92
|
+
currentTheme: themeClient.currentTheme,
|
|
93
|
+
});
|
|
94
|
+
})}
|
|
95
|
+
>
|
|
96
|
+
<${ViraIcon.assign({
|
|
97
|
+
icon: themeIcons[theme],
|
|
98
|
+
fitContainer: true,
|
|
99
|
+
})}></${ViraIcon}>
|
|
100
|
+
</button>
|
|
101
|
+
`;
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
});
|
package/dist/util/index.d.ts
CHANGED
package/dist/util/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Creates an observer that monitors whether an element's content overflows its visible width. Uses
|
|
3
3
|
* a ResizeObserver for size changes and a MutationObserver for DOM content changes.
|
|
4
4
|
*
|
|
5
|
+
* @category Util
|
|
5
6
|
* @returns A cleanup function that disconnects all observers.
|
|
6
7
|
*/
|
|
7
8
|
export declare function createOverflowObserver({ element, widthElement, onChange, hysteresisPx, }: Readonly<{
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Creates an observer that monitors whether an element's content overflows its visible width. Uses
|
|
3
3
|
* a ResizeObserver for size changes and a MutationObserver for DOM content changes.
|
|
4
4
|
*
|
|
5
|
+
* @category Util
|
|
5
6
|
* @returns A cleanup function that disconnects all observers.
|
|
6
7
|
*/
|
|
7
8
|
export function createOverflowObserver({ element, widthElement, onChange, hysteresisPx = 0, }) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { LocalStorageClient } from '@electrovir/local-storage-client';
|
|
3
|
+
/**
|
|
4
|
+
* A user-facing selection of which theme to display. `Auto` follows the system color scheme; the
|
|
5
|
+
* other values force a specific theme regardless of system preference.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
*/
|
|
9
|
+
export declare enum ViraThemeSelection {
|
|
10
|
+
Light = "light",
|
|
11
|
+
Dark = "dark",
|
|
12
|
+
Auto = "auto"
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Used by {@link ViraThemeClient} to apply themes. By default, vira themes will be applied (via
|
|
16
|
+
* {@link defaultApplyThemeCallback}).
|
|
17
|
+
*
|
|
18
|
+
* @category Internal
|
|
19
|
+
* @default `defaultApplyThemeCallback`
|
|
20
|
+
*/
|
|
21
|
+
export type ApplyThemeCallback = (params: Readonly<{
|
|
22
|
+
useDarkTheme: boolean;
|
|
23
|
+
}>) => MaybePromise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Default implementation of {@link ApplyThemeCallback}, which simply applies Vira themes.
|
|
26
|
+
*
|
|
27
|
+
* @category Internal
|
|
28
|
+
*/
|
|
29
|
+
export declare const defaultApplyThemeCallback: ApplyThemeCallback;
|
|
30
|
+
/**
|
|
31
|
+
* Constructor params for {@link ViraThemeClient}.
|
|
32
|
+
*
|
|
33
|
+
* @category Internal
|
|
34
|
+
*/
|
|
35
|
+
export type ViraThemeClientParams = PartialWithUndefined<{
|
|
36
|
+
/**
|
|
37
|
+
* Called whenever the effective theme should change. If not provided, the default Vira themes
|
|
38
|
+
* will be used.
|
|
39
|
+
*/
|
|
40
|
+
applyTheme: ApplyThemeCallback;
|
|
41
|
+
/**
|
|
42
|
+
* Override the LocalStorage store name used for theme persistence. Useful if a single page
|
|
43
|
+
* hosts multiple isolated theme clients.
|
|
44
|
+
*
|
|
45
|
+
* @default 'vira-theme'
|
|
46
|
+
*/
|
|
47
|
+
storeName: string;
|
|
48
|
+
}>;
|
|
49
|
+
declare const themeStorageShapes: {
|
|
50
|
+
selectedTheme: import("object-shape-tester").Shape<{
|
|
51
|
+
theme: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TLiteral<ViraThemeSelection.Light> | import("@sinclair/typebox").TLiteral<ViraThemeSelection.Dark> | import("@sinclair/typebox").TLiteral<ViraThemeSelection.Auto>)[]>>;
|
|
52
|
+
}>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Tracks the user's {@link ViraThemeSelection} and bridges it to a consumer-supplied `applyTheme`
|
|
56
|
+
* callback. Persists the selection in LocalStorage via an internal `LocalStorageClient`, and
|
|
57
|
+
* listens for system color-scheme changes to re-apply the theme in auto mode without persisting.
|
|
58
|
+
*
|
|
59
|
+
* The initial theme is applied during construction.
|
|
60
|
+
*
|
|
61
|
+
* @category Util
|
|
62
|
+
*/
|
|
63
|
+
export declare class ViraThemeClient {
|
|
64
|
+
/** The callback that will be called to apply a new theme. */
|
|
65
|
+
protected readonly applyThemeCallback: ApplyThemeCallback;
|
|
66
|
+
/** Contains the user's last selected theme, saving and loading it to disk for persistence. */
|
|
67
|
+
protected readonly localStorageClient: LocalStorageClient<typeof themeStorageShapes>;
|
|
68
|
+
/** A callback to remove the global theme preference listener. */
|
|
69
|
+
protected readonly removeThemePreferenceListener: () => void;
|
|
70
|
+
constructor(params?: Readonly<ViraThemeClientParams>);
|
|
71
|
+
/**
|
|
72
|
+
* The currently selected theme. If you use multiple clients to set the same theme, this might
|
|
73
|
+
* get out of sync.
|
|
74
|
+
*/
|
|
75
|
+
get currentTheme(): ViraThemeSelection;
|
|
76
|
+
/** Set the selected theme. */
|
|
77
|
+
setSelectedTheme(selection: ViraThemeSelection): void;
|
|
78
|
+
/** Cleanup internal state and listeners. */
|
|
79
|
+
destroy(): void;
|
|
80
|
+
/** Apply the currently selected theme. */
|
|
81
|
+
protected applySelection(selection: ViraThemeSelection): void;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { assert } from '@augment-vir/assert';
|
|
2
|
+
import { LocalStorageClient } from '@electrovir/local-storage-client';
|
|
3
|
+
import { defineShape, enumShape } from 'object-shape-tester';
|
|
4
|
+
import { applyColorThemeViaStyleElement } from 'theme-vir';
|
|
5
|
+
import { listenTo } from 'typed-event-target';
|
|
6
|
+
import { viraTheme, viraThemeDarkOverride } from '../styles/vira-color-theme.js';
|
|
7
|
+
/**
|
|
8
|
+
* A user-facing selection of which theme to display. `Auto` follows the system color scheme; the
|
|
9
|
+
* other values force a specific theme regardless of system preference.
|
|
10
|
+
*
|
|
11
|
+
* @category Internal
|
|
12
|
+
*/
|
|
13
|
+
export var ViraThemeSelection;
|
|
14
|
+
(function (ViraThemeSelection) {
|
|
15
|
+
ViraThemeSelection["Light"] = "light";
|
|
16
|
+
ViraThemeSelection["Dark"] = "dark";
|
|
17
|
+
ViraThemeSelection["Auto"] = "auto";
|
|
18
|
+
})(ViraThemeSelection || (ViraThemeSelection = {}));
|
|
19
|
+
/**
|
|
20
|
+
* Default implementation of {@link ApplyThemeCallback}, which simply applies Vira themes.
|
|
21
|
+
*
|
|
22
|
+
* @category Internal
|
|
23
|
+
*/
|
|
24
|
+
export const defaultApplyThemeCallback = ({ useDarkTheme }) => {
|
|
25
|
+
applyColorThemeViaStyleElement(viraTheme, useDarkTheme ? viraThemeDarkOverride : undefined);
|
|
26
|
+
};
|
|
27
|
+
const darkSchemeMediaQuery = '(prefers-color-scheme: dark)';
|
|
28
|
+
const themeStorageShapes = {
|
|
29
|
+
selectedTheme: defineShape({
|
|
30
|
+
theme: enumShape(ViraThemeSelection),
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Tracks the user's {@link ViraThemeSelection} and bridges it to a consumer-supplied `applyTheme`
|
|
35
|
+
* callback. Persists the selection in LocalStorage via an internal `LocalStorageClient`, and
|
|
36
|
+
* listens for system color-scheme changes to re-apply the theme in auto mode without persisting.
|
|
37
|
+
*
|
|
38
|
+
* The initial theme is applied during construction.
|
|
39
|
+
*
|
|
40
|
+
* @category Util
|
|
41
|
+
*/
|
|
42
|
+
export class ViraThemeClient {
|
|
43
|
+
/** The callback that will be called to apply a new theme. */
|
|
44
|
+
applyThemeCallback = defaultApplyThemeCallback;
|
|
45
|
+
/** Contains the user's last selected theme, saving and loading it to disk for persistence. */
|
|
46
|
+
localStorageClient;
|
|
47
|
+
/** A callback to remove the global theme preference listener. */
|
|
48
|
+
removeThemePreferenceListener = listenTo(globalThis.matchMedia(darkSchemeMediaQuery), 'change', (event) => {
|
|
49
|
+
assert.instanceOf(event, MediaQueryListEvent);
|
|
50
|
+
if (this.currentTheme === ViraThemeSelection.Auto) {
|
|
51
|
+
void this.applyThemeCallback({
|
|
52
|
+
useDarkTheme: event.matches,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
constructor(params = {}) {
|
|
57
|
+
if (params.applyTheme) {
|
|
58
|
+
this.applyThemeCallback = params.applyTheme;
|
|
59
|
+
}
|
|
60
|
+
this.localStorageClient = new LocalStorageClient(themeStorageShapes, {
|
|
61
|
+
storeName: params.storeName || 'vira-theme',
|
|
62
|
+
});
|
|
63
|
+
this.applySelection(this.currentTheme);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The currently selected theme. If you use multiple clients to set the same theme, this might
|
|
67
|
+
* get out of sync.
|
|
68
|
+
*/
|
|
69
|
+
get currentTheme() {
|
|
70
|
+
return this.localStorageClient.get.selectedTheme()?.theme || ViraThemeSelection.Auto;
|
|
71
|
+
}
|
|
72
|
+
/** Set the selected theme. */
|
|
73
|
+
setSelectedTheme(selection) {
|
|
74
|
+
this.applySelection(selection);
|
|
75
|
+
this.localStorageClient.set.selectedTheme({
|
|
76
|
+
theme: selection,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Cleanup internal state and listeners. */
|
|
80
|
+
destroy() {
|
|
81
|
+
this.removeThemePreferenceListener();
|
|
82
|
+
this.localStorageClient.destroy();
|
|
83
|
+
}
|
|
84
|
+
/** Apply the currently selected theme. */
|
|
85
|
+
applySelection(selection) {
|
|
86
|
+
const useDarkTheme = selection === ViraThemeSelection.Dark ||
|
|
87
|
+
(selection === ViraThemeSelection.Auto &&
|
|
88
|
+
globalThis.matchMedia(darkSchemeMediaQuery).matches);
|
|
89
|
+
void this.applyThemeCallback({
|
|
90
|
+
useDarkTheme,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vira",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.20.0",
|
|
4
4
|
"description": "A simple and highly versatile design system using element-vir.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design",
|
|
@@ -42,10 +42,12 @@
|
|
|
42
42
|
"@augment-vir/common": "^31.70.1",
|
|
43
43
|
"@augment-vir/web": "^31.70.1",
|
|
44
44
|
"@electrovir/color": "^1.7.9",
|
|
45
|
+
"@electrovir/local-storage-client": "^0.1.0",
|
|
45
46
|
"date-vir": "^8.3.2",
|
|
46
47
|
"device-navigation": "^4.5.5",
|
|
47
48
|
"json-schema-to-ts": "^3.1.1",
|
|
48
49
|
"lit-css-vars": "^3.6.2",
|
|
50
|
+
"object-shape-tester": "^6.13.0",
|
|
49
51
|
"observavir": "^2.3.2",
|
|
50
52
|
"page-active": "^1.0.3",
|
|
51
53
|
"spa-router-vir": "^6.6.0",
|