Description:
prob manage
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
r882:50181c2869eb - - 7 files changed: 4208 inserted, 62 deleted
@@ -0,0 +1,290 | |||||
|
|
1 | + /*! | ||
|
|
2 | + * Tempus Dominus v6.2.4 (https://getdatepicker.com/) | ||
|
|
3 | + * Copyright 2013-2022 Jonathan Peterson | ||
|
|
4 | + * Licensed under MIT (https://github.com/Eonasdan/tempus-dominus/blob/master/LICENSE) | ||
|
|
5 | + */ | ||
|
|
6 | + (function(g,f){typeof exports==='object'&&typeof module!=='undefined'?module.exports=f():typeof define==='function'&&define.amd?define(f):(g=typeof globalThis!=='undefined'?globalThis:g||self,(g.tempusDominus=g.tempusDominus||{},g.tempusDominus.plugins=g.tempusDominus.plugins||{},g.tempusDominus.plugins.customDateFormat=f()));})(this,(function(){'use strict';class CustomDateFormat { | ||
|
|
7 | + constructor(dateTime, errorMessages) { | ||
|
|
8 | + this.REGEX_FORMAT = /\[([^\]]+)]|y{1,4}|M{1,4}|d{1,4}|H{1,2}|h{1,2}|t|T|m{1,2}|s{1,2}|Z{1,2}/g; | ||
|
|
9 | + // noinspection SpellCheckingInspection | ||
|
|
10 | + this.englishFormats = { | ||
|
|
11 | + LTS: 'h:mm:ss T', | ||
|
|
12 | + LT: 'h:mm T', | ||
|
|
13 | + L: 'MM/dd/yyyy', | ||
|
|
14 | + LL: 'MMMM d, yyyy', | ||
|
|
15 | + LLL: 'MMMM d, yyyy h:mm T', | ||
|
|
16 | + LLLL: 'dddd, MMMM d, yyyy h:mm T', | ||
|
|
17 | + }; | ||
|
|
18 | + this.formattingTokens = /(\[[^[]*])|([-_:/.,()\s]+)|(T|t|yyyy|yy?|MM?M?M?|Do|dd?|hh?|HH?|mm?|ss?|z|ZZ?)/g; | ||
|
|
19 | + this.match1 = /\d/; // 0 - 9 | ||
|
|
20 | + this.match2 = /\d\d/; // 00 - 99 | ||
|
|
21 | + this.match3 = /\d{3}/; // 000 - 999 | ||
|
|
22 | + this.match4 = /\d{4}/; // 0000 - 9999 | ||
|
|
23 | + this.match1to2 = /\d\d?/; // 0 - 99 | ||
|
|
24 | + this.matchSigned = /[+-]?\d+/; // -inf - inf | ||
|
|
25 | + this.matchOffset = /[+-]\d\d:?(\d\d)?|Z/; // +00:00 -00:00 +0000 or -0000 +00 or Z | ||
|
|
26 | + this.matchWord = /\d*[^-_:/,()\s\d]+/; // Word | ||
|
|
27 | + this.zoneExpressions = [ | ||
|
|
28 | + this.matchOffset, | ||
|
|
29 | + (obj, input) => { | ||
|
|
30 | + obj.offset = this.offsetFromString(input); | ||
|
|
31 | + }, | ||
|
|
32 | + ]; | ||
|
|
33 | + this.expressions = { | ||
|
|
34 | + t: [ | ||
|
|
35 | + this.matchWord, | ||
|
|
36 | + (ojb, input) => { | ||
|
|
37 | + ojb.afternoon = this.meridiemMatch(input); | ||
|
|
38 | + }, | ||
|
|
39 | + ], | ||
|
|
40 | + T: [ | ||
|
|
41 | + this.matchWord, | ||
|
|
42 | + (ojb, input) => { | ||
|
|
43 | + ojb.afternoon = this.meridiemMatch(input); | ||
|
|
44 | + }, | ||
|
|
45 | + ], | ||
|
|
46 | + fff: [ | ||
|
|
47 | + this.match3, | ||
|
|
48 | + (ojb, input) => { | ||
|
|
49 | + ojb.milliseconds = +input; | ||
|
|
50 | + }, | ||
|
|
51 | + ], | ||
|
|
52 | + s: [this.match1to2, this.addInput('seconds')], | ||
|
|
53 | + ss: [this.match1to2, this.addInput('seconds')], | ||
|
|
54 | + m: [this.match1to2, this.addInput('minutes')], | ||
|
|
55 | + mm: [this.match1to2, this.addInput('minutes')], | ||
|
|
56 | + H: [this.match1to2, this.addInput('hours')], | ||
|
|
57 | + h: [this.match1to2, this.addInput('hours')], | ||
|
|
58 | + HH: [this.match1to2, this.addInput('hours')], | ||
|
|
59 | + hh: [this.match1to2, this.addInput('hours')], | ||
|
|
60 | + d: [this.match1to2, this.addInput('day')], | ||
|
|
61 | + dd: [this.match2, this.addInput('day')], | ||
|
|
62 | + Do: [ | ||
|
|
63 | + this.matchWord, | ||
|
|
64 | + (ojb, input) => { | ||
|
|
65 | + [ojb.day] = input.match(/\d+/); | ||
|
|
66 | + if (!this.localization.ordinal) | ||
|
|
67 | + return; | ||
|
|
68 | + for (let i = 1; i <= 31; i += 1) { | ||
|
|
69 | + if (this.localization.ordinal(i).replace(/[\[\]]/g, '') === input) { | ||
|
|
70 | + ojb.day = i; | ||
|
|
71 | + } | ||
|
|
72 | + } | ||
|
|
73 | + }, | ||
|
|
74 | + ], | ||
|
|
75 | + M: [this.match1to2, this.addInput('month')], | ||
|
|
76 | + MM: [this.match2, this.addInput('month')], | ||
|
|
77 | + MMM: [ | ||
|
|
78 | + this.matchWord, | ||
|
|
79 | + (obj, input) => { | ||
|
|
80 | + const months = this.getAllMonths(); | ||
|
|
81 | + const monthsShort = this.getAllMonths('short'); | ||
|
|
82 | + const matchIndex = (monthsShort || months.map((_) => _.slice(0, 3))).indexOf(input) + 1; | ||
|
|
83 | + if (matchIndex < 1) { | ||
|
|
84 | + throw new Error(); | ||
|
|
85 | + } | ||
|
|
86 | + obj.month = matchIndex % 12 || matchIndex; | ||
|
|
87 | + }, | ||
|
|
88 | + ], | ||
|
|
89 | + MMMM: [ | ||
|
|
90 | + this.matchWord, | ||
|
|
91 | + (obj, input) => { | ||
|
|
92 | + const months = this.getAllMonths(); | ||
|
|
93 | + const matchIndex = months.indexOf(input) + 1; | ||
|
|
94 | + if (matchIndex < 1) { | ||
|
|
95 | + throw new Error(); | ||
|
|
96 | + } | ||
|
|
97 | + obj.month = matchIndex % 12 || matchIndex; | ||
|
|
98 | + }, | ||
|
|
99 | + ], | ||
|
|
100 | + y: [this.matchSigned, this.addInput('year')], | ||
|
|
101 | + yy: [ | ||
|
|
102 | + this.match2, | ||
|
|
103 | + (obj, input) => { | ||
|
|
104 | + obj.year = this.parseTwoDigitYear(input); | ||
|
|
105 | + }, | ||
|
|
106 | + ], | ||
|
|
107 | + yyyy: [this.match4, this.addInput('year')], | ||
|
|
108 | + Z: this.zoneExpressions, | ||
|
|
109 | + ZZ: this.zoneExpressions, | ||
|
|
110 | + }; | ||
|
|
111 | + this.parseFormattedInput = (input) => { | ||
|
|
112 | + if (!this.localization.format) { | ||
|
|
113 | + this.errorMessages.customDateFormatError('No format was provided'); | ||
|
|
114 | + } | ||
|
|
115 | + try { | ||
|
|
116 | + if (['x', 'X'].indexOf(this.localization.format) > -1) | ||
|
|
117 | + return new this.DateTime((this.localization.format === 'X' ? 1000 : 1) * input); | ||
|
|
118 | + const parser = this.makeParser(this.localization.format); | ||
|
|
119 | + const { year, month, day, hours, minutes, seconds, milliseconds, zone } = parser(input); | ||
|
|
120 | + const now = new this.DateTime(); | ||
|
|
121 | + const d = day || (!year && !month ? now.getDate() : 1); | ||
|
|
122 | + const y = year || now.getFullYear(); | ||
|
|
123 | + let M = 0; | ||
|
|
124 | + if (!(year && !month)) { | ||
|
|
125 | + M = month > 0 ? month - 1 : now.getMonth(); | ||
|
|
126 | + } | ||
|
|
127 | + const h = hours || 0; | ||
|
|
128 | + const m = minutes || 0; | ||
|
|
129 | + const s = seconds || 0; | ||
|
|
130 | + const ms = milliseconds || 0; | ||
|
|
131 | + if (zone) { | ||
|
|
132 | + return new this.DateTime(Date.UTC(y, M, d, h, m, s, ms + zone.offset * 60 * 1000)); | ||
|
|
133 | + } | ||
|
|
134 | + return new this.DateTime(y, M, d, h, m, s, ms); | ||
|
|
135 | + } | ||
|
|
136 | + catch (e) { | ||
|
|
137 | + this.errorMessages.customDateFormatError(`Unable to parse provided input: ${input}, format: ${this.localization.format}`); | ||
|
|
138 | + return new this.DateTime(''); // Invalid Date | ||
|
|
139 | + } | ||
|
|
140 | + }; | ||
|
|
141 | + this.DateTime = dateTime; | ||
|
|
142 | + this.errorMessages = errorMessages; | ||
|
|
143 | + } | ||
|
|
144 | + getAllMonths(format = 'long') { | ||
|
|
145 | + const applyFormat = new Intl.DateTimeFormat(this.localization.locale, { month: format }).format; | ||
|
|
146 | + return [...Array(12).keys()].map((m) => applyFormat(new Date(2021, m))); | ||
|
|
147 | + } | ||
|
|
148 | + replaceExtendedTokens(format) { | ||
|
|
149 | + return format.replace(/(\[[^\]]+])|(MMMM|MM|dd|dddd)/g, (_, a, b) => a || b.slice(1)); | ||
|
|
150 | + } | ||
|
|
151 | + replaceTokens(formatStr, formats) { | ||
|
|
152 | + return formatStr.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => { | ||
|
|
153 | + const B = b && b.toUpperCase(); | ||
|
|
154 | + return a || this.englishFormats[b] || this.replaceExtendedTokens(formats[B]); | ||
|
|
155 | + }); | ||
|
|
156 | + } | ||
|
|
157 | + parseTwoDigitYear(input) { | ||
|
|
158 | + input = +input; | ||
|
|
159 | + return input + (input > 68 ? 1900 : 2000); | ||
|
|
160 | + } | ||
|
|
161 | + ; | ||
|
|
162 | + offsetFromString(string) { | ||
|
|
163 | + if (!string) | ||
|
|
164 | + return 0; | ||
|
|
165 | + if (string === 'Z') | ||
|
|
166 | + return 0; | ||
|
|
167 | + const parts = string.match(/([+-]|\d\d)/g); | ||
|
|
168 | + const minutes = +(parts[1] * 60) + (+parts[2] || 0); | ||
|
|
169 | + return minutes === 0 ? 0 : parts[0] === '+' ? -minutes : minutes; // eslint-disable-line no-nested-ternary | ||
|
|
170 | + } | ||
|
|
171 | + addInput(property) { | ||
|
|
172 | + return (time, input) => { | ||
|
|
173 | + time[property] = +input; | ||
|
|
174 | + }; | ||
|
|
175 | + } | ||
|
|
176 | + ; | ||
|
|
177 | + meridiemMatch(input) { | ||
|
|
178 | + const meridiem = new Intl.DateTimeFormat(this.localization.locale, { | ||
|
|
179 | + hour: 'numeric', | ||
|
|
180 | + hour12: true, | ||
|
|
181 | + }) | ||
|
|
182 | + .formatToParts(new Date(2022, 3, 4, 13)) | ||
|
|
183 | + .find((p) => p.type === 'dayPeriod')?.value; | ||
|
|
184 | + return input.toLowerCase() === meridiem.toLowerCase(); | ||
|
|
185 | + } | ||
|
|
186 | + ; | ||
|
|
187 | + correctHours(time) { | ||
|
|
188 | + const { afternoon } = time; | ||
|
|
189 | + if (afternoon !== undefined) { | ||
|
|
190 | + const { hours } = time; | ||
|
|
191 | + if (afternoon) { | ||
|
|
192 | + if (hours < 12) { | ||
|
|
193 | + time.hours += 12; | ||
|
|
194 | + } | ||
|
|
195 | + } | ||
|
|
196 | + else if (hours === 12) { | ||
|
|
197 | + time.hours = 0; | ||
|
|
198 | + } | ||
|
|
199 | + delete time.afternoon; | ||
|
|
200 | + } | ||
|
|
201 | + } | ||
|
|
202 | + makeParser(format) { | ||
|
|
203 | + format = this.replaceTokens(format, this.localization.dateFormats); | ||
|
|
204 | + const array = format.match(this.formattingTokens); | ||
|
|
205 | + const { length } = array; | ||
|
|
206 | + for (let i = 0; i < length; i += 1) { | ||
|
|
207 | + const token = array[i]; | ||
|
|
208 | + const parseTo = this.expressions[token]; | ||
|
|
209 | + const regex = parseTo && parseTo[0]; | ||
|
|
210 | + const parser = parseTo && parseTo[1]; | ||
|
|
211 | + if (parser) { | ||
|
|
212 | + array[i] = { regex, parser }; | ||
|
|
213 | + } | ||
|
|
214 | + else { | ||
|
|
215 | + array[i] = token.replace(/^\[|]$/g, ''); | ||
|
|
216 | + } | ||
|
|
217 | + } | ||
|
|
218 | + return (input) => { | ||
|
|
219 | + const time = {}; | ||
|
|
220 | + for (let i = 0, start = 0; i < length; i += 1) { | ||
|
|
221 | + const token = array[i]; | ||
|
|
222 | + if (typeof token === 'string') { | ||
|
|
223 | + start += token.length; | ||
|
|
224 | + } | ||
|
|
225 | + else { | ||
|
|
226 | + const { regex, parser } = token; | ||
|
|
227 | + const part = input.slice(start); | ||
|
|
228 | + const match = regex.exec(part); | ||
|
|
229 | + const value = match[0]; | ||
|
|
230 | + parser.call(this, time, value); | ||
|
|
231 | + input = input.replace(value, ''); | ||
|
|
232 | + } | ||
|
|
233 | + } | ||
|
|
234 | + this.correctHours(time); | ||
|
|
235 | + return time; | ||
|
|
236 | + }; | ||
|
|
237 | + } | ||
|
|
238 | + format(dateTime) { | ||
|
|
239 | + if (!dateTime) | ||
|
|
240 | + return dateTime; | ||
|
|
241 | + if (JSON.stringify(dateTime) === 'null') | ||
|
|
242 | + return 'Invalid Date'; | ||
|
|
243 | + const format = this.replaceTokens(this.localization.format || `${this.englishFormats.L}, ${this.englishFormats.LT}`, this.localization.dateFormats); | ||
|
|
244 | + const formatter = (template) => new Intl.DateTimeFormat(this.localization.locale, template).format(dateTime); | ||
|
|
245 | + const matches = { | ||
|
|
246 | + yy: formatter({ year: '2-digit' }), | ||
|
|
247 | + yyyy: dateTime.year, | ||
|
|
248 | + M: formatter({ month: 'numeric' }), | ||
|
|
249 | + MM: dateTime.monthFormatted, | ||
|
|
250 | + MMM: this.getAllMonths('short')[dateTime.getMonth()], | ||
|
|
251 | + MMMM: this.getAllMonths()[dateTime.getMonth()], | ||
|
|
252 | + d: dateTime.date, | ||
|
|
253 | + dd: dateTime.dateFormatted, | ||
|
|
254 | + ddd: formatter({ weekday: "short" }), | ||
|
|
255 | + dddd: formatter({ weekday: "long" }), | ||
|
|
256 | + H: dateTime.getHours(), | ||
|
|
257 | + HH: dateTime.hoursFormatted, | ||
|
|
258 | + h: dateTime.hours > 12 ? dateTime.hours - 12 : dateTime.hours, | ||
|
|
259 | + hh: dateTime.twelveHoursFormatted, | ||
|
|
260 | + t: dateTime.meridiem(), | ||
|
|
261 | + T: dateTime.meridiem().toUpperCase(), | ||
|
|
262 | + m: dateTime.minutes, | ||
|
|
263 | + mm: dateTime.minutesFormatted, | ||
|
|
264 | + s: dateTime.seconds, | ||
|
|
265 | + ss: dateTime.secondsFormatted, | ||
|
|
266 | + fff: dateTime.getMilliseconds(), | ||
|
|
267 | + //z: dateTime.getTimezoneOffset() todo zones are stupid | ||
|
|
268 | + }; | ||
|
|
269 | + return format.replace(this.REGEX_FORMAT, (match, $1) => { | ||
|
|
270 | + return $1 || matches[match]; | ||
|
|
271 | + }); | ||
|
|
272 | + } | ||
|
|
273 | + } | ||
|
|
274 | + var index = (_, tdClasses, __) => { | ||
|
|
275 | + const customDateFormat = new CustomDateFormat(tdClasses.DateTime, tdClasses.ErrorMessages); | ||
|
|
276 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
277 | + tdClasses.Dates.prototype.formatInput = function (date) { | ||
|
|
278 | + customDateFormat.localization = this.optionsStore.options.localization; | ||
|
|
279 | + return customDateFormat.format(date); | ||
|
|
280 | + }; | ||
|
|
281 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
282 | + tdClasses.Dates.prototype.parseInput = function (input) { | ||
|
|
283 | + customDateFormat.localization = this.optionsStore.options.localization; | ||
|
|
284 | + return customDateFormat.parseFormattedInput(input); | ||
|
|
285 | + }; | ||
|
|
286 | + tdClasses.DateTime.fromString = function (input, localization) { | ||
|
|
287 | + customDateFormat.localization = localization; | ||
|
|
288 | + return customDateFormat.parseFormattedInput(input); | ||
|
|
289 | + }; | ||
|
|
290 | + };return index;})); |
This diff has been collapsed as it changes many lines, (3883 lines changed) Show them Hide them | |||||
@@ -0,0 +1,3883 | |||||
|
|
1 | + /*! | ||
|
|
2 | + * Tempus Dominus v6.2.4 (https://getdatepicker.com/) | ||
|
|
3 | + * Copyright 2013-2022 Jonathan Peterson | ||
|
|
4 | + * Licensed under MIT (https://github.com/Eonasdan/tempus-dominus/blob/master/LICENSE) | ||
|
|
5 | + */ | ||
|
|
6 | + var Unit; | ||
|
|
7 | + (function (Unit) { | ||
|
|
8 | + Unit["seconds"] = "seconds"; | ||
|
|
9 | + Unit["minutes"] = "minutes"; | ||
|
|
10 | + Unit["hours"] = "hours"; | ||
|
|
11 | + Unit["date"] = "date"; | ||
|
|
12 | + Unit["month"] = "month"; | ||
|
|
13 | + Unit["year"] = "year"; | ||
|
|
14 | + })(Unit || (Unit = {})); | ||
|
|
15 | + const twoDigitTemplate = { | ||
|
|
16 | + month: '2-digit', | ||
|
|
17 | + day: '2-digit', | ||
|
|
18 | + year: 'numeric', | ||
|
|
19 | + hour: '2-digit', | ||
|
|
20 | + minute: '2-digit', | ||
|
|
21 | + second: '2-digit', | ||
|
|
22 | + hour12: true, | ||
|
|
23 | + }; | ||
|
|
24 | + const twoDigitTwentyFourTemplate = { | ||
|
|
25 | + hour: '2-digit', | ||
|
|
26 | + hour12: false | ||
|
|
27 | + }; | ||
|
|
28 | + const getFormatByUnit = (unit) => { | ||
|
|
29 | + switch (unit) { | ||
|
|
30 | + case 'date': | ||
|
|
31 | + return { dateStyle: 'short' }; | ||
|
|
32 | + case 'month': | ||
|
|
33 | + return { | ||
|
|
34 | + month: 'numeric', | ||
|
|
35 | + year: 'numeric' | ||
|
|
36 | + }; | ||
|
|
37 | + case 'year': | ||
|
|
38 | + return { year: 'numeric' }; | ||
|
|
39 | + } | ||
|
|
40 | + }; | ||
|
|
41 | + /** | ||
|
|
42 | + * For the most part this object behaves exactly the same way | ||
|
|
43 | + * as the native Date object with a little extra spice. | ||
|
|
44 | + */ | ||
|
|
45 | + class DateTime extends Date { | ||
|
|
46 | + constructor() { | ||
|
|
47 | + super(...arguments); | ||
|
|
48 | + /** | ||
|
|
49 | + * Used with Intl.DateTimeFormat | ||
|
|
50 | + */ | ||
|
|
51 | + this.locale = 'default'; | ||
|
|
52 | + this.nonLeapLadder = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; | ||
|
|
53 | + this.leapLadder = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]; | ||
|
|
54 | + } | ||
|
|
55 | + /** | ||
|
|
56 | + * Chainable way to set the {@link locale} | ||
|
|
57 | + * @param value | ||
|
|
58 | + */ | ||
|
|
59 | + setLocale(value) { | ||
|
|
60 | + this.locale = value; | ||
|
|
61 | + return this; | ||
|
|
62 | + } | ||
|
|
63 | + /** | ||
|
|
64 | + * Converts a plain JS date object to a DateTime object. | ||
|
|
65 | + * Doing this allows access to format, etc. | ||
|
|
66 | + * @param date | ||
|
|
67 | + * @param locale | ||
|
|
68 | + */ | ||
|
|
69 | + static convert(date, locale = 'default') { | ||
|
|
70 | + if (!date) | ||
|
|
71 | + throw new Error(`A date is required`); | ||
|
|
72 | + return new DateTime(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()).setLocale(locale); | ||
|
|
73 | + } | ||
|
|
74 | + /** | ||
|
|
75 | + * Attempts to create a DateTime from a string. A customDateFormat is required for non US dates. | ||
|
|
76 | + * @param input | ||
|
|
77 | + * @param localization | ||
|
|
78 | + */ | ||
|
|
79 | + static fromString(input, localization) { | ||
|
|
80 | + return new DateTime(input); | ||
|
|
81 | + } | ||
|
|
82 | + /** | ||
|
|
83 | + * Native date manipulations are not pure functions. This function creates a duplicate of the DateTime object. | ||
|
|
84 | + */ | ||
|
|
85 | + get clone() { | ||
|
|
86 | + return new DateTime(this.year, this.month, this.date, this.hours, this.minutes, this.seconds, this.getMilliseconds()).setLocale(this.locale); | ||
|
|
87 | + } | ||
|
|
88 | + /** | ||
|
|
89 | + * Sets the current date to the start of the {@link unit} provided | ||
|
|
90 | + * Example: Consider a date of "April 30, 2021, 11:45:32.984 AM" => new DateTime(2021, 3, 30, 11, 45, 32, 984).startOf('month') | ||
|
|
91 | + * would return April 1, 2021, 12:00:00.000 AM (midnight) | ||
|
|
92 | + * @param unit | ||
|
|
93 | + * @param startOfTheWeek Allows for the changing the start of the week. | ||
|
|
94 | + */ | ||
|
|
95 | + startOf(unit, startOfTheWeek = 0) { | ||
|
|
96 | + if (this[unit] === undefined) | ||
|
|
97 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
98 | + switch (unit) { | ||
|
|
99 | + case 'seconds': | ||
|
|
100 | + this.setMilliseconds(0); | ||
|
|
101 | + break; | ||
|
|
102 | + case 'minutes': | ||
|
|
103 | + this.setSeconds(0, 0); | ||
|
|
104 | + break; | ||
|
|
105 | + case 'hours': | ||
|
|
106 | + this.setMinutes(0, 0, 0); | ||
|
|
107 | + break; | ||
|
|
108 | + case 'date': | ||
|
|
109 | + this.setHours(0, 0, 0, 0); | ||
|
|
110 | + break; | ||
|
|
111 | + case 'weekDay': | ||
|
|
112 | + this.startOf(Unit.date); | ||
|
|
113 | + if (this.weekDay === startOfTheWeek) | ||
|
|
114 | + break; | ||
|
|
115 | + let goBack = this.weekDay; | ||
|
|
116 | + if (startOfTheWeek !== 0 && this.weekDay === 0) | ||
|
|
117 | + goBack = 8 - startOfTheWeek; | ||
|
|
118 | + this.manipulate(startOfTheWeek - goBack, Unit.date); | ||
|
|
119 | + break; | ||
|
|
120 | + case 'month': | ||
|
|
121 | + this.startOf(Unit.date); | ||
|
|
122 | + this.setDate(1); | ||
|
|
123 | + break; | ||
|
|
124 | + case 'year': | ||
|
|
125 | + this.startOf(Unit.date); | ||
|
|
126 | + this.setMonth(0, 1); | ||
|
|
127 | + break; | ||
|
|
128 | + } | ||
|
|
129 | + return this; | ||
|
|
130 | + } | ||
|
|
131 | + /** | ||
|
|
132 | + * Sets the current date to the end of the {@link unit} provided | ||
|
|
133 | + * Example: Consider a date of "April 30, 2021, 11:45:32.984 AM" => new DateTime(2021, 3, 30, 11, 45, 32, 984).endOf('month') | ||
|
|
134 | + * would return April 30, 2021, 11:59:59.999 PM | ||
|
|
135 | + * @param unit | ||
|
|
136 | + * @param startOfTheWeek | ||
|
|
137 | + */ | ||
|
|
138 | + endOf(unit, startOfTheWeek = 0) { | ||
|
|
139 | + if (this[unit] === undefined) | ||
|
|
140 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
141 | + switch (unit) { | ||
|
|
142 | + case 'seconds': | ||
|
|
143 | + this.setMilliseconds(999); | ||
|
|
144 | + break; | ||
|
|
145 | + case 'minutes': | ||
|
|
146 | + this.setSeconds(59, 999); | ||
|
|
147 | + break; | ||
|
|
148 | + case 'hours': | ||
|
|
149 | + this.setMinutes(59, 59, 999); | ||
|
|
150 | + break; | ||
|
|
151 | + case 'date': | ||
|
|
152 | + this.setHours(23, 59, 59, 999); | ||
|
|
153 | + break; | ||
|
|
154 | + case 'weekDay': | ||
|
|
155 | + this.endOf(Unit.date); | ||
|
|
156 | + this.manipulate((6 + startOfTheWeek) - this.weekDay, Unit.date); | ||
|
|
157 | + break; | ||
|
|
158 | + case 'month': | ||
|
|
159 | + this.endOf(Unit.date); | ||
|
|
160 | + this.manipulate(1, Unit.month); | ||
|
|
161 | + this.setDate(0); | ||
|
|
162 | + break; | ||
|
|
163 | + case 'year': | ||
|
|
164 | + this.endOf(Unit.date); | ||
|
|
165 | + this.manipulate(1, Unit.year); | ||
|
|
166 | + this.setDate(0); | ||
|
|
167 | + break; | ||
|
|
168 | + } | ||
|
|
169 | + return this; | ||
|
|
170 | + } | ||
|
|
171 | + /** | ||
|
|
172 | + * Change a {@link unit} value. Value can be positive or negative | ||
|
|
173 | + * Example: Consider a date of "April 30, 2021, 11:45:32.984 AM" => new DateTime(2021, 3, 30, 11, 45, 32, 984).manipulate(1, 'month') | ||
|
|
174 | + * would return May 30, 2021, 11:45:32.984 AM | ||
|
|
175 | + * @param value A positive or negative number | ||
|
|
176 | + * @param unit | ||
|
|
177 | + */ | ||
|
|
178 | + manipulate(value, unit) { | ||
|
|
179 | + if (this[unit] === undefined) | ||
|
|
180 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
181 | + this[unit] += value; | ||
|
|
182 | + return this; | ||
|
|
183 | + } | ||
|
|
184 | + /** | ||
|
|
185 | + * Returns a string format. | ||
|
|
186 | + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat | ||
|
|
187 | + * for valid templates and locale objects | ||
|
|
188 | + * @param template An object. Uses browser defaults otherwise. | ||
|
|
189 | + * @param locale Can be a string or an array of strings. Uses browser defaults otherwise. | ||
|
|
190 | + */ | ||
|
|
191 | + format(template, locale = this.locale) { | ||
|
|
192 | + return new Intl.DateTimeFormat(locale, template).format(this); | ||
|
|
193 | + } | ||
|
|
194 | + /** | ||
|
|
195 | + * Return true if {@link compare} is before this date | ||
|
|
196 | + * @param compare The Date/DateTime to compare | ||
|
|
197 | + * @param unit If provided, uses {@link startOf} for | ||
|
|
198 | + * comparision. | ||
|
|
199 | + */ | ||
|
|
200 | + isBefore(compare, unit) { | ||
|
|
201 | + if (!unit) | ||
|
|
202 | + return this.valueOf() < compare.valueOf(); | ||
|
|
203 | + if (this[unit] === undefined) | ||
|
|
204 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
205 | + return (this.clone.startOf(unit).valueOf() < compare.clone.startOf(unit).valueOf()); | ||
|
|
206 | + } | ||
|
|
207 | + /** | ||
|
|
208 | + * Return true if {@link compare} is after this date | ||
|
|
209 | + * @param compare The Date/DateTime to compare | ||
|
|
210 | + * @param unit If provided, uses {@link startOf} for | ||
|
|
211 | + * comparision. | ||
|
|
212 | + */ | ||
|
|
213 | + isAfter(compare, unit) { | ||
|
|
214 | + if (!unit) | ||
|
|
215 | + return this.valueOf() > compare.valueOf(); | ||
|
|
216 | + if (this[unit] === undefined) | ||
|
|
217 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
218 | + return (this.clone.startOf(unit).valueOf() > compare.clone.startOf(unit).valueOf()); | ||
|
|
219 | + } | ||
|
|
220 | + /** | ||
|
|
221 | + * Return true if {@link compare} is same this date | ||
|
|
222 | + * @param compare The Date/DateTime to compare | ||
|
|
223 | + * @param unit If provided, uses {@link startOf} for | ||
|
|
224 | + * comparision. | ||
|
|
225 | + */ | ||
|
|
226 | + isSame(compare, unit) { | ||
|
|
227 | + if (!unit) | ||
|
|
228 | + return this.valueOf() === compare.valueOf(); | ||
|
|
229 | + if (this[unit] === undefined) | ||
|
|
230 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
231 | + compare = DateTime.convert(compare); | ||
|
|
232 | + return (this.clone.startOf(unit).valueOf() === compare.startOf(unit).valueOf()); | ||
|
|
233 | + } | ||
|
|
234 | + /** | ||
|
|
235 | + * Check if this is between two other DateTimes, optionally looking at unit scale. The match is exclusive. | ||
|
|
236 | + * @param left | ||
|
|
237 | + * @param right | ||
|
|
238 | + * @param unit. | ||
|
|
239 | + * @param inclusivity. A [ indicates inclusion of a value. A ( indicates exclusion. | ||
|
|
240 | + * If the inclusivity parameter is used, both indicators must be passed. | ||
|
|
241 | + */ | ||
|
|
242 | + isBetween(left, right, unit, inclusivity = '()') { | ||
|
|
243 | + if (unit && this[unit] === undefined) | ||
|
|
244 | + throw new Error(`Unit '${unit}' is not valid`); | ||
|
|
245 | + const leftInclusivity = inclusivity[0] === '('; | ||
|
|
246 | + const rightInclusivity = inclusivity[1] === ')'; | ||
|
|
247 | + return (((leftInclusivity | ||
|
|
248 | + ? this.isAfter(left, unit) | ||
|
|
249 | + : !this.isBefore(left, unit)) && | ||
|
|
250 | + (rightInclusivity | ||
|
|
251 | + ? this.isBefore(right, unit) | ||
|
|
252 | + : !this.isAfter(right, unit))) || | ||
|
|
253 | + ((leftInclusivity | ||
|
|
254 | + ? this.isBefore(left, unit) | ||
|
|
255 | + : !this.isAfter(left, unit)) && | ||
|
|
256 | + (rightInclusivity | ||
|
|
257 | + ? this.isAfter(right, unit) | ||
|
|
258 | + : !this.isBefore(right, unit)))); | ||
|
|
259 | + } | ||
|
|
260 | + /** | ||
|
|
261 | + * Returns flattened object of the date. Does not include literals | ||
|
|
262 | + * @param locale | ||
|
|
263 | + * @param template | ||
|
|
264 | + */ | ||
|
|
265 | + parts(locale = this.locale, template = { dateStyle: 'full', timeStyle: 'long' }) { | ||
|
|
266 | + const parts = {}; | ||
|
|
267 | + new Intl.DateTimeFormat(locale, template) | ||
|
|
268 | + .formatToParts(this) | ||
|
|
269 | + .filter((x) => x.type !== 'literal') | ||
|
|
270 | + .forEach((x) => (parts[x.type] = x.value)); | ||
|
|
271 | + return parts; | ||
|
|
272 | + } | ||
|
|
273 | + /** | ||
|
|
274 | + * Shortcut to Date.getSeconds() | ||
|
|
275 | + */ | ||
|
|
276 | + get seconds() { | ||
|
|
277 | + return this.getSeconds(); | ||
|
|
278 | + } | ||
|
|
279 | + /** | ||
|
|
280 | + * Shortcut to Date.setSeconds() | ||
|
|
281 | + */ | ||
|
|
282 | + set seconds(value) { | ||
|
|
283 | + this.setSeconds(value); | ||
|
|
284 | + } | ||
|
|
285 | + /** | ||
|
|
286 | + * Returns two digit hours | ||
|
|
287 | + */ | ||
|
|
288 | + get secondsFormatted() { | ||
|
|
289 | + return this.parts(undefined, twoDigitTemplate).second; | ||
|
|
290 | + } | ||
|
|
291 | + /** | ||
|
|
292 | + * Shortcut to Date.getMinutes() | ||
|
|
293 | + */ | ||
|
|
294 | + get minutes() { | ||
|
|
295 | + return this.getMinutes(); | ||
|
|
296 | + } | ||
|
|
297 | + /** | ||
|
|
298 | + * Shortcut to Date.setMinutes() | ||
|
|
299 | + */ | ||
|
|
300 | + set minutes(value) { | ||
|
|
301 | + this.setMinutes(value); | ||
|
|
302 | + } | ||
|
|
303 | + /** | ||
|
|
304 | + * Returns two digit minutes | ||
|
|
305 | + */ | ||
|
|
306 | + get minutesFormatted() { | ||
|
|
307 | + return this.parts(undefined, twoDigitTemplate).minute; | ||
|
|
308 | + } | ||
|
|
309 | + /** | ||
|
|
310 | + * Shortcut to Date.getHours() | ||
|
|
311 | + */ | ||
|
|
312 | + get hours() { | ||
|
|
313 | + return this.getHours(); | ||
|
|
314 | + } | ||
|
|
315 | + /** | ||
|
|
316 | + * Shortcut to Date.setHours() | ||
|
|
317 | + */ | ||
|
|
318 | + set hours(value) { | ||
|
|
319 | + this.setHours(value); | ||
|
|
320 | + } | ||
|
|
321 | + /** | ||
|
|
322 | + * Returns two digit hours | ||
|
|
323 | + */ | ||
|
|
324 | + get hoursFormatted() { | ||
|
|
325 | + return this.parts(undefined, twoDigitTwentyFourTemplate).hour; | ||
|
|
326 | + } | ||
|
|
327 | + /** | ||
|
|
328 | + * Returns two digit hours but in twelve hour mode e.g. 13 -> 1 | ||
|
|
329 | + */ | ||
|
|
330 | + get twelveHoursFormatted() { | ||
|
|
331 | + return this.parts(undefined, twoDigitTemplate).hour; | ||
|
|
332 | + } | ||
|
|
333 | + /** | ||
|
|
334 | + * Get the meridiem of the date. E.g. AM or PM. | ||
|
|
335 | + * If the {@link locale} provides a "dayPeriod" then this will be returned, | ||
|
|
336 | + * otherwise it will return AM or PM. | ||
|
|
337 | + * @param locale | ||
|
|
338 | + */ | ||
|
|
339 | + meridiem(locale = this.locale) { | ||
|
|
340 | + return new Intl.DateTimeFormat(locale, { | ||
|
|
341 | + hour: 'numeric', | ||
|
|
342 | + hour12: true | ||
|
|
343 | + }) | ||
|
|
344 | + .formatToParts(this) | ||
|
|
345 | + .find((p) => p.type === 'dayPeriod')?.value; | ||
|
|
346 | + } | ||
|
|
347 | + /** | ||
|
|
348 | + * Shortcut to Date.getDate() | ||
|
|
349 | + */ | ||
|
|
350 | + get date() { | ||
|
|
351 | + return this.getDate(); | ||
|
|
352 | + } | ||
|
|
353 | + /** | ||
|
|
354 | + * Shortcut to Date.setDate() | ||
|
|
355 | + */ | ||
|
|
356 | + set date(value) { | ||
|
|
357 | + this.setDate(value); | ||
|
|
358 | + } | ||
|
|
359 | + /** | ||
|
|
360 | + * Return two digit date | ||
|
|
361 | + */ | ||
|
|
362 | + get dateFormatted() { | ||
|
|
363 | + return this.parts(undefined, twoDigitTemplate).day; | ||
|
|
364 | + } | ||
|
|
365 | + /** | ||
|
|
366 | + * Shortcut to Date.getDay() | ||
|
|
367 | + */ | ||
|
|
368 | + get weekDay() { | ||
|
|
369 | + return this.getDay(); | ||
|
|
370 | + } | ||
|
|
371 | + /** | ||
|
|
372 | + * Shortcut to Date.getMonth() | ||
|
|
373 | + */ | ||
|
|
374 | + get month() { | ||
|
|
375 | + return this.getMonth(); | ||
|
|
376 | + } | ||
|
|
377 | + /** | ||
|
|
378 | + * Shortcut to Date.setMonth() | ||
|
|
379 | + */ | ||
|
|
380 | + set month(value) { | ||
|
|
381 | + const targetMonth = new Date(this.year, value + 1); | ||
|
|
382 | + targetMonth.setDate(0); | ||
|
|
383 | + const endOfMonth = targetMonth.getDate(); | ||
|
|
384 | + if (this.date > endOfMonth) { | ||
|
|
385 | + this.date = endOfMonth; | ||
|
|
386 | + } | ||
|
|
387 | + this.setMonth(value); | ||
|
|
388 | + } | ||
|
|
389 | + /** | ||
|
|
390 | + * Return two digit, human expected month. E.g. January = 1, December = 12 | ||
|
|
391 | + */ | ||
|
|
392 | + get monthFormatted() { | ||
|
|
393 | + return this.parts(undefined, twoDigitTemplate).month; | ||
|
|
394 | + } | ||
|
|
395 | + /** | ||
|
|
396 | + * Shortcut to Date.getFullYear() | ||
|
|
397 | + */ | ||
|
|
398 | + get year() { | ||
|
|
399 | + return this.getFullYear(); | ||
|
|
400 | + } | ||
|
|
401 | + /** | ||
|
|
402 | + * Shortcut to Date.setFullYear() | ||
|
|
403 | + */ | ||
|
|
404 | + set year(value) { | ||
|
|
405 | + this.setFullYear(value); | ||
|
|
406 | + } | ||
|
|
407 | + // borrowed a bunch of stuff from Luxon | ||
|
|
408 | + /** | ||
|
|
409 | + * Gets the week of the year | ||
|
|
410 | + */ | ||
|
|
411 | + get week() { | ||
|
|
412 | + const ordinal = this.computeOrdinal(), weekday = this.getUTCDay(); | ||
|
|
413 | + let weekNumber = Math.floor((ordinal - weekday + 10) / 7); | ||
|
|
414 | + if (weekNumber < 1) { | ||
|
|
415 | + weekNumber = this.weeksInWeekYear(this.year - 1); | ||
|
|
416 | + } | ||
|
|
417 | + else if (weekNumber > this.weeksInWeekYear(this.year)) { | ||
|
|
418 | + weekNumber = 1; | ||
|
|
419 | + } | ||
|
|
420 | + return weekNumber; | ||
|
|
421 | + } | ||
|
|
422 | + weeksInWeekYear(weekYear) { | ||
|
|
423 | + const p1 = (weekYear + | ||
|
|
424 | + Math.floor(weekYear / 4) - | ||
|
|
425 | + Math.floor(weekYear / 100) + | ||
|
|
426 | + Math.floor(weekYear / 400)) % | ||
|
|
427 | + 7, last = weekYear - 1, p2 = (last + | ||
|
|
428 | + Math.floor(last / 4) - | ||
|
|
429 | + Math.floor(last / 100) + | ||
|
|
430 | + Math.floor(last / 400)) % | ||
|
|
431 | + 7; | ||
|
|
432 | + return p1 === 4 || p2 === 3 ? 53 : 52; | ||
|
|
433 | + } | ||
|
|
434 | + get isLeapYear() { | ||
|
|
435 | + return this.year % 4 === 0 && (this.year % 100 !== 0 || this.year % 400 === 0); | ||
|
|
436 | + } | ||
|
|
437 | + computeOrdinal() { | ||
|
|
438 | + return this.date + (this.isLeapYear ? this.leapLadder : this.nonLeapLadder)[this.month]; | ||
|
|
439 | + } | ||
|
|
440 | + } | ||
|
|
441 | + | ||
|
|
442 | + class TdError extends Error { | ||
|
|
443 | + } | ||
|
|
444 | + class ErrorMessages { | ||
|
|
445 | + constructor() { | ||
|
|
446 | + this.base = 'TD:'; | ||
|
|
447 | + //#endregion | ||
|
|
448 | + //#region used with notify.error | ||
|
|
449 | + /** | ||
|
|
450 | + * Used with an Error Event type if the user selects a date that | ||
|
|
451 | + * fails restriction validation. | ||
|
|
452 | + */ | ||
|
|
453 | + this.failedToSetInvalidDate = 'Failed to set invalid date'; | ||
|
|
454 | + /** | ||
|
|
455 | + * Used with an Error Event type when a user changes the value of the | ||
|
|
456 | + * input field directly, and does not provide a valid date. | ||
|
|
457 | + */ | ||
|
|
458 | + this.failedToParseInput = 'Failed parse input field'; | ||
|
|
459 | + //#endregion | ||
|
|
460 | + } | ||
|
|
461 | + //#region out to console | ||
|
|
462 | + /** | ||
|
|
463 | + * Throws an error indicating that a key in the options object is invalid. | ||
|
|
464 | + * @param optionName | ||
|
|
465 | + */ | ||
|
|
466 | + unexpectedOption(optionName) { | ||
|
|
467 | + const error = new TdError(`${this.base} Unexpected option: ${optionName} does not match a known option.`); | ||
|
|
468 | + error.code = 1; | ||
|
|
469 | + throw error; | ||
|
|
470 | + } | ||
|
|
471 | + /** | ||
|
|
472 | + * Throws an error indicating that one more keys in the options object is invalid. | ||
|
|
473 | + * @param optionName | ||
|
|
474 | + */ | ||
|
|
475 | + unexpectedOptions(optionName) { | ||
|
|
476 | + const error = new TdError(`${this.base}: ${optionName.join(', ')}`); | ||
|
|
477 | + error.code = 1; | ||
|
|
478 | + throw error; | ||
|
|
479 | + } | ||
|
|
480 | + /** | ||
|
|
481 | + * Throws an error when an option is provide an unsupported value. | ||
|
|
482 | + * For example a value of 'cheese' for toolbarPlacement which only supports | ||
|
|
483 | + * 'top', 'bottom', 'default'. | ||
|
|
484 | + * @param optionName | ||
|
|
485 | + * @param badValue | ||
|
|
486 | + * @param validOptions | ||
|
|
487 | + */ | ||
|
|
488 | + unexpectedOptionValue(optionName, badValue, validOptions) { | ||
|
|
489 | + const error = new TdError(`${this.base} Unexpected option value: ${optionName} does not accept a value of "${badValue}". Valid values are: ${validOptions.join(', ')}`); | ||
|
|
490 | + error.code = 2; | ||
|
|
491 | + throw error; | ||
|
|
492 | + } | ||
|
|
493 | + /** | ||
|
|
494 | + * Throws an error when an option value is the wrong type. | ||
|
|
495 | + * For example a string value was provided to multipleDates which only | ||
|
|
496 | + * supports true or false. | ||
|
|
497 | + * @param optionName | ||
|
|
498 | + * @param badType | ||
|
|
499 | + * @param expectedType | ||
|
|
500 | + */ | ||
|
|
501 | + typeMismatch(optionName, badType, expectedType) { | ||
|
|
502 | + const error = new TdError(`${this.base} Mismatch types: ${optionName} has a type of ${badType} instead of the required ${expectedType}`); | ||
|
|
503 | + error.code = 3; | ||
|
|
504 | + throw error; | ||
|
|
505 | + } | ||
|
|
506 | + /** | ||
|
|
507 | + * Throws an error when an option value is outside of the expected range. | ||
|
|
508 | + * For example restrictions.daysOfWeekDisabled excepts a value between 0 and 6. | ||
|
|
509 | + * @param optionName | ||
|
|
510 | + * @param lower | ||
|
|
511 | + * @param upper | ||
|
|
512 | + */ | ||
|
|
513 | + numbersOutOfRange(optionName, lower, upper) { | ||
|
|
514 | + const error = new TdError(`${this.base} ${optionName} expected an array of number between ${lower} and ${upper}.`); | ||
|
|
515 | + error.code = 4; | ||
|
|
516 | + throw error; | ||
|
|
517 | + } | ||
|
|
518 | + /** | ||
|
|
519 | + * Throws an error when a value for a date options couldn't be parsed. Either | ||
|
|
520 | + * the option was an invalid string or an invalid Date object. | ||
|
|
521 | + * @param optionName | ||
|
|
522 | + * @param date | ||
|
|
523 | + * @param soft If true, logs a warning instead of an error. | ||
|
|
524 | + */ | ||
|
|
525 | + failedToParseDate(optionName, date, soft = false) { | ||
|
|
526 | + const error = new TdError(`${this.base} Could not correctly parse "${date}" to a date for ${optionName}.`); | ||
|
|
527 | + error.code = 5; | ||
|
|
528 | + if (!soft) | ||
|
|
529 | + throw error; | ||
|
|
530 | + console.warn(error); | ||
|
|
531 | + } | ||
|
|
532 | + /** | ||
|
|
533 | + * Throws when an element to attach to was not provided in the constructor. | ||
|
|
534 | + */ | ||
|
|
535 | + mustProvideElement() { | ||
|
|
536 | + const error = new TdError(`${this.base} No element was provided.`); | ||
|
|
537 | + error.code = 6; | ||
|
|
538 | + throw error; | ||
|
|
539 | + } | ||
|
|
540 | + /** | ||
|
|
541 | + * Throws if providing an array for the events to subscribe method doesn't have | ||
|
|
542 | + * the same number of callbacks. E.g., subscribe([1,2], [1]) | ||
|
|
543 | + */ | ||
|
|
544 | + subscribeMismatch() { | ||
|
|
545 | + const error = new TdError(`${this.base} The subscribed events does not match the number of callbacks`); | ||
|
|
546 | + error.code = 7; | ||
|
|
547 | + throw error; | ||
|
|
548 | + } | ||
|
|
549 | + /** | ||
|
|
550 | + * Throws if the configuration has conflicting rules e.g. minDate is after maxDate | ||
|
|
551 | + */ | ||
|
|
552 | + conflictingConfiguration(message) { | ||
|
|
553 | + const error = new TdError(`${this.base} A configuration value conflicts with another rule. ${message}`); | ||
|
|
554 | + error.code = 8; | ||
|
|
555 | + throw error; | ||
|
|
556 | + } | ||
|
|
557 | + /** | ||
|
|
558 | + * customDateFormat errors | ||
|
|
559 | + */ | ||
|
|
560 | + customDateFormatError(message) { | ||
|
|
561 | + const error = new TdError(`${this.base} customDateFormat: ${message}`); | ||
|
|
562 | + error.code = 9; | ||
|
|
563 | + throw error; | ||
|
|
564 | + } | ||
|
|
565 | + /** | ||
|
|
566 | + * Logs a warning if a date option value is provided as a string, instead of | ||
|
|
567 | + * a date/datetime object. | ||
|
|
568 | + */ | ||
|
|
569 | + dateString() { | ||
|
|
570 | + console.warn(`${this.base} Using a string for date options is not recommended unless you specify an ISO string or use the customDateFormat plugin.`); | ||
|
|
571 | + } | ||
|
|
572 | + throwError(message) { | ||
|
|
573 | + const error = new TdError(`${this.base} ${message}`); | ||
|
|
574 | + error.code = 9; | ||
|
|
575 | + throw error; | ||
|
|
576 | + } | ||
|
|
577 | + } | ||
|
|
578 | + | ||
|
|
579 | + // this is not the way I want this to stay but nested classes seemed to blown up once its compiled. | ||
|
|
580 | + const NAME = 'tempus-dominus', dataKey = 'td'; | ||
|
|
581 | + /** | ||
|
|
582 | + * Events | ||
|
|
583 | + */ | ||
|
|
584 | + class Events { | ||
|
|
585 | + constructor() { | ||
|
|
586 | + this.key = `.${dataKey}`; | ||
|
|
587 | + /** | ||
|
|
588 | + * Change event. Fired when the user selects a date. | ||
|
|
589 | + * See also EventTypes.ChangeEvent | ||
|
|
590 | + */ | ||
|
|
591 | + this.change = `change${this.key}`; | ||
|
|
592 | + /** | ||
|
|
593 | + * Emit when the view changes for example from month view to the year view. | ||
|
|
594 | + * See also EventTypes.ViewUpdateEvent | ||
|
|
595 | + */ | ||
|
|
596 | + this.update = `update${this.key}`; | ||
|
|
597 | + /** | ||
|
|
598 | + * Emits when a selected date or value from the input field fails to meet the provided validation rules. | ||
|
|
599 | + * See also EventTypes.FailEvent | ||
|
|
600 | + */ | ||
|
|
601 | + this.error = `error${this.key}`; | ||
|
|
602 | + /** | ||
|
|
603 | + * Show event | ||
|
|
604 | + * @event Events#show | ||
|
|
605 | + */ | ||
|
|
606 | + this.show = `show${this.key}`; | ||
|
|
607 | + /** | ||
|
|
608 | + * Hide event | ||
|
|
609 | + * @event Events#hide | ||
|
|
610 | + */ | ||
|
|
611 | + this.hide = `hide${this.key}`; | ||
|
|
612 | + // blur and focus are used in the jQuery provider but are otherwise unused. | ||
|
|
613 | + // keyup/down will be used later for keybinding options | ||
|
|
614 | + this.blur = `blur${this.key}`; | ||
|
|
615 | + this.focus = `focus${this.key}`; | ||
|
|
616 | + this.keyup = `keyup${this.key}`; | ||
|
|
617 | + this.keydown = `keydown${this.key}`; | ||
|
|
618 | + } | ||
|
|
619 | + } | ||
|
|
620 | + class Css { | ||
|
|
621 | + constructor() { | ||
|
|
622 | + /** | ||
|
|
623 | + * The outer element for the widget. | ||
|
|
624 | + */ | ||
|
|
625 | + this.widget = `${NAME}-widget`; | ||
|
|
626 | + /** | ||
|
|
627 | + * Hold the previous, next and switcher divs | ||
|
|
628 | + */ | ||
|
|
629 | + this.calendarHeader = 'calendar-header'; | ||
|
|
630 | + /** | ||
|
|
631 | + * The element for the action to change the calendar view. E.g. month -> year. | ||
|
|
632 | + */ | ||
|
|
633 | + this.switch = 'picker-switch'; | ||
|
|
634 | + /** | ||
|
|
635 | + * The elements for all the toolbar options | ||
|
|
636 | + */ | ||
|
|
637 | + this.toolbar = 'toolbar'; | ||
|
|
638 | + /** | ||
|
|
639 | + * Disables the hover and rounding affect. | ||
|
|
640 | + */ | ||
|
|
641 | + this.noHighlight = 'no-highlight'; | ||
|
|
642 | + /** | ||
|
|
643 | + * Applied to the widget element when the side by side option is in use. | ||
|
|
644 | + */ | ||
|
|
645 | + this.sideBySide = 'timepicker-sbs'; | ||
|
|
646 | + /** | ||
|
|
647 | + * The element for the action to change the calendar view, e.g. August -> July | ||
|
|
648 | + */ | ||
|
|
649 | + this.previous = 'previous'; | ||
|
|
650 | + /** | ||
|
|
651 | + * The element for the action to change the calendar view, e.g. August -> September | ||
|
|
652 | + */ | ||
|
|
653 | + this.next = 'next'; | ||
|
|
654 | + /** | ||
|
|
655 | + * Applied to any action that would violate any restriction options. ALso applied | ||
|
|
656 | + * to an input field if the disabled function is called. | ||
|
|
657 | + */ | ||
|
|
658 | + this.disabled = 'disabled'; | ||
|
|
659 | + /** | ||
|
|
660 | + * Applied to any date that is less than requested view, | ||
|
|
661 | + * e.g. the last day of the previous month. | ||
|
|
662 | + */ | ||
|
|
663 | + this.old = 'old'; | ||
|
|
664 | + /** | ||
|
|
665 | + * Applied to any date that is greater than of requested view, | ||
|
|
666 | + * e.g. the last day of the previous month. | ||
|
|
667 | + */ | ||
|
|
668 | + this.new = 'new'; | ||
|
|
669 | + /** | ||
|
|
670 | + * Applied to any date that is currently selected. | ||
|
|
671 | + */ | ||
|
|
672 | + this.active = 'active'; | ||
|
|
673 | + //#region date element | ||
|
|
674 | + /** | ||
|
|
675 | + * The outer element for the calendar view. | ||
|
|
676 | + */ | ||
|
|
677 | + this.dateContainer = 'date-container'; | ||
|
|
678 | + /** | ||
|
|
679 | + * The outer element for the decades view. | ||
|
|
680 | + */ | ||
|
|
681 | + this.decadesContainer = `${this.dateContainer}-decades`; | ||
|
|
682 | + /** | ||
|
|
683 | + * Applied to elements within the decades container, e.g. 2020, 2030 | ||
|
|
684 | + */ | ||
|
|
685 | + this.decade = 'decade'; | ||
|
|
686 | + /** | ||
|
|
687 | + * The outer element for the years view. | ||
|
|
688 | + */ | ||
|
|
689 | + this.yearsContainer = `${this.dateContainer}-years`; | ||
|
|
690 | + /** | ||
|
|
691 | + * Applied to elements within the years container, e.g. 2021, 2021 | ||
|
|
692 | + */ | ||
|
|
693 | + this.year = 'year'; | ||
|
|
694 | + /** | ||
|
|
695 | + * The outer element for the month view. | ||
|
|
696 | + */ | ||
|
|
697 | + this.monthsContainer = `${this.dateContainer}-months`; | ||
|
|
698 | + /** | ||
|
|
699 | + * Applied to elements within the month container, e.g. January, February | ||
|
|
700 | + */ | ||
|
|
701 | + this.month = 'month'; | ||
|
|
702 | + /** | ||
|
|
703 | + * The outer element for the calendar view. | ||
|
|
704 | + */ | ||
|
|
705 | + this.daysContainer = `${this.dateContainer}-days`; | ||
|
|
706 | + /** | ||
|
|
707 | + * Applied to elements within the day container, e.g. 1, 2..31 | ||
|
|
708 | + */ | ||
|
|
709 | + this.day = 'day'; | ||
|
|
710 | + /** | ||
|
|
711 | + * If display.calendarWeeks is enabled, a column displaying the week of year | ||
|
|
712 | + * is shown. This class is applied to each cell in that column. | ||
|
|
713 | + */ | ||
|
|
714 | + this.calendarWeeks = 'cw'; | ||
|
|
715 | + /** | ||
|
|
716 | + * Applied to the first row of the calendar view, e.g. Sunday, Monday | ||
|
|
717 | + */ | ||
|
|
718 | + this.dayOfTheWeek = 'dow'; | ||
|
|
719 | + /** | ||
|
|
720 | + * Applied to the current date on the calendar view. | ||
|
|
721 | + */ | ||
|
|
722 | + this.today = 'today'; | ||
|
|
723 | + /** | ||
|
|
724 | + * Applied to the locale's weekend dates on the calendar view, e.g. Sunday, Saturday | ||
|
|
725 | + */ | ||
|
|
726 | + this.weekend = 'weekend'; | ||
|
|
727 | + //#endregion | ||
|
|
728 | + //#region time element | ||
|
|
729 | + /** | ||
|
|
730 | + * The outer element for all time related elements. | ||
|
|
731 | + */ | ||
|
|
732 | + this.timeContainer = 'time-container'; | ||
|
|
733 | + /** | ||
|
|
734 | + * Applied the separator columns between time elements, e.g. hour *:* minute *:* second | ||
|
|
735 | + */ | ||
|
|
736 | + this.separator = 'separator'; | ||
|
|
737 | + /** | ||
|
|
738 | + * The outer element for the clock view. | ||
|
|
739 | + */ | ||
|
|
740 | + this.clockContainer = `${this.timeContainer}-clock`; | ||
|
|
741 | + /** | ||
|
|
742 | + * The outer element for the hours selection view. | ||
|
|
743 | + */ | ||
|
|
744 | + this.hourContainer = `${this.timeContainer}-hour`; | ||
|
|
745 | + /** | ||
|
|
746 | + * The outer element for the minutes selection view. | ||
|
|
747 | + */ | ||
|
|
748 | + this.minuteContainer = `${this.timeContainer}-minute`; | ||
|
|
749 | + /** | ||
|
|
750 | + * The outer element for the seconds selection view. | ||
|
|
751 | + */ | ||
|
|
752 | + this.secondContainer = `${this.timeContainer}-second`; | ||
|
|
753 | + /** | ||
|
|
754 | + * Applied to each element in the hours selection view. | ||
|
|
755 | + */ | ||
|
|
756 | + this.hour = 'hour'; | ||
|
|
757 | + /** | ||
|
|
758 | + * Applied to each element in the minutes selection view. | ||
|
|
759 | + */ | ||
|
|
760 | + this.minute = 'minute'; | ||
|
|
761 | + /** | ||
|
|
762 | + * Applied to each element in the seconds selection view. | ||
|
|
763 | + */ | ||
|
|
764 | + this.second = 'second'; | ||
|
|
765 | + /** | ||
|
|
766 | + * Applied AM/PM toggle button. | ||
|
|
767 | + */ | ||
|
|
768 | + this.toggleMeridiem = 'toggleMeridiem'; | ||
|
|
769 | + //#endregion | ||
|
|
770 | + //#region collapse | ||
|
|
771 | + /** | ||
|
|
772 | + * Applied the element of the current view mode, e.g. calendar or clock. | ||
|
|
773 | + */ | ||
|
|
774 | + this.show = 'show'; | ||
|
|
775 | + /** | ||
|
|
776 | + * Applied to the currently showing view mode during a transition | ||
|
|
777 | + * between calendar and clock views | ||
|
|
778 | + */ | ||
|
|
779 | + this.collapsing = 'td-collapsing'; | ||
|
|
780 | + /** | ||
|
|
781 | + * Applied to the currently hidden view mode. | ||
|
|
782 | + */ | ||
|
|
783 | + this.collapse = 'td-collapse'; | ||
|
|
784 | + //#endregion | ||
|
|
785 | + /** | ||
|
|
786 | + * Applied to the widget when the option display.inline is enabled. | ||
|
|
787 | + */ | ||
|
|
788 | + this.inline = 'inline'; | ||
|
|
789 | + /** | ||
|
|
790 | + * Applied to the widget when the option display.theme is light. | ||
|
|
791 | + */ | ||
|
|
792 | + this.lightTheme = 'light'; | ||
|
|
793 | + /** | ||
|
|
794 | + * Applied to the widget when the option display.theme is dark. | ||
|
|
795 | + */ | ||
|
|
796 | + this.darkTheme = 'dark'; | ||
|
|
797 | + /** | ||
|
|
798 | + * Used for detecting if the system color preference is dark mode | ||
|
|
799 | + */ | ||
|
|
800 | + this.isDarkPreferredQuery = '(prefers-color-scheme: dark)'; | ||
|
|
801 | + } | ||
|
|
802 | + } | ||
|
|
803 | + class Namespace { | ||
|
|
804 | + } | ||
|
|
805 | + Namespace.NAME = NAME; | ||
|
|
806 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
807 | + Namespace.dataKey = dataKey; | ||
|
|
808 | + Namespace.events = new Events(); | ||
|
|
809 | + Namespace.css = new Css(); | ||
|
|
810 | + Namespace.errorMessages = new ErrorMessages(); | ||
|
|
811 | + | ||
|
|
812 | + class ServiceLocator { | ||
|
|
813 | + constructor() { | ||
|
|
814 | + this.cache = new Map(); | ||
|
|
815 | + } | ||
|
|
816 | + locate(identifier) { | ||
|
|
817 | + const service = this.cache.get(identifier); | ||
|
|
818 | + if (service) | ||
|
|
819 | + return service; | ||
|
|
820 | + const value = new identifier(); | ||
|
|
821 | + this.cache.set(identifier, value); | ||
|
|
822 | + return value; | ||
|
|
823 | + } | ||
|
|
824 | + } | ||
|
|
825 | + const setupServiceLocator = () => { | ||
|
|
826 | + serviceLocator = new ServiceLocator(); | ||
|
|
827 | + }; | ||
|
|
828 | + let serviceLocator; | ||
|
|
829 | + | ||
|
|
830 | + const CalendarModes = [ | ||
|
|
831 | + { | ||
|
|
832 | + name: 'calendar', | ||
|
|
833 | + className: Namespace.css.daysContainer, | ||
|
|
834 | + unit: Unit.month, | ||
|
|
835 | + step: 1, | ||
|
|
836 | + }, | ||
|
|
837 | + { | ||
|
|
838 | + name: 'months', | ||
|
|
839 | + className: Namespace.css.monthsContainer, | ||
|
|
840 | + unit: Unit.year, | ||
|
|
841 | + step: 1, | ||
|
|
842 | + }, | ||
|
|
843 | + { | ||
|
|
844 | + name: 'years', | ||
|
|
845 | + className: Namespace.css.yearsContainer, | ||
|
|
846 | + unit: Unit.year, | ||
|
|
847 | + step: 10, | ||
|
|
848 | + }, | ||
|
|
849 | + { | ||
|
|
850 | + name: 'decades', | ||
|
|
851 | + className: Namespace.css.decadesContainer, | ||
|
|
852 | + unit: Unit.year, | ||
|
|
853 | + step: 100, | ||
|
|
854 | + }, | ||
|
|
855 | + ]; | ||
|
|
856 | + | ||
|
|
857 | + class OptionsStore { | ||
|
|
858 | + constructor() { | ||
|
|
859 | + this.viewDate = new DateTime(); | ||
|
|
860 | + this._currentCalendarViewMode = 0; | ||
|
|
861 | + this.minimumCalendarViewMode = 0; | ||
|
|
862 | + this.currentView = 'calendar'; | ||
|
|
863 | + } | ||
|
|
864 | + get currentCalendarViewMode() { | ||
|
|
865 | + return this._currentCalendarViewMode; | ||
|
|
866 | + } | ||
|
|
867 | + set currentCalendarViewMode(value) { | ||
|
|
868 | + this._currentCalendarViewMode = value; | ||
|
|
869 | + this.currentView = CalendarModes[value].name; | ||
|
|
870 | + } | ||
|
|
871 | + /** | ||
|
|
872 | + * When switching back to the calendar from the clock, | ||
|
|
873 | + * this sets currentView to the correct calendar view. | ||
|
|
874 | + */ | ||
|
|
875 | + refreshCurrentView() { | ||
|
|
876 | + this.currentView = CalendarModes[this.currentCalendarViewMode].name; | ||
|
|
877 | + } | ||
|
|
878 | + } | ||
|
|
879 | + | ||
|
|
880 | + /** | ||
|
|
881 | + * Main class for date validation rules based on the options provided. | ||
|
|
882 | + */ | ||
|
|
883 | + class Validation { | ||
|
|
884 | + constructor() { | ||
|
|
885 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
886 | + } | ||
|
|
887 | + /** | ||
|
|
888 | + * Checks to see if the target date is valid based on the rules provided in the options. | ||
|
|
889 | + * Granularity can be provided to check portions of the date instead of the whole. | ||
|
|
890 | + * @param targetDate | ||
|
|
891 | + * @param granularity | ||
|
|
892 | + */ | ||
|
|
893 | + isValid(targetDate, granularity) { | ||
|
|
894 | + if (this.optionsStore.options.restrictions.disabledDates.length > 0 && | ||
|
|
895 | + this._isInDisabledDates(targetDate)) { | ||
|
|
896 | + return false; | ||
|
|
897 | + } | ||
|
|
898 | + if (this.optionsStore.options.restrictions.enabledDates.length > 0 && | ||
|
|
899 | + !this._isInEnabledDates(targetDate)) { | ||
|
|
900 | + return false; | ||
|
|
901 | + } | ||
|
|
902 | + if (granularity !== Unit.month && | ||
|
|
903 | + granularity !== Unit.year && | ||
|
|
904 | + this.optionsStore.options.restrictions.daysOfWeekDisabled?.length > 0 && | ||
|
|
905 | + this.optionsStore.options.restrictions.daysOfWeekDisabled.indexOf(targetDate.weekDay) !== -1) { | ||
|
|
906 | + return false; | ||
|
|
907 | + } | ||
|
|
908 | + if (this.optionsStore.options.restrictions.minDate && | ||
|
|
909 | + targetDate.isBefore(this.optionsStore.options.restrictions.minDate, granularity)) { | ||
|
|
910 | + return false; | ||
|
|
911 | + } | ||
|
|
912 | + if (this.optionsStore.options.restrictions.maxDate && | ||
|
|
913 | + targetDate.isAfter(this.optionsStore.options.restrictions.maxDate, granularity)) { | ||
|
|
914 | + return false; | ||
|
|
915 | + } | ||
|
|
916 | + if (granularity === Unit.hours || | ||
|
|
917 | + granularity === Unit.minutes || | ||
|
|
918 | + granularity === Unit.seconds) { | ||
|
|
919 | + if (this.optionsStore.options.restrictions.disabledHours.length > 0 && | ||
|
|
920 | + this._isInDisabledHours(targetDate)) { | ||
|
|
921 | + return false; | ||
|
|
922 | + } | ||
|
|
923 | + if (this.optionsStore.options.restrictions.enabledHours.length > 0 && | ||
|
|
924 | + !this._isInEnabledHours(targetDate)) { | ||
|
|
925 | + return false; | ||
|
|
926 | + } | ||
|
|
927 | + if (this.optionsStore.options.restrictions.disabledTimeIntervals.length > 0) { | ||
|
|
928 | + for (let disabledTimeIntervals of this.optionsStore.options.restrictions.disabledTimeIntervals) { | ||
|
|
929 | + if (targetDate.isBetween(disabledTimeIntervals.from, disabledTimeIntervals.to)) | ||
|
|
930 | + return false; | ||
|
|
931 | + } | ||
|
|
932 | + } | ||
|
|
933 | + } | ||
|
|
934 | + return true; | ||
|
|
935 | + } | ||
|
|
936 | + /** | ||
|
|
937 | + * Checks to see if the disabledDates option is in use and returns true (meaning invalid) | ||
|
|
938 | + * if the `testDate` is with in the array. Granularity is by date. | ||
|
|
939 | + * @param testDate | ||
|
|
940 | + * @private | ||
|
|
941 | + */ | ||
|
|
942 | + _isInDisabledDates(testDate) { | ||
|
|
943 | + if (!this.optionsStore.options.restrictions.disabledDates || | ||
|
|
944 | + this.optionsStore.options.restrictions.disabledDates.length === 0) | ||
|
|
945 | + return false; | ||
|
|
946 | + const formattedDate = testDate.format(getFormatByUnit(Unit.date)); | ||
|
|
947 | + return this.optionsStore.options.restrictions.disabledDates | ||
|
|
948 | + .map((x) => x.format(getFormatByUnit(Unit.date))) | ||
|
|
949 | + .find((x) => x === formattedDate); | ||
|
|
950 | + } | ||
|
|
951 | + /** | ||
|
|
952 | + * Checks to see if the enabledDates option is in use and returns true (meaning valid) | ||
|
|
953 | + * if the `testDate` is with in the array. Granularity is by date. | ||
|
|
954 | + * @param testDate | ||
|
|
955 | + * @private | ||
|
|
956 | + */ | ||
|
|
957 | + _isInEnabledDates(testDate) { | ||
|
|
958 | + if (!this.optionsStore.options.restrictions.enabledDates || | ||
|
|
959 | + this.optionsStore.options.restrictions.enabledDates.length === 0) | ||
|
|
960 | + return true; | ||
|
|
961 | + const formattedDate = testDate.format(getFormatByUnit(Unit.date)); | ||
|
|
962 | + return this.optionsStore.options.restrictions.enabledDates | ||
|
|
963 | + .map((x) => x.format(getFormatByUnit(Unit.date))) | ||
|
|
964 | + .find((x) => x === formattedDate); | ||
|
|
965 | + } | ||
|
|
966 | + /** | ||
|
|
967 | + * Checks to see if the disabledHours option is in use and returns true (meaning invalid) | ||
|
|
968 | + * if the `testDate` is with in the array. Granularity is by hours. | ||
|
|
969 | + * @param testDate | ||
|
|
970 | + * @private | ||
|
|
971 | + */ | ||
|
|
972 | + _isInDisabledHours(testDate) { | ||
|
|
973 | + if (!this.optionsStore.options.restrictions.disabledHours || | ||
|
|
974 | + this.optionsStore.options.restrictions.disabledHours.length === 0) | ||
|
|
975 | + return false; | ||
|
|
976 | + const formattedDate = testDate.hours; | ||
|
|
977 | + return this.optionsStore.options.restrictions.disabledHours.find((x) => x === formattedDate); | ||
|
|
978 | + } | ||
|
|
979 | + /** | ||
|
|
980 | + * Checks to see if the enabledHours option is in use and returns true (meaning valid) | ||
|
|
981 | + * if the `testDate` is with in the array. Granularity is by hours. | ||
|
|
982 | + * @param testDate | ||
|
|
983 | + * @private | ||
|
|
984 | + */ | ||
|
|
985 | + _isInEnabledHours(testDate) { | ||
|
|
986 | + if (!this.optionsStore.options.restrictions.enabledHours || | ||
|
|
987 | + this.optionsStore.options.restrictions.enabledHours.length === 0) | ||
|
|
988 | + return true; | ||
|
|
989 | + const formattedDate = testDate.hours; | ||
|
|
990 | + return this.optionsStore.options.restrictions.enabledHours.find((x) => x === formattedDate); | ||
|
|
991 | + } | ||
|
|
992 | + } | ||
|
|
993 | + | ||
|
|
994 | + class EventEmitter { | ||
|
|
995 | + constructor() { | ||
|
|
996 | + this.subscribers = []; | ||
|
|
997 | + } | ||
|
|
998 | + subscribe(callback) { | ||
|
|
999 | + this.subscribers.push(callback); | ||
|
|
1000 | + return this.unsubscribe.bind(this, this.subscribers.length - 1); | ||
|
|
1001 | + } | ||
|
|
1002 | + unsubscribe(index) { | ||
|
|
1003 | + this.subscribers.splice(index, 1); | ||
|
|
1004 | + } | ||
|
|
1005 | + emit(value) { | ||
|
|
1006 | + this.subscribers.forEach((callback) => { | ||
|
|
1007 | + callback(value); | ||
|
|
1008 | + }); | ||
|
|
1009 | + } | ||
|
|
1010 | + destroy() { | ||
|
|
1011 | + this.subscribers = null; | ||
|
|
1012 | + this.subscribers = []; | ||
|
|
1013 | + } | ||
|
|
1014 | + } | ||
|
|
1015 | + class EventEmitters { | ||
|
|
1016 | + constructor() { | ||
|
|
1017 | + this.triggerEvent = new EventEmitter(); | ||
|
|
1018 | + this.viewUpdate = new EventEmitter(); | ||
|
|
1019 | + this.updateDisplay = new EventEmitter(); | ||
|
|
1020 | + this.action = new EventEmitter(); | ||
|
|
1021 | + } | ||
|
|
1022 | + destroy() { | ||
|
|
1023 | + this.triggerEvent.destroy(); | ||
|
|
1024 | + this.viewUpdate.destroy(); | ||
|
|
1025 | + this.updateDisplay.destroy(); | ||
|
|
1026 | + this.action.destroy(); | ||
|
|
1027 | + } | ||
|
|
1028 | + } | ||
|
|
1029 | + | ||
|
|
1030 | + const DefaultOptions = { | ||
|
|
1031 | + restrictions: { | ||
|
|
1032 | + minDate: undefined, | ||
|
|
1033 | + maxDate: undefined, | ||
|
|
1034 | + disabledDates: [], | ||
|
|
1035 | + enabledDates: [], | ||
|
|
1036 | + daysOfWeekDisabled: [], | ||
|
|
1037 | + disabledTimeIntervals: [], | ||
|
|
1038 | + disabledHours: [], | ||
|
|
1039 | + enabledHours: [] | ||
|
|
1040 | + }, | ||
|
|
1041 | + display: { | ||
|
|
1042 | + icons: { | ||
|
|
1043 | + type: 'icons', | ||
|
|
1044 | + time: 'fa-solid fa-clock', | ||
|
|
1045 | + date: 'fa-solid fa-calendar', | ||
|
|
1046 | + up: 'fa-solid fa-arrow-up', | ||
|
|
1047 | + down: 'fa-solid fa-arrow-down', | ||
|
|
1048 | + previous: 'fa-solid fa-chevron-left', | ||
|
|
1049 | + next: 'fa-solid fa-chevron-right', | ||
|
|
1050 | + today: 'fa-solid fa-calendar-check', | ||
|
|
1051 | + clear: 'fa-solid fa-trash', | ||
|
|
1052 | + close: 'fa-solid fa-xmark' | ||
|
|
1053 | + }, | ||
|
|
1054 | + sideBySide: false, | ||
|
|
1055 | + calendarWeeks: false, | ||
|
|
1056 | + viewMode: 'calendar', | ||
|
|
1057 | + toolbarPlacement: 'bottom', | ||
|
|
1058 | + keepOpen: false, | ||
|
|
1059 | + buttons: { | ||
|
|
1060 | + today: false, | ||
|
|
1061 | + clear: false, | ||
|
|
1062 | + close: false | ||
|
|
1063 | + }, | ||
|
|
1064 | + components: { | ||
|
|
1065 | + calendar: true, | ||
|
|
1066 | + date: true, | ||
|
|
1067 | + month: true, | ||
|
|
1068 | + year: true, | ||
|
|
1069 | + decades: true, | ||
|
|
1070 | + clock: true, | ||
|
|
1071 | + hours: true, | ||
|
|
1072 | + minutes: true, | ||
|
|
1073 | + seconds: false, | ||
|
|
1074 | + useTwentyfourHour: undefined | ||
|
|
1075 | + }, | ||
|
|
1076 | + inline: false, | ||
|
|
1077 | + theme: 'auto' | ||
|
|
1078 | + }, | ||
|
|
1079 | + stepping: 1, | ||
|
|
1080 | + useCurrent: true, | ||
|
|
1081 | + defaultDate: undefined, | ||
|
|
1082 | + localization: { | ||
|
|
1083 | + today: 'Go to today', | ||
|
|
1084 | + clear: 'Clear selection', | ||
|
|
1085 | + close: 'Close the picker', | ||
|
|
1086 | + selectMonth: 'Select Month', | ||
|
|
1087 | + previousMonth: 'Previous Month', | ||
|
|
1088 | + nextMonth: 'Next Month', | ||
|
|
1089 | + selectYear: 'Select Year', | ||
|
|
1090 | + previousYear: 'Previous Year', | ||
|
|
1091 | + nextYear: 'Next Year', | ||
|
|
1092 | + selectDecade: 'Select Decade', | ||
|
|
1093 | + previousDecade: 'Previous Decade', | ||
|
|
1094 | + nextDecade: 'Next Decade', | ||
|
|
1095 | + previousCentury: 'Previous Century', | ||
|
|
1096 | + nextCentury: 'Next Century', | ||
|
|
1097 | + pickHour: 'Pick Hour', | ||
|
|
1098 | + incrementHour: 'Increment Hour', | ||
|
|
1099 | + decrementHour: 'Decrement Hour', | ||
|
|
1100 | + pickMinute: 'Pick Minute', | ||
|
|
1101 | + incrementMinute: 'Increment Minute', | ||
|
|
1102 | + decrementMinute: 'Decrement Minute', | ||
|
|
1103 | + pickSecond: 'Pick Second', | ||
|
|
1104 | + incrementSecond: 'Increment Second', | ||
|
|
1105 | + decrementSecond: 'Decrement Second', | ||
|
|
1106 | + toggleMeridiem: 'Toggle Meridiem', | ||
|
|
1107 | + selectTime: 'Select Time', | ||
|
|
1108 | + selectDate: 'Select Date', | ||
|
|
1109 | + dayViewHeaderFormat: { month: 'long', year: '2-digit' }, | ||
|
|
1110 | + locale: 'default', | ||
|
|
1111 | + startOfTheWeek: 0, | ||
|
|
1112 | + /** | ||
|
|
1113 | + * This is only used with the customDateFormat plugin | ||
|
|
1114 | + */ | ||
|
|
1115 | + dateFormats: { | ||
|
|
1116 | + LTS: 'h:mm:ss T', | ||
|
|
1117 | + LT: 'h:mm T', | ||
|
|
1118 | + L: 'MM/dd/yyyy', | ||
|
|
1119 | + LL: 'MMMM d, yyyy', | ||
|
|
1120 | + LLL: 'MMMM d, yyyy h:mm T', | ||
|
|
1121 | + LLLL: 'dddd, MMMM d, yyyy h:mm T', | ||
|
|
1122 | + }, | ||
|
|
1123 | + /** | ||
|
|
1124 | + * This is only used with the customDateFormat plugin | ||
|
|
1125 | + */ | ||
|
|
1126 | + ordinal: (n) => n, | ||
|
|
1127 | + /** | ||
|
|
1128 | + * This is only used with the customDateFormat plugin | ||
|
|
1129 | + */ | ||
|
|
1130 | + format: 'L LT' | ||
|
|
1131 | + }, | ||
|
|
1132 | + keepInvalid: false, | ||
|
|
1133 | + debug: false, | ||
|
|
1134 | + allowInputToggle: false, | ||
|
|
1135 | + viewDate: new DateTime(), | ||
|
|
1136 | + multipleDates: false, | ||
|
|
1137 | + multipleDatesSeparator: '; ', | ||
|
|
1138 | + promptTimeOnDateChange: false, | ||
|
|
1139 | + promptTimeOnDateChangeTransitionDelay: 200, | ||
|
|
1140 | + meta: {}, | ||
|
|
1141 | + container: undefined | ||
|
|
1142 | + }; | ||
|
|
1143 | + | ||
|
|
1144 | + class OptionConverter { | ||
|
|
1145 | + static deepCopy(input) { | ||
|
|
1146 | + const o = {}; | ||
|
|
1147 | + Object.keys(input).forEach((key) => { | ||
|
|
1148 | + const inputElement = input[key]; | ||
|
|
1149 | + o[key] = inputElement; | ||
|
|
1150 | + if (typeof inputElement !== 'object' || | ||
|
|
1151 | + inputElement instanceof HTMLElement || | ||
|
|
1152 | + inputElement instanceof Element || | ||
|
|
1153 | + inputElement instanceof Date) | ||
|
|
1154 | + return; | ||
|
|
1155 | + if (!Array.isArray(inputElement)) { | ||
|
|
1156 | + o[key] = OptionConverter.deepCopy(inputElement); | ||
|
|
1157 | + } | ||
|
|
1158 | + }); | ||
|
|
1159 | + return o; | ||
|
|
1160 | + } | ||
|
|
1161 | + /** | ||
|
|
1162 | + * Finds value out of an object based on a string, period delimited, path | ||
|
|
1163 | + * @param paths | ||
|
|
1164 | + * @param obj | ||
|
|
1165 | + */ | ||
|
|
1166 | + static objectPath(paths, obj) { | ||
|
|
1167 | + if (paths.charAt(0) === '.') | ||
|
|
1168 | + paths = paths.slice(1); | ||
|
|
1169 | + if (!paths) | ||
|
|
1170 | + return obj; | ||
|
|
1171 | + return paths.split('.') | ||
|
|
1172 | + .reduce((value, key) => (OptionConverter.isValue(value) || OptionConverter.isValue(value[key]) ? | ||
|
|
1173 | + value[key] : | ||
|
|
1174 | + undefined), obj); | ||
|
|
1175 | + } | ||
|
|
1176 | + /** | ||
|
|
1177 | + * The spread operator caused sub keys to be missing after merging. | ||
|
|
1178 | + * This is to fix that issue by using spread on the child objects first. | ||
|
|
1179 | + * Also handles complex options like disabledDates | ||
|
|
1180 | + * @param provided An option from new providedOptions | ||
|
|
1181 | + * @param copyTo Destination object. This was added to prevent reference copies | ||
|
|
1182 | + * @param path | ||
|
|
1183 | + * @param localization | ||
|
|
1184 | + */ | ||
|
|
1185 | + static spread(provided, copyTo, path = '', localization) { | ||
|
|
1186 | + const defaultOptions = OptionConverter.objectPath(path, DefaultOptions); | ||
|
|
1187 | + const unsupportedOptions = Object.keys(provided).filter((x) => !Object.keys(defaultOptions).includes(x)); | ||
|
|
1188 | + if (unsupportedOptions.length > 0) { | ||
|
|
1189 | + const flattenedOptions = OptionConverter.getFlattenDefaultOptions(); | ||
|
|
1190 | + const errors = unsupportedOptions.map((x) => { | ||
|
|
1191 | + let error = `"${path}.${x}" in not a known option.`; | ||
|
|
1192 | + let didYouMean = flattenedOptions.find((y) => y.includes(x)); | ||
|
|
1193 | + if (didYouMean) | ||
|
|
1194 | + error += ` Did you mean "${didYouMean}"?`; | ||
|
|
1195 | + return error; | ||
|
|
1196 | + }); | ||
|
|
1197 | + Namespace.errorMessages.unexpectedOptions(errors); | ||
|
|
1198 | + } | ||
|
|
1199 | + Object.keys(provided).filter(key => key !== '__proto__' && key !== 'constructor').forEach((key) => { | ||
|
|
1200 | + path += `.${key}`; | ||
|
|
1201 | + if (path.charAt(0) === '.') | ||
|
|
1202 | + path = path.slice(1); | ||
|
|
1203 | + const defaultOptionValue = defaultOptions[key]; | ||
|
|
1204 | + let providedType = typeof provided[key]; | ||
|
|
1205 | + let defaultType = typeof defaultOptionValue; | ||
|
|
1206 | + let value = provided[key]; | ||
|
|
1207 | + if (value === undefined || value === null) { | ||
|
|
1208 | + copyTo[key] = value; | ||
|
|
1209 | + path = path.substring(0, path.lastIndexOf(`.${key}`)); | ||
|
|
1210 | + return; | ||
|
|
1211 | + } | ||
|
|
1212 | + if (typeof defaultOptionValue === 'object' && | ||
|
|
1213 | + !Array.isArray(provided[key]) && | ||
|
|
1214 | + !(defaultOptionValue instanceof Date || OptionConverter.ignoreProperties.includes(key))) { | ||
|
|
1215 | + OptionConverter.spread(provided[key], copyTo[key], path, localization); | ||
|
|
1216 | + } | ||
|
|
1217 | + else { | ||
|
|
1218 | + copyTo[key] = OptionConverter.processKey(key, value, providedType, defaultType, path, localization); | ||
|
|
1219 | + } | ||
|
|
1220 | + path = path.substring(0, path.lastIndexOf(`.${key}`)); | ||
|
|
1221 | + }); | ||
|
|
1222 | + } | ||
|
|
1223 | + static processKey(key, value, providedType, defaultType, path, localization) { | ||
|
|
1224 | + switch (key) { | ||
|
|
1225 | + case 'defaultDate': { | ||
|
|
1226 | + const dateTime = this.dateConversion(value, 'defaultDate', localization); | ||
|
|
1227 | + if (dateTime !== undefined) { | ||
|
|
1228 | + dateTime.setLocale(localization.locale); | ||
|
|
1229 | + return dateTime; | ||
|
|
1230 | + } | ||
|
|
1231 | + Namespace.errorMessages.typeMismatch('defaultDate', providedType, 'DateTime or Date'); | ||
|
|
1232 | + break; | ||
|
|
1233 | + } | ||
|
|
1234 | + case 'viewDate': { | ||
|
|
1235 | + const dateTime = this.dateConversion(value, 'viewDate', localization); | ||
|
|
1236 | + if (dateTime !== undefined) { | ||
|
|
1237 | + dateTime.setLocale(localization.locale); | ||
|
|
1238 | + return dateTime; | ||
|
|
1239 | + } | ||
|
|
1240 | + Namespace.errorMessages.typeMismatch('viewDate', providedType, 'DateTime or Date'); | ||
|
|
1241 | + break; | ||
|
|
1242 | + } | ||
|
|
1243 | + case 'minDate': { | ||
|
|
1244 | + if (value === undefined) { | ||
|
|
1245 | + return value; | ||
|
|
1246 | + } | ||
|
|
1247 | + const dateTime = this.dateConversion(value, 'restrictions.minDate', localization); | ||
|
|
1248 | + if (dateTime !== undefined) { | ||
|
|
1249 | + dateTime.setLocale(localization.locale); | ||
|
|
1250 | + return dateTime; | ||
|
|
1251 | + } | ||
|
|
1252 | + Namespace.errorMessages.typeMismatch('restrictions.minDate', providedType, 'DateTime or Date'); | ||
|
|
1253 | + break; | ||
|
|
1254 | + } | ||
|
|
1255 | + case 'maxDate': { | ||
|
|
1256 | + if (value === undefined) { | ||
|
|
1257 | + return value; | ||
|
|
1258 | + } | ||
|
|
1259 | + const dateTime = this.dateConversion(value, 'restrictions.maxDate', localization); | ||
|
|
1260 | + if (dateTime !== undefined) { | ||
|
|
1261 | + dateTime.setLocale(localization.locale); | ||
|
|
1262 | + return dateTime; | ||
|
|
1263 | + } | ||
|
|
1264 | + Namespace.errorMessages.typeMismatch('restrictions.maxDate', providedType, 'DateTime or Date'); | ||
|
|
1265 | + break; | ||
|
|
1266 | + } | ||
|
|
1267 | + case 'disabledHours': | ||
|
|
1268 | + if (value === undefined) { | ||
|
|
1269 | + return []; | ||
|
|
1270 | + } | ||
|
|
1271 | + this._typeCheckNumberArray('restrictions.disabledHours', value, providedType); | ||
|
|
1272 | + if (value.filter((x) => x < 0 || x > 24).length > 0) | ||
|
|
1273 | + Namespace.errorMessages.numbersOutOfRange('restrictions.disabledHours', 0, 23); | ||
|
|
1274 | + return value; | ||
|
|
1275 | + case 'enabledHours': | ||
|
|
1276 | + if (value === undefined) { | ||
|
|
1277 | + return []; | ||
|
|
1278 | + } | ||
|
|
1279 | + this._typeCheckNumberArray('restrictions.enabledHours', value, providedType); | ||
|
|
1280 | + if (value.filter((x) => x < 0 || x > 24).length > 0) | ||
|
|
1281 | + Namespace.errorMessages.numbersOutOfRange('restrictions.enabledHours', 0, 23); | ||
|
|
1282 | + return value; | ||
|
|
1283 | + case 'daysOfWeekDisabled': | ||
|
|
1284 | + if (value === undefined) { | ||
|
|
1285 | + return []; | ||
|
|
1286 | + } | ||
|
|
1287 | + this._typeCheckNumberArray('restrictions.daysOfWeekDisabled', value, providedType); | ||
|
|
1288 | + if (value.filter((x) => x < 0 || x > 6).length > 0) | ||
|
|
1289 | + Namespace.errorMessages.numbersOutOfRange('restrictions.daysOfWeekDisabled', 0, 6); | ||
|
|
1290 | + return value; | ||
|
|
1291 | + case 'enabledDates': | ||
|
|
1292 | + if (value === undefined) { | ||
|
|
1293 | + return []; | ||
|
|
1294 | + } | ||
|
|
1295 | + this._typeCheckDateArray('restrictions.enabledDates', value, providedType, localization); | ||
|
|
1296 | + return value; | ||
|
|
1297 | + case 'disabledDates': | ||
|
|
1298 | + if (value === undefined) { | ||
|
|
1299 | + return []; | ||
|
|
1300 | + } | ||
|
|
1301 | + this._typeCheckDateArray('restrictions.disabledDates', value, providedType, localization); | ||
|
|
1302 | + return value; | ||
|
|
1303 | + case 'disabledTimeIntervals': | ||
|
|
1304 | + if (value === undefined) { | ||
|
|
1305 | + return []; | ||
|
|
1306 | + } | ||
|
|
1307 | + if (!Array.isArray(value)) { | ||
|
|
1308 | + Namespace.errorMessages.typeMismatch(key, providedType, 'array of { from: DateTime|Date, to: DateTime|Date }'); | ||
|
|
1309 | + } | ||
|
|
1310 | + const valueObject = value; | ||
|
|
1311 | + for (let i = 0; i < valueObject.length; i++) { | ||
|
|
1312 | + Object.keys(valueObject[i]).forEach((vk) => { | ||
|
|
1313 | + const subOptionName = `${key}[${i}].${vk}`; | ||
|
|
1314 | + let d = valueObject[i][vk]; | ||
|
|
1315 | + const dateTime = this.dateConversion(d, subOptionName, localization); | ||
|
|
1316 | + if (!dateTime) { | ||
|
|
1317 | + Namespace.errorMessages.typeMismatch(subOptionName, typeof d, 'DateTime or Date'); | ||
|
|
1318 | + } | ||
|
|
1319 | + dateTime.setLocale(localization.locale); | ||
|
|
1320 | + valueObject[i][vk] = dateTime; | ||
|
|
1321 | + }); | ||
|
|
1322 | + } | ||
|
|
1323 | + return valueObject; | ||
|
|
1324 | + case 'toolbarPlacement': | ||
|
|
1325 | + case 'type': | ||
|
|
1326 | + case 'viewMode': | ||
|
|
1327 | + case 'theme': | ||
|
|
1328 | + const optionValues = { | ||
|
|
1329 | + toolbarPlacement: ['top', 'bottom', 'default'], | ||
|
|
1330 | + type: ['icons', 'sprites'], | ||
|
|
1331 | + viewMode: ['clock', 'calendar', 'months', 'years', 'decades'], | ||
|
|
1332 | + theme: ['light', 'dark', 'auto'] | ||
|
|
1333 | + }; | ||
|
|
1334 | + const keyOptions = optionValues[key]; | ||
|
|
1335 | + if (!keyOptions.includes(value)) | ||
|
|
1336 | + Namespace.errorMessages.unexpectedOptionValue(path.substring(1), value, keyOptions); | ||
|
|
1337 | + return value; | ||
|
|
1338 | + case 'meta': | ||
|
|
1339 | + case 'dayViewHeaderFormat': | ||
|
|
1340 | + return value; | ||
|
|
1341 | + case 'container': | ||
|
|
1342 | + if (value && | ||
|
|
1343 | + !(value instanceof HTMLElement || | ||
|
|
1344 | + value instanceof Element || | ||
|
|
1345 | + value?.appendChild)) { | ||
|
|
1346 | + Namespace.errorMessages.typeMismatch(path.substring(1), typeof value, 'HTMLElement'); | ||
|
|
1347 | + } | ||
|
|
1348 | + return value; | ||
|
|
1349 | + case 'useTwentyfourHour': | ||
|
|
1350 | + if (value === undefined || providedType === 'boolean') | ||
|
|
1351 | + return value; | ||
|
|
1352 | + Namespace.errorMessages.typeMismatch(path, providedType, defaultType); | ||
|
|
1353 | + break; | ||
|
|
1354 | + default: | ||
|
|
1355 | + switch (defaultType) { | ||
|
|
1356 | + case 'boolean': | ||
|
|
1357 | + return value === 'true' || value === true; | ||
|
|
1358 | + case 'number': | ||
|
|
1359 | + return +value; | ||
|
|
1360 | + case 'string': | ||
|
|
1361 | + return value.toString(); | ||
|
|
1362 | + case 'object': | ||
|
|
1363 | + return {}; | ||
|
|
1364 | + case 'function': | ||
|
|
1365 | + return value; | ||
|
|
1366 | + default: | ||
|
|
1367 | + Namespace.errorMessages.typeMismatch(path, providedType, defaultType); | ||
|
|
1368 | + } | ||
|
|
1369 | + } | ||
|
|
1370 | + } | ||
|
|
1371 | + static _mergeOptions(providedOptions, mergeTo) { | ||
|
|
1372 | + const newConfig = OptionConverter.deepCopy(mergeTo); | ||
|
|
1373 | + //see if the options specify a locale | ||
|
|
1374 | + const localization = mergeTo.localization?.locale !== 'default' | ||
|
|
1375 | + ? mergeTo.localization | ||
|
|
1376 | + : providedOptions?.localization || DefaultOptions.localization; | ||
|
|
1377 | + OptionConverter.spread(providedOptions, newConfig, '', localization); | ||
|
|
1378 | + return newConfig; | ||
|
|
1379 | + } | ||
|
|
1380 | + static _dataToOptions(element, options) { | ||
|
|
1381 | + const eData = JSON.parse(JSON.stringify(element.dataset)); | ||
|
|
1382 | + if (eData?.tdTargetInput) | ||
|
|
1383 | + delete eData.tdTargetInput; | ||
|
|
1384 | + if (eData?.tdTargetToggle) | ||
|
|
1385 | + delete eData.tdTargetToggle; | ||
|
|
1386 | + if (!eData || | ||
|
|
1387 | + Object.keys(eData).length === 0 || | ||
|
|
1388 | + eData.constructor !== DOMStringMap) | ||
|
|
1389 | + return options; | ||
|
|
1390 | + let dataOptions = {}; | ||
|
|
1391 | + // because dataset returns camelCase including the 'td' key the option | ||
|
|
1392 | + // key won't align | ||
|
|
1393 | + const objectToNormalized = (object) => { | ||
|
|
1394 | + const lowered = {}; | ||
|
|
1395 | + Object.keys(object).forEach((x) => { | ||
|
|
1396 | + lowered[x.toLowerCase()] = x; | ||
|
|
1397 | + }); | ||
|
|
1398 | + return lowered; | ||
|
|
1399 | + }; | ||
|
|
1400 | + const rabbitHole = (split, index, optionSubgroup, value) => { | ||
|
|
1401 | + // first round = display { ... } | ||
|
|
1402 | + const normalizedOptions = objectToNormalized(optionSubgroup); | ||
|
|
1403 | + const keyOption = normalizedOptions[split[index].toLowerCase()]; | ||
|
|
1404 | + const internalObject = {}; | ||
|
|
1405 | + if (keyOption === undefined) | ||
|
|
1406 | + return internalObject; | ||
|
|
1407 | + // if this is another object, continue down the rabbit hole | ||
|
|
1408 | + if (optionSubgroup[keyOption].constructor === Object) { | ||
|
|
1409 | + index++; | ||
|
|
1410 | + internalObject[keyOption] = rabbitHole(split, index, optionSubgroup[keyOption], value); | ||
|
|
1411 | + } | ||
|
|
1412 | + else { | ||
|
|
1413 | + internalObject[keyOption] = value; | ||
|
|
1414 | + } | ||
|
|
1415 | + return internalObject; | ||
|
|
1416 | + }; | ||
|
|
1417 | + const optionsLower = objectToNormalized(options); | ||
|
|
1418 | + Object.keys(eData) | ||
|
|
1419 | + .filter((x) => x.startsWith(Namespace.dataKey)) | ||
|
|
1420 | + .map((x) => x.substring(2)) | ||
|
|
1421 | + .forEach((key) => { | ||
|
|
1422 | + let keyOption = optionsLower[key.toLowerCase()]; | ||
|
|
1423 | + // dataset merges dashes to camelCase... yay | ||
|
|
1424 | + // i.e. key = display_components_seconds | ||
|
|
1425 | + if (key.includes('_')) { | ||
|
|
1426 | + // [display, components, seconds] | ||
|
|
1427 | + const split = key.split('_'); | ||
|
|
1428 | + // display | ||
|
|
1429 | + keyOption = optionsLower[split[0].toLowerCase()]; | ||
|
|
1430 | + if (keyOption !== undefined && | ||
|
|
1431 | + options[keyOption].constructor === Object) { | ||
|
|
1432 | + dataOptions[keyOption] = rabbitHole(split, 1, options[keyOption], eData[`td${key}`]); | ||
|
|
1433 | + } | ||
|
|
1434 | + } | ||
|
|
1435 | + // or key = multipleDate | ||
|
|
1436 | + else if (keyOption !== undefined) { | ||
|
|
1437 | + dataOptions[keyOption] = eData[`td${key}`]; | ||
|
|
1438 | + } | ||
|
|
1439 | + }); | ||
|
|
1440 | + return this._mergeOptions(dataOptions, options); | ||
|
|
1441 | + } | ||
|
|
1442 | + /** | ||
|
|
1443 | + * Attempts to prove `d` is a DateTime or Date or can be converted into one. | ||
|
|
1444 | + * @param d If a string will attempt creating a date from it. | ||
|
|
1445 | + * @param localization object containing locale and format settings. Only used with the custom formats | ||
|
|
1446 | + * @private | ||
|
|
1447 | + */ | ||
|
|
1448 | + static _dateTypeCheck(d, localization) { | ||
|
|
1449 | + if (d.constructor.name === DateTime.name) | ||
|
|
1450 | + return d; | ||
|
|
1451 | + if (d.constructor.name === Date.name) { | ||
|
|
1452 | + return DateTime.convert(d); | ||
|
|
1453 | + } | ||
|
|
1454 | + if (typeof d === typeof '') { | ||
|
|
1455 | + const dateTime = DateTime.fromString(d, localization); | ||
|
|
1456 | + if (JSON.stringify(dateTime) === 'null') { | ||
|
|
1457 | + return null; | ||
|
|
1458 | + } | ||
|
|
1459 | + return dateTime; | ||
|
|
1460 | + } | ||
|
|
1461 | + return null; | ||
|
|
1462 | + } | ||
|
|
1463 | + /** | ||
|
|
1464 | + * Type checks that `value` is an array of Date or DateTime | ||
|
|
1465 | + * @param optionName Provides text to error messages e.g. disabledDates | ||
|
|
1466 | + * @param value Option value | ||
|
|
1467 | + * @param providedType Used to provide text to error messages | ||
|
|
1468 | + * @param localization | ||
|
|
1469 | + */ | ||
|
|
1470 | + static _typeCheckDateArray(optionName, value, providedType, localization) { | ||
|
|
1471 | + if (!Array.isArray(value)) { | ||
|
|
1472 | + Namespace.errorMessages.typeMismatch(optionName, providedType, 'array of DateTime or Date'); | ||
|
|
1473 | + } | ||
|
|
1474 | + for (let i = 0; i < value.length; i++) { | ||
|
|
1475 | + let d = value[i]; | ||
|
|
1476 | + const dateTime = this.dateConversion(d, optionName, localization); | ||
|
|
1477 | + if (!dateTime) { | ||
|
|
1478 | + Namespace.errorMessages.typeMismatch(optionName, typeof d, 'DateTime or Date'); | ||
|
|
1479 | + } | ||
|
|
1480 | + dateTime.setLocale(localization?.locale ?? 'default'); | ||
|
|
1481 | + value[i] = dateTime; | ||
|
|
1482 | + } | ||
|
|
1483 | + } | ||
|
|
1484 | + /** | ||
|
|
1485 | + * Type checks that `value` is an array of numbers | ||
|
|
1486 | + * @param optionName Provides text to error messages e.g. disabledDates | ||
|
|
1487 | + * @param value Option value | ||
|
|
1488 | + * @param providedType Used to provide text to error messages | ||
|
|
1489 | + */ | ||
|
|
1490 | + static _typeCheckNumberArray(optionName, value, providedType) { | ||
|
|
1491 | + if (!Array.isArray(value) || value.find((x) => typeof x !== typeof 0)) { | ||
|
|
1492 | + Namespace.errorMessages.typeMismatch(optionName, providedType, 'array of numbers'); | ||
|
|
1493 | + } | ||
|
|
1494 | + } | ||
|
|
1495 | + /** | ||
|
|
1496 | + * Attempts to convert `d` to a DateTime object | ||
|
|
1497 | + * @param d value to convert | ||
|
|
1498 | + * @param optionName Provides text to error messages e.g. disabledDates | ||
|
|
1499 | + * @param localization object containing locale and format settings. Only used with the custom formats | ||
|
|
1500 | + */ | ||
|
|
1501 | + static dateConversion(d, optionName, localization) { | ||
|
|
1502 | + if (typeof d === typeof '' && optionName !== 'input') { | ||
|
|
1503 | + Namespace.errorMessages.dateString(); | ||
|
|
1504 | + } | ||
|
|
1505 | + const converted = this._dateTypeCheck(d, localization); | ||
|
|
1506 | + if (!converted) { | ||
|
|
1507 | + Namespace.errorMessages.failedToParseDate(optionName, d, optionName === 'input'); | ||
|
|
1508 | + } | ||
|
|
1509 | + return converted; | ||
|
|
1510 | + } | ||
|
|
1511 | + static getFlattenDefaultOptions() { | ||
|
|
1512 | + if (this._flattenDefaults) | ||
|
|
1513 | + return this._flattenDefaults; | ||
|
|
1514 | + const deepKeys = (t, pre = []) => { | ||
|
|
1515 | + if (Array.isArray(t)) | ||
|
|
1516 | + return []; | ||
|
|
1517 | + if (Object(t) === t) { | ||
|
|
1518 | + return Object.entries(t).flatMap(([k, v]) => deepKeys(v, [...pre, k])); | ||
|
|
1519 | + } | ||
|
|
1520 | + else { | ||
|
|
1521 | + return pre.join('.'); | ||
|
|
1522 | + } | ||
|
|
1523 | + }; | ||
|
|
1524 | + this._flattenDefaults = deepKeys(DefaultOptions); | ||
|
|
1525 | + return this._flattenDefaults; | ||
|
|
1526 | + } | ||
|
|
1527 | + /** | ||
|
|
1528 | + * Some options conflict like min/max date. Verify that these kinds of options | ||
|
|
1529 | + * are set correctly. | ||
|
|
1530 | + * @param config | ||
|
|
1531 | + */ | ||
|
|
1532 | + static _validateConflicts(config) { | ||
|
|
1533 | + if (config.display.sideBySide && | ||
|
|
1534 | + (!config.display.components.clock || | ||
|
|
1535 | + !(config.display.components.hours || | ||
|
|
1536 | + config.display.components.minutes || | ||
|
|
1537 | + config.display.components.seconds))) { | ||
|
|
1538 | + Namespace.errorMessages.conflictingConfiguration('Cannot use side by side mode without the clock components'); | ||
|
|
1539 | + } | ||
|
|
1540 | + if (config.restrictions.minDate && config.restrictions.maxDate) { | ||
|
|
1541 | + if (config.restrictions.minDate.isAfter(config.restrictions.maxDate)) { | ||
|
|
1542 | + Namespace.errorMessages.conflictingConfiguration('minDate is after maxDate'); | ||
|
|
1543 | + } | ||
|
|
1544 | + if (config.restrictions.maxDate.isBefore(config.restrictions.minDate)) { | ||
|
|
1545 | + Namespace.errorMessages.conflictingConfiguration('maxDate is before minDate'); | ||
|
|
1546 | + } | ||
|
|
1547 | + } | ||
|
|
1548 | + } | ||
|
|
1549 | + } | ||
|
|
1550 | + OptionConverter.ignoreProperties = ['meta', 'dayViewHeaderFormat', | ||
|
|
1551 | + 'container', 'dateForms', 'ordinal']; | ||
|
|
1552 | + OptionConverter.isValue = a => a != null; // everything except undefined + null | ||
|
|
1553 | + | ||
|
|
1554 | + class Dates { | ||
|
|
1555 | + constructor() { | ||
|
|
1556 | + this._dates = []; | ||
|
|
1557 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
1558 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
1559 | + this._eventEmitters = serviceLocator.locate(EventEmitters); | ||
|
|
1560 | + } | ||
|
|
1561 | + /** | ||
|
|
1562 | + * Returns the array of selected dates | ||
|
|
1563 | + */ | ||
|
|
1564 | + get picked() { | ||
|
|
1565 | + return this._dates; | ||
|
|
1566 | + } | ||
|
|
1567 | + /** | ||
|
|
1568 | + * Returns the last picked value. | ||
|
|
1569 | + */ | ||
|
|
1570 | + get lastPicked() { | ||
|
|
1571 | + return this._dates[this.lastPickedIndex]; | ||
|
|
1572 | + } | ||
|
|
1573 | + /** | ||
|
|
1574 | + * Returns the length of picked dates -1 or 0 if none are selected. | ||
|
|
1575 | + */ | ||
|
|
1576 | + get lastPickedIndex() { | ||
|
|
1577 | + if (this._dates.length === 0) | ||
|
|
1578 | + return 0; | ||
|
|
1579 | + return this._dates.length - 1; | ||
|
|
1580 | + } | ||
|
|
1581 | + /** | ||
|
|
1582 | + * Formats a DateTime object to a string. Used when setting the input value. | ||
|
|
1583 | + * @param date | ||
|
|
1584 | + */ | ||
|
|
1585 | + formatInput(date) { | ||
|
|
1586 | + const components = this.optionsStore.options.display.components; | ||
|
|
1587 | + if (!date) | ||
|
|
1588 | + return ''; | ||
|
|
1589 | + return date.format({ | ||
|
|
1590 | + year: components.calendar && components.year ? 'numeric' : undefined, | ||
|
|
1591 | + month: components.calendar && components.month ? '2-digit' : undefined, | ||
|
|
1592 | + day: components.calendar && components.date ? '2-digit' : undefined, | ||
|
|
1593 | + hour: components.clock && components.hours | ||
|
|
1594 | + ? components.useTwentyfourHour | ||
|
|
1595 | + ? '2-digit' | ||
|
|
1596 | + : 'numeric' | ||
|
|
1597 | + : undefined, | ||
|
|
1598 | + minute: components.clock && components.minutes ? '2-digit' : undefined, | ||
|
|
1599 | + second: components.clock && components.seconds ? '2-digit' : undefined, | ||
|
|
1600 | + hour12: !components.useTwentyfourHour, | ||
|
|
1601 | + }); | ||
|
|
1602 | + } | ||
|
|
1603 | + /** | ||
|
|
1604 | + * parse the value into a DateTime object. | ||
|
|
1605 | + * this can be overwritten to supply your own parsing. | ||
|
|
1606 | + */ | ||
|
|
1607 | + parseInput(value) { | ||
|
|
1608 | + return OptionConverter.dateConversion(value, 'input', this.optionsStore.options.localization); | ||
|
|
1609 | + } | ||
|
|
1610 | + /** | ||
|
|
1611 | + * Tries to convert the provided value to a DateTime object. | ||
|
|
1612 | + * If value is null|undefined then clear the value of the provided index (or 0). | ||
|
|
1613 | + * @param value Value to convert or null|undefined | ||
|
|
1614 | + * @param index When using multidates this is the index in the array | ||
|
|
1615 | + */ | ||
|
|
1616 | + setFromInput(value, index) { | ||
|
|
1617 | + if (!value) { | ||
|
|
1618 | + this.setValue(undefined, index); | ||
|
|
1619 | + return; | ||
|
|
1620 | + } | ||
|
|
1621 | + const converted = this.parseInput(value); | ||
|
|
1622 | + if (converted) { | ||
|
|
1623 | + converted.setLocale(this.optionsStore.options.localization.locale); | ||
|
|
1624 | + this.setValue(converted, index); | ||
|
|
1625 | + } | ||
|
|
1626 | + } | ||
|
|
1627 | + /** | ||
|
|
1628 | + * Adds a new DateTime to selected dates array | ||
|
|
1629 | + * @param date | ||
|
|
1630 | + */ | ||
|
|
1631 | + add(date) { | ||
|
|
1632 | + this._dates.push(date); | ||
|
|
1633 | + } | ||
|
|
1634 | + /** | ||
|
|
1635 | + * Returns true if the `targetDate` is part of the selected dates array. | ||
|
|
1636 | + * If `unit` is provided then a granularity to that unit will be used. | ||
|
|
1637 | + * @param targetDate | ||
|
|
1638 | + * @param unit | ||
|
|
1639 | + */ | ||
|
|
1640 | + isPicked(targetDate, unit) { | ||
|
|
1641 | + if (!unit) | ||
|
|
1642 | + return this._dates.find((x) => x === targetDate) !== undefined; | ||
|
|
1643 | + const format = getFormatByUnit(unit); | ||
|
|
1644 | + let innerDateFormatted = targetDate.format(format); | ||
|
|
1645 | + return (this._dates | ||
|
|
1646 | + .map((x) => x.format(format)) | ||
|
|
1647 | + .find((x) => x === innerDateFormatted) !== undefined); | ||
|
|
1648 | + } | ||
|
|
1649 | + /** | ||
|
|
1650 | + * Returns the index at which `targetDate` is in the array. | ||
|
|
1651 | + * This is used for updating or removing a date when multi-date is used | ||
|
|
1652 | + * If `unit` is provided then a granularity to that unit will be used. | ||
|
|
1653 | + * @param targetDate | ||
|
|
1654 | + * @param unit | ||
|
|
1655 | + */ | ||
|
|
1656 | + pickedIndex(targetDate, unit) { | ||
|
|
1657 | + if (!unit) | ||
|
|
1658 | + return this._dates.indexOf(targetDate); | ||
|
|
1659 | + const format = getFormatByUnit(unit); | ||
|
|
1660 | + let innerDateFormatted = targetDate.format(format); | ||
|
|
1661 | + return this._dates.map((x) => x.format(format)).indexOf(innerDateFormatted); | ||
|
|
1662 | + } | ||
|
|
1663 | + /** | ||
|
|
1664 | + * Clears all selected dates. | ||
|
|
1665 | + */ | ||
|
|
1666 | + clear() { | ||
|
|
1667 | + this.optionsStore.unset = true; | ||
|
|
1668 | + this._eventEmitters.triggerEvent.emit({ | ||
|
|
1669 | + type: Namespace.events.change, | ||
|
|
1670 | + date: undefined, | ||
|
|
1671 | + oldDate: this.lastPicked, | ||
|
|
1672 | + isClear: true, | ||
|
|
1673 | + isValid: true, | ||
|
|
1674 | + }); | ||
|
|
1675 | + this._dates = []; | ||
|
|
1676 | + } | ||
|
|
1677 | + /** | ||
|
|
1678 | + * Find the "book end" years given a `year` and a `factor` | ||
|
|
1679 | + * @param factor e.g. 100 for decades | ||
|
|
1680 | + * @param year e.g. 2021 | ||
|
|
1681 | + */ | ||
|
|
1682 | + static getStartEndYear(factor, year) { | ||
|
|
1683 | + const step = factor / 10, startYear = Math.floor(year / factor) * factor, endYear = startYear + step * 9, focusValue = Math.floor(year / step) * step; | ||
|
|
1684 | + return [startYear, endYear, focusValue]; | ||
|
|
1685 | + } | ||
|
|
1686 | + /** | ||
|
|
1687 | + * Attempts to either clear or set the `target` date at `index`. | ||
|
|
1688 | + * If the `target` is null then the date will be cleared. | ||
|
|
1689 | + * If multi-date is being used then it will be removed from the array. | ||
|
|
1690 | + * If `target` is valid and multi-date is used then if `index` is | ||
|
|
1691 | + * provided the date at that index will be replaced, otherwise it is appended. | ||
|
|
1692 | + * @param target | ||
|
|
1693 | + * @param index | ||
|
|
1694 | + */ | ||
|
|
1695 | + setValue(target, index) { | ||
|
|
1696 | + const noIndex = typeof index === 'undefined', isClear = !target && noIndex; | ||
|
|
1697 | + let oldDate = this.optionsStore.unset ? null : this._dates[index]; | ||
|
|
1698 | + if (!oldDate && !this.optionsStore.unset && noIndex && isClear) { | ||
|
|
1699 | + oldDate = this.lastPicked; | ||
|
|
1700 | + } | ||
|
|
1701 | + const updateInput = () => { | ||
|
|
1702 | + if (!this.optionsStore.input) | ||
|
|
1703 | + return; | ||
|
|
1704 | + let newValue = this.formatInput(target); | ||
|
|
1705 | + if (this.optionsStore.options.multipleDates) { | ||
|
|
1706 | + newValue = this._dates | ||
|
|
1707 | + .map((d) => this.formatInput(d)) | ||
|
|
1708 | + .join(this.optionsStore.options.multipleDatesSeparator); | ||
|
|
1709 | + } | ||
|
|
1710 | + if (this.optionsStore.input.value != newValue) | ||
|
|
1711 | + this.optionsStore.input.value = newValue; | ||
|
|
1712 | + }; | ||
|
|
1713 | + if (target && oldDate?.isSame(target)) { | ||
|
|
1714 | + updateInput(); | ||
|
|
1715 | + return; | ||
|
|
1716 | + } | ||
|
|
1717 | + // case of calling setValue(null) | ||
|
|
1718 | + if (!target) { | ||
|
|
1719 | + if (!this.optionsStore.options.multipleDates || | ||
|
|
1720 | + this._dates.length === 1 || | ||
|
|
1721 | + isClear) { | ||
|
|
1722 | + this.optionsStore.unset = true; | ||
|
|
1723 | + this._dates = []; | ||
|
|
1724 | + } | ||
|
|
1725 | + else { | ||
|
|
1726 | + this._dates.splice(index, 1); | ||
|
|
1727 | + } | ||
|
|
1728 | + updateInput(); | ||
|
|
1729 | + this._eventEmitters.triggerEvent.emit({ | ||
|
|
1730 | + type: Namespace.events.change, | ||
|
|
1731 | + date: undefined, | ||
|
|
1732 | + oldDate, | ||
|
|
1733 | + isClear, | ||
|
|
1734 | + isValid: true, | ||
|
|
1735 | + }); | ||
|
|
1736 | + this._eventEmitters.updateDisplay.emit('all'); | ||
|
|
1737 | + return; | ||
|
|
1738 | + } | ||
|
|
1739 | + index = index || 0; | ||
|
|
1740 | + target = target.clone; | ||
|
|
1741 | + // minute stepping is being used, force the minute to the closest value | ||
|
|
1742 | + if (this.optionsStore.options.stepping !== 1) { | ||
|
|
1743 | + target.minutes = | ||
|
|
1744 | + Math.round(target.minutes / this.optionsStore.options.stepping) * | ||
|
|
1745 | + this.optionsStore.options.stepping; | ||
|
|
1746 | + target.seconds = 0; | ||
|
|
1747 | + } | ||
|
|
1748 | + if (this.validation.isValid(target)) { | ||
|
|
1749 | + this._dates[index] = target; | ||
|
|
1750 | + this.optionsStore.viewDate = target.clone; | ||
|
|
1751 | + updateInput(); | ||
|
|
1752 | + this.optionsStore.unset = false; | ||
|
|
1753 | + this._eventEmitters.updateDisplay.emit('all'); | ||
|
|
1754 | + this._eventEmitters.triggerEvent.emit({ | ||
|
|
1755 | + type: Namespace.events.change, | ||
|
|
1756 | + date: target, | ||
|
|
1757 | + oldDate, | ||
|
|
1758 | + isClear, | ||
|
|
1759 | + isValid: true, | ||
|
|
1760 | + }); | ||
|
|
1761 | + return; | ||
|
|
1762 | + } | ||
|
|
1763 | + if (this.optionsStore.options.keepInvalid) { | ||
|
|
1764 | + this._dates[index] = target; | ||
|
|
1765 | + this.optionsStore.viewDate = target.clone; | ||
|
|
1766 | + updateInput(); | ||
|
|
1767 | + this._eventEmitters.triggerEvent.emit({ | ||
|
|
1768 | + type: Namespace.events.change, | ||
|
|
1769 | + date: target, | ||
|
|
1770 | + oldDate, | ||
|
|
1771 | + isClear, | ||
|
|
1772 | + isValid: false, | ||
|
|
1773 | + }); | ||
|
|
1774 | + } | ||
|
|
1775 | + this._eventEmitters.triggerEvent.emit({ | ||
|
|
1776 | + type: Namespace.events.error, | ||
|
|
1777 | + reason: Namespace.errorMessages.failedToSetInvalidDate, | ||
|
|
1778 | + date: target, | ||
|
|
1779 | + oldDate, | ||
|
|
1780 | + }); | ||
|
|
1781 | + } | ||
|
|
1782 | + } | ||
|
|
1783 | + | ||
|
|
1784 | + var ActionTypes; | ||
|
|
1785 | + (function (ActionTypes) { | ||
|
|
1786 | + ActionTypes["next"] = "next"; | ||
|
|
1787 | + ActionTypes["previous"] = "previous"; | ||
|
|
1788 | + ActionTypes["changeCalendarView"] = "changeCalendarView"; | ||
|
|
1789 | + ActionTypes["selectMonth"] = "selectMonth"; | ||
|
|
1790 | + ActionTypes["selectYear"] = "selectYear"; | ||
|
|
1791 | + ActionTypes["selectDecade"] = "selectDecade"; | ||
|
|
1792 | + ActionTypes["selectDay"] = "selectDay"; | ||
|
|
1793 | + ActionTypes["selectHour"] = "selectHour"; | ||
|
|
1794 | + ActionTypes["selectMinute"] = "selectMinute"; | ||
|
|
1795 | + ActionTypes["selectSecond"] = "selectSecond"; | ||
|
|
1796 | + ActionTypes["incrementHours"] = "incrementHours"; | ||
|
|
1797 | + ActionTypes["incrementMinutes"] = "incrementMinutes"; | ||
|
|
1798 | + ActionTypes["incrementSeconds"] = "incrementSeconds"; | ||
|
|
1799 | + ActionTypes["decrementHours"] = "decrementHours"; | ||
|
|
1800 | + ActionTypes["decrementMinutes"] = "decrementMinutes"; | ||
|
|
1801 | + ActionTypes["decrementSeconds"] = "decrementSeconds"; | ||
|
|
1802 | + ActionTypes["toggleMeridiem"] = "toggleMeridiem"; | ||
|
|
1803 | + ActionTypes["togglePicker"] = "togglePicker"; | ||
|
|
1804 | + ActionTypes["showClock"] = "showClock"; | ||
|
|
1805 | + ActionTypes["showHours"] = "showHours"; | ||
|
|
1806 | + ActionTypes["showMinutes"] = "showMinutes"; | ||
|
|
1807 | + ActionTypes["showSeconds"] = "showSeconds"; | ||
|
|
1808 | + ActionTypes["clear"] = "clear"; | ||
|
|
1809 | + ActionTypes["close"] = "close"; | ||
|
|
1810 | + ActionTypes["today"] = "today"; | ||
|
|
1811 | + })(ActionTypes || (ActionTypes = {})); | ||
|
|
1812 | + var ActionTypes$1 = ActionTypes; | ||
|
|
1813 | + | ||
|
|
1814 | + /** | ||
|
|
1815 | + * Creates and updates the grid for `date` | ||
|
|
1816 | + */ | ||
|
|
1817 | + class DateDisplay { | ||
|
|
1818 | + constructor() { | ||
|
|
1819 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
1820 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
1821 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
1822 | + } | ||
|
|
1823 | + /** | ||
|
|
1824 | + * Build the container html for the display | ||
|
|
1825 | + * @private | ||
|
|
1826 | + */ | ||
|
|
1827 | + getPicker() { | ||
|
|
1828 | + const container = document.createElement("div"); | ||
|
|
1829 | + container.classList.add(Namespace.css.daysContainer); | ||
|
|
1830 | + container.append(...this._daysOfTheWeek()); | ||
|
|
1831 | + if (this.optionsStore.options.display.calendarWeeks) { | ||
|
|
1832 | + const div = document.createElement("div"); | ||
|
|
1833 | + div.classList.add(Namespace.css.calendarWeeks, Namespace.css.noHighlight); | ||
|
|
1834 | + container.appendChild(div); | ||
|
|
1835 | + } | ||
|
|
1836 | + for (let i = 0; i < 42; i++) { | ||
|
|
1837 | + if (i !== 0 && i % 7 === 0) { | ||
|
|
1838 | + if (this.optionsStore.options.display.calendarWeeks) { | ||
|
|
1839 | + const div = document.createElement("div"); | ||
|
|
1840 | + div.classList.add(Namespace.css.calendarWeeks, Namespace.css.noHighlight); | ||
|
|
1841 | + container.appendChild(div); | ||
|
|
1842 | + } | ||
|
|
1843 | + } | ||
|
|
1844 | + const div = document.createElement("div"); | ||
|
|
1845 | + div.setAttribute("data-action", ActionTypes$1.selectDay); | ||
|
|
1846 | + container.appendChild(div); | ||
|
|
1847 | + } | ||
|
|
1848 | + return container; | ||
|
|
1849 | + } | ||
|
|
1850 | + /** | ||
|
|
1851 | + * Populates the grid and updates enabled states | ||
|
|
1852 | + * @private | ||
|
|
1853 | + */ | ||
|
|
1854 | + _update(widget, paint) { | ||
|
|
1855 | + const container = widget.getElementsByClassName(Namespace.css.daysContainer)[0]; | ||
|
|
1856 | + if (this.optionsStore.currentView === "calendar") { | ||
|
|
1857 | + const [previous, switcher, next] = container.parentElement | ||
|
|
1858 | + .getElementsByClassName(Namespace.css.calendarHeader)[0] | ||
|
|
1859 | + .getElementsByTagName("div"); | ||
|
|
1860 | + switcher.setAttribute(Namespace.css.daysContainer, this.optionsStore.viewDate.format(this.optionsStore.options.localization.dayViewHeaderFormat)); | ||
|
|
1861 | + this.optionsStore.options.display.components.month | ||
|
|
1862 | + ? switcher.classList.remove(Namespace.css.disabled) | ||
|
|
1863 | + : switcher.classList.add(Namespace.css.disabled); | ||
|
|
1864 | + this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(-1, Unit.month), Unit.month) | ||
|
|
1865 | + ? previous.classList.remove(Namespace.css.disabled) | ||
|
|
1866 | + : previous.classList.add(Namespace.css.disabled); | ||
|
|
1867 | + this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(1, Unit.month), Unit.month) | ||
|
|
1868 | + ? next.classList.remove(Namespace.css.disabled) | ||
|
|
1869 | + : next.classList.add(Namespace.css.disabled); | ||
|
|
1870 | + } | ||
|
|
1871 | + let innerDate = this.optionsStore.viewDate.clone | ||
|
|
1872 | + .startOf(Unit.month) | ||
|
|
1873 | + .startOf("weekDay", this.optionsStore.options.localization.startOfTheWeek) | ||
|
|
1874 | + .manipulate(12, Unit.hours); | ||
|
|
1875 | + container | ||
|
|
1876 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectDay}"], .${Namespace.css.calendarWeeks}`) | ||
|
|
1877 | + .forEach((containerClone) => { | ||
|
|
1878 | + if (this.optionsStore.options.display.calendarWeeks && | ||
|
|
1879 | + containerClone.classList.contains(Namespace.css.calendarWeeks)) { | ||
|
|
1880 | + if (containerClone.innerText === "#") | ||
|
|
1881 | + return; | ||
|
|
1882 | + containerClone.innerText = `${innerDate.week}`; | ||
|
|
1883 | + return; | ||
|
|
1884 | + } | ||
|
|
1885 | + let classes = []; | ||
|
|
1886 | + classes.push(Namespace.css.day); | ||
|
|
1887 | + if (innerDate.isBefore(this.optionsStore.viewDate, Unit.month)) { | ||
|
|
1888 | + classes.push(Namespace.css.old); | ||
|
|
1889 | + } | ||
|
|
1890 | + if (innerDate.isAfter(this.optionsStore.viewDate, Unit.month)) { | ||
|
|
1891 | + classes.push(Namespace.css.new); | ||
|
|
1892 | + } | ||
|
|
1893 | + if (!this.optionsStore.unset && | ||
|
|
1894 | + this.dates.isPicked(innerDate, Unit.date)) { | ||
|
|
1895 | + classes.push(Namespace.css.active); | ||
|
|
1896 | + } | ||
|
|
1897 | + if (!this.validation.isValid(innerDate, Unit.date)) { | ||
|
|
1898 | + classes.push(Namespace.css.disabled); | ||
|
|
1899 | + } | ||
|
|
1900 | + if (innerDate.isSame(new DateTime(), Unit.date)) { | ||
|
|
1901 | + classes.push(Namespace.css.today); | ||
|
|
1902 | + } | ||
|
|
1903 | + if (innerDate.weekDay === 0 || innerDate.weekDay === 6) { | ||
|
|
1904 | + classes.push(Namespace.css.weekend); | ||
|
|
1905 | + } | ||
|
|
1906 | + paint(Unit.date, innerDate, classes, containerClone); | ||
|
|
1907 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
1908 | + containerClone.classList.add(...classes); | ||
|
|
1909 | + containerClone.setAttribute("data-value", `${innerDate.year}-${innerDate.monthFormatted}-${innerDate.dateFormatted}`); | ||
|
|
1910 | + containerClone.setAttribute("data-day", `${innerDate.date}`); | ||
|
|
1911 | + containerClone.innerText = innerDate.format({ day: "numeric" }); | ||
|
|
1912 | + innerDate.manipulate(1, Unit.date); | ||
|
|
1913 | + }); | ||
|
|
1914 | + } | ||
|
|
1915 | + /*** | ||
|
|
1916 | + * Generates an html row that contains the days of the week. | ||
|
|
1917 | + * @private | ||
|
|
1918 | + */ | ||
|
|
1919 | + _daysOfTheWeek() { | ||
|
|
1920 | + let innerDate = this.optionsStore.viewDate.clone | ||
|
|
1921 | + .startOf("weekDay", this.optionsStore.options.localization.startOfTheWeek) | ||
|
|
1922 | + .startOf(Unit.date); | ||
|
|
1923 | + const row = []; | ||
|
|
1924 | + document.createElement("div"); | ||
|
|
1925 | + if (this.optionsStore.options.display.calendarWeeks) { | ||
|
|
1926 | + const htmlDivElement = document.createElement("div"); | ||
|
|
1927 | + htmlDivElement.classList.add(Namespace.css.calendarWeeks, Namespace.css.noHighlight); | ||
|
|
1928 | + htmlDivElement.innerText = "#"; | ||
|
|
1929 | + row.push(htmlDivElement); | ||
|
|
1930 | + } | ||
|
|
1931 | + for (let i = 0; i < 7; i++) { | ||
|
|
1932 | + const htmlDivElement = document.createElement("div"); | ||
|
|
1933 | + htmlDivElement.classList.add(Namespace.css.dayOfTheWeek, Namespace.css.noHighlight); | ||
|
|
1934 | + htmlDivElement.innerText = innerDate.format({ weekday: "short" }); | ||
|
|
1935 | + innerDate.manipulate(1, Unit.date); | ||
|
|
1936 | + row.push(htmlDivElement); | ||
|
|
1937 | + } | ||
|
|
1938 | + return row; | ||
|
|
1939 | + } | ||
|
|
1940 | + } | ||
|
|
1941 | + | ||
|
|
1942 | + /** | ||
|
|
1943 | + * Creates and updates the grid for `month` | ||
|
|
1944 | + */ | ||
|
|
1945 | + class MonthDisplay { | ||
|
|
1946 | + constructor() { | ||
|
|
1947 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
1948 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
1949 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
1950 | + } | ||
|
|
1951 | + /** | ||
|
|
1952 | + * Build the container html for the display | ||
|
|
1953 | + * @private | ||
|
|
1954 | + */ | ||
|
|
1955 | + getPicker() { | ||
|
|
1956 | + const container = document.createElement('div'); | ||
|
|
1957 | + container.classList.add(Namespace.css.monthsContainer); | ||
|
|
1958 | + for (let i = 0; i < 12; i++) { | ||
|
|
1959 | + const div = document.createElement('div'); | ||
|
|
1960 | + div.setAttribute('data-action', ActionTypes$1.selectMonth); | ||
|
|
1961 | + container.appendChild(div); | ||
|
|
1962 | + } | ||
|
|
1963 | + return container; | ||
|
|
1964 | + } | ||
|
|
1965 | + /** | ||
|
|
1966 | + * Populates the grid and updates enabled states | ||
|
|
1967 | + * @private | ||
|
|
1968 | + */ | ||
|
|
1969 | + _update(widget, paint) { | ||
|
|
1970 | + const container = widget.getElementsByClassName(Namespace.css.monthsContainer)[0]; | ||
|
|
1971 | + if (this.optionsStore.currentView === 'months') { | ||
|
|
1972 | + const [previous, switcher, next] = container.parentElement | ||
|
|
1973 | + .getElementsByClassName(Namespace.css.calendarHeader)[0] | ||
|
|
1974 | + .getElementsByTagName('div'); | ||
|
|
1975 | + switcher.setAttribute(Namespace.css.monthsContainer, this.optionsStore.viewDate.format({ year: 'numeric' })); | ||
|
|
1976 | + this.optionsStore.options.display.components.year | ||
|
|
1977 | + ? switcher.classList.remove(Namespace.css.disabled) | ||
|
|
1978 | + : switcher.classList.add(Namespace.css.disabled); | ||
|
|
1979 | + this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(-1, Unit.year), Unit.year) | ||
|
|
1980 | + ? previous.classList.remove(Namespace.css.disabled) | ||
|
|
1981 | + : previous.classList.add(Namespace.css.disabled); | ||
|
|
1982 | + this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(1, Unit.year), Unit.year) | ||
|
|
1983 | + ? next.classList.remove(Namespace.css.disabled) | ||
|
|
1984 | + : next.classList.add(Namespace.css.disabled); | ||
|
|
1985 | + } | ||
|
|
1986 | + let innerDate = this.optionsStore.viewDate.clone.startOf(Unit.year); | ||
|
|
1987 | + container | ||
|
|
1988 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectMonth}"]`) | ||
|
|
1989 | + .forEach((containerClone, index) => { | ||
|
|
1990 | + let classes = []; | ||
|
|
1991 | + classes.push(Namespace.css.month); | ||
|
|
1992 | + if (!this.optionsStore.unset && | ||
|
|
1993 | + this.dates.isPicked(innerDate, Unit.month)) { | ||
|
|
1994 | + classes.push(Namespace.css.active); | ||
|
|
1995 | + } | ||
|
|
1996 | + if (!this.validation.isValid(innerDate, Unit.month)) { | ||
|
|
1997 | + classes.push(Namespace.css.disabled); | ||
|
|
1998 | + } | ||
|
|
1999 | + paint(Unit.month, innerDate, classes, containerClone); | ||
|
|
2000 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
2001 | + containerClone.classList.add(...classes); | ||
|
|
2002 | + containerClone.setAttribute('data-value', `${index}`); | ||
|
|
2003 | + containerClone.innerText = `${innerDate.format({ month: 'short' })}`; | ||
|
|
2004 | + innerDate.manipulate(1, Unit.month); | ||
|
|
2005 | + }); | ||
|
|
2006 | + } | ||
|
|
2007 | + } | ||
|
|
2008 | + | ||
|
|
2009 | + /** | ||
|
|
2010 | + * Creates and updates the grid for `year` | ||
|
|
2011 | + */ | ||
|
|
2012 | + class YearDisplay { | ||
|
|
2013 | + constructor() { | ||
|
|
2014 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2015 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
2016 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2017 | + } | ||
|
|
2018 | + /** | ||
|
|
2019 | + * Build the container html for the display | ||
|
|
2020 | + * @private | ||
|
|
2021 | + */ | ||
|
|
2022 | + getPicker() { | ||
|
|
2023 | + const container = document.createElement("div"); | ||
|
|
2024 | + container.classList.add(Namespace.css.yearsContainer); | ||
|
|
2025 | + for (let i = 0; i < 12; i++) { | ||
|
|
2026 | + const div = document.createElement("div"); | ||
|
|
2027 | + div.setAttribute("data-action", ActionTypes$1.selectYear); | ||
|
|
2028 | + container.appendChild(div); | ||
|
|
2029 | + } | ||
|
|
2030 | + return container; | ||
|
|
2031 | + } | ||
|
|
2032 | + /** | ||
|
|
2033 | + * Populates the grid and updates enabled states | ||
|
|
2034 | + * @private | ||
|
|
2035 | + */ | ||
|
|
2036 | + _update(widget, paint) { | ||
|
|
2037 | + this._startYear = this.optionsStore.viewDate.clone.manipulate(-1, Unit.year); | ||
|
|
2038 | + this._endYear = this.optionsStore.viewDate.clone.manipulate(10, Unit.year); | ||
|
|
2039 | + const container = widget.getElementsByClassName(Namespace.css.yearsContainer)[0]; | ||
|
|
2040 | + if (this.optionsStore.currentView === "years") { | ||
|
|
2041 | + const [previous, switcher, next] = container.parentElement | ||
|
|
2042 | + .getElementsByClassName(Namespace.css.calendarHeader)[0] | ||
|
|
2043 | + .getElementsByTagName("div"); | ||
|
|
2044 | + switcher.setAttribute(Namespace.css.yearsContainer, `${this._startYear.format({ year: "numeric" })}-${this._endYear.format({ year: "numeric" })}`); | ||
|
|
2045 | + this.optionsStore.options.display.components.decades | ||
|
|
2046 | + ? switcher.classList.remove(Namespace.css.disabled) | ||
|
|
2047 | + : switcher.classList.add(Namespace.css.disabled); | ||
|
|
2048 | + this.validation.isValid(this._startYear, Unit.year) | ||
|
|
2049 | + ? previous.classList.remove(Namespace.css.disabled) | ||
|
|
2050 | + : previous.classList.add(Namespace.css.disabled); | ||
|
|
2051 | + this.validation.isValid(this._endYear, Unit.year) | ||
|
|
2052 | + ? next.classList.remove(Namespace.css.disabled) | ||
|
|
2053 | + : next.classList.add(Namespace.css.disabled); | ||
|
|
2054 | + } | ||
|
|
2055 | + let innerDate = this.optionsStore.viewDate.clone | ||
|
|
2056 | + .startOf(Unit.year) | ||
|
|
2057 | + .manipulate(-1, Unit.year); | ||
|
|
2058 | + container | ||
|
|
2059 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectYear}"]`) | ||
|
|
2060 | + .forEach((containerClone) => { | ||
|
|
2061 | + let classes = []; | ||
|
|
2062 | + classes.push(Namespace.css.year); | ||
|
|
2063 | + if (!this.optionsStore.unset && | ||
|
|
2064 | + this.dates.isPicked(innerDate, Unit.year)) { | ||
|
|
2065 | + classes.push(Namespace.css.active); | ||
|
|
2066 | + } | ||
|
|
2067 | + if (!this.validation.isValid(innerDate, Unit.year)) { | ||
|
|
2068 | + classes.push(Namespace.css.disabled); | ||
|
|
2069 | + } | ||
|
|
2070 | + paint(Unit.year, innerDate, classes, containerClone); | ||
|
|
2071 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
2072 | + containerClone.classList.add(...classes); | ||
|
|
2073 | + containerClone.setAttribute("data-value", `${innerDate.year}`); | ||
|
|
2074 | + containerClone.innerText = innerDate.format({ year: "numeric" }); | ||
|
|
2075 | + innerDate.manipulate(1, Unit.year); | ||
|
|
2076 | + }); | ||
|
|
2077 | + } | ||
|
|
2078 | + } | ||
|
|
2079 | + | ||
|
|
2080 | + /** | ||
|
|
2081 | + * Creates and updates the grid for `seconds` | ||
|
|
2082 | + */ | ||
|
|
2083 | + class DecadeDisplay { | ||
|
|
2084 | + constructor() { | ||
|
|
2085 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2086 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
2087 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2088 | + } | ||
|
|
2089 | + /** | ||
|
|
2090 | + * Build the container html for the display | ||
|
|
2091 | + * @private | ||
|
|
2092 | + */ | ||
|
|
2093 | + getPicker() { | ||
|
|
2094 | + const container = document.createElement("div"); | ||
|
|
2095 | + container.classList.add(Namespace.css.decadesContainer); | ||
|
|
2096 | + for (let i = 0; i < 12; i++) { | ||
|
|
2097 | + const div = document.createElement("div"); | ||
|
|
2098 | + div.setAttribute("data-action", ActionTypes$1.selectDecade); | ||
|
|
2099 | + container.appendChild(div); | ||
|
|
2100 | + } | ||
|
|
2101 | + return container; | ||
|
|
2102 | + } | ||
|
|
2103 | + /** | ||
|
|
2104 | + * Populates the grid and updates enabled states | ||
|
|
2105 | + * @private | ||
|
|
2106 | + */ | ||
|
|
2107 | + _update(widget, paint) { | ||
|
|
2108 | + const [start, end] = Dates.getStartEndYear(100, this.optionsStore.viewDate.year); | ||
|
|
2109 | + this._startDecade = this.optionsStore.viewDate.clone.startOf(Unit.year); | ||
|
|
2110 | + this._startDecade.year = start; | ||
|
|
2111 | + this._endDecade = this.optionsStore.viewDate.clone.startOf(Unit.year); | ||
|
|
2112 | + this._endDecade.year = end; | ||
|
|
2113 | + const container = widget.getElementsByClassName(Namespace.css.decadesContainer)[0]; | ||
|
|
2114 | + const [previous, switcher, next] = container.parentElement | ||
|
|
2115 | + .getElementsByClassName(Namespace.css.calendarHeader)[0] | ||
|
|
2116 | + .getElementsByTagName("div"); | ||
|
|
2117 | + if (this.optionsStore.currentView === 'decades') { | ||
|
|
2118 | + switcher.setAttribute(Namespace.css.decadesContainer, `${this._startDecade.format({ year: "numeric" })}-${this._endDecade.format({ year: "numeric" })}`); | ||
|
|
2119 | + this.validation.isValid(this._startDecade, Unit.year) | ||
|
|
2120 | + ? previous.classList.remove(Namespace.css.disabled) | ||
|
|
2121 | + : previous.classList.add(Namespace.css.disabled); | ||
|
|
2122 | + this.validation.isValid(this._endDecade, Unit.year) | ||
|
|
2123 | + ? next.classList.remove(Namespace.css.disabled) | ||
|
|
2124 | + : next.classList.add(Namespace.css.disabled); | ||
|
|
2125 | + } | ||
|
|
2126 | + const pickedYears = this.dates.picked.map((x) => x.year); | ||
|
|
2127 | + container | ||
|
|
2128 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectDecade}"]`) | ||
|
|
2129 | + .forEach((containerClone, index) => { | ||
|
|
2130 | + if (index === 0) { | ||
|
|
2131 | + containerClone.classList.add(Namespace.css.old); | ||
|
|
2132 | + if (this._startDecade.year - 10 < 0) { | ||
|
|
2133 | + containerClone.textContent = " "; | ||
|
|
2134 | + previous.classList.add(Namespace.css.disabled); | ||
|
|
2135 | + containerClone.classList.add(Namespace.css.disabled); | ||
|
|
2136 | + containerClone.setAttribute("data-value", ``); | ||
|
|
2137 | + return; | ||
|
|
2138 | + } | ||
|
|
2139 | + else { | ||
|
|
2140 | + containerClone.innerText = this._startDecade.clone.manipulate(-10, Unit.year).format({ year: "numeric" }); | ||
|
|
2141 | + containerClone.setAttribute("data-value", `${this._startDecade.year}`); | ||
|
|
2142 | + return; | ||
|
|
2143 | + } | ||
|
|
2144 | + } | ||
|
|
2145 | + let classes = []; | ||
|
|
2146 | + classes.push(Namespace.css.decade); | ||
|
|
2147 | + const startDecadeYear = this._startDecade.year; | ||
|
|
2148 | + const endDecadeYear = this._startDecade.year + 9; | ||
|
|
2149 | + if (!this.optionsStore.unset && | ||
|
|
2150 | + pickedYears.filter((x) => x >= startDecadeYear && x <= endDecadeYear) | ||
|
|
2151 | + .length > 0) { | ||
|
|
2152 | + classes.push(Namespace.css.active); | ||
|
|
2153 | + } | ||
|
|
2154 | + paint("decade", this._startDecade, classes, containerClone); | ||
|
|
2155 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
2156 | + containerClone.classList.add(...classes); | ||
|
|
2157 | + containerClone.setAttribute("data-value", `${this._startDecade.year}`); | ||
|
|
2158 | + containerClone.innerText = `${this._startDecade.format({ year: "numeric" })}`; | ||
|
|
2159 | + this._startDecade.manipulate(10, Unit.year); | ||
|
|
2160 | + }); | ||
|
|
2161 | + } | ||
|
|
2162 | + } | ||
|
|
2163 | + | ||
|
|
2164 | + /** | ||
|
|
2165 | + * Creates the clock display | ||
|
|
2166 | + */ | ||
|
|
2167 | + class TimeDisplay { | ||
|
|
2168 | + constructor() { | ||
|
|
2169 | + this._gridColumns = ''; | ||
|
|
2170 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2171 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
2172 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2173 | + } | ||
|
|
2174 | + /** | ||
|
|
2175 | + * Build the container html for the clock display | ||
|
|
2176 | + * @private | ||
|
|
2177 | + */ | ||
|
|
2178 | + getPicker(iconTag) { | ||
|
|
2179 | + const container = document.createElement('div'); | ||
|
|
2180 | + container.classList.add(Namespace.css.clockContainer); | ||
|
|
2181 | + container.append(...this._grid(iconTag)); | ||
|
|
2182 | + return container; | ||
|
|
2183 | + } | ||
|
|
2184 | + /** | ||
|
|
2185 | + * Populates the various elements with in the clock display | ||
|
|
2186 | + * like the current hour and if the manipulation icons are enabled. | ||
|
|
2187 | + * @private | ||
|
|
2188 | + */ | ||
|
|
2189 | + _update(widget) { | ||
|
|
2190 | + const timesDiv = (widget.getElementsByClassName(Namespace.css.clockContainer)[0]); | ||
|
|
2191 | + const lastPicked = (this.dates.lastPicked || this.optionsStore.viewDate).clone; | ||
|
|
2192 | + timesDiv | ||
|
|
2193 | + .querySelectorAll('.disabled') | ||
|
|
2194 | + .forEach((element) => element.classList.remove(Namespace.css.disabled)); | ||
|
|
2195 | + if (this.optionsStore.options.display.components.hours) { | ||
|
|
2196 | + if (!this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(1, Unit.hours), Unit.hours)) { | ||
|
|
2197 | + timesDiv | ||
|
|
2198 | + .querySelector(`[data-action=${ActionTypes$1.incrementHours}]`) | ||
|
|
2199 | + .classList.add(Namespace.css.disabled); | ||
|
|
2200 | + } | ||
|
|
2201 | + if (!this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(-1, Unit.hours), Unit.hours)) { | ||
|
|
2202 | + timesDiv | ||
|
|
2203 | + .querySelector(`[data-action=${ActionTypes$1.decrementHours}]`) | ||
|
|
2204 | + .classList.add(Namespace.css.disabled); | ||
|
|
2205 | + } | ||
|
|
2206 | + timesDiv.querySelector(`[data-time-component=${Unit.hours}]`).innerText = this.optionsStore.options.display.components.useTwentyfourHour | ||
|
|
2207 | + ? lastPicked.hoursFormatted | ||
|
|
2208 | + : lastPicked.twelveHoursFormatted; | ||
|
|
2209 | + } | ||
|
|
2210 | + if (this.optionsStore.options.display.components.minutes) { | ||
|
|
2211 | + if (!this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(1, Unit.minutes), Unit.minutes)) { | ||
|
|
2212 | + timesDiv | ||
|
|
2213 | + .querySelector(`[data-action=${ActionTypes$1.incrementMinutes}]`) | ||
|
|
2214 | + .classList.add(Namespace.css.disabled); | ||
|
|
2215 | + } | ||
|
|
2216 | + if (!this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(-1, Unit.minutes), Unit.minutes)) { | ||
|
|
2217 | + timesDiv | ||
|
|
2218 | + .querySelector(`[data-action=${ActionTypes$1.decrementMinutes}]`) | ||
|
|
2219 | + .classList.add(Namespace.css.disabled); | ||
|
|
2220 | + } | ||
|
|
2221 | + timesDiv.querySelector(`[data-time-component=${Unit.minutes}]`).innerText = lastPicked.minutesFormatted; | ||
|
|
2222 | + } | ||
|
|
2223 | + if (this.optionsStore.options.display.components.seconds) { | ||
|
|
2224 | + if (!this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(1, Unit.seconds), Unit.seconds)) { | ||
|
|
2225 | + timesDiv | ||
|
|
2226 | + .querySelector(`[data-action=${ActionTypes$1.incrementSeconds}]`) | ||
|
|
2227 | + .classList.add(Namespace.css.disabled); | ||
|
|
2228 | + } | ||
|
|
2229 | + if (!this.validation.isValid(this.optionsStore.viewDate.clone.manipulate(-1, Unit.seconds), Unit.seconds)) { | ||
|
|
2230 | + timesDiv | ||
|
|
2231 | + .querySelector(`[data-action=${ActionTypes$1.decrementSeconds}]`) | ||
|
|
2232 | + .classList.add(Namespace.css.disabled); | ||
|
|
2233 | + } | ||
|
|
2234 | + timesDiv.querySelector(`[data-time-component=${Unit.seconds}]`).innerText = lastPicked.secondsFormatted; | ||
|
|
2235 | + } | ||
|
|
2236 | + if (!this.optionsStore.options.display.components.useTwentyfourHour) { | ||
|
|
2237 | + const toggle = timesDiv.querySelector(`[data-action=${ActionTypes$1.toggleMeridiem}]`); | ||
|
|
2238 | + toggle.innerText = lastPicked.meridiem(); | ||
|
|
2239 | + if (!this.validation.isValid(lastPicked.clone.manipulate(lastPicked.hours >= 12 ? -12 : 12, Unit.hours))) { | ||
|
|
2240 | + toggle.classList.add(Namespace.css.disabled); | ||
|
|
2241 | + } | ||
|
|
2242 | + else { | ||
|
|
2243 | + toggle.classList.remove(Namespace.css.disabled); | ||
|
|
2244 | + } | ||
|
|
2245 | + } | ||
|
|
2246 | + timesDiv.style.gridTemplateAreas = `"${this._gridColumns}"`; | ||
|
|
2247 | + } | ||
|
|
2248 | + /** | ||
|
|
2249 | + * Creates the table for the clock display depending on what options are selected. | ||
|
|
2250 | + * @private | ||
|
|
2251 | + */ | ||
|
|
2252 | + _grid(iconTag) { | ||
|
|
2253 | + this._gridColumns = ''; | ||
|
|
2254 | + const top = [], middle = [], bottom = [], separator = document.createElement('div'), upIcon = iconTag(this.optionsStore.options.display.icons.up), downIcon = iconTag(this.optionsStore.options.display.icons.down); | ||
|
|
2255 | + separator.classList.add(Namespace.css.separator, Namespace.css.noHighlight); | ||
|
|
2256 | + const separatorColon = separator.cloneNode(true); | ||
|
|
2257 | + separatorColon.innerHTML = ':'; | ||
|
|
2258 | + const getSeparator = (colon = false) => { | ||
|
|
2259 | + return colon | ||
|
|
2260 | + ? separatorColon.cloneNode(true) | ||
|
|
2261 | + : separator.cloneNode(true); | ||
|
|
2262 | + }; | ||
|
|
2263 | + if (this.optionsStore.options.display.components.hours) { | ||
|
|
2264 | + let divElement = document.createElement('div'); | ||
|
|
2265 | + divElement.setAttribute('title', this.optionsStore.options.localization.incrementHour); | ||
|
|
2266 | + divElement.setAttribute('data-action', ActionTypes$1.incrementHours); | ||
|
|
2267 | + divElement.appendChild(upIcon.cloneNode(true)); | ||
|
|
2268 | + top.push(divElement); | ||
|
|
2269 | + divElement = document.createElement('div'); | ||
|
|
2270 | + divElement.setAttribute('title', this.optionsStore.options.localization.pickHour); | ||
|
|
2271 | + divElement.setAttribute('data-action', ActionTypes$1.showHours); | ||
|
|
2272 | + divElement.setAttribute('data-time-component', Unit.hours); | ||
|
|
2273 | + middle.push(divElement); | ||
|
|
2274 | + divElement = document.createElement('div'); | ||
|
|
2275 | + divElement.setAttribute('title', this.optionsStore.options.localization.decrementHour); | ||
|
|
2276 | + divElement.setAttribute('data-action', ActionTypes$1.decrementHours); | ||
|
|
2277 | + divElement.appendChild(downIcon.cloneNode(true)); | ||
|
|
2278 | + bottom.push(divElement); | ||
|
|
2279 | + this._gridColumns += 'a'; | ||
|
|
2280 | + } | ||
|
|
2281 | + if (this.optionsStore.options.display.components.minutes) { | ||
|
|
2282 | + this._gridColumns += ' a'; | ||
|
|
2283 | + if (this.optionsStore.options.display.components.hours) { | ||
|
|
2284 | + top.push(getSeparator()); | ||
|
|
2285 | + middle.push(getSeparator(true)); | ||
|
|
2286 | + bottom.push(getSeparator()); | ||
|
|
2287 | + this._gridColumns += ' a'; | ||
|
|
2288 | + } | ||
|
|
2289 | + let divElement = document.createElement('div'); | ||
|
|
2290 | + divElement.setAttribute('title', this.optionsStore.options.localization.incrementMinute); | ||
|
|
2291 | + divElement.setAttribute('data-action', ActionTypes$1.incrementMinutes); | ||
|
|
2292 | + divElement.appendChild(upIcon.cloneNode(true)); | ||
|
|
2293 | + top.push(divElement); | ||
|
|
2294 | + divElement = document.createElement('div'); | ||
|
|
2295 | + divElement.setAttribute('title', this.optionsStore.options.localization.pickMinute); | ||
|
|
2296 | + divElement.setAttribute('data-action', ActionTypes$1.showMinutes); | ||
|
|
2297 | + divElement.setAttribute('data-time-component', Unit.minutes); | ||
|
|
2298 | + middle.push(divElement); | ||
|
|
2299 | + divElement = document.createElement('div'); | ||
|
|
2300 | + divElement.setAttribute('title', this.optionsStore.options.localization.decrementMinute); | ||
|
|
2301 | + divElement.setAttribute('data-action', ActionTypes$1.decrementMinutes); | ||
|
|
2302 | + divElement.appendChild(downIcon.cloneNode(true)); | ||
|
|
2303 | + bottom.push(divElement); | ||
|
|
2304 | + } | ||
|
|
2305 | + if (this.optionsStore.options.display.components.seconds) { | ||
|
|
2306 | + this._gridColumns += ' a'; | ||
|
|
2307 | + if (this.optionsStore.options.display.components.minutes) { | ||
|
|
2308 | + top.push(getSeparator()); | ||
|
|
2309 | + middle.push(getSeparator(true)); | ||
|
|
2310 | + bottom.push(getSeparator()); | ||
|
|
2311 | + this._gridColumns += ' a'; | ||
|
|
2312 | + } | ||
|
|
2313 | + let divElement = document.createElement('div'); | ||
|
|
2314 | + divElement.setAttribute('title', this.optionsStore.options.localization.incrementSecond); | ||
|
|
2315 | + divElement.setAttribute('data-action', ActionTypes$1.incrementSeconds); | ||
|
|
2316 | + divElement.appendChild(upIcon.cloneNode(true)); | ||
|
|
2317 | + top.push(divElement); | ||
|
|
2318 | + divElement = document.createElement('div'); | ||
|
|
2319 | + divElement.setAttribute('title', this.optionsStore.options.localization.pickSecond); | ||
|
|
2320 | + divElement.setAttribute('data-action', ActionTypes$1.showSeconds); | ||
|
|
2321 | + divElement.setAttribute('data-time-component', Unit.seconds); | ||
|
|
2322 | + middle.push(divElement); | ||
|
|
2323 | + divElement = document.createElement('div'); | ||
|
|
2324 | + divElement.setAttribute('title', this.optionsStore.options.localization.decrementSecond); | ||
|
|
2325 | + divElement.setAttribute('data-action', ActionTypes$1.decrementSeconds); | ||
|
|
2326 | + divElement.appendChild(downIcon.cloneNode(true)); | ||
|
|
2327 | + bottom.push(divElement); | ||
|
|
2328 | + } | ||
|
|
2329 | + if (!this.optionsStore.options.display.components.useTwentyfourHour) { | ||
|
|
2330 | + this._gridColumns += ' a'; | ||
|
|
2331 | + let divElement = getSeparator(); | ||
|
|
2332 | + top.push(divElement); | ||
|
|
2333 | + let button = document.createElement('button'); | ||
|
|
2334 | + button.setAttribute('title', this.optionsStore.options.localization.toggleMeridiem); | ||
|
|
2335 | + button.setAttribute('data-action', ActionTypes$1.toggleMeridiem); | ||
|
|
2336 | + button.setAttribute('tabindex', '-1'); | ||
|
|
2337 | + if (Namespace.css.toggleMeridiem.includes(',')) { //todo move this to paint function? | ||
|
|
2338 | + button.classList.add(...Namespace.css.toggleMeridiem.split(',')); | ||
|
|
2339 | + } | ||
|
|
2340 | + else | ||
|
|
2341 | + button.classList.add(Namespace.css.toggleMeridiem); | ||
|
|
2342 | + divElement = document.createElement('div'); | ||
|
|
2343 | + divElement.classList.add(Namespace.css.noHighlight); | ||
|
|
2344 | + divElement.appendChild(button); | ||
|
|
2345 | + middle.push(divElement); | ||
|
|
2346 | + divElement = getSeparator(); | ||
|
|
2347 | + bottom.push(divElement); | ||
|
|
2348 | + } | ||
|
|
2349 | + this._gridColumns = this._gridColumns.trim(); | ||
|
|
2350 | + return [...top, ...middle, ...bottom]; | ||
|
|
2351 | + } | ||
|
|
2352 | + } | ||
|
|
2353 | + | ||
|
|
2354 | + /** | ||
|
|
2355 | + * Creates and updates the grid for `hours` | ||
|
|
2356 | + */ | ||
|
|
2357 | + class HourDisplay { | ||
|
|
2358 | + constructor() { | ||
|
|
2359 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2360 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2361 | + } | ||
|
|
2362 | + /** | ||
|
|
2363 | + * Build the container html for the display | ||
|
|
2364 | + * @private | ||
|
|
2365 | + */ | ||
|
|
2366 | + getPicker() { | ||
|
|
2367 | + const container = document.createElement('div'); | ||
|
|
2368 | + container.classList.add(Namespace.css.hourContainer); | ||
|
|
2369 | + for (let i = 0; i < | ||
|
|
2370 | + (this.optionsStore.options.display.components.useTwentyfourHour ? 24 : 12); i++) { | ||
|
|
2371 | + const div = document.createElement('div'); | ||
|
|
2372 | + div.setAttribute('data-action', ActionTypes$1.selectHour); | ||
|
|
2373 | + container.appendChild(div); | ||
|
|
2374 | + } | ||
|
|
2375 | + return container; | ||
|
|
2376 | + } | ||
|
|
2377 | + /** | ||
|
|
2378 | + * Populates the grid and updates enabled states | ||
|
|
2379 | + * @private | ||
|
|
2380 | + */ | ||
|
|
2381 | + _update(widget, paint) { | ||
|
|
2382 | + const container = widget.getElementsByClassName(Namespace.css.hourContainer)[0]; | ||
|
|
2383 | + let innerDate = this.optionsStore.viewDate.clone.startOf(Unit.date); | ||
|
|
2384 | + container | ||
|
|
2385 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectHour}"]`) | ||
|
|
2386 | + .forEach((containerClone) => { | ||
|
|
2387 | + let classes = []; | ||
|
|
2388 | + classes.push(Namespace.css.hour); | ||
|
|
2389 | + if (!this.validation.isValid(innerDate, Unit.hours)) { | ||
|
|
2390 | + classes.push(Namespace.css.disabled); | ||
|
|
2391 | + } | ||
|
|
2392 | + paint(Unit.hours, innerDate, classes, containerClone); | ||
|
|
2393 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
2394 | + containerClone.classList.add(...classes); | ||
|
|
2395 | + containerClone.setAttribute('data-value', `${innerDate.hours}`); | ||
|
|
2396 | + containerClone.innerText = this.optionsStore.options.display.components | ||
|
|
2397 | + .useTwentyfourHour | ||
|
|
2398 | + ? innerDate.hoursFormatted | ||
|
|
2399 | + : innerDate.twelveHoursFormatted; | ||
|
|
2400 | + innerDate.manipulate(1, Unit.hours); | ||
|
|
2401 | + }); | ||
|
|
2402 | + } | ||
|
|
2403 | + } | ||
|
|
2404 | + | ||
|
|
2405 | + /** | ||
|
|
2406 | + * Creates and updates the grid for `minutes` | ||
|
|
2407 | + */ | ||
|
|
2408 | + class MinuteDisplay { | ||
|
|
2409 | + constructor() { | ||
|
|
2410 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2411 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2412 | + } | ||
|
|
2413 | + /** | ||
|
|
2414 | + * Build the container html for the display | ||
|
|
2415 | + * @private | ||
|
|
2416 | + */ | ||
|
|
2417 | + getPicker() { | ||
|
|
2418 | + const container = document.createElement('div'); | ||
|
|
2419 | + container.classList.add(Namespace.css.minuteContainer); | ||
|
|
2420 | + let step = this.optionsStore.options.stepping === 1 | ||
|
|
2421 | + ? 5 | ||
|
|
2422 | + : this.optionsStore.options.stepping; | ||
|
|
2423 | + for (let i = 0; i < 60 / step; i++) { | ||
|
|
2424 | + const div = document.createElement('div'); | ||
|
|
2425 | + div.setAttribute('data-action', ActionTypes$1.selectMinute); | ||
|
|
2426 | + container.appendChild(div); | ||
|
|
2427 | + } | ||
|
|
2428 | + return container; | ||
|
|
2429 | + } | ||
|
|
2430 | + /** | ||
|
|
2431 | + * Populates the grid and updates enabled states | ||
|
|
2432 | + * @private | ||
|
|
2433 | + */ | ||
|
|
2434 | + _update(widget, paint) { | ||
|
|
2435 | + const container = widget.getElementsByClassName(Namespace.css.minuteContainer)[0]; | ||
|
|
2436 | + let innerDate = this.optionsStore.viewDate.clone.startOf(Unit.hours); | ||
|
|
2437 | + let step = this.optionsStore.options.stepping === 1 | ||
|
|
2438 | + ? 5 | ||
|
|
2439 | + : this.optionsStore.options.stepping; | ||
|
|
2440 | + container | ||
|
|
2441 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectMinute}"]`) | ||
|
|
2442 | + .forEach((containerClone) => { | ||
|
|
2443 | + let classes = []; | ||
|
|
2444 | + classes.push(Namespace.css.minute); | ||
|
|
2445 | + if (!this.validation.isValid(innerDate, Unit.minutes)) { | ||
|
|
2446 | + classes.push(Namespace.css.disabled); | ||
|
|
2447 | + } | ||
|
|
2448 | + paint(Unit.minutes, innerDate, classes, containerClone); | ||
|
|
2449 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
2450 | + containerClone.classList.add(...classes); | ||
|
|
2451 | + containerClone.setAttribute('data-value', `${innerDate.minutes}`); | ||
|
|
2452 | + containerClone.innerText = innerDate.minutesFormatted; | ||
|
|
2453 | + innerDate.manipulate(step, Unit.minutes); | ||
|
|
2454 | + }); | ||
|
|
2455 | + } | ||
|
|
2456 | + } | ||
|
|
2457 | + | ||
|
|
2458 | + /** | ||
|
|
2459 | + * Creates and updates the grid for `seconds` | ||
|
|
2460 | + */ | ||
|
|
2461 | + class secondDisplay { | ||
|
|
2462 | + constructor() { | ||
|
|
2463 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2464 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2465 | + } | ||
|
|
2466 | + /** | ||
|
|
2467 | + * Build the container html for the display | ||
|
|
2468 | + * @private | ||
|
|
2469 | + */ | ||
|
|
2470 | + getPicker() { | ||
|
|
2471 | + const container = document.createElement('div'); | ||
|
|
2472 | + container.classList.add(Namespace.css.secondContainer); | ||
|
|
2473 | + for (let i = 0; i < 12; i++) { | ||
|
|
2474 | + const div = document.createElement('div'); | ||
|
|
2475 | + div.setAttribute('data-action', ActionTypes$1.selectSecond); | ||
|
|
2476 | + container.appendChild(div); | ||
|
|
2477 | + } | ||
|
|
2478 | + return container; | ||
|
|
2479 | + } | ||
|
|
2480 | + /** | ||
|
|
2481 | + * Populates the grid and updates enabled states | ||
|
|
2482 | + * @private | ||
|
|
2483 | + */ | ||
|
|
2484 | + _update(widget, paint) { | ||
|
|
2485 | + const container = widget.getElementsByClassName(Namespace.css.secondContainer)[0]; | ||
|
|
2486 | + let innerDate = this.optionsStore.viewDate.clone.startOf(Unit.minutes); | ||
|
|
2487 | + container | ||
|
|
2488 | + .querySelectorAll(`[data-action="${ActionTypes$1.selectSecond}"]`) | ||
|
|
2489 | + .forEach((containerClone) => { | ||
|
|
2490 | + let classes = []; | ||
|
|
2491 | + classes.push(Namespace.css.second); | ||
|
|
2492 | + if (!this.validation.isValid(innerDate, Unit.seconds)) { | ||
|
|
2493 | + classes.push(Namespace.css.disabled); | ||
|
|
2494 | + } | ||
|
|
2495 | + paint(Unit.seconds, innerDate, classes, containerClone); | ||
|
|
2496 | + containerClone.classList.remove(...containerClone.classList); | ||
|
|
2497 | + containerClone.classList.add(...classes); | ||
|
|
2498 | + containerClone.setAttribute('data-value', `${innerDate.seconds}`); | ||
|
|
2499 | + containerClone.innerText = innerDate.secondsFormatted; | ||
|
|
2500 | + innerDate.manipulate(5, Unit.seconds); | ||
|
|
2501 | + }); | ||
|
|
2502 | + } | ||
|
|
2503 | + } | ||
|
|
2504 | + | ||
|
|
2505 | + /** | ||
|
|
2506 | + * Provides a collapse functionality to the view changes | ||
|
|
2507 | + */ | ||
|
|
2508 | + class Collapse { | ||
|
|
2509 | + /** | ||
|
|
2510 | + * Flips the show/hide state of `target` | ||
|
|
2511 | + * @param target html element to affect. | ||
|
|
2512 | + */ | ||
|
|
2513 | + static toggle(target) { | ||
|
|
2514 | + if (target.classList.contains(Namespace.css.show)) { | ||
|
|
2515 | + this.hide(target); | ||
|
|
2516 | + } | ||
|
|
2517 | + else { | ||
|
|
2518 | + this.show(target); | ||
|
|
2519 | + } | ||
|
|
2520 | + } | ||
|
|
2521 | + /** | ||
|
|
2522 | + * Skips any animation or timeouts and immediately set the element to show. | ||
|
|
2523 | + * @param target | ||
|
|
2524 | + */ | ||
|
|
2525 | + static showImmediately(target) { | ||
|
|
2526 | + target.classList.remove(Namespace.css.collapsing); | ||
|
|
2527 | + target.classList.add(Namespace.css.collapse, Namespace.css.show); | ||
|
|
2528 | + target.style.height = ''; | ||
|
|
2529 | + } | ||
|
|
2530 | + /** | ||
|
|
2531 | + * If `target` is not already showing, then show after the animation. | ||
|
|
2532 | + * @param target | ||
|
|
2533 | + */ | ||
|
|
2534 | + static show(target) { | ||
|
|
2535 | + if (target.classList.contains(Namespace.css.collapsing) || | ||
|
|
2536 | + target.classList.contains(Namespace.css.show)) | ||
|
|
2537 | + return; | ||
|
|
2538 | + const complete = () => { | ||
|
|
2539 | + Collapse.showImmediately(target); | ||
|
|
2540 | + }; | ||
|
|
2541 | + target.style.height = '0'; | ||
|
|
2542 | + target.classList.remove(Namespace.css.collapse); | ||
|
|
2543 | + target.classList.add(Namespace.css.collapsing); | ||
|
|
2544 | + setTimeout(complete, this.getTransitionDurationFromElement(target)); | ||
|
|
2545 | + target.style.height = `${target.scrollHeight}px`; | ||
|
|
2546 | + } | ||
|
|
2547 | + /** | ||
|
|
2548 | + * Skips any animation or timeouts and immediately set the element to hide. | ||
|
|
2549 | + * @param target | ||
|
|
2550 | + */ | ||
|
|
2551 | + static hideImmediately(target) { | ||
|
|
2552 | + if (!target) | ||
|
|
2553 | + return; | ||
|
|
2554 | + target.classList.remove(Namespace.css.collapsing, Namespace.css.show); | ||
|
|
2555 | + target.classList.add(Namespace.css.collapse); | ||
|
|
2556 | + } | ||
|
|
2557 | + /** | ||
|
|
2558 | + * If `target` is not already hidden, then hide after the animation. | ||
|
|
2559 | + * @param target HTML Element | ||
|
|
2560 | + */ | ||
|
|
2561 | + static hide(target) { | ||
|
|
2562 | + if (target.classList.contains(Namespace.css.collapsing) || | ||
|
|
2563 | + !target.classList.contains(Namespace.css.show)) | ||
|
|
2564 | + return; | ||
|
|
2565 | + const complete = () => { | ||
|
|
2566 | + Collapse.hideImmediately(target); | ||
|
|
2567 | + }; | ||
|
|
2568 | + target.style.height = `${target.getBoundingClientRect()['height']}px`; | ||
|
|
2569 | + const reflow = (element) => element.offsetHeight; | ||
|
|
2570 | + reflow(target); | ||
|
|
2571 | + target.classList.remove(Namespace.css.collapse, Namespace.css.show); | ||
|
|
2572 | + target.classList.add(Namespace.css.collapsing); | ||
|
|
2573 | + target.style.height = ''; | ||
|
|
2574 | + setTimeout(complete, this.getTransitionDurationFromElement(target)); | ||
|
|
2575 | + } | ||
|
|
2576 | + } | ||
|
|
2577 | + /** | ||
|
|
2578 | + * Gets the transition duration from the `element` by getting css properties | ||
|
|
2579 | + * `transition-duration` and `transition-delay` | ||
|
|
2580 | + * @param element HTML Element | ||
|
|
2581 | + */ | ||
|
|
2582 | + Collapse.getTransitionDurationFromElement = (element) => { | ||
|
|
2583 | + if (!element) { | ||
|
|
2584 | + return 0; | ||
|
|
2585 | + } | ||
|
|
2586 | + // Get transition-duration of the element | ||
|
|
2587 | + let { transitionDuration, transitionDelay } = window.getComputedStyle(element); | ||
|
|
2588 | + const floatTransitionDuration = Number.parseFloat(transitionDuration); | ||
|
|
2589 | + const floatTransitionDelay = Number.parseFloat(transitionDelay); | ||
|
|
2590 | + // Return 0 if element or transition duration is not found | ||
|
|
2591 | + if (!floatTransitionDuration && !floatTransitionDelay) { | ||
|
|
2592 | + return 0; | ||
|
|
2593 | + } | ||
|
|
2594 | + // If multiple durations are defined, take the first | ||
|
|
2595 | + transitionDuration = transitionDuration.split(',')[0]; | ||
|
|
2596 | + transitionDelay = transitionDelay.split(',')[0]; | ||
|
|
2597 | + return ((Number.parseFloat(transitionDuration) + | ||
|
|
2598 | + Number.parseFloat(transitionDelay)) * | ||
|
|
2599 | + 1000); | ||
|
|
2600 | + }; | ||
|
|
2601 | + | ||
|
|
2602 | + /** | ||
|
|
2603 | + * Main class for all things display related. | ||
|
|
2604 | + */ | ||
|
|
2605 | + class Display { | ||
|
|
2606 | + constructor() { | ||
|
|
2607 | + this._isVisible = false; | ||
|
|
2608 | + /** | ||
|
|
2609 | + * A document click event to hide the widget if click is outside | ||
|
|
2610 | + * @private | ||
|
|
2611 | + * @param e MouseEvent | ||
|
|
2612 | + */ | ||
|
|
2613 | + this._documentClickEvent = (e) => { | ||
|
|
2614 | + if (this.optionsStore.options.debug || window.debug) | ||
|
|
2615 | + return; | ||
|
|
2616 | + if (this._isVisible && | ||
|
|
2617 | + !e.composedPath().includes(this.widget) && // click inside the widget | ||
|
|
2618 | + !e.composedPath()?.includes(this.optionsStore.element) // click on the element | ||
|
|
2619 | + ) { | ||
|
|
2620 | + this.hide(); | ||
|
|
2621 | + } | ||
|
|
2622 | + }; | ||
|
|
2623 | + /** | ||
|
|
2624 | + * Click event for any action like selecting a date | ||
|
|
2625 | + * @param e MouseEvent | ||
|
|
2626 | + * @private | ||
|
|
2627 | + */ | ||
|
|
2628 | + this._actionsClickEvent = (e) => { | ||
|
|
2629 | + this._eventEmitters.action.emit({ e: e }); | ||
|
|
2630 | + }; | ||
|
|
2631 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
2632 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
2633 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
2634 | + this.dateDisplay = serviceLocator.locate(DateDisplay); | ||
|
|
2635 | + this.monthDisplay = serviceLocator.locate(MonthDisplay); | ||
|
|
2636 | + this.yearDisplay = serviceLocator.locate(YearDisplay); | ||
|
|
2637 | + this.decadeDisplay = serviceLocator.locate(DecadeDisplay); | ||
|
|
2638 | + this.timeDisplay = serviceLocator.locate(TimeDisplay); | ||
|
|
2639 | + this.hourDisplay = serviceLocator.locate(HourDisplay); | ||
|
|
2640 | + this.minuteDisplay = serviceLocator.locate(MinuteDisplay); | ||
|
|
2641 | + this.secondDisplay = serviceLocator.locate(secondDisplay); | ||
|
|
2642 | + this._eventEmitters = serviceLocator.locate(EventEmitters); | ||
|
|
2643 | + this._widget = undefined; | ||
|
|
2644 | + this._eventEmitters.updateDisplay.subscribe((result) => { | ||
|
|
2645 | + this._update(result); | ||
|
|
2646 | + }); | ||
|
|
2647 | + } | ||
|
|
2648 | + /** | ||
|
|
2649 | + * Returns the widget body or undefined | ||
|
|
2650 | + * @private | ||
|
|
2651 | + */ | ||
|
|
2652 | + get widget() { | ||
|
|
2653 | + return this._widget; | ||
|
|
2654 | + } | ||
|
|
2655 | + /** | ||
|
|
2656 | + * Returns this visible state of the picker (shown) | ||
|
|
2657 | + */ | ||
|
|
2658 | + get isVisible() { | ||
|
|
2659 | + return this._isVisible; | ||
|
|
2660 | + } | ||
|
|
2661 | + /** | ||
|
|
2662 | + * Updates the table for a particular unit. Used when an option as changed or | ||
|
|
2663 | + * whenever the class list might need to be refreshed. | ||
|
|
2664 | + * @param unit | ||
|
|
2665 | + * @private | ||
|
|
2666 | + */ | ||
|
|
2667 | + _update(unit) { | ||
|
|
2668 | + if (!this.widget) | ||
|
|
2669 | + return; | ||
|
|
2670 | + //todo do I want some kind of error catching or other guards here? | ||
|
|
2671 | + switch (unit) { | ||
|
|
2672 | + case Unit.seconds: | ||
|
|
2673 | + this.secondDisplay._update(this.widget, this.paint); | ||
|
|
2674 | + break; | ||
|
|
2675 | + case Unit.minutes: | ||
|
|
2676 | + this.minuteDisplay._update(this.widget, this.paint); | ||
|
|
2677 | + break; | ||
|
|
2678 | + case Unit.hours: | ||
|
|
2679 | + this.hourDisplay._update(this.widget, this.paint); | ||
|
|
2680 | + break; | ||
|
|
2681 | + case Unit.date: | ||
|
|
2682 | + this.dateDisplay._update(this.widget, this.paint); | ||
|
|
2683 | + break; | ||
|
|
2684 | + case Unit.month: | ||
|
|
2685 | + this.monthDisplay._update(this.widget, this.paint); | ||
|
|
2686 | + break; | ||
|
|
2687 | + case Unit.year: | ||
|
|
2688 | + this.yearDisplay._update(this.widget, this.paint); | ||
|
|
2689 | + break; | ||
|
|
2690 | + case 'clock': | ||
|
|
2691 | + if (!this._hasTime) | ||
|
|
2692 | + break; | ||
|
|
2693 | + this.timeDisplay._update(this.widget); | ||
|
|
2694 | + this._update(Unit.hours); | ||
|
|
2695 | + this._update(Unit.minutes); | ||
|
|
2696 | + this._update(Unit.seconds); | ||
|
|
2697 | + break; | ||
|
|
2698 | + case 'calendar': | ||
|
|
2699 | + this._update(Unit.date); | ||
|
|
2700 | + this._update(Unit.year); | ||
|
|
2701 | + this._update(Unit.month); | ||
|
|
2702 | + this.decadeDisplay._update(this.widget, this.paint); | ||
|
|
2703 | + this._updateCalendarHeader(); | ||
|
|
2704 | + break; | ||
|
|
2705 | + case 'all': | ||
|
|
2706 | + if (this._hasTime) { | ||
|
|
2707 | + this._update('clock'); | ||
|
|
2708 | + } | ||
|
|
2709 | + if (this._hasDate) { | ||
|
|
2710 | + this._update('calendar'); | ||
|
|
2711 | + } | ||
|
|
2712 | + } | ||
|
|
2713 | + } | ||
|
|
2714 | + // noinspection JSUnusedLocalSymbols | ||
|
|
2715 | + /** | ||
|
|
2716 | + * Allows developers to add/remove classes from an element. | ||
|
|
2717 | + * @param _unit | ||
|
|
2718 | + * @param _date | ||
|
|
2719 | + * @param _classes | ||
|
|
2720 | + * @param _element | ||
|
|
2721 | + */ | ||
|
|
2722 | + paint(_unit, _date, _classes, _element) { | ||
|
|
2723 | + // implemented in plugin | ||
|
|
2724 | + } | ||
|
|
2725 | + /** | ||
|
|
2726 | + * Shows the picker and creates a Popper instance if needed. | ||
|
|
2727 | + * Add document click event to hide when clicking outside the picker. | ||
|
|
2728 | + * fires Events#show | ||
|
|
2729 | + */ | ||
|
|
2730 | + show() { | ||
|
|
2731 | + if (this.widget == undefined) { | ||
|
|
2732 | + if (this.dates.picked.length == 0) { | ||
|
|
2733 | + if (this.optionsStore.options.useCurrent && | ||
|
|
2734 | + !this.optionsStore.options.defaultDate) { | ||
|
|
2735 | + const date = new DateTime().setLocale(this.optionsStore.options.localization.locale); | ||
|
|
2736 | + if (!this.optionsStore.options.keepInvalid) { | ||
|
|
2737 | + let tries = 0; | ||
|
|
2738 | + let direction = 1; | ||
|
|
2739 | + if (this.optionsStore.options.restrictions.maxDate?.isBefore(date)) { | ||
|
|
2740 | + direction = -1; | ||
|
|
2741 | + } | ||
|
|
2742 | + while (!this.validation.isValid(date)) { | ||
|
|
2743 | + date.manipulate(direction, Unit.date); | ||
|
|
2744 | + if (tries > 31) | ||
|
|
2745 | + break; | ||
|
|
2746 | + tries++; | ||
|
|
2747 | + } | ||
|
|
2748 | + } | ||
|
|
2749 | + this.dates.setValue(date); | ||
|
|
2750 | + } | ||
|
|
2751 | + if (this.optionsStore.options.defaultDate) { | ||
|
|
2752 | + this.dates.setValue(this.optionsStore.options.defaultDate); | ||
|
|
2753 | + } | ||
|
|
2754 | + } | ||
|
|
2755 | + this._buildWidget(); | ||
|
|
2756 | + this._updateTheme(); | ||
|
|
2757 | + // If modeView is only clock | ||
|
|
2758 | + const onlyClock = this._hasTime && !this._hasDate; | ||
|
|
2759 | + // reset the view to the clock if there's no date components | ||
|
|
2760 | + if (onlyClock) { | ||
|
|
2761 | + this.optionsStore.currentView = 'clock'; | ||
|
|
2762 | + this._eventEmitters.action.emit({ | ||
|
|
2763 | + e: null, | ||
|
|
2764 | + action: ActionTypes$1.showClock, | ||
|
|
2765 | + }); | ||
|
|
2766 | + } | ||
|
|
2767 | + // otherwise return to the calendar view | ||
|
|
2768 | + if (!this.optionsStore.currentCalendarViewMode) { | ||
|
|
2769 | + this.optionsStore.currentCalendarViewMode = | ||
|
|
2770 | + this.optionsStore.minimumCalendarViewMode; | ||
|
|
2771 | + } | ||
|
|
2772 | + if (!onlyClock && | ||
|
|
2773 | + this.optionsStore.options.display.viewMode !== 'clock') { | ||
|
|
2774 | + if (this._hasTime) { | ||
|
|
2775 | + if (!this.optionsStore.options.display.sideBySide) { | ||
|
|
2776 | + Collapse.hideImmediately(this.widget.querySelector(`div.${Namespace.css.timeContainer}`)); | ||
|
|
2777 | + } | ||
|
|
2778 | + else { | ||
|
|
2779 | + Collapse.show(this.widget.querySelector(`div.${Namespace.css.timeContainer}`)); | ||
|
|
2780 | + } | ||
|
|
2781 | + } | ||
|
|
2782 | + Collapse.show(this.widget.querySelector(`div.${Namespace.css.dateContainer}`)); | ||
|
|
2783 | + } | ||
|
|
2784 | + if (this._hasDate) { | ||
|
|
2785 | + this._showMode(); | ||
|
|
2786 | + } | ||
|
|
2787 | + if (!this.optionsStore.options.display.inline) { | ||
|
|
2788 | + // If needed to change the parent container | ||
|
|
2789 | + const container = this.optionsStore.options?.container || document.body; | ||
|
|
2790 | + container.appendChild(this.widget); | ||
|
|
2791 | + this.createPopup(this.optionsStore.element, this.widget, { | ||
|
|
2792 | + modifiers: [{ name: 'eventListeners', enabled: true }], | ||
|
|
2793 | + //#2400 | ||
|
|
2794 | + placement: document.documentElement.dir === 'rtl' | ||
|
|
2795 | + ? 'bottom-end' | ||
|
|
2796 | + : 'bottom-start', | ||
|
|
2797 | + }).then(); | ||
|
|
2798 | + } | ||
|
|
2799 | + else { | ||
|
|
2800 | + this.optionsStore.element.appendChild(this.widget); | ||
|
|
2801 | + } | ||
|
|
2802 | + if (this.optionsStore.options.display.viewMode == 'clock') { | ||
|
|
2803 | + this._eventEmitters.action.emit({ | ||
|
|
2804 | + e: null, | ||
|
|
2805 | + action: ActionTypes$1.showClock, | ||
|
|
2806 | + }); | ||
|
|
2807 | + } | ||
|
|
2808 | + this.widget | ||
|
|
2809 | + .querySelectorAll('[data-action]') | ||
|
|
2810 | + .forEach((element) => element.addEventListener('click', this._actionsClickEvent)); | ||
|
|
2811 | + // show the clock when using sideBySide | ||
|
|
2812 | + if (this._hasTime && this.optionsStore.options.display.sideBySide) { | ||
|
|
2813 | + this.timeDisplay._update(this.widget); | ||
|
|
2814 | + this.widget.getElementsByClassName(Namespace.css.clockContainer)[0].style.display = 'grid'; | ||
|
|
2815 | + } | ||
|
|
2816 | + } | ||
|
|
2817 | + this.widget.classList.add(Namespace.css.show); | ||
|
|
2818 | + if (!this.optionsStore.options.display.inline) { | ||
|
|
2819 | + this.updatePopup(); | ||
|
|
2820 | + document.addEventListener('click', this._documentClickEvent); | ||
|
|
2821 | + } | ||
|
|
2822 | + this._eventEmitters.triggerEvent.emit({ type: Namespace.events.show }); | ||
|
|
2823 | + this._isVisible = true; | ||
|
|
2824 | + } | ||
|
|
2825 | + async createPopup(element, widget, options) { | ||
|
|
2826 | + let createPopperFunction; | ||
|
|
2827 | + if (window?.Popper) { | ||
|
|
2828 | + createPopperFunction = window?.Popper?.createPopper; | ||
|
|
2829 | + } | ||
|
|
2830 | + else { | ||
|
|
2831 | + const { createPopper } = await import('@popperjs/core'); | ||
|
|
2832 | + createPopperFunction = createPopper; | ||
|
|
2833 | + } | ||
|
|
2834 | + if (createPopperFunction) { | ||
|
|
2835 | + this._popperInstance = createPopperFunction(element, widget, options); | ||
|
|
2836 | + } | ||
|
|
2837 | + } | ||
|
|
2838 | + updatePopup() { | ||
|
|
2839 | + this._popperInstance?.update(); | ||
|
|
2840 | + } | ||
|
|
2841 | + /** | ||
|
|
2842 | + * Changes the calendar view mode. E.g. month <-> year | ||
|
|
2843 | + * @param direction -/+ number to move currentViewMode | ||
|
|
2844 | + * @private | ||
|
|
2845 | + */ | ||
|
|
2846 | + _showMode(direction) { | ||
|
|
2847 | + if (!this.widget) { | ||
|
|
2848 | + return; | ||
|
|
2849 | + } | ||
|
|
2850 | + if (direction) { | ||
|
|
2851 | + const max = Math.max(this.optionsStore.minimumCalendarViewMode, Math.min(3, this.optionsStore.currentCalendarViewMode + direction)); | ||
|
|
2852 | + if (this.optionsStore.currentCalendarViewMode == max) | ||
|
|
2853 | + return; | ||
|
|
2854 | + this.optionsStore.currentCalendarViewMode = max; | ||
|
|
2855 | + } | ||
|
|
2856 | + this.widget | ||
|
|
2857 | + .querySelectorAll(`.${Namespace.css.dateContainer} > div:not(.${Namespace.css.calendarHeader}), .${Namespace.css.timeContainer} > div:not(.${Namespace.css.clockContainer})`) | ||
|
|
2858 | + .forEach((e) => (e.style.display = 'none')); | ||
|
|
2859 | + const datePickerMode = CalendarModes[this.optionsStore.currentCalendarViewMode]; | ||
|
|
2860 | + let picker = this.widget.querySelector(`.${datePickerMode.className}`); | ||
|
|
2861 | + switch (datePickerMode.className) { | ||
|
|
2862 | + case Namespace.css.decadesContainer: | ||
|
|
2863 | + this.decadeDisplay._update(this.widget, this.paint); | ||
|
|
2864 | + break; | ||
|
|
2865 | + case Namespace.css.yearsContainer: | ||
|
|
2866 | + this.yearDisplay._update(this.widget, this.paint); | ||
|
|
2867 | + break; | ||
|
|
2868 | + case Namespace.css.monthsContainer: | ||
|
|
2869 | + this.monthDisplay._update(this.widget, this.paint); | ||
|
|
2870 | + break; | ||
|
|
2871 | + case Namespace.css.daysContainer: | ||
|
|
2872 | + this.dateDisplay._update(this.widget, this.paint); | ||
|
|
2873 | + break; | ||
|
|
2874 | + } | ||
|
|
2875 | + picker.style.display = 'grid'; | ||
|
|
2876 | + this._updateCalendarHeader(); | ||
|
|
2877 | + this._eventEmitters.viewUpdate.emit(); | ||
|
|
2878 | + } | ||
|
|
2879 | + /** | ||
|
|
2880 | + * Changes the theme. E.g. light, dark or auto | ||
|
|
2881 | + * @param theme the theme name | ||
|
|
2882 | + * @private | ||
|
|
2883 | + */ | ||
|
|
2884 | + _updateTheme(theme) { | ||
|
|
2885 | + if (!this.widget) { | ||
|
|
2886 | + return; | ||
|
|
2887 | + } | ||
|
|
2888 | + if (theme) { | ||
|
|
2889 | + if (this.optionsStore.options.display.theme === theme) | ||
|
|
2890 | + return; | ||
|
|
2891 | + this.optionsStore.options.display.theme = theme; | ||
|
|
2892 | + } | ||
|
|
2893 | + this.widget.classList.remove('light', 'dark'); | ||
|
|
2894 | + this.widget.classList.add(this._getThemeClass()); | ||
|
|
2895 | + if (this.optionsStore.options.display.theme === 'auto') { | ||
|
|
2896 | + window | ||
|
|
2897 | + .matchMedia(Namespace.css.isDarkPreferredQuery) | ||
|
|
2898 | + .addEventListener('change', () => this._updateTheme()); | ||
|
|
2899 | + } | ||
|
|
2900 | + else { | ||
|
|
2901 | + window | ||
|
|
2902 | + .matchMedia(Namespace.css.isDarkPreferredQuery) | ||
|
|
2903 | + .removeEventListener('change', () => this._updateTheme()); | ||
|
|
2904 | + } | ||
|
|
2905 | + } | ||
|
|
2906 | + _getThemeClass() { | ||
|
|
2907 | + const currentTheme = this.optionsStore.options.display.theme || 'auto'; | ||
|
|
2908 | + const isDarkMode = window.matchMedia && | ||
|
|
2909 | + window.matchMedia(Namespace.css.isDarkPreferredQuery).matches; | ||
|
|
2910 | + switch (currentTheme) { | ||
|
|
2911 | + case 'light': | ||
|
|
2912 | + return Namespace.css.lightTheme; | ||
|
|
2913 | + case 'dark': | ||
|
|
2914 | + return Namespace.css.darkTheme; | ||
|
|
2915 | + case 'auto': | ||
|
|
2916 | + return isDarkMode ? Namespace.css.darkTheme : Namespace.css.lightTheme; | ||
|
|
2917 | + } | ||
|
|
2918 | + } | ||
|
|
2919 | + _updateCalendarHeader() { | ||
|
|
2920 | + const showing = [ | ||
|
|
2921 | + ...this.widget.querySelector(`.${Namespace.css.dateContainer} div[style*="display: grid"]`).classList, | ||
|
|
2922 | + ].find((x) => x.startsWith(Namespace.css.dateContainer)); | ||
|
|
2923 | + const [previous, switcher, next] = this.widget | ||
|
|
2924 | + .getElementsByClassName(Namespace.css.calendarHeader)[0] | ||
|
|
2925 | + .getElementsByTagName('div'); | ||
|
|
2926 | + switch (showing) { | ||
|
|
2927 | + case Namespace.css.decadesContainer: | ||
|
|
2928 | + previous.setAttribute('title', this.optionsStore.options.localization.previousCentury); | ||
|
|
2929 | + switcher.setAttribute('title', ''); | ||
|
|
2930 | + next.setAttribute('title', this.optionsStore.options.localization.nextCentury); | ||
|
|
2931 | + break; | ||
|
|
2932 | + case Namespace.css.yearsContainer: | ||
|
|
2933 | + previous.setAttribute('title', this.optionsStore.options.localization.previousDecade); | ||
|
|
2934 | + switcher.setAttribute('title', this.optionsStore.options.localization.selectDecade); | ||
|
|
2935 | + next.setAttribute('title', this.optionsStore.options.localization.nextDecade); | ||
|
|
2936 | + break; | ||
|
|
2937 | + case Namespace.css.monthsContainer: | ||
|
|
2938 | + previous.setAttribute('title', this.optionsStore.options.localization.previousYear); | ||
|
|
2939 | + switcher.setAttribute('title', this.optionsStore.options.localization.selectYear); | ||
|
|
2940 | + next.setAttribute('title', this.optionsStore.options.localization.nextYear); | ||
|
|
2941 | + break; | ||
|
|
2942 | + case Namespace.css.daysContainer: | ||
|
|
2943 | + previous.setAttribute('title', this.optionsStore.options.localization.previousMonth); | ||
|
|
2944 | + switcher.setAttribute('title', this.optionsStore.options.localization.selectMonth); | ||
|
|
2945 | + next.setAttribute('title', this.optionsStore.options.localization.nextMonth); | ||
|
|
2946 | + switcher.innerText = this.optionsStore.viewDate.format(this.optionsStore.options.localization.dayViewHeaderFormat); | ||
|
|
2947 | + break; | ||
|
|
2948 | + } | ||
|
|
2949 | + switcher.innerText = switcher.getAttribute(showing); | ||
|
|
2950 | + } | ||
|
|
2951 | + /** | ||
|
|
2952 | + * Hides the picker if needed. | ||
|
|
2953 | + * Remove document click event to hide when clicking outside the picker. | ||
|
|
2954 | + * fires Events#hide | ||
|
|
2955 | + */ | ||
|
|
2956 | + hide() { | ||
|
|
2957 | + if (!this.widget || !this._isVisible) | ||
|
|
2958 | + return; | ||
|
|
2959 | + this.widget.classList.remove(Namespace.css.show); | ||
|
|
2960 | + if (this._isVisible) { | ||
|
|
2961 | + this._eventEmitters.triggerEvent.emit({ | ||
|
|
2962 | + type: Namespace.events.hide, | ||
|
|
2963 | + date: this.optionsStore.unset | ||
|
|
2964 | + ? null | ||
|
|
2965 | + : this.dates.lastPicked | ||
|
|
2966 | + ? this.dates.lastPicked.clone | ||
|
|
2967 | + : void 0, | ||
|
|
2968 | + }); | ||
|
|
2969 | + this._isVisible = false; | ||
|
|
2970 | + } | ||
|
|
2971 | + document.removeEventListener('click', this._documentClickEvent); | ||
|
|
2972 | + } | ||
|
|
2973 | + /** | ||
|
|
2974 | + * Toggles the picker's open state. Fires a show/hide event depending. | ||
|
|
2975 | + */ | ||
|
|
2976 | + toggle() { | ||
|
|
2977 | + return this._isVisible ? this.hide() : this.show(); | ||
|
|
2978 | + } | ||
|
|
2979 | + /** | ||
|
|
2980 | + * Removes document and data-action click listener and reset the widget | ||
|
|
2981 | + * @private | ||
|
|
2982 | + */ | ||
|
|
2983 | + _dispose() { | ||
|
|
2984 | + document.removeEventListener('click', this._documentClickEvent); | ||
|
|
2985 | + if (!this.widget) | ||
|
|
2986 | + return; | ||
|
|
2987 | + this.widget | ||
|
|
2988 | + .querySelectorAll('[data-action]') | ||
|
|
2989 | + .forEach((element) => element.removeEventListener('click', this._actionsClickEvent)); | ||
|
|
2990 | + this.widget.parentNode.removeChild(this.widget); | ||
|
|
2991 | + this._widget = undefined; | ||
|
|
2992 | + } | ||
|
|
2993 | + /** | ||
|
|
2994 | + * Builds the widgets html template. | ||
|
|
2995 | + * @private | ||
|
|
2996 | + */ | ||
|
|
2997 | + _buildWidget() { | ||
|
|
2998 | + const template = document.createElement('div'); | ||
|
|
2999 | + template.classList.add(Namespace.css.widget); | ||
|
|
3000 | + const dateView = document.createElement('div'); | ||
|
|
3001 | + dateView.classList.add(Namespace.css.dateContainer); | ||
|
|
3002 | + dateView.append(this.getHeadTemplate(), this.decadeDisplay.getPicker(), this.yearDisplay.getPicker(), this.monthDisplay.getPicker(), this.dateDisplay.getPicker()); | ||
|
|
3003 | + const timeView = document.createElement('div'); | ||
|
|
3004 | + timeView.classList.add(Namespace.css.timeContainer); | ||
|
|
3005 | + timeView.appendChild(this.timeDisplay.getPicker(this._iconTag.bind(this))); | ||
|
|
3006 | + timeView.appendChild(this.hourDisplay.getPicker()); | ||
|
|
3007 | + timeView.appendChild(this.minuteDisplay.getPicker()); | ||
|
|
3008 | + timeView.appendChild(this.secondDisplay.getPicker()); | ||
|
|
3009 | + const toolbar = document.createElement('div'); | ||
|
|
3010 | + toolbar.classList.add(Namespace.css.toolbar); | ||
|
|
3011 | + toolbar.append(...this.getToolbarElements()); | ||
|
|
3012 | + if (this.optionsStore.options.display.inline) { | ||
|
|
3013 | + template.classList.add(Namespace.css.inline); | ||
|
|
3014 | + } | ||
|
|
3015 | + if (this.optionsStore.options.display.calendarWeeks) { | ||
|
|
3016 | + template.classList.add('calendarWeeks'); | ||
|
|
3017 | + } | ||
|
|
3018 | + if (this.optionsStore.options.display.sideBySide && | ||
|
|
3019 | + this._hasDate && | ||
|
|
3020 | + this._hasTime) { | ||
|
|
3021 | + template.classList.add(Namespace.css.sideBySide); | ||
|
|
3022 | + if (this.optionsStore.options.display.toolbarPlacement === 'top') { | ||
|
|
3023 | + template.appendChild(toolbar); | ||
|
|
3024 | + } | ||
|
|
3025 | + const row = document.createElement('div'); | ||
|
|
3026 | + row.classList.add('td-row'); | ||
|
|
3027 | + dateView.classList.add('td-half'); | ||
|
|
3028 | + timeView.classList.add('td-half'); | ||
|
|
3029 | + row.appendChild(dateView); | ||
|
|
3030 | + row.appendChild(timeView); | ||
|
|
3031 | + template.appendChild(row); | ||
|
|
3032 | + if (this.optionsStore.options.display.toolbarPlacement === 'bottom') { | ||
|
|
3033 | + template.appendChild(toolbar); | ||
|
|
3034 | + } | ||
|
|
3035 | + this._widget = template; | ||
|
|
3036 | + return; | ||
|
|
3037 | + } | ||
|
|
3038 | + if (this.optionsStore.options.display.toolbarPlacement === 'top') { | ||
|
|
3039 | + template.appendChild(toolbar); | ||
|
|
3040 | + } | ||
|
|
3041 | + if (this._hasDate) { | ||
|
|
3042 | + if (this._hasTime) { | ||
|
|
3043 | + dateView.classList.add(Namespace.css.collapse); | ||
|
|
3044 | + if (this.optionsStore.options.display.viewMode !== 'clock') | ||
|
|
3045 | + dateView.classList.add(Namespace.css.show); | ||
|
|
3046 | + } | ||
|
|
3047 | + template.appendChild(dateView); | ||
|
|
3048 | + } | ||
|
|
3049 | + if (this._hasTime) { | ||
|
|
3050 | + if (this._hasDate) { | ||
|
|
3051 | + timeView.classList.add(Namespace.css.collapse); | ||
|
|
3052 | + if (this.optionsStore.options.display.viewMode === 'clock') | ||
|
|
3053 | + timeView.classList.add(Namespace.css.show); | ||
|
|
3054 | + } | ||
|
|
3055 | + template.appendChild(timeView); | ||
|
|
3056 | + } | ||
|
|
3057 | + if (this.optionsStore.options.display.toolbarPlacement === 'bottom') { | ||
|
|
3058 | + template.appendChild(toolbar); | ||
|
|
3059 | + } | ||
|
|
3060 | + const arrow = document.createElement('div'); | ||
|
|
3061 | + arrow.classList.add('arrow'); | ||
|
|
3062 | + arrow.setAttribute('data-popper-arrow', ''); | ||
|
|
3063 | + template.appendChild(arrow); | ||
|
|
3064 | + this._widget = template; | ||
|
|
3065 | + } | ||
|
|
3066 | + /** | ||
|
|
3067 | + * Returns true if the hours, minutes, or seconds component is turned on | ||
|
|
3068 | + */ | ||
|
|
3069 | + get _hasTime() { | ||
|
|
3070 | + return (this.optionsStore.options.display.components.clock && | ||
|
|
3071 | + (this.optionsStore.options.display.components.hours || | ||
|
|
3072 | + this.optionsStore.options.display.components.minutes || | ||
|
|
3073 | + this.optionsStore.options.display.components.seconds)); | ||
|
|
3074 | + } | ||
|
|
3075 | + /** | ||
|
|
3076 | + * Returns true if the year, month, or date component is turned on | ||
|
|
3077 | + */ | ||
|
|
3078 | + get _hasDate() { | ||
|
|
3079 | + return (this.optionsStore.options.display.components.calendar && | ||
|
|
3080 | + (this.optionsStore.options.display.components.year || | ||
|
|
3081 | + this.optionsStore.options.display.components.month || | ||
|
|
3082 | + this.optionsStore.options.display.components.date)); | ||
|
|
3083 | + } | ||
|
|
3084 | + /** | ||
|
|
3085 | + * Get the toolbar html based on options like buttons.today | ||
|
|
3086 | + * @private | ||
|
|
3087 | + */ | ||
|
|
3088 | + getToolbarElements() { | ||
|
|
3089 | + const toolbar = []; | ||
|
|
3090 | + if (this.optionsStore.options.display.buttons.today) { | ||
|
|
3091 | + const div = document.createElement('div'); | ||
|
|
3092 | + div.setAttribute('data-action', ActionTypes$1.today); | ||
|
|
3093 | + div.setAttribute('title', this.optionsStore.options.localization.today); | ||
|
|
3094 | + div.appendChild(this._iconTag(this.optionsStore.options.display.icons.today)); | ||
|
|
3095 | + toolbar.push(div); | ||
|
|
3096 | + } | ||
|
|
3097 | + if (!this.optionsStore.options.display.sideBySide && | ||
|
|
3098 | + this._hasDate && | ||
|
|
3099 | + this._hasTime) { | ||
|
|
3100 | + let title, icon; | ||
|
|
3101 | + if (this.optionsStore.options.display.viewMode === 'clock') { | ||
|
|
3102 | + title = this.optionsStore.options.localization.selectDate; | ||
|
|
3103 | + icon = this.optionsStore.options.display.icons.date; | ||
|
|
3104 | + } | ||
|
|
3105 | + else { | ||
|
|
3106 | + title = this.optionsStore.options.localization.selectTime; | ||
|
|
3107 | + icon = this.optionsStore.options.display.icons.time; | ||
|
|
3108 | + } | ||
|
|
3109 | + const div = document.createElement('div'); | ||
|
|
3110 | + div.setAttribute('data-action', ActionTypes$1.togglePicker); | ||
|
|
3111 | + div.setAttribute('title', title); | ||
|
|
3112 | + div.appendChild(this._iconTag(icon)); | ||
|
|
3113 | + toolbar.push(div); | ||
|
|
3114 | + } | ||
|
|
3115 | + if (this.optionsStore.options.display.buttons.clear) { | ||
|
|
3116 | + const div = document.createElement('div'); | ||
|
|
3117 | + div.setAttribute('data-action', ActionTypes$1.clear); | ||
|
|
3118 | + div.setAttribute('title', this.optionsStore.options.localization.clear); | ||
|
|
3119 | + div.appendChild(this._iconTag(this.optionsStore.options.display.icons.clear)); | ||
|
|
3120 | + toolbar.push(div); | ||
|
|
3121 | + } | ||
|
|
3122 | + if (this.optionsStore.options.display.buttons.close) { | ||
|
|
3123 | + const div = document.createElement('div'); | ||
|
|
3124 | + div.setAttribute('data-action', ActionTypes$1.close); | ||
|
|
3125 | + div.setAttribute('title', this.optionsStore.options.localization.close); | ||
|
|
3126 | + div.appendChild(this._iconTag(this.optionsStore.options.display.icons.close)); | ||
|
|
3127 | + toolbar.push(div); | ||
|
|
3128 | + } | ||
|
|
3129 | + return toolbar; | ||
|
|
3130 | + } | ||
|
|
3131 | + /*** | ||
|
|
3132 | + * Builds the base header template with next and previous icons | ||
|
|
3133 | + * @private | ||
|
|
3134 | + */ | ||
|
|
3135 | + getHeadTemplate() { | ||
|
|
3136 | + const calendarHeader = document.createElement('div'); | ||
|
|
3137 | + calendarHeader.classList.add(Namespace.css.calendarHeader); | ||
|
|
3138 | + const previous = document.createElement('div'); | ||
|
|
3139 | + previous.classList.add(Namespace.css.previous); | ||
|
|
3140 | + previous.setAttribute('data-action', ActionTypes$1.previous); | ||
|
|
3141 | + previous.appendChild(this._iconTag(this.optionsStore.options.display.icons.previous)); | ||
|
|
3142 | + const switcher = document.createElement('div'); | ||
|
|
3143 | + switcher.classList.add(Namespace.css.switch); | ||
|
|
3144 | + switcher.setAttribute('data-action', ActionTypes$1.changeCalendarView); | ||
|
|
3145 | + const next = document.createElement('div'); | ||
|
|
3146 | + next.classList.add(Namespace.css.next); | ||
|
|
3147 | + next.setAttribute('data-action', ActionTypes$1.next); | ||
|
|
3148 | + next.appendChild(this._iconTag(this.optionsStore.options.display.icons.next)); | ||
|
|
3149 | + calendarHeader.append(previous, switcher, next); | ||
|
|
3150 | + return calendarHeader; | ||
|
|
3151 | + } | ||
|
|
3152 | + /** | ||
|
|
3153 | + * Builds an icon tag as either an `<i>` | ||
|
|
3154 | + * or with icons.type is `sprites` then a svg tag instead | ||
|
|
3155 | + * @param iconClass | ||
|
|
3156 | + * @private | ||
|
|
3157 | + */ | ||
|
|
3158 | + _iconTag(iconClass) { | ||
|
|
3159 | + if (this.optionsStore.options.display.icons.type === 'sprites') { | ||
|
|
3160 | + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | ||
|
|
3161 | + const icon = document.createElementNS('http://www.w3.org/2000/svg', 'use'); | ||
|
|
3162 | + icon.setAttribute('xlink:href', iconClass); // Deprecated. Included for backward compatibility | ||
|
|
3163 | + icon.setAttribute('href', iconClass); | ||
|
|
3164 | + svg.appendChild(icon); | ||
|
|
3165 | + return svg; | ||
|
|
3166 | + } | ||
|
|
3167 | + const icon = document.createElement('i'); | ||
|
|
3168 | + icon.classList.add(...iconClass.split(' ')); | ||
|
|
3169 | + return icon; | ||
|
|
3170 | + } | ||
|
|
3171 | + /** | ||
|
|
3172 | + * Causes the widget to get rebuilt on next show. If the picker is already open | ||
|
|
3173 | + * then hide and reshow it. | ||
|
|
3174 | + * @private | ||
|
|
3175 | + */ | ||
|
|
3176 | + _rebuild() { | ||
|
|
3177 | + const wasVisible = this._isVisible; | ||
|
|
3178 | + if (wasVisible) | ||
|
|
3179 | + this.hide(); | ||
|
|
3180 | + this._dispose(); | ||
|
|
3181 | + if (wasVisible) { | ||
|
|
3182 | + this.show(); | ||
|
|
3183 | + } | ||
|
|
3184 | + } | ||
|
|
3185 | + } | ||
|
|
3186 | + | ||
|
|
3187 | + /** | ||
|
|
3188 | + * | ||
|
|
3189 | + */ | ||
|
|
3190 | + class Actions { | ||
|
|
3191 | + constructor() { | ||
|
|
3192 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
3193 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
3194 | + this.validation = serviceLocator.locate(Validation); | ||
|
|
3195 | + this.display = serviceLocator.locate(Display); | ||
|
|
3196 | + this._eventEmitters = serviceLocator.locate(EventEmitters); | ||
|
|
3197 | + this._eventEmitters.action.subscribe((result) => { | ||
|
|
3198 | + this.do(result.e, result.action); | ||
|
|
3199 | + }); | ||
|
|
3200 | + } | ||
|
|
3201 | + /** | ||
|
|
3202 | + * Performs the selected `action`. See ActionTypes | ||
|
|
3203 | + * @param e This is normally a click event | ||
|
|
3204 | + * @param action If not provided, then look for a [data-action] | ||
|
|
3205 | + */ | ||
|
|
3206 | + do(e, action) { | ||
|
|
3207 | + const currentTarget = e?.currentTarget; | ||
|
|
3208 | + if (currentTarget?.classList?.contains(Namespace.css.disabled)) | ||
|
|
3209 | + return false; | ||
|
|
3210 | + action = action || currentTarget?.dataset?.action; | ||
|
|
3211 | + const lastPicked = (this.dates.lastPicked || this.optionsStore.viewDate) | ||
|
|
3212 | + .clone; | ||
|
|
3213 | + switch (action) { | ||
|
|
3214 | + case ActionTypes$1.next: | ||
|
|
3215 | + case ActionTypes$1.previous: | ||
|
|
3216 | + this.handleNextPrevious(action); | ||
|
|
3217 | + break; | ||
|
|
3218 | + case ActionTypes$1.changeCalendarView: | ||
|
|
3219 | + this.display._showMode(1); | ||
|
|
3220 | + this.display._updateCalendarHeader(); | ||
|
|
3221 | + break; | ||
|
|
3222 | + case ActionTypes$1.selectMonth: | ||
|
|
3223 | + case ActionTypes$1.selectYear: | ||
|
|
3224 | + case ActionTypes$1.selectDecade: | ||
|
|
3225 | + const value = +currentTarget.dataset.value; | ||
|
|
3226 | + switch (action) { | ||
|
|
3227 | + case ActionTypes$1.selectMonth: | ||
|
|
3228 | + this.optionsStore.viewDate.month = value; | ||
|
|
3229 | + break; | ||
|
|
3230 | + case ActionTypes$1.selectYear: | ||
|
|
3231 | + case ActionTypes$1.selectDecade: | ||
|
|
3232 | + this.optionsStore.viewDate.year = value; | ||
|
|
3233 | + break; | ||
|
|
3234 | + } | ||
|
|
3235 | + if (this.optionsStore.currentCalendarViewMode === | ||
|
|
3236 | + this.optionsStore.minimumCalendarViewMode) { | ||
|
|
3237 | + this.dates.setValue(this.optionsStore.viewDate, this.dates.lastPickedIndex); | ||
|
|
3238 | + if (!this.optionsStore.options.display.inline) { | ||
|
|
3239 | + this.display.hide(); | ||
|
|
3240 | + } | ||
|
|
3241 | + } | ||
|
|
3242 | + else { | ||
|
|
3243 | + this.display._showMode(-1); | ||
|
|
3244 | + } | ||
|
|
3245 | + break; | ||
|
|
3246 | + case ActionTypes$1.selectDay: | ||
|
|
3247 | + const day = this.optionsStore.viewDate.clone; | ||
|
|
3248 | + if (currentTarget.classList.contains(Namespace.css.old)) { | ||
|
|
3249 | + day.manipulate(-1, Unit.month); | ||
|
|
3250 | + } | ||
|
|
3251 | + if (currentTarget.classList.contains(Namespace.css.new)) { | ||
|
|
3252 | + day.manipulate(1, Unit.month); | ||
|
|
3253 | + } | ||
|
|
3254 | + day.date = +currentTarget.dataset.day; | ||
|
|
3255 | + let index = 0; | ||
|
|
3256 | + if (this.optionsStore.options.multipleDates) { | ||
|
|
3257 | + index = this.dates.pickedIndex(day, Unit.date); | ||
|
|
3258 | + if (index !== -1) { | ||
|
|
3259 | + this.dates.setValue(null, index); //deselect multi-date | ||
|
|
3260 | + } | ||
|
|
3261 | + else { | ||
|
|
3262 | + this.dates.setValue(day, this.dates.lastPickedIndex + 1); | ||
|
|
3263 | + } | ||
|
|
3264 | + } | ||
|
|
3265 | + else { | ||
|
|
3266 | + this.dates.setValue(day, this.dates.lastPickedIndex); | ||
|
|
3267 | + } | ||
|
|
3268 | + if (!this.display._hasTime && | ||
|
|
3269 | + !this.optionsStore.options.display.keepOpen && | ||
|
|
3270 | + !this.optionsStore.options.display.inline && | ||
|
|
3271 | + !this.optionsStore.options.multipleDates) { | ||
|
|
3272 | + this.display.hide(); | ||
|
|
3273 | + } | ||
|
|
3274 | + break; | ||
|
|
3275 | + case ActionTypes$1.selectHour: | ||
|
|
3276 | + let hour = +currentTarget.dataset.value; | ||
|
|
3277 | + if (lastPicked.hours >= 12 && | ||
|
|
3278 | + !this.optionsStore.options.display.components.useTwentyfourHour) | ||
|
|
3279 | + hour += 12; | ||
|
|
3280 | + lastPicked.hours = hour; | ||
|
|
3281 | + this.dates.setValue(lastPicked, this.dates.lastPickedIndex); | ||
|
|
3282 | + this.hideOrClock(e); | ||
|
|
3283 | + break; | ||
|
|
3284 | + case ActionTypes$1.selectMinute: | ||
|
|
3285 | + lastPicked.minutes = +currentTarget.dataset.value; | ||
|
|
3286 | + this.dates.setValue(lastPicked, this.dates.lastPickedIndex); | ||
|
|
3287 | + this.hideOrClock(e); | ||
|
|
3288 | + break; | ||
|
|
3289 | + case ActionTypes$1.selectSecond: | ||
|
|
3290 | + lastPicked.seconds = +currentTarget.dataset.value; | ||
|
|
3291 | + this.dates.setValue(lastPicked, this.dates.lastPickedIndex); | ||
|
|
3292 | + this.hideOrClock(e); | ||
|
|
3293 | + break; | ||
|
|
3294 | + case ActionTypes$1.incrementHours: | ||
|
|
3295 | + this.manipulateAndSet(lastPicked, Unit.hours); | ||
|
|
3296 | + break; | ||
|
|
3297 | + case ActionTypes$1.incrementMinutes: | ||
|
|
3298 | + this.manipulateAndSet(lastPicked, Unit.minutes, this.optionsStore.options.stepping); | ||
|
|
3299 | + break; | ||
|
|
3300 | + case ActionTypes$1.incrementSeconds: | ||
|
|
3301 | + this.manipulateAndSet(lastPicked, Unit.seconds); | ||
|
|
3302 | + break; | ||
|
|
3303 | + case ActionTypes$1.decrementHours: | ||
|
|
3304 | + this.manipulateAndSet(lastPicked, Unit.hours, -1); | ||
|
|
3305 | + break; | ||
|
|
3306 | + case ActionTypes$1.decrementMinutes: | ||
|
|
3307 | + this.manipulateAndSet(lastPicked, Unit.minutes, this.optionsStore.options.stepping * -1); | ||
|
|
3308 | + break; | ||
|
|
3309 | + case ActionTypes$1.decrementSeconds: | ||
|
|
3310 | + this.manipulateAndSet(lastPicked, Unit.seconds, -1); | ||
|
|
3311 | + break; | ||
|
|
3312 | + case ActionTypes$1.toggleMeridiem: | ||
|
|
3313 | + this.manipulateAndSet(lastPicked, Unit.hours, this.dates.lastPicked.hours >= 12 ? -12 : 12); | ||
|
|
3314 | + break; | ||
|
|
3315 | + case ActionTypes$1.togglePicker: | ||
|
|
3316 | + if (currentTarget.getAttribute('title') === | ||
|
|
3317 | + this.optionsStore.options.localization.selectDate) { | ||
|
|
3318 | + currentTarget.setAttribute('title', this.optionsStore.options.localization.selectTime); | ||
|
|
3319 | + currentTarget.innerHTML = this.display._iconTag(this.optionsStore.options.display.icons.time).outerHTML; | ||
|
|
3320 | + this.display._updateCalendarHeader(); | ||
|
|
3321 | + this.optionsStore.refreshCurrentView(); | ||
|
|
3322 | + } | ||
|
|
3323 | + else { | ||
|
|
3324 | + currentTarget.setAttribute('title', this.optionsStore.options.localization.selectDate); | ||
|
|
3325 | + currentTarget.innerHTML = this.display._iconTag(this.optionsStore.options.display.icons.date).outerHTML; | ||
|
|
3326 | + if (this.display._hasTime) { | ||
|
|
3327 | + this.handleShowClockContainers(ActionTypes$1.showClock); | ||
|
|
3328 | + this.display._update('clock'); | ||
|
|
3329 | + } | ||
|
|
3330 | + } | ||
|
|
3331 | + this.display.widget | ||
|
|
3332 | + .querySelectorAll(`.${Namespace.css.dateContainer}, .${Namespace.css.timeContainer}`) | ||
|
|
3333 | + .forEach((htmlElement) => Collapse.toggle(htmlElement)); | ||
|
|
3334 | + this._eventEmitters.viewUpdate.emit(); | ||
|
|
3335 | + break; | ||
|
|
3336 | + case ActionTypes$1.showClock: | ||
|
|
3337 | + case ActionTypes$1.showHours: | ||
|
|
3338 | + case ActionTypes$1.showMinutes: | ||
|
|
3339 | + case ActionTypes$1.showSeconds: | ||
|
|
3340 | + //make sure the clock is actually displaying | ||
|
|
3341 | + if (!this.optionsStore.options.display.sideBySide && this.optionsStore.currentView !== 'clock') { | ||
|
|
3342 | + //hide calendar | ||
|
|
3343 | + Collapse.hideImmediately(this.display.widget.querySelector(`div.${Namespace.css.dateContainer}`)); | ||
|
|
3344 | + //show clock | ||
|
|
3345 | + Collapse.showImmediately(this.display.widget.querySelector(`div.${Namespace.css.timeContainer}`)); | ||
|
|
3346 | + } | ||
|
|
3347 | + this.handleShowClockContainers(action); | ||
|
|
3348 | + break; | ||
|
|
3349 | + case ActionTypes$1.clear: | ||
|
|
3350 | + this.dates.setValue(null); | ||
|
|
3351 | + this.display._updateCalendarHeader(); | ||
|
|
3352 | + break; | ||
|
|
3353 | + case ActionTypes$1.close: | ||
|
|
3354 | + this.display.hide(); | ||
|
|
3355 | + break; | ||
|
|
3356 | + case ActionTypes$1.today: | ||
|
|
3357 | + const today = new DateTime().setLocale(this.optionsStore.options.localization.locale); | ||
|
|
3358 | + this.optionsStore.viewDate = today; | ||
|
|
3359 | + if (this.validation.isValid(today, Unit.date)) | ||
|
|
3360 | + this.dates.setValue(today, this.dates.lastPickedIndex); | ||
|
|
3361 | + break; | ||
|
|
3362 | + } | ||
|
|
3363 | + } | ||
|
|
3364 | + handleShowClockContainers(action) { | ||
|
|
3365 | + if (!this.display._hasTime) { | ||
|
|
3366 | + Namespace.errorMessages.throwError('Cannot show clock containers when time is disabled.'); | ||
|
|
3367 | + return; | ||
|
|
3368 | + } | ||
|
|
3369 | + this.optionsStore.currentView = 'clock'; | ||
|
|
3370 | + this.display.widget | ||
|
|
3371 | + .querySelectorAll(`.${Namespace.css.timeContainer} > div`) | ||
|
|
3372 | + .forEach((htmlElement) => (htmlElement.style.display = 'none')); | ||
|
|
3373 | + let classToUse = ''; | ||
|
|
3374 | + switch (action) { | ||
|
|
3375 | + case ActionTypes$1.showClock: | ||
|
|
3376 | + classToUse = Namespace.css.clockContainer; | ||
|
|
3377 | + this.display._update('clock'); | ||
|
|
3378 | + break; | ||
|
|
3379 | + case ActionTypes$1.showHours: | ||
|
|
3380 | + classToUse = Namespace.css.hourContainer; | ||
|
|
3381 | + this.display._update(Unit.hours); | ||
|
|
3382 | + break; | ||
|
|
3383 | + case ActionTypes$1.showMinutes: | ||
|
|
3384 | + classToUse = Namespace.css.minuteContainer; | ||
|
|
3385 | + this.display._update(Unit.minutes); | ||
|
|
3386 | + break; | ||
|
|
3387 | + case ActionTypes$1.showSeconds: | ||
|
|
3388 | + classToUse = Namespace.css.secondContainer; | ||
|
|
3389 | + this.display._update(Unit.seconds); | ||
|
|
3390 | + break; | ||
|
|
3391 | + } | ||
|
|
3392 | + (this.display.widget.getElementsByClassName(classToUse)[0]).style.display = 'grid'; | ||
|
|
3393 | + } | ||
|
|
3394 | + handleNextPrevious(action) { | ||
|
|
3395 | + const { unit, step } = CalendarModes[this.optionsStore.currentCalendarViewMode]; | ||
|
|
3396 | + if (action === ActionTypes$1.next) | ||
|
|
3397 | + this.optionsStore.viewDate.manipulate(step, unit); | ||
|
|
3398 | + else | ||
|
|
3399 | + this.optionsStore.viewDate.manipulate(step * -1, unit); | ||
|
|
3400 | + this._eventEmitters.viewUpdate.emit(); | ||
|
|
3401 | + this.display._showMode(); | ||
|
|
3402 | + } | ||
|
|
3403 | + /** | ||
|
|
3404 | + * After setting the value it will either show the clock or hide the widget. | ||
|
|
3405 | + * @param e | ||
|
|
3406 | + */ | ||
|
|
3407 | + hideOrClock(e) { | ||
|
|
3408 | + if (this.optionsStore.options.display.components.useTwentyfourHour && | ||
|
|
3409 | + !this.optionsStore.options.display.components.minutes && | ||
|
|
3410 | + !this.optionsStore.options.display.keepOpen && | ||
|
|
3411 | + !this.optionsStore.options.display.inline) { | ||
|
|
3412 | + this.display.hide(); | ||
|
|
3413 | + } | ||
|
|
3414 | + else { | ||
|
|
3415 | + this.do(e, ActionTypes$1.showClock); | ||
|
|
3416 | + } | ||
|
|
3417 | + } | ||
|
|
3418 | + /** | ||
|
|
3419 | + * Common function to manipulate {@link lastPicked} by `unit`. | ||
|
|
3420 | + * @param lastPicked | ||
|
|
3421 | + * @param unit | ||
|
|
3422 | + * @param value Value to change by | ||
|
|
3423 | + */ | ||
|
|
3424 | + manipulateAndSet(lastPicked, unit, value = 1) { | ||
|
|
3425 | + const newDate = lastPicked.manipulate(value, unit); | ||
|
|
3426 | + if (this.validation.isValid(newDate, unit)) { | ||
|
|
3427 | + this.dates.setValue(newDate, this.dates.lastPickedIndex); | ||
|
|
3428 | + } | ||
|
|
3429 | + } | ||
|
|
3430 | + } | ||
|
|
3431 | + | ||
|
|
3432 | + /** | ||
|
|
3433 | + * A robust and powerful date/time picker component. | ||
|
|
3434 | + */ | ||
|
|
3435 | + class TempusDominus { | ||
|
|
3436 | + constructor(element, options = {}) { | ||
|
|
3437 | + this._subscribers = {}; | ||
|
|
3438 | + this._isDisabled = false; | ||
|
|
3439 | + /** | ||
|
|
3440 | + * Event for when the input field changes. This is a class level method so there's | ||
|
|
3441 | + * something for the remove listener function. | ||
|
|
3442 | + * @private | ||
|
|
3443 | + */ | ||
|
|
3444 | + this._inputChangeEvent = (event) => { | ||
|
|
3445 | + const internallyTriggered = event?.detail; | ||
|
|
3446 | + if (internallyTriggered) | ||
|
|
3447 | + return; | ||
|
|
3448 | + const setViewDate = () => { | ||
|
|
3449 | + if (this.dates.lastPicked) | ||
|
|
3450 | + this.optionsStore.viewDate = this.dates.lastPicked.clone; | ||
|
|
3451 | + }; | ||
|
|
3452 | + const value = this.optionsStore.input.value; | ||
|
|
3453 | + if (this.optionsStore.options.multipleDates) { | ||
|
|
3454 | + try { | ||
|
|
3455 | + const valueSplit = value.split(this.optionsStore.options.multipleDatesSeparator); | ||
|
|
3456 | + for (let i = 0; i < valueSplit.length; i++) { | ||
|
|
3457 | + this.dates.setFromInput(valueSplit[i], i); | ||
|
|
3458 | + } | ||
|
|
3459 | + setViewDate(); | ||
|
|
3460 | + } | ||
|
|
3461 | + catch { | ||
|
|
3462 | + console.warn('TD: Something went wrong trying to set the multipleDates values from the input field.'); | ||
|
|
3463 | + } | ||
|
|
3464 | + } | ||
|
|
3465 | + else { | ||
|
|
3466 | + this.dates.setFromInput(value, 0); | ||
|
|
3467 | + setViewDate(); | ||
|
|
3468 | + } | ||
|
|
3469 | + }; | ||
|
|
3470 | + /** | ||
|
|
3471 | + * Event for when the toggle is clicked. This is a class level method so there's | ||
|
|
3472 | + * something for the remove listener function. | ||
|
|
3473 | + * @private | ||
|
|
3474 | + */ | ||
|
|
3475 | + this._toggleClickEvent = () => { | ||
|
|
3476 | + if (this.optionsStore.element?.disabled || this.optionsStore.input?.disabled) | ||
|
|
3477 | + return; | ||
|
|
3478 | + this.toggle(); | ||
|
|
3479 | + }; | ||
|
|
3480 | + setupServiceLocator(); | ||
|
|
3481 | + this._eventEmitters = serviceLocator.locate(EventEmitters); | ||
|
|
3482 | + this.optionsStore = serviceLocator.locate(OptionsStore); | ||
|
|
3483 | + this.display = serviceLocator.locate(Display); | ||
|
|
3484 | + this.dates = serviceLocator.locate(Dates); | ||
|
|
3485 | + this.actions = serviceLocator.locate(Actions); | ||
|
|
3486 | + if (!element) { | ||
|
|
3487 | + Namespace.errorMessages.mustProvideElement(); | ||
|
|
3488 | + } | ||
|
|
3489 | + this.optionsStore.element = element; | ||
|
|
3490 | + this._initializeOptions(options, DefaultOptions, true); | ||
|
|
3491 | + this.optionsStore.viewDate.setLocale(this.optionsStore.options.localization.locale); | ||
|
|
3492 | + this.optionsStore.unset = true; | ||
|
|
3493 | + this._initializeInput(); | ||
|
|
3494 | + this._initializeToggle(); | ||
|
|
3495 | + if (this.optionsStore.options.display.inline) | ||
|
|
3496 | + this.display.show(); | ||
|
|
3497 | + this._eventEmitters.triggerEvent.subscribe((e) => { | ||
|
|
3498 | + this._triggerEvent(e); | ||
|
|
3499 | + }); | ||
|
|
3500 | + this._eventEmitters.viewUpdate.subscribe(() => { | ||
|
|
3501 | + this._viewUpdate(); | ||
|
|
3502 | + }); | ||
|
|
3503 | + } | ||
|
|
3504 | + get viewDate() { | ||
|
|
3505 | + return this.optionsStore.viewDate; | ||
|
|
3506 | + } | ||
|
|
3507 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3508 | + /** | ||
|
|
3509 | + * Update the picker options. If `reset` is provide `options` will be merged with DefaultOptions instead. | ||
|
|
3510 | + * @param options | ||
|
|
3511 | + * @param reset | ||
|
|
3512 | + * @public | ||
|
|
3513 | + */ | ||
|
|
3514 | + updateOptions(options, reset = false) { | ||
|
|
3515 | + if (reset) | ||
|
|
3516 | + this._initializeOptions(options, DefaultOptions); | ||
|
|
3517 | + else | ||
|
|
3518 | + this._initializeOptions(options, this.optionsStore.options); | ||
|
|
3519 | + this.display._rebuild(); | ||
|
|
3520 | + } | ||
|
|
3521 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3522 | + /** | ||
|
|
3523 | + * Toggles the picker open or closed. If the picker is disabled, nothing will happen. | ||
|
|
3524 | + * @public | ||
|
|
3525 | + */ | ||
|
|
3526 | + toggle() { | ||
|
|
3527 | + if (this._isDisabled) | ||
|
|
3528 | + return; | ||
|
|
3529 | + this.display.toggle(); | ||
|
|
3530 | + } | ||
|
|
3531 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3532 | + /** | ||
|
|
3533 | + * Shows the picker unless the picker is disabled. | ||
|
|
3534 | + * @public | ||
|
|
3535 | + */ | ||
|
|
3536 | + show() { | ||
|
|
3537 | + if (this._isDisabled) | ||
|
|
3538 | + return; | ||
|
|
3539 | + this.display.show(); | ||
|
|
3540 | + } | ||
|
|
3541 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3542 | + /** | ||
|
|
3543 | + * Hides the picker unless the picker is disabled. | ||
|
|
3544 | + * @public | ||
|
|
3545 | + */ | ||
|
|
3546 | + hide() { | ||
|
|
3547 | + this.display.hide(); | ||
|
|
3548 | + } | ||
|
|
3549 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3550 | + /** | ||
|
|
3551 | + * Disables the picker and the target input field. | ||
|
|
3552 | + * @public | ||
|
|
3553 | + */ | ||
|
|
3554 | + disable() { | ||
|
|
3555 | + this._isDisabled = true; | ||
|
|
3556 | + // todo this might be undesired. If a dev disables the input field to | ||
|
|
3557 | + // only allow using the picker, this will break that. | ||
|
|
3558 | + this.optionsStore.input?.setAttribute('disabled', 'disabled'); | ||
|
|
3559 | + this.display.hide(); | ||
|
|
3560 | + } | ||
|
|
3561 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3562 | + /** | ||
|
|
3563 | + * Enables the picker and the target input field. | ||
|
|
3564 | + * @public | ||
|
|
3565 | + */ | ||
|
|
3566 | + enable() { | ||
|
|
3567 | + this._isDisabled = false; | ||
|
|
3568 | + this.optionsStore.input?.removeAttribute('disabled'); | ||
|
|
3569 | + } | ||
|
|
3570 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3571 | + /** | ||
|
|
3572 | + * Clears all the selected dates | ||
|
|
3573 | + * @public | ||
|
|
3574 | + */ | ||
|
|
3575 | + clear() { | ||
|
|
3576 | + this.optionsStore.input.value = ''; | ||
|
|
3577 | + this.dates.clear(); | ||
|
|
3578 | + } | ||
|
|
3579 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3580 | + /** | ||
|
|
3581 | + * Allows for a direct subscription to picker events, without having to use addEventListener on the element. | ||
|
|
3582 | + * @param eventTypes See Namespace.Events | ||
|
|
3583 | + * @param callbacks Function to call when event is triggered | ||
|
|
3584 | + * @public | ||
|
|
3585 | + */ | ||
|
|
3586 | + subscribe(eventTypes, callbacks) { | ||
|
|
3587 | + if (typeof eventTypes === 'string') { | ||
|
|
3588 | + eventTypes = [eventTypes]; | ||
|
|
3589 | + } | ||
|
|
3590 | + let callBackArray; | ||
|
|
3591 | + if (!Array.isArray(callbacks)) { | ||
|
|
3592 | + callBackArray = [callbacks]; | ||
|
|
3593 | + } | ||
|
|
3594 | + else { | ||
|
|
3595 | + callBackArray = callbacks; | ||
|
|
3596 | + } | ||
|
|
3597 | + if (eventTypes.length !== callBackArray.length) { | ||
|
|
3598 | + Namespace.errorMessages.subscribeMismatch(); | ||
|
|
3599 | + } | ||
|
|
3600 | + const returnArray = []; | ||
|
|
3601 | + for (let i = 0; i < eventTypes.length; i++) { | ||
|
|
3602 | + const eventType = eventTypes[i]; | ||
|
|
3603 | + if (!Array.isArray(this._subscribers[eventType])) { | ||
|
|
3604 | + this._subscribers[eventType] = []; | ||
|
|
3605 | + } | ||
|
|
3606 | + this._subscribers[eventType].push(callBackArray[i]); | ||
|
|
3607 | + returnArray.push({ | ||
|
|
3608 | + unsubscribe: this._unsubscribe.bind(this, eventType, this._subscribers[eventType].length - 1), | ||
|
|
3609 | + }); | ||
|
|
3610 | + if (eventTypes.length === 1) { | ||
|
|
3611 | + return returnArray[0]; | ||
|
|
3612 | + } | ||
|
|
3613 | + } | ||
|
|
3614 | + return returnArray; | ||
|
|
3615 | + } | ||
|
|
3616 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3617 | + /** | ||
|
|
3618 | + * Hides the picker and removes event listeners | ||
|
|
3619 | + */ | ||
|
|
3620 | + dispose() { | ||
|
|
3621 | + this.display.hide(); | ||
|
|
3622 | + // this will clear the document click event listener | ||
|
|
3623 | + this.display._dispose(); | ||
|
|
3624 | + this.optionsStore.input?.removeEventListener('change', this._inputChangeEvent); | ||
|
|
3625 | + if (this.optionsStore.options.allowInputToggle) { | ||
|
|
3626 | + this.optionsStore.input?.removeEventListener('click', this._toggleClickEvent); | ||
|
|
3627 | + } | ||
|
|
3628 | + this._toggle?.removeEventListener('click', this._toggleClickEvent); | ||
|
|
3629 | + this._subscribers = {}; | ||
|
|
3630 | + } | ||
|
|
3631 | + /** | ||
|
|
3632 | + * Updates the options to use the provided language. | ||
|
|
3633 | + * THe language file must be loaded first. | ||
|
|
3634 | + * @param language | ||
|
|
3635 | + */ | ||
|
|
3636 | + locale(language) { | ||
|
|
3637 | + let asked = loadedLocales[language]; | ||
|
|
3638 | + if (!asked) | ||
|
|
3639 | + return; | ||
|
|
3640 | + this.updateOptions({ | ||
|
|
3641 | + localization: asked, | ||
|
|
3642 | + }); | ||
|
|
3643 | + } | ||
|
|
3644 | + /** | ||
|
|
3645 | + * Triggers an event like ChangeEvent when the picker has updated the value | ||
|
|
3646 | + * of a selected date. | ||
|
|
3647 | + * @param event Accepts a BaseEvent object. | ||
|
|
3648 | + * @private | ||
|
|
3649 | + */ | ||
|
|
3650 | + _triggerEvent(event) { | ||
|
|
3651 | + event.viewMode = this.optionsStore.currentView; | ||
|
|
3652 | + const isChangeEvent = event.type === Namespace.events.change; | ||
|
|
3653 | + if (isChangeEvent) { | ||
|
|
3654 | + const { date, oldDate, isClear } = event; | ||
|
|
3655 | + if ((date && oldDate && date.isSame(oldDate)) || | ||
|
|
3656 | + (!isClear && !date && !oldDate)) { | ||
|
|
3657 | + return; | ||
|
|
3658 | + } | ||
|
|
3659 | + this._handleAfterChangeEvent(event); | ||
|
|
3660 | + this.optionsStore.input?.dispatchEvent(new CustomEvent(event.type, { detail: event })); | ||
|
|
3661 | + this.optionsStore.input?.dispatchEvent(new CustomEvent('change', { detail: event })); | ||
|
|
3662 | + } | ||
|
|
3663 | + this.optionsStore.element.dispatchEvent(new CustomEvent(event.type, { detail: event })); | ||
|
|
3664 | + if (window.jQuery) { | ||
|
|
3665 | + const $ = window.jQuery; | ||
|
|
3666 | + if (isChangeEvent && this.optionsStore.input) { | ||
|
|
3667 | + $(this.optionsStore.input).trigger(event); | ||
|
|
3668 | + } | ||
|
|
3669 | + else { | ||
|
|
3670 | + $(this.optionsStore.element).trigger(event); | ||
|
|
3671 | + } | ||
|
|
3672 | + } | ||
|
|
3673 | + this._publish(event); | ||
|
|
3674 | + } | ||
|
|
3675 | + _publish(event) { | ||
|
|
3676 | + // return if event is not subscribed | ||
|
|
3677 | + if (!Array.isArray(this._subscribers[event.type])) { | ||
|
|
3678 | + return; | ||
|
|
3679 | + } | ||
|
|
3680 | + // Trigger callback for each subscriber | ||
|
|
3681 | + this._subscribers[event.type].forEach((callback) => { | ||
|
|
3682 | + callback(event); | ||
|
|
3683 | + }); | ||
|
|
3684 | + } | ||
|
|
3685 | + /** | ||
|
|
3686 | + * Fires a ViewUpdate event when, for example, the month view is changed. | ||
|
|
3687 | + * @private | ||
|
|
3688 | + */ | ||
|
|
3689 | + _viewUpdate() { | ||
|
|
3690 | + this._triggerEvent({ | ||
|
|
3691 | + type: Namespace.events.update, | ||
|
|
3692 | + viewDate: this.optionsStore.viewDate.clone, | ||
|
|
3693 | + }); | ||
|
|
3694 | + } | ||
|
|
3695 | + _unsubscribe(eventName, index) { | ||
|
|
3696 | + this._subscribers[eventName].splice(index, 1); | ||
|
|
3697 | + } | ||
|
|
3698 | + /** | ||
|
|
3699 | + * Merges two Option objects together and validates options type | ||
|
|
3700 | + * @param config new Options | ||
|
|
3701 | + * @param mergeTo Options to merge into | ||
|
|
3702 | + * @param includeDataset When true, the elements data-td attributes will be included in the | ||
|
|
3703 | + * @private | ||
|
|
3704 | + */ | ||
|
|
3705 | + _initializeOptions(config, mergeTo, includeDataset = false) { | ||
|
|
3706 | + let newConfig = OptionConverter.deepCopy(config); | ||
|
|
3707 | + newConfig = OptionConverter._mergeOptions(newConfig, mergeTo); | ||
|
|
3708 | + if (includeDataset) | ||
|
|
3709 | + newConfig = OptionConverter._dataToOptions(this.optionsStore.element, newConfig); | ||
|
|
3710 | + OptionConverter._validateConflicts(newConfig); | ||
|
|
3711 | + newConfig.viewDate = newConfig.viewDate.setLocale(newConfig.localization.locale); | ||
|
|
3712 | + if (!this.optionsStore.viewDate.isSame(newConfig.viewDate)) { | ||
|
|
3713 | + this.optionsStore.viewDate = newConfig.viewDate; | ||
|
|
3714 | + } | ||
|
|
3715 | + /** | ||
|
|
3716 | + * Sets the minimum view allowed by the picker. For example the case of only | ||
|
|
3717 | + * allowing year and month to be selected but not date. | ||
|
|
3718 | + */ | ||
|
|
3719 | + if (newConfig.display.components.year) { | ||
|
|
3720 | + this.optionsStore.minimumCalendarViewMode = 2; | ||
|
|
3721 | + } | ||
|
|
3722 | + if (newConfig.display.components.month) { | ||
|
|
3723 | + this.optionsStore.minimumCalendarViewMode = 1; | ||
|
|
3724 | + } | ||
|
|
3725 | + if (newConfig.display.components.date) { | ||
|
|
3726 | + this.optionsStore.minimumCalendarViewMode = 0; | ||
|
|
3727 | + } | ||
|
|
3728 | + this.optionsStore.currentCalendarViewMode = Math.max(this.optionsStore.minimumCalendarViewMode, this.optionsStore.currentCalendarViewMode); | ||
|
|
3729 | + // Update view mode if needed | ||
|
|
3730 | + if (CalendarModes[this.optionsStore.currentCalendarViewMode].name !== | ||
|
|
3731 | + newConfig.display.viewMode) { | ||
|
|
3732 | + this.optionsStore.currentCalendarViewMode = Math.max(CalendarModes.findIndex((x) => x.name === newConfig.display.viewMode), this.optionsStore.minimumCalendarViewMode); | ||
|
|
3733 | + } | ||
|
|
3734 | + if (this.display?.isVisible) { | ||
|
|
3735 | + this.display._update('all'); | ||
|
|
3736 | + } | ||
|
|
3737 | + if (newConfig.display.components.useTwentyfourHour === undefined) { | ||
|
|
3738 | + newConfig.display.components.useTwentyfourHour = !!!newConfig.viewDate.parts()?.dayPeriod; | ||
|
|
3739 | + } | ||
|
|
3740 | + this.optionsStore.options = newConfig; | ||
|
|
3741 | + } | ||
|
|
3742 | + /** | ||
|
|
3743 | + * Checks if an input field is being used, attempts to locate one and sets an | ||
|
|
3744 | + * event listener if found. | ||
|
|
3745 | + * @private | ||
|
|
3746 | + */ | ||
|
|
3747 | + _initializeInput() { | ||
|
|
3748 | + if (this.optionsStore.element.tagName == 'INPUT') { | ||
|
|
3749 | + this.optionsStore.input = this.optionsStore.element; | ||
|
|
3750 | + } | ||
|
|
3751 | + else { | ||
|
|
3752 | + let query = this.optionsStore.element.dataset.tdTargetInput; | ||
|
|
3753 | + if (query == undefined || query == 'nearest') { | ||
|
|
3754 | + this.optionsStore.input = | ||
|
|
3755 | + this.optionsStore.element.querySelector('input'); | ||
|
|
3756 | + } | ||
|
|
3757 | + else { | ||
|
|
3758 | + this.optionsStore.input = | ||
|
|
3759 | + this.optionsStore.element.querySelector(query); | ||
|
|
3760 | + } | ||
|
|
3761 | + } | ||
|
|
3762 | + if (!this.optionsStore.input) | ||
|
|
3763 | + return; | ||
|
|
3764 | + this.optionsStore.input.addEventListener('change', this._inputChangeEvent); | ||
|
|
3765 | + if (this.optionsStore.options.allowInputToggle) { | ||
|
|
3766 | + this.optionsStore.input.addEventListener('click', this._toggleClickEvent); | ||
|
|
3767 | + } | ||
|
|
3768 | + if (this.optionsStore.input.value) { | ||
|
|
3769 | + this._inputChangeEvent(); | ||
|
|
3770 | + } | ||
|
|
3771 | + } | ||
|
|
3772 | + /** | ||
|
|
3773 | + * Attempts to locate a toggle for the picker and sets an event listener | ||
|
|
3774 | + * @private | ||
|
|
3775 | + */ | ||
|
|
3776 | + _initializeToggle() { | ||
|
|
3777 | + if (this.optionsStore.options.display.inline) | ||
|
|
3778 | + return; | ||
|
|
3779 | + let query = this.optionsStore.element.dataset.tdTargetToggle; | ||
|
|
3780 | + if (query == 'nearest') { | ||
|
|
3781 | + query = '[data-td-toggle="datetimepicker"]'; | ||
|
|
3782 | + } | ||
|
|
3783 | + this._toggle = | ||
|
|
3784 | + query == undefined | ||
|
|
3785 | + ? this.optionsStore.element | ||
|
|
3786 | + : this.optionsStore.element.querySelector(query); | ||
|
|
3787 | + this._toggle.addEventListener('click', this._toggleClickEvent); | ||
|
|
3788 | + } | ||
|
|
3789 | + /** | ||
|
|
3790 | + * If the option is enabled this will render the clock view after a date pick. | ||
|
|
3791 | + * @param e change event | ||
|
|
3792 | + * @private | ||
|
|
3793 | + */ | ||
|
|
3794 | + _handleAfterChangeEvent(e) { | ||
|
|
3795 | + if ( | ||
|
|
3796 | + // options is disabled | ||
|
|
3797 | + !this.optionsStore.options.promptTimeOnDateChange || | ||
|
|
3798 | + this.optionsStore.options.display.inline || | ||
|
|
3799 | + this.optionsStore.options.display.sideBySide || | ||
|
|
3800 | + // time is disabled | ||
|
|
3801 | + !this.display._hasTime || | ||
|
|
3802 | + // clock component is already showing | ||
|
|
3803 | + this.display.widget | ||
|
|
3804 | + ?.getElementsByClassName(Namespace.css.show)[0] | ||
|
|
3805 | + .classList.contains(Namespace.css.timeContainer)) | ||
|
|
3806 | + return; | ||
|
|
3807 | + // First time ever. If useCurrent option is set to true (default), do nothing | ||
|
|
3808 | + // because the first date is selected automatically. | ||
|
|
3809 | + // or date didn't change (time did) or date changed because time did. | ||
|
|
3810 | + if ((!e.oldDate && this.optionsStore.options.useCurrent) || | ||
|
|
3811 | + (e.oldDate && e.date?.isSame(e.oldDate))) { | ||
|
|
3812 | + return; | ||
|
|
3813 | + } | ||
|
|
3814 | + clearTimeout(this._currentPromptTimeTimeout); | ||
|
|
3815 | + this._currentPromptTimeTimeout = setTimeout(() => { | ||
|
|
3816 | + if (this.display.widget) { | ||
|
|
3817 | + this._eventEmitters.action.emit({ | ||
|
|
3818 | + e: { | ||
|
|
3819 | + currentTarget: this.display.widget.querySelector(`.${Namespace.css.switch} div`), | ||
|
|
3820 | + }, | ||
|
|
3821 | + action: ActionTypes$1.togglePicker, | ||
|
|
3822 | + }); | ||
|
|
3823 | + } | ||
|
|
3824 | + }, this.optionsStore.options.promptTimeOnDateChangeTransitionDelay); | ||
|
|
3825 | + } | ||
|
|
3826 | + } | ||
|
|
3827 | + /** | ||
|
|
3828 | + * Whenever a locale is loaded via a plugin then store it here based on the | ||
|
|
3829 | + * locale name. E.g. loadedLocales['ru'] | ||
|
|
3830 | + */ | ||
|
|
3831 | + const loadedLocales = {}; | ||
|
|
3832 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3833 | + /** | ||
|
|
3834 | + * Called from a locale plugin. | ||
|
|
3835 | + * @param l locale object for localization options | ||
|
|
3836 | + */ | ||
|
|
3837 | + const loadLocale = (l) => { | ||
|
|
3838 | + if (loadedLocales[l.name]) | ||
|
|
3839 | + return; | ||
|
|
3840 | + loadedLocales[l.name] = l.localization; | ||
|
|
3841 | + }; | ||
|
|
3842 | + /** | ||
|
|
3843 | + * A sets the global localization options to the provided locale name. | ||
|
|
3844 | + * `loadLocale` MUST be called first. | ||
|
|
3845 | + * @param l | ||
|
|
3846 | + */ | ||
|
|
3847 | + const locale = (l) => { | ||
|
|
3848 | + let asked = loadedLocales[l]; | ||
|
|
3849 | + if (!asked) | ||
|
|
3850 | + return; | ||
|
|
3851 | + DefaultOptions.localization = asked; | ||
|
|
3852 | + }; | ||
|
|
3853 | + // noinspection JSUnusedGlobalSymbols | ||
|
|
3854 | + /** | ||
|
|
3855 | + * Called from a plugin to extend or override picker defaults. | ||
|
|
3856 | + * @param plugin | ||
|
|
3857 | + * @param option | ||
|
|
3858 | + */ | ||
|
|
3859 | + const extend = function (plugin, option) { | ||
|
|
3860 | + if (!plugin) | ||
|
|
3861 | + return tempusDominus; | ||
|
|
3862 | + if (!plugin.installed) { | ||
|
|
3863 | + // install plugin only once | ||
|
|
3864 | + plugin(option, { TempusDominus, Dates, Display, DateTime, ErrorMessages }, tempusDominus); | ||
|
|
3865 | + plugin.installed = true; | ||
|
|
3866 | + } | ||
|
|
3867 | + return tempusDominus; | ||
|
|
3868 | + }; | ||
|
|
3869 | + const version = '6.2.4'; | ||
|
|
3870 | + const tempusDominus = { | ||
|
|
3871 | + TempusDominus, | ||
|
|
3872 | + extend, | ||
|
|
3873 | + loadLocale, | ||
|
|
3874 | + locale, | ||
|
|
3875 | + Namespace, | ||
|
|
3876 | + DefaultOptions, | ||
|
|
3877 | + DateTime, | ||
|
|
3878 | + Unit, | ||
|
|
3879 | + version | ||
|
|
3880 | + }; | ||
|
|
3881 | + | ||
|
|
3882 | + export { DateTime, DefaultOptions, Namespace, TempusDominus, Unit, extend, loadLocale, locale, version }; | ||
|
|
3883 | + //# sourceMappingURL=tempus-dominus.esm.js.map |
@@ -154,19 +154,14 | |||||
|
154 | end |
|
154 | end |
|
155 |
|
155 | ||
|
156 | def manage |
|
156 | def manage |
|
157 | - @problems = Problem.order(date_added: :desc) |
|
157 | + @problems = Problem.order(date_added: :desc).includes(:tags) |
|
158 | end |
|
158 | end |
|
159 |
|
159 | ||
|
160 | def do_manage |
|
160 | def do_manage |
|
161 |
- if params |
|
161 | + change_date_added if params[:change_date_added] == '1' && params[:date_added].strip.empty? == false |
|
162 | - change_date_added |
|
162 | + add_to_contest if params.has_key? 'add_to_contest' |
|
163 | - elsif params.has_key? 'add_to_contest' |
|
163 | + set_available(params[:enable] == 'yes') if params[:change_enable] == '1' |
|
164 | - add_to_contest |
|
164 | + if params[:add_group] == '1' |
|
165 | - elsif params.has_key? 'enable_problem' |
|
||
|
166 | - set_available(true) |
|
||
|
167 | - elsif params.has_key? 'disable_problem' |
|
||
|
168 | - set_available(false) |
|
||
|
169 | - elsif params.has_key? 'add_group' |
|
||
|
170 | group = Group.find(params[:group_id]) |
|
165 | group = Group.find(params[:group_id]) |
|
171 | ok = [] |
|
166 | ok = [] |
|
172 | failed = [] |
|
167 | failed = [] |
@@ -180,10 +175,10 | |||||
|
180 | end |
|
175 | end |
|
181 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
176 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
182 | flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 |
|
177 | flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 |
|
183 | - elsif params.has_key? 'add_tags' |
|
178 | + end |
|
184 | - get_problems_from_params.each do |p| |
|
179 | + |
|
185 | - p.tag_ids += params[:tag_ids] |
|
180 | + if params[:add_tags] == '1' |
|
186 | - end |
|
181 | + get_problems_from_params.each { |p| p.tag_ids += params[:tag_ids] } |
|
187 | end |
|
182 | end |
|
188 |
|
183 | ||
|
189 | redirect_to :action => 'manage' |
|
184 | redirect_to :action => 'manage' |
@@ -45,8 +45,14 | |||||
|
45 |
|
45 | ||
|
46 | import "select2" |
|
46 | import "select2" |
|
47 | import "chart" |
|
47 | import "chart" |
|
|
48 | + | ||
|
|
49 | + //tempus dominus | ||
|
48 | import { TempusDominus } from "@eonasdan/tempus-dominus" |
|
50 | import { TempusDominus } from "@eonasdan/tempus-dominus" |
|
|
51 | + import * as TD from "@eonasdan/tempus-dominus-esm" | ||
|
|
52 | + import * as customDateFormat from '@eonasdan/tempus-dominus/customDateFormat' | ||
|
|
53 | + window.TD = TD | ||
|
49 | window.TempusDominus = TempusDominus |
|
54 | window.TempusDominus = TempusDominus |
|
|
55 | + window.customDateFormat = customDateFormat | ||
|
50 |
|
56 | ||
|
51 | //my own customization |
|
57 | //my own customization |
|
52 | import 'custom' |
|
58 | import 'custom' |
@@ -36,7 +36,7 | |||||
|
36 | = f.label :add_group, 'Add selected problems to user group' |
|
36 | = f.label :add_group, 'Add selected problems to user group' |
|
37 | = f.check_box :add_group, class: 'form-check-input' |
|
37 | = f.check_box :add_group, class: 'form-check-input' |
|
38 | .col-md-auto |
|
38 | .col-md-auto |
|
39 | - = f.select "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), {}, class: 'select2 form-control' |
|
39 | + = f.select "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), {}, class: 'select2 form-control', data: {width: "400px"} |
|
40 | .col-md-6 |
|
40 | .col-md-6 |
|
41 | .row.mb-3.align-items-center |
|
41 | .row.mb-3.align-items-center |
|
42 | .col-md-auto |
|
42 | .col-md-auto |
@@ -57,37 +57,6 | |||||
|
57 | .col-auto |
|
57 | .col-auto |
|
58 | = f.submit :go, class: 'btn btn-primary' |
|
58 | = f.submit :go, class: 'btn btn-primary' |
|
59 |
|
59 | ||
|
60 | - |
|
||
|
61 | - -# |
|
||
|
62 | - %ul.form-inline |
|
||
|
63 | - %li |
|
||
|
64 | - Change "Date added" to |
|
||
|
65 | - .input-group.date |
|
||
|
66 | - = text_field_tag :date_added, class: 'form-control' |
|
||
|
67 | - %span.input-group-addon |
|
||
|
68 | - %span.glyphicon.glyphicon-calendar |
|
||
|
69 | - -# = select_date Date.current, :prefix => 'date_added' |
|
||
|
70 | - |
|
||
|
71 | - = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm' |
|
||
|
72 | - %li |
|
||
|
73 | - Set "Available" to |
|
||
|
74 | - = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm' |
|
||
|
75 | - = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm' |
|
||
|
76 | - |
|
||
|
77 | - - if GraderConfiguration.multicontests? |
|
||
|
78 | - %li |
|
||
|
79 | - Add selected problems to contest |
|
||
|
80 | - = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) |
|
||
|
81 | - = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm' |
|
||
|
82 | - %li |
|
||
|
83 | - Add selected problems to user group |
|
||
|
84 | - = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2' |
|
||
|
85 | - = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary' |
|
||
|
86 | - %li |
|
||
|
87 | - Add the following tags to the selected problems |
|
||
|
88 | - = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"} |
|
||
|
89 | - = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary' |
|
||
|
90 | - |
|
||
|
91 | %table.table.table-hover.datatable |
|
60 | %table.table.table-hover.datatable |
|
92 | %thead |
|
61 | %thead |
|
93 | %tr{style: "text-align: left;"} |
|
62 | %tr{style: "text-align: left;"} |
@@ -110,7 +79,7 | |||||
|
110 | %td= problem.full_name |
|
79 | %td= problem.full_name |
|
111 | %td |
|
80 | %td |
|
112 | - problem.tags.each do |t| |
|
81 | - problem.tags.each do |t| |
|
113 |
- %span. |
|
82 | + %span.badge.text-bg-secondary= t.name |
|
114 | %td= problem.available |
|
83 | %td= problem.available |
|
115 | %td= problem.date_added |
|
84 | %td= problem.date_added |
|
116 | - if GraderConfiguration.multicontests? |
|
85 | - if GraderConfiguration.multicontests? |
@@ -149,33 +118,34 | |||||
|
149 | } |
|
118 | } |
|
150 | }); |
|
119 | }); |
|
151 |
|
120 | ||
|
152 | - $('.input-group.date').datetimepicker({ |
|
||
|
153 | - format: 'DD/MMM/YYYY', |
|
||
|
154 | - showTodayButton: true, |
|
||
|
155 | - locale: 'en', |
|
||
|
156 | - widgetPositioning: {horizontal: 'auto', vertical: 'bottom'}, |
|
||
|
157 | - |
|
||
|
158 | - }); |
|
||
|
159 | - |
|
||
|
160 | $('.datatable').DataTable({ |
|
121 | $('.datatable').DataTable({ |
|
161 | paging: false |
|
122 | paging: false |
|
162 | }); |
|
123 | }); |
|
163 | $('.select2').select2(); |
|
124 | $('.select2').select2(); |
|
164 |
|
125 | ||
|
165 |
|
126 | ||
|
166 | - new TempusDominus(document.getElementById('date_added'), { |
|
127 | + td = new TempusDominus(document.getElementById('date_added'), { |
|
167 | display: { |
|
128 | display: { |
|
168 | icons: { |
|
129 | icons: { |
|
169 | time: 'mi mi-td-time', |
|
130 | time: 'mi mi-td-time', |
|
170 | date: 'mi mi-td-date', |
|
131 | date: 'mi mi-td-date', |
|
171 | up: 'mi mi-td-up', |
|
132 | up: 'mi mi-td-up', |
|
172 | down: 'mi mi-td-down', |
|
133 | down: 'mi mi-td-down', |
|
173 |
- previous: ' |
|
134 | + previous: 'mi mi-td-previous', |
|
174 |
- next: ' |
|
135 | + next: 'mi mi-td-next', |
|
175 |
- today: ' |
|
136 | + today: 'mi mi-td-today', |
|
176 |
- clear: ' |
|
137 | + clear: 'mi mi-td-clear', |
|
177 |
- close: ' |
|
138 | + close: 'mi mi-td-close', |
|
178 | }, |
|
139 | }, |
|
|
140 | + components: { | ||
|
|
141 | + hours: false, | ||
|
|
142 | + minutes: false, | ||
|
|
143 | + seconds: false | ||
|
|
144 | + } | ||
|
|
145 | + }, | ||
|
|
146 | + localization: { | ||
|
|
147 | + locale: 'en-uk', | ||
|
|
148 | + format: 'dd/MMM/yyyy', | ||
|
179 | } |
|
149 | } |
|
180 | }); |
|
150 | }); |
|
181 |
|
151 |
@@ -56,4 +56,6 | |||||
|
56 |
|
56 | ||
|
57 | #pin "ace-rails-ap" |
|
57 | #pin "ace-rails-ap" |
|
58 | pin "chart", to: 'chart.js' # @3.9.1 |
|
58 | pin "chart", to: 'chart.js' # @3.9.1 |
|
59 | - pin "@eonasdan/tempus-dominus", to: "@eonasdan--tempus-dominus.js" # @6.2.4 |
|
59 | + pin "@eonasdan/tempus-dominus", to: "tempus-dominus/@eonasdan--tempus-dominus.js" # @6.2.4 |
|
|
60 | + pin "@eonasdan/tempus-dominus-esm", to: "tempus-dominus/tempus-dominus.esm.js" # @6.2.4 | ||
|
|
61 | + pin "@eonasdan/tempus-dominus/customDateFormat", to: "tempus-dominus/customDateFormat.js" # @6.2.4 |
file renamed from vendor/javascript/@eonasdan--tempus-dominus.js to vendor/javascript/tempus-dominus/@eonasdan--tempus-dominus.js |
You need to be logged in to leave comments.
Login now