happy-dom 10.0.7 → 10.1.1

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.

Potentially problematic release.


This version of happy-dom might be problematic. Click here for more details.

Files changed (35) hide show
  1. package/cjs/nodes/document/Document.cjs +1 -0
  2. package/cjs/nodes/document/Document.cjs.map +1 -1
  3. package/cjs/nodes/document/Document.d.ts.map +1 -1
  4. package/cjs/nodes/html-input-element/HTMLInputDateUtility.cjs +24 -0
  5. package/cjs/nodes/html-input-element/HTMLInputDateUtility.cjs.map +1 -0
  6. package/cjs/nodes/html-input-element/HTMLInputDateUtility.d.ts +8 -0
  7. package/cjs/nodes/html-input-element/HTMLInputDateUtility.d.ts.map +1 -0
  8. package/cjs/nodes/html-input-element/HTMLInputElement.cjs +98 -1
  9. package/cjs/nodes/html-input-element/HTMLInputElement.cjs.map +1 -1
  10. package/cjs/nodes/html-input-element/HTMLInputElement.d.ts +6 -0
  11. package/cjs/nodes/html-input-element/HTMLInputElement.d.ts.map +1 -1
  12. package/cjs/nodes/html-input-element/HTMLInputElementValueSanitizer.cjs +151 -1
  13. package/cjs/nodes/html-input-element/HTMLInputElementValueSanitizer.cjs.map +1 -1
  14. package/cjs/nodes/html-input-element/HTMLInputElementValueSanitizer.d.ts +32 -0
  15. package/cjs/nodes/html-input-element/HTMLInputElementValueSanitizer.d.ts.map +1 -1
  16. package/lib/nodes/document/Document.d.ts.map +1 -1
  17. package/lib/nodes/document/Document.js +1 -0
  18. package/lib/nodes/document/Document.js.map +1 -1
  19. package/lib/nodes/html-input-element/HTMLInputDateUtility.d.ts +8 -0
  20. package/lib/nodes/html-input-element/HTMLInputDateUtility.d.ts.map +1 -0
  21. package/lib/nodes/html-input-element/HTMLInputDateUtility.js +20 -0
  22. package/lib/nodes/html-input-element/HTMLInputDateUtility.js.map +1 -0
  23. package/lib/nodes/html-input-element/HTMLInputElement.d.ts +6 -0
  24. package/lib/nodes/html-input-element/HTMLInputElement.d.ts.map +1 -1
  25. package/lib/nodes/html-input-element/HTMLInputElement.js +98 -1
  26. package/lib/nodes/html-input-element/HTMLInputElement.js.map +1 -1
  27. package/lib/nodes/html-input-element/HTMLInputElementValueSanitizer.d.ts +32 -0
  28. package/lib/nodes/html-input-element/HTMLInputElementValueSanitizer.d.ts.map +1 -1
  29. package/lib/nodes/html-input-element/HTMLInputElementValueSanitizer.js +153 -2
  30. package/lib/nodes/html-input-element/HTMLInputElementValueSanitizer.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/nodes/document/Document.ts +1 -0
  33. package/src/nodes/html-input-element/HTMLInputDateUtility.ts +21 -0
  34. package/src/nodes/html-input-element/HTMLInputElement.ts +99 -1
  35. package/src/nodes/html-input-element/HTMLInputElementValueSanitizer.ts +153 -1
@@ -1,8 +1,9 @@
1
1
  const NEW_LINES_REGEXP = /[\n\r]/gm;
2
+ const parseInts = (a) => a.map((v) => parseInt(v, 10));
2
3
  /**
3
4
  * HTML input element value sanitizer.
4
5
  */
5
- export default class HTMLInputElementValueSanitizer {
6
+ class HTMLInputElementValueSanitizer {
6
7
  /**
7
8
  * Sanitizes a value.
8
9
  *
@@ -32,7 +33,7 @@ export default class HTMLInputElementValueSanitizer {
32
33
  case 'number':
33
34
  // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm
34
35
  return !isNaN(Number.parseFloat(value)) ? value : '';
35
- case 'range':
36
+ case 'range': {
36
37
  // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm
37
38
  const number = Number.parseFloat(value);
38
39
  const min = parseFloat(input.min) || 0;
@@ -47,11 +48,161 @@ export default class HTMLInputElementValueSanitizer {
47
48
  return String(max);
48
49
  }
49
50
  return value;
51
+ }
50
52
  case 'url':
51
53
  // https://html.spec.whatwg.org/multipage/forms.html#url-state-(type=url):value-sanitization-algorithm
52
54
  return value.trim().replace(NEW_LINES_REGEXP, '');
55
+ case 'date':
56
+ // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm
57
+ value = this.sanitizeDate(value);
58
+ return value && this.checkBoundaries(value, input.min, input.max) ? value : '';
59
+ case 'datetime-local': {
60
+ // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm
61
+ const match = value.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d)(?::(\d\d)(?:\.(\d{1,3}))?)?$/);
62
+ if (!match) {
63
+ return '';
64
+ }
65
+ const dateString = this.sanitizeDate(value.slice(0, 10));
66
+ let timeString = this.sanitizeTime(value.slice(11));
67
+ if (!(dateString && timeString)) {
68
+ return '';
69
+ }
70
+ // Has seconds so needs to remove trailing zeros
71
+ if (match[6] !== undefined) {
72
+ if (timeString.indexOf('.') !== -1) {
73
+ // Remove unecessary zeros milliseconds
74
+ timeString = timeString.replace(/(?:\.0*|(\.\d+?)0+)$/, '$1');
75
+ }
76
+ timeString = timeString.replace(/(\d\d:\d\d)(:00)$/, '$1');
77
+ }
78
+ return dateString + 'T' + timeString;
79
+ }
80
+ case 'month':
81
+ // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm
82
+ if (!(value.match(/^(\d\d\d\d)-(\d\d)$/) && this.parseMonthComponent(value))) {
83
+ return '';
84
+ }
85
+ return this.checkBoundaries(value, input.min, input.max) ? value : '';
86
+ case 'time': {
87
+ // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm
88
+ value = this.sanitizeTime(value);
89
+ return value && this.checkBoundaries(value, input.min, input.max) ? value : '';
90
+ }
91
+ case 'week': {
92
+ // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm
93
+ const match = value.match(/^(\d\d\d\d)-W(\d\d)$/);
94
+ if (!match) {
95
+ return '';
96
+ }
97
+ const [intY, intW] = parseInts(match.slice(1, 3));
98
+ if (intY <= 0 || intW < 1 || intW > 53) {
99
+ return '';
100
+ }
101
+ // Check date is valid
102
+ const lastWeek = this.lastIsoWeekOfYear(intY);
103
+ if (intW < 1 || intW > 52 + lastWeek) {
104
+ return '';
105
+ }
106
+ if (!this.checkBoundaries(value, input.min, input.max)) {
107
+ return '';
108
+ }
109
+ return value;
110
+ }
111
+ }
112
+ return value;
113
+ }
114
+ /**
115
+ * Checks if a value is within the boundaries of min and max.
116
+ *
117
+ * @param value
118
+ * @param min
119
+ * @param max
120
+ */
121
+ static checkBoundaries(value, min, max) {
122
+ if (min && min > value) {
123
+ return false;
124
+ }
125
+ else if (max && max < value) {
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+ /**
131
+ * Parses the month component of a date string.
132
+ *
133
+ * @param value
134
+ */
135
+ static parseMonthComponent(value) {
136
+ const [Y, M] = value.split('-');
137
+ const [intY, intM] = parseInts([Y, M]);
138
+ if (isNaN(intY) || isNaN(intM) || intY <= 0 || intM < 1 || intM > 12) {
139
+ return '';
53
140
  }
54
141
  return value;
55
142
  }
143
+ /**
144
+ * Sanitizes a date string.
145
+ *
146
+ * @param value
147
+ */
148
+ static sanitizeDate(value) {
149
+ const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
150
+ if (!match) {
151
+ return '';
152
+ }
153
+ const month = this.parseMonthComponent(value.slice(0, 7));
154
+ if (!month) {
155
+ return '';
156
+ }
157
+ const [intY, intM, intD] = parseInts(match.slice(1, 4));
158
+ if (intD < 1 || intD > 31) {
159
+ return '';
160
+ }
161
+ // Check date is valid
162
+ const lastDayOfMonth = new Date(intY, intM, 0).getDate();
163
+ if (intD > lastDayOfMonth) {
164
+ return '';
165
+ }
166
+ return value;
167
+ }
168
+ /**
169
+ * Sanitizes a time string.
170
+ *
171
+ * @param value
172
+ */
173
+ static sanitizeTime(value) {
174
+ const match = value.match(/^(\d{2}):(\d{2})(?::(\d{2}(?:\.(\d{1,3}))?))?$/);
175
+ if (!match) {
176
+ return '';
177
+ }
178
+ const [intH, intM] = parseInts(match.slice(1, 3));
179
+ const ms = parseFloat(match[3] || '0') * 1000;
180
+ if (intH > 23 || intM > 59 || ms > 59999) {
181
+ return '';
182
+ }
183
+ if (ms === 0) {
184
+ return `${match[1]}:${match[2]}`;
185
+ }
186
+ else {
187
+ return `${match[1]}:${match[2]}${ms >= 10000 ? `:${ms / 1000}` : `:0${ms / 1000}`}`;
188
+ }
189
+ }
56
190
  }
191
+ /**
192
+ * Returns the last ISO week of a year.
193
+ *
194
+ * @param year
195
+ */
196
+ HTMLInputElementValueSanitizer.lastIsoWeekOfYear = (year) => {
197
+ const date = new Date(+year, 11, 31);
198
+ const day = (date.getDay() + 6) % 7;
199
+ date.setDate(date.getDate() - day + 3);
200
+ const firstThursday = date.getTime();
201
+ date.setMonth(0, 1);
202
+ if (date.getDay() !== 4) {
203
+ date.setMonth(0, 1 + ((4 - date.getDay() + 7) % 7));
204
+ }
205
+ return 1 + Math.ceil((firstThursday - date.getTime()) / 604800000);
206
+ };
207
+ export default HTMLInputElementValueSanitizer;
57
208
  //# sourceMappingURL=HTMLInputElementValueSanitizer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"HTMLInputElementValueSanitizer.js","sourceRoot":"","sources":["../../../src/nodes/html-input-element/HTMLInputElementValueSanitizer.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,8BAA8B;IAClD;;;;;OAKG;IACI,MAAM,CAAC,QAAQ,CAAC,KAAuB,EAAE,KAAa;QAC5D,QAAQ,KAAK,CAAC,IAAI,EAAE;YACnB,KAAK,UAAU,CAAC;YAChB,KAAK,QAAQ,CAAC;YACd,KAAK,KAAK,CAAC;YACX,KAAK,MAAM;gBACV,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;YAC5C,KAAK,OAAO;gBACX,0GAA0G;gBAC1G,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YACzE,KAAK,OAAO;gBACX,2GAA2G;gBAC3G,6GAA6G;gBAC7G,IAAI,KAAK,CAAC,QAAQ,EAAE;oBACnB,OAAO,KAAK;yBACV,KAAK,CAAC,GAAG,CAAC;yBACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;yBAC5B,IAAI,CAAC,GAAG,CAAC,CAAC;iBACZ;gBACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;YACnD,KAAK,QAAQ;gBACZ,4GAA4G;gBAC5G,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,KAAK,OAAO;gBACX,0GAA0G;gBAC1G,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;gBACxC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACvC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;gBAEzC,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE;oBAClB,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;iBACzD;qBAAM,IAAI,MAAM,GAAG,GAAG,EAAE;oBACxB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;iBACnB;qBAAM,IAAI,MAAM,GAAG,GAAG,EAAE;oBACxB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;iBACnB;gBAED,OAAO,KAAK,CAAC;YACd,KAAK,KAAK;gBACT,sGAAsG;gBACtG,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;SACnD;QAED,OAAO,KAAK,CAAC;IACd,CAAC;CACD"}
1
+ {"version":3,"file":"HTMLInputElementValueSanitizer.js","sourceRoot":"","sources":["../../../src/nodes/html-input-element/HTMLInputElementValueSanitizer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-dom",
3
- "version": "10.0.7",
3
+ "version": "10.1.1",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/capricorn86/happy-dom",
6
6
  "repository": "https://github.com/capricorn86/happy-dom",
@@ -198,6 +198,7 @@ export default class Document extends Node implements IDocument {
198
198
  this.implementation = new DOMImplementation(this);
199
199
 
200
200
  this._readyStateManager = new DocumentReadyStateManager(this.defaultView);
201
+ this._rootNode = this;
201
202
 
202
203
  const doctype = this.implementation.createDocumentType('html', '', '');
203
204
  const documentElement = this.createElement('html');
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Return iso-week number from given date
3
+ *
4
+ * @param date Date|number.
5
+ * @returns Iso-week string.
6
+ */
7
+ export const dateIsoWeek = (date: Date | number): string => {
8
+ date = new Date(date);
9
+ const day = (date.getUTCDay() + 6) % 7;
10
+ date.setUTCDate(date.getUTCDate() - day + 3);
11
+ const firstThursday = date.getTime();
12
+ date.setUTCMonth(0, 1);
13
+ if (date.getDay() !== 4) {
14
+ date.setUTCMonth(0, 1 + ((4 - date.getDay() + 7) % 7));
15
+ }
16
+ return (
17
+ date.getUTCFullYear() +
18
+ '-W' +
19
+ String(1 + Math.ceil((firstThursday - date.getTime()) / 604800000)).padStart(2, '0')
20
+ );
21
+ };
@@ -22,6 +22,7 @@ import IDocument from '../document/IDocument.js';
22
22
  import IShadowRoot from '../shadow-root/IShadowRoot.js';
23
23
  import NodeList from '../node/NodeList.js';
24
24
  import EventPhaseEnum from '../../event/EventPhaseEnum.js';
25
+ import { dateIsoWeek } from './HTMLInputDateUtility.js';
25
26
 
26
27
  /**
27
28
  * HTML Input Element.
@@ -797,7 +798,104 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE
797
798
  * @returns Number.
798
799
  */
799
800
  public get valueAsNumber(): number {
800
- return this.value ? parseFloat(this.value) : NaN;
801
+ const value = this.value;
802
+ if (!this.type.match(/^(range|number|date|datetime-local|month|time|week)$/) || !value) {
803
+ return NaN;
804
+ }
805
+ switch (this.type) {
806
+ case 'number':
807
+ return parseFloat(value);
808
+ case 'range': {
809
+ const number = parseFloat(value);
810
+ const min = parseFloat(this.min) || 0;
811
+ const max = parseFloat(this.max) || 100;
812
+ if (isNaN(number)) {
813
+ return max < min ? min : (min + max) / 2;
814
+ } else if (number < min) {
815
+ return min;
816
+ } else if (number > max) {
817
+ return max;
818
+ }
819
+ return number;
820
+ }
821
+ case 'date':
822
+ return new Date(value).getTime();
823
+ case 'datetime-local':
824
+ return new Date(value).getTime() - new Date(value).getTimezoneOffset() * 60000;
825
+ case 'month':
826
+ return (new Date(value).getUTCFullYear() - 1970) * 12 + new Date(value).getUTCMonth();
827
+ case 'time':
828
+ return (
829
+ new Date('1970-01-01T' + value).getTime() - new Date('1970-01-01T00:00:00').getTime()
830
+ );
831
+ case 'week': {
832
+ // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week)
833
+ const match = value.match(/^(\d{4})-W(\d{2})$/);
834
+ if (!match) {
835
+ return NaN;
836
+ }
837
+ const d = new Date(Date.UTC(parseInt(match[1], 10), 0));
838
+ const day = d.getUTCDay();
839
+ const diff = ((day === 0 ? -6 : 1) - day) * 86400000 + parseInt(match[2], 10) * 604800000;
840
+ return d.getTime() + diff;
841
+ }
842
+ }
843
+ }
844
+
845
+ /**
846
+ * Sets value from a number.
847
+ *
848
+ * @param value number.
849
+ */
850
+ public set valueAsNumber(value: number) {
851
+ // Specs at https://html.spec.whatwg.org/multipage/input.html
852
+ switch (this.type) {
853
+ case 'number':
854
+ case 'range':
855
+ // We Rely on HTMLInputElementValueSanitizer
856
+ this.value = Number(value).toString();
857
+ break;
858
+ case 'date':
859
+ case 'datetime-local': {
860
+ const d = new Date(Number(value));
861
+ if (isNaN(d.getTime())) {
862
+ // Reset to default value
863
+ this.value = '';
864
+ break;
865
+ }
866
+ if (this.type == 'date') {
867
+ this.value = d.toISOString().slice(0, 10);
868
+ } else {
869
+ this.value = d.toISOString().slice(0, -1);
870
+ }
871
+ break;
872
+ }
873
+ case 'month':
874
+ if (!Number.isInteger(value) || value < 0) {
875
+ this.value = '';
876
+ } else {
877
+ this.value = new Date(Date.UTC(1970, Number(value))).toISOString().slice(0, 7);
878
+ }
879
+ break;
880
+ case 'time':
881
+ if (!Number.isInteger(value) || value < 0) {
882
+ this.value = '';
883
+ } else {
884
+ this.value = new Date(Number(value)).toISOString().slice(11, -1);
885
+ }
886
+ break;
887
+ case 'week':
888
+ case 'week': {
889
+ const d = new Date(Number(value));
890
+ this.value = isNaN(d.getTime()) ? '' : dateIsoWeek(d);
891
+ break;
892
+ }
893
+ default:
894
+ throw new DOMException(
895
+ "Failed to set the 'valueAsNumber' property on 'HTMLInputElement': This input element does not support Number values.",
896
+ DOMExceptionNameEnum.invalidStateError
897
+ );
898
+ }
801
899
  }
802
900
 
803
901
  /**
@@ -1,6 +1,7 @@
1
1
  import HTMLInputElement from './HTMLInputElement.js';
2
2
 
3
3
  const NEW_LINES_REGEXP = /[\n\r]/gm;
4
+ const parseInts = (a: string[]): number[] => a.map((v) => parseInt(v, 10));
4
5
 
5
6
  /**
6
7
  * HTML input element value sanitizer.
@@ -35,7 +36,7 @@ export default class HTMLInputElementValueSanitizer {
35
36
  case 'number':
36
37
  // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm
37
38
  return !isNaN(Number.parseFloat(value)) ? value : '';
38
- case 'range':
39
+ case 'range': {
39
40
  // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm
40
41
  const number = Number.parseFloat(value);
41
42
  const min = parseFloat(input.min) || 0;
@@ -50,11 +51,162 @@ export default class HTMLInputElementValueSanitizer {
50
51
  }
51
52
 
52
53
  return value;
54
+ }
53
55
  case 'url':
54
56
  // https://html.spec.whatwg.org/multipage/forms.html#url-state-(type=url):value-sanitization-algorithm
55
57
  return value.trim().replace(NEW_LINES_REGEXP, '');
58
+ case 'date':
59
+ // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm
60
+ value = this.sanitizeDate(value);
61
+ return value && this.checkBoundaries(value, input.min, input.max) ? value : '';
62
+ case 'datetime-local': {
63
+ // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm
64
+ const match = value.match(
65
+ /^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d)(?::(\d\d)(?:\.(\d{1,3}))?)?$/
66
+ );
67
+ if (!match) {
68
+ return '';
69
+ }
70
+ const dateString = this.sanitizeDate(value.slice(0, 10));
71
+ let timeString = this.sanitizeTime(value.slice(11));
72
+ if (!(dateString && timeString)) {
73
+ return '';
74
+ }
75
+ // Has seconds so needs to remove trailing zeros
76
+ if (match[6] !== undefined) {
77
+ if (timeString.indexOf('.') !== -1) {
78
+ // Remove unecessary zeros milliseconds
79
+ timeString = timeString.replace(/(?:\.0*|(\.\d+?)0+)$/, '$1');
80
+ }
81
+ timeString = timeString.replace(/(\d\d:\d\d)(:00)$/, '$1');
82
+ }
83
+ return dateString + 'T' + timeString;
84
+ }
85
+ case 'month':
86
+ // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm
87
+ if (!(value.match(/^(\d\d\d\d)-(\d\d)$/) && this.parseMonthComponent(value))) {
88
+ return '';
89
+ }
90
+ return this.checkBoundaries(value, input.min, input.max) ? value : '';
91
+ case 'time': {
92
+ // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm
93
+ value = this.sanitizeTime(value);
94
+ return value && this.checkBoundaries(value, input.min, input.max) ? value : '';
95
+ }
96
+ case 'week': {
97
+ // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm
98
+ const match = value.match(/^(\d\d\d\d)-W(\d\d)$/);
99
+ if (!match) {
100
+ return '';
101
+ }
102
+ const [intY, intW] = parseInts(match.slice(1, 3));
103
+ if (intY <= 0 || intW < 1 || intW > 53) {
104
+ return '';
105
+ }
106
+ // Check date is valid
107
+ const lastWeek = this.lastIsoWeekOfYear(intY);
108
+ if (intW < 1 || intW > 52 + lastWeek) {
109
+ return '';
110
+ }
111
+ if (!this.checkBoundaries(value, input.min, input.max)) {
112
+ return '';
113
+ }
114
+ return value;
115
+ }
116
+ }
117
+
118
+ return value;
119
+ }
120
+ /**
121
+ * Checks if a value is within the boundaries of min and max.
122
+ *
123
+ * @param value
124
+ * @param min
125
+ * @param max
126
+ */
127
+ private static checkBoundaries<T>(value: T, min: T, max: T): boolean {
128
+ if (min && min > value) {
129
+ return false;
130
+ } else if (max && max < value) {
131
+ return false;
132
+ }
133
+ return true;
134
+ }
135
+ /**
136
+ * Parses the month component of a date string.
137
+ *
138
+ * @param value
139
+ */
140
+ private static parseMonthComponent(value: string): string {
141
+ const [Y, M] = value.split('-');
142
+ const [intY, intM] = parseInts([Y, M]);
143
+ if (isNaN(intY) || isNaN(intM) || intY <= 0 || intM < 1 || intM > 12) {
144
+ return '';
56
145
  }
146
+ return value;
147
+ }
148
+ /**
149
+ * Returns the last ISO week of a year.
150
+ *
151
+ * @param year
152
+ */
153
+ private static lastIsoWeekOfYear = (year: string | number): number => {
154
+ const date = new Date(+year, 11, 31);
155
+ const day = (date.getDay() + 6) % 7;
156
+ date.setDate(date.getDate() - day + 3);
157
+ const firstThursday = date.getTime();
158
+ date.setMonth(0, 1);
159
+ if (date.getDay() !== 4) {
160
+ date.setMonth(0, 1 + ((4 - date.getDay() + 7) % 7));
161
+ }
162
+ return 1 + Math.ceil((firstThursday - date.getTime()) / 604800000);
163
+ };
57
164
 
165
+ /**
166
+ * Sanitizes a date string.
167
+ *
168
+ * @param value
169
+ */
170
+ private static sanitizeDate(value: string): string {
171
+ const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
172
+ if (!match) {
173
+ return '';
174
+ }
175
+ const month = this.parseMonthComponent(value.slice(0, 7));
176
+ if (!month) {
177
+ return '';
178
+ }
179
+ const [intY, intM, intD] = parseInts(match.slice(1, 4));
180
+ if (intD < 1 || intD > 31) {
181
+ return '';
182
+ }
183
+ // Check date is valid
184
+ const lastDayOfMonth = new Date(intY, intM, 0).getDate();
185
+ if (intD > lastDayOfMonth) {
186
+ return '';
187
+ }
58
188
  return value;
59
189
  }
190
+
191
+ /**
192
+ * Sanitizes a time string.
193
+ *
194
+ * @param value
195
+ */
196
+ private static sanitizeTime(value: string): string {
197
+ const match = value.match(/^(\d{2}):(\d{2})(?::(\d{2}(?:\.(\d{1,3}))?))?$/);
198
+ if (!match) {
199
+ return '';
200
+ }
201
+ const [intH, intM] = parseInts(match.slice(1, 3));
202
+ const ms = parseFloat(match[3] || '0') * 1000;
203
+ if (intH > 23 || intM > 59 || ms > 59999) {
204
+ return '';
205
+ }
206
+ if (ms === 0) {
207
+ return `${match[1]}:${match[2]}`;
208
+ } else {
209
+ return `${match[1]}:${match[2]}${ms >= 10000 ? `:${ms / 1000}` : `:0${ms / 1000}`}`;
210
+ }
211
+ }
60
212
  }