Description:
prob manage
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r882:50181c2869eb - - 7 files changed: 4208 inserted, 62 deleted

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