@design.estate/dees-domtools 5.0.0 → 5.1.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_bundle/bundle.js +129 -129
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_bundle/bundle.js.third-party-notices.json +28 -2
- package/dist_bundle/bundle.js.third-party-notices.txt +61 -3
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/domtools.classes.domtools.d.ts +0 -3
- package/dist_ts/domtools.classes.domtools.js +16 -10
- package/dist_ts/domtools.classes.keyboard.js +1 -1
- package/dist_ts/domtools.classes.thememanager.d.ts +32 -3
- package/dist_ts/domtools.classes.thememanager.js +80 -6
- package/dist_ts/domtools.elementbasic.js +1 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/package.json +7 -6
- package/readme.md +17 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/domtools.classes.domtools.ts +15 -12
- package/ts/domtools.classes.thememanager.ts +86 -5
- package/ts/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design.estate/dees-domtools",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A package providing tools to simplify complex CSS structures and web development tasks, featuring TypeScript support and integration with various web technologies.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -9,17 +9,18 @@
|
|
|
9
9
|
"author": "Lossless GmbH",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@git.zone/tsbuild": "^
|
|
13
|
-
"@git.zone/tsbundle": "^3.1
|
|
14
|
-
"@git.zone/tstest": "^7.
|
|
15
|
-
"@types/node": "
|
|
12
|
+
"@git.zone/tsbuild": "^6.0.2",
|
|
13
|
+
"@git.zone/tsbundle": "^3.2.1",
|
|
14
|
+
"@git.zone/tstest": "^7.2.0",
|
|
15
|
+
"@types/node": "26.6.4",
|
|
16
|
+
"typescript": "^6.0.3"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"@api.global/typedrequest": "^9.0.2",
|
|
19
20
|
"@design.estate/dees-comms": "^2.0.0",
|
|
20
21
|
"@push.rocks/lik": "^6.4.4",
|
|
21
22
|
"@push.rocks/smartjson": "^6.0.2",
|
|
22
|
-
"@push.rocks/smartmarkdown": "^3.
|
|
23
|
+
"@push.rocks/smartmarkdown": "^3.5.0",
|
|
23
24
|
"@push.rocks/smartpromise": "^4.3.0",
|
|
24
25
|
"@push.rocks/smartrouter": "^1.3.3",
|
|
25
26
|
"@push.rocks/smartrx": "^3.0.12",
|
package/readme.md
CHANGED
|
@@ -17,6 +17,7 @@ pnpm add @design.estate/dees-domtools
|
|
|
17
17
|
## What It Includes
|
|
18
18
|
|
|
19
19
|
- `DomTools` for one-time app bootstrap and shared browser services
|
|
20
|
+
- `ThemeManager` (the class behind `domtools.themeManager`) and its `TThemePreference` type, for a bright/dark theme that follows the system until the reader picks one
|
|
20
21
|
- `breakpoints` helpers for viewport and container-query driven Lit CSS
|
|
21
22
|
- `css.cssGridColumns()` for simple grid-template generation
|
|
22
23
|
- `elementBasic` helpers for Lit base styles and one-time global CSS setup
|
|
@@ -144,6 +145,20 @@ await themeManager.enableAutomaticGlobalThemeChange();
|
|
|
144
145
|
|
|
145
146
|
The theme manager starts from `prefers-color-scheme` and publishes updates through an RxJS `ReplaySubject<boolean>`.
|
|
146
147
|
|
|
148
|
+
### Theme preference
|
|
149
|
+
|
|
150
|
+
A theme toggle sets the reader's preference rather than the theme of the moment:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
themeManager.preference; // 'system' | 'bright' | 'dark' (TThemePreference)
|
|
154
|
+
themeManager.setPreference(themeManager.goBrightBoolean ? 'dark' : 'bright'); // true when kept
|
|
155
|
+
themeManager.setPreference('system'); // follow prefers-color-scheme again
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`'system'` follows `prefers-color-scheme`; `'bright'` and `'dark'` hold that theme while the system's scheme changes. A picked preference is kept in `localStorage` under `ThemeManager.preferenceStorageKey` (`dees-domtools:theme-preference`, per browser and origin; `'system'` removes the key) and is read before the theme manager's first emission, so a reload starts in it without showing the system theme first. Another tab's pick reaches every open page of the origin through the `storage` event. `setPreference()` returns `false` when the browser refuses the page its storage: the pick then holds for this page only, and it throws on a value that is no `TThemePreference`. An unknown stored value counts as no pick. `goBright()`, `goDark()` and `toggleDarkBright()` set the theme for now and leave the preference alone.
|
|
159
|
+
|
|
160
|
+
The page's `DomTools` is one instance on `globalThis` shared by every copy of this package, so it is the copy that creates it whose `ThemeManager` serves the page: every copy in a bundle must be a version with `setPreference()` for a toggle to rely on it.
|
|
161
|
+
|
|
147
162
|
`enableAutomaticGlobalThemeChange()` waits for `domReady`, so it is safe to call before `document.body` exists.
|
|
148
163
|
|
|
149
164
|
## Keyboard Shortcuts
|
|
@@ -305,9 +320,10 @@ Repeated callers receive the first result, and repeated failures re-throw the st
|
|
|
305
320
|
## Extra Exports
|
|
306
321
|
|
|
307
322
|
```ts
|
|
308
|
-
import { TypedRequest, plugins } from '@design.estate/dees-domtools';
|
|
323
|
+
import { TypedRequest, ThemeManager, type TThemePreference, plugins } from '@design.estate/dees-domtools';
|
|
309
324
|
```
|
|
310
325
|
|
|
326
|
+
- `ThemeManager` is the class of `domtools.themeManager`, exported for `ThemeManager.preferenceStorageKey` and for typing; use the page's instance rather than constructing one, and `TThemePreference` (`'system' | 'bright' | 'dark'`) for a preference (see Theme preference)
|
|
311
327
|
- `TypedRequest` is re-exported from `@api.global/typedrequest` 9 for typed request flows
|
|
312
328
|
- `plugins` exposes the underlying modules used by the package, including `smartrouter`, `smartstate`, `smartrx`, `smartpromise`, and `typedrequest`
|
|
313
329
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@design.estate/dees-domtools',
|
|
6
|
-
version: '5.
|
|
6
|
+
version: '5.1.0',
|
|
7
7
|
description: 'A package providing tools to simplify complex CSS structures and web development tasks, featuring TypeScript support and integration with various web technologies.'
|
|
8
8
|
}
|
|
@@ -5,9 +5,12 @@ import { WebSetup } from '@push.rocks/websetup';
|
|
|
5
5
|
import { ThemeManager } from './domtools.classes.thememanager.js';
|
|
6
6
|
import { Keyboard } from './domtools.classes.keyboard.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The page's one DomTools lives on `globalThis` as `deesDomTools`, so every copy of this package on a page shares
|
|
10
|
+
* it. It is typed here, for this module only: a global declaration names each copy's own `DomTools`, and two copies
|
|
11
|
+
* in one program — two versions, or one installed twice for different peers — would clash on it (TS2403).
|
|
12
|
+
*/
|
|
13
|
+
const globalScope = globalThis as typeof globalThis & { deesDomTools?: DomTools };
|
|
11
14
|
|
|
12
15
|
export interface IDomToolsState {
|
|
13
16
|
virtualViewport: TViewport;
|
|
@@ -34,19 +37,19 @@ export class DomTools {
|
|
|
34
37
|
return domToolsInstance;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
if (
|
|
38
|
-
await
|
|
39
|
-
return
|
|
40
|
+
if (globalScope.deesDomTools && !globalScope.deesDomTools.disposed) {
|
|
41
|
+
await globalScope.deesDomTools.initializationPromise;
|
|
42
|
+
return globalScope.deesDomTools;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
if (!DomTools.initializationPromise) {
|
|
43
46
|
const domToolsInstance = new DomTools(optionsArg);
|
|
44
|
-
|
|
47
|
+
globalScope.deesDomTools = domToolsInstance;
|
|
45
48
|
DomTools.initializationPromise = domToolsInstance.initializationPromise
|
|
46
49
|
.then(() => domToolsInstance)
|
|
47
50
|
.catch((error) => {
|
|
48
|
-
if (
|
|
49
|
-
|
|
51
|
+
if (globalScope.deesDomTools === domToolsInstance) {
|
|
52
|
+
globalScope.deesDomTools = undefined;
|
|
50
53
|
}
|
|
51
54
|
DomTools.initializationPromise = null;
|
|
52
55
|
throw error;
|
|
@@ -60,7 +63,7 @@ export class DomTools {
|
|
|
60
63
|
* if you can, use the static asysnc .setupDomTools() function instead since it is safer to use.
|
|
61
64
|
*/
|
|
62
65
|
public static getGlobalDomToolsSync(): DomTools {
|
|
63
|
-
const globalDomTools =
|
|
66
|
+
const globalDomTools = globalScope.deesDomTools;
|
|
64
67
|
if (!globalDomTools || globalDomTools.disposed) {
|
|
65
68
|
throw new Error('You tried to access domtools synchronously too early');
|
|
66
69
|
}
|
|
@@ -265,8 +268,8 @@ export class DomTools {
|
|
|
265
268
|
}
|
|
266
269
|
this.managedDomNodes.length = 0;
|
|
267
270
|
|
|
268
|
-
if (
|
|
269
|
-
|
|
271
|
+
if (globalScope.deesDomTools === this) {
|
|
272
|
+
globalScope.deesDomTools = undefined;
|
|
270
273
|
DomTools.initializationPromise = null;
|
|
271
274
|
}
|
|
272
275
|
}
|
|
@@ -1,28 +1,73 @@
|
|
|
1
1
|
import { DomTools } from './domtools.classes.domtools.js';
|
|
2
2
|
import * as plugins from './domtools.plugins.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Which theme the reader asked for: `'system'` follows `prefers-color-scheme`, `'bright'` and
|
|
6
|
+
* `'dark'` hold that theme whatever the system does.
|
|
7
|
+
*/
|
|
8
|
+
export type TThemePreference = 'system' | 'bright' | 'dark';
|
|
9
|
+
|
|
10
|
+
const themePreferences: readonly TThemePreference[] = ['system', 'bright', 'dark'];
|
|
11
|
+
|
|
12
|
+
const isThemePreference = (valueArg: unknown): valueArg is TThemePreference => (
|
|
13
|
+
themePreferences.includes(valueArg as TThemePreference)
|
|
14
|
+
);
|
|
15
|
+
|
|
4
16
|
export class ThemeManager {
|
|
17
|
+
/**
|
|
18
|
+
* The `localStorage` key a picked preference is kept under, per browser and origin. `'system'` is
|
|
19
|
+
* the absence of the key.
|
|
20
|
+
*/
|
|
21
|
+
public static readonly preferenceStorageKey = 'dees-domtools:theme-preference';
|
|
22
|
+
|
|
5
23
|
public domtoolsRef: DomTools;
|
|
6
24
|
|
|
7
25
|
public goBrightBoolean = false;
|
|
26
|
+
/** The reader's preference; the theme follows the system only while it is `'system'`. */
|
|
27
|
+
public preference: TThemePreference = 'system';
|
|
8
28
|
public preferredColorSchemeMediaMatch = window.matchMedia('(prefers-color-scheme: light)');
|
|
9
29
|
|
|
10
30
|
public themeObservable = new plugins.smartrx.rxjs.ReplaySubject<boolean>(1);
|
|
11
31
|
private automaticGlobalThemeChangeSubscription: plugins.smartrx.rxjs.Subscription | null = null;
|
|
12
32
|
private readonly preferredColorSchemeChangeHandler = (eventArg: MediaQueryListEvent) => {
|
|
33
|
+
if (this.preference !== 'system') return;
|
|
13
34
|
this.goBrightBoolean = eventArg.matches;
|
|
14
35
|
this.updateAllConnectedElements();
|
|
15
36
|
};
|
|
37
|
+
/** Another tab of this origin picked a preference; this page follows it. */
|
|
38
|
+
private readonly storageHandler = (eventArg: StorageEvent) => {
|
|
39
|
+
if (eventArg.storageArea !== window.localStorage) return;
|
|
40
|
+
if (eventArg.key !== ThemeManager.preferenceStorageKey && eventArg.key !== null) return;
|
|
41
|
+
// A cleared storage (`key` null) forgets the pick as well.
|
|
42
|
+
const value = eventArg.key === null ? null : eventArg.newValue;
|
|
43
|
+
this.applyPreference(isThemePreference(value) ? value : 'system');
|
|
44
|
+
};
|
|
16
45
|
|
|
17
46
|
constructor(domtoolsRefArg: DomTools) {
|
|
18
47
|
this.domtoolsRef = domtoolsRefArg;
|
|
19
48
|
|
|
20
|
-
//
|
|
21
|
-
this.
|
|
49
|
+
// The stored preference is read before the first emission, so a page never flashes the system theme.
|
|
50
|
+
this.preference = ThemeManager.storedPreference();
|
|
51
|
+
this.goBrightBoolean = this.resolveBright();
|
|
22
52
|
this.preferredColorSchemeMediaMatch.addEventListener('change', this.preferredColorSchemeChangeHandler);
|
|
53
|
+
window.addEventListener('storage', this.storageHandler);
|
|
23
54
|
this.updateAllConnectedElements();
|
|
24
55
|
}
|
|
25
56
|
|
|
57
|
+
/**
|
|
58
|
+
* The preference kept in this browser. A browser that does not let the page read its storage, or a
|
|
59
|
+
* value this version does not know, keeps no preference: the theme follows the system.
|
|
60
|
+
*/
|
|
61
|
+
private static storedPreference(): TThemePreference {
|
|
62
|
+
let stored: string | null;
|
|
63
|
+
try {
|
|
64
|
+
stored = window.localStorage.getItem(ThemeManager.preferenceStorageKey);
|
|
65
|
+
} catch {
|
|
66
|
+
return 'system';
|
|
67
|
+
}
|
|
68
|
+
return isThemePreference(stored) ? stored : 'system';
|
|
69
|
+
}
|
|
70
|
+
|
|
26
71
|
public async enableAutomaticGlobalThemeChange() {
|
|
27
72
|
await this.domtoolsRef.domReady.promise;
|
|
28
73
|
if (!this.automaticGlobalThemeChangeSubscription) {
|
|
@@ -41,8 +86,43 @@ export class ThemeManager {
|
|
|
41
86
|
this.themeObservable.next(this.goBrightBoolean);
|
|
42
87
|
}
|
|
43
88
|
|
|
89
|
+
private resolveBright(): boolean {
|
|
90
|
+
return this.preference === 'system'
|
|
91
|
+
? this.preferredColorSchemeMediaMatch.matches
|
|
92
|
+
: this.preference === 'bright';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private applyPreference(preferenceArg: TThemePreference): void {
|
|
96
|
+
this.preference = preferenceArg;
|
|
97
|
+
const goBright = this.resolveBright();
|
|
98
|
+
if (goBright === this.goBrightBoolean) return;
|
|
99
|
+
this.goBrightBoolean = goBright;
|
|
100
|
+
this.updateAllConnectedElements();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Picks the theme and keeps the pick in this browser (`'system'` forgets it), so the next page
|
|
105
|
+
* load starts in it. While the preference is `'bright'` or `'dark'` the system's colour scheme no
|
|
106
|
+
* longer moves the theme. Returns whether the pick was kept: false when the browser refuses the
|
|
107
|
+
* page its storage, in which case it holds for this page only. Throws on a value that is no
|
|
108
|
+
* `TThemePreference`.
|
|
109
|
+
*/
|
|
110
|
+
public setPreference(preferenceArg: TThemePreference): boolean {
|
|
111
|
+
if (!isThemePreference(preferenceArg)) {
|
|
112
|
+
throw new Error(`Unknown theme preference "${String(preferenceArg)}".`);
|
|
113
|
+
}
|
|
114
|
+
this.applyPreference(preferenceArg);
|
|
115
|
+
try {
|
|
116
|
+
if (preferenceArg === 'system') window.localStorage.removeItem(ThemeManager.preferenceStorageKey);
|
|
117
|
+
else window.localStorage.setItem(ThemeManager.preferenceStorageKey, preferenceArg);
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
44
124
|
/**
|
|
45
|
-
* set the theme of the website to bright
|
|
125
|
+
* set the theme of the website to bright, for now: the preference is unchanged
|
|
46
126
|
*/
|
|
47
127
|
public goBright() {
|
|
48
128
|
this.goBrightBoolean = true;
|
|
@@ -50,7 +130,7 @@ export class ThemeManager {
|
|
|
50
130
|
}
|
|
51
131
|
|
|
52
132
|
/**
|
|
53
|
-
* set the theme of the website to dark
|
|
133
|
+
* set the theme of the website to dark, for now: the preference is unchanged
|
|
54
134
|
*/
|
|
55
135
|
public goDark() {
|
|
56
136
|
this.goBrightBoolean = false;
|
|
@@ -58,7 +138,7 @@ export class ThemeManager {
|
|
|
58
138
|
}
|
|
59
139
|
|
|
60
140
|
/**
|
|
61
|
-
* simply toggle between bright and dark
|
|
141
|
+
* simply toggle between bright and dark, for now: the preference is unchanged
|
|
62
142
|
*/
|
|
63
143
|
public toggleDarkBright() {
|
|
64
144
|
this.goBrightBoolean = !this.goBrightBoolean;
|
|
@@ -67,6 +147,7 @@ export class ThemeManager {
|
|
|
67
147
|
|
|
68
148
|
public dispose() {
|
|
69
149
|
this.preferredColorSchemeMediaMatch.removeEventListener('change', this.preferredColorSchemeChangeHandler);
|
|
150
|
+
window.removeEventListener('storage', this.storageHandler);
|
|
70
151
|
this.automaticGlobalThemeChangeSubscription?.unsubscribe();
|
|
71
152
|
this.automaticGlobalThemeChangeSubscription = null;
|
|
72
153
|
this.themeObservable.complete();
|
package/ts/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import * as css from './domtools.css.js';
|
|
|
6
6
|
|
|
7
7
|
export { css, breakpoints, elementBasic };
|
|
8
8
|
export { DomTools, type IDomToolsConstructorOptions } from './domtools.classes.domtools.js';
|
|
9
|
+
export { ThemeManager, type TThemePreference } from './domtools.classes.thememanager.js';
|
|
9
10
|
export { TypedRequest } from '@api.global/typedrequest';
|
|
10
11
|
export { type IWebSetupConstructorOptions } from '@push.rocks/websetup';
|
|
11
12
|
|