@descope/web-components-ui 1.104.0 → 1.105.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.
@@ -17,7 +17,14 @@ import {
17
17
  } from './helpers';
18
18
  import { formats } from './formats';
19
19
  import { calendarIcon } from './icons';
20
- import { counterConfig, DEFAULT_FORMAT, DIVIDER, NATIVE_FORMAT, valRange } from './consts';
20
+ import {
21
+ counterConfig,
22
+ DEFAULT_FORMAT,
23
+ DIVIDER,
24
+ MOBILE_DEVICE_INTERACTION_TIMEOUT_MS,
25
+ NATIVE_FORMAT,
26
+ valRange,
27
+ } from './consts';
21
28
  import { DateCounter } from './DateCounterClass';
22
29
  import { TextFieldClass } from '../descope-text-field/TextFieldClass';
23
30
  import { injectStyle } from '@descope-ui/common/components-helpers';
@@ -40,8 +47,6 @@ class RawDateFieldClass extends BaseInputClass {
40
47
 
41
48
  selectedCounterIdx = 0;
42
49
 
43
- #focused = false;
44
-
45
50
  updateCountersDisplay() {
46
51
  this.inputElement.value = this.countersValue;
47
52
  }
@@ -251,22 +256,7 @@ class RawDateFieldClass extends BaseInputClass {
251
256
  this.inputElement.addEventListener('click', this.handleMouseCaretPositionChange.bind(this));
252
257
  this.inputElement.addEventListener('keydown', this.handleArrowKeys.bind(this));
253
258
  this.inputElement.addEventListener('beforeinput', this.handleInput.bind(this));
254
-
255
- // We want to handle touch events the same way we handle `click` events.
256
- // Since we can't seem to block touch events (`touch-action: none` or preventing default on `touchstart`
257
- // or `touchend`, we listen to `pointerdown` and in case it's of type `touch` we execute
258
- // the component's logic for range selection.
259
- this.inputElement.addEventListener('pointerdown', (e) => {
260
- if (e.pointerType === 'touch') {
261
- e.preventDefault();
262
- if (!this.#focused) {
263
- this.inputElement.focus();
264
- }
265
- setTimeout(() => {
266
- this.handleMouseCaretPositionChange(e);
267
- }, 250);
268
- }
269
- });
259
+ this.inputElement.addEventListener('pointerdown', this.onPointerDown.bind(this));
270
260
 
271
261
  forwardAttrs(this, this.inputElement, {
272
262
  includeAttrs: [
@@ -464,16 +454,19 @@ class RawDateFieldClass extends BaseInputClass {
464
454
  });
465
455
  }
466
456
 
457
+ // In mobile devices, there are cases were `pointerdown` is triggered
458
+ // instead of `click`.
459
+ onPointerDown(e) {
460
+ setTimeout(() => this.handleMouseCaretPositionChange(e), MOBILE_DEVICE_INTERACTION_TIMEOUT_MS);
461
+ }
462
+
467
463
  onFocus() {
468
- if (this.isReadOnly || this.#focused) {
464
+ if (this.isReadOnly) {
469
465
  return;
470
466
  }
471
467
 
472
- // We use this flag to support mobile logic, which calls `focus` on touch event, we want to make sure
473
- // focus executes it logic only when needed.
474
- this.#focused = true;
475
-
476
- this.resetDisplay();
468
+ // We need to wait for focus to end before we set selection
469
+ setTimeout(() => this.resetDisplay());
477
470
  }
478
471
 
479
472
  resetDisplay() {
@@ -481,18 +474,12 @@ class RawDateFieldClass extends BaseInputClass {
481
474
  this.inputElement.value = this.format;
482
475
  }
483
476
 
484
- // On focus select the first part of the format placeholder
477
+ // On focus select the first counter
485
478
  this.selectedCounterIdx = 0;
486
-
487
- setTimeout(() => {
488
- // set selection on first counter
489
- this.inputElement.setSelectionRange(0, this.sortedCounters[0].length);
490
- });
479
+ this.inputElement.setSelectionRange(0, this.sortedCounters[0].length);
491
480
  }
492
481
 
493
482
  onBlur() {
494
- this.#focused = false;
495
-
496
483
  if (this.opened) {
497
484
  return;
498
485
  }
@@ -522,6 +509,8 @@ class RawDateFieldClass extends BaseInputClass {
522
509
  this.selectNextCounter();
523
510
  }
524
511
 
512
+ // We wait for the digit to be parsed, and only then set the selection.
513
+ // Failing to do so results in unexpected "jump" of the screen in mobile devices.
525
514
  this.setInputSelectionRange();
526
515
  }
527
516
 
@@ -537,7 +526,11 @@ class RawDateFieldClass extends BaseInputClass {
537
526
 
538
527
  setSelectedCounterByCaretPosition(e) {
539
528
  this.selectedCounterIdx = this.getCounterIdx(
540
- e.target.selectionStart || this.inputElement.selectionStart
529
+ // if triggered by touch event, target might not include `selectionStart`
530
+ // in that case we fall back to the inputElement's `selectionStart` value.
531
+ // Therefore, it is recommended to run this function with setTimeout,
532
+ // at least for mobile events.
533
+ e.target?.selectionStart || this.inputElement.selectionStart
541
534
  );
542
535
  }
543
536
 
@@ -563,14 +556,18 @@ class RawDateFieldClass extends BaseInputClass {
563
556
  return;
564
557
  }
565
558
 
566
- const caretStart = this.sortedCounters
567
- .slice(0, this.selectedCounterIdx)
568
- .reduce((acc, counter) => acc + counter.length, this.selectedCounterIdx);
559
+ // We wait for before setting the selection, otherwise there's an
560
+ // unexpected "jump" of the screen in mobile devices.
561
+ setTimeout(() => {
562
+ const caretStart = this.sortedCounters
563
+ .slice(0, this.selectedCounterIdx)
564
+ .reduce((acc, counter) => acc + counter.length, this.selectedCounterIdx);
569
565
 
570
- this.inputElement.setSelectionRange(
571
- caretStart,
572
- caretStart + this.sortedCounters[this.selectedCounterIdx].length
573
- );
566
+ this.inputElement.setSelectionRange(
567
+ caretStart,
568
+ caretStart + this.sortedCounters[this.selectedCounterIdx].length
569
+ );
570
+ });
574
571
  }
575
572
 
576
573
  resetDateCounters() {
@@ -607,9 +604,7 @@ class RawDateFieldClass extends BaseInputClass {
607
604
  this.selectPrevCounter();
608
605
  }
609
606
 
610
- setTimeout(() => {
611
- this.setInputSelectionRange();
612
- });
607
+ this.setInputSelectionRange();
613
608
  }
614
609
 
615
610
  handleNavKeys(e) {
@@ -631,27 +626,32 @@ class RawDateFieldClass extends BaseInputClass {
631
626
  }
632
627
 
633
628
  handleBackspace() {
634
- if (this.activeCounter.isEmpty) {
635
- this.activeCounter.clear();
629
+ const counter = this.activeCounter;
630
+
631
+ if (counter.isEmpty) {
636
632
  this.selectPrevCounter();
637
633
  this.setInputSelectionRange();
638
634
  } else {
639
- this.activeCounter.del();
635
+ counter.set('');
640
636
  }
637
+
638
+ // To support keyboards like SwiftKey, we need to re-render the counters display and selection,
639
+ // otherwise we get an unexpected behavior, where the format is deleted.
640
+ setTimeout(() => {
641
+ this.updateCountersDisplay();
642
+ this.setInputSelectionRange();
643
+ });
641
644
  }
642
645
 
643
646
  handleMouseCaretPositionChange(e) {
644
647
  if (this.opened) {
645
648
  return;
646
649
  }
650
+
647
651
  e.preventDefault();
648
- this.setSelectedCounterByCaretPosition(e);
649
652
 
650
- // On keydown - in desktop mode - selection is sometimes not set, and instead there is a cursor.
651
- // We need to wait until we can set selection range.
652
- setTimeout(() => {
653
- this.setInputSelectionRange();
654
- });
653
+ this.setSelectedCounterByCaretPosition(e);
654
+ this.setInputSelectionRange();
655
655
  }
656
656
 
657
657
  onInitialValueChange(val) {
@@ -46,3 +46,5 @@ export const valRange = {
46
46
  export const BUTTON_LABEL_DONE = 'Done';
47
47
  export const BUTTON_LABEL_CANCEL = 'Cancel';
48
48
  export const CALENDAR_LABEL_TODAY = 'Today';
49
+
50
+ export const MOBILE_DEVICE_INTERACTION_TIMEOUT_MS = 150;