vira 33.5.1 → 33.7.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-slider.element.d.ts +35 -0
- package/dist/elements/vira-slider.element.js +179 -0
- package/dist/util/vira-theme-client.d.ts +56 -7
- package/dist/util/vira-theme-client.js +60 -19
- package/package.json +1 -1
package/dist/elements/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export * from './vira-link.element.js';
|
|
|
33
33
|
export * from './vira-modal.element.js';
|
|
34
34
|
export * from './vira-progress.element.js';
|
|
35
35
|
export * from './vira-relative-time.element.js';
|
|
36
|
+
export * from './vira-slider.element.js';
|
|
36
37
|
export * from './vira-tabs.element.js';
|
|
37
38
|
export * from './vira-tag.element.js';
|
|
38
39
|
export * from './vira-text-area.element.js';
|
package/dist/elements/index.js
CHANGED
|
@@ -33,6 +33,7 @@ export * from './vira-link.element.js';
|
|
|
33
33
|
export * from './vira-modal.element.js';
|
|
34
34
|
export * from './vira-progress.element.js';
|
|
35
35
|
export * from './vira-relative-time.element.js';
|
|
36
|
+
export * from './vira-slider.element.js';
|
|
36
37
|
export * from './vira-tabs.element.js';
|
|
37
38
|
export * from './vira-tag.element.js';
|
|
38
39
|
export * from './vira-text-area.element.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
/**
|
|
3
|
+
* Inputs for {@link ViraSlider}.
|
|
4
|
+
*
|
|
5
|
+
* @category Internal
|
|
6
|
+
*/
|
|
7
|
+
export type ViraSliderInputs = {
|
|
8
|
+
value: number;
|
|
9
|
+
} & PartialWithUndefined<{
|
|
10
|
+
/** Defaults to `0`. */
|
|
11
|
+
min: number;
|
|
12
|
+
/** Defaults to `100`. */
|
|
13
|
+
max: number;
|
|
14
|
+
/** Defaults to `1`. */
|
|
15
|
+
step: number;
|
|
16
|
+
isDisabled: boolean;
|
|
17
|
+
/** The slider's accessible name. It is not rendered visibly. */
|
|
18
|
+
label: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* A themed replacement for `<input type="range">`. The track, fill, and thumb are drawn with Vira's
|
|
22
|
+
* form colors, so they follow the current theme (including dark mode) where a browser's native
|
|
23
|
+
* range styling does not. An invisible native range input sits on top, so keyboard, pointer, and
|
|
24
|
+
* screen reader behavior are the browser's own.
|
|
25
|
+
*
|
|
26
|
+
* `valueChange` fires on every change while dragging. The slider is controlled: it only moves when
|
|
27
|
+
* `value` is updated in response.
|
|
28
|
+
*
|
|
29
|
+
* @category Input
|
|
30
|
+
* @category Elements
|
|
31
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-slider
|
|
32
|
+
*/
|
|
33
|
+
export declare const ViraSlider: import("element-vir").DeclarativeElementDefinition<"vira-slider", Readonly<ViraSliderInputs>, {}, {
|
|
34
|
+
valueChange: import("element-vir").DefineElementEvent<number>;
|
|
35
|
+
}, "vira-slider-", "vira-slider-track-height" | "vira-slider-thumb-size" | "vira-slider-track-color" | "vira-slider-fill-color" | "vira-slider-thumb-color" | "vira-slider-thumb-hover-color" | "vira-slider-thumb-active-color", readonly [], readonly []>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { extractEventTarget } from '@augment-vir/web';
|
|
2
|
+
import { classMap, css, defineElementEvent, html, ifDefined, listen } from 'element-vir';
|
|
3
|
+
import { viraDisabledStyles } from '../styles/disabled.js';
|
|
4
|
+
import { createFocusStyles } from '../styles/focus.js';
|
|
5
|
+
import { viraFormCssVars } from '../styles/form-styles.js';
|
|
6
|
+
import { defineViraElement } from '../util/define-vira-element.js';
|
|
7
|
+
/**
|
|
8
|
+
* A themed replacement for `<input type="range">`. The track, fill, and thumb are drawn with Vira's
|
|
9
|
+
* form colors, so they follow the current theme (including dark mode) where a browser's native
|
|
10
|
+
* range styling does not. An invisible native range input sits on top, so keyboard, pointer, and
|
|
11
|
+
* screen reader behavior are the browser's own.
|
|
12
|
+
*
|
|
13
|
+
* `valueChange` fires on every change while dragging. The slider is controlled: it only moves when
|
|
14
|
+
* `value` is updated in response.
|
|
15
|
+
*
|
|
16
|
+
* @category Input
|
|
17
|
+
* @category Elements
|
|
18
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-slider
|
|
19
|
+
*/
|
|
20
|
+
export const ViraSlider = defineViraElement()({
|
|
21
|
+
tagName: 'vira-slider',
|
|
22
|
+
cssVars: {
|
|
23
|
+
'vira-slider-track-height': '4px',
|
|
24
|
+
'vira-slider-thumb-size': '16px',
|
|
25
|
+
'vira-slider-track-color': viraFormCssVars['vira-form-border-color'].value,
|
|
26
|
+
'vira-slider-fill-color': viraFormCssVars['vira-form-accent-primary-color'].value,
|
|
27
|
+
'vira-slider-thumb-color': viraFormCssVars['vira-form-accent-primary-color'].value,
|
|
28
|
+
'vira-slider-thumb-hover-color': viraFormCssVars['vira-form-accent-primary-hover-color'].value,
|
|
29
|
+
'vira-slider-thumb-active-color': viraFormCssVars['vira-form-accent-primary-active-color'].value,
|
|
30
|
+
},
|
|
31
|
+
styles({ cssVars }) {
|
|
32
|
+
return css `
|
|
33
|
+
:host {
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
width: 160px;
|
|
36
|
+
vertical-align: middle;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.slider {
|
|
40
|
+
position: relative;
|
|
41
|
+
flex-grow: 1;
|
|
42
|
+
height: ${cssVars['vira-slider-thumb-size'].value};
|
|
43
|
+
|
|
44
|
+
&.disabled {
|
|
45
|
+
${viraDisabledStyles};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.track,
|
|
50
|
+
.fill {
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: 50%;
|
|
53
|
+
left: 0;
|
|
54
|
+
height: ${cssVars['vira-slider-track-height'].value};
|
|
55
|
+
transform: translateY(-50%);
|
|
56
|
+
border-radius: ${cssVars['vira-slider-track-height'].value};
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.track {
|
|
61
|
+
width: 100%;
|
|
62
|
+
background-color: ${cssVars['vira-slider-track-color'].value};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.fill {
|
|
66
|
+
background-color: ${cssVars['vira-slider-fill-color'].value};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.thumb {
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: 0;
|
|
72
|
+
width: ${cssVars['vira-slider-thumb-size'].value};
|
|
73
|
+
height: ${cssVars['vira-slider-thumb-size'].value};
|
|
74
|
+
box-sizing: border-box;
|
|
75
|
+
border-radius: 50%;
|
|
76
|
+
border: 2px solid ${viraFormCssVars['vira-form-background-color'].value};
|
|
77
|
+
background-color: ${cssVars['vira-slider-thumb-color'].value};
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
input {
|
|
82
|
+
position: absolute;
|
|
83
|
+
inset: 0;
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 100%;
|
|
86
|
+
margin: 0;
|
|
87
|
+
padding: 0;
|
|
88
|
+
appearance: none;
|
|
89
|
+
background: transparent;
|
|
90
|
+
opacity: 0;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
The invisible native thumb must match the drawn thumb's width so the browser
|
|
95
|
+
maps pointer positions to the same values the drawn thumb shows.
|
|
96
|
+
*/
|
|
97
|
+
&::-webkit-slider-thumb {
|
|
98
|
+
appearance: none;
|
|
99
|
+
width: ${cssVars['vira-slider-thumb-size'].value};
|
|
100
|
+
height: ${cssVars['vira-slider-thumb-size'].value};
|
|
101
|
+
}
|
|
102
|
+
&::-moz-range-thumb {
|
|
103
|
+
width: ${cssVars['vira-slider-thumb-size'].value};
|
|
104
|
+
height: ${cssVars['vira-slider-thumb-size'].value};
|
|
105
|
+
border: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&:hover ~ .thumb {
|
|
109
|
+
background-color: ${cssVars['vira-slider-thumb-hover-color'].value};
|
|
110
|
+
}
|
|
111
|
+
&:active ~ .thumb {
|
|
112
|
+
background-color: ${cssVars['vira-slider-thumb-active-color'].value};
|
|
113
|
+
}
|
|
114
|
+
&:focus-visible:not(:active) ~ .thumb::after {
|
|
115
|
+
${createFocusStyles({
|
|
116
|
+
elementBorderSize: '2px',
|
|
117
|
+
borderRadius: '50%',
|
|
118
|
+
noNesting: true,
|
|
119
|
+
})}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
`;
|
|
123
|
+
},
|
|
124
|
+
events: {
|
|
125
|
+
valueChange: defineElementEvent(),
|
|
126
|
+
},
|
|
127
|
+
render({ inputs, dispatch, events, cssVars }) {
|
|
128
|
+
const min = inputs.min ?? 0;
|
|
129
|
+
const max = inputs.max ?? 100;
|
|
130
|
+
const fraction = max > min ? Math.min(1, Math.max(0, (inputs.value - min) / (max - min))) : 0;
|
|
131
|
+
const thumbOffset = css `calc((100% - ${cssVars['vira-slider-thumb-size'].value}) * ${fraction})`;
|
|
132
|
+
return html `
|
|
133
|
+
<div
|
|
134
|
+
class="slider ${classMap({
|
|
135
|
+
disabled: !!inputs.isDisabled,
|
|
136
|
+
})}"
|
|
137
|
+
>
|
|
138
|
+
<div class="track"></div>
|
|
139
|
+
<div
|
|
140
|
+
class="fill"
|
|
141
|
+
style=${css `
|
|
142
|
+
width: calc(
|
|
143
|
+
${thumbOffset} + ${cssVars['vira-slider-thumb-size'].value} / 2
|
|
144
|
+
);
|
|
145
|
+
`}
|
|
146
|
+
></div>
|
|
147
|
+
<input
|
|
148
|
+
type="range"
|
|
149
|
+
min=${min}
|
|
150
|
+
max=${max}
|
|
151
|
+
step=${inputs.step ?? 1}
|
|
152
|
+
aria-label=${ifDefined(inputs.label || undefined)}
|
|
153
|
+
?disabled=${inputs.isDisabled}
|
|
154
|
+
.value=${String(inputs.value)}
|
|
155
|
+
${listen('input', (event) => {
|
|
156
|
+
const inputElement = extractEventTarget(event, HTMLInputElement);
|
|
157
|
+
const newValue = Number(inputElement.value);
|
|
158
|
+
/**
|
|
159
|
+
* Snap the native input back to the controlled value; it only moves once
|
|
160
|
+
* the new value is assigned back in.
|
|
161
|
+
*/
|
|
162
|
+
inputElement.value = String(inputs.value);
|
|
163
|
+
if (newValue !== inputs.value) {
|
|
164
|
+
dispatch(new events.valueChange({
|
|
165
|
+
detail: newValue,
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
})}
|
|
169
|
+
/>
|
|
170
|
+
<div
|
|
171
|
+
class="thumb"
|
|
172
|
+
style=${css `
|
|
173
|
+
left: ${thumbOffset};
|
|
174
|
+
`}
|
|
175
|
+
></div>
|
|
176
|
+
</div>
|
|
177
|
+
`;
|
|
178
|
+
},
|
|
179
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
1
|
+
import { type MaybePromise, type PartialWithUndefined, type RequireOneOrNone } from '@augment-vir/common';
|
|
2
2
|
import { LocalStorageClient } from '@electrovir/local-storage-client';
|
|
3
|
+
import { ListenTarget } from 'typed-event-target';
|
|
3
4
|
/**
|
|
4
5
|
* A user-facing selection of which theme to display. `Auto` follows the system color scheme; the
|
|
5
6
|
* other values force a specific theme regardless of system preference.
|
|
@@ -11,6 +12,39 @@ export declare enum ViraThemeSelection {
|
|
|
11
12
|
Dark = "dark",
|
|
12
13
|
Auto = "auto"
|
|
13
14
|
}
|
|
15
|
+
declare const ViraThemeAppliedEvent_base: (new (eventInitDict: {
|
|
16
|
+
bubbles?: boolean;
|
|
17
|
+
cancelable?: boolean;
|
|
18
|
+
composed?: boolean;
|
|
19
|
+
detail: ViraThemeSelection.Light | ViraThemeSelection.Dark;
|
|
20
|
+
}) => import("typed-event-target").TypedCustomEvent<ViraThemeSelection.Light | ViraThemeSelection.Dark, "vira-theme-applied">) & Pick<{
|
|
21
|
+
new (type: string, eventInitDict?: EventInit): Event;
|
|
22
|
+
prototype: Event;
|
|
23
|
+
readonly NONE: 0;
|
|
24
|
+
readonly CAPTURING_PHASE: 1;
|
|
25
|
+
readonly AT_TARGET: 2;
|
|
26
|
+
readonly BUBBLING_PHASE: 3;
|
|
27
|
+
}, "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE" | "prototype"> & Pick<import("typed-event-target").TypedCustomEvent<ViraThemeSelection.Light | ViraThemeSelection.Dark, "vira-theme-applied">, "type">;
|
|
28
|
+
/**
|
|
29
|
+
* Fired by {@link ViraThemeClient} whenever its applied theme changes, including when the system
|
|
30
|
+
* color scheme flips while the selection is `Auto`. Not fired when a new selection leaves the
|
|
31
|
+
* applied theme the same (such as switching from `Dark` to `Auto` on a dark system).
|
|
32
|
+
*
|
|
33
|
+
* @category Internal
|
|
34
|
+
*/
|
|
35
|
+
export declare class ViraThemeAppliedEvent extends ViraThemeAppliedEvent_base {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Persists the selection of a {@link ViraThemeClient}.
|
|
39
|
+
*
|
|
40
|
+
* @category Internal
|
|
41
|
+
*/
|
|
42
|
+
export type ViraThemeStorage = {
|
|
43
|
+
/** The previously saved selection. `undefined` (nothing saved yet) is treated as `Auto`. */
|
|
44
|
+
init: ViraThemeSelection | undefined;
|
|
45
|
+
/** Called after the selection has already been applied, so it can be async. */
|
|
46
|
+
save(selection: ViraThemeSelection): MaybePromise<void>;
|
|
47
|
+
};
|
|
14
48
|
/**
|
|
15
49
|
* Used by {@link ViraThemeClient} to apply themes. By default, vira themes will be applied (via
|
|
16
50
|
* {@link defaultApplyThemeCallback}).
|
|
@@ -38,6 +72,7 @@ export type ViraThemeClientParams = PartialWithUndefined<{
|
|
|
38
72
|
* will be used.
|
|
39
73
|
*/
|
|
40
74
|
applyTheme: ApplyThemeCallback;
|
|
75
|
+
}> & RequireOneOrNone<{
|
|
41
76
|
/**
|
|
42
77
|
* Override the LocalStorage store name used for theme persistence. Useful if a single page
|
|
43
78
|
* hosts multiple isolated theme clients.
|
|
@@ -45,6 +80,11 @@ export type ViraThemeClientParams = PartialWithUndefined<{
|
|
|
45
80
|
* @default 'vira-theme'
|
|
46
81
|
*/
|
|
47
82
|
storeName: string;
|
|
83
|
+
/**
|
|
84
|
+
* Where to persist the selected theme. If not provided, the selection is persisted in
|
|
85
|
+
* LocalStorage under `storeName`.
|
|
86
|
+
*/
|
|
87
|
+
storage: ViraThemeStorage;
|
|
48
88
|
}>;
|
|
49
89
|
declare const themeStorageShapes: {
|
|
50
90
|
selectedTheme: import("object-shape-tester").Shape<{
|
|
@@ -53,18 +93,25 @@ declare const themeStorageShapes: {
|
|
|
53
93
|
};
|
|
54
94
|
/**
|
|
55
95
|
* Tracks the user's {@link ViraThemeSelection} and bridges it to a consumer-supplied `applyTheme`
|
|
56
|
-
* callback. Persists the selection
|
|
57
|
-
*
|
|
96
|
+
* callback. Persists the selection through `storage` (LocalStorage by default), and listens for
|
|
97
|
+
* system color-scheme changes to re-apply the theme in auto mode without persisting. Listen for
|
|
98
|
+
* {@link ViraThemeAppliedEvent} to react to the displayed theme changing.
|
|
58
99
|
*
|
|
59
100
|
* The initial theme is applied during construction.
|
|
60
101
|
*
|
|
61
102
|
* @category Util
|
|
62
103
|
*/
|
|
63
|
-
export declare class ViraThemeClient {
|
|
104
|
+
export declare class ViraThemeClient extends ListenTarget<ViraThemeAppliedEvent> {
|
|
64
105
|
/** The callback that will be called to apply a new theme. */
|
|
65
106
|
protected readonly applyThemeCallback: ApplyThemeCallback;
|
|
66
|
-
/**
|
|
67
|
-
protected readonly localStorageClient: LocalStorageClient<typeof themeStorageShapes
|
|
107
|
+
/** The default storage, only created when no `storage` param is given. */
|
|
108
|
+
protected readonly localStorageClient: LocalStorageClient<typeof themeStorageShapes> | undefined;
|
|
109
|
+
/** Persists the user's selected theme. */
|
|
110
|
+
protected readonly storage: ViraThemeStorage;
|
|
111
|
+
/** The user's selected theme. */
|
|
112
|
+
protected selectedTheme: ViraThemeSelection;
|
|
113
|
+
/** The theme that is currently displayed. */
|
|
114
|
+
protected appliedTheme: Exclude<ViraThemeSelection, ViraThemeSelection.Auto> | undefined;
|
|
68
115
|
/** A callback to remove the global theme preference listener. */
|
|
69
116
|
protected readonly removeThemePreferenceListener: () => void;
|
|
70
117
|
constructor(params?: Readonly<ViraThemeClientParams>);
|
|
@@ -73,11 +120,13 @@ export declare class ViraThemeClient {
|
|
|
73
120
|
* get out of sync.
|
|
74
121
|
*/
|
|
75
122
|
getCurrentTheme(): ViraThemeSelection;
|
|
123
|
+
/** The theme that is currently displayed, with `Auto` resolved from the system color scheme. */
|
|
124
|
+
getAppliedTheme(): Exclude<ViraThemeSelection, ViraThemeSelection.Auto>;
|
|
76
125
|
/** Set the selected theme. */
|
|
77
126
|
setSelectedTheme(selection: ViraThemeSelection): void;
|
|
78
127
|
/** Cleanup internal state and listeners. */
|
|
79
128
|
destroy(): void;
|
|
80
|
-
/** Apply the
|
|
129
|
+
/** Apply the given selection and fire {@link ViraThemeAppliedEvent} if the result changed. */
|
|
81
130
|
protected applySelection(selection: ViraThemeSelection): void;
|
|
82
131
|
}
|
|
83
132
|
export {};
|
|
@@ -2,7 +2,7 @@ import { assert } from '@augment-vir/assert';
|
|
|
2
2
|
import { LocalStorageClient } from '@electrovir/local-storage-client';
|
|
3
3
|
import { defineShape, enumShape } from 'object-shape-tester';
|
|
4
4
|
import { applyColorThemeViaStyleElement } from 'theme-vir/dist/color-theme/apply-color-theme.js';
|
|
5
|
-
import { listenTo } from 'typed-event-target';
|
|
5
|
+
import { defineTypedCustomEvent, ListenTarget, listenTo } from 'typed-event-target';
|
|
6
6
|
import { viraTheme, viraThemeDarkOverride } from '../styles/vira-color-theme.js';
|
|
7
7
|
/**
|
|
8
8
|
* A user-facing selection of which theme to display. `Auto` follows the system color scheme; the
|
|
@@ -16,6 +16,15 @@ export var ViraThemeSelection;
|
|
|
16
16
|
ViraThemeSelection["Dark"] = "dark";
|
|
17
17
|
ViraThemeSelection["Auto"] = "auto";
|
|
18
18
|
})(ViraThemeSelection || (ViraThemeSelection = {}));
|
|
19
|
+
/**
|
|
20
|
+
* Fired by {@link ViraThemeClient} whenever its applied theme changes, including when the system
|
|
21
|
+
* color scheme flips while the selection is `Auto`. Not fired when a new selection leaves the
|
|
22
|
+
* applied theme the same (such as switching from `Dark` to `Auto` on a dark system).
|
|
23
|
+
*
|
|
24
|
+
* @category Internal
|
|
25
|
+
*/
|
|
26
|
+
export class ViraThemeAppliedEvent extends defineTypedCustomEvent()('vira-theme-applied') {
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* Default implementation of {@link ApplyThemeCallback}, which simply applies Vira themes.
|
|
21
30
|
*
|
|
@@ -32,62 +41,94 @@ const themeStorageShapes = {
|
|
|
32
41
|
};
|
|
33
42
|
/**
|
|
34
43
|
* Tracks the user's {@link ViraThemeSelection} and bridges it to a consumer-supplied `applyTheme`
|
|
35
|
-
* callback. Persists the selection
|
|
36
|
-
*
|
|
44
|
+
* callback. Persists the selection through `storage` (LocalStorage by default), and listens for
|
|
45
|
+
* system color-scheme changes to re-apply the theme in auto mode without persisting. Listen for
|
|
46
|
+
* {@link ViraThemeAppliedEvent} to react to the displayed theme changing.
|
|
37
47
|
*
|
|
38
48
|
* The initial theme is applied during construction.
|
|
39
49
|
*
|
|
40
50
|
* @category Util
|
|
41
51
|
*/
|
|
42
|
-
export class ViraThemeClient {
|
|
52
|
+
export class ViraThemeClient extends ListenTarget {
|
|
43
53
|
/** The callback that will be called to apply a new theme. */
|
|
44
54
|
applyThemeCallback = defaultApplyThemeCallback;
|
|
45
|
-
/**
|
|
55
|
+
/** The default storage, only created when no `storage` param is given. */
|
|
46
56
|
localStorageClient;
|
|
57
|
+
/** Persists the user's selected theme. */
|
|
58
|
+
storage;
|
|
59
|
+
/** The user's selected theme. */
|
|
60
|
+
selectedTheme;
|
|
61
|
+
/** The theme that is currently displayed. */
|
|
62
|
+
appliedTheme;
|
|
47
63
|
/** A callback to remove the global theme preference listener. */
|
|
48
64
|
removeThemePreferenceListener = listenTo(globalThis.matchMedia(darkSchemeMediaQuery), 'change', (event) => {
|
|
49
65
|
assert.instanceOf(event, MediaQueryListEvent);
|
|
50
|
-
if (this.
|
|
51
|
-
|
|
52
|
-
useDarkTheme: event.matches,
|
|
53
|
-
});
|
|
66
|
+
if (this.selectedTheme === ViraThemeSelection.Auto) {
|
|
67
|
+
this.applySelection(ViraThemeSelection.Auto);
|
|
54
68
|
}
|
|
55
69
|
});
|
|
56
70
|
constructor(params = {}) {
|
|
71
|
+
super();
|
|
57
72
|
if (params.applyTheme) {
|
|
58
73
|
this.applyThemeCallback = params.applyTheme;
|
|
59
74
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
75
|
+
if (params.storage) {
|
|
76
|
+
this.storage = params.storage;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const localStorageClient = new LocalStorageClient(themeStorageShapes, {
|
|
80
|
+
storeName: params.storeName || 'vira-theme',
|
|
81
|
+
});
|
|
82
|
+
this.localStorageClient = localStorageClient;
|
|
83
|
+
this.storage = {
|
|
84
|
+
init: localStorageClient.get.selectedTheme()?.theme,
|
|
85
|
+
save(theme) {
|
|
86
|
+
localStorageClient.set.selectedTheme({
|
|
87
|
+
theme,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
this.selectedTheme = this.storage.init || ViraThemeSelection.Auto;
|
|
93
|
+
this.applySelection(this.selectedTheme);
|
|
64
94
|
}
|
|
65
95
|
/**
|
|
66
96
|
* The currently selected theme. If you use multiple clients to set the same theme, this might
|
|
67
97
|
* get out of sync.
|
|
68
98
|
*/
|
|
69
99
|
getCurrentTheme() {
|
|
70
|
-
return this.
|
|
100
|
+
return this.selectedTheme;
|
|
101
|
+
}
|
|
102
|
+
/** The theme that is currently displayed, with `Auto` resolved from the system color scheme. */
|
|
103
|
+
getAppliedTheme() {
|
|
104
|
+
return this.appliedTheme || ViraThemeSelection.Light;
|
|
71
105
|
}
|
|
72
106
|
/** Set the selected theme. */
|
|
73
107
|
setSelectedTheme(selection) {
|
|
108
|
+
this.selectedTheme = selection;
|
|
74
109
|
this.applySelection(selection);
|
|
75
|
-
this.
|
|
76
|
-
theme: selection,
|
|
77
|
-
});
|
|
110
|
+
void this.storage.save(selection);
|
|
78
111
|
}
|
|
79
112
|
/** Cleanup internal state and listeners. */
|
|
80
113
|
destroy() {
|
|
81
114
|
this.removeThemePreferenceListener();
|
|
82
|
-
this.localStorageClient
|
|
115
|
+
this.localStorageClient?.destroy();
|
|
116
|
+
super.destroy();
|
|
83
117
|
}
|
|
84
|
-
/** Apply the
|
|
118
|
+
/** Apply the given selection and fire {@link ViraThemeAppliedEvent} if the result changed. */
|
|
85
119
|
applySelection(selection) {
|
|
86
120
|
const useDarkTheme = selection === ViraThemeSelection.Dark ||
|
|
87
121
|
(selection === ViraThemeSelection.Auto &&
|
|
88
122
|
globalThis.matchMedia(darkSchemeMediaQuery).matches);
|
|
123
|
+
const appliedTheme = useDarkTheme ? ViraThemeSelection.Dark : ViraThemeSelection.Light;
|
|
89
124
|
void this.applyThemeCallback({
|
|
90
125
|
useDarkTheme,
|
|
91
126
|
});
|
|
127
|
+
if (appliedTheme !== this.appliedTheme) {
|
|
128
|
+
this.appliedTheme = appliedTheme;
|
|
129
|
+
this.dispatch(new ViraThemeAppliedEvent({
|
|
130
|
+
detail: appliedTheme,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
92
133
|
}
|
|
93
134
|
}
|