@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.
- package/dist_bundle/bundle.js +83 -83
- 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 +3 -4
- 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
|
@@ -129,8 +129,15 @@ export enum Key {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
export class Keyboard {
|
|
132
|
-
private mapCombosToHandlers = new Map<
|
|
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
|
-
|
|
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
|
|
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.
|
|
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((
|
|
180
|
-
if (this.areAllKeysPressed(
|
|
181
|
-
subjectArg.
|
|
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.
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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 = (
|
|
17
|
+
private handleNativeScroll = (_event: Event): void => {
|
|
15
18
|
this.executeScrollCallbacks();
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
private handleLenisScroll = (
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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.
|
|
173
|
-
|
|
174
|
-
this.
|
|
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',
|
|
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
|
-
|
|
26
|
-
|
|
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
|
|
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
|
}
|