@design.estate/dees-domtools 2.5.4 → 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.
@@ -129,8 +129,15 @@ export enum Key {
129
129
  }
130
130
 
131
131
  export class Keyboard {
132
- private mapCombosToHandlers = new Map<number[], plugins.smartrx.rxjs.Subject<KeyboardEvent>>();
132
+ private mapCombosToHandlers = new Map<
133
+ string,
134
+ {
135
+ keys: Key[];
136
+ subjects: plugins.smartrx.rxjs.Subject<KeyboardEvent>[];
137
+ }
138
+ >();
133
139
  private pressedKeys = new Set<Key>();
140
+ private listening = false;
134
141
 
135
142
  constructor(private domNode: Element | Document) {
136
143
  this.startListening();
@@ -145,46 +152,74 @@ export class Keyboard {
145
152
  }
146
153
 
147
154
  public triggerKeyPress(keysArg: Key[]) {
148
- for (const key of keysArg) {
155
+ const normalizedKeys = this.normalizeKeys(keysArg);
156
+ for (const key of normalizedKeys) {
149
157
  this.pressedKeys.add(key);
150
158
  }
151
- this.checkMatchingKeyboardSubjects();
152
- for (const key of keysArg) {
159
+ this.checkMatchingKeyboardSubjects(this.createKeyboardEvent(normalizedKeys));
160
+ for (const key of normalizedKeys) {
153
161
  this.pressedKeys.delete(key);
154
162
  }
155
163
  }
156
164
 
157
165
  public startListening() {
166
+ if (this.listening) {
167
+ return;
168
+ }
158
169
  this.domNode.addEventListener('keydown', this.handleKeyDown as EventListener);
159
170
  this.domNode.addEventListener('keyup', this.handleKeyUp as EventListener);
171
+ this.listening = true;
160
172
  }
161
173
 
162
174
  public stopListening() {
175
+ if (!this.listening) {
176
+ return;
177
+ }
163
178
  this.domNode.removeEventListener('keydown', this.handleKeyDown as EventListener);
164
179
  this.domNode.removeEventListener('keyup', this.handleKeyUp as EventListener);
180
+ this.listening = false;
165
181
  }
166
182
 
167
183
  public clear() {
168
184
  this.stopListening();
185
+ for (const comboEntry of this.mapCombosToHandlers.values()) {
186
+ for (const subject of comboEntry.subjects) {
187
+ subject.complete();
188
+ }
189
+ }
169
190
  this.mapCombosToHandlers.clear();
170
191
  this.pressedKeys.clear();
171
192
  }
172
193
 
194
+ public dispose() {
195
+ this.clear();
196
+ }
197
+
173
198
  private handleKeyDown = (event: KeyboardEvent) => {
174
- this.pressedKeys.add(event.keyCode);
199
+ const resolvedKey = this.resolveKey(event);
200
+ if (resolvedKey === null) {
201
+ return;
202
+ }
203
+ this.pressedKeys.add(resolvedKey);
175
204
  this.checkMatchingKeyboardSubjects(event);
176
205
  };
177
206
 
178
- private checkMatchingKeyboardSubjects(payloadArg?) {
179
- this.mapCombosToHandlers.forEach((subjectArg, keysArg) => {
180
- if (this.areAllKeysPressed(keysArg)) {
181
- subjectArg.next(payloadArg);
207
+ private checkMatchingKeyboardSubjects(payloadArg: KeyboardEvent) {
208
+ this.mapCombosToHandlers.forEach((comboEntry) => {
209
+ if (this.areAllKeysPressed(comboEntry.keys)) {
210
+ for (const subjectArg of comboEntry.subjects) {
211
+ subjectArg.next(payloadArg);
212
+ }
182
213
  }
183
214
  });
184
215
  }
185
216
 
186
217
  private handleKeyUp = (event: KeyboardEvent) => {
187
- this.pressedKeys.delete(event.keyCode);
218
+ const resolvedKey = this.resolveKey(event);
219
+ if (resolvedKey === null) {
220
+ return;
221
+ }
222
+ this.pressedKeys.delete(resolvedKey);
188
223
  };
189
224
 
190
225
  private areAllKeysPressed(keysArg: Key[]) {
@@ -203,11 +238,335 @@ export class Keyboard {
203
238
  keysArg: Array<Key>,
204
239
  subjectArg: plugins.smartrx.rxjs.Subject<KeyboardEvent>
205
240
  ) {
206
- if (!this.mapCombosToHandlers.has(keysArg)) {
207
- this.mapCombosToHandlers.set(keysArg, subjectArg);
208
- } else {
209
- const subject = this.mapCombosToHandlers.get(keysArg);
210
- return subject;
241
+ const normalizedKeys = this.normalizeKeys(keysArg);
242
+ const comboKey = normalizedKeys.join('+');
243
+ const existingEntry = this.mapCombosToHandlers.get(comboKey);
244
+ if (!existingEntry) {
245
+ this.mapCombosToHandlers.set(comboKey, {
246
+ keys: normalizedKeys,
247
+ subjects: [subjectArg],
248
+ });
249
+ return;
250
+ }
251
+ existingEntry.subjects.push(subjectArg);
252
+ }
253
+
254
+ private normalizeKeys(keysArg: Key[]) {
255
+ return [...new Set(keysArg)].sort((leftKey, rightKey) => leftKey - rightKey);
256
+ }
257
+
258
+ private resolveKey(event: KeyboardEvent): Key | null {
259
+ return this.resolveKeyFromCode(event.code)
260
+ ?? this.resolveKeyFromKey(event.key)
261
+ ?? this.resolveKeyFromKeyCode(event.keyCode);
262
+ }
263
+
264
+ private resolveKeyFromCode(codeArg: string): Key | null {
265
+ if (!codeArg) {
266
+ return null;
267
+ }
268
+
269
+ if (codeArg.startsWith('Key') && codeArg.length === 4) {
270
+ return Key[codeArg.slice(3) as keyof typeof Key] ?? null;
271
+ }
272
+
273
+ if (codeArg.startsWith('Digit') && codeArg.length === 6) {
274
+ return this.resolveKeyFromKey(codeArg.slice(5));
275
+ }
276
+
277
+ switch (codeArg) {
278
+ case 'Backspace':
279
+ return Key.Backspace;
280
+ case 'Tab':
281
+ return Key.Tab;
282
+ case 'Enter':
283
+ case 'NumpadEnter':
284
+ return Key.Enter;
285
+ case 'ShiftLeft':
286
+ case 'ShiftRight':
287
+ return Key.Shift;
288
+ case 'ControlLeft':
289
+ case 'ControlRight':
290
+ return Key.Ctrl;
291
+ case 'AltLeft':
292
+ case 'AltRight':
293
+ return Key.Alt;
294
+ case 'Pause':
295
+ return Key.PauseBreak;
296
+ case 'CapsLock':
297
+ return Key.CapsLock;
298
+ case 'Escape':
299
+ return Key.Escape;
300
+ case 'Space':
301
+ return Key.Space;
302
+ case 'PageUp':
303
+ return Key.PageUp;
304
+ case 'PageDown':
305
+ return Key.PageDown;
306
+ case 'End':
307
+ return Key.End;
308
+ case 'Home':
309
+ return Key.Home;
310
+ case 'ArrowLeft':
311
+ return Key.LeftArrow;
312
+ case 'ArrowUp':
313
+ return Key.UpArrow;
314
+ case 'ArrowRight':
315
+ return Key.RightArrow;
316
+ case 'ArrowDown':
317
+ return Key.DownArrow;
318
+ case 'Insert':
319
+ return Key.Insert;
320
+ case 'Delete':
321
+ return Key.Delete;
322
+ case 'MetaLeft':
323
+ return Key.LeftWindowKey;
324
+ case 'MetaRight':
325
+ return Key.RightWindowKey;
326
+ case 'ContextMenu':
327
+ return Key.SelectKey;
328
+ case 'Numpad0':
329
+ return Key.Numpad0;
330
+ case 'Numpad1':
331
+ return Key.Numpad1;
332
+ case 'Numpad2':
333
+ return Key.Numpad2;
334
+ case 'Numpad3':
335
+ return Key.Numpad3;
336
+ case 'Numpad4':
337
+ return Key.Numpad4;
338
+ case 'Numpad5':
339
+ return Key.Numpad5;
340
+ case 'Numpad6':
341
+ return Key.Numpad6;
342
+ case 'Numpad7':
343
+ return Key.Numpad7;
344
+ case 'Numpad8':
345
+ return Key.Numpad8;
346
+ case 'Numpad9':
347
+ return Key.Numpad9;
348
+ case 'NumpadMultiply':
349
+ return Key.Multiply;
350
+ case 'NumpadAdd':
351
+ return Key.Add;
352
+ case 'NumpadSubtract':
353
+ return Key.Subtract;
354
+ case 'NumpadDecimal':
355
+ return Key.DecimalPoint;
356
+ case 'NumpadDivide':
357
+ return Key.Divide;
358
+ case 'F1':
359
+ return Key.F1;
360
+ case 'F2':
361
+ return Key.F2;
362
+ case 'F3':
363
+ return Key.F3;
364
+ case 'F4':
365
+ return Key.F4;
366
+ case 'F5':
367
+ return Key.F5;
368
+ case 'F6':
369
+ return Key.F6;
370
+ case 'F7':
371
+ return Key.F7;
372
+ case 'F8':
373
+ return Key.F8;
374
+ case 'F9':
375
+ return Key.F9;
376
+ case 'F10':
377
+ return Key.F10;
378
+ case 'F11':
379
+ return Key.F11;
380
+ case 'F12':
381
+ return Key.F12;
382
+ case 'NumLock':
383
+ return Key.NumLock;
384
+ case 'ScrollLock':
385
+ return Key.ScrollLock;
386
+ case 'Semicolon':
387
+ return Key.SemiColon;
388
+ case 'Equal':
389
+ return Key.Equals;
390
+ case 'Comma':
391
+ return Key.Comma;
392
+ case 'Minus':
393
+ return Key.Dash;
394
+ case 'Period':
395
+ return Key.Period;
396
+ case 'Slash':
397
+ return Key.ForwardSlash;
398
+ case 'Backquote':
399
+ return Key.Tilde;
400
+ case 'BracketLeft':
401
+ return Key.OpenBracket;
402
+ case 'BracketRight':
403
+ return Key.ClosedBracket;
404
+ case 'Quote':
405
+ return Key.Quote;
406
+ default:
407
+ return null;
408
+ }
409
+ }
410
+
411
+ private resolveKeyFromKey(keyArg: string): Key | null {
412
+ if (!keyArg) {
413
+ return null;
414
+ }
415
+
416
+ if (keyArg.length === 1) {
417
+ const upperKey = keyArg.toUpperCase();
418
+ if (upperKey >= 'A' && upperKey <= 'Z') {
419
+ return Key[upperKey as keyof typeof Key] ?? null;
420
+ }
421
+
422
+ switch (keyArg) {
423
+ case '0':
424
+ return Key.Zero;
425
+ case '1':
426
+ return Key.One;
427
+ case '2':
428
+ return Key.Two;
429
+ case '3':
430
+ return Key.Three;
431
+ case '4':
432
+ return Key.Four;
433
+ case '5':
434
+ return Key.Five;
435
+ case '6':
436
+ return Key.Six;
437
+ case '7':
438
+ return Key.Seven;
439
+ case '8':
440
+ return Key.Eight;
441
+ case '9':
442
+ return Key.Nine;
443
+ case ' ':
444
+ return Key.Space;
445
+ case ';':
446
+ return Key.SemiColon;
447
+ case '=':
448
+ return Key.Equals;
449
+ case ',':
450
+ return Key.Comma;
451
+ case '-':
452
+ return Key.Dash;
453
+ case '.':
454
+ return Key.Period;
455
+ case '/':
456
+ return Key.ForwardSlash;
457
+ case '`':
458
+ return Key.Tilde;
459
+ case '[':
460
+ return Key.OpenBracket;
461
+ case ']':
462
+ return Key.ClosedBracket;
463
+ case '\'':
464
+ return Key.Quote;
465
+ default:
466
+ return null;
467
+ }
468
+ }
469
+
470
+ switch (keyArg) {
471
+ case 'Backspace':
472
+ return Key.Backspace;
473
+ case 'Tab':
474
+ return Key.Tab;
475
+ case 'Enter':
476
+ return Key.Enter;
477
+ case 'Shift':
478
+ return Key.Shift;
479
+ case 'Control':
480
+ return Key.Ctrl;
481
+ case 'Alt':
482
+ return Key.Alt;
483
+ case 'Pause':
484
+ return Key.PauseBreak;
485
+ case 'CapsLock':
486
+ return Key.CapsLock;
487
+ case 'Escape':
488
+ return Key.Escape;
489
+ case 'PageUp':
490
+ return Key.PageUp;
491
+ case 'PageDown':
492
+ return Key.PageDown;
493
+ case 'End':
494
+ return Key.End;
495
+ case 'Home':
496
+ return Key.Home;
497
+ case 'ArrowLeft':
498
+ return Key.LeftArrow;
499
+ case 'ArrowUp':
500
+ return Key.UpArrow;
501
+ case 'ArrowRight':
502
+ return Key.RightArrow;
503
+ case 'ArrowDown':
504
+ return Key.DownArrow;
505
+ case 'Insert':
506
+ return Key.Insert;
507
+ case 'Delete':
508
+ return Key.Delete;
509
+ case 'Meta':
510
+ return Key.LeftWindowKey;
511
+ case 'ContextMenu':
512
+ return Key.SelectKey;
513
+ case 'NumLock':
514
+ return Key.NumLock;
515
+ case 'ScrollLock':
516
+ return Key.ScrollLock;
517
+ default:
518
+ return null;
519
+ }
520
+ }
521
+
522
+ private resolveKeyFromKeyCode(keyCodeArg: number): Key | null {
523
+ if (typeof keyCodeArg !== 'number') {
524
+ return null;
525
+ }
526
+
527
+ return typeof Key[keyCodeArg as Key] !== 'undefined' ? (keyCodeArg as Key) : null;
528
+ }
529
+
530
+ private createKeyboardEvent(keysArg: Key[]) {
531
+ const triggerKey = keysArg[keysArg.length - 1] ?? Key.Enter;
532
+ const keyDescriptor = this.getKeyDescriptor(triggerKey);
533
+ return new KeyboardEvent('keydown', {
534
+ key: keyDescriptor.key,
535
+ code: keyDescriptor.code,
536
+ ctrlKey: keysArg.includes(Key.Ctrl),
537
+ shiftKey: keysArg.includes(Key.Shift),
538
+ altKey: keysArg.includes(Key.Alt),
539
+ metaKey: keysArg.includes(Key.LeftWindowKey) || keysArg.includes(Key.RightWindowKey),
540
+ });
541
+ }
542
+
543
+ private getKeyDescriptor(keyArg: Key): { key: string; code: string } {
544
+ switch (keyArg) {
545
+ case Key.Ctrl:
546
+ return { key: 'Control', code: 'ControlLeft' };
547
+ case Key.Shift:
548
+ return { key: 'Shift', code: 'ShiftLeft' };
549
+ case Key.Alt:
550
+ return { key: 'Alt', code: 'AltLeft' };
551
+ case Key.Enter:
552
+ return { key: 'Enter', code: 'Enter' };
553
+ case Key.Space:
554
+ return { key: ' ', code: 'Space' };
555
+ default: {
556
+ const keyName = Key[keyArg];
557
+ if (keyName && keyName.length === 1) {
558
+ if (keyName >= 'A' && keyName <= 'Z') {
559
+ return { key: keyName.toLowerCase(), code: `Key${keyName}` };
560
+ }
561
+ }
562
+
563
+ if (keyArg >= Key.Zero && keyArg <= Key.Nine) {
564
+ const digit = String(keyArg - Key.Zero);
565
+ return { key: digit, code: `Digit${digit}` };
566
+ }
567
+
568
+ return { key: keyName ?? 'Unidentified', code: 'Unidentified' };
569
+ }
211
570
  }
212
571
  }
213
572
  }
@@ -3,19 +3,22 @@ import * as plugins from './domtools.plugins.js';
3
3
 
4
4
  export class Scroller {
5
5
  public domtoolsInstance: DomTools;
6
+ private disposed = false;
6
7
 
7
8
  // Array to store scroll callback functions.
8
9
  private scrollCallbacks: Array<() => void> = [];
9
10
 
10
11
  // Lenis instance (if activated) or null.
11
12
  private lenisInstance: plugins.Lenis | null = null;
13
+ private lenisScrollUnsubscribe: (() => void) | null = null;
14
+ private nativeScrollListenerAttached = false;
12
15
 
13
16
  // Bound handlers to allow removal from event listeners.
14
- private handleNativeScroll = (event: Event): void => {
17
+ private handleNativeScroll = (_event: Event): void => {
15
18
  this.executeScrollCallbacks();
16
19
  };
17
20
 
18
- private handleLenisScroll = (info: any): void => {
21
+ private handleLenisScroll = (_info: plugins.Lenis): void => {
19
22
  this.executeScrollCallbacks();
20
23
  };
21
24
 
@@ -44,23 +47,40 @@ export class Scroller {
44
47
  * Detects whether native smooth scrolling is enabled.
45
48
  */
46
49
  public async detectNativeSmoothScroll() {
50
+ const rootScrollBehavior = getComputedStyle(document.documentElement).scrollBehavior;
51
+ const bodyScrollBehavior = document.body ? getComputedStyle(document.body).scrollBehavior : 'auto';
52
+ if (rootScrollBehavior === 'smooth' || bodyScrollBehavior === 'smooth') {
53
+ return true;
54
+ }
55
+
47
56
  const done = plugins.smartpromise.defer<boolean>();
48
- const sampleSize = 100;
57
+ const sampleSize = 12;
49
58
  const acceptableDeltaDifference = 3;
50
59
  const minimumSmoothRatio = 0.75;
60
+ const timeoutInMs = 1200;
51
61
 
52
62
  const eventDeltas: number[] = [];
53
63
 
64
+ const finalize = (result: boolean) => {
65
+ window.removeEventListener('wheel', onWheel);
66
+ window.clearTimeout(timeoutId);
67
+ done.resolve(result);
68
+ };
69
+
54
70
  function onWheel(event: WheelEvent) {
55
71
  eventDeltas.push(event.deltaY);
56
72
 
57
73
  if (eventDeltas.length >= sampleSize) {
58
- window.removeEventListener('wheel', onWheel);
59
74
  analyzeEvents();
60
75
  }
61
76
  }
62
77
 
63
78
  function analyzeEvents() {
79
+ if (eventDeltas.length < 2) {
80
+ finalize(false);
81
+ return;
82
+ }
83
+
64
84
  const totalDiffs = eventDeltas.length - 1;
65
85
  let smallDiffCount = 0;
66
86
 
@@ -72,16 +92,14 @@ export class Scroller {
72
92
  }
73
93
 
74
94
  const smoothRatio = smallDiffCount / totalDiffs;
75
- if (smoothRatio >= minimumSmoothRatio) {
76
- console.log('Smooth scrolling detected.');
77
- done.resolve(true);
78
- } else {
79
- console.log('Smooth scrolling NOT detected.');
80
- done.resolve(false);
81
- }
95
+ finalize(smoothRatio >= minimumSmoothRatio);
82
96
  }
83
97
 
84
- window.addEventListener('wheel', onWheel);
98
+ const timeoutId = window.setTimeout(() => {
99
+ analyzeEvents();
100
+ }, timeoutInMs);
101
+
102
+ window.addEventListener('wheel', onWheel, { passive: true });
85
103
  return done.promise;
86
104
  }
87
105
 
@@ -91,6 +109,14 @@ export class Scroller {
91
109
  * Lenis will be destroyed immediately.
92
110
  */
93
111
  public async enableLenisScroll(optionsArg?: { disableOnNativeSmoothScroll?: boolean }) {
112
+ if (this.disposed) {
113
+ return;
114
+ }
115
+
116
+ if (this.lenisInstance) {
117
+ this.disableLenisScroll();
118
+ }
119
+
94
120
  const lenis = new plugins.Lenis({
95
121
  autoRaf: true,
96
122
  });
@@ -107,24 +133,28 @@ export class Scroller {
107
133
  // Switch from native scroll listener to Lenis scroll listener.
108
134
  this.detachNativeScrollListener();
109
135
  this.attachLenisScrollListener();
136
+ }
110
137
 
111
- // Monkey-patch the destroy method so that when Lenis is destroyed,
112
- // the native scroll listener is reattached.
113
- const originalDestroy = lenis.destroy.bind(lenis);
114
- lenis.destroy = () => {
115
- originalDestroy();
116
- this.detachLenisScrollListener();
117
- this.attachNativeScrollListener();
118
- this.lenisInstance = null;
119
- };
138
+ public disableLenisScroll() {
139
+ if (!this.lenisInstance) {
140
+ return;
141
+ }
142
+
143
+ this.detachLenisScrollListener();
144
+ this.lenisInstance.destroy();
145
+ this.lenisInstance = null;
146
+ this.attachNativeScrollListener();
120
147
  }
121
148
 
122
149
  /**
123
150
  * Registers a callback to be executed on scroll.
124
151
  * @param callback A function to execute on each scroll event.
125
152
  */
126
- public onScroll(callback: () => void): void {
153
+ public onScroll(callback: () => void): () => void {
127
154
  this.scrollCallbacks.push(callback);
155
+ return () => {
156
+ this.scrollCallbacks = this.scrollCallbacks.filter((existingCallback) => existingCallback !== callback);
157
+ };
128
158
  }
129
159
 
130
160
  /**
@@ -145,23 +175,30 @@ export class Scroller {
145
175
  * Attaches the native scroll event listener.
146
176
  */
147
177
  private attachNativeScrollListener(): void {
178
+ if (this.nativeScrollListenerAttached || this.disposed) {
179
+ return;
180
+ }
148
181
  window.addEventListener('scroll', this.handleNativeScroll);
182
+ this.nativeScrollListenerAttached = true;
149
183
  }
150
184
 
151
185
  /**
152
186
  * Detaches the native scroll event listener.
153
187
  */
154
188
  private detachNativeScrollListener(): void {
189
+ if (!this.nativeScrollListenerAttached) {
190
+ return;
191
+ }
155
192
  window.removeEventListener('scroll', this.handleNativeScroll);
193
+ this.nativeScrollListenerAttached = false;
156
194
  }
157
195
 
158
196
  /**
159
197
  * Attaches the Lenis scroll event listener.
160
198
  */
161
199
  private attachLenisScrollListener(): void {
162
- if (this.lenisInstance) {
163
- // Assuming that Lenis exposes an `on` method to listen to scroll events.
164
- this.lenisInstance.on('scroll', this.handleLenisScroll);
200
+ if (this.lenisInstance && !this.lenisScrollUnsubscribe) {
201
+ this.lenisScrollUnsubscribe = this.lenisInstance.on('scroll', this.handleLenisScroll);
165
202
  }
166
203
  }
167
204
 
@@ -169,9 +206,23 @@ export class Scroller {
169
206
  * Detaches the Lenis scroll event listener.
170
207
  */
171
208
  private detachLenisScrollListener(): void {
172
- if (this.lenisInstance) {
173
- // Assuming that Lenis exposes an `off` method to remove scroll event listeners.
174
- this.lenisInstance.off('scroll', this.handleLenisScroll);
209
+ if (this.lenisScrollUnsubscribe) {
210
+ this.lenisScrollUnsubscribe();
211
+ this.lenisScrollUnsubscribe = null;
175
212
  }
176
213
  }
177
- }
214
+
215
+ public dispose() {
216
+ if (this.disposed) {
217
+ return;
218
+ }
219
+
220
+ this.disposed = true;
221
+ this.detachLenisScrollListener();
222
+ this.lenisInstance?.destroy();
223
+ this.lenisInstance = null;
224
+ this.detachNativeScrollListener();
225
+ this.scrollCallbacks = [];
226
+ this.sweetScroller.destroy();
227
+ }
228
+ }
@@ -8,30 +8,36 @@ export class ThemeManager {
8
8
  public preferredColorSchemeMediaMatch = window.matchMedia('(prefers-color-scheme: light)');
9
9
 
10
10
  public themeObservable = new plugins.smartrx.rxjs.ReplaySubject<boolean>(1);
11
+ private automaticGlobalThemeChangeSubscription: plugins.smartrx.rxjs.Subscription | null = null;
12
+ private readonly preferredColorSchemeChangeHandler = (eventArg: MediaQueryListEvent) => {
13
+ this.goBrightBoolean = eventArg.matches;
14
+ this.updateAllConnectedElements();
15
+ };
11
16
 
12
17
  constructor(domtoolsRefArg: DomTools) {
13
18
  this.domtoolsRef = domtoolsRefArg;
14
19
 
15
20
  // lets care
16
21
  this.goBrightBoolean = this.preferredColorSchemeMediaMatch.matches;
17
- this.preferredColorSchemeMediaMatch.addEventListener('change', (eventArg) => {
18
- this.goBrightBoolean = eventArg.matches;
19
- this.updateAllConnectedElements();
20
- });
22
+ this.preferredColorSchemeMediaMatch.addEventListener('change', this.preferredColorSchemeChangeHandler);
21
23
  this.updateAllConnectedElements();
22
24
  }
23
25
 
24
26
  public async enableAutomaticGlobalThemeChange() {
25
- if (document.body && document.body.style) {
26
- this.themeObservable.subscribe({
27
+ await this.domtoolsRef.domReady.promise;
28
+ if (!this.automaticGlobalThemeChangeSubscription) {
29
+ this.automaticGlobalThemeChangeSubscription = this.themeObservable.subscribe({
27
30
  next: (goBright) => {
28
31
  document.body.style.background = goBright ? '#fff' : '#000';
29
- }
32
+ },
30
33
  });
31
34
  }
32
35
  }
33
36
 
34
- private async updateAllConnectedElements() {
37
+ private updateAllConnectedElements() {
38
+ if (this.domtoolsRef.disposed) {
39
+ return;
40
+ }
35
41
  this.themeObservable.next(this.goBrightBoolean);
36
42
  }
37
43
 
@@ -58,4 +64,11 @@ export class ThemeManager {
58
64
  this.goBrightBoolean = !this.goBrightBoolean;
59
65
  this.updateAllConnectedElements();
60
66
  }
67
+
68
+ public dispose() {
69
+ this.preferredColorSchemeMediaMatch.removeEventListener('change', this.preferredColorSchemeChangeHandler);
70
+ this.automaticGlobalThemeChangeSubscription?.unsubscribe();
71
+ this.automaticGlobalThemeChangeSubscription = null;
72
+ this.themeObservable.complete();
73
+ }
61
74
  }