@design.estate/dees-domtools 2.5.3 → 2.5.6
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 +187 -40857
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/domtools.classes.domtools.d.ts +14 -4
- package/dist_ts/domtools.classes.domtools.js +115 -80
- package/dist_ts/domtools.classes.keyboard.d.ts +9 -0
- package/dist_ts/domtools.classes.keyboard.js +349 -14
- package/dist_ts/domtools.classes.scroller.d.ts +6 -1
- package/dist_ts/domtools.classes.scroller.js +70 -29
- package/dist_ts/domtools.classes.thememanager.d.ts +3 -0
- package/dist_ts/domtools.classes.thememanager.js +21 -9
- package/dist_ts/domtools.elementbasic.js +7 -4
- package/dist_ts/domtools.pluginexports.d.ts +1 -11
- package/dist_ts/domtools.pluginexports.js +2 -12
- package/license +2 -2
- package/package.json +4 -5
- package/readme.md +187 -371
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/domtools.classes.domtools.ts +136 -81
- package/ts/domtools.classes.keyboard.ts +374 -15
- package/ts/domtools.classes.scroller.ts +80 -29
- package/ts/domtools.classes.thememanager.ts +21 -8
- package/ts/domtools.elementbasic.ts +6 -3
- package/ts/domtools.pluginexports.ts +11 -22
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: '2.5.
|
|
6
|
+
version: '2.5.6',
|
|
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,6 +5,10 @@ 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
|
+
declare global {
|
|
9
|
+
var deesDomTools: DomTools | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
export interface IDomToolsState {
|
|
9
13
|
virtualViewport: TViewport;
|
|
10
14
|
jwt: string;
|
|
@@ -24,59 +28,43 @@ export class DomTools {
|
|
|
24
28
|
* setups domtools
|
|
25
29
|
*/
|
|
26
30
|
public static async setupDomTools(optionsArg: IDomToolsContructorOptions = {}): Promise<DomTools> {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
if (optionsArg.ignoreGlobal) {
|
|
32
|
+
const domToolsInstance = new DomTools(optionsArg);
|
|
33
|
+
await domToolsInstance.initializationPromise;
|
|
34
|
+
return domToolsInstance;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
domToolsInstance = globalThis.deesDomTools;
|
|
39
|
-
|
|
40
|
-
// lets make sure the dom is ready
|
|
41
|
-
const readyStateChangedFunc = () => {
|
|
42
|
-
if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
43
|
-
domToolsInstance.elements.headElement = document.querySelector('head');
|
|
44
|
-
domToolsInstance.elements.bodyElement = document.querySelector('body');
|
|
45
|
-
// Initialize keyboard now that document.body exists
|
|
46
|
-
domToolsInstance.keyboard = new Keyboard(document.body);
|
|
47
|
-
domToolsInstance.domReady.resolve();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
// Check current state immediately to avoid race condition
|
|
51
|
-
if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
52
|
-
readyStateChangedFunc();
|
|
53
|
-
} else {
|
|
54
|
-
document.addEventListener('readystatechange', readyStateChangedFunc);
|
|
55
|
-
}
|
|
56
|
-
domToolsInstance.domToolsReady.resolve();
|
|
57
|
-
} else {
|
|
58
|
-
domToolsInstance = globalThis.deesDomTools;
|
|
59
|
-
}
|
|
60
|
-
await domToolsInstance.domToolsReady.promise;
|
|
61
|
-
return domToolsInstance;
|
|
62
|
-
})();
|
|
63
|
-
return await DomTools.initializationPromise;
|
|
64
|
-
} else {
|
|
65
|
-
// ignoreGlobal case - create isolated instance
|
|
37
|
+
if (globalThis.deesDomTools && !globalThis.deesDomTools.disposed) {
|
|
38
|
+
await globalThis.deesDomTools.initializationPromise;
|
|
39
|
+
return globalThis.deesDomTools;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!DomTools.initializationPromise) {
|
|
66
43
|
const domToolsInstance = new DomTools(optionsArg);
|
|
67
|
-
|
|
44
|
+
globalThis.deesDomTools = domToolsInstance;
|
|
45
|
+
DomTools.initializationPromise = domToolsInstance.initializationPromise
|
|
46
|
+
.then(() => domToolsInstance)
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
if (globalThis.deesDomTools === domToolsInstance) {
|
|
49
|
+
globalThis.deesDomTools = undefined;
|
|
50
|
+
}
|
|
51
|
+
DomTools.initializationPromise = null;
|
|
52
|
+
throw error;
|
|
53
|
+
});
|
|
68
54
|
}
|
|
55
|
+
|
|
56
|
+
return await DomTools.initializationPromise;
|
|
69
57
|
}
|
|
70
58
|
|
|
71
59
|
/**
|
|
72
60
|
* if you can, use the static asysnc .setupDomTools() function instead since it is safer to use.
|
|
73
61
|
*/
|
|
74
62
|
public static getGlobalDomToolsSync(): DomTools {
|
|
75
|
-
const globalDomTools
|
|
76
|
-
if (!globalDomTools) {
|
|
63
|
+
const globalDomTools = globalThis.deesDomTools;
|
|
64
|
+
if (!globalDomTools || globalDomTools.disposed) {
|
|
77
65
|
throw new Error('You tried to access domtools synchronously too early');
|
|
78
66
|
}
|
|
79
|
-
return
|
|
67
|
+
return globalDomTools;
|
|
80
68
|
}
|
|
81
69
|
|
|
82
70
|
// ========
|
|
@@ -118,16 +106,68 @@ export class DomTools {
|
|
|
118
106
|
public scroller = new Scroller(this);
|
|
119
107
|
public themeManager = new ThemeManager(this);
|
|
120
108
|
public keyboard: Keyboard | null = null; // Initialized after DOM ready to avoid accessing document.body before it exists
|
|
109
|
+
public disposed = false;
|
|
121
110
|
|
|
122
111
|
public domToolsReady = plugins.smartpromise.defer();
|
|
123
112
|
public domReady = plugins.smartpromise.defer();
|
|
124
113
|
public globalStylesReady = plugins.smartpromise.defer();
|
|
125
114
|
|
|
126
|
-
|
|
115
|
+
private readonly initializationPromise: Promise<void>;
|
|
116
|
+
private readonly managedDomNodes: Element[] = [];
|
|
117
|
+
private readonly readyStateChangedFunc = () => {
|
|
118
|
+
this.tryMarkDomReady();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
constructor(optionsArg: IDomToolsContructorOptions) {
|
|
122
|
+
this.initializationPromise = this.initialize();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private runOncePromiseMap = new Map<string, Promise<unknown>>();
|
|
126
|
+
|
|
127
|
+
private async initialize() {
|
|
128
|
+
this.tryMarkDomReady();
|
|
129
|
+
if (this.domReady.status === 'pending') {
|
|
130
|
+
document.addEventListener('readystatechange', this.readyStateChangedFunc);
|
|
131
|
+
}
|
|
132
|
+
if (this.domToolsReady.status === 'pending') {
|
|
133
|
+
this.domToolsReady.resolve();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private tryMarkDomReady() {
|
|
138
|
+
if (this.disposed || this.domReady.status !== 'pending') {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (document.readyState !== 'interactive' && document.readyState !== 'complete') {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!document.head || !document.body) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.elements.headElement = document.head;
|
|
151
|
+
this.elements.bodyElement = document.body;
|
|
152
|
+
if (!this.keyboard) {
|
|
153
|
+
this.keyboard = new Keyboard(document.body);
|
|
154
|
+
}
|
|
155
|
+
document.removeEventListener('readystatechange', this.readyStateChangedFunc);
|
|
156
|
+
this.domReady.resolve();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private trackManagedDomNode<T extends Element>(elementArg: T): T {
|
|
160
|
+
this.managedDomNodes.push(elementArg);
|
|
161
|
+
return elementArg;
|
|
162
|
+
}
|
|
127
163
|
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
|
|
164
|
+
private getHeadOrBodyElement() {
|
|
165
|
+
const targetElement = this.elements.headElement || document.head || this.elements.bodyElement || document.body;
|
|
166
|
+
if (!targetElement) {
|
|
167
|
+
throw new Error('DomTools could not find a DOM target element to attach resources to');
|
|
168
|
+
}
|
|
169
|
+
return targetElement;
|
|
170
|
+
}
|
|
131
171
|
|
|
132
172
|
/**
|
|
133
173
|
* run a function once and always get the Promise of the first execution
|
|
@@ -135,34 +175,14 @@ export class DomTools {
|
|
|
135
175
|
* @param funcArg the actual func arg to run
|
|
136
176
|
*/
|
|
137
177
|
public async runOnce<T>(identifierArg: string, funcArg: () => Promise<T>) {
|
|
138
|
-
|
|
139
|
-
if (!
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
this.runOnceResultMap.addToMap(identifierArg, result);
|
|
145
|
-
} catch (error) {
|
|
146
|
-
// Store error so waiting callers can receive it
|
|
147
|
-
this.runOnceErrorMap.addToMap(identifierArg, error);
|
|
148
|
-
} finally {
|
|
149
|
-
// Always remove running flag to prevent permanent stuck state
|
|
150
|
-
this.runOnceTrackerStringMap.removeString(runningId);
|
|
151
|
-
}
|
|
178
|
+
let runOncePromise = this.runOncePromiseMap.get(identifierArg) as Promise<T> | undefined;
|
|
179
|
+
if (!runOncePromise) {
|
|
180
|
+
runOncePromise = Promise.resolve().then(async () => {
|
|
181
|
+
return await funcArg();
|
|
182
|
+
});
|
|
183
|
+
this.runOncePromiseMap.set(identifierArg, runOncePromise);
|
|
152
184
|
}
|
|
153
|
-
return await
|
|
154
|
-
(stringMap?: string[]) => {
|
|
155
|
-
return !stringMap?.includes(runningId);
|
|
156
|
-
},
|
|
157
|
-
() => {
|
|
158
|
-
// Check if there was an error and re-throw it
|
|
159
|
-
const error = this.runOnceErrorMap.getByKey(identifierArg);
|
|
160
|
-
if (error) {
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
163
|
-
return this.runOnceResultMap.getByKey(identifierArg);
|
|
164
|
-
}
|
|
165
|
-
);
|
|
185
|
+
return await runOncePromise;
|
|
166
186
|
}
|
|
167
187
|
|
|
168
188
|
// setStuff
|
|
@@ -172,10 +192,10 @@ export class DomTools {
|
|
|
172
192
|
*/
|
|
173
193
|
public async setGlobalStyles(stylesText: string) {
|
|
174
194
|
await this.domReady.promise;
|
|
175
|
-
const styleElement = document.createElement('style');
|
|
195
|
+
const styleElement = this.trackManagedDomNode(document.createElement('style'));
|
|
176
196
|
styleElement.type = 'text/css';
|
|
177
197
|
styleElement.appendChild(document.createTextNode(stylesText));
|
|
178
|
-
this.
|
|
198
|
+
this.getHeadOrBodyElement().appendChild(styleElement);
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
/**
|
|
@@ -184,14 +204,16 @@ export class DomTools {
|
|
|
184
204
|
*/
|
|
185
205
|
public async setExternalScript(scriptLinkArg: string) {
|
|
186
206
|
await this.domReady.promise;
|
|
187
|
-
const done = plugins.smartpromise.defer();
|
|
188
|
-
const script = document.createElement('script');
|
|
207
|
+
const done = plugins.smartpromise.defer<void>();
|
|
208
|
+
const script = this.trackManagedDomNode(document.createElement('script'));
|
|
189
209
|
script.src = scriptLinkArg;
|
|
190
|
-
script.addEventListener('load',
|
|
210
|
+
script.addEventListener('load', () => {
|
|
191
211
|
done.resolve();
|
|
192
212
|
});
|
|
193
|
-
|
|
194
|
-
|
|
213
|
+
script.addEventListener('error', () => {
|
|
214
|
+
done.reject(new Error(`Failed to load external script: ${scriptLinkArg}`));
|
|
215
|
+
});
|
|
216
|
+
this.getHeadOrBodyElement().append(script);
|
|
195
217
|
await done.promise;
|
|
196
218
|
}
|
|
197
219
|
|
|
@@ -200,11 +222,20 @@ export class DomTools {
|
|
|
200
222
|
* @param cssLinkArg a url to an external stylesheet
|
|
201
223
|
*/
|
|
202
224
|
public async setExternalCss(cssLinkArg: string) {
|
|
203
|
-
|
|
225
|
+
await this.domReady.promise;
|
|
226
|
+
const done = plugins.smartpromise.defer<void>();
|
|
227
|
+
const cssTag = this.trackManagedDomNode(document.createElement('link'));
|
|
204
228
|
cssTag.rel = 'stylesheet';
|
|
205
229
|
cssTag.crossOrigin = 'anonymous';
|
|
206
230
|
cssTag.href = cssLinkArg;
|
|
207
|
-
|
|
231
|
+
cssTag.addEventListener('load', () => {
|
|
232
|
+
done.resolve();
|
|
233
|
+
});
|
|
234
|
+
cssTag.addEventListener('error', () => {
|
|
235
|
+
done.reject(new Error(`Failed to load external stylesheet: ${cssLinkArg}`));
|
|
236
|
+
});
|
|
237
|
+
this.getHeadOrBodyElement().append(cssTag);
|
|
238
|
+
await done.promise;
|
|
208
239
|
}
|
|
209
240
|
|
|
210
241
|
/**
|
|
@@ -215,4 +246,28 @@ export class DomTools {
|
|
|
215
246
|
await this.websetup.setup(optionsArg);
|
|
216
247
|
await this.websetup.readyPromise;
|
|
217
248
|
}
|
|
249
|
+
|
|
250
|
+
public dispose() {
|
|
251
|
+
if (this.disposed) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.disposed = true;
|
|
256
|
+
document.removeEventListener('readystatechange', this.readyStateChangedFunc);
|
|
257
|
+
|
|
258
|
+
this.keyboard?.dispose();
|
|
259
|
+
this.keyboard = null;
|
|
260
|
+
this.scroller.dispose();
|
|
261
|
+
this.themeManager.dispose();
|
|
262
|
+
|
|
263
|
+
for (const managedDomNode of this.managedDomNodes) {
|
|
264
|
+
managedDomNode.remove();
|
|
265
|
+
}
|
|
266
|
+
this.managedDomNodes.length = 0;
|
|
267
|
+
|
|
268
|
+
if (globalThis.deesDomTools === this) {
|
|
269
|
+
globalThis.deesDomTools = undefined;
|
|
270
|
+
DomTools.initializationPromise = null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
218
273
|
}
|