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: 4207 inserted, 61 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 | 154 | end |
|
155 | 155 | |
|
156 | 156 | def manage |
|
157 | - @problems = Problem.order(date_added: :desc) | |
|
157 | + @problems = Problem.order(date_added: :desc).includes(:tags) | |
|
158 | 158 | end |
|
159 | 159 | |
|
160 | 160 | def do_manage |
|
161 |
- if params |
|
|
162 | - change_date_added | |
|
163 | - elsif params.has_key? 'add_to_contest' | |
|
164 | - add_to_contest | |
|
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' | |
|
161 | + change_date_added if params[:change_date_added] == '1' && params[:date_added].strip.empty? == false | |
|
162 | + add_to_contest if params.has_key? 'add_to_contest' | |
|
163 | + set_available(params[:enable] == 'yes') if params[:change_enable] == '1' | |
|
164 | + if params[:add_group] == '1' | |
|
170 | 165 | group = Group.find(params[:group_id]) |
|
171 | 166 | ok = [] |
|
172 | 167 | failed = [] |
@@ -180,10 +175,10 | |||
|
180 | 175 | end |
|
181 | 176 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
182 | 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' | |
|
184 | - get_problems_from_params.each do |p| | |
|
185 | - p.tag_ids += params[:tag_ids] | |
|
186 | 178 |
|
|
179 | + | |
|
180 | + if params[:add_tags] == '1' | |
|
181 | + get_problems_from_params.each { |p| p.tag_ids += params[:tag_ids] } | |
|
187 | 182 | end |
|
188 | 183 | |
|
189 | 184 | redirect_to :action => 'manage' |
@@ -45,8 +45,14 | |||
|
45 | 45 | |
|
46 | 46 | import "select2" |
|
47 | 47 | import "chart" |
|
48 | + | |
|
49 | + //tempus dominus | |
|
48 | 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 | 54 | window.TempusDominus = TempusDominus |
|
55 | + window.customDateFormat = customDateFormat | |
|
50 | 56 | |
|
51 | 57 | //my own customization |
|
52 | 58 | import 'custom' |
@@ -36,7 +36,7 | |||
|
36 | 36 | = f.label :add_group, 'Add selected problems to user group' |
|
37 | 37 | = f.check_box :add_group, class: 'form-check-input' |
|
38 | 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 | 40 | .col-md-6 |
|
41 | 41 | .row.mb-3.align-items-center |
|
42 | 42 | .col-md-auto |
@@ -57,37 +57,6 | |||
|
57 | 57 | .col-auto |
|
58 | 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 | 60 | %table.table.table-hover.datatable |
|
92 | 61 | %thead |
|
93 | 62 | %tr{style: "text-align: left;"} |
@@ -110,7 +79,7 | |||
|
110 | 79 | %td= problem.full_name |
|
111 | 80 | %td |
|
112 | 81 | - problem.tags.each do |t| |
|
113 |
- %span. |
|
|
82 | + %span.badge.text-bg-secondary= t.name | |
|
114 | 83 | %td= problem.available |
|
115 | 84 | %td= problem.date_added |
|
116 | 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 | 121 | $('.datatable').DataTable({ |
|
161 | 122 | paging: false |
|
162 | 123 | }); |
|
163 | 124 | $('.select2').select2(); |
|
164 | 125 | |
|
165 | 126 | |
|
166 | - new TempusDominus(document.getElementById('date_added'), { | |
|
127 | + td = new TempusDominus(document.getElementById('date_added'), { | |
|
167 | 128 | display: { |
|
168 | 129 | icons: { |
|
169 | 130 | time: 'mi mi-td-time', |
|
170 | 131 | date: 'mi mi-td-date', |
|
171 | 132 | up: 'mi mi-td-up', |
|
172 | 133 | down: 'mi mi-td-down', |
|
173 |
- previous: ' |
|
|
174 |
- next: ' |
|
|
175 |
- today: ' |
|
|
176 |
- clear: ' |
|
|
177 |
- close: ' |
|
|
134 | + previous: 'mi mi-td-previous', | |
|
135 | + next: 'mi mi-td-next', | |
|
136 | + today: 'mi mi-td-today', | |
|
137 | + clear: 'mi mi-td-clear', | |
|
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 | 57 | #pin "ace-rails-ap" |
|
58 | 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