vira 33.6.0 → 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.
@@ -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 in LocalStorage via an internal `LocalStorageClient`, and
57
- * listens for system color-scheme changes to re-apply the theme in auto mode without persisting.
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
- /** Contains the user's last selected theme, saving and loading it to disk for persistence. */
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 currently selected theme. */
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 in LocalStorage via an internal `LocalStorageClient`, and
36
- * listens for system color-scheme changes to re-apply the theme in auto mode without persisting.
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
- /** Contains the user's last selected theme, saving and loading it to disk for persistence. */
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.getCurrentTheme() === ViraThemeSelection.Auto) {
51
- void this.applyThemeCallback({
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
- this.localStorageClient = new LocalStorageClient(themeStorageShapes, {
61
- storeName: params.storeName || 'vira-theme',
62
- });
63
- this.applySelection(this.getCurrentTheme());
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.localStorageClient.get.selectedTheme()?.theme || ViraThemeSelection.Auto;
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.localStorageClient.set.selectedTheme({
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.destroy();
115
+ this.localStorageClient?.destroy();
116
+ super.destroy();
83
117
  }
84
- /** Apply the currently selected theme. */
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vira",
3
- "version": "33.6.0",
3
+ "version": "33.7.0",
4
4
  "description": "A simple and highly versatile design system using element-vir.",
5
5
  "keywords": [
6
6
  "design",