@progress/kendo-dateinputs-common 0.2.0-dev.202301130811 → 0.2.0-dev.202301161405

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.
@@ -600,8 +600,8 @@ var DateObject = /** @class */ (function () {
600
600
  var hasFixedFormat = (this.format === baseFormat) ||
601
601
  (this.format === originalFormat) ||
602
602
  (this.format.length === originalFormat.length);
603
- if (isInCaretMode) {
604
- var processedSegmentCharsCount = 0;
603
+ var processedSegmentCharsCount = 0;
604
+ if (isInCaretMode || (!isInCaretMode && !this.autoCorrectParts)) {
605
605
  for (var i = 0; i < baseDate.length; i++) {
606
606
  if (baseFormat[i] === symbol) {
607
607
  var existing = this.getExisting(symbol);
@@ -627,10 +627,17 @@ var DateObject = /** @class */ (function () {
627
627
  baseSuffix += baseDate[i];
628
628
  }
629
629
  }
630
- if (hasFixedFormat) {
630
+ if (hasFixedFormat && this.autoCorrectParts) {
631
631
  if (originalFormat.length < rawInputValue.length) {
632
632
  datePartText += currentChar;
633
633
  }
634
+ else if (!isDeleting && originalFormat.length > rawInputValue.length) {
635
+ var lengthDiff = originalFormat.length - rawInputValue.length;
636
+ var trimmedDatePartText = datePartText.substring(0, datePartText.length - lengthDiff);
637
+ if (trimmedDatePartText && trimmedDatePartText.length > 0) {
638
+ datePartText = trimmedDatePartText;
639
+ }
640
+ }
634
641
  if (datePartText.length > segmentLength) {
635
642
  return extend(parseResult, { value: null, switchToNext: false });
636
643
  }
@@ -668,22 +675,45 @@ var DateObject = /** @class */ (function () {
668
675
  if (originalFormat.length < rawInputValue.length) {
669
676
  datePartText += currentChar;
670
677
  }
678
+ else if (!isDeleting && originalFormat.length > rawInputValue.length) {
679
+ var lengthDiff = originalFormat.length - rawInputValue.length;
680
+ var trimmedDatePartText = datePartText.substring(0, datePartText.length - lengthDiff);
681
+ if (trimmedDatePartText && trimmedDatePartText.length > 0) {
682
+ var originalDatePartText = datePartText;
683
+ datePartText = trimmedDatePartText;
684
+ if (prefix + datePartText + suffix !== rawInputValue) {
685
+ // move trimmed characters to the suffix
686
+ suffix = originalDatePartText.substring(datePartText.length) + suffix;
687
+ }
688
+ }
689
+ }
671
690
  }
672
691
  }
673
- else {
674
- for (var i = 0; i < baseDate.length; i++) {
675
- if (baseFormat[i] === symbol) {
676
- var existing = this.getExisting(symbol);
677
- current += existing ? baseDate[i] : '0';
678
- replaced = true;
679
- }
680
- else if (!replaced) {
681
- prefix += baseDate[i];
682
- }
683
- else {
684
- suffix += baseDate[i];
692
+ if (!isInCaretMode) {
693
+ if (this.autoCorrectParts) {
694
+ processedSegmentCharsCount = 0;
695
+ current = "";
696
+ datePartText = "";
697
+ prefix = "";
698
+ suffix = "";
699
+ replaced = false;
700
+ for (var i = 0; i < baseDate.length; i++) {
701
+ if (baseFormat[i] === symbol) {
702
+ var existing = this.getExisting(symbol);
703
+ current += existing ? baseDate[i] : '0';
704
+ replaced = true;
705
+ }
706
+ else if (!replaced) {
707
+ prefix += baseDate[i];
708
+ }
709
+ else {
710
+ suffix += baseDate[i];
711
+ }
685
712
  }
686
713
  }
714
+ else {
715
+ current = resetSegmentValue ? datePartText : current;
716
+ }
687
717
  }
688
718
  var parsedDate = null;
689
719
  var month = this.matchMonth(currentChar);
@@ -709,18 +739,24 @@ var DateObject = /** @class */ (function () {
709
739
  }
710
740
  var currentMaxLength = current.length - 3;
711
741
  var tryParse = true;
742
+ var middle = isInCaretMode ? datePartText : current;
712
743
  for (var i = Math.max(0, currentMaxLength); i <= current.length; i++) {
713
744
  if (!tryParse) {
714
745
  break;
715
746
  }
716
- if (!this.autoCorrectParts) {
717
- tryParse = false;
718
- }
719
- var middle = resetSegmentValue ?
747
+ middle = resetSegmentValue ?
720
748
  currentChar :
721
749
  isInCaretMode ?
722
750
  datePartText :
723
751
  (current.substring(i) + currentChar);
752
+ if (!this.autoCorrectParts) {
753
+ tryParse = false;
754
+ if (!isInCaretMode) {
755
+ // try to make an exact match as there will be only 1 attempt
756
+ middle = unpadZero(middle);
757
+ middle = padZero(segmentLength - middle.length) + middle;
758
+ }
759
+ }
724
760
  if (isInCaretMode) {
725
761
  // try to make an exact match as there will be only 1 attempt
726
762
  tryParse = false;
@@ -737,11 +773,13 @@ var DateObject = /** @class */ (function () {
737
773
  // use the base prefix and suffix, e.g. convert the candidate date string
738
774
  // to "Thursday, February 1, 2022 3:04:05 AM"
739
775
  // as "EEEE, February..." is not parsable
740
- parsedDate = this.intl.parseDate(basePrefix + middle + baseSuffix, this.format, this.localeId);
741
- datePartText = middle;
776
+ if (this.autoCorrectParts) {
777
+ parsedDate = this.intl.parseDate(basePrefix + middle + baseSuffix, this.format, this.localeId);
778
+ datePartText = middle;
779
+ }
742
780
  }
743
781
  var isCurrentCharParsable = !isNaN(parseInt(currentChar, 10)) || (isInCaretMode && isDeleting && currentChar === "");
744
- if (!parsedDate && !isNaN(middleNumber) && isCurrentCharParsable) {
782
+ if (!parsedDate && !isNaN(middleNumber) && isCurrentCharParsable && this.autoCorrectParts) {
745
783
  if (symbol === MONTH_SYMBOL && !month) {
746
784
  // JS months start from 0 (January) instead of 1 (January)
747
785
  var monthNumber = middleNumber - JS_MONTH_OFFSET;
@@ -776,6 +814,26 @@ var DateObject = /** @class */ (function () {
776
814
  }
777
815
  this._value = parsedDate;
778
816
  this.setExisting(symbol, true);
817
+ this.resetInvalidDateSymbol(symbol);
818
+ if (!this.autoCorrectParts) {
819
+ if (symbol === "M") {
820
+ if (this.getExisting("M") && this.getExisting("y")) {
821
+ // changing from 28/Feb to 29/Feb to 29/March
822
+ this.setExisting("d", true);
823
+ this.resetInvalidDateSymbol("d");
824
+ }
825
+ }
826
+ else if (symbol === "d") {
827
+ if (this.getExisting("d") && this.getExisting("y")) {
828
+ // changing from 31/Jan to 31/Feb to 28/Feb
829
+ this.setExisting("M", true);
830
+ this.resetInvalidDateSymbol("M");
831
+ }
832
+ }
833
+ if (!this.hasInvalidDatePart()) {
834
+ this.markDatePartsAsExisting();
835
+ }
836
+ }
779
837
  if (isInCaretMode && switchToNext) {
780
838
  if (symbol === "M") {
781
839
  if (segmentLength <= MONTH_PART_WITH_WORDS_THRESHOLD) {
@@ -822,28 +880,30 @@ var DateObject = /** @class */ (function () {
822
880
  return extend(parseResult, { value: null, switchToNext: false });
823
881
  }
824
882
  if (!this.autoCorrectParts) {
825
- this.setExisting(symbol, false);
826
- // todo check if string is better
827
- // const padPrefix = padZero(Math.abs(current.length - datePartText.length));
828
- // const paddedDatePartText = padPrefix + datePartText;
829
883
  var datePartValue = void 0;
830
- var textToParse = isInCaretMode ? datePartText : current;
884
+ var textToParse = isInCaretMode ? datePartText : middle;
831
885
  var parsedValue = parseToInt(textToParse);
832
886
  if (isNumber(parsedValue)) {
887
+ if ((symbol === "d" && (parsedValue <= 0 || parsedValue > 31)) ||
888
+ (symbol === "M" && (parsedValue < 0 || parsedValue > 11))) {
889
+ return extend(parseResult, { value: null, switchToNext: false });
890
+ }
833
891
  datePartValue = symbol === "M" ?
834
892
  clamp(parsedValue - JS_MONTH_OFFSET, 0, 12 - JS_MONTH_OFFSET) :
835
893
  parsedValue;
836
- }
837
- if (isNumber(datePartValue)) {
838
- var newDate = this.modifyDateSymbolWithValue(this.value, symbol, datePartValue);
839
894
  // if (!isEqual(newDate, this.value)) {
840
895
  this.setExisting(symbol, false);
841
896
  this.setInvalidDatePart(symbol, {
842
897
  value: datePartValue,
843
- date: cloneDate(newDate),
898
+ date: cloneDate(this.value),
844
899
  startDate: this._partiallyInvalidDate.startDate || cloneDate(this.value)
845
900
  });
846
901
  // }
902
+ return extend(parseResult, {
903
+ value: null,
904
+ switchToNext: false,
905
+ hasInvalidDatePart: true
906
+ });
847
907
  }
848
908
  }
849
909
  return extend(parseResult, { value: null, switchToNext: false });
@@ -1060,13 +1120,16 @@ var DateObject = /** @class */ (function () {
1060
1120
  }
1061
1121
  else {
1062
1122
  datePartText = (parseToInt(this.getInvalidDatePartValue(symbol)) + JS_MONTH_OFFSET).toString();
1063
- resultText = datePartText + resultText;
1123
+ var formattedDatePart = padZero(partsForSegment.length - datePartText.length) + datePartText;
1124
+ resultText = formattedDatePart + resultText;
1125
+ formatSymbolIndexModifier = partsForSegment.length - 1;
1064
1126
  ignoreFormatSymbolsCount = datePartText.length - partsForSegment.length;
1065
1127
  }
1066
1128
  }
1067
1129
  else {
1068
- resultText = datePartText + resultText;
1069
- formatSymbolIndexModifier = datePartText.length - 1;
1130
+ var formattedDatePart = padZero(partsForSegment.length - datePartText.length) + datePartText;
1131
+ resultText = formattedDatePart + resultText;
1132
+ formatSymbolIndexModifier = partsForSegment.length - 1;
1070
1133
  ignoreFormatSymbolsCount = datePartText.length - partsForSegment.length;
1071
1134
  }
1072
1135
  }
@@ -1157,13 +1220,34 @@ var DateObject = /** @class */ (function () {
1157
1220
  };
1158
1221
  DateObject.prototype.setInvalidDatePart = function (symbol, _a) {
1159
1222
  var _b = _a.value, value = _b === void 0 ? null : _b, _c = _a.date, date = _c === void 0 ? null : _c, _d = _a.startDateOffset, startDateOffset = _d === void 0 ? 0 : _d, _e = _a.startDate, startDate = _e === void 0 ? null : _e;
1223
+ var clampedValue = value;
1224
+ if (symbol === "d") {
1225
+ clampedValue = clamp(isPresent(value) ? value : 1, 1, 31);
1226
+ }
1227
+ else if (symbol === "M") {
1228
+ clampedValue = clamp(isPresent(value) ? value : 0, 0, 11);
1229
+ }
1160
1230
  if (this._partiallyInvalidDate.invalidDateParts[symbol]) {
1161
- this._partiallyInvalidDate.invalidDateParts[symbol].value = value;
1231
+ this._partiallyInvalidDate.invalidDateParts[symbol].value = clampedValue;
1162
1232
  this._partiallyInvalidDate.invalidDateParts[symbol].date = date;
1163
1233
  this._partiallyInvalidDate.invalidDateParts[symbol].startDateOffset = startDateOffset;
1164
1234
  this._partiallyInvalidDate.startDate = startDate;
1165
1235
  }
1166
1236
  };
1237
+ /**
1238
+ * @hidden
1239
+ */
1240
+ DateObject.prototype.hasInvalidDatePart = function () {
1241
+ var _this = this;
1242
+ var hasInvalidDatePart = false;
1243
+ Object.keys(this._partiallyInvalidDate.invalidDateParts).forEach(function (key) {
1244
+ if (_this._partiallyInvalidDate.invalidDateParts[key] &&
1245
+ isPresent(_this._partiallyInvalidDate.invalidDateParts[key].value)) {
1246
+ hasInvalidDatePart = true;
1247
+ }
1248
+ });
1249
+ return hasInvalidDatePart;
1250
+ };
1167
1251
  /**
1168
1252
  * @hidden
1169
1253
  */
@@ -344,6 +344,7 @@ var DateInput = /** @class */ (function (_super) {
344
344
  var lastParseResultHasNoValue = lastParseResult && !lastParseResult.value;
345
345
  var parsingFailedOnDelete = (hasCaret && (isBackspaceKey || isDeleteKey) && lastParseResultHasNoValue);
346
346
  var resetPart = lastParseResult ? lastParseResult.resetPart : false;
347
+ var hasInvalidDatePart = lastParseResult ? lastParseResult.hasInvalidDatePart : false;
347
348
  var hasDateValueChanged = !isEqual(oldDateObjectValue, this.dateObject.value);
348
349
  var symbolForSelection;
349
350
  var currentSelection = this.selection;
@@ -369,20 +370,14 @@ var DateInput = /** @class */ (function (_super) {
369
370
  }
370
371
  }
371
372
  else if (hasCaret) {
372
- if (this.options.format.length !== this.currentFormat.length) {
373
- if (hasDateValueChanged && isPresent(this.dateObject.getValue())) {
374
- if (switchPart) {
375
- this.switchDateSegment(1);
376
- }
373
+ if ((hasDateValueChanged && isPresent(this.dateObject.getValue()) ||
374
+ (!hasDateValueChanged && switchPart && isPresent(this.dateObject.getValue())))) {
375
+ if (switchPart) {
376
+ this.switchDateSegment(1);
377
377
  }
378
378
  }
379
379
  else {
380
- if (hasDateValueChanged) {
381
- if (lastParseResultHasNoValue) {
382
- this.restorePreviousInputEventState();
383
- }
384
- }
385
- else {
380
+ if (lastParseResultHasNoValue && !hasInvalidDatePart) {
386
381
  this.restorePreviousInputEventState();
387
382
  }
388
383
  }
@@ -395,20 +390,22 @@ var DateInput = /** @class */ (function (_super) {
395
390
  }
396
391
  else {
397
392
  // the input is not complete, not parsable or not updatable
398
- if (hasCaret && originalInteractionMode !== DateInputInteractionMode.Caret && hasDateValueChanged) {
393
+ if (originalInteractionMode !== DateInputInteractionMode.Caret && hasDateValueChanged) {
399
394
  }
400
- else if (hasCaret && originalInteractionMode !== DateInputInteractionMode.Caret) {
395
+ else if (originalInteractionMode !== DateInputInteractionMode.Caret) {
401
396
  symbolForSelection = this.currentFormat[currentSelection.start];
402
397
  this.forceUpdate();
403
398
  this.setSelection(this.selectionBySymbol(symbolForSelection));
404
399
  }
405
- else {
400
+ else if (!hasInvalidDatePart) {
406
401
  this.restorePreviousInputEventState();
407
402
  }
408
403
  }
409
404
  }
410
405
  else {
411
- this.restorePreviousInputEventState();
406
+ if (!hasInvalidDatePart) {
407
+ this.restorePreviousInputEventState();
408
+ }
412
409
  }
413
410
  }
414
411
  else if (this.options.autoSwitchParts && (switchPart || navigationOnly)) {
@@ -709,11 +706,12 @@ var DateInput = /** @class */ (function (_super) {
709
706
  var selection = this.selection;
710
707
  if (this.isInCaretMode()) {
711
708
  var start = selection.start;
712
- var currentSymbol = this.currentFormat[start - (this.elementValue.length - this.currentFormat.length)];
709
+ var currentSymbol = this.currentFormat[start - (this.elementValue.length - this.currentFormat.length)] ||
710
+ this.currentFormat[start];
713
711
  var symbol = "";
714
712
  var symbolCandidate = "";
715
713
  if (offset < 0) {
716
- for (var i = 0; i < start + offset; i++) {
714
+ for (var i = start + offset; i >= 0; i--) {
717
715
  symbolCandidate = this.currentFormat[i];
718
716
  if (symbolCandidate !== Constants.formatSeparator &&
719
717
  symbolCandidate !== currentSymbol) {
@@ -585,8 +585,8 @@ export class DateObject {
585
585
  const hasFixedFormat = (this.format === baseFormat) ||
586
586
  (this.format === originalFormat) ||
587
587
  (this.format.length === originalFormat.length);
588
- if (isInCaretMode) {
589
- let processedSegmentCharsCount = 0;
588
+ let processedSegmentCharsCount = 0;
589
+ if (isInCaretMode || (!isInCaretMode && !this.autoCorrectParts)) {
590
590
  for (let i = 0; i < baseDate.length; i++) {
591
591
  if (baseFormat[i] === symbol) {
592
592
  const existing = this.getExisting(symbol);
@@ -612,10 +612,17 @@ export class DateObject {
612
612
  baseSuffix += baseDate[i];
613
613
  }
614
614
  }
615
- if (hasFixedFormat) {
615
+ if (hasFixedFormat && this.autoCorrectParts) {
616
616
  if (originalFormat.length < rawInputValue.length) {
617
617
  datePartText += currentChar;
618
618
  }
619
+ else if (!isDeleting && originalFormat.length > rawInputValue.length) {
620
+ const lengthDiff = originalFormat.length - rawInputValue.length;
621
+ const trimmedDatePartText = datePartText.substring(0, datePartText.length - lengthDiff);
622
+ if (trimmedDatePartText && trimmedDatePartText.length > 0) {
623
+ datePartText = trimmedDatePartText;
624
+ }
625
+ }
619
626
  if (datePartText.length > segmentLength) {
620
627
  return extend(parseResult, { value: null, switchToNext: false });
621
628
  }
@@ -653,22 +660,45 @@ export class DateObject {
653
660
  if (originalFormat.length < rawInputValue.length) {
654
661
  datePartText += currentChar;
655
662
  }
663
+ else if (!isDeleting && originalFormat.length > rawInputValue.length) {
664
+ const lengthDiff = originalFormat.length - rawInputValue.length;
665
+ const trimmedDatePartText = datePartText.substring(0, datePartText.length - lengthDiff);
666
+ if (trimmedDatePartText && trimmedDatePartText.length > 0) {
667
+ const originalDatePartText = datePartText;
668
+ datePartText = trimmedDatePartText;
669
+ if (prefix + datePartText + suffix !== rawInputValue) {
670
+ // move trimmed characters to the suffix
671
+ suffix = originalDatePartText.substring(datePartText.length) + suffix;
672
+ }
673
+ }
674
+ }
656
675
  }
657
676
  }
658
- else {
659
- for (let i = 0; i < baseDate.length; i++) {
660
- if (baseFormat[i] === symbol) {
661
- const existing = this.getExisting(symbol);
662
- current += existing ? baseDate[i] : '0';
663
- replaced = true;
664
- }
665
- else if (!replaced) {
666
- prefix += baseDate[i];
667
- }
668
- else {
669
- suffix += baseDate[i];
677
+ if (!isInCaretMode) {
678
+ if (this.autoCorrectParts) {
679
+ processedSegmentCharsCount = 0;
680
+ current = "";
681
+ datePartText = "";
682
+ prefix = "";
683
+ suffix = "";
684
+ replaced = false;
685
+ for (let i = 0; i < baseDate.length; i++) {
686
+ if (baseFormat[i] === symbol) {
687
+ const existing = this.getExisting(symbol);
688
+ current += existing ? baseDate[i] : '0';
689
+ replaced = true;
690
+ }
691
+ else if (!replaced) {
692
+ prefix += baseDate[i];
693
+ }
694
+ else {
695
+ suffix += baseDate[i];
696
+ }
670
697
  }
671
698
  }
699
+ else {
700
+ current = resetSegmentValue ? datePartText : current;
701
+ }
672
702
  }
673
703
  let parsedDate = null;
674
704
  const month = this.matchMonth(currentChar);
@@ -694,18 +724,24 @@ export class DateObject {
694
724
  }
695
725
  const currentMaxLength = current.length - 3;
696
726
  let tryParse = true;
727
+ let middle = isInCaretMode ? datePartText : current;
697
728
  for (let i = Math.max(0, currentMaxLength); i <= current.length; i++) {
698
729
  if (!tryParse) {
699
730
  break;
700
731
  }
701
- if (!this.autoCorrectParts) {
702
- tryParse = false;
703
- }
704
- let middle = resetSegmentValue ?
732
+ middle = resetSegmentValue ?
705
733
  currentChar :
706
734
  isInCaretMode ?
707
735
  datePartText :
708
736
  (current.substring(i) + currentChar);
737
+ if (!this.autoCorrectParts) {
738
+ tryParse = false;
739
+ if (!isInCaretMode) {
740
+ // try to make an exact match as there will be only 1 attempt
741
+ middle = unpadZero(middle);
742
+ middle = padZero(segmentLength - middle.length) + middle;
743
+ }
744
+ }
709
745
  if (isInCaretMode) {
710
746
  // try to make an exact match as there will be only 1 attempt
711
747
  tryParse = false;
@@ -722,11 +758,13 @@ export class DateObject {
722
758
  // use the base prefix and suffix, e.g. convert the candidate date string
723
759
  // to "Thursday, February 1, 2022 3:04:05 AM"
724
760
  // as "EEEE, February..." is not parsable
725
- parsedDate = this.intl.parseDate(basePrefix + middle + baseSuffix, this.format, this.localeId);
726
- datePartText = middle;
761
+ if (this.autoCorrectParts) {
762
+ parsedDate = this.intl.parseDate(basePrefix + middle + baseSuffix, this.format, this.localeId);
763
+ datePartText = middle;
764
+ }
727
765
  }
728
766
  const isCurrentCharParsable = !isNaN(parseInt(currentChar, 10)) || (isInCaretMode && isDeleting && currentChar === "");
729
- if (!parsedDate && !isNaN(middleNumber) && isCurrentCharParsable) {
767
+ if (!parsedDate && !isNaN(middleNumber) && isCurrentCharParsable && this.autoCorrectParts) {
730
768
  if (symbol === MONTH_SYMBOL && !month) {
731
769
  // JS months start from 0 (January) instead of 1 (January)
732
770
  const monthNumber = middleNumber - JS_MONTH_OFFSET;
@@ -761,6 +799,26 @@ export class DateObject {
761
799
  }
762
800
  this._value = parsedDate;
763
801
  this.setExisting(symbol, true);
802
+ this.resetInvalidDateSymbol(symbol);
803
+ if (!this.autoCorrectParts) {
804
+ if (symbol === "M") {
805
+ if (this.getExisting("M") && this.getExisting("y")) {
806
+ // changing from 28/Feb to 29/Feb to 29/March
807
+ this.setExisting("d", true);
808
+ this.resetInvalidDateSymbol("d");
809
+ }
810
+ }
811
+ else if (symbol === "d") {
812
+ if (this.getExisting("d") && this.getExisting("y")) {
813
+ // changing from 31/Jan to 31/Feb to 28/Feb
814
+ this.setExisting("M", true);
815
+ this.resetInvalidDateSymbol("M");
816
+ }
817
+ }
818
+ if (!this.hasInvalidDatePart()) {
819
+ this.markDatePartsAsExisting();
820
+ }
821
+ }
764
822
  if (isInCaretMode && switchToNext) {
765
823
  if (symbol === "M") {
766
824
  if (segmentLength <= MONTH_PART_WITH_WORDS_THRESHOLD) {
@@ -807,28 +865,30 @@ export class DateObject {
807
865
  return extend(parseResult, { value: null, switchToNext: false });
808
866
  }
809
867
  if (!this.autoCorrectParts) {
810
- this.setExisting(symbol, false);
811
- // todo check if string is better
812
- // const padPrefix = padZero(Math.abs(current.length - datePartText.length));
813
- // const paddedDatePartText = padPrefix + datePartText;
814
868
  let datePartValue;
815
- const textToParse = isInCaretMode ? datePartText : current;
869
+ const textToParse = isInCaretMode ? datePartText : middle;
816
870
  const parsedValue = parseToInt(textToParse);
817
871
  if (isNumber(parsedValue)) {
872
+ if ((symbol === "d" && (parsedValue <= 0 || parsedValue > 31)) ||
873
+ (symbol === "M" && (parsedValue < 0 || parsedValue > 11))) {
874
+ return extend(parseResult, { value: null, switchToNext: false });
875
+ }
818
876
  datePartValue = symbol === "M" ?
819
877
  clamp(parsedValue - JS_MONTH_OFFSET, 0, 12 - JS_MONTH_OFFSET) :
820
878
  parsedValue;
821
- }
822
- if (isNumber(datePartValue)) {
823
- const newDate = this.modifyDateSymbolWithValue(this.value, symbol, datePartValue);
824
879
  // if (!isEqual(newDate, this.value)) {
825
880
  this.setExisting(symbol, false);
826
881
  this.setInvalidDatePart(symbol, {
827
882
  value: datePartValue,
828
- date: cloneDate(newDate),
883
+ date: cloneDate(this.value),
829
884
  startDate: this._partiallyInvalidDate.startDate || cloneDate(this.value)
830
885
  });
831
886
  // }
887
+ return extend(parseResult, {
888
+ value: null,
889
+ switchToNext: false,
890
+ hasInvalidDatePart: true
891
+ });
832
892
  }
833
893
  }
834
894
  return extend(parseResult, { value: null, switchToNext: false });
@@ -1043,13 +1103,16 @@ export class DateObject {
1043
1103
  }
1044
1104
  else {
1045
1105
  datePartText = (parseToInt(this.getInvalidDatePartValue(symbol)) + JS_MONTH_OFFSET).toString();
1046
- resultText = datePartText + resultText;
1106
+ const formattedDatePart = padZero(partsForSegment.length - datePartText.length) + datePartText;
1107
+ resultText = formattedDatePart + resultText;
1108
+ formatSymbolIndexModifier = partsForSegment.length - 1;
1047
1109
  ignoreFormatSymbolsCount = datePartText.length - partsForSegment.length;
1048
1110
  }
1049
1111
  }
1050
1112
  else {
1051
- resultText = datePartText + resultText;
1052
- formatSymbolIndexModifier = datePartText.length - 1;
1113
+ const formattedDatePart = padZero(partsForSegment.length - datePartText.length) + datePartText;
1114
+ resultText = formattedDatePart + resultText;
1115
+ formatSymbolIndexModifier = partsForSegment.length - 1;
1053
1116
  ignoreFormatSymbolsCount = datePartText.length - partsForSegment.length;
1054
1117
  }
1055
1118
  }
@@ -1137,13 +1200,33 @@ export class DateObject {
1137
1200
  return (invalidDatePart || {}).value;
1138
1201
  }
1139
1202
  setInvalidDatePart(symbol, { value = null, date = null, startDateOffset = 0, startDate = null }) {
1203
+ let clampedValue = value;
1204
+ if (symbol === "d") {
1205
+ clampedValue = clamp(isPresent(value) ? value : 1, 1, 31);
1206
+ }
1207
+ else if (symbol === "M") {
1208
+ clampedValue = clamp(isPresent(value) ? value : 0, 0, 11);
1209
+ }
1140
1210
  if (this._partiallyInvalidDate.invalidDateParts[symbol]) {
1141
- this._partiallyInvalidDate.invalidDateParts[symbol].value = value;
1211
+ this._partiallyInvalidDate.invalidDateParts[symbol].value = clampedValue;
1142
1212
  this._partiallyInvalidDate.invalidDateParts[symbol].date = date;
1143
1213
  this._partiallyInvalidDate.invalidDateParts[symbol].startDateOffset = startDateOffset;
1144
1214
  this._partiallyInvalidDate.startDate = startDate;
1145
1215
  }
1146
1216
  }
1217
+ /**
1218
+ * @hidden
1219
+ */
1220
+ hasInvalidDatePart() {
1221
+ let hasInvalidDatePart = false;
1222
+ Object.keys(this._partiallyInvalidDate.invalidDateParts).forEach(key => {
1223
+ if (this._partiallyInvalidDate.invalidDateParts[key] &&
1224
+ isPresent(this._partiallyInvalidDate.invalidDateParts[key].value)) {
1225
+ hasInvalidDatePart = true;
1226
+ }
1227
+ });
1228
+ return hasInvalidDatePart;
1229
+ }
1147
1230
  /**
1148
1231
  * @hidden
1149
1232
  */
@@ -335,6 +335,7 @@ export class DateInput extends Observable {
335
335
  const lastParseResultHasNoValue = lastParseResult && !lastParseResult.value;
336
336
  const parsingFailedOnDelete = (hasCaret && (isBackspaceKey || isDeleteKey) && lastParseResultHasNoValue);
337
337
  const resetPart = lastParseResult ? lastParseResult.resetPart : false;
338
+ const hasInvalidDatePart = lastParseResult ? lastParseResult.hasInvalidDatePart : false;
338
339
  const hasDateValueChanged = !isEqual(oldDateObjectValue, this.dateObject.value);
339
340
  let symbolForSelection;
340
341
  const currentSelection = this.selection;
@@ -360,20 +361,14 @@ export class DateInput extends Observable {
360
361
  }
361
362
  }
362
363
  else if (hasCaret) {
363
- if (this.options.format.length !== this.currentFormat.length) {
364
- if (hasDateValueChanged && isPresent(this.dateObject.getValue())) {
365
- if (switchPart) {
366
- this.switchDateSegment(1);
367
- }
364
+ if ((hasDateValueChanged && isPresent(this.dateObject.getValue()) ||
365
+ (!hasDateValueChanged && switchPart && isPresent(this.dateObject.getValue())))) {
366
+ if (switchPart) {
367
+ this.switchDateSegment(1);
368
368
  }
369
369
  }
370
370
  else {
371
- if (hasDateValueChanged) {
372
- if (lastParseResultHasNoValue) {
373
- this.restorePreviousInputEventState();
374
- }
375
- }
376
- else {
371
+ if (lastParseResultHasNoValue && !hasInvalidDatePart) {
377
372
  this.restorePreviousInputEventState();
378
373
  }
379
374
  }
@@ -386,20 +381,22 @@ export class DateInput extends Observable {
386
381
  }
387
382
  else {
388
383
  // the input is not complete, not parsable or not updatable
389
- if (hasCaret && originalInteractionMode !== DateInputInteractionMode.Caret && hasDateValueChanged) {
384
+ if (originalInteractionMode !== DateInputInteractionMode.Caret && hasDateValueChanged) {
390
385
  }
391
- else if (hasCaret && originalInteractionMode !== DateInputInteractionMode.Caret) {
386
+ else if (originalInteractionMode !== DateInputInteractionMode.Caret) {
392
387
  symbolForSelection = this.currentFormat[currentSelection.start];
393
388
  this.forceUpdate();
394
389
  this.setSelection(this.selectionBySymbol(symbolForSelection));
395
390
  }
396
- else {
391
+ else if (!hasInvalidDatePart) {
397
392
  this.restorePreviousInputEventState();
398
393
  }
399
394
  }
400
395
  }
401
396
  else {
402
- this.restorePreviousInputEventState();
397
+ if (!hasInvalidDatePart) {
398
+ this.restorePreviousInputEventState();
399
+ }
403
400
  }
404
401
  }
405
402
  else if (this.options.autoSwitchParts && (switchPart || navigationOnly)) {
@@ -684,11 +681,12 @@ export class DateInput extends Observable {
684
681
  const selection = this.selection;
685
682
  if (this.isInCaretMode()) {
686
683
  let start = selection.start;
687
- const currentSymbol = this.currentFormat[start - (this.elementValue.length - this.currentFormat.length)];
684
+ const currentSymbol = this.currentFormat[start - (this.elementValue.length - this.currentFormat.length)] ||
685
+ this.currentFormat[start];
688
686
  let symbol = "";
689
687
  let symbolCandidate = "";
690
688
  if (offset < 0) {
691
- for (let i = 0; i < start + offset; i++) {
689
+ for (let i = start + offset; i >= 0; i--) {
692
690
  symbolCandidate = this.currentFormat[i];
693
691
  if (symbolCandidate !== Constants.formatSeparator &&
694
692
  symbolCandidate !== currentSymbol) {