@design.estate/dees-domtools 2.3.3 → 2.3.5

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/readme.md CHANGED
@@ -1,104 +1,479 @@
1
1
  # @design.estate/dees-domtools
2
- tools to simplify complex css structures
3
2
 
4
- ## Install
3
+ > 🎨 A comprehensive TypeScript toolkit for simplifying DOM manipulation, CSS management, and web component development
5
4
 
6
- To install `@design.estate/dees-domtools`, simply use npm:
5
+ Modern web development made elegant. `@design.estate/dees-domtools` provides a powerful suite of utilities for managing complex CSS structures, handling browser events, implementing smooth scrolling, and building responsive web applications with ease.
6
+
7
+ ## Features
8
+
9
+ 🚀 **Smart DOM Management** - Singleton-based DomTools instance with race-condition-free initialization
10
+ 📱 **Responsive Breakpoints** - Built-in support for desktop, tablet, phablet, and phone viewports with container queries
11
+ 🎭 **Theme Management** - Automatic dark/light mode detection and switching with RxJS observables
12
+ ⌨️ **Keyboard Shortcuts** - Elegant keyboard event handling with combo support
13
+ 📜 **Smooth Scrolling** - Native and Lenis-powered smooth scrolling with automatic detection
14
+ 🎯 **State Management** - Integrated state management with smartstate
15
+ 🧭 **Routing** - Client-side routing with smartrouter
16
+ 🌐 **WebSetup** - Easy management of website metadata, favicons, and SEO tags
17
+ 💅 **CSS Utilities** - Grid helpers, breakpoint utilities, and base styles for web components
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @design.estate/dees-domtools
23
+ ```
24
+
25
+ Or with pnpm:
7
26
 
8
27
  ```bash
9
- npm install --save @design.estate/dees-domtools
28
+ pnpm add @design.estate/dees-domtools
10
29
  ```
11
30
 
12
- Ensure your project supports TypeScript and ESM syntax for the best development experience.
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { DomTools } from '@design.estate/dees-domtools';
35
+
36
+ // Initialize DomTools (singleton pattern - safe to call multiple times)
37
+ const domtools = await DomTools.setupDomTools();
38
+
39
+ // Wait for DOM to be ready
40
+ await domtools.domReady.promise;
41
+
42
+ // Now you're ready to rock! 🎸
43
+ console.log('DOM is ready, head and body elements are available');
44
+ ```
45
+
46
+ ## Core API
47
+
48
+ ### DomTools Instance
49
+
50
+ The `DomTools` class is the heart of the library. It provides a singleton instance that manages all the utilities.
51
+
52
+ ```typescript
53
+ import { DomTools } from '@design.estate/dees-domtools';
54
+
55
+ // Setup with options
56
+ const domtools = await DomTools.setupDomTools({
57
+ ignoreGlobal: false // Set to true to create isolated instance
58
+ });
59
+
60
+ // Access DOM elements (available after domReady)
61
+ await domtools.domReady.promise;
62
+ const head = domtools.elements.headElement;
63
+ const body = domtools.elements.bodyElement;
64
+ ```
65
+
66
+ **Key Properties:**
67
+
68
+ - `domtools.router` - SmartRouter instance for client-side routing
69
+ - `domtools.themeManager` - Theme management (dark/light mode)
70
+ - `domtools.scroller` - Smooth scrolling utilities
71
+ - `domtools.keyboard` - Keyboard event handling
72
+ - `domtools.websetup` - Website metadata management
73
+ - `domtools.smartstate` - State management
74
+ - `domtools.deesComms` - Communication utilities
75
+
76
+ **Lifecycle Promises:**
77
+
78
+ - `domtools.domToolsReady.promise` - Resolves when DomTools is initialized
79
+ - `domtools.domReady.promise` - Resolves when DOM is interactive/complete
80
+ - `domtools.globalStylesReady.promise` - Resolves when global styles are set
81
+
82
+ ### Responsive Breakpoints
83
+
84
+ Built-in breakpoint system with both media queries and container queries:
85
+
86
+ ```typescript
87
+ import { breakpoints, css } from '@design.estate/dees-domtools';
88
+ import { css as litCss } from 'lit';
89
+
90
+ // Breakpoint values (in pixels)
91
+ breakpoints.desktop // 1600px
92
+ breakpoints.notebook // 1240px
93
+ breakpoints.tablet // 1024px
94
+ breakpoints.phablet // 600px
95
+ breakpoints.phone // 400px
96
+
97
+ // Use with Lit components
98
+ const myStyles = litCss`
99
+ .container {
100
+ padding: 20px;
101
+ }
102
+
103
+ ${breakpoints.cssForTablet(litCss`
104
+ .container {
105
+ padding: 10px;
106
+ }
107
+ `)}
108
+
109
+ ${breakpoints.cssForPhone(litCss`
110
+ .container {
111
+ padding: 5px;
112
+ }
113
+ `)}
114
+ `;
115
+ ```
116
+
117
+ Available breakpoint helpers:
118
+ - `cssForDesktop(css)` - Styles for 1600px and above
119
+ - `cssForNotebook(css)` - Styles for 1240px and below
120
+ - `cssForTablet(css)` - Styles for 1024px and below
121
+ - `cssForPhablet(css)` - Styles for 600px and below
122
+ - `cssForPhone(css)` - Styles for 400px and below
123
+
124
+ ### Theme Management
125
+
126
+ Automatic theme detection with system preference support:
127
+
128
+ ```typescript
129
+ const domtools = await DomTools.setupDomTools();
130
+ const { themeManager } = domtools;
131
+
132
+ // Toggle between dark and light
133
+ themeManager.toggleDarkBright();
134
+
135
+ // Set specific theme
136
+ themeManager.goDark();
137
+ themeManager.goBright();
138
+
139
+ // Enable automatic global background changes
140
+ await themeManager.enableAutomaticGlobalThemeChange();
141
+
142
+ // Subscribe to theme changes
143
+ themeManager.themeObservable.subscribe((isBright) => {
144
+ console.log(`Theme is now: ${isBright ? 'light' : 'dark'}`);
145
+ });
146
+
147
+ // Check current theme
148
+ if (themeManager.goBrightBoolean) {
149
+ console.log('Light mode active');
150
+ }
151
+ ```
152
+
153
+ ### Keyboard Shortcuts
154
+
155
+ Handle keyboard events with ease, including complex combinations:
156
+
157
+ ```typescript
158
+ import { Keyboard, Key } from '@design.estate/dees-domtools';
159
+
160
+ const domtools = await DomTools.setupDomTools();
161
+ await domtools.domReady.promise;
13
162
 
14
- ## Usage
163
+ // Access the keyboard instance
164
+ const { keyboard } = domtools;
15
165
 
16
- This documentation aims to give you comprehensive guidance on how to utilize `@design.estate/dees-domtools` in your web projects to simplify complex CSS structures. Whether you're building a sophisticated web application or need a more manageable way to handle your CSS, this toolkit offers a variety of features designed to enhance and streamline your workflow. Throughout this guide, we'll cover the installation process, key features, examples, and best practices when working with `@design.estate/dees-domtools`.
166
+ // Listen for Ctrl+S
167
+ keyboard.on([Key.Ctrl, Key.S]).subscribe((event) => {
168
+ event.preventDefault();
169
+ console.log('Save triggered!');
170
+ });
171
+
172
+ // Listen for Ctrl+Shift+P
173
+ keyboard.on([Key.Ctrl, Key.Shift, Key.P]).subscribe(() => {
174
+ console.log('Command palette opened!');
175
+ });
17
176
 
18
- Before diving into the examples, please ensure that you've completed the installation step as described above.
177
+ // Programmatically trigger key presses
178
+ keyboard.triggerKeyPress([Key.Ctrl, Key.S]);
19
179
 
20
- ### Key Features
180
+ // Clean up when done
181
+ keyboard.stopListening();
182
+ ```
21
183
 
22
- - **CSS Simplification**: Tools and utilities designed to abstract and simplify the handling of complex CSS structures.
23
- - **Responsive Design Helpers**: Tools for managing responsive designs more easily, with utilities for breakpoints, and adaptable grids.
24
- - **Element Utilities**: Simplified interactions with DOM elements, including dynamic style manipulations and more.
25
- - **Theme Management**: Simplify the process of theme toggling (dark mode/light mode) in your applications.
26
- - **Keyboard Event Handling**: Utilities for managing keyboard events, facilitating better interaction handling.
27
- - **External Resources Management**: Easily manage external CSS and JavaScript resources within your project.
184
+ **Available Keys:**
28
185
 
29
- ### Getting Started
186
+ All standard keyboard keys are available in the `Key` enum, including:
187
+ - Modifiers: `Ctrl`, `Shift`, `Alt`
188
+ - Letters: `A` through `Z`
189
+ - Numbers: `Zero` through `Nine`
190
+ - Function keys: `F1` through `F12`
191
+ - Navigation: `Home`, `End`, `PageUp`, `PageDown`, arrows
192
+ - And many more...
30
193
 
31
- To get started with `@design.estate/dees-domtools`, you first need to import the modules you intend to use in your project. Here's a simple example to illustrate how to import and use the package:
194
+ ### Smooth Scrolling
195
+
196
+ Powerful scrolling utilities with Lenis integration:
32
197
 
33
198
  ```typescript
34
- import { DomTools, elementBasic, breakpoints, css } from '@design.estate/dees-domtools';
199
+ const domtools = await DomTools.setupDomTools();
200
+ const { scroller } = domtools;
201
+
202
+ // Scroll to an element smoothly
203
+ const targetElement = document.querySelector('#section-2');
204
+ await scroller.toElement(targetElement, {
205
+ duration: 1000,
206
+ easing: 'easeInOutQuad'
207
+ });
35
208
 
36
- // Setting up DomTools for your application
37
- const domToolsInstance = await DomTools.setupDomTools();
209
+ // Enable Lenis smooth scrolling
210
+ await scroller.enableLenisScroll({
211
+ disableOnNativeSmoothScroll: true // Auto-disable if browser has native smooth scroll
212
+ });
38
213
 
39
- // Example: Using elementBasic utilities
40
- elementBasic.setup();
214
+ // Register scroll callbacks
215
+ scroller.onScroll(() => {
216
+ console.log('Page scrolled!');
217
+ });
218
+
219
+ // Detect if native smooth scrolling is enabled
220
+ const hasNativeSmooth = await scroller.detectNativeSmoothScroll();
41
221
  ```
42
222
 
43
- ### Simplifying CSS with CSS Utilities
223
+ ### CSS Utilities
44
224
 
45
- The package offers a variety of CSS utilities that simplify the handling of complex CSS structures. Here's how you can use them in your project:
225
+ Helper functions for common CSS patterns:
46
226
 
47
227
  ```typescript
48
228
  import { css } from '@design.estate/dees-domtools';
49
229
 
50
- // Example: Creating a CSS grid with a specified number of columns and gap size
51
- const gridStyles = css.cssGridColumns(3, 10); // 3 columns with a 10px gap
230
+ // Create responsive grid columns
231
+ const gridTemplate = css.cssGridColumns(4, 16);
232
+ // Returns: calc((100%/4) - (48px/4)) calc((100%/4) - (48px/4)) ...
233
+
234
+ // Use in your styles
235
+ const styles = `
236
+ .grid {
237
+ display: grid;
238
+ grid-template-columns: ${gridTemplate};
239
+ gap: 16px;
240
+ }
241
+ `;
52
242
  ```
53
243
 
54
- ### Managing Responsive Design
244
+ ### Global Styles & External Resources
245
+
246
+ ```typescript
247
+ const domtools = await DomTools.setupDomTools();
248
+
249
+ // Add global CSS
250
+ await domtools.setGlobalStyles(`
251
+ body {
252
+ margin: 0;
253
+ font-family: 'Inter', sans-serif;
254
+ }
255
+ `);
55
256
 
56
- Responsive design is critical for modern web development. `@design.estate/dees-domtools` provides utilities to make it easier to manage responsive designs and breakpoints.
257
+ // Load external CSS
258
+ await domtools.setExternalCss('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
259
+
260
+ // Load external JavaScript
261
+ await domtools.setExternalScript('https://cdn.example.com/analytics.js');
262
+ ```
263
+
264
+ ### Website Metadata
265
+
266
+ Manage your website's metadata easily:
267
+
268
+ ```typescript
269
+ const domtools = await DomTools.setupDomTools();
270
+
271
+ await domtools.setWebsiteInfo({
272
+ metaObject: {
273
+ title: 'My Awesome App',
274
+ description: 'The best app ever created',
275
+ keywords: ['awesome', 'app', 'web'],
276
+ author: 'Your Name'
277
+ },
278
+ faviconUrl: '/favicon.ico',
279
+ appleTouchIconUrl: '/apple-touch-icon.png'
280
+ });
281
+ ```
282
+
283
+ ### Web Component Base Styles
284
+
285
+ Kickstart your Lit elements with pre-configured styles:
57
286
 
58
287
  ```typescript
59
- import { breakpoints } from '@design.estate/dees-domtools';
288
+ import { LitElement } from 'lit';
289
+ import { elementBasic } from '@design.estate/dees-domtools';
60
290
 
61
- // Example: Applying styles for different viewport sizes
62
- const tabletStyles = breakpoints.cssForTablet(myCssStyles);
63
- const phoneStyles = breakpoints.cssForPhone(myCssStyles);
291
+ class MyElement extends LitElement {
292
+ static styles = [elementBasic.staticStyles];
293
+
294
+ async connectedCallback() {
295
+ super.connectedCallback();
296
+ await elementBasic.setup(this);
297
+ }
298
+ }
64
299
  ```
65
300
 
66
- ### Theme Management
301
+ The `elementBasic.staticStyles` includes:
302
+ - Box-sizing reset
303
+ - Smooth transitions for background and color
304
+ - Custom scrollbar styles
305
+ - Default font family (Geist Sans, Inter fallback)
306
+
307
+ ### State Management
67
308
 
68
- Managing themes (such as toggling between dark and light mode) is simplified with the built-in theme management tools.
309
+ Integrated state management with smartstate:
69
310
 
70
311
  ```typescript
71
- // Toggle between light and dark themes
72
- domToolsInstance.themeManager.toggleDarkBright();
312
+ const domtools = await DomTools.setupDomTools();
313
+
314
+ // Access the state part
315
+ const state = domtools.domToolsStatePart;
316
+
317
+ // Get current state
318
+ const currentState = state.getState();
319
+ console.log(currentState.virtualViewport); // 'native'
320
+ console.log(currentState.jwt); // null
321
+
322
+ // Update state
323
+ state.setState({
324
+ virtualViewport: 'tablet',
325
+ jwt: 'your-token-here'
326
+ });
327
+
328
+ // Subscribe to state changes
329
+ state.subscribe((newState) => {
330
+ console.log('State updated:', newState);
331
+ });
73
332
  ```
74
333
 
75
- ### Handling Keyboard Events
334
+ ### Run Once Pattern
76
335
 
77
- Keyboard event handling is made easy with the utilities provided by the toolkit. This can be particularly useful for adding keyboard navigation and shortcuts.
336
+ Execute expensive operations only once, even if called multiple times:
78
337
 
79
338
  ```typescript
80
- import { Keyboard } from '@design.estate/dees-domtools';
339
+ const domtools = await DomTools.setupDomTools();
81
340
 
82
- // Initialize keyboard handling
83
- const keyboard = new Keyboard(document.body);
341
+ // This will only execute once, even if called multiple times
342
+ const result = await domtools.runOnce('myExpensiveOperation', async () => {
343
+ console.log('Running expensive operation...');
344
+ await someExpensiveAsyncOperation();
345
+ return 'result';
346
+ });
84
347
 
85
- // Bind an event to a specific key combination
86
- keyboard.on([Keyboard.Key.Ctrl, Keyboard.Key.S]).subscribe(() => {
87
- console.log('Ctrl+S was pressed');
348
+ // Subsequent calls return the same result without re-executing
349
+ const sameResult = await domtools.runOnce('myExpensiveOperation', async () => {
350
+ console.log('This will never run!');
351
+ return 'different result';
88
352
  });
353
+
354
+ console.log(result === sameResult); // true
355
+ ```
356
+
357
+ Error handling is built-in - if the function throws, all waiting callers receive the same error.
358
+
359
+ ## Advanced Usage
360
+
361
+ ### Combining Features
362
+
363
+ Here's a real-world example combining multiple features:
364
+
365
+ ```typescript
366
+ import { DomTools, breakpoints, elementBasic, Key } from '@design.estate/dees-domtools';
367
+ import { LitElement, html, css as litCss } from 'lit';
368
+ import { customElement } from 'lit/decorators.js';
369
+
370
+ @customElement('my-app')
371
+ class MyApp extends LitElement {
372
+ static styles = [
373
+ elementBasic.staticStyles,
374
+ litCss`
375
+ :host {
376
+ display: block;
377
+ padding: 2rem;
378
+ }
379
+
380
+ ${breakpoints.cssForTablet(litCss`
381
+ :host {
382
+ padding: 1rem;
383
+ }
384
+ `)}
385
+ `
386
+ ];
387
+
388
+ private domtools?: DomTools;
389
+
390
+ async connectedCallback() {
391
+ super.connectedCallback();
392
+
393
+ // Setup DomTools
394
+ this.domtools = await elementBasic.setup(this);
395
+ await this.domtools.domReady.promise;
396
+
397
+ // Setup keyboard shortcuts
398
+ this.domtools.keyboard.on([Key.Ctrl, Key.K]).subscribe(() => {
399
+ this.openCommandPalette();
400
+ });
401
+
402
+ // Subscribe to theme changes
403
+ this.domtools.themeManager.themeObservable.subscribe((isBright) => {
404
+ this.requestUpdate();
405
+ });
406
+
407
+ // Enable smooth scrolling
408
+ await this.domtools.scroller.enableLenisScroll({
409
+ disableOnNativeSmoothScroll: true
410
+ });
411
+ }
412
+
413
+ private openCommandPalette() {
414
+ console.log('Command palette opened!');
415
+ }
416
+
417
+ render() {
418
+ const isDark = !this.domtools?.themeManager.goBrightBoolean;
419
+
420
+ return html`
421
+ <div class="app" style="background: ${isDark ? '#1a1a1a' : '#ffffff'}">
422
+ <h1>My Awesome App</h1>
423
+ <button @click=${() => this.domtools?.themeManager.toggleDarkBright()}>
424
+ Toggle Theme
425
+ </button>
426
+ </div>
427
+ `;
428
+ }
429
+ }
430
+ ```
431
+
432
+ ## TypeScript Support
433
+
434
+ This package is written in TypeScript and provides full type definitions:
435
+
436
+ ```typescript
437
+ import type {
438
+ IDomToolsState,
439
+ IDomToolsContructorOptions,
440
+ TViewport
441
+ } from '@design.estate/dees-domtools';
442
+
443
+ // Custom state interface
444
+ interface MyState extends IDomToolsState {
445
+ customProperty: string;
446
+ }
447
+
448
+ // Type-safe viewport handling
449
+ const viewport: TViewport = 'tablet';
89
450
  ```
90
451
 
91
- ### Best Practices
452
+ ## Browser Support
453
+
454
+ Targets the latest version of Chrome. For other browsers, you may need to include polyfills.
455
+
456
+ ## Why @design.estate/dees-domtools?
457
+
458
+ ✅ **Race-condition free** - Carefully designed initialization prevents common timing issues
459
+ ✅ **TypeScript first** - Full type safety and IntelliSense support
460
+ ✅ **Modern APIs** - Built on Lit, RxJS, and other modern web standards
461
+ ✅ **Batteries included** - Everything you need for sophisticated web apps
462
+ ✅ **Production ready** - Used in real-world applications at design.estate
463
+ ✅ **Well maintained** - Active development and support
464
+
465
+ ## Related Packages
92
466
 
93
- - **Organize Your CSS**: Use the provided CSS utilities to structure your styles in a way that makes them easy to maintain and scale.
94
- - **Embrace Responsiveness**: Leverage the responsive design helpers to ensure your application looks great on any device.
95
- - **Consistent Theme Handling**: Utilize the theme management tools to provide a seamless experience for your users, allowing them to choose their preferred theme.
467
+ This library integrates with the design.estate ecosystem:
96
468
 
97
- By integrating `@design.estate/dees-domtools` into your project, you can leverage a comprehensive suite of utilities designed to simplify complex CSS structures and enhance your web development workflow.
469
+ - `@design.estate/dees-comms` - Communication utilities
470
+ - `@push.rocks/websetup` - Website setup and meta management
471
+ - `@push.rocks/smartrouter` - Client-side routing
472
+ - `@push.rocks/smartstate` - State management
98
473
 
99
474
  ## License and Legal Information
100
475
 
101
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
476
+ This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
102
477
 
103
478
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
104
479
 
@@ -108,7 +483,7 @@ This project is owned and maintained by Task Venture Capital GmbH. The names and
108
483
 
109
484
  ### Company Information
110
485
 
111
- Task Venture Capital GmbH
486
+ Task Venture Capital GmbH
112
487
  Registered at District court Bremen HRB 35230 HB, Germany
113
488
 
114
489
  For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-domtools',
6
- version: '2.3.3',
6
+ version: '2.3.5',
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
  }
@@ -18,32 +18,54 @@ export class DomTools {
18
18
  // ======
19
19
  // STATIC
20
20
  // ======
21
+ private static initializationPromise: Promise<DomTools> | null = null;
22
+
21
23
  /**
22
24
  * setups domtools
23
25
  */
24
- public static async setupDomTools(optionsArg: IDomToolsContructorOptions = {}) {
25
- let domToolsInstance: DomTools;
26
- if (!globalThis.deesDomTools && !optionsArg.ignoreGlobal) {
27
- globalThis.deesDomTools = new DomTools(optionsArg);
28
- domToolsInstance = globalThis.deesDomTools;
29
-
30
- // lets make sure the dom is ready
31
- const readyStateChangedFunc = () => {
32
- if (document.readyState === 'interactive' || document.readyState === 'complete') {
33
- domToolsInstance.elements.headElement = document.querySelector('head');
34
- domToolsInstance.elements.bodyElement = document.querySelector('body');
35
- domToolsInstance.domReady.resolve();
26
+ public static async setupDomTools(optionsArg: IDomToolsContructorOptions = {}): Promise<DomTools> {
27
+ // If initialization is already in progress and we're not ignoring global, wait for it
28
+ if (!optionsArg.ignoreGlobal && DomTools.initializationPromise) {
29
+ return await DomTools.initializationPromise;
30
+ }
31
+
32
+ // Create initialization promise to prevent race conditions
33
+ if (!optionsArg.ignoreGlobal) {
34
+ DomTools.initializationPromise = (async () => {
35
+ let domToolsInstance: DomTools;
36
+ if (!globalThis.deesDomTools) {
37
+ globalThis.deesDomTools = new DomTools(optionsArg);
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;
36
59
  }
37
- };
38
- document.addEventListener('readystatechange', readyStateChangedFunc);
39
- domToolsInstance.domToolsReady.resolve();
40
- } else if (optionsArg.ignoreGlobal) {
41
- domToolsInstance = new DomTools(optionsArg);
60
+ await domToolsInstance.domToolsReady.promise;
61
+ return domToolsInstance;
62
+ })();
63
+ return await DomTools.initializationPromise;
42
64
  } else {
43
- domToolsInstance = globalThis.deesDomTools;
65
+ // ignoreGlobal case - create isolated instance
66
+ const domToolsInstance = new DomTools(optionsArg);
67
+ return domToolsInstance;
44
68
  }
45
- await domToolsInstance.domToolsReady.promise;
46
- return domToolsInstance;
47
69
  }
48
70
 
49
71
  /**
@@ -95,7 +117,7 @@ export class DomTools {
95
117
  public deesComms = new plugins.deesComms.DeesComms();
96
118
  public scroller = new Scroller(this);
97
119
  public themeManager = new ThemeManager(this);
98
- public keyboard = new Keyboard(document.body);
120
+ public keyboard: Keyboard = null; // Initialized after DOM ready to avoid accessing document.body before it exists
99
121
 
100
122
  public domToolsReady = plugins.smartpromise.defer();
101
123
  public domReady = plugins.smartpromise.defer();
@@ -105,6 +127,7 @@ export class DomTools {
105
127
 
106
128
  private runOnceTrackerStringMap = new plugins.lik.Stringmap();
107
129
  private runOnceResultMap = new plugins.lik.FastMap();
130
+ private runOnceErrorMap = new plugins.lik.FastMap();
108
131
 
109
132
  /**
110
133
  * run a function once and always get the Promise of the first execution
@@ -116,15 +139,27 @@ export class DomTools {
116
139
  if (!this.runOnceTrackerStringMap.checkString(identifierArg)) {
117
140
  this.runOnceTrackerStringMap.addString(identifierArg);
118
141
  this.runOnceTrackerStringMap.addString(runningId);
119
- const result = await funcArg();
120
- this.runOnceResultMap.addToMap(identifierArg, result);
121
- this.runOnceTrackerStringMap.removeString(runningId);
142
+ try {
143
+ const result = await funcArg();
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
+ }
122
152
  }
123
153
  return await this.runOnceTrackerStringMap.registerUntilTrue(
124
154
  (stringMap) => {
125
155
  return !stringMap.includes(runningId);
126
156
  },
127
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
+ }
128
163
  return this.runOnceResultMap.getByKey(identifierArg);
129
164
  }
130
165
  );