@descope/web-components-ui 1.88.0 → 1.90.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.
@@ -42,10 +42,40 @@ class RawDateFieldClass extends BaseInputClass {
42
42
 
43
43
  selectedCounterIdx = 0;
44
44
 
45
+ updateCountersDisplay() {
46
+ this.inputElement.value = this.countersValue;
47
+ }
48
+
49
+ updateValue() {
50
+ if (this.isCountersOutOfRange) {
51
+ this.updateTimestamp('');
52
+ } else {
53
+ const date = formats[this.format].getDate(this.inputElement.value);
54
+ this.updateTimestamp(date.getTime());
55
+ }
56
+ }
57
+
58
+ onDateCounterChange = () => {
59
+ this.updateCountersDisplay();
60
+ this.updateValue();
61
+ // update validity
62
+ this.#dispatchInput();
63
+ };
64
+
65
+ updateTimestamp(epochOrDate) {
66
+ if (!epochOrDate) {
67
+ this.timestamp = '';
68
+ } else {
69
+ this.timestamp = newDate(epochOrDate).getTime();
70
+ }
71
+ }
72
+
73
+ #yearDateCounter = new DateCounter(counterConfig.YEAR, this.onDateCounterChange.bind(this));
74
+
45
75
  dateCounters = [
46
- new DateCounter(counterConfig.MONTH),
47
- new DateCounter(counterConfig.DAY),
48
- new DateCounter(counterConfig.YEAR),
76
+ new DateCounter(counterConfig.MONTH, this.onDateCounterChange.bind(this)),
77
+ new DateCounter(counterConfig.DAY, this.onDateCounterChange.bind(this)),
78
+ this.#yearDateCounter,
49
79
  ];
50
80
 
51
81
  static get observedAttributes() {
@@ -97,11 +127,10 @@ class RawDateFieldClass extends BaseInputClass {
97
127
 
98
128
  this.inputElement = this.shadowRoot.querySelector('descope-text-field');
99
129
  this.popoverToggleButton = this.inputElement.querySelector('.toggle-calendar');
130
+ }
100
131
 
101
- this.oninvalid = () => {
102
- this.inputElement.setAttribute('invalid', 'true');
103
- this.inputElement.focus();
104
- };
132
+ get validationTarget() {
133
+ return this.inputElement;
105
134
  }
106
135
 
107
136
  get opened() {
@@ -109,7 +138,7 @@ class RawDateFieldClass extends BaseInputClass {
109
138
  }
110
139
 
111
140
  // returns the input's value as a timestamp
112
- get inputValueTimestamp() {
141
+ get displayValueEpoch() {
113
142
  const date = formats[this.format].getDate(this.inputElement.value);
114
143
 
115
144
  if (!isValidTimestamp(date?.getTime())) {
@@ -161,37 +190,32 @@ class RawDateFieldClass extends BaseInputClass {
161
190
  }
162
191
 
163
192
  set value(val) {
164
- if (!val) return;
165
-
166
- const numVal = Number(val);
167
- const isValTimestamp = !Number.isNaN(numVal);
168
-
169
- let date;
170
- let timestamp;
171
-
172
- if (isValTimestamp) {
173
- date = newDate(numVal);
174
- timestamp = numVal;
193
+ if (val) {
194
+ this.updateTimestamp(val);
195
+ this.updateDateCounters(newDate(val));
175
196
  } else {
176
- date = newDate(val);
177
- timestamp = date.getTime();
197
+ this.updateTimestamp('');
178
198
  }
199
+ }
179
200
 
180
- if (!isValidTimestamp(timestamp) || timestamp === this.timestamp) {
181
- return;
182
- }
201
+ get isCountersEmpty() {
202
+ return this.dateCounters.every((dc) => dc.isEmpty);
203
+ }
183
204
 
184
- this.timestamp = timestamp;
205
+ get isCountersOutOfRange() {
206
+ return this.dateCounters.some((dc) => !dc.isInRange(dc.numberValue));
207
+ }
185
208
 
186
- this.updateInputDisplay();
187
- this.updateDateCounters(date);
209
+ reportValidity() {
210
+ this.inputElement.reportValidity();
211
+ }
188
212
 
189
- // since baseElement is set to vaadin-popover, we need to manually dispatch an input event to trigger getValidity
190
- this.dispatchEvent(new Event('input'));
213
+ #dispatchInput() {
214
+ this.inputElement.baseElement.dispatchEvent(new Event('input', { bubbles: true }));
191
215
  }
192
216
 
193
217
  updateInputDisplay() {
194
- this.inputElement.value = formatTimestamp(newDate(this.value).getTime(), this.format);
218
+ this.inputElement.value = formatTimestamp(newDate(this.countersValue).getTime(), this.format);
195
219
  }
196
220
 
197
221
  init() {
@@ -199,6 +223,7 @@ class RawDateFieldClass extends BaseInputClass {
199
223
 
200
224
  this.updateFormatPattern();
201
225
  this.initPopover();
226
+ this.onDateCounterChange();
202
227
  this.initInputElement();
203
228
 
204
229
  setTimeout(() => {
@@ -207,15 +232,16 @@ class RawDateFieldClass extends BaseInputClass {
207
232
  }
208
233
 
209
234
  initInputElement() {
235
+ this.inputElement.getValidity = this.getValidity.bind(this);
236
+ this.inputElement.baseElement.checkValidity = this.checkValidity.bind(this);
237
+
210
238
  this.popoverToggleButton.addEventListener('click', this.onPopoverToggle.bind(this));
211
239
 
212
240
  this.inputElement.addEventListener('focus', this.onFocus.bind(this));
213
241
  this.inputElement.addEventListener('blur', this.onBlur.bind(this));
214
- this.inputElement.addEventListener('input', this.onInput.bind(this));
215
242
  this.inputElement.addEventListener('click', this.handleMouseCaretPositionChange.bind(this));
216
- this.inputElement.addEventListener('keydown', this.handleKeyDownValueChange.bind(this));
217
- this.inputElement.addEventListener('keydown', this.handleKeydownCaretPositionChange.bind(this));
218
- this.inputElement.addEventListener('keydown', this.handleValueChange.bind(this));
243
+ this.inputElement.addEventListener('keydown', this.handleNavKeys.bind(this));
244
+ this.inputElement.addEventListener('keydown', this.handleDigitKeys.bind(this));
219
245
 
220
246
  forwardAttrs(this, this.inputElement, {
221
247
  includeAttrs: [
@@ -229,8 +255,14 @@ class RawDateFieldClass extends BaseInputClass {
229
255
  'full-width',
230
256
  'st-host-direction',
231
257
  'pattern',
232
- 'invalid',
233
258
  'bordered',
259
+ 'data-errormessage-value-missing',
260
+ 'data-errormessage-pattern-mismatch',
261
+ 'data-errormessage-range-underflow',
262
+ 'data-errormessage-range-overflow',
263
+ 'st-error-message-icon',
264
+ 'st-error-message-icon-size',
265
+ 'st-error-message-icon-padding',
234
266
  ],
235
267
  });
236
268
  }
@@ -358,7 +390,7 @@ class RawDateFieldClass extends BaseInputClass {
358
390
  this.getCounterById('month').replaceValue(calendarDate.getMonth() + 1);
359
391
  this.getCounterById('day').replaceValue(calendarDate.getDate());
360
392
 
361
- this.dispatchEvent(new Event('input'));
393
+ this.#dispatchInput();
362
394
  }
363
395
 
364
396
  this.closePopover();
@@ -369,10 +401,10 @@ class RawDateFieldClass extends BaseInputClass {
369
401
  isValidTimestamp(newDate(this.inputElement.value || '').getTime()) &&
370
402
  formats[this.format].validate(this.inputElement.value);
371
403
 
372
- if (this.inputValueTimestamp || validInputVal) {
404
+ if (this.displayValueEpoch || validInputVal) {
373
405
  this.calendar.setAttribute(
374
406
  'initial-value',
375
- formatTimestamp(this.inputValueTimestamp || this.timestamp, NATIVE_FORMAT)
407
+ formatTimestamp(this.displayValueEpoch || this.timestamp, NATIVE_FORMAT)
376
408
  );
377
409
  } else {
378
410
  this.calendar.clearValue();
@@ -395,13 +427,6 @@ class RawDateFieldClass extends BaseInputClass {
395
427
  });
396
428
  }
397
429
 
398
- onInput(e) {
399
- if (!e.target.value) {
400
- this.calendar?.clear();
401
- this.calendar?.renderCalendar();
402
- }
403
- }
404
-
405
430
  onFocus() {
406
431
  if (this.isReadOnly) {
407
432
  return;
@@ -413,16 +438,13 @@ class RawDateFieldClass extends BaseInputClass {
413
438
  }
414
439
  }
415
440
 
416
- clearInputValue() {
417
- this.inputElement.value = '';
418
- this.resetDateCounters();
419
- }
420
-
421
441
  onBlur() {
422
- if (this.inputValueTimestamp) {
423
- this.value = this.inputValueTimestamp;
424
- } else if (!this.opened && this.countersValue === this.format) {
425
- this.clearInputValue();
442
+ if (this.opened) {
443
+ return;
444
+ }
445
+
446
+ if (this.inputElement.value === this.format) {
447
+ this.inputElement.value = '';
426
448
  }
427
449
  }
428
450
 
@@ -439,11 +461,11 @@ class RawDateFieldClass extends BaseInputClass {
439
461
  this.setAttribute('pattern', formats[format].pattern);
440
462
  }
441
463
 
442
- handleValueChange(e) {
464
+ handleDigitKeys(e) {
443
465
  if (isNumber(e.key)) {
444
466
  e.preventDefault();
445
467
 
446
- this.handleCountersValue(e.key);
468
+ this.activeCounter.add(e.key);
447
469
 
448
470
  if (this.activeCounter.isFull) {
449
471
  this.selectNextCounter();
@@ -463,11 +485,6 @@ class RawDateFieldClass extends BaseInputClass {
463
485
  return [c1, c2, c3].indexOf(true);
464
486
  }
465
487
 
466
- handleCountersValue(val) {
467
- this.activeCounter.add(val);
468
- this.inputElement.value = this.countersValue;
469
- }
470
-
471
488
  setSelectedCounterByCaretPosition(e) {
472
489
  this.selectedCounterIdx = this.getCounterIdx(e.target.selectionStart);
473
490
  }
@@ -527,21 +544,21 @@ class RawDateFieldClass extends BaseInputClass {
527
544
  });
528
545
  }
529
546
 
530
- handleKeyDownValueChange(e) {
547
+ handleNavKeys(e) {
531
548
  if (this.isReadOnly) {
532
549
  return;
533
550
  }
534
551
 
535
552
  const { key, shiftKey, metaKey } = e;
536
553
  const keys = getKeyMap(key, shiftKey, metaKey);
537
- const allowedOperations = keys.refresh || keys.tab || keys.shiftTab;
538
554
 
539
555
  if (this.opened) {
540
556
  this.closePopover();
541
557
  }
542
558
 
559
+ e.preventDefault();
560
+
543
561
  if (isSupportedKey(key)) {
544
- e.preventDefault();
545
562
  const counter = this.activeCounter;
546
563
 
547
564
  if (!counter) return;
@@ -558,12 +575,10 @@ class RawDateFieldClass extends BaseInputClass {
558
575
  else if (keys.pageDown) counter.dec(count);
559
576
  else if (keys.shiftPageUp) counter.inc(shiftCount);
560
577
  else if (keys.shiftPageDown) counter.dec(shiftCount);
561
-
562
- this.inputElement.value = this.countersValue;
578
+ else if (keys.arrowRight) this.selectNextCounter();
579
+ else if (keys.arrowLeft) this.selectPrevCounter();
563
580
 
564
581
  this.setInputSelectionRange();
565
- } else if (!allowedOperations) {
566
- e.preventDefault();
567
582
  }
568
583
  }
569
584
 
@@ -578,25 +593,6 @@ class RawDateFieldClass extends BaseInputClass {
578
593
  }
579
594
  }
580
595
 
581
- handleKeydownCaretPositionChange(e) {
582
- if (this.opened) {
583
- return;
584
- }
585
-
586
- const { key } = e;
587
-
588
- if (isSupportedKey(key)) {
589
- e.preventDefault();
590
-
591
- const keys = getKeyMap(key, false);
592
-
593
- if (keys.arrowRight) this.selectNextCounter();
594
- else if (keys.arrowLeft) this.selectPrevCounter();
595
-
596
- this.setInputSelectionRange();
597
- }
598
- }
599
-
600
596
  handleMouseCaretPositionChange(e) {
601
597
  if (this.opened) {
602
598
  return;
@@ -616,10 +612,22 @@ class RawDateFieldClass extends BaseInputClass {
616
612
  });
617
613
  }
618
614
 
615
+ setYearRange(val) {
616
+ if (!val) return;
617
+ const [min, max] = val.split?.('-');
618
+ if (min && max) {
619
+ this.#yearDateCounter.setMin(min);
620
+ this.#yearDateCounter.setMax(max);
621
+ }
622
+ }
623
+
619
624
  attributeChangedCallback(attrName, oldValue, newValue) {
620
625
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
621
626
 
622
627
  if (oldValue !== newValue) {
628
+ if (attrName === 'years-range') {
629
+ this.setYearRange(newValue);
630
+ }
623
631
  if (dateFieldAttrs.includes(attrName)) {
624
632
  if (newValue && attrName === 'format') {
625
633
  this.onFormatUpdate(newValue);
@@ -638,16 +646,20 @@ class RawDateFieldClass extends BaseInputClass {
638
646
  }
639
647
 
640
648
  getValidity() {
641
- if (this.isRequired && !this.inputElement.value) {
649
+ if (this.isRequired && this.isCountersEmpty) {
642
650
  return { valueMissing: true };
643
651
  }
644
652
 
653
+ if (this.isCountersOutOfRange) {
654
+ return { patternMismatch: true };
655
+ }
656
+
645
657
  return {};
646
658
  }
647
659
  }
648
660
 
649
661
  const textVars = TextFieldClass.cssVarList;
650
- const { host, input, inputEleRTL, toggleButton, overlay, backdrop } = {
662
+ const { host, input, inputEleRTL, toggleButton, overlay, backdrop, errorMessage } = {
651
663
  host: { selector: () => ':host' },
652
664
  input: { selector: () => 'descope-text-field' },
653
665
  inputEleRTL: { selector: () => ':host([st-host-direction="rtl"]) descope-text-field' },
@@ -685,6 +697,30 @@ export const DateFieldClass = compose(
685
697
  overlayOutlineStyle: {
686
698
  property: () => DateFieldClass.cssVarList.overlayOutlineStyle,
687
699
  },
700
+ errorMessageIcon: {
701
+ selector: TextFieldClass.componentName,
702
+ property: TextFieldClass.cssVarList.errorMessageIcon,
703
+ },
704
+ errorMessageIconSize: {
705
+ selector: TextFieldClass.componentName,
706
+ property: TextFieldClass.cssVarList.errorMessageIconSize,
707
+ },
708
+ errorMessageIconPadding: {
709
+ selector: TextFieldClass.componentName,
710
+ property: TextFieldClass.cssVarList.errorMessageIconPadding,
711
+ },
712
+ errorMessageIconRepeat: {
713
+ selector: TextFieldClass.componentName,
714
+ property: TextFieldClass.cssVarList.errorMessageIconRepeat,
715
+ },
716
+ errorMessageIconPosition: {
717
+ selector: TextFieldClass.componentName,
718
+ property: TextFieldClass.cssVarList.errorMessageIconPosition,
719
+ },
720
+ errorMessageFontSize: {
721
+ selector: TextFieldClass.componentName,
722
+ property: TextFieldClass.cssVarList.errorMessageFontSize,
723
+ },
688
724
  },
689
725
  }),
690
726
  portalMixin({
@@ -17,6 +17,7 @@ export const COUNTER_SUPPORTED_KEYS = [
17
17
  'PageUp',
18
18
  'PageDown',
19
19
  'Meta',
20
+ 'Enter',
20
21
  ];
21
22
 
22
23
  export const months = [
@@ -47,7 +48,7 @@ export const weekdays = [
47
48
  export const counterConfig = {
48
49
  MONTH: { id: 'month', min: 1, max: 12, placeholder: 'MM', count: 5, shiftCount: 10 },
49
50
  DAY: { id: 'day', min: 1, max: 31, placeholder: 'DD', count: 5, shiftCount: 10 },
50
- YEAR: { id: 'year', min: 0, max: 9999, placeholder: 'YYYY', count: 10, shiftCount: 100 },
51
+ YEAR: { id: 'year', min: 1900, max: 2099, placeholder: 'YYYY', count: 10, shiftCount: 100 },
51
52
  };
52
53
 
53
54
  export const BUTTON_LABEL_DONE = 'Done';
@@ -5,7 +5,7 @@ export const isValidTimestamp = (val) => !Number.isNaN(Number(val));
5
5
  export const isNumber = (val) => !Number.isNaN(Number(val));
6
6
 
7
7
  export const getTimestampParts = (timestamp) => {
8
- const date = new Date(timestamp);
8
+ const date = newDate(timestamp);
9
9
  const year = date.getFullYear();
10
10
  const month = date.getMonth() + 1;
11
11
  const day = date.getDate();
@@ -53,6 +53,7 @@ export const getKeyMap = (key, shiftKey, metaKey) => {
53
53
  shiftArrowDown: shiftKey && key === 'ArrowDown',
54
54
  shiftPageUp: shiftKey && key === 'PageUp',
55
55
  shiftPageDown: shiftKey && key === 'PageDown',
56
+ enter: key === 'Enter',
56
57
  };
57
58
  };
58
59
 
@@ -26,6 +26,13 @@ const dateField = {
26
26
 
27
27
  [vars.rtlInputDirection]: 'ltr',
28
28
  [vars.rtlInputAlignment]: 'right',
29
+
30
+ [vars.errorMessageIcon]: refs.errorMessageIcon,
31
+ [vars.errorMessageIconSize]: refs.errorMessageIconSize,
32
+ [vars.errorMessageIconPadding]: refs.errorMessageIconPadding,
33
+ [vars.errorMessageIconRepeat]: refs.errorMessageIconRepeat,
34
+ [vars.errorMessageIconPosition]: refs.errorMessageIconPosition,
35
+ [vars.errorMessageFontSize]: refs.errorMessageFontSize,
29
36
  };
30
37
 
31
38
  export default dateField;