@wernfried/daterangepicker 5.2.2 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2367 +1,2367 @@
1
- "use strict";
2
- var luxon = require("luxon");
3
- class DateRangePicker {
4
- #startDate = null;
5
- #endDate = null;
6
- constructor(element, options, cb) {
7
- if (typeof element === "string" && document.querySelectorAll(element).length > 1)
8
- throw new RangeError(`Option 'element' must match to one element only`);
9
- this.parentEl = "body";
10
- this.element = element instanceof HTMLElement ? element : document.querySelector(element);
11
- this.isInputText = this.element instanceof HTMLInputElement && this.element.type === "text";
12
- this.#startDate = luxon.DateTime.now().startOf("day");
13
- this.#endDate = luxon.DateTime.now().plus({ day: 1 }).startOf("day");
14
- this.minDate = null;
15
- this.maxDate = null;
16
- this.maxSpan = null;
17
- this.minSpan = null;
18
- this.defaultSpan = null;
19
- this.initalMonth = luxon.DateTime.now().startOf("month");
20
- this.autoApply = false;
21
- this.singleDatePicker = false;
22
- this.singleMonthView = false;
23
- this.showDropdowns = false;
24
- this.minYear = luxon.DateTime.now().minus({ year: 100 }).year;
25
- this.maxYear = luxon.DateTime.now().plus({ year: 100 }).year;
26
- this.showWeekNumbers = false;
27
- this.showISOWeekNumbers = false;
28
- this.showCustomRangeLabel = true;
29
- this.showLabel = !this.isInputText;
30
- this.timePicker = false;
31
- const usesMeridiems = new Intl.DateTimeFormat(luxon.DateTime.now().locale, { hour: "numeric" }).resolvedOptions();
32
- this.timePicker24Hour = !usesMeridiems.hour12;
33
- this.timePickerStepSize = luxon.Duration.fromObject({ minutes: 1 });
34
- this.linkedCalendars = true;
35
- this.autoUpdateInput = true;
36
- this.alwaysShowCalendars = false;
37
- this.isInvalidDate = null;
38
- this.isInvalidTime = null;
39
- this.isCustomDate = null;
40
- this.onOutsideClick = "apply";
41
- this.opens = this.element?.classList.contains("pull-right") ? "left" : "right";
42
- this.drops = this.element?.classList.contains("dropup") ? "up" : "down";
43
- this.buttonClasses = "btn btn-sm";
44
- this.applyButtonClasses = "btn-primary";
45
- this.cancelButtonClasses = "btn-default";
46
- this.weekendClasses = "weekend";
47
- this.weekendDayClasses = "weekend-day";
48
- this.todayClasses = "today";
49
- this.altInput = null;
50
- this.altFormat = null;
51
- this.externalStyle = null;
52
- this.ranges = {};
53
- this.locale = {
54
- direction: "ltr",
55
- format: luxon.DateTime.DATE_SHORT,
56
- // or DateTime.DATETIME_SHORT when timePicker: true
57
- separator: " - ",
58
- applyLabel: "Apply",
59
- cancelLabel: "Cancel",
60
- weekLabel: "W",
61
- customRangeLabel: "Custom Range",
62
- daysOfWeek: luxon.Info.weekdays("short"),
63
- monthNames: luxon.Info.months("long"),
64
- firstDay: luxon.Info.getStartOfWeek(),
65
- durationFormat: null
66
- };
67
- if (this.element == null)
68
- return;
69
- this.callback = function() {
70
- };
71
- this.isShowing = false;
72
- this.leftCalendar = {};
73
- this.rightCalendar = {};
74
- if (typeof options !== "object" || options === null)
75
- options = {};
76
- let dataOptions = {};
77
- const data = Array.from(this.element.attributes).filter((x) => x.name.startsWith("data-"));
78
- for (let item of data) {
79
- const name = item.name.replace(/^data-/g, "").replace(/-([a-z])/g, function(str) {
80
- return str[1].toUpperCase();
81
- });
82
- if (!Object.keys(this).concat(["startDate", "endDate"]).includes(name) || Object.keys(options).includes(name))
83
- continue;
84
- let ts = luxon.DateTime.fromISO(item.value);
85
- const isDate = ["startDate", "endDate", "minDate", "maxDate", "initalMonth"].includes(name);
86
- dataOptions[name] = ts.isValid && isDate ? ts : JSON.parse(item.value);
87
- }
88
- options = { ...dataOptions, ...options };
89
- if (typeof options.singleDatePicker === "boolean")
90
- this.singleDatePicker = options.singleDatePicker;
91
- if (!this.singleDatePicker && typeof options.singleMonthView === "boolean") {
92
- this.singleMonthView = options.singleMonthView;
93
- } else {
94
- this.singleMonthView = false;
95
- }
96
- if (!(options.externalStyle === null)) {
97
- const bodyStyle = window.getComputedStyle(document.body);
98
- if (bodyStyle && typeof bodyStyle[Symbol.iterator] === "function" && [...bodyStyle].some((x) => x.startsWith("--bulma-")))
99
- this.externalStyle = "bulma";
100
- }
101
- if (typeof options.template === "string" || options.template instanceof HTMLElement) {
102
- this.container = typeof options.template === "string" ? createElementFromHTML(options.template) : options.template;
103
- } else {
104
- let template = [
105
- '<div class="daterangepicker">',
106
- '<div class="ranges"></div>',
107
- '<div class="drp-calendar left">',
108
- '<table class="calendar-table">',
109
- "<thead></thead>",
110
- "<tbody></tbody>",
111
- "<tfoot>",
112
- '<tr class="calendar-time start-time"></tr>',
113
- this.singleMonthView ? '<tr class="calendar-time end-time"></tr>' : "",
114
- "</tfoot>",
115
- "</table>",
116
- "</div>"
117
- ];
118
- template.push(...[
119
- '<div class="drp-calendar right">',
120
- '<table class="calendar-table">',
121
- "<thead></thead>",
122
- "<tbody></tbody>",
123
- "<tfoot>",
124
- this.singleMonthView ? "" : '<tr class="calendar-time end-time"></tr>',
125
- "</tfoot>",
126
- "</table>",
127
- "</div>"
128
- ]);
129
- template.push(...[
130
- '<div class="drp-buttons">',
131
- '<div class="drp-duration-label"></div>',
132
- '<div class="drp-selected"></div>'
133
- ]);
134
- if (this.externalStyle === "bulma") {
135
- template.push(...[
136
- '<div class="buttons">',
137
- '<button class="cancelBtn button is-small" type="button"></button>',
138
- '<button class="applyBtn button is-small" disabled type="button"></button>',
139
- "</div>"
140
- ]);
141
- } else {
142
- template.push(...[
143
- "<div>",
144
- '<button class="cancelBtn" type="button"></button>',
145
- '<button class="applyBtn" disabled type="button"></button>',
146
- "</div>"
147
- ]);
148
- }
149
- template.push("</div></div>");
150
- options.template = template.join("");
151
- this.container = createElementFromHTML(options.template);
152
- }
153
- this.parentEl = document.querySelector(typeof options.parentEl === "string" ? options.parentEl : this.parentEl);
154
- this.parentEl.appendChild(this.container);
155
- if (typeof options.timePicker === "boolean")
156
- this.timePicker = options.timePicker;
157
- if (this.timePicker)
158
- this.locale.format = luxon.DateTime.DATETIME_SHORT;
159
- if (typeof options.locale === "object") {
160
- for (let key2 of ["separator", "applyLabel", "cancelLabel", "weekLabel"]) {
161
- if (typeof options.locale[key2] === "string")
162
- this.locale[key2] = options.locale[key2];
163
- }
164
- if (typeof options.locale.direction === "string") {
165
- if (["rtl", "ltr"].includes(options.locale.direction))
166
- this.locale.direction = options.locale.direction;
167
- else
168
- console.error(`Option 'options.locale.direction' must be 'rtl' or 'ltr'`);
169
- }
170
- if (["string", "object"].includes(typeof options.locale.format))
171
- this.locale.format = options.locale.format;
172
- if (Array.isArray(options.locale.daysOfWeek)) {
173
- if (options.locale.daysOfWeek.some((x) => typeof x !== "string"))
174
- console.error(`Option 'options.locale.daysOfWeek' must be an array of strings`);
175
- else
176
- this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
177
- }
178
- if (Array.isArray(options.locale.monthNames)) {
179
- if (options.locale.monthNames.some((x) => typeof x !== "string"))
180
- console.error(`Option 'locale.monthNames' must be an array of strings`);
181
- else
182
- this.locale.monthNames = options.locale.monthNames.slice();
183
- }
184
- if (typeof options.locale.firstDay === "number")
185
- this.locale.firstDay = options.locale.firstDay;
186
- if (typeof options.locale.customRangeLabel === "string") {
187
- var elem = document.createElement("textarea");
188
- elem.innerHTML = options.locale.customRangeLabel;
189
- var rangeHtml = elem.value;
190
- this.locale.customRangeLabel = rangeHtml;
191
- }
192
- if (["string", "object", "function"].includes(typeof options.locale.durationFormat) && options.locale.durationFormat != null)
193
- this.locale.durationFormat = options.locale.durationFormat;
194
- }
195
- this.container.classList.add(this.locale.direction);
196
- for (let key2 of [
197
- "timePicker24Hour",
198
- "showWeekNumbers",
199
- "showISOWeekNumbers",
200
- "showDropdowns",
201
- "linkedCalendars",
202
- "showCustomRangeLabel",
203
- "alwaysShowCalendars",
204
- "autoApply",
205
- "autoUpdateInput",
206
- "showLabel"
207
- ]) {
208
- if (typeof options[key2] === "boolean")
209
- this[key2] = options[key2];
210
- }
211
- for (let key2 of ["applyButtonClasses", "cancelButtonClasses", "weekendClasses", "weekendDayClasses", "todayClasses"]) {
212
- if (typeof options[key2] === "string") {
213
- this[key2] = options[key2];
214
- } else if (["weekendClasses", "weekendDayClasses", "todayClasses"].includes(key2) && options[key2] === null) {
215
- this[key2] = options[key2];
216
- }
217
- }
218
- for (let key2 of ["minYear", "maxYear"]) {
219
- if (typeof options[key2] === "number")
220
- this[key2] = options[key2];
221
- }
222
- for (let key2 of ["isInvalidDate", "isInvalidTime", "isCustomDate"]) {
223
- if (typeof options[key2] === "function")
224
- this[key2] = options[key2];
225
- else
226
- this[key2] = function() {
227
- return false;
228
- };
229
- }
230
- if (!this.singleDatePicker) {
231
- for (let opt of ["minSpan", "maxSpan", "defaultSpan"]) {
232
- if (["string", "number", "object"].includes(typeof options[opt])) {
233
- if (luxon.Duration.isDuration(options[opt]) && options[opt].isValid) {
234
- this[opt] = options[opt];
235
- } else if (luxon.Duration.fromISO(options[opt]).isValid) {
236
- this[opt] = luxon.Duration.fromISO(options[opt]);
237
- } else if (typeof options[opt] === "number" && luxon.Duration.fromObject({ seconds: options[opt] }).isValid) {
238
- this[opt] = luxon.Duration.fromObject({ seconds: options[opt] });
239
- } else if (options[opt] === null) {
240
- this[opt] = null;
241
- } else {
242
- console.error(`Option '${key}' is not valid`);
243
- }
244
- }
245
- }
246
- if (this.minSpan && this.maxSpan && this.minSpan > this.maxSpan) {
247
- this.minSpan = null;
248
- this.maxSpan = null;
249
- console.warn(`Ignore option 'minSpan' and 'maxSpan', because 'minSpan' must be smaller than 'maxSpan'`);
250
- }
251
- if (this.defaultSpan && this.minSpan && this.minSpan > this.defaultSpan) {
252
- this.defaultSpan = null;
253
- console.warn(`Ignore option 'defaultSpan', because 'defaultSpan' must be greater than 'minSpan'`);
254
- } else if (this.defaultSpan && this.maxSpan && this.maxSpan < this.defaultSpan) {
255
- this.defaultSpan = null;
256
- console.warn(`Ignore option 'defaultSpan', because 'defaultSpan' must be smaller than 'maxSpan'`);
257
- }
258
- }
259
- if (this.timePicker) {
260
- if (["string", "object", "number"].includes(typeof options.timePickerStepSize)) {
261
- let duration;
262
- if (luxon.Duration.isDuration(options.timePickerStepSize) && options.timePickerStepSize.isValid) {
263
- duration = options.timePickerStepSize;
264
- } else if (luxon.Duration.fromISO(options.timePickerStepSize).isValid) {
265
- duration = luxon.Duration.fromISO(options.timePickerStepSize);
266
- } else if (typeof options.timePickerStepSize === "number" && luxon.Duration.fromObject({ seconds: options.timePickerStepSize }).isValid) {
267
- duration = luxon.Duration.fromObject({ seconds: options.timePickerStepSize });
268
- } else {
269
- console.error(`Option 'timePickerStepSize' is not valid`);
270
- duration = this.timePickerStepSize;
271
- }
272
- var valid = [];
273
- for (let unit of ["minutes", "seconds"])
274
- valid.push(...[1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30].map((x) => {
275
- return luxon.Duration.fromObject({ [unit]: x });
276
- }));
277
- valid.push(...[1, 2, 3, 4, 6].map((x) => {
278
- return luxon.Duration.fromObject({ hours: x });
279
- }));
280
- if (this.timePicker24Hour)
281
- valid.push(...[8, 12].map((x) => {
282
- return luxon.Duration.fromObject({ hours: x });
283
- }));
284
- if (valid.some((x) => duration.rescale().equals(x))) {
285
- this.timePickerStepSize = duration.rescale();
286
- } else {
287
- console.error(`Option 'timePickerStepSize' ${JSON.stringify(duration.toObject())} is not valid`);
288
- }
289
- }
290
- if (this.maxSpan && this.timePickerStepSize > this.maxSpan)
291
- console.error(`Option 'timePickerStepSize' ${JSON.stringify(this.timePickerStepSize.toObject())} must be smaller than 'maxSpan'`);
292
- this.timePickerOpts = {
293
- showMinutes: this.timePickerStepSize < luxon.Duration.fromObject({ hours: 1 }),
294
- showSeconds: this.timePickerStepSize < luxon.Duration.fromObject({ minutes: 1 }),
295
- hourStep: this.timePickerStepSize >= luxon.Duration.fromObject({ hours: 1 }) ? this.timePickerStepSize.hours : 1,
296
- minuteStep: this.timePickerStepSize >= luxon.Duration.fromObject({ minutes: 1 }) ? this.timePickerStepSize.minutes : 1,
297
- secondStep: this.timePickerStepSize.seconds
298
- };
299
- }
300
- for (let opt of ["startDate", "endDate", "minDate", "maxDate", "initalMonth"]) {
301
- if (opt === "endDate" && this.singleDatePicker)
302
- continue;
303
- if (typeof options[opt] === "object") {
304
- if (luxon.DateTime.isDateTime(options[opt]) && options[opt].isValid) {
305
- this[opt] = options[opt];
306
- } else if (options[opt] instanceof Date) {
307
- this[opt] = luxon.DateTime.fromJSDate(options[opt]);
308
- } else if (options[opt] === null) {
309
- this[opt] = null;
310
- } else {
311
- console.error(`Option '${opt}' must be a luxon.DateTime or Date or string`);
312
- }
313
- } else if (typeof options[opt] === "string") {
314
- const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
315
- if (luxon.DateTime.fromISO(options[opt]).isValid) {
316
- this[opt] = luxon.DateTime.fromISO(options[opt]);
317
- } else if (luxon.DateTime.fromFormat(options[opt], format, { locale: luxon.DateTime.now().locale }).isValid) {
318
- this[opt] = luxon.DateTime.fromFormat(options[opt], format, { locale: luxon.DateTime.now().locale });
319
- } else {
320
- const invalid = luxon.DateTime.fromFormat(options[opt], format, { locale: luxon.DateTime.now().locale }).invalidExplanation;
321
- console.error(`Option '${opt}' is not a valid string: ${invalid}`);
322
- }
323
- }
324
- }
325
- if (this.isInputText) {
326
- if (this.element.value != "") {
327
- const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
328
- if (this.singleDatePicker && typeof options.startDate === "undefined") {
329
- const start = luxon.DateTime.fromFormat(this.element.value, format, { locale: luxon.DateTime.now().locale });
330
- if (start.isValid) {
331
- this.#startDate = start;
332
- } else {
333
- console.error(`Value "${this.element.value}" in <input> is not a valid string: ${start.invalidExplanation}`);
334
- }
335
- } else if (!this.singleDatePicker && typeof options.startDate === "undefined" && typeof options.endDate === "undefined") {
336
- const split = this.element.value.split(this.locale.separator);
337
- if (split.length === 2) {
338
- const start = luxon.DateTime.fromFormat(split[0], format, { locale: luxon.DateTime.now().locale });
339
- const end = luxon.DateTime.fromFormat(split[1], format, { locale: luxon.DateTime.now().locale });
340
- if (start.isValid && end.isValid) {
341
- this.#startDate = start;
342
- this.#endDate = end;
343
- } else {
344
- console.error(`Value in <input> is not a valid string: ${start.invalidExplanation} - ${end.invalidExplanation}`);
345
- }
346
- } else {
347
- console.error(`Value "${this.element.value}" in <input> is not a valid string`);
348
- }
349
- }
350
- }
351
- }
352
- if (this.singleDatePicker) {
353
- this.#endDate = this.#startDate;
354
- } else if (this.#endDate < this.#startDate) {
355
- console.error(`Option 'endDate' ${this.#endDate} must not be earlier than 'startDate' ${this.#startDate}`);
356
- }
357
- if (!this.timePicker) {
358
- if (this.minDate) this.minDate = this.minDate.startOf("day");
359
- if (this.maxDate) this.maxDate = this.maxDate.endOf("day");
360
- this.#startDate = this.#startDate.startOf("day");
361
- this.#endDate = this.#endDate.endOf("day");
362
- }
363
- if (!this.#startDate && this.initalMonth) {
364
- this.#endDate = null;
365
- if (this.timePicker)
366
- console.error(`Option 'initalMonth' works only with 'timePicker: false'`);
367
- } else {
368
- const violations = this.validateInput(null, false);
369
- if (violations != null) {
370
- let vio = violations.startDate;
371
- if (vio.length > 0) {
372
- if (vio.some((x) => x.reason.startsWith("isInvalid"))) {
373
- console.error(`Value of startDate "${this.#startDate}" violates ${vio.find((x) => x.reason.startsWith("isInvalid")).reason}`);
374
- } else {
375
- const newDate = vio.filter((x) => x.new != null).at(-1).new;
376
- if (typeof process !== "undefined" && process.env.JEST_WORKER_ID == null)
377
- console.warn(`Correcting startDate from ${this.#startDate} to ${newDate}`);
378
- this.#startDate = newDate;
379
- }
380
- }
381
- if (!this.singleDatePicker) {
382
- vio = violations.endDate.filter((x) => x.new != null);
383
- if (vio.length > 0) {
384
- if (vio.some((x) => x.reason.startsWith("isInvalid"))) {
385
- console.error(`Value of endDate "${this.#endDate}" violates ${vio.find((x) => x.reason.startsWith("isInvalid")).reason}`);
386
- } else {
387
- const newDate = vio.filter((x) => x.new != null).at(-1).new;
388
- if (typeof process !== "undefined" && process.env.JEST_WORKER_ID == null)
389
- console.warn(`Correcting endDate from ${this.#endDate} to ${newDate}`);
390
- this.#endDate = newDate;
391
- }
392
- }
393
- }
394
- }
395
- }
396
- if (this.singleDatePicker) {
397
- if (typeof options.altInput === "string") {
398
- const el = document.querySelector(options.altInput);
399
- this.altInput = el instanceof HTMLInputElement && el.type === "text" ? el : null;
400
- } else if (options.altInput instanceof HTMLElement) {
401
- this.altInput = options.altInput instanceof HTMLInputElement && options.altInput.type === "text" ? options.altInput : null;
402
- }
403
- } else if (!this.singleDatePicker && Array.isArray(options.altInput) && options.altInput.length === 2) {
404
- this.altInput = [];
405
- for (let item of options.altInput) {
406
- const el = typeof item === "string" ? document.querySelector(item) : item;
407
- if (el instanceof HTMLInputElement && el.type === "text")
408
- this.altInput.push(el);
409
- }
410
- if (this.altInput.length !== 2)
411
- this.altInput = null;
412
- } else if (options.altInput != null) {
413
- console.warn(`Option 'altInput' ${JSON.stringify(options.altInput)} is not valid`);
414
- }
415
- if (options.altInput && ["function", "string"].includes(typeof options.altFormat))
416
- this.altFormat = options.altFormat;
417
- if (typeof options.opens === "string") {
418
- if (["left", "right", "center"].includes(options.opens))
419
- this.opens = options.opens;
420
- else
421
- console.error(`Option 'options.opens' must be 'left', 'right' or 'center'`);
422
- }
423
- if (typeof options.drops === "string") {
424
- if (["up", "down", "auto"].includes(options.drops))
425
- this.drops = options.drops;
426
- else
427
- console.error(`Option 'options.drops' must be 'up', 'down' or 'auto'`);
428
- }
429
- if (Array.isArray(options.buttonClasses)) {
430
- this.buttonClasses = options.buttonClasses.join(" ");
431
- } else if (typeof options.buttonClasses === "string") {
432
- this.buttonClasses = options.buttonClasses;
433
- }
434
- if (typeof options.onOutsideClick === "string") {
435
- if (["cancel", "apply"].includes(options.onOutsideClick))
436
- this.onOutsideClick = options.onOutsideClick;
437
- else
438
- console.error(`Option 'options.onOutsideClick' must be 'cancel' or 'apply'`);
439
- }
440
- if (this.locale.firstDay != 1) {
441
- let iterator = this.locale.firstDay;
442
- while (iterator > 1) {
443
- this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
444
- iterator--;
445
- }
446
- }
447
- if (!this.singleDatePicker && typeof options.ranges === "object") {
448
- for (let range in options.ranges) {
449
- let start, end;
450
- if (["string", "object"].includes(typeof options.ranges[range][0])) {
451
- if (luxon.DateTime.isDateTime(options.ranges[range][0]) && options.ranges[range][0].isValid) {
452
- start = options.ranges[range][0];
453
- } else if (options.ranges[range][0] instanceof Date) {
454
- start = luxon.DateTime.fromJSDate(options.ranges[range][0]);
455
- } else if (typeof options.ranges[range][0] === "string" && luxon.DateTime.fromISO(options.ranges[range][0]).isValid) {
456
- start = luxon.DateTime.fromISO(options.ranges[range][0]);
457
- } else {
458
- console.error(`Option ranges['${range}'] is not am array of valid ISO-8601 string or luxon.DateTime or Date`);
459
- }
460
- }
461
- if (["string", "object"].includes(typeof options.ranges[range][1])) {
462
- if (luxon.DateTime.isDateTime(options.ranges[range][1]) && options.ranges[range][1].isValid) {
463
- end = options.ranges[range][1];
464
- } else if (options.ranges[range][1] instanceof Date) {
465
- end = luxon.DateTime.fromJSDate(options.ranges[range][1]);
466
- } else if (typeof options.ranges[range][1] === "string" && luxon.DateTime.fromISO(options.ranges[range][1]).isValid) {
467
- end = luxon.DateTime.fromISO(options.ranges[range][1]);
468
- } else {
469
- console.error(`Option ranges['${range}'] is not a valid ISO-8601 string or luxon.DateTime or Date`);
470
- }
471
- }
472
- if (start == null || end == null)
473
- continue;
474
- var elem = document.createElement("textarea");
475
- elem.innerHTML = range;
476
- this.ranges[elem.value] = [start, end];
477
- }
478
- var list = "<ul>";
479
- for (let range in this.ranges)
480
- list += `<li data-range-key="${range}">${range}</li>`;
481
- if (this.showCustomRangeLabel)
482
- list += `<li data-range-key="${this.locale.customRangeLabel}">${this.locale.customRangeLabel}</li>`;
483
- list += "</ul>";
484
- this.container.querySelector(".ranges").prepend(createElementFromHTML(list));
485
- this.container.classList.add("show-ranges");
486
- }
487
- if (typeof cb === "function")
488
- this.callback = cb;
489
- if (!this.timePicker)
490
- this.container.querySelectorAll(".calendar-time").forEach((el) => {
491
- el.style.display = "none";
492
- });
493
- if (this.timePicker && this.autoApply)
494
- this.autoApply = false;
495
- if (this.autoApply)
496
- this.container.classList.add("auto-apply");
497
- if (this.singleDatePicker || this.singleMonthView) {
498
- this.container.classList.add("single");
499
- this.container.querySelector(".drp-calendar.left").classList.add("single");
500
- this.container.querySelector(".drp-calendar.left").style.display = "";
501
- this.container.querySelector(".drp-calendar.right").style.display = "none";
502
- if (!this.timePicker && this.autoApply)
503
- this.container.classList.add("auto-apply");
504
- }
505
- if (this.singleDatePicker || !Object.keys(this.ranges).length || this.alwaysShowCalendars)
506
- this.container.classList.add("show-calendar");
507
- this.container.classList.add(`opens${this.opens}`);
508
- this.container.querySelectorAll(".applyBtn, .cancelBtn").forEach((el) => {
509
- el.classList.add(...this.buttonClasses.split(" "));
510
- });
511
- if (this.applyButtonClasses.length)
512
- this.container.querySelector(".applyBtn").classList.add(...this.applyButtonClasses.split(" "));
513
- if (this.cancelButtonClasses.length)
514
- this.container.querySelector(".cancelBtn").classList.add(...this.cancelButtonClasses.split(" "));
515
- this.container.querySelector(".applyBtn").innerHTML = this.locale.applyLabel;
516
- this.container.querySelector(".cancelBtn").innerHTML = this.locale.cancelLabel;
517
- this.addListener(".drp-calendar", "click", ".prev", this.clickPrev.bind(this));
518
- this.addListener(".drp-calendar", "click", ".next", this.clickNext.bind(this));
519
- this.addListener(".drp-calendar", "mousedown", "td.available", this.clickDate.bind(this));
520
- this.addListener(".drp-calendar", "mouseenter", "td.available", this.hoverDate.bind(this));
521
- this.addListener(".drp-calendar", "change", "select.yearselect,select.monthselect", this.monthOrYearChanged.bind(this));
522
- this.addListener(".drp-calendar", "change", "select.hourselect,select.minuteselect,select.secondselect,select.ampmselect", this.timeChanged.bind(this));
523
- this.addListener(".ranges", "click", "li", this.clickRange.bind(this));
524
- this.addListener(".ranges", "mouseenter", "li", this.hoverRange.bind(this));
525
- this.addListener(".ranges", "mouseleave", "li", this.leaveRange.bind(this));
526
- this.addListener(".drp-buttons", "click", "button.applyBtn", this.clickApply.bind(this));
527
- this.addListener(".drp-buttons", "click", "button.cancelBtn", this.clickCancel.bind(this));
528
- if (this.element.matches("input") || this.element.matches("button")) {
529
- this.element.addEventListener("click", this.#showProxy);
530
- this.element.addEventListener("focus", this.#showProxy);
531
- this.element.addEventListener("keyup", this.#elementChangedProxy);
532
- this.element.addEventListener("keydown", this.#keydownProxy);
533
- } else {
534
- this.element.addEventListener("click", this.#toggleProxy);
535
- this.element.addEventListener("keydown", this.#toggleProxy);
536
- }
537
- this.updateElement();
538
- }
539
- /**
540
- * startDate
541
- * @type {external:DateTime}
542
- */
543
- get startDate() {
544
- return this.timePicker ? this.#startDate : this.#startDate.startOf("day");
545
- }
546
- /**
547
- * endDate
548
- * @type {external:DateTime}
549
- */
550
- get endDate() {
551
- return this.singleDatePicker ? null : this.timePicker ? this.#endDate : this.#endDate.endOf("day");
552
- }
553
- set startDate(val) {
554
- this.#startDate = val;
555
- }
556
- set endDate(val) {
557
- this.#endDate = val;
558
- }
559
- /**
560
- * DateRangePicker specific events
561
- */
562
- #events = {
563
- /**
564
- * Emitted when the date is changed through `<input>` element or via {@link #DateRangePicker+setStartDate|setStartDate} or
565
- * {@link #DateRangePicker+setRange|setRange} and date is not valid due to
566
- * `minDate`, `maxDate`, `minSpan`, `maxSpan`, `invalidDate` and `invalidTime` constraints.<br>
567
- * Event is only triggered when date string is valid and date value is changing<br>
568
- * @event
569
- * @name "violate"
570
- * @property {DateRangePickerEvent} event - The Event object
571
- * @property {DateRangePicker} event.picker - The daterangepicker object
572
- * @property {InputViolation} event.violation - The daterangepicker object
573
- * @property {NewDate} event.newDate - Object of corrected date values
574
- * @property {boolean} event.cancelable=true - By calling `event.preventDefault()` the `newDate` values will apply
575
- * @example
576
- * daterangepicker('#picker', {
577
- * startDate: DateTime.now(),
578
- * // allow only dates from current year
579
- * minDate: DateTime.now().startOf('year'),
580
- * manDate: DateTime.now().endOf('year'),
581
- * singleDatePicker: true,
582
- * locale: {
583
- * format: DateTime.DATETIME_SHORT
584
- * }
585
- * }).addEventListener('violate', (ev) => {
586
- * ev.newDate.startDate = DateTime.now().minus({ days: 3 }).startOf('day');
587
- * ev.preventDefault();
588
- * });
589
- *
590
- * // Try to set date outside permitted range at <input> elemet
591
- * const input = document.querySelector('#picker');
592
- * input.value = DateTime.now().minus({ years: 10 })).toLocaleString(DateTime.DATETIME_SHORT)
593
- * input.dispatchEvent(new Event('keyup'));
594
-
595
- * // Try to set date outside permitted range by code
596
- * const drp = getDateRangePicker('#picker');
597
- * drp.setStartDate(DateTime.now().minus({ years: 10 });
598
- *
599
- * // -> Calendar selects and shows "today - 3 days"
600
- */
601
- onViolate: { type: "violate", param: (violation, newDate) => {
602
- return { ...violation, ...{ cancelable: true } };
603
- } },
604
- /**
605
- * Emitted before the calendar time picker is rendered.
606
- * @event
607
- * @name "beforeRenderTimePicker"
608
- * @property {DateRangePickerEvent} event - The Event object
609
- * @property {DateRangePicker} event.picker - The daterangepicker object
610
- */
611
- onBeforeRenderTimePicker: { type: "beforeRenderTimePicker" },
612
- /**
613
- * Emitted before the calendar is rendered.
614
- * @event
615
- * @name "beforeRenderCalendar"
616
- * @property {DateRangePickerEvent} event - The Event object
617
- * @property {DateRangePicker} event.picker - The daterangepicker object
618
- */
619
- onBeforeRenderCalendar: { type: "beforeRenderCalendar" },
620
- /**
621
- * Emitted when the picker is shown
622
- * @event
623
- * @name "show"
624
- * @property {DateRangePickerEvent} event - The Event object
625
- * @property {DateRangePicker} event.picker - The daterangepicker object
626
- */
627
- onShow: { type: "show" },
628
- /**
629
- * Emitted before the picker will hide.
630
- * @event
631
- * @name "beforeHide"
632
- * @property {DateRangePickerEvent} event - The Event object
633
- * @property {DateRangePicker} event.picker - The daterangepicker object
634
- * @property {boolean} event.cancelable=true - Hide is canceled by calling `event.preventDefault()`
635
- */
636
- onBeforeHide: { type: "beforeHide", param: { cancelable: true } },
637
- /**
638
- * Emitted when the picker is hidden
639
- * @event
640
- * @name "hide"
641
- * @property {DateRangePickerEvent} event - The Event object
642
- * @property {DateRangePicker} event.picker - The daterangepicker object
643
- */
644
- onHide: { type: "hide" },
645
- /**
646
- * Emitted when the calendar(s) are shown.
647
- * Only useful when {@link #Ranges|Ranges} are used.
648
- * @event
649
- * @name "showCalendar"
650
- * @property {DateRangePickerEvent} event - The Event object
651
- * @property {DateRangePicker} event.picker - The daterangepicker object
652
- */
653
- onShowCalendar: { type: "showCalendar" },
654
- /**
655
- * Emitted when the calendar(s) are hidden. Only used when {@link #Ranges|Ranges} are used.
656
- * @event
657
- * @name "hideCalendar"
658
- * @property {DateRangePickerEvent} event - The Event object
659
- * @property {DateRangePicker} event.picker - The daterangepicker object
660
- */
661
- onHideCalendar: { type: "hideCalendar" },
662
- /**
663
- * Emitted when user clicks outside the picker. Use option `onOutsideClick` to define the default action, then you may not need to handle this event.
664
- * @event
665
- * @name "outsideClick"
666
- * @property {DateRangePickerEvent} event - The Event object
667
- * @property {DateRangePicker} event.picker - The daterangepicker object
668
- * @property {boolean} event.cancelable=true - Call `event.preventDefault()` to prevent default behaviour.<br>
669
- * Useful to define custome areas where click shall not hide the picker
670
- */
671
- onOutsideClick: { type: "outsideClick", param: { cancelable: true } },
672
- /**
673
- * Emitted when the date changed. Does not trigger when time is changed, use {@link #event_timeChange|"timeChange"} to handle it
674
- * @event
675
- * @name "dateChange"
676
- * @property {DateRangePickerEvent} event - The Event object
677
- * @property {DateRangePicker} event.picker - The daterangepicker object
678
- * @property {string} event.side - Either `'start'` or `'end'` indicating whether `startDate` or `endDate` was changed. `null` for singleDatePicker
679
- */
680
- onDateChange: { type: "dateChange", param: (side) => {
681
- return side;
682
- } },
683
- /**
684
- * Emitted when the time changed. Does not trigger when date is changed
685
- * @event
686
- * @name "timeChange"
687
- * @property {DateRangePickerEvent} event - The Event object
688
- * @property {DateRangePicker} event.picker - The daterangepicker object
689
- * @property {string} event.side - Either `'start'` or `'end'` indicating whether `startDate` or `endDate` was changed. `null` for singleDatePicker
690
- */
691
- onTimeChange: { type: "timeChange", param: (side) => {
692
- return side;
693
- } },
694
- /**
695
- * Emitted when the `Apply` button is clicked, or when a predefined {@link #Ranges|Ranges} is clicked
696
- * @event
697
- * @name "apply"
698
- * @property {DateRangePickerEvent} event - The Event object
699
- * @property {DateRangePicker} event.picker - The daterangepicker object
700
- */
701
- onApply: { type: "apply" },
702
- /**
703
- * Emitted when the `Cancel` button is clicked
704
- * @event
705
- * @name "cancel"
706
- * @property {DateRangePickerEvent} event - The Event object
707
- * @property {DateRangePicker} event.picker - The daterangepicker object
708
- */
709
- onCancel: { type: "cancel" },
710
- /**
711
- * Emitted when the date is changed through `<input>` element. Event is only triggered when date string is valid and date value has changed
712
- * @event
713
- * @name "inputChange"
714
- * @property {DateRangePickerEvent} event - The Event object
715
- * @property {DateRangePicker} event.picker - The daterangepicker object
716
- */
717
- onInputChange: { type: "inputChange" },
718
- /**
719
- * Emitted after month view changed, for example by click on 'prev' or 'next'
720
- * @event
721
- * @name "monthViewChange"
722
- * @property {DateRangePickerEvent} event - The Event object
723
- * @property {DateRangePicker} event.picker - The daterangepicker object
724
- * @property {external:DateTime} event.left - The first day of month in left-hand calendar
725
- * @property {external:DateTime} event.right - The first day of month in left-hand calendar or `null` for singleDatePicker
726
- */
727
- onMonthViewChange: {
728
- type: "monthViewChange",
729
- param: (left, right) => {
730
- return {
731
- left: this.leftCalendar.month.startOf("month"),
732
- right: this.singleMonthView || this.singleDatePicker ? null : this.rightCalendar.month.startOf("month")
733
- };
734
- }
735
- }
736
- };
737
- /**
738
- * Getter for all DateRangePickerEvents
739
- */
740
- get events() {
741
- return this.#events;
742
- }
743
- #outsideClickProxy = this.outsideClick.bind(this);
744
- #onResizeProxy = this.move.bind(this);
745
- #dropdownClickWrapper = (e) => {
746
- const match = e.target.closest('[data-toggle="dropdown"]');
747
- if (match && document.contains(match))
748
- this.#outsideClickProxy(e);
749
- };
750
- #showProxy = this.show.bind(this);
751
- #elementChangedProxy = this.elementChanged.bind(this);
752
- #keydownProxy = this.keydown.bind(this);
753
- #toggleProxy = this.toggle.bind(this);
754
- /* #region Set startDate/endDate */
755
- /**
756
- * Sets the date range picker's currently selected start date to the provided date.<br>
757
- * `startDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
758
- * Invalid date values are handled by {@link #DateRangePicker+violate|violate} Event
759
- * @param {external:DateTime|external:Date|string} startDate - startDate to be set. In case of ranges, the current `endDate` is used.
760
- * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
761
- * @returns {InputViolation} - Object of violations or `null` if no violation have been found
762
- * @example
763
- * const drp = getDateRangePicker('#picker');
764
- * drp.setStartDate(DateTime.now().startOf('hour'));
765
- */
766
- setStartDate(startDate, updateView = true) {
767
- if (!this.singleDatePicker)
768
- return setRange(startDate, this.#endDate, updateView);
769
- const oldDate = this.#startDate;
770
- let newDate = this.parseDate(startDate);
771
- if (newDate.equals(oldDate))
772
- return null;
773
- const violations = this.validateInput([newDate, null], true);
774
- if (violations != null) {
775
- if (violations.newDate != null) {
776
- newDate = violations.newDate.startDate;
777
- } else {
778
- return violations;
779
- }
780
- }
781
- const monthChange = !this.#startDate.hasSame(newDate, "month");
782
- this.#startDate = newDate;
783
- this.#endDate = this.#startDate;
784
- if (!this.timePicker) {
785
- this.#startDate = this.#startDate.startOf("day");
786
- this.#endDate = this.#endDate.endOf("day");
787
- }
788
- this.updateElement();
789
- if (updateView)
790
- this.updateView(monthChange);
791
- return violations;
792
- }
793
- /**
794
- * Sets the date range picker's currently selected start date to the provided date.<br>
795
- * `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
796
- * Invalid date values are handled by {@link #DateRangePicker+violate|violate} Event
797
- * @param {external:DateTime|external:Date|string} endDate - endDate to be set. In case of ranges, the current `startDate` is used.
798
- * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
799
- * @returns {InputViolation} - Object of violations or `null` if no violation have been found
800
- * @example
801
- * const drp = getDateRangePicker('#picker');
802
- * drp.setEndDate(DateTime.now().startOf('hour'));
803
- */
804
- setEndDate(endDate, updateView = true) {
805
- return this.singleDatePicker ? null : setRange(this.#startDate, endDate, updateView);
806
- }
807
- /**
808
- * Sets the date range picker's currently selected start date to the provided date.<br>
809
- * `startDate` and `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
810
- * Invalid date values are handled by {@link #DateRangePicker+violate|violate} Event
811
- * @param {external:DateTime|external:Date|string} startDate - startDate to be set
812
- * @param {external:DateTime|external:Date|string} endDate - endDate to be set
813
- * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
814
- * @returns {InputViolation} - Object of violations or `null` if no violation have been found
815
- * @example
816
- * const drp = getDateRangePicker('#picker');
817
- * drp.setRange(DateTime.now().startOf('hour'), DateTime.now().endOf('day'));
818
- */
819
- setRange(startDate, endDate, updateView = true) {
820
- if (this.singleDatePicker)
821
- return;
822
- if (!this.#endDate)
823
- this.#endDate = this.#startDate;
824
- const oldDate = [this.#startDate, this.#endDate];
825
- let newDate = [this.parseDate(startDate), this.parseDate(endDate)];
826
- if (oldDate[0].equals(newDate[0]) && oldDate[1].equals(newDate[1]) || newDate[0] > newDate[1])
827
- return;
828
- const violations = this.validateInput([newDate[0], newDate[1]], true);
829
- if (violations != null) {
830
- if (violations.newDate != null) {
831
- newDate[0] = violations.newDate.startDate;
832
- newDate[1] = violations.newDate.endDate;
833
- } else {
834
- return violations;
835
- }
836
- }
837
- const monthChange = !this.#startDate.hasSame(newDate[0], "month") || !this.#endDate.hasSame(newDate[1], "month");
838
- this.#startDate = newDate[0];
839
- this.#endDate = newDate[1];
840
- if (!this.timePicker) {
841
- this.#startDate = this.#startDate.startOf("day");
842
- this.#endDate = this.#endDate.endOf("day");
843
- }
844
- this.updateElement();
845
- if (updateView)
846
- this.updateView(monthChange);
847
- return violations;
848
- }
849
- /**
850
- * Parse date value
851
- * @param {sting|external:DateTime|Date} value - The value to be parsed
852
- * @returns {external:DateTime} - DateTime object
853
- */
854
- parseDate(value) {
855
- if (typeof value === "object") {
856
- if (luxon.DateTime.isDateTime(value) && value.isValid) {
857
- return value;
858
- } else if (value instanceof Date) {
859
- return luxon.DateTime.fromJSDate(value);
860
- } else {
861
- throw RangeError(`Value must be a luxon.DateTime or Date or string`);
862
- }
863
- } else if (typeof value === "string") {
864
- const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
865
- if (luxon.DateTime.fromISO(value).isValid) {
866
- return luxon.DateTime.fromISO(value);
867
- } else if (luxon.DateTime.fromFormat(value, format, { locale: luxon.DateTime.now().locale }).isValid) {
868
- return luxon.DateTime.fromFormat(value, format, { locale: luxon.DateTime.now().locale });
869
- } else {
870
- const invalid = luxon.DateTime.fromFormat(value, format, { locale: luxon.DateTime.now().locale }).invalidExplanation;
871
- throw RangeError(`Value is not a valid string: ${invalid}`);
872
- }
873
- }
874
- }
875
- /* #endregion */
876
- /**
877
- * Format a DateTime object
878
- * @param {external:DateTime} date - The DateTime to format
879
- * @param {object|string} format=this.locale.format - The format option
880
- * @returns {string} - Formatted date string
881
- */
882
- formatDate(date, format = this.locale.format) {
883
- if (typeof format === "object") {
884
- return date.toLocaleString(format);
885
- } else {
886
- if (luxon.Settings.defaultLocale === null) {
887
- const locale = luxon.DateTime.now().locale;
888
- return date.toFormat(format, { locale });
889
- } else {
890
- return date.toFormat(format);
891
- }
892
- }
893
- }
894
- /**
895
- * Set Duration Label to selected range (if used) and selected dates
896
- * @private
897
- */
898
- updateLabel() {
899
- if (this.showLabel) {
900
- let text = this.formatDate(this.#startDate);
901
- if (!this.singleDatePicker) {
902
- text += this.locale.separator;
903
- if (this.#endDate)
904
- text += this.formatDate(this.#endDate);
905
- }
906
- this.container.querySelector(".drp-selected").innerHTML = text;
907
- }
908
- if (this.singleDatePicker || this.locale.durationFormat == null)
909
- return;
910
- if (!this.#endDate) {
911
- this.container.querySelector(".drp-duration-label").innerHTML = "";
912
- return;
913
- }
914
- if (typeof this.locale.durationFormat === "function") {
915
- this.container.querySelector(".drp-duration-label").innerHTML = this.locale.durationFormat(this.#startDate, this.#endDate);
916
- } else {
917
- let duration = this.#endDate.plus({ milliseconds: 1 }).diff(this.#startDate).rescale().set({ milliseconds: 0 });
918
- if (!this.timePicker)
919
- duration = duration.set({ seconds: 0, minutes: 0, hours: 0 });
920
- duration = duration.removeZeros();
921
- if (typeof this.locale.durationFormat === "object") {
922
- this.container.querySelector(".drp-duration-label").innerHTML = duration.toHuman(this.locale.durationFormat);
923
- } else {
924
- this.container.querySelector(".drp-duration-label").innerHTML = duration.toFormat(this.locale.durationFormat);
925
- }
926
- }
927
- }
928
- /**
929
- * @typedef InputViolation
930
- * @type {Object}
931
- * @typedef {object} Violation
932
- * @property {string} reason - The type/reason of violation
933
- * @property {external:DateTime} old - Old value startDate/endDate
934
- * @property {external:DateTime} new? - Corrected value of startDate/endDate if existing
935
- * @typedef {object} NewDate
936
- * @property {external:DateTime} newDate.startDate- Object with corrected values
937
- * @property {external:DateTime} newDate.endDate - Object with corrected values
938
- * @property {Violation[]} startDate - The constraints which violates the input
939
- * @property {Violation[]?} endDate - The constraints which violates the input or `null` for singleDatePicker
940
- * @property {NewDate} newDate - Object with corrected values
941
- */
942
- /**
943
- * Validate `startDate` and `endDate` against `timePickerStepSize`, `minDate`, `maxDate`,
944
- * `minSpan`, `maxSpan`, `invalidDate` and `invalidTime`.
945
- * @param {Array} range - `[startDate, endDate]`<br>Range to be checked, defaults to current `startDate` and `endDate`
946
- * @param {boolean} dipatch=false - If `true` then event "violate" is dispated.<br>
947
- * If eventHandler returns `true`, then `null` is returned, otherwiese the object of violations.
948
- * @emits "violate"
949
- * @returns {InputViolation|null} - Object of violations and corrected values or `null` if no violation have been found
950
- * @example
951
- * options => {
952
- * minDate: DateTime.now().minus({months: 3}).startOf('day'),
953
- * maxDate: DateTime.now().minus({day: 3}).startOf('day'),
954
- * minSpan: Duration.fromObject({days: 7}),
955
- * maxSpan: Duration.fromObject({days: 70}),
956
- * timePickerStepSize: Duration.fromObject({hours: 1})
957
- * }
958
- * const result = validateInput(DateTime.now(), DateTime.now().plus({day: 3}));
959
- *
960
- * result => {
961
- * startDate: [
962
- * { old: "2026-03-13T10:35:52", reason: "timePickerStepSize", new: "2026-03-13T11:00:00" },
963
- * { old: "2026-03-13T11:00:00", reason: "maxDate", new: "2026-03-10T00:00:00" }
964
- * ],
965
- * endDate: {
966
- * { old: "2026-03-16T10:35:52", reason: "stepSize", new: "2026-03-16T11:00:00" },
967
- * { old: "2026-03-16T11:00:00", reason: "maxDate", new: "2026-03-10T00:00:00" },
968
- * { old: "2026-03-10T00:00:00", reason: "minSpan", new: "2026-03-17T00:00:00" }
969
- * ],
970
- * newDate: {
971
- * startDate: "2026-03-10T00:00:00",
972
- * endDate: "2026-03-17T00:00:00"
973
- * }
974
- * }
975
- */
976
- validateInput(range, dipatch = false) {
977
- let startDate = range == null ? this.#startDate : range[0];
978
- let endDate = range == null ? this.#endDate : range[1];
979
- if (startDate == null)
980
- return null;
981
- let result = { startDate: [] };
982
- let violation = { old: startDate, reason: this.timePicker ? "timePickerStepSize" : "timePicker" };
983
- if (this.timePicker) {
984
- const secs = this.timePickerStepSize.as("seconds");
985
- startDate = luxon.DateTime.fromSeconds(secs * Math.round(startDate.toSeconds() / secs));
986
- violation.new = startDate;
987
- if (!violation.new.equals(violation.old))
988
- result.startDate.push(violation);
989
- } else {
990
- startDate = startDate.startOf("day");
991
- }
992
- const shiftStep = this.timePicker ? this.timePickerStepSize.as("seconds") : luxon.Duration.fromObject({ days: 1 }).as("seconds");
993
- if (this.minDate && startDate < this.minDate) {
994
- violation = { old: startDate, reason: "minDate" };
995
- startDate = startDate.plus({ seconds: Math.trunc(this.minDate.diff(startDate).as("seconds") / shiftStep) * shiftStep });
996
- if (startDate < this.minDate)
997
- startDate = startDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
998
- violation.new = startDate;
999
- if (!violation.new.equals(violation.old))
1000
- result.startDate.push(violation);
1001
- } else if (this.maxDate && startDate > this.maxDate) {
1002
- violation = { old: startDate, reason: "maxDate" };
1003
- startDate = startDate.minus({ seconds: Math.trunc(startDate.diff(this.maxDate).as("seconds") / shiftStep) * shiftStep });
1004
- if (startDate > this.maxDate)
1005
- startDate = startDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1006
- violation.new = startDate;
1007
- if (!violation.new.equals(violation.old))
1008
- result.startDate.push(violation);
1009
- }
1010
- let units = ["hour"];
1011
- if (this.timePicker) {
1012
- if (this.timePickerOpts.showMinutes)
1013
- units.push("minute");
1014
- if (this.timePickerOpts.showSeconds)
1015
- units.push("second");
1016
- if (!this.timePicker24Hour)
1017
- units.push("ampm");
1018
- }
1019
- if (this.isInvalidDate(startDate))
1020
- result.startDate.push({ old: startDate, reason: "isInvalidDate" });
1021
- if (this.timePicker) {
1022
- for (let unit of units) {
1023
- if (this.isInvalidTime(startDate, unit, "start"))
1024
- result.startDate.push({ old: startDate, reason: "isInvalidTime", unit });
1025
- }
1026
- }
1027
- if (this.singleDatePicker) {
1028
- if (result.startDate.length == 0)
1029
- return null;
1030
- if (dipatch) {
1031
- let newValues = { startDate };
1032
- const event = this.triggerEvent(this.#events.onViolate, { violation: result, newDate: newValues });
1033
- if (event.defaultPrevented) {
1034
- result.newDate = event.newDate;
1035
- return result;
1036
- }
1037
- return result;
1038
- } else {
1039
- return result;
1040
- }
1041
- }
1042
- if (endDate == null)
1043
- return null;
1044
- result.endDate = [];
1045
- violation = { old: endDate, reason: this.timePicker ? "stepSize" : "timePicker" };
1046
- if (this.timePicker) {
1047
- const secs = this.timePickerStepSize.as("seconds");
1048
- endDate = luxon.DateTime.fromSeconds(secs * Math.round(endDate.toSeconds() / secs));
1049
- violation.new = endDate;
1050
- if (!violation.new.equals(violation.old))
1051
- result.endDate.push(violation);
1052
- } else {
1053
- endDate = endDate.endOf("day");
1054
- }
1055
- if (this.maxDate && endDate > this.maxDate) {
1056
- violation = { old: endDate, reason: "maxDate" };
1057
- endDate = endDate.minus({ seconds: Math.trunc(endDate.diff(this.maxDate).as("seconds") / shiftStep) * shiftStep });
1058
- if (endDate > this.maxDate)
1059
- endDate = endDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1060
- violation.new = endDate;
1061
- if (!violation.new.equals(violation.old))
1062
- result.endDate.push(violation);
1063
- } else if (this.minDate && endDate < this.minDate) {
1064
- violation = { old: endDate, reason: "minDate" };
1065
- endDate = endDate.plus({ seconds: Math.trunc(this.minDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
1066
- if (endDate < this.minDate)
1067
- endDate = endDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1068
- violation.new = endDate;
1069
- if (!violation.new.equals(violation.old))
1070
- result.endDate.push(violation);
1071
- }
1072
- if (this.maxSpan) {
1073
- const maxDate = startDate.plus(this.maxSpan);
1074
- if (endDate > maxDate) {
1075
- violation = { old: endDate, reason: "maxSpan" };
1076
- endDate = endDate.minus({ seconds: Math.trunc(maxDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
1077
- if (endDate > maxDate)
1078
- endDate = endDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1079
- violation.new = endDate;
1080
- if (!violation.new.equals(violation.old))
1081
- result.endDate.push(violation);
1082
- }
1083
- }
1084
- if (this.minSpan) {
1085
- const minDate = startDate.plus(this.defaultSpan ?? this.minSpan);
1086
- if (endDate < minDate) {
1087
- violation = { old: endDate, reason: "minSpan" };
1088
- endDate = endDate.plus({ seconds: Math.trunc(minDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
1089
- if (endDate < minDate)
1090
- endDate = endDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1091
- violation.new = endDate;
1092
- if (!violation.new.equals(violation.old))
1093
- result.endDate.push(violation);
1094
- }
1095
- }
1096
- if (this.isInvalidDate(endDate))
1097
- result.endDate.push({ old: endDate, reason: "isInvalidDate" });
1098
- if (this.timePicker) {
1099
- for (let unit of units) {
1100
- if (this.isInvalidTime(endDate, unit, "end"))
1101
- result.endDate.push({ old: endDate, reason: "isInvalidTime", unit });
1102
- }
1103
- }
1104
- if (result.startDate.length == 0 && result.endDate.length == 0)
1105
- return null;
1106
- if (dipatch) {
1107
- let newValues = { startDate, endDate };
1108
- const event = this.triggerEvent(this.#events.onViolate, { violation: result, newDate: newValues });
1109
- if (event.defaultPrevented) {
1110
- result.newDate = event.newDate;
1111
- return result;
1112
- }
1113
- return result;
1114
- } else {
1115
- return result;
1116
- }
1117
- }
1118
- /* #region Rendering */
1119
- /**
1120
- * Updates the picker when calendar is initiated or any date has been selected.
1121
- * Could be useful after running {@link #DateRangePicker+setStartDate|setStartDate} or {@link #DateRangePicker+setEndDate|setRange}
1122
- * @param {boolean} monthChange - If `true` then monthView changed
1123
- * @emits "beforeRenderTimePicker"
1124
- */
1125
- updateView(monthChange) {
1126
- if (this.timePicker) {
1127
- this.triggerEvent(this.#events.onBeforeRenderTimePicker);
1128
- this.renderTimePicker("start");
1129
- this.renderTimePicker("end");
1130
- this.container.querySelector(".calendar-time.end-time select").disabled = !this.#endDate;
1131
- this.container.querySelector(".calendar-time.end-time select").classList.toggle("disabled", !this.#endDate);
1132
- }
1133
- this.updateLabel();
1134
- this.updateMonthsInView();
1135
- this.updateCalendars(monthChange);
1136
- this.setApplyBtnState();
1137
- }
1138
- /**
1139
- * Shows calendar months based on selected date values
1140
- * @private
1141
- */
1142
- updateMonthsInView() {
1143
- if (this.#endDate) {
1144
- if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month && (this.#startDate.hasSame(this.leftCalendar.month, "month") || this.#startDate.hasSame(this.rightCalendar.month, "month")) && (this.#endDate.hasSame(this.leftCalendar.month, "month") || this.#endDate.hasSame(this.rightCalendar.month, "month")))
1145
- return;
1146
- this.leftCalendar.month = this.#startDate.startOf("month");
1147
- if (!this.singleMonthView) {
1148
- if (!this.linkedCalendars && !this.#endDate.hasSame(this.#startDate, "month")) {
1149
- this.rightCalendar.month = this.#endDate.startOf("month");
1150
- } else {
1151
- this.rightCalendar.month = this.#startDate.startOf("month").plus({ month: 1 });
1152
- }
1153
- }
1154
- } else {
1155
- if (!this.#startDate && this.initalMonth) {
1156
- this.leftCalendar.month = this.initalMonth;
1157
- if (!this.singleMonthView)
1158
- this.rightCalendar.month = this.initalMonth.plus({ month: 1 });
1159
- } else {
1160
- if (!this.leftCalendar.month.hasSame(this.#startDate, "month") && !this.rightCalendar.month.hasSame(this.#startDate, "month")) {
1161
- this.leftCalendar.month = this.#startDate.startOf("month");
1162
- this.rightCalendar.month = this.#startDate.startOf("month").plus({ month: 1 });
1163
- }
1164
- }
1165
- }
1166
- if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && !this.singleMonthView && this.rightCalendar.month > this.maxDate) {
1167
- this.rightCalendar.month = this.maxDate.startOf("month");
1168
- this.leftCalendar.month = this.maxDate.startOf("month").minus({ month: 1 });
1169
- }
1170
- }
1171
- /**
1172
- * Updates the selected day value from calendar with selected time values
1173
- * @emits "beforeRenderCalendar"
1174
- * @emits "monthViewChange"
1175
- * @param {boolean} monthChange - If `true` then monthView changed
1176
- * @private
1177
- */
1178
- updateCalendars(monthChange) {
1179
- if (this.timePicker) {
1180
- var hour, minute, second;
1181
- if (this.#endDate) {
1182
- hour = parseInt(this.container.querySelector(".start-time .hourselect").value, 10);
1183
- if (isNaN(hour))
1184
- hour = parseInt(this.container.querySelector(".start-time .hourselect option:last-child").value, 10);
1185
- minute = 0;
1186
- if (this.timePickerOpts.showMinutes) {
1187
- minute = parseInt(this.container.querySelector(".start-time .minuteselect").value, 10);
1188
- if (isNaN(minute))
1189
- minute = parseInt(this.container.querySelector(".start-time .minuteselect option:last-child").value, 10);
1190
- }
1191
- second = 0;
1192
- if (this.timePickerOpts.showSeconds) {
1193
- second = parseInt(this.container.querySelector(".start-time .secondselect").value, 10);
1194
- if (isNaN(second))
1195
- second = parseInt(this.container.querySelector(".start-time .secondselect option:last-child").value, 10);
1196
- }
1197
- } else {
1198
- hour = parseInt(this.container.querySelector(".end-time .hourselect").value, 10);
1199
- if (isNaN(hour))
1200
- hour = parseInt(this.container.querySelector(".end-time .hourselect option:last-child").value, 10);
1201
- minute = 0;
1202
- if (this.timePickerOpts.showMinutes) {
1203
- minute = parseInt(this.container.querySelector(".end-time .minuteselect").value, 10);
1204
- if (isNaN(minute))
1205
- minute = parseInt(this.container.querySelector(".end-time .minuteselect option:last-child").value, 10);
1206
- }
1207
- second = 0;
1208
- if (this.timePickerOpts.showSeconds) {
1209
- second = parseInt(this.container.querySelector(".end-time .secondselect").value, 10);
1210
- if (isNaN(second))
1211
- second = parseInt(this.container.querySelector(".end-time .secondselect option:last-child").value, 10);
1212
- }
1213
- }
1214
- this.leftCalendar.month = this.leftCalendar.month.set({ hour, minute, second });
1215
- if (!this.singleMonthView)
1216
- this.rightCalendar.month = this.rightCalendar.month.set({ hour, minute, second });
1217
- } else {
1218
- this.leftCalendar.month = this.leftCalendar.month.set({ hour: 0, minute: 0, second: 0 });
1219
- if (!this.singleMonthView)
1220
- this.rightCalendar.month = this.rightCalendar.month.set({ hour: 0, minute: 0, second: 0 });
1221
- }
1222
- this.triggerEvent(this.#events.onBeforeRenderCalendar);
1223
- this.renderCalendar("left");
1224
- this.renderCalendar("right");
1225
- if (monthChange)
1226
- this.triggerEvent(this.#events.onMonthViewChange);
1227
- this.container.querySelectorAll(".ranges li").forEach((el) => {
1228
- el.classList.remove("active");
1229
- });
1230
- if (this.#endDate == null) return;
1231
- this.calculateChosenLabel();
1232
- }
1233
- /**
1234
- * Renders the calendar month
1235
- * @private
1236
- */
1237
- renderCalendar(side) {
1238
- if (side === "right" && this.singleMonthView)
1239
- return;
1240
- var calendar = side === "left" ? this.leftCalendar : this.rightCalendar;
1241
- if (calendar.month == null && !this.#startDate && this.initalMonth)
1242
- calendar.month = this.initalMonth.startOf("month");
1243
- const firstDay = calendar.month.startOf("month");
1244
- const lastDay = calendar.month.endOf("month").startOf("day");
1245
- var theDate = calendar.month.startOf("month").minus({ day: 1 });
1246
- const time = { hour: calendar.month.hour, minute: calendar.month.minute, second: calendar.month.second };
1247
- var calendar = [];
1248
- calendar.firstDay = firstDay;
1249
- calendar.lastDay = lastDay;
1250
- for (var i = 0; i < 6; i++)
1251
- calendar[i] = [];
1252
- while (theDate.weekday != this.locale.firstDay)
1253
- theDate = theDate.minus({ day: 1 });
1254
- for (let col = 0, row = -1; col < 42; col++, theDate = theDate.plus({ day: 1 })) {
1255
- if (col % 7 === 0)
1256
- row++;
1257
- calendar[row][col % 7] = theDate.set(time);
1258
- }
1259
- if (side === "left") {
1260
- this.leftCalendar.calendar = calendar;
1261
- } else {
1262
- this.rightCalendar.calendar = calendar;
1263
- }
1264
- var minDate = side === "left" ? this.minDate : this.#startDate;
1265
- var maxDate = this.maxDate;
1266
- var html = "<tr>";
1267
- if (this.showWeekNumbers || this.showISOWeekNumbers)
1268
- html += "<th></th>";
1269
- if ((!minDate || minDate < calendar.firstDay) && (!this.linkedCalendars || side === "left")) {
1270
- html += '<th class="prev available"><span></span></th>';
1271
- } else {
1272
- html += "<th></th>";
1273
- }
1274
- var dateHtml = `${this.locale.monthNames[calendar.firstDay.month - 1]} ${calendar.firstDay.year}`;
1275
- if (this.showDropdowns) {
1276
- const maxYear = (maxDate && maxDate.year) ?? this.maxYear;
1277
- const minYear = (minDate && minDate.year) ?? this.minYear;
1278
- let div = this.externalStyle === "bulma" ? '<div class="select is-small mr-1">' : "";
1279
- var monthHtml = `${div}<select class="monthselect">`;
1280
- for (var m = 1; m <= 12; m++) {
1281
- monthHtml += `<option value="${m}"${m === calendar.firstDay.month ? " selected" : ""}`;
1282
- if (minDate && calendar.firstDay.set({ month: m }) < minDate.startOf("month") || maxDate && calendar.firstDay.set({ month: m }) > maxDate.endOf("month"))
1283
- monthHtml += ` disabled`;
1284
- monthHtml += `>${this.locale.monthNames[m - 1]}</option>`;
1285
- }
1286
- monthHtml += "</select>";
1287
- if (this.externalStyle === "bulma")
1288
- monthHtml += "</div>";
1289
- div = this.externalStyle === "bulma" ? '<div class="select is-small ml-1">' : "";
1290
- var yearHtml = `${div}<select class="yearselect">`;
1291
- for (var y = minYear; y <= maxYear; y++)
1292
- yearHtml += `<option value="${y}"${y === calendar.firstDay.year ? " selected" : ""}>${y}</option>`;
1293
- yearHtml += "</select>";
1294
- if (this.externalStyle === "bulma")
1295
- yearHtml += "</div>";
1296
- dateHtml = monthHtml + yearHtml;
1297
- }
1298
- html += '<th colspan="5" class="month">' + dateHtml + "</th>";
1299
- if ((!maxDate || maxDate > calendar.lastDay.endOf("day")) && (!this.linkedCalendars || side === "right" || this.singleDatePicker || this.singleMonthView)) {
1300
- html += '<th class="next available"><span></span></th>';
1301
- } else {
1302
- html += "<th></th>";
1303
- }
1304
- html += "</tr>";
1305
- html += "<tr>";
1306
- if (this.showWeekNumbers || this.showISOWeekNumbers)
1307
- html += `<th class="week">${this.locale.weekLabel}</th>`;
1308
- for (let [index, dayOfWeek] of this.locale.daysOfWeek.entries()) {
1309
- html += "<th";
1310
- if (this.weekendDayClasses && this.weekendDayClasses.length && luxon.Info.getWeekendWeekdays().includes(index + 1))
1311
- html += ` class="${this.weekendDayClasses}"`;
1312
- html += `>${dayOfWeek}</th>`;
1313
- }
1314
- html += "</tr>";
1315
- this.container.querySelector(`.drp-calendar.${side} .calendar-table thead`).innerHTML = html;
1316
- html = "";
1317
- if (this.#endDate == null && this.maxSpan) {
1318
- var maxLimit = this.#startDate.plus(this.maxSpan).endOf("day");
1319
- if (!maxDate || maxLimit < maxDate) {
1320
- maxDate = maxLimit;
1321
- }
1322
- }
1323
- var minLimit;
1324
- if (this.#endDate == null && this.minSpan)
1325
- minLimit = this.#startDate.plus(this.minSpan).startOf("day");
1326
- for (let row = 0; row < 6; row++) {
1327
- html += "<tr>";
1328
- if (this.showISOWeekNumbers)
1329
- html += `<td class="week">${calendar[row][0].weekNumber}</td>`;
1330
- else if (this.showWeekNumbers)
1331
- html += `<td class="week">${calendar[row][0].localWeekNumber}</td>`;
1332
- for (let col = 0; col < 7; col++) {
1333
- var classes = [];
1334
- if (this.todayClasses && this.todayClasses.length && calendar[row][col].hasSame(luxon.DateTime.now(), "day"))
1335
- classes.push(this.todayClasses);
1336
- if (this.weekendClasses && this.weekendClasses.length && luxon.Info.getWeekendWeekdays().includes(calendar[row][col].weekday))
1337
- classes.push(this.weekendClasses);
1338
- if (calendar[row][col].month != calendar[1][1].month)
1339
- classes.push("off", "ends");
1340
- if (this.minDate && calendar[row][col].startOf("day") < this.minDate.startOf("day"))
1341
- classes.push("off", "disabled");
1342
- if (maxDate && calendar[row][col].startOf("day") > maxDate.startOf("day"))
1343
- classes.push("off", "disabled");
1344
- if (minLimit && calendar[row][col].startOf("day") > this.#startDate.startOf("day") && calendar[row][col].startOf("day") < minLimit.startOf("day"))
1345
- classes.push("off", "disabled");
1346
- if (this.isInvalidDate(calendar[row][col]))
1347
- classes.push("off", "disabled");
1348
- if (this.#startDate != null && calendar[row][col].hasSame(this.#startDate, "day"))
1349
- classes.push("active", "start-date");
1350
- if (this.#endDate != null && calendar[row][col].hasSame(this.#endDate, "day"))
1351
- classes.push("active", "end-date");
1352
- if (this.#endDate != null && calendar[row][col] > this.#startDate && calendar[row][col] < this.#endDate)
1353
- classes.push("in-range");
1354
- var isCustom = this.isCustomDate(calendar[row][col]);
1355
- if (isCustom !== false)
1356
- typeof isCustom === "string" ? classes.push(isCustom) : classes.push(...isCustom);
1357
- if (!classes.includes("disabled"))
1358
- classes.push("available");
1359
- html += `<td class="${classes.join(" ")}" data-title="r${row}c${col}">${calendar[row][col].day}</td>`;
1360
- }
1361
- html += "</tr>";
1362
- }
1363
- this.container.querySelector(`.drp-calendar.${side} .calendar-table tbody`).innerHTML = html;
1364
- }
1365
- /**
1366
- * Renders the time pickers
1367
- * @private
1368
- * @emits "beforeRenderTimePicker"
1369
- */
1370
- renderTimePicker(side) {
1371
- if (side === "end" && !this.#endDate) return;
1372
- var selected, minLimit, minDate, maxDate = this.maxDate;
1373
- let html = "";
1374
- if (this.showWeekNumbers || this.showISOWeekNumbers)
1375
- html += "<th></th>";
1376
- if (this.maxSpan && (!this.maxDate || this.#startDate.plus(this.maxSpan) < this.maxDate))
1377
- maxDate = this.#startDate.plus(this.maxSpan);
1378
- if (this.minSpan && side === "end")
1379
- minLimit = this.#startDate.plus(this.defaultSpan ?? this.minSpan);
1380
- if (side === "start") {
1381
- selected = this.#startDate;
1382
- minDate = this.minDate;
1383
- } else if (side === "end") {
1384
- selected = this.#endDate;
1385
- minDate = this.#startDate;
1386
- let timeSelector = this.container.querySelector(".drp-calendar .calendar-time.end-time");
1387
- if (timeSelector.innerHTML != "") {
1388
- selected = selected.set({
1389
- hour: !isNaN(selected.hour) ? selected.hour : timeSelector.querySelector(".hourselect option:selected").value,
1390
- minute: !isNaN(selected.minute) ? selected.minute : timeSelector.querySelector(".minuteselect option:selected").value,
1391
- second: !isNaN(selected.second) ? selected.second : timeSelector.querySelector(".secondselect option:selected").value
1392
- });
1393
- }
1394
- if (selected < this.#startDate)
1395
- selected = this.#startDate;
1396
- if (maxDate && selected > maxDate)
1397
- selected = maxDate;
1398
- }
1399
- html += `<th colspan="7">`;
1400
- if (this.externalStyle === "bulma")
1401
- html += '<div class="select is-small mx-1">';
1402
- html += '<select class="hourselect">';
1403
- const ampm = selected.toFormat("a", { locale: "en-US" });
1404
- let start = 0;
1405
- if (!this.timePicker24Hour)
1406
- start = ampm === "AM" ? 1 : 13;
1407
- for (var i = start; i <= start + 23; i += this.timePickerOpts.hourStep) {
1408
- let time = selected.set({ hour: i % 24 });
1409
- let disabled = false;
1410
- if (minDate && time.set({ minute: 59 }) < minDate)
1411
- disabled = true;
1412
- if (maxDate && time.set({ minute: 0 }) > maxDate)
1413
- disabled = true;
1414
- if (minLimit && time.endOf("hour") < minLimit)
1415
- disabled = true;
1416
- if (!disabled && this.isInvalidTime(time, this.singleDatePicker ? null : side, "hour"))
1417
- disabled = true;
1418
- if (this.timePicker24Hour) {
1419
- if (!disabled && i == selected.hour) {
1420
- html += `<option value="${i}" selected>${i}</option>`;
1421
- } else if (disabled) {
1422
- html += `<option value="${i}" disabled class="disabled">${i}</option>`;
1423
- } else {
1424
- html += `<option value="${i}">${i}</option>`;
1425
- }
1426
- } else {
1427
- const i_12 = luxon.DateTime.fromFormat(`${i % 24}`, "H").toFormat("h");
1428
- const i_ampm = luxon.DateTime.fromFormat(`${i % 24}`, "H").toFormat("a", { locale: "en-US" });
1429
- if (ampm == i_ampm) {
1430
- if (!disabled && i == selected.hour) {
1431
- html += `<option ampm="${i_ampm}" value="${i % 24}" selected>${i_12}</option>`;
1432
- } else if (disabled) {
1433
- html += `<option ampm="${i_ampm}" value="${i % 24}" disabled class="disabled">${i_12}</option>`;
1434
- } else {
1435
- html += `<option ampm="${i_ampm}" value="${i % 24}">${i_12}</option>`;
1436
- }
1437
- } else {
1438
- html += `<option ampm="${i_ampm}" hidden="hidden" value="${i % 24}">${i_12}</option>`;
1439
- }
1440
- }
1441
- }
1442
- html += "</select>";
1443
- if (this.externalStyle === "bulma")
1444
- html += "</div>";
1445
- if (this.timePickerOpts.showMinutes) {
1446
- html += " : ";
1447
- if (this.externalStyle === "bulma")
1448
- html += '<div class="select is-small mx-1">';
1449
- html += '<select class="minuteselect">';
1450
- for (var i = 0; i < 60; i += this.timePickerOpts.minuteStep) {
1451
- var padded = i < 10 ? "0" + i : i;
1452
- let time = selected.set({ minute: i });
1453
- let disabled = false;
1454
- if (minDate && time.set({ second: 59 }) < minDate)
1455
- disabled = true;
1456
- if (maxDate && time.set({ second: 0 }) > maxDate)
1457
- disabled = true;
1458
- if (minLimit && time.endOf("minute") < minLimit)
1459
- disabled = true;
1460
- if (!disabled && this.isInvalidTime(time, this.singleDatePicker ? null : side, "minute"))
1461
- disabled = true;
1462
- if (selected.minute == i && !disabled) {
1463
- html += `<option value="${i}" selected>${padded}</option>`;
1464
- } else if (disabled) {
1465
- html += `<option value="${i}" disabled class="disabled">${padded}</option>`;
1466
- } else {
1467
- html += `<option value="${i}">${padded}</option>`;
1468
- }
1469
- }
1470
- html += "</select>";
1471
- if (this.externalStyle === "bulma")
1472
- html += "</div>";
1473
- }
1474
- if (this.timePickerOpts.showSeconds) {
1475
- html += " : ";
1476
- if (this.externalStyle === "bulma")
1477
- html += '<div class="select is-small mx-1">';
1478
- html += '<select class="secondselect">';
1479
- for (var i = 0; i < 60; i += this.timePickerOpts.secondStep) {
1480
- var padded = i < 10 ? "0" + i : i;
1481
- let time = selected.set({ second: i });
1482
- let disabled = false;
1483
- if (minDate && time < minDate)
1484
- disabled = true;
1485
- if (maxDate && time > maxDate)
1486
- disabled = true;
1487
- if (minLimit && time < minLimit)
1488
- disabled = true;
1489
- if (!disabled && this.isInvalidTime(time, this.singleDatePicker ? null : side, "second"))
1490
- disabled = true;
1491
- if (selected.second == i && !disabled) {
1492
- html += `<option value="${i}" selected>${padded}</option>`;
1493
- } else if (disabled) {
1494
- html += `<option value="${i}" disabled class="disabled">${padded}</option>`;
1495
- } else {
1496
- html += `<option value="${i}">${padded}</option>`;
1497
- }
1498
- }
1499
- html += "</select>";
1500
- if (this.externalStyle === "bulma")
1501
- html += "</div>";
1502
- }
1503
- if (!this.timePicker24Hour) {
1504
- if (this.externalStyle === "bulma")
1505
- html += '<div class="select is-small mx-1">';
1506
- html += '<select class="ampmselect">';
1507
- var am_html = "";
1508
- var pm_html = "";
1509
- let disabled = false;
1510
- if (minDate && selected.startOf("day") < minDate)
1511
- disabled = true;
1512
- if (maxDate && selected.endOf("day") > maxDate)
1513
- disabled = true;
1514
- if (minLimit && selected.startOf("day") < minLimit)
1515
- disabled = true;
1516
- if (disabled) {
1517
- am_html = ' disabled class="disabled "';
1518
- pm_html = ' disabled class="disabled"';
1519
- } else {
1520
- if (this.isInvalidTime(selected, this.singleDatePicker ? null : side, "ampm")) {
1521
- if (selected.toFormat("a", { locale: "en-US" }) === "AM") {
1522
- pm_html = ' disabled class="disabled"';
1523
- } else {
1524
- am_html = ' disabled class="disabled"';
1525
- }
1526
- }
1527
- }
1528
- html += `<option value="AM"${am_html}`;
1529
- if (selected.toFormat("a", { locale: "en-US" }) === "AM")
1530
- html += " selected";
1531
- html += `>${luxon.Info.meridiems()[0]}</option><option value="PM"${pm_html}`;
1532
- if (selected.toFormat("a", { locale: "en-US" }) === "PM")
1533
- html += " selected";
1534
- html += `>${luxon.Info.meridiems()[1]}</option>`;
1535
- html += "</select>";
1536
- if (this.externalStyle === "bulma")
1537
- html += "</div>";
1538
- }
1539
- html += "</div></th>";
1540
- this.container.querySelector(`.drp-calendar .calendar-time.${side}-time`).innerHTML = html;
1541
- }
1542
- /**
1543
- * Disable the `Apply` button if no date value is selected
1544
- * @private
1545
- */
1546
- setApplyBtnState() {
1547
- const state = this.singleDatePicker || this.#endDate && this.#startDate <= this.#endDate;
1548
- this.container.querySelector("button.applyBtn").disabled = !state;
1549
- }
1550
- /* #endregion */
1551
- /* #region Move/Show/Hide */
1552
- /**
1553
- * Place the picker at the right place in the document
1554
- */
1555
- move() {
1556
- let parentOffset = { top: 0, left: 0 };
1557
- let containerTop;
1558
- let containerLeft;
1559
- let drops = this.drops;
1560
- let parentRightEdge = window.innerWidth;
1561
- if (!this.parentEl.matches("body")) {
1562
- parentOffset = {
1563
- top: offset(this.parentEl).top - this.parentEl.scrollTop(),
1564
- left: offset(this.parentEl).left - this.parentEl.scrollLeft()
1565
- };
1566
- parentRightEdge = this.parentEl[0].clientWidth + offset(this.parentEl).left;
1567
- }
1568
- switch (this.drops) {
1569
- case "auto":
1570
- containerTop = offset(this.element).top + outerHeight(this.element) - parentOffset.top;
1571
- if (containerTop + outerHeight(this.container) >= this.parentEl.scrollHeight) {
1572
- containerTop = offset(this.element).top - outerHeight(this.container) - parentOffset.top;
1573
- drops = "up";
1574
- }
1575
- break;
1576
- case "up":
1577
- containerTop = offset(this.element).top - outerHeight(this.container) - parentOffset.top;
1578
- break;
1579
- case "down":
1580
- containerTop = offset(this.element).top + outerHeight(this.element) - parentOffset.top;
1581
- break;
1582
- default:
1583
- console.error(`Option drops '${drops}' not defined`);
1584
- break;
1585
- }
1586
- for (const [key2, value] of Object.entries({ top: 0, left: 0, right: "auto" }))
1587
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1588
- const containerWidth = outerWidth(this.container);
1589
- this.container.classList.toggle("drop-up", drops === "up");
1590
- switch (this.opens) {
1591
- case "left":
1592
- const containerRight = parentRightEdge - offset(this.element).left - outerWidth(this.element);
1593
- if (containerWidth + containerRight > window.innerWidth) {
1594
- for (const [key2, value] of Object.entries({ top: containerTop, right: "auto", left: 9 }))
1595
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1596
- } else {
1597
- for (const [key2, value] of Object.entries({ top: containerTop, right: containerRight, left: "auto" }))
1598
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1599
- }
1600
- break;
1601
- case "center":
1602
- containerLeft = offset(this.element).left - parentOffset.left + outerWidth(this.element) / 2 - containerWidth / 2;
1603
- if (containerLeft < 0) {
1604
- for (const [key2, value] of Object.entries({ top: containerTop, right: "auto", left: 9 }))
1605
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1606
- } else if (containerLeft + containerWidth > window.innerWidth) {
1607
- for (const [key2, value] of Object.entries({ top: containerTop, left: "auto", right: 0 }))
1608
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1609
- } else {
1610
- for (const [key2, value] of Object.entries({ top: containerTop, left: containerLeft, right: "auto" }))
1611
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1612
- }
1613
- break;
1614
- case "right":
1615
- containerLeft = offset(this.element).left - parentOffset.left;
1616
- if (containerLeft + containerWidth > window.innerWidth) {
1617
- for (const [key2, value] of Object.entries({ top: containerTop, left: "auto", right: 0 }))
1618
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1619
- } else {
1620
- for (const [key2, value] of Object.entries({ top: `${containerTop}px`, left: containerLeft, right: "auto" }))
1621
- this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1622
- }
1623
- break;
1624
- default:
1625
- console.error(`Option opens '${this.opens}' not defined`);
1626
- break;
1627
- }
1628
- }
1629
- /**
1630
- * Shows the picker
1631
- * @emits "show"
1632
- */
1633
- show() {
1634
- if (this.isShowing) return;
1635
- document.addEventListener("mousedown", this.#outsideClickProxy);
1636
- document.addEventListener("touchend", this.#outsideClickProxy);
1637
- document.addEventListener("click", this.#dropdownClickWrapper);
1638
- document.addEventListener("focusin", this.#outsideClickProxy);
1639
- window.addEventListener("resize", this.#onResizeProxy);
1640
- this.oldStartDate = this.#startDate;
1641
- this.oldEndDate = this.#endDate;
1642
- this.updateView(false);
1643
- this.container.style.display = "block";
1644
- this.move();
1645
- this.triggerEvent(this.#events.onShow);
1646
- this.isShowing = true;
1647
- }
1648
- /**
1649
- * Hides the picker
1650
- * @emits "beforeHide"
1651
- * @emits "hide"
1652
- */
1653
- hide() {
1654
- if (!this.isShowing) return;
1655
- if (!this.#endDate) {
1656
- this.#startDate = this.oldStartDate;
1657
- this.#endDate = this.oldEndDate;
1658
- }
1659
- if (!this.#startDate.equals(this.oldStartDate) || !this.#endDate.equals(this.oldEndDate))
1660
- this.callback(this.startDate, this.endDate, this.chosenLabel);
1661
- this.updateElement();
1662
- const event = this.triggerEvent(this.#events.onBeforeHide);
1663
- if (event.defaultPrevented)
1664
- return;
1665
- document.removeEventListener("mousedown", this.#outsideClickProxy);
1666
- document.removeEventListener("touchend", this.#outsideClickProxy);
1667
- document.removeEventListener("focusin", this.#outsideClickProxy);
1668
- document.removeEventListener("click", this.#dropdownClickWrapper);
1669
- window.removeEventListener("resize", this.#onResizeProxy);
1670
- this.container.style.display = "none";
1671
- this.triggerEvent(this.#events.onHide);
1672
- this.isShowing = false;
1673
- }
1674
- /**
1675
- * Toggles visibility of the picker
1676
- */
1677
- toggle() {
1678
- if (this.isShowing) {
1679
- this.hide();
1680
- } else {
1681
- this.show();
1682
- }
1683
- }
1684
- /**
1685
- * Shows calendar when user selects "Custom Ranges"
1686
- * @emits "showCalendar"
1687
- */
1688
- showCalendars() {
1689
- this.container.classList.add("show-calendar");
1690
- this.move();
1691
- this.triggerEvent(this.#events.onShowCalendar);
1692
- }
1693
- /**
1694
- * Hides calendar when user selects a predefined range
1695
- * @emits "hideCalendar"
1696
- */
1697
- hideCalendars() {
1698
- this.container.classList.remove("show-calendar");
1699
- this.triggerEvent(this.#events.onHideCalendar);
1700
- }
1701
- /* #endregion */
1702
- /* #region Handle mouse related events */
1703
- /**
1704
- * Closes the picker when user clicks outside
1705
- * @param {external:Event} e - The Event target
1706
- * @emits "outsideClick"
1707
- * @private
1708
- */
1709
- outsideClick(e) {
1710
- const target = e.target;
1711
- function closest2(el, selector) {
1712
- let parent = el.parentElement;
1713
- while (parent) {
1714
- if (parent == selector)
1715
- return parent;
1716
- parent = parent.parentElement;
1717
- }
1718
- return null;
1719
- }
1720
- if (
1721
- // ie modal dialog fix
1722
- e.type === "focusin" || closest2(target, this.element) || closest2(target, this.container) || target.closest(".calendar-table")
1723
- ) return;
1724
- const event = this.triggerEvent(this.#events.onOutsideClick);
1725
- if (event.defaultPrevented)
1726
- return;
1727
- if (this.onOutsideClick === "cancel") {
1728
- this.#startDate = this.oldStartDate;
1729
- this.#endDate = this.oldEndDate;
1730
- }
1731
- this.hide();
1732
- }
1733
- /**
1734
- * Move calendar to previous month
1735
- * @param {external:Event} e - The Event target
1736
- * @private
1737
- */
1738
- clickPrev(e) {
1739
- let cal = e.target.closest(".drp-calendar");
1740
- if (cal.classList.contains("left")) {
1741
- this.leftCalendar.month = this.leftCalendar.month.minus({ month: 1 });
1742
- if (this.linkedCalendars && !this.singleMonthView)
1743
- this.rightCalendar.month = this.rightCalendar.month.minus({ month: 1 });
1744
- } else {
1745
- this.rightCalendar.month = this.rightCalendar.month.minus({ month: 1 });
1746
- }
1747
- this.updateCalendars(true);
1748
- }
1749
- /**
1750
- * Move calendar to next month
1751
- * @param {external:Event} e - The Event target
1752
- * @private
1753
- */
1754
- clickNext(e) {
1755
- let cal = e.target.closest(".drp-calendar");
1756
- if (cal.classList.contains("left")) {
1757
- this.leftCalendar.month = this.leftCalendar.month.plus({ month: 1 });
1758
- } else {
1759
- this.rightCalendar.month = this.rightCalendar.month.plus({ month: 1 });
1760
- if (this.linkedCalendars)
1761
- this.leftCalendar.month = this.leftCalendar.month.plus({ month: 1 });
1762
- }
1763
- this.updateCalendars(true);
1764
- }
1765
- /**
1766
- * User hovers over date values
1767
- * @param {external:Event} e - The Event target
1768
- * @private
1769
- */
1770
- hoverDate(e) {
1771
- if (!e.target.classList.contains("available")) return;
1772
- let title = e.target.dataset.title;
1773
- const row = title.substring(1, 2);
1774
- const col = title.substring(3, 4);
1775
- const cal = e.target(closest, ".drp-calendar");
1776
- var date = cal.classList.contains("left") ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1777
- const leftCalendar = this.leftCalendar;
1778
- const rightCalendar = this.rightCalendar;
1779
- const startDate = this.#startDate;
1780
- const initalMonth = this.initalMonth;
1781
- if (!this.#endDate) {
1782
- this.container.querySelectorAll(".drp-calendar tbody td").forEach((el) => {
1783
- if (el.classList.contains("week")) return;
1784
- const title2 = el.dataset.title;
1785
- const row2 = title2.substring(1, 2);
1786
- const col2 = title2.substring(3, 4);
1787
- const cal2 = el.closest(".drp-calendar");
1788
- const dt = cal2.classList.contains("left") ? leftCalendar.calendar[row2][col2] : rightCalendar.calendar[row2][col2];
1789
- if (!startDate && initalMonth) {
1790
- el.classList.remove("in-range");
1791
- } else {
1792
- el.classList.toggle("in-range", dt > startDate && dt < date || dt.hasSame(date, "day"));
1793
- }
1794
- });
1795
- }
1796
- }
1797
- /**
1798
- * User hovers over ranges
1799
- * @param {external:Event} e - The Event target
1800
- * @private
1801
- */
1802
- hoverRange(e) {
1803
- const label = e.target.dataset.rangeKey;
1804
- const previousDates = [this.#startDate, this.#endDate];
1805
- const dates = this.ranges[label] ?? [this.#startDate, this.#endDate];
1806
- const leftCalendar = this.leftCalendar;
1807
- const rightCalendar = this.rightCalendar;
1808
- this.container.querySelectorAll(".drp-calendar tbody td").forEach((el) => {
1809
- if (el.classList.contains("week")) return;
1810
- const title = el.dataset.ttitle;
1811
- const row = title.substring(1, 2);
1812
- const col = title.substring(3, 4);
1813
- const cal = el.closest(".drp-calendar");
1814
- const dt = cal.classList.contains("left") ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col];
1815
- el.classList.toggle("start-hover", dt.hasSame(dates[0], "day"));
1816
- el.classList.toggle("start-date", dt.hasSame(previousDates[0], "day"));
1817
- el.classList.toggle("end-hover", dt.hasSame(dates[1], "day"));
1818
- el.classList.toggle("end-date", previousDates[1] != null && dt.hasSame(previousDates[1], "day"));
1819
- el.classList.toggle("range-hover", dt.startOf("day") >= dates[0].startOf("day") && dt.startOf("day") <= dates[1].startOf("day"));
1820
- el.classList.toggle("in-range", dt.startOf("day") >= previousDates[0].startOf("day") && previousDates[1] != null && dt.startOf("day") <= previousDates[1].startOf("day"));
1821
- });
1822
- }
1823
- /**
1824
- * User leave ranges, remove hightlight from dates
1825
- * @private
1826
- */
1827
- leaveRange() {
1828
- this.container.querySelectorAll(".drp-calendar tbody td").forEach((el) => {
1829
- if (el.classList.contains("week")) return;
1830
- el.classList.remove("start-hover");
1831
- el.classList.remove("end-hover");
1832
- el.classList.remove("range-hover");
1833
- });
1834
- }
1835
- /* #endregion */
1836
- /* #region Select values by Mouse */
1837
- /**
1838
- * Set date values after user selected a date
1839
- * @param {external:Event} e - The Event target
1840
- * @private
1841
- */
1842
- clickRange(e) {
1843
- let label = e.target.getAttribute("data-range-key");
1844
- this.chosenLabel = label;
1845
- if (label == this.locale.customRangeLabel) {
1846
- this.showCalendars();
1847
- } else {
1848
- let newDate = this.ranges[label];
1849
- const monthChange = !this.#startDate.hasSame(newDate[0], "month") || !this.#endDate.hasSame(newDate[1], "month");
1850
- this.#startDate = newDate[0];
1851
- this.#endDate = newDate[1];
1852
- if (!this.timePicker) {
1853
- this.#startDate.startOf("day");
1854
- this.#endDate.endOf("day");
1855
- }
1856
- if (!this.alwaysShowCalendars)
1857
- this.hideCalendars();
1858
- const event = this.triggerEvent(this.#events.onBeforeHide);
1859
- if (event.defaultPrevented)
1860
- this.updateView(monthChange);
1861
- this.clickApply();
1862
- }
1863
- }
1864
- /**
1865
- * User clicked a date
1866
- * @param {external:Event} e - The Event target
1867
- * @emits "dateChange"
1868
- * @private
1869
- */
1870
- clickDate(e) {
1871
- if (!e.target.classList.contains("available")) return;
1872
- let title = e.target.dataset.title;
1873
- let row = title.substring(1, 2);
1874
- let col = title.substring(3, 4);
1875
- let cal = e.target.closest(".drp-calendar");
1876
- let date = cal.classList.contains("left") ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1877
- let side;
1878
- if (this.#endDate || !this.#startDate || date < this.#startDate.startOf("day")) {
1879
- if (this.timePicker) {
1880
- let hour = parseInt(this.container.querySelector(".start-time .hourselect").value, 10);
1881
- if (isNaN(hour))
1882
- hour = parseInt(this.container.querySelector(".start-time .hourselect option:last-child").value, 10);
1883
- let minute = 0;
1884
- if (this.timePickerOpts.showMinutes) {
1885
- minute = parseInt(this.container.querySelector(".start-time .minuteselect").value, 10);
1886
- if (isNaN(minute))
1887
- minute = parseInt(this.container.querySelector(".start-time .minuteselect option:last-child").value, 10);
1888
- }
1889
- let second = 0;
1890
- if (this.timePickerOpts.showSeconds) {
1891
- second = parseInt(this.container.querySelector(".start-time .secondselect").value, 10);
1892
- if (isNaN(second))
1893
- second = parseInt(this.container.querySelector(".start-time .secondselect option:last-child").value, 10);
1894
- }
1895
- date = date.set({ hour, minute, second });
1896
- } else {
1897
- date = date.startOf("day");
1898
- }
1899
- this.#endDate = null;
1900
- this.#startDate = date;
1901
- side = "start";
1902
- } else if (!this.#endDate && date < this.#startDate) {
1903
- this.#endDate = this.#startDate;
1904
- side = "end";
1905
- } else {
1906
- if (this.timePicker) {
1907
- let hour = parseInt(this.container.querySelector(".end-time .hourselect").value, 10);
1908
- if (isNaN(hour))
1909
- hour = parseInt(this.container.querySelector(".end-time .hourselect option:last-child").value, 10);
1910
- let minute = 0;
1911
- if (this.timePickerOpts.showMinutes) {
1912
- minute = parseInt(this.container.querySelector(".end-time .minuteselect").value, 10);
1913
- if (isNaN(minute))
1914
- minute = parseInt(this.container.querySelector(".end-time .minuteselect option:last-child").value, 10);
1915
- }
1916
- let second = 0;
1917
- if (this.timePickerOpts.showSeconds) {
1918
- second = parseInt(this.container.querySelector(".end-time .secondselect").value, 10);
1919
- if (isNaN(second))
1920
- second = parseInt(this.container.querySelector(".end-time .secondselect option:last-child").value, 10);
1921
- }
1922
- date = date.set({ hour, minute, second });
1923
- } else {
1924
- date = date.endOf("day");
1925
- }
1926
- this.#endDate = date;
1927
- if (this.autoApply) {
1928
- this.calculateChosenLabel();
1929
- this.clickApply();
1930
- }
1931
- side = "end";
1932
- }
1933
- if (this.singleDatePicker) {
1934
- this.#endDate = this.#startDate;
1935
- if (!this.timePicker && this.autoApply)
1936
- this.clickApply();
1937
- side = null;
1938
- }
1939
- this.updateView(false);
1940
- e.stopPropagation();
1941
- if (this.autoUpdateInput)
1942
- this.updateElement();
1943
- this.triggerEvent(this.#events.onDateChange, { side });
1944
- }
1945
- /**
1946
- * Hightlight selected predefined range in calendar
1947
- * @private
1948
- */
1949
- calculateChosenLabel() {
1950
- if (Object.keys(this.ranges).length === 0)
1951
- return;
1952
- let customRange = true;
1953
- let unit = this.timePicker ? "hour" : "day";
1954
- if (this.timePicker) {
1955
- if (this.timePickerOpts.showMinutes) {
1956
- unit = "minute";
1957
- } else if (this.timePickerOpts.showSeconds) {
1958
- unit = "second";
1959
- }
1960
- }
1961
- for (const [key2, [start, end]] of Object.entries(this.ranges)) {
1962
- if (this.#startDate.startOf(unit).equals(start.startOf(unit)) && this.#endDate.startOf(unit).equals(end.startOf(unit))) {
1963
- customRange = false;
1964
- const range = this.container.querySelector(`.ranges li[data-range-key="${key2}"]`);
1965
- this.chosenLabel = key2;
1966
- range.classList.add("active");
1967
- break;
1968
- }
1969
- }
1970
- if (customRange) {
1971
- if (this.showCustomRangeLabel) {
1972
- const range = this.container.querySelector(".ranges li:last-child");
1973
- this.chosenLabel = range.dataset.rangeKey;
1974
- range.classList.add("active");
1975
- } else {
1976
- this.chosenLabel = null;
1977
- }
1978
- this.showCalendars();
1979
- }
1980
- }
1981
- /**
1982
- * User clicked a time
1983
- * @param {external:Event} e - The Event target
1984
- * @emits "timeChange"
1985
- * @private
1986
- */
1987
- timeChanged(e) {
1988
- const time = e.target.closest(".calendar-time");
1989
- const side = time.classList.contains("start-time") ? "start" : "end";
1990
- var hour = parseInt(time.querySelector(".hourselect").value, 10);
1991
- if (isNaN(hour))
1992
- hour = parseInt(time.querySelector(".hourselect option:last-child").value, 10);
1993
- if (!this.timePicker24Hour) {
1994
- const ampm = time.querySelector(".ampmselect").value;
1995
- if (ampm == null)
1996
- time.querySelector(".ampmselect option:last-child").value;
1997
- if (ampm != luxon.DateTime.fromFormat(`${hour}`, "H").toFormat("a", { locale: "en-US" })) {
1998
- time.querySelectorAll(".hourselect > option").forEach((el) => {
1999
- el.hidden = !el.hidden;
2000
- });
2001
- const h = luxon.DateTime.fromFormat(`${hour}`, "H").toFormat("h");
2002
- hour = luxon.DateTime.fromFormat(`${h}${ampm}`, "ha", { locale: "en-US" }).hour;
2003
- }
2004
- }
2005
- var minute = 0;
2006
- if (this.timePickerOpts.showMinutes) {
2007
- minute = parseInt(time.querySelector(".minuteselect").value, 10);
2008
- if (isNaN(minute))
2009
- minute = parseInt(time.querySelector(".minuteselect option:last-child").value, 10);
2010
- }
2011
- var second = 0;
2012
- if (this.timePickerOpts.showSeconds) {
2013
- second = parseInt(time.querySelector(".secondselect").value, 10);
2014
- if (isNaN(second))
2015
- second = parseInt(time.querySelector(".secondselect option:last-child").value, 10);
2016
- }
2017
- if (side === "start") {
2018
- if (this.#startDate)
2019
- this.#startDate = this.#startDate.set({ hour, minute, second });
2020
- if (this.singleDatePicker) {
2021
- this.#endDate = this.#startDate;
2022
- } else if (this.#endDate && this.#endDate.hasSame(this.#startDate, "day") && this.#endDate < this.#startDate) {
2023
- this.#endDate = this.#startDate;
2024
- }
2025
- } else if (this.#endDate) {
2026
- this.#endDate = this.#endDate.set({ hour, minute, second });
2027
- }
2028
- this.updateCalendars(false);
2029
- this.setApplyBtnState();
2030
- this.triggerEvent(this.#events.onBeforeRenderTimePicker);
2031
- this.renderTimePicker("start");
2032
- this.renderTimePicker("end");
2033
- if (this.autoUpdateInput)
2034
- this.updateElement();
2035
- this.triggerEvent(this.#events.onTimeChange, { side: this.singleDatePicker ? null : side });
2036
- }
2037
- /**
2038
- * Calender month moved
2039
- * @param {external:Event} e - The Event target
2040
- * @private
2041
- */
2042
- monthOrYearChanged(e) {
2043
- const isLeft = e.target.closest(".drp-calendar").classList.contains("left");
2044
- const leftOrRight = isLeft ? "left" : "right";
2045
- const cal = this.container.querySelector(`.drp-calendar.${leftOrRight}`);
2046
- let month = parseInt(cal.querySelector(".monthselect").value, 10);
2047
- let year = cal.querySelector(".yearselect").value;
2048
- let monthChange = false;
2049
- if (!isLeft) {
2050
- if (year < this.#startDate.year || year == this.#startDate.year && month < this.#startDate.month) {
2051
- month = this.#startDate.month;
2052
- year = this.#startDate.year;
2053
- }
2054
- }
2055
- if (this.minDate) {
2056
- if (year < this.minDate.year || year == this.minDate.year && month < this.minDate.month) {
2057
- month = this.minDate.month;
2058
- year = this.minDate.year;
2059
- }
2060
- }
2061
- if (this.maxDate) {
2062
- if (year > this.maxDate.year || year == this.maxDate.year && month > this.maxDate.month) {
2063
- month = this.maxDate.month;
2064
- year = this.maxDate.year;
2065
- }
2066
- }
2067
- if (isLeft) {
2068
- monthChange = !luxon.DateTime.fromObject({ year, month }).hasSame(this.leftCalendar.month, "month");
2069
- this.leftCalendar.month = this.leftCalendar.month.set({ year, month });
2070
- if (this.linkedCalendars)
2071
- this.rightCalendar.month = this.leftCalendar.month.plus({ month: 1 });
2072
- } else {
2073
- monthChange = !luxon.DateTime.fromObject({ year, month }).hasSame(this.leftCalendar.month, "month");
2074
- this.rightCalendar.month = this.rightCalendar.month.set({ year, month });
2075
- if (this.linkedCalendars)
2076
- this.leftCalendar.month = this.rightCalendar.month.minus({ month: 1 });
2077
- }
2078
- this.updateCalendars(monthChange);
2079
- }
2080
- /**
2081
- * User clicked `Apply` button
2082
- * @emits "apply"
2083
- * @private
2084
- */
2085
- clickApply() {
2086
- this.hide();
2087
- this.triggerEvent(this.#events.onApply);
2088
- }
2089
- /**
2090
- * User clicked `Cancel` button
2091
- * @emits "cancel"
2092
- * @private
2093
- */
2094
- clickCancel() {
2095
- this.#startDate = this.oldStartDate;
2096
- this.#endDate = this.oldEndDate;
2097
- this.hide();
2098
- this.triggerEvent(this.#events.onCancel);
2099
- }
2100
- /* #endregion */
2101
- /**
2102
- * Update the picker with value from `<input>` element.<br>
2103
- * Input values must be given in format of `locale.format`. Invalid values are handles by `violate` Event
2104
- * @emits "inputChange"
2105
- * @private
2106
- */
2107
- elementChanged() {
2108
- if (!this.isInputText) return;
2109
- if (!this.element.value.length) return;
2110
- const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
2111
- const dateString = this.element.value.split(this.locale.separator);
2112
- let monthChange = false;
2113
- if (this.singleDatePicker) {
2114
- let newDate = luxon.DateTime.fromFormat(this.element.value, format, { locale: luxon.DateTime.now().locale });
2115
- const oldDate = this.#startDate;
2116
- if (!newDate.isValid || oldDate.equals(newDate))
2117
- return;
2118
- const violations = this.validateInput([newDate, null], true);
2119
- if (violations != null) {
2120
- if (violations.newDate != null) {
2121
- newDate = violations.newDate.startDate;
2122
- } else {
2123
- return;
2124
- }
2125
- }
2126
- monthChange = !this.#startDate.hasSame(newDate, "month");
2127
- this.#startDate = newDate;
2128
- this.#endDate = this.#startDate;
2129
- if (!this.timePicker) {
2130
- this.#startDate = this.#startDate.startOf("day");
2131
- this.#endDate = this.#endDate.endOf("day");
2132
- }
2133
- } else if (!this.singleDatePicker && dateString.length === 2) {
2134
- const newDate = [0, 1].map((i) => luxon.DateTime.fromFormat(dateString[i], format, { locale: luxon.DateTime.now().locale }));
2135
- const oldDate = [this.#startDate, this.#endDate];
2136
- if (!newDate[0].isValid || !newDate[1].isValid || (oldDate[0].equals(newDate[0]) && oldDate[1].equals(newDate[1]) || newDate[0] > newDate[1]))
2137
- return;
2138
- const violations = this.validateInput([newDate[0], newDate[1]], true);
2139
- if (violations != null) {
2140
- if (violations.newDate != null) {
2141
- newDate[0] = violations.newDate.startDate;
2142
- newDate[1] = violations.newDate.endDate;
2143
- } else {
2144
- return;
2145
- }
2146
- }
2147
- monthChange = !this.#startDate.hasSame(newDate[0], "month") || !this.#endDate.hasSame(newDate[1], "month");
2148
- this.#startDate = newDate[0];
2149
- this.#endDate = newDate[1];
2150
- if (!this.timePicker) {
2151
- this.#startDate = this.#startDate.startOf("day");
2152
- this.#endDate = this.#endDate.endOf("day");
2153
- }
2154
- } else {
2155
- return;
2156
- }
2157
- this.updateView(monthChange);
2158
- this.updateElement();
2159
- this.triggerEvent(this.#events.onInputChange);
2160
- }
2161
- /**
2162
- * Handles key press, IE 11 compatibility
2163
- * @param {external:Event} e - The Event target
2164
- * @private
2165
- */
2166
- keydown(e) {
2167
- if ([9, 11].includes(e.keyCode))
2168
- this.hide();
2169
- if (e.keyCode === 27) {
2170
- e.preventDefault();
2171
- e.stopPropagation();
2172
- this.hide();
2173
- }
2174
- }
2175
- /**
2176
- * Update attached `<input>` element with selected value
2177
- * @emits external:change
2178
- */
2179
- updateElement() {
2180
- if (this.#startDate == null && this.initalMonth)
2181
- return;
2182
- if (this.isInputText) {
2183
- let newValue = this.formatDate(this.#startDate);
2184
- if (!this.singleDatePicker) {
2185
- newValue += this.locale.separator;
2186
- if (this.#endDate)
2187
- newValue += this.formatDate(this.#endDate);
2188
- }
2189
- this.updateAltInput();
2190
- if (newValue !== this.element.value) {
2191
- this.element.value = newValue;
2192
- this.element.dispatchEvent(new Event("change", { bubbles: true }));
2193
- }
2194
- } else {
2195
- this.updateAltInput();
2196
- }
2197
- }
2198
- /**
2199
- * Update altInput `<input>` element with selected value
2200
- */
2201
- updateAltInput() {
2202
- if (this.altInput == null)
2203
- return;
2204
- if (this.altFormat == null) {
2205
- let precision = "day";
2206
- if (this.timePicker) {
2207
- if (this.timePickerOpts.showSeconds) {
2208
- precision = "second";
2209
- } else if (this.timePickerOpts.showMinutes) {
2210
- precision = "minute";
2211
- } else {
2212
- precision = "hour";
2213
- }
2214
- }
2215
- const startDate = this.#startDate.toISO({ format: "basic", precision, includeOffset: false });
2216
- (this.singleDatePicker ? this.altInput : this.altInput[0]).value = startDate;
2217
- if (!this.singleDatePicker && this.#endDate) {
2218
- const endDate = this.#endDate.toISO({ format: "basic", precision, includeOffset: false });
2219
- this.altInput[1].value = endDate;
2220
- }
2221
- } else {
2222
- const startDate = typeof this.altFormat === "function" ? this.altFormat(this.#startDate) : this.formatDate(this.#startDate, this.altFormat);
2223
- (this.singleDatePicker ? this.altInput : this.altInput[0]).value = startDate;
2224
- if (!this.singleDatePicker && this.#endDate) {
2225
- const endDate = typeof this.altFormat === "function" ? this.altFormat(this.#endDate) : this.formatDate(this.#endDate, this.altFormat);
2226
- this.altInput[1].value = endDate;
2227
- }
2228
- }
2229
- }
2230
- /**
2231
- * Removes the picker from document
2232
- */
2233
- remove() {
2234
- this.element.removeEventListener("click", this.#showProxy);
2235
- this.element.removeEventListener("focus", this.#showProxy);
2236
- this.element.removeEventListener("keyup", this.#elementChangedProxy);
2237
- this.element.removeEventListener("keydown", this.#keydownProxy);
2238
- this.element.removeEventListener("click", this.#toggleProxy);
2239
- this.element.removeEventListener("keydown", this.#toggleProxy);
2240
- this.container.remove();
2241
- }
2242
- /**
2243
- * Helper function to dispatch events
2244
- * @param {object} ev - Event template from this.#events
2245
- * @param {...object} args - Additional parameters if needed
2246
- */
2247
- triggerEvent(ev, ...args) {
2248
- if (args.length === 0) {
2249
- const event = new DateRangePickerEvent(this, ev);
2250
- this.element.dispatchEvent(event);
2251
- return event;
2252
- } else {
2253
- const event = new DateRangePickerEvent(this, ev, ...args);
2254
- this.element.dispatchEvent(event);
2255
- return event;
2256
- }
2257
- }
2258
- /**
2259
- * Helper function to add eventListener similar to jQuery .on( events [, selector ] [, data ] )
2260
- * @param {string} element - Query selector of element where listener is added
2261
- * @param {string} eventName - Name of the event
2262
- * @param {string} selector - Query selector string to filter the descendants of the element
2263
- * @param {function} delegate - Handler data
2264
- * @private
2265
- */
2266
- addListener(element, eventName, selector, delegate) {
2267
- this.container.querySelectorAll(element).forEach((el) => {
2268
- el.addEventListener(eventName, function(event) {
2269
- const target = event.target.closest(selector);
2270
- if (target && el.contains(target))
2271
- delegate.call(target, event);
2272
- });
2273
- });
2274
- }
2275
- }
2276
- function createElementFromHTML(html) {
2277
- const template = document.createElement("template");
2278
- template.innerHTML = html.trim();
2279
- return template.content.firstElementChild;
2280
- }
2281
- class DateRangePickerEvent extends Event {
2282
- #picker;
2283
- constructor(drp, ev, args = {}) {
2284
- let param = {};
2285
- if (ev.param)
2286
- param = typeof ev.param === "function" ? ev.param() : ev.param;
2287
- param = { ...param, ...args };
2288
- super(ev.type, param);
2289
- this.#picker = drp;
2290
- for (const [key2, value] of Object.entries(param)) {
2291
- if (Object.getOwnPropertyNames(Event.prototype).includes(key2)) continue;
2292
- this[key2] = value;
2293
- }
2294
- }
2295
- get picker() {
2296
- return this.#picker;
2297
- }
2298
- }
2299
- function offset(el) {
2300
- const rect = el.getBoundingClientRect();
2301
- return {
2302
- top: rect.top + window.scrollY,
2303
- left: rect.left + window.scrollX
2304
- };
2305
- }
2306
- function outerWidth(el, withMargin = false) {
2307
- if (!withMargin)
2308
- return el.offsetWidth;
2309
- const style = getComputedStyle(el);
2310
- return el.offsetWidth + parseFloat(style.marginLeft) + parseFloat(style.marginRight);
2311
- }
2312
- function outerHeight(el, withMargin = false) {
2313
- if (!withMargin)
2314
- return el.offsetHeight;
2315
- const style = getComputedStyle(el);
2316
- return el.offsetHeight + parseFloat(style.marginTop) + parseFloat(style.marginBottom);
2317
- }
2318
- function daterangepicker(elements, options, callback) {
2319
- if (typeof elements === "string")
2320
- return daterangepicker(document.querySelectorAll(elements), options, callback);
2321
- if (elements instanceof HTMLElement)
2322
- elements = [elements];
2323
- if (elements instanceof NodeList || elements instanceof HTMLCollection)
2324
- elements = Array.from(elements);
2325
- if (elements == null)
2326
- return new DateRangePicker(null, options || {}, callback);
2327
- elements.forEach((el) => {
2328
- if (el._daterangepicker && typeof el._daterangepicker.remove === "function")
2329
- el._daterangepicker.remove();
2330
- el._daterangepicker = new DateRangePicker(el, options || {}, callback);
2331
- });
2332
- return elements.length === 1 ? elements[0] : elements;
2333
- }
2334
- function getDateRangePicker(target) {
2335
- if (typeof target === "string")
2336
- target = document.querySelector(target);
2337
- return target instanceof HTMLElement ? target._daterangepicker : void 0;
2338
- }
2339
- if (window.jQuery?.fn) {
2340
- jQuery.fn.daterangepicker = function(options, callback) {
2341
- return this.each(function() {
2342
- daterangepicker(this, options, callback);
2343
- });
2344
- };
2345
- }
2346
- function registerJqueryPlugin(jq) {
2347
- if (!jq?.fn) return;
2348
- jq.fn.daterangepicker = function(options, callback) {
2349
- return this.each(function() {
2350
- daterangepicker(this, options, callback);
2351
- });
2352
- };
2353
- }
2354
- Object.defineProperty(window, "jQuery", {
2355
- configurable: true,
2356
- set(value) {
2357
- this._jQuery = value;
2358
- registerJqueryPlugin(value);
2359
- },
2360
- get() {
2361
- return this._jQuery;
2362
- }
2363
- });
2364
- exports.DateRangePicker = DateRangePicker;
2365
- exports.daterangepicker = daterangepicker;
2366
- exports.getDateRangePicker = getDateRangePicker;
2367
- //# sourceMappingURL=daterangepicker.js.map
1
+ "use strict";
2
+ var luxon = require("luxon");
3
+ class DateRangePicker {
4
+ #startDate = null;
5
+ #endDate = null;
6
+ constructor(element, options, cb) {
7
+ if (typeof element === "string" && document.querySelectorAll(element).length > 1)
8
+ throw new RangeError(`Option 'element' must match to one element only`);
9
+ this.parentEl = "body";
10
+ this.element = element instanceof HTMLElement ? element : document.querySelector(element);
11
+ this.isInputText = this.element instanceof HTMLInputElement && this.element.type === "text";
12
+ this.#startDate = luxon.DateTime.now().startOf("day");
13
+ this.#endDate = luxon.DateTime.now().plus({ day: 1 }).startOf("day");
14
+ this.minDate = null;
15
+ this.maxDate = null;
16
+ this.maxSpan = null;
17
+ this.minSpan = null;
18
+ this.defaultSpan = null;
19
+ this.initalMonth = luxon.DateTime.now().startOf("month");
20
+ this.autoApply = false;
21
+ this.singleDatePicker = false;
22
+ this.singleMonthView = false;
23
+ this.showDropdowns = false;
24
+ this.minYear = luxon.DateTime.now().minus({ year: 100 }).year;
25
+ this.maxYear = luxon.DateTime.now().plus({ year: 100 }).year;
26
+ this.showWeekNumbers = false;
27
+ this.showISOWeekNumbers = false;
28
+ this.showCustomRangeLabel = true;
29
+ this.showLabel = !this.isInputText;
30
+ this.timePicker = false;
31
+ const usesMeridiems = new Intl.DateTimeFormat(luxon.DateTime.now().locale, { hour: "numeric" }).resolvedOptions();
32
+ this.timePicker24Hour = !usesMeridiems.hour12;
33
+ this.timePickerStepSize = luxon.Duration.fromObject({ minutes: 1 });
34
+ this.linkedCalendars = true;
35
+ this.autoUpdateInput = true;
36
+ this.alwaysShowCalendars = false;
37
+ this.isInvalidDate = null;
38
+ this.isInvalidTime = null;
39
+ this.isCustomDate = null;
40
+ this.onOutsideClick = "apply";
41
+ this.opens = this.element?.classList.contains("pull-right") ? "left" : "right";
42
+ this.drops = this.element?.classList.contains("dropup") ? "up" : "down";
43
+ this.buttonClasses = "btn btn-sm";
44
+ this.applyButtonClasses = "btn-primary";
45
+ this.cancelButtonClasses = "btn-default";
46
+ this.weekendClasses = "weekend";
47
+ this.weekendDayClasses = "weekend-day";
48
+ this.todayClasses = "today";
49
+ this.altInput = null;
50
+ this.altFormat = null;
51
+ this.externalStyle = null;
52
+ this.ranges = {};
53
+ this.locale = {
54
+ direction: "ltr",
55
+ format: luxon.DateTime.DATE_SHORT,
56
+ // or DateTime.DATETIME_SHORT when timePicker: true
57
+ separator: " - ",
58
+ applyLabel: "Apply",
59
+ cancelLabel: "Cancel",
60
+ weekLabel: "W",
61
+ customRangeLabel: "Custom Range",
62
+ daysOfWeek: luxon.Info.weekdays("short"),
63
+ monthNames: luxon.Info.months("long"),
64
+ firstDay: luxon.Info.getStartOfWeek(),
65
+ durationFormat: null
66
+ };
67
+ if (this.element == null)
68
+ return;
69
+ this.callback = function() {
70
+ };
71
+ this.isShowing = false;
72
+ this.leftCalendar = {};
73
+ this.rightCalendar = {};
74
+ if (typeof options !== "object" || options === null)
75
+ options = {};
76
+ let dataOptions = {};
77
+ const data = Array.from(this.element.attributes).filter((x) => x.name.startsWith("data-"));
78
+ for (let item of data) {
79
+ const name = item.name.replace(/^data-/g, "").replace(/-([a-z])/g, function(str) {
80
+ return str[1].toUpperCase();
81
+ });
82
+ if (!Object.keys(this).concat(["startDate", "endDate"]).includes(name) || Object.keys(options).includes(name))
83
+ continue;
84
+ let ts = luxon.DateTime.fromISO(item.value);
85
+ const isDate = ["startDate", "endDate", "minDate", "maxDate", "initalMonth"].includes(name);
86
+ dataOptions[name] = ts.isValid && isDate ? ts : JSON.parse(item.value);
87
+ }
88
+ options = { ...dataOptions, ...options };
89
+ if (typeof options.singleDatePicker === "boolean")
90
+ this.singleDatePicker = options.singleDatePicker;
91
+ if (!this.singleDatePicker && typeof options.singleMonthView === "boolean") {
92
+ this.singleMonthView = options.singleMonthView;
93
+ } else {
94
+ this.singleMonthView = false;
95
+ }
96
+ if (!(options.externalStyle === null)) {
97
+ const bodyStyle = window.getComputedStyle(document.body);
98
+ if (bodyStyle && typeof bodyStyle[Symbol.iterator] === "function" && [...bodyStyle].some((x) => x.startsWith("--bulma-")))
99
+ this.externalStyle = "bulma";
100
+ }
101
+ if (typeof options.template === "string" || options.template instanceof HTMLElement) {
102
+ this.container = typeof options.template === "string" ? createElementFromHTML(options.template) : options.template;
103
+ } else {
104
+ let template = [
105
+ '<div class="daterangepicker">',
106
+ '<div class="ranges"></div>',
107
+ '<div class="drp-calendar left">',
108
+ '<table class="calendar-table">',
109
+ "<thead></thead>",
110
+ "<tbody></tbody>",
111
+ "<tfoot>",
112
+ '<tr class="calendar-time start-time"></tr>',
113
+ this.singleMonthView ? '<tr class="calendar-time end-time"></tr>' : "",
114
+ "</tfoot>",
115
+ "</table>",
116
+ "</div>"
117
+ ];
118
+ template.push(...[
119
+ '<div class="drp-calendar right">',
120
+ '<table class="calendar-table">',
121
+ "<thead></thead>",
122
+ "<tbody></tbody>",
123
+ "<tfoot>",
124
+ this.singleMonthView ? "" : '<tr class="calendar-time end-time"></tr>',
125
+ "</tfoot>",
126
+ "</table>",
127
+ "</div>"
128
+ ]);
129
+ template.push(...[
130
+ '<div class="drp-buttons">',
131
+ '<div class="drp-duration-label"></div>',
132
+ '<div class="drp-selected"></div>'
133
+ ]);
134
+ if (this.externalStyle === "bulma") {
135
+ template.push(...[
136
+ '<div class="buttons">',
137
+ '<button class="cancelBtn button is-small" type="button"></button>',
138
+ '<button class="applyBtn button is-small" disabled type="button"></button>',
139
+ "</div>"
140
+ ]);
141
+ } else {
142
+ template.push(...[
143
+ "<div>",
144
+ '<button class="cancelBtn" type="button"></button>',
145
+ '<button class="applyBtn" disabled type="button"></button>',
146
+ "</div>"
147
+ ]);
148
+ }
149
+ template.push("</div></div>");
150
+ options.template = template.join("");
151
+ this.container = createElementFromHTML(options.template);
152
+ }
153
+ this.parentEl = document.querySelector(typeof options.parentEl === "string" ? options.parentEl : this.parentEl);
154
+ this.parentEl.appendChild(this.container);
155
+ if (typeof options.timePicker === "boolean")
156
+ this.timePicker = options.timePicker;
157
+ if (this.timePicker)
158
+ this.locale.format = luxon.DateTime.DATETIME_SHORT;
159
+ if (typeof options.locale === "object") {
160
+ for (let key2 of ["separator", "applyLabel", "cancelLabel", "weekLabel"]) {
161
+ if (typeof options.locale[key2] === "string")
162
+ this.locale[key2] = options.locale[key2];
163
+ }
164
+ if (typeof options.locale.direction === "string") {
165
+ if (["rtl", "ltr"].includes(options.locale.direction))
166
+ this.locale.direction = options.locale.direction;
167
+ else
168
+ console.error(`Option 'options.locale.direction' must be 'rtl' or 'ltr'`);
169
+ }
170
+ if (["string", "object"].includes(typeof options.locale.format))
171
+ this.locale.format = options.locale.format;
172
+ if (Array.isArray(options.locale.daysOfWeek)) {
173
+ if (options.locale.daysOfWeek.some((x) => typeof x !== "string"))
174
+ console.error(`Option 'options.locale.daysOfWeek' must be an array of strings`);
175
+ else
176
+ this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
177
+ }
178
+ if (Array.isArray(options.locale.monthNames)) {
179
+ if (options.locale.monthNames.some((x) => typeof x !== "string"))
180
+ console.error(`Option 'locale.monthNames' must be an array of strings`);
181
+ else
182
+ this.locale.monthNames = options.locale.monthNames.slice();
183
+ }
184
+ if (typeof options.locale.firstDay === "number")
185
+ this.locale.firstDay = options.locale.firstDay;
186
+ if (typeof options.locale.customRangeLabel === "string") {
187
+ var elem = document.createElement("textarea");
188
+ elem.innerHTML = options.locale.customRangeLabel;
189
+ var rangeHtml = elem.value;
190
+ this.locale.customRangeLabel = rangeHtml;
191
+ }
192
+ if (["string", "object", "function"].includes(typeof options.locale.durationFormat) && options.locale.durationFormat != null)
193
+ this.locale.durationFormat = options.locale.durationFormat;
194
+ }
195
+ this.container.classList.add(this.locale.direction);
196
+ for (let key2 of [
197
+ "timePicker24Hour",
198
+ "showWeekNumbers",
199
+ "showISOWeekNumbers",
200
+ "showDropdowns",
201
+ "linkedCalendars",
202
+ "showCustomRangeLabel",
203
+ "alwaysShowCalendars",
204
+ "autoApply",
205
+ "autoUpdateInput",
206
+ "showLabel"
207
+ ]) {
208
+ if (typeof options[key2] === "boolean")
209
+ this[key2] = options[key2];
210
+ }
211
+ for (let key2 of ["applyButtonClasses", "cancelButtonClasses", "weekendClasses", "weekendDayClasses", "todayClasses"]) {
212
+ if (typeof options[key2] === "string") {
213
+ this[key2] = options[key2];
214
+ } else if (["weekendClasses", "weekendDayClasses", "todayClasses"].includes(key2) && options[key2] === null) {
215
+ this[key2] = options[key2];
216
+ }
217
+ }
218
+ for (let key2 of ["minYear", "maxYear"]) {
219
+ if (typeof options[key2] === "number")
220
+ this[key2] = options[key2];
221
+ }
222
+ for (let key2 of ["isInvalidDate", "isInvalidTime", "isCustomDate"]) {
223
+ if (typeof options[key2] === "function")
224
+ this[key2] = options[key2];
225
+ else
226
+ this[key2] = function() {
227
+ return false;
228
+ };
229
+ }
230
+ if (!this.singleDatePicker) {
231
+ for (let opt of ["minSpan", "maxSpan", "defaultSpan"]) {
232
+ if (["string", "number", "object"].includes(typeof options[opt])) {
233
+ if (luxon.Duration.isDuration(options[opt]) && options[opt].isValid) {
234
+ this[opt] = options[opt];
235
+ } else if (luxon.Duration.fromISO(options[opt]).isValid) {
236
+ this[opt] = luxon.Duration.fromISO(options[opt]);
237
+ } else if (typeof options[opt] === "number" && luxon.Duration.fromObject({ seconds: options[opt] }).isValid) {
238
+ this[opt] = luxon.Duration.fromObject({ seconds: options[opt] });
239
+ } else if (options[opt] === null) {
240
+ this[opt] = null;
241
+ } else {
242
+ console.error(`Option '${key}' is not valid`);
243
+ }
244
+ }
245
+ }
246
+ if (this.minSpan && this.maxSpan && this.minSpan > this.maxSpan) {
247
+ this.minSpan = null;
248
+ this.maxSpan = null;
249
+ console.warn(`Ignore option 'minSpan' and 'maxSpan', because 'minSpan' must be smaller than 'maxSpan'`);
250
+ }
251
+ if (this.defaultSpan && this.minSpan && this.minSpan > this.defaultSpan) {
252
+ this.defaultSpan = null;
253
+ console.warn(`Ignore option 'defaultSpan', because 'defaultSpan' must be greater than 'minSpan'`);
254
+ } else if (this.defaultSpan && this.maxSpan && this.maxSpan < this.defaultSpan) {
255
+ this.defaultSpan = null;
256
+ console.warn(`Ignore option 'defaultSpan', because 'defaultSpan' must be smaller than 'maxSpan'`);
257
+ }
258
+ }
259
+ if (this.timePicker) {
260
+ if (["string", "object", "number"].includes(typeof options.timePickerStepSize)) {
261
+ let duration;
262
+ if (luxon.Duration.isDuration(options.timePickerStepSize) && options.timePickerStepSize.isValid) {
263
+ duration = options.timePickerStepSize;
264
+ } else if (luxon.Duration.fromISO(options.timePickerStepSize).isValid) {
265
+ duration = luxon.Duration.fromISO(options.timePickerStepSize);
266
+ } else if (typeof options.timePickerStepSize === "number" && luxon.Duration.fromObject({ seconds: options.timePickerStepSize }).isValid) {
267
+ duration = luxon.Duration.fromObject({ seconds: options.timePickerStepSize });
268
+ } else {
269
+ console.error(`Option 'timePickerStepSize' is not valid`);
270
+ duration = this.timePickerStepSize;
271
+ }
272
+ var valid = [];
273
+ for (let unit of ["minutes", "seconds"])
274
+ valid.push(...[1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30].map((x) => {
275
+ return luxon.Duration.fromObject({ [unit]: x });
276
+ }));
277
+ valid.push(...[1, 2, 3, 4, 6].map((x) => {
278
+ return luxon.Duration.fromObject({ hours: x });
279
+ }));
280
+ if (this.timePicker24Hour)
281
+ valid.push(...[8, 12].map((x) => {
282
+ return luxon.Duration.fromObject({ hours: x });
283
+ }));
284
+ if (valid.some((x) => duration.rescale().equals(x))) {
285
+ this.timePickerStepSize = duration.rescale();
286
+ } else {
287
+ console.error(`Option 'timePickerStepSize' ${JSON.stringify(duration.toObject())} is not valid`);
288
+ }
289
+ }
290
+ if (this.maxSpan && this.timePickerStepSize > this.maxSpan)
291
+ console.error(`Option 'timePickerStepSize' ${JSON.stringify(this.timePickerStepSize.toObject())} must be smaller than 'maxSpan'`);
292
+ this.timePickerOpts = {
293
+ showMinutes: this.timePickerStepSize < luxon.Duration.fromObject({ hours: 1 }),
294
+ showSeconds: this.timePickerStepSize < luxon.Duration.fromObject({ minutes: 1 }),
295
+ hourStep: this.timePickerStepSize >= luxon.Duration.fromObject({ hours: 1 }) ? this.timePickerStepSize.hours : 1,
296
+ minuteStep: this.timePickerStepSize >= luxon.Duration.fromObject({ minutes: 1 }) ? this.timePickerStepSize.minutes : 1,
297
+ secondStep: this.timePickerStepSize.seconds
298
+ };
299
+ }
300
+ for (let opt of ["startDate", "endDate", "minDate", "maxDate", "initalMonth"]) {
301
+ if (opt === "endDate" && this.singleDatePicker)
302
+ continue;
303
+ if (typeof options[opt] === "object") {
304
+ if (luxon.DateTime.isDateTime(options[opt]) && options[opt].isValid) {
305
+ this[opt] = options[opt];
306
+ } else if (options[opt] instanceof Date) {
307
+ this[opt] = luxon.DateTime.fromJSDate(options[opt]);
308
+ } else if (options[opt] === null) {
309
+ this[opt] = null;
310
+ } else {
311
+ console.error(`Option '${opt}' must be a luxon.DateTime or Date or string`);
312
+ }
313
+ } else if (typeof options[opt] === "string") {
314
+ const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
315
+ if (luxon.DateTime.fromISO(options[opt]).isValid) {
316
+ this[opt] = luxon.DateTime.fromISO(options[opt]);
317
+ } else if (luxon.DateTime.fromFormat(options[opt], format, { locale: luxon.DateTime.now().locale }).isValid) {
318
+ this[opt] = luxon.DateTime.fromFormat(options[opt], format, { locale: luxon.DateTime.now().locale });
319
+ } else {
320
+ const invalid = luxon.DateTime.fromFormat(options[opt], format, { locale: luxon.DateTime.now().locale }).invalidExplanation;
321
+ console.error(`Option '${opt}' is not a valid string: ${invalid}`);
322
+ }
323
+ }
324
+ }
325
+ if (this.isInputText) {
326
+ if (this.element.value != "") {
327
+ const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
328
+ if (this.singleDatePicker && typeof options.startDate === "undefined") {
329
+ const start = luxon.DateTime.fromFormat(this.element.value, format, { locale: luxon.DateTime.now().locale });
330
+ if (start.isValid) {
331
+ this.#startDate = start;
332
+ } else {
333
+ console.error(`Value "${this.element.value}" in <input> is not a valid string: ${start.invalidExplanation}`);
334
+ }
335
+ } else if (!this.singleDatePicker && typeof options.startDate === "undefined" && typeof options.endDate === "undefined") {
336
+ const split = this.element.value.split(this.locale.separator);
337
+ if (split.length === 2) {
338
+ const start = luxon.DateTime.fromFormat(split[0], format, { locale: luxon.DateTime.now().locale });
339
+ const end = luxon.DateTime.fromFormat(split[1], format, { locale: luxon.DateTime.now().locale });
340
+ if (start.isValid && end.isValid) {
341
+ this.#startDate = start;
342
+ this.#endDate = end;
343
+ } else {
344
+ console.error(`Value in <input> is not a valid string: ${start.invalidExplanation} - ${end.invalidExplanation}`);
345
+ }
346
+ } else {
347
+ console.error(`Value "${this.element.value}" in <input> is not a valid string`);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ if (this.singleDatePicker) {
353
+ this.#endDate = this.#startDate;
354
+ } else if (this.#endDate < this.#startDate) {
355
+ console.error(`Option 'endDate' ${this.#endDate} must not be earlier than 'startDate' ${this.#startDate}`);
356
+ }
357
+ if (!this.timePicker) {
358
+ if (this.minDate) this.minDate = this.minDate.startOf("day");
359
+ if (this.maxDate) this.maxDate = this.maxDate.endOf("day");
360
+ this.#startDate = this.#startDate.startOf("day");
361
+ this.#endDate = this.#endDate.endOf("day");
362
+ }
363
+ if (!this.#startDate && this.initalMonth) {
364
+ this.#endDate = null;
365
+ if (this.timePicker)
366
+ console.error(`Option 'initalMonth' works only with 'timePicker: false'`);
367
+ } else {
368
+ const violations = this.validateInput(null, false);
369
+ if (violations != null) {
370
+ let vio = violations.startDate;
371
+ if (vio.length > 0) {
372
+ if (vio.some((x) => x.reason.startsWith("isInvalid"))) {
373
+ console.error(`Value of startDate "${this.#startDate}" violates ${vio.find((x) => x.reason.startsWith("isInvalid")).reason}`);
374
+ } else {
375
+ const newDate = vio.filter((x) => x.new != null).at(-1).new;
376
+ if (typeof process !== "undefined" && process.env.JEST_WORKER_ID == null)
377
+ console.warn(`Correcting startDate from ${this.#startDate} to ${newDate}`);
378
+ this.#startDate = newDate;
379
+ }
380
+ }
381
+ if (!this.singleDatePicker) {
382
+ vio = violations.endDate.filter((x) => x.new != null);
383
+ if (vio.length > 0) {
384
+ if (vio.some((x) => x.reason.startsWith("isInvalid"))) {
385
+ console.error(`Value of endDate "${this.#endDate}" violates ${vio.find((x) => x.reason.startsWith("isInvalid")).reason}`);
386
+ } else {
387
+ const newDate = vio.filter((x) => x.new != null).at(-1).new;
388
+ if (typeof process !== "undefined" && process.env.JEST_WORKER_ID == null)
389
+ console.warn(`Correcting endDate from ${this.#endDate} to ${newDate}`);
390
+ this.#endDate = newDate;
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+ if (this.singleDatePicker) {
397
+ if (typeof options.altInput === "string") {
398
+ const el = document.querySelector(options.altInput);
399
+ this.altInput = el instanceof HTMLInputElement && el.type === "text" ? el : null;
400
+ } else if (options.altInput instanceof HTMLElement) {
401
+ this.altInput = options.altInput instanceof HTMLInputElement && options.altInput.type === "text" ? options.altInput : null;
402
+ }
403
+ } else if (!this.singleDatePicker && Array.isArray(options.altInput) && options.altInput.length === 2) {
404
+ this.altInput = [];
405
+ for (let item of options.altInput) {
406
+ const el = typeof item === "string" ? document.querySelector(item) : item;
407
+ if (el instanceof HTMLInputElement && el.type === "text")
408
+ this.altInput.push(el);
409
+ }
410
+ if (this.altInput.length !== 2)
411
+ this.altInput = null;
412
+ } else if (options.altInput != null) {
413
+ console.warn(`Option 'altInput' ${JSON.stringify(options.altInput)} is not valid`);
414
+ }
415
+ if (options.altInput && ["function", "string"].includes(typeof options.altFormat))
416
+ this.altFormat = options.altFormat;
417
+ if (typeof options.opens === "string") {
418
+ if (["left", "right", "center"].includes(options.opens))
419
+ this.opens = options.opens;
420
+ else
421
+ console.error(`Option 'options.opens' must be 'left', 'right' or 'center'`);
422
+ }
423
+ if (typeof options.drops === "string") {
424
+ if (["up", "down", "auto"].includes(options.drops))
425
+ this.drops = options.drops;
426
+ else
427
+ console.error(`Option 'options.drops' must be 'up', 'down' or 'auto'`);
428
+ }
429
+ if (Array.isArray(options.buttonClasses)) {
430
+ this.buttonClasses = options.buttonClasses.join(" ");
431
+ } else if (typeof options.buttonClasses === "string") {
432
+ this.buttonClasses = options.buttonClasses;
433
+ }
434
+ if (typeof options.onOutsideClick === "string") {
435
+ if (["cancel", "apply"].includes(options.onOutsideClick))
436
+ this.onOutsideClick = options.onOutsideClick;
437
+ else
438
+ console.error(`Option 'options.onOutsideClick' must be 'cancel' or 'apply'`);
439
+ }
440
+ if (this.locale.firstDay != 1) {
441
+ let iterator = this.locale.firstDay;
442
+ while (iterator > 1) {
443
+ this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
444
+ iterator--;
445
+ }
446
+ }
447
+ if (!this.singleDatePicker && typeof options.ranges === "object") {
448
+ for (let range in options.ranges) {
449
+ let start, end;
450
+ if (["string", "object"].includes(typeof options.ranges[range][0])) {
451
+ if (luxon.DateTime.isDateTime(options.ranges[range][0]) && options.ranges[range][0].isValid) {
452
+ start = options.ranges[range][0];
453
+ } else if (options.ranges[range][0] instanceof Date) {
454
+ start = luxon.DateTime.fromJSDate(options.ranges[range][0]);
455
+ } else if (typeof options.ranges[range][0] === "string" && luxon.DateTime.fromISO(options.ranges[range][0]).isValid) {
456
+ start = luxon.DateTime.fromISO(options.ranges[range][0]);
457
+ } else {
458
+ console.error(`Option ranges['${range}'] is not am array of valid ISO-8601 string or luxon.DateTime or Date`);
459
+ }
460
+ }
461
+ if (["string", "object"].includes(typeof options.ranges[range][1])) {
462
+ if (luxon.DateTime.isDateTime(options.ranges[range][1]) && options.ranges[range][1].isValid) {
463
+ end = options.ranges[range][1];
464
+ } else if (options.ranges[range][1] instanceof Date) {
465
+ end = luxon.DateTime.fromJSDate(options.ranges[range][1]);
466
+ } else if (typeof options.ranges[range][1] === "string" && luxon.DateTime.fromISO(options.ranges[range][1]).isValid) {
467
+ end = luxon.DateTime.fromISO(options.ranges[range][1]);
468
+ } else {
469
+ console.error(`Option ranges['${range}'] is not a valid ISO-8601 string or luxon.DateTime or Date`);
470
+ }
471
+ }
472
+ if (start == null || end == null)
473
+ continue;
474
+ var elem = document.createElement("textarea");
475
+ elem.innerHTML = range;
476
+ this.ranges[elem.value] = [start, end];
477
+ }
478
+ var list = "<ul>";
479
+ for (let range in this.ranges)
480
+ list += `<li data-range-key="${range}">${range}</li>`;
481
+ if (this.showCustomRangeLabel)
482
+ list += `<li data-range-key="${this.locale.customRangeLabel}">${this.locale.customRangeLabel}</li>`;
483
+ list += "</ul>";
484
+ this.container.querySelector(".ranges").prepend(createElementFromHTML(list));
485
+ this.container.classList.add("show-ranges");
486
+ }
487
+ if (typeof cb === "function")
488
+ this.callback = cb;
489
+ if (!this.timePicker)
490
+ this.container.querySelectorAll(".calendar-time").forEach((el) => {
491
+ el.style.display = "none";
492
+ });
493
+ if (this.timePicker && this.autoApply)
494
+ this.autoApply = false;
495
+ if (this.autoApply)
496
+ this.container.classList.add("auto-apply");
497
+ if (this.singleDatePicker || this.singleMonthView) {
498
+ this.container.classList.add("single");
499
+ this.container.querySelector(".drp-calendar.left").classList.add("single");
500
+ this.container.querySelector(".drp-calendar.left").style.display = "";
501
+ this.container.querySelector(".drp-calendar.right").style.display = "none";
502
+ if (!this.timePicker && this.autoApply)
503
+ this.container.classList.add("auto-apply");
504
+ }
505
+ if (this.singleDatePicker || !Object.keys(this.ranges).length || this.alwaysShowCalendars)
506
+ this.container.classList.add("show-calendar");
507
+ this.container.classList.add(`opens${this.opens}`);
508
+ this.container.querySelectorAll(".applyBtn, .cancelBtn").forEach((el) => {
509
+ el.classList.add(...this.buttonClasses.split(" "));
510
+ });
511
+ if (this.applyButtonClasses.length)
512
+ this.container.querySelector(".applyBtn").classList.add(...this.applyButtonClasses.split(" "));
513
+ if (this.cancelButtonClasses.length)
514
+ this.container.querySelector(".cancelBtn").classList.add(...this.cancelButtonClasses.split(" "));
515
+ this.container.querySelector(".applyBtn").innerHTML = this.locale.applyLabel;
516
+ this.container.querySelector(".cancelBtn").innerHTML = this.locale.cancelLabel;
517
+ this.addListener(".drp-calendar", "click", ".prev", this.clickPrev.bind(this));
518
+ this.addListener(".drp-calendar", "click", ".next", this.clickNext.bind(this));
519
+ this.addListener(".drp-calendar", "mousedown", "td.available", this.clickDate.bind(this));
520
+ this.addListener(".drp-calendar", "mouseenter", "td.available", this.hoverDate.bind(this));
521
+ this.addListener(".drp-calendar", "change", "select.yearselect,select.monthselect", this.monthOrYearChanged.bind(this));
522
+ this.addListener(".drp-calendar", "change", "select.hourselect,select.minuteselect,select.secondselect,select.ampmselect", this.timeChanged.bind(this));
523
+ this.addListener(".ranges", "click", "li", this.clickRange.bind(this));
524
+ this.addListener(".ranges", "mouseenter", "li", this.hoverRange.bind(this));
525
+ this.addListener(".ranges", "mouseleave", "li", this.leaveRange.bind(this));
526
+ this.addListener(".drp-buttons", "click", "button.applyBtn", this.clickApply.bind(this));
527
+ this.addListener(".drp-buttons", "click", "button.cancelBtn", this.clickCancel.bind(this));
528
+ if (this.element.matches("input") || this.element.matches("button")) {
529
+ this.element.addEventListener("click", this.#showProxy);
530
+ this.element.addEventListener("focus", this.#showProxy);
531
+ this.element.addEventListener("keyup", this.#elementChangedProxy);
532
+ this.element.addEventListener("keydown", this.#keydownProxy);
533
+ } else {
534
+ this.element.addEventListener("click", this.#toggleProxy);
535
+ this.element.addEventListener("keydown", this.#toggleProxy);
536
+ }
537
+ this.updateElement();
538
+ }
539
+ /**
540
+ * startDate
541
+ * @type {external:DateTime}
542
+ */
543
+ get startDate() {
544
+ return this.timePicker ? this.#startDate : this.#startDate.startOf("day");
545
+ }
546
+ /**
547
+ * endDate
548
+ * @type {external:DateTime}
549
+ */
550
+ get endDate() {
551
+ return this.singleDatePicker ? null : this.timePicker ? this.#endDate : this.#endDate.endOf("day");
552
+ }
553
+ set startDate(val) {
554
+ this.#startDate = val;
555
+ }
556
+ set endDate(val) {
557
+ this.#endDate = val;
558
+ }
559
+ /**
560
+ * DateRangePicker specific events
561
+ */
562
+ #events = {
563
+ /**
564
+ * Emitted when the date is changed through `<input>` element or via {@link #DateRangePicker+setStartDate|setStartDate} or
565
+ * {@link #DateRangePicker+setRange|setRange} and date is not valid due to
566
+ * `minDate`, `maxDate`, `minSpan`, `maxSpan`, `invalidDate` and `invalidTime` constraints.<br>
567
+ * Event is only triggered when date string is valid and date value is changing<br>
568
+ * @event
569
+ * @name "violate"
570
+ * @property {DateRangePickerEvent} event - The Event object
571
+ * @property {DateRangePicker} event.picker - The daterangepicker object
572
+ * @property {InputViolation} event.violation - The daterangepicker object
573
+ * @property {NewDate} event.newDate - Object of corrected date values
574
+ * @property {boolean} event.cancelable=true - By calling `event.preventDefault()` the `newDate` values will apply
575
+ * @example
576
+ * daterangepicker('#picker', {
577
+ * startDate: DateTime.now(),
578
+ * // allow only dates from current year
579
+ * minDate: DateTime.now().startOf('year'),
580
+ * manDate: DateTime.now().endOf('year'),
581
+ * singleDatePicker: true,
582
+ * locale: {
583
+ * format: DateTime.DATETIME_SHORT
584
+ * }
585
+ * }).addEventListener('violate', (ev) => {
586
+ * ev.newDate.startDate = DateTime.now().minus({ days: 3 }).startOf('day');
587
+ * ev.preventDefault();
588
+ * });
589
+ *
590
+ * // Try to set date outside permitted range at <input> elemet
591
+ * const input = document.querySelector('#picker');
592
+ * input.value = DateTime.now().minus({ years: 10 })).toLocaleString(DateTime.DATETIME_SHORT)
593
+ * input.dispatchEvent(new Event('keyup'));
594
+
595
+ * // Try to set date outside permitted range by code
596
+ * const drp = getDateRangePicker('#picker');
597
+ * drp.setStartDate(DateTime.now().minus({ years: 10 });
598
+ *
599
+ * // -> Calendar selects and shows "today - 3 days"
600
+ */
601
+ onViolate: { type: "violate", param: (violation, newDate) => {
602
+ return { ...violation, ...{ cancelable: true } };
603
+ } },
604
+ /**
605
+ * Emitted before the calendar time picker is rendered.
606
+ * @event
607
+ * @name "beforeRenderTimePicker"
608
+ * @property {DateRangePickerEvent} event - The Event object
609
+ * @property {DateRangePicker} event.picker - The daterangepicker object
610
+ */
611
+ onBeforeRenderTimePicker: { type: "beforeRenderTimePicker" },
612
+ /**
613
+ * Emitted before the calendar is rendered.
614
+ * @event
615
+ * @name "beforeRenderCalendar"
616
+ * @property {DateRangePickerEvent} event - The Event object
617
+ * @property {DateRangePicker} event.picker - The daterangepicker object
618
+ */
619
+ onBeforeRenderCalendar: { type: "beforeRenderCalendar" },
620
+ /**
621
+ * Emitted when the picker is shown
622
+ * @event
623
+ * @name "show"
624
+ * @property {DateRangePickerEvent} event - The Event object
625
+ * @property {DateRangePicker} event.picker - The daterangepicker object
626
+ */
627
+ onShow: { type: "show" },
628
+ /**
629
+ * Emitted before the picker will hide.
630
+ * @event
631
+ * @name "beforeHide"
632
+ * @property {DateRangePickerEvent} event - The Event object
633
+ * @property {DateRangePicker} event.picker - The daterangepicker object
634
+ * @property {boolean} event.cancelable=true - Hide is canceled by calling `event.preventDefault()`
635
+ */
636
+ onBeforeHide: { type: "beforeHide", param: { cancelable: true } },
637
+ /**
638
+ * Emitted when the picker is hidden
639
+ * @event
640
+ * @name "hide"
641
+ * @property {DateRangePickerEvent} event - The Event object
642
+ * @property {DateRangePicker} event.picker - The daterangepicker object
643
+ */
644
+ onHide: { type: "hide" },
645
+ /**
646
+ * Emitted when the calendar(s) are shown.
647
+ * Only useful when {@link #Ranges|Ranges} are used.
648
+ * @event
649
+ * @name "showCalendar"
650
+ * @property {DateRangePickerEvent} event - The Event object
651
+ * @property {DateRangePicker} event.picker - The daterangepicker object
652
+ */
653
+ onShowCalendar: { type: "showCalendar" },
654
+ /**
655
+ * Emitted when the calendar(s) are hidden. Only used when {@link #Ranges|Ranges} are used.
656
+ * @event
657
+ * @name "hideCalendar"
658
+ * @property {DateRangePickerEvent} event - The Event object
659
+ * @property {DateRangePicker} event.picker - The daterangepicker object
660
+ */
661
+ onHideCalendar: { type: "hideCalendar" },
662
+ /**
663
+ * Emitted when user clicks outside the picker. Use option `onOutsideClick` to define the default action, then you may not need to handle this event.
664
+ * @event
665
+ * @name "outsideClick"
666
+ * @property {DateRangePickerEvent} event - The Event object
667
+ * @property {DateRangePicker} event.picker - The daterangepicker object
668
+ * @property {boolean} event.cancelable=true - Call `event.preventDefault()` to prevent default behaviour.<br>
669
+ * Useful to define custome areas where click shall not hide the picker
670
+ */
671
+ onOutsideClick: { type: "outsideClick", param: { cancelable: true } },
672
+ /**
673
+ * Emitted when the date changed. Does not trigger when time is changed, use {@link #event_timeChange|"timeChange"} to handle it
674
+ * @event
675
+ * @name "dateChange"
676
+ * @property {DateRangePickerEvent} event - The Event object
677
+ * @property {DateRangePicker} event.picker - The daterangepicker object
678
+ * @property {string} event.side - Either `'start'` or `'end'` indicating whether `startDate` or `endDate` was changed. `null` for singleDatePicker
679
+ */
680
+ onDateChange: { type: "dateChange", param: (side) => {
681
+ return side;
682
+ } },
683
+ /**
684
+ * Emitted when the time changed. Does not trigger when date is changed
685
+ * @event
686
+ * @name "timeChange"
687
+ * @property {DateRangePickerEvent} event - The Event object
688
+ * @property {DateRangePicker} event.picker - The daterangepicker object
689
+ * @property {string} event.side - Either `'start'` or `'end'` indicating whether `startDate` or `endDate` was changed. `null` for singleDatePicker
690
+ */
691
+ onTimeChange: { type: "timeChange", param: (side) => {
692
+ return side;
693
+ } },
694
+ /**
695
+ * Emitted when the `Apply` button is clicked, or when a predefined {@link #Ranges|Ranges} is clicked
696
+ * @event
697
+ * @name "apply"
698
+ * @property {DateRangePickerEvent} event - The Event object
699
+ * @property {DateRangePicker} event.picker - The daterangepicker object
700
+ */
701
+ onApply: { type: "apply" },
702
+ /**
703
+ * Emitted when the `Cancel` button is clicked
704
+ * @event
705
+ * @name "cancel"
706
+ * @property {DateRangePickerEvent} event - The Event object
707
+ * @property {DateRangePicker} event.picker - The daterangepicker object
708
+ */
709
+ onCancel: { type: "cancel" },
710
+ /**
711
+ * Emitted when the date is changed through `<input>` element. Event is only triggered when date string is valid and date value has changed
712
+ * @event
713
+ * @name "inputChange"
714
+ * @property {DateRangePickerEvent} event - The Event object
715
+ * @property {DateRangePicker} event.picker - The daterangepicker object
716
+ */
717
+ onInputChange: { type: "inputChange" },
718
+ /**
719
+ * Emitted after month view changed, for example by click on 'prev' or 'next'
720
+ * @event
721
+ * @name "monthViewChange"
722
+ * @property {DateRangePickerEvent} event - The Event object
723
+ * @property {DateRangePicker} event.picker - The daterangepicker object
724
+ * @property {external:DateTime} event.left - The first day of month in left-hand calendar
725
+ * @property {external:DateTime} event.right - The first day of month in left-hand calendar or `null` for singleDatePicker
726
+ */
727
+ onMonthViewChange: {
728
+ type: "monthViewChange",
729
+ param: (left, right) => {
730
+ return {
731
+ left: this.leftCalendar.month.startOf("month"),
732
+ right: this.singleMonthView || this.singleDatePicker ? null : this.rightCalendar.month.startOf("month")
733
+ };
734
+ }
735
+ }
736
+ };
737
+ /**
738
+ * Getter for all DateRangePickerEvents
739
+ */
740
+ get events() {
741
+ return this.#events;
742
+ }
743
+ #outsideClickProxy = this.outsideClick.bind(this);
744
+ #onResizeProxy = this.move.bind(this);
745
+ #dropdownClickWrapper = (e) => {
746
+ const match = e.target.closest('[data-toggle="dropdown"]');
747
+ if (match && document.contains(match))
748
+ this.#outsideClickProxy(e);
749
+ };
750
+ #showProxy = this.show.bind(this);
751
+ #elementChangedProxy = this.elementChanged.bind(this);
752
+ #keydownProxy = this.keydown.bind(this);
753
+ #toggleProxy = this.toggle.bind(this);
754
+ /* #region Set startDate/endDate */
755
+ /**
756
+ * Sets the date range picker's currently selected start date to the provided date.<br>
757
+ * `startDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
758
+ * Invalid date values are handled by {@link #DateRangePicker+violate|violate} Event
759
+ * @param {external:DateTime|external:Date|string} startDate - startDate to be set. In case of ranges, the current `endDate` is used.
760
+ * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
761
+ * @returns {InputViolation} - Object of violations or `null` if no violation have been found
762
+ * @example
763
+ * const drp = getDateRangePicker('#picker');
764
+ * drp.setStartDate(DateTime.now().startOf('hour'));
765
+ */
766
+ setStartDate(startDate, updateView = true) {
767
+ if (!this.singleDatePicker)
768
+ return setRange(startDate, this.#endDate, updateView);
769
+ const oldDate = this.#startDate;
770
+ let newDate = this.parseDate(startDate);
771
+ if (newDate.equals(oldDate))
772
+ return null;
773
+ const violations = this.validateInput([newDate, null], true);
774
+ if (violations != null) {
775
+ if (violations.newDate != null) {
776
+ newDate = violations.newDate.startDate;
777
+ } else {
778
+ return violations;
779
+ }
780
+ }
781
+ const monthChange = !this.#startDate.hasSame(newDate, "month");
782
+ this.#startDate = newDate;
783
+ this.#endDate = this.#startDate;
784
+ if (!this.timePicker) {
785
+ this.#startDate = this.#startDate.startOf("day");
786
+ this.#endDate = this.#endDate.endOf("day");
787
+ }
788
+ this.updateElement();
789
+ if (updateView)
790
+ this.updateView(monthChange);
791
+ return violations;
792
+ }
793
+ /**
794
+ * Sets the date range picker's currently selected start date to the provided date.<br>
795
+ * `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
796
+ * Invalid date values are handled by {@link #DateRangePicker+violate|violate} Event
797
+ * @param {external:DateTime|external:Date|string} endDate - endDate to be set. In case of ranges, the current `startDate` is used.
798
+ * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
799
+ * @returns {InputViolation} - Object of violations or `null` if no violation have been found
800
+ * @example
801
+ * const drp = getDateRangePicker('#picker');
802
+ * drp.setEndDate(DateTime.now().startOf('hour'));
803
+ */
804
+ setEndDate(endDate, updateView = true) {
805
+ return this.singleDatePicker ? null : setRange(this.#startDate, endDate, updateView);
806
+ }
807
+ /**
808
+ * Sets the date range picker's currently selected start date to the provided date.<br>
809
+ * `startDate` and `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
810
+ * Invalid date values are handled by {@link #DateRangePicker+violate|violate} Event
811
+ * @param {external:DateTime|external:Date|string} startDate - startDate to be set
812
+ * @param {external:DateTime|external:Date|string} endDate - endDate to be set
813
+ * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
814
+ * @returns {InputViolation} - Object of violations or `null` if no violation have been found
815
+ * @example
816
+ * const drp = getDateRangePicker('#picker');
817
+ * drp.setRange(DateTime.now().startOf('hour'), DateTime.now().endOf('day'));
818
+ */
819
+ setRange(startDate, endDate, updateView = true) {
820
+ if (this.singleDatePicker)
821
+ return;
822
+ if (!this.#endDate)
823
+ this.#endDate = this.#startDate;
824
+ const oldDate = [this.#startDate, this.#endDate];
825
+ let newDate = [this.parseDate(startDate), this.parseDate(endDate)];
826
+ if (oldDate[0].equals(newDate[0]) && oldDate[1].equals(newDate[1]) || newDate[0] > newDate[1])
827
+ return;
828
+ const violations = this.validateInput([newDate[0], newDate[1]], true);
829
+ if (violations != null) {
830
+ if (violations.newDate != null) {
831
+ newDate[0] = violations.newDate.startDate;
832
+ newDate[1] = violations.newDate.endDate;
833
+ } else {
834
+ return violations;
835
+ }
836
+ }
837
+ const monthChange = !this.#startDate.hasSame(newDate[0], "month") || !this.#endDate.hasSame(newDate[1], "month");
838
+ this.#startDate = newDate[0];
839
+ this.#endDate = newDate[1];
840
+ if (!this.timePicker) {
841
+ this.#startDate = this.#startDate.startOf("day");
842
+ this.#endDate = this.#endDate.endOf("day");
843
+ }
844
+ this.updateElement();
845
+ if (updateView)
846
+ this.updateView(monthChange);
847
+ return violations;
848
+ }
849
+ /**
850
+ * Parse date value
851
+ * @param {sting|external:DateTime|Date} value - The value to be parsed
852
+ * @returns {external:DateTime} - DateTime object
853
+ */
854
+ parseDate(value) {
855
+ if (typeof value === "object") {
856
+ if (luxon.DateTime.isDateTime(value) && value.isValid) {
857
+ return value;
858
+ } else if (value instanceof Date) {
859
+ return luxon.DateTime.fromJSDate(value);
860
+ } else {
861
+ throw RangeError(`Value must be a luxon.DateTime or Date or string`);
862
+ }
863
+ } else if (typeof value === "string") {
864
+ const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
865
+ if (luxon.DateTime.fromISO(value).isValid) {
866
+ return luxon.DateTime.fromISO(value);
867
+ } else if (luxon.DateTime.fromFormat(value, format, { locale: luxon.DateTime.now().locale }).isValid) {
868
+ return luxon.DateTime.fromFormat(value, format, { locale: luxon.DateTime.now().locale });
869
+ } else {
870
+ const invalid = luxon.DateTime.fromFormat(value, format, { locale: luxon.DateTime.now().locale }).invalidExplanation;
871
+ throw RangeError(`Value is not a valid string: ${invalid}`);
872
+ }
873
+ }
874
+ }
875
+ /* #endregion */
876
+ /**
877
+ * Format a DateTime object
878
+ * @param {external:DateTime} date - The DateTime to format
879
+ * @param {object|string} format=this.locale.format - The format option
880
+ * @returns {string} - Formatted date string
881
+ */
882
+ formatDate(date, format = this.locale.format) {
883
+ if (typeof format === "object") {
884
+ return date.toLocaleString(format);
885
+ } else {
886
+ if (luxon.Settings.defaultLocale === null) {
887
+ const locale = luxon.DateTime.now().locale;
888
+ return date.toFormat(format, { locale });
889
+ } else {
890
+ return date.toFormat(format);
891
+ }
892
+ }
893
+ }
894
+ /**
895
+ * Set Duration Label to selected range (if used) and selected dates
896
+ * @private
897
+ */
898
+ updateLabel() {
899
+ if (this.showLabel) {
900
+ let text = this.formatDate(this.#startDate);
901
+ if (!this.singleDatePicker) {
902
+ text += this.locale.separator;
903
+ if (this.#endDate)
904
+ text += this.formatDate(this.#endDate);
905
+ }
906
+ this.container.querySelector(".drp-selected").innerHTML = text;
907
+ }
908
+ if (this.singleDatePicker || this.locale.durationFormat == null)
909
+ return;
910
+ if (!this.#endDate) {
911
+ this.container.querySelector(".drp-duration-label").innerHTML = "";
912
+ return;
913
+ }
914
+ if (typeof this.locale.durationFormat === "function") {
915
+ this.container.querySelector(".drp-duration-label").innerHTML = this.locale.durationFormat(this.#startDate, this.#endDate);
916
+ } else {
917
+ let duration = this.#endDate.plus({ milliseconds: 1 }).diff(this.#startDate).rescale().set({ milliseconds: 0 });
918
+ if (!this.timePicker)
919
+ duration = duration.set({ seconds: 0, minutes: 0, hours: 0 });
920
+ duration = duration.removeZeros();
921
+ if (typeof this.locale.durationFormat === "object") {
922
+ this.container.querySelector(".drp-duration-label").innerHTML = duration.toHuman(this.locale.durationFormat);
923
+ } else {
924
+ this.container.querySelector(".drp-duration-label").innerHTML = duration.toFormat(this.locale.durationFormat);
925
+ }
926
+ }
927
+ }
928
+ /**
929
+ * @typedef InputViolation
930
+ * @type {Object}
931
+ * @typedef {object} Violation
932
+ * @property {string} reason - The type/reason of violation
933
+ * @property {external:DateTime} old - Old value startDate/endDate
934
+ * @property {external:DateTime} new? - Corrected value of startDate/endDate if existing
935
+ * @typedef {object} NewDate
936
+ * @property {external:DateTime} newDate.startDate- Object with corrected values
937
+ * @property {external:DateTime} newDate.endDate - Object with corrected values
938
+ * @property {Violation[]} startDate - The constraints which violates the input
939
+ * @property {Violation[]?} endDate - The constraints which violates the input or `null` for singleDatePicker
940
+ * @property {NewDate} newDate - Object with corrected values
941
+ */
942
+ /**
943
+ * Validate `startDate` and `endDate` against `timePickerStepSize`, `minDate`, `maxDate`,
944
+ * `minSpan`, `maxSpan`, `invalidDate` and `invalidTime`.
945
+ * @param {Array} range - `[startDate, endDate]`<br>Range to be checked, defaults to current `startDate` and `endDate`
946
+ * @param {boolean} dipatch=false - If `true` then event "violate" is dispated.<br>
947
+ * If eventHandler returns `true`, then `null` is returned, otherwiese the object of violations.
948
+ * @emits "violate"
949
+ * @returns {InputViolation|null} - Object of violations and corrected values or `null` if no violation have been found
950
+ * @example
951
+ * options => {
952
+ * minDate: DateTime.now().minus({months: 3}).startOf('day'),
953
+ * maxDate: DateTime.now().minus({day: 3}).startOf('day'),
954
+ * minSpan: Duration.fromObject({days: 7}),
955
+ * maxSpan: Duration.fromObject({days: 70}),
956
+ * timePickerStepSize: Duration.fromObject({hours: 1})
957
+ * }
958
+ * const result = validateInput(DateTime.now(), DateTime.now().plus({day: 3}));
959
+ *
960
+ * result => {
961
+ * startDate: [
962
+ * { old: "2026-03-13T10:35:52", reason: "timePickerStepSize", new: "2026-03-13T11:00:00" },
963
+ * { old: "2026-03-13T11:00:00", reason: "maxDate", new: "2026-03-10T00:00:00" }
964
+ * ],
965
+ * endDate: {
966
+ * { old: "2026-03-16T10:35:52", reason: "stepSize", new: "2026-03-16T11:00:00" },
967
+ * { old: "2026-03-16T11:00:00", reason: "maxDate", new: "2026-03-10T00:00:00" },
968
+ * { old: "2026-03-10T00:00:00", reason: "minSpan", new: "2026-03-17T00:00:00" }
969
+ * ],
970
+ * newDate: {
971
+ * startDate: "2026-03-10T00:00:00",
972
+ * endDate: "2026-03-17T00:00:00"
973
+ * }
974
+ * }
975
+ */
976
+ validateInput(range, dipatch = false) {
977
+ let startDate = range == null ? this.#startDate : range[0];
978
+ let endDate = range == null ? this.#endDate : range[1];
979
+ if (startDate == null)
980
+ return null;
981
+ let result = { startDate: [] };
982
+ let violation = { old: startDate, reason: this.timePicker ? "timePickerStepSize" : "timePicker" };
983
+ if (this.timePicker) {
984
+ const secs = this.timePickerStepSize.as("seconds");
985
+ startDate = luxon.DateTime.fromSeconds(secs * Math.round(startDate.toSeconds() / secs));
986
+ violation.new = startDate;
987
+ if (!violation.new.equals(violation.old))
988
+ result.startDate.push(violation);
989
+ } else {
990
+ startDate = startDate.startOf("day");
991
+ }
992
+ const shiftStep = this.timePicker ? this.timePickerStepSize.as("seconds") : luxon.Duration.fromObject({ days: 1 }).as("seconds");
993
+ if (this.minDate && startDate < this.minDate) {
994
+ violation = { old: startDate, reason: "minDate" };
995
+ startDate = startDate.plus({ seconds: Math.trunc(this.minDate.diff(startDate).as("seconds") / shiftStep) * shiftStep });
996
+ if (startDate < this.minDate)
997
+ startDate = startDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
998
+ violation.new = startDate;
999
+ if (!violation.new.equals(violation.old))
1000
+ result.startDate.push(violation);
1001
+ } else if (this.maxDate && startDate > this.maxDate) {
1002
+ violation = { old: startDate, reason: "maxDate" };
1003
+ startDate = startDate.minus({ seconds: Math.trunc(startDate.diff(this.maxDate).as("seconds") / shiftStep) * shiftStep });
1004
+ if (startDate > this.maxDate)
1005
+ startDate = startDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1006
+ violation.new = startDate;
1007
+ if (!violation.new.equals(violation.old))
1008
+ result.startDate.push(violation);
1009
+ }
1010
+ let units = ["hour"];
1011
+ if (this.timePicker) {
1012
+ if (this.timePickerOpts.showMinutes)
1013
+ units.push("minute");
1014
+ if (this.timePickerOpts.showSeconds)
1015
+ units.push("second");
1016
+ if (!this.timePicker24Hour)
1017
+ units.push("ampm");
1018
+ }
1019
+ if (this.isInvalidDate(startDate))
1020
+ result.startDate.push({ old: startDate, reason: "isInvalidDate" });
1021
+ if (this.timePicker) {
1022
+ for (let unit of units) {
1023
+ if (this.isInvalidTime(startDate, unit, "start"))
1024
+ result.startDate.push({ old: startDate, reason: "isInvalidTime", unit });
1025
+ }
1026
+ }
1027
+ if (this.singleDatePicker) {
1028
+ if (result.startDate.length == 0)
1029
+ return null;
1030
+ if (dipatch) {
1031
+ let newValues = { startDate };
1032
+ const event = this.triggerEvent(this.#events.onViolate, { violation: result, newDate: newValues });
1033
+ if (event.defaultPrevented) {
1034
+ result.newDate = event.newDate;
1035
+ return result;
1036
+ }
1037
+ return result;
1038
+ } else {
1039
+ return result;
1040
+ }
1041
+ }
1042
+ if (endDate == null)
1043
+ return null;
1044
+ result.endDate = [];
1045
+ violation = { old: endDate, reason: this.timePicker ? "stepSize" : "timePicker" };
1046
+ if (this.timePicker) {
1047
+ const secs = this.timePickerStepSize.as("seconds");
1048
+ endDate = luxon.DateTime.fromSeconds(secs * Math.round(endDate.toSeconds() / secs));
1049
+ violation.new = endDate;
1050
+ if (!violation.new.equals(violation.old))
1051
+ result.endDate.push(violation);
1052
+ } else {
1053
+ endDate = endDate.endOf("day");
1054
+ }
1055
+ if (this.maxDate && endDate > this.maxDate) {
1056
+ violation = { old: endDate, reason: "maxDate" };
1057
+ endDate = endDate.minus({ seconds: Math.trunc(endDate.diff(this.maxDate).as("seconds") / shiftStep) * shiftStep });
1058
+ if (endDate > this.maxDate)
1059
+ endDate = endDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1060
+ violation.new = endDate;
1061
+ if (!violation.new.equals(violation.old))
1062
+ result.endDate.push(violation);
1063
+ } else if (this.minDate && endDate < this.minDate) {
1064
+ violation = { old: endDate, reason: "minDate" };
1065
+ endDate = endDate.plus({ seconds: Math.trunc(this.minDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
1066
+ if (endDate < this.minDate)
1067
+ endDate = endDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1068
+ violation.new = endDate;
1069
+ if (!violation.new.equals(violation.old))
1070
+ result.endDate.push(violation);
1071
+ }
1072
+ if (this.maxSpan) {
1073
+ const maxDate = startDate.plus(this.maxSpan);
1074
+ if (endDate > maxDate) {
1075
+ violation = { old: endDate, reason: "maxSpan" };
1076
+ endDate = endDate.minus({ seconds: Math.trunc(maxDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
1077
+ if (endDate > maxDate)
1078
+ endDate = endDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1079
+ violation.new = endDate;
1080
+ if (!violation.new.equals(violation.old))
1081
+ result.endDate.push(violation);
1082
+ }
1083
+ }
1084
+ if (this.minSpan) {
1085
+ const minDate = startDate.plus(this.defaultSpan ?? this.minSpan);
1086
+ if (endDate < minDate) {
1087
+ violation = { old: endDate, reason: "minSpan" };
1088
+ endDate = endDate.plus({ seconds: Math.trunc(minDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
1089
+ if (endDate < minDate)
1090
+ endDate = endDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
1091
+ violation.new = endDate;
1092
+ if (!violation.new.equals(violation.old))
1093
+ result.endDate.push(violation);
1094
+ }
1095
+ }
1096
+ if (this.isInvalidDate(endDate))
1097
+ result.endDate.push({ old: endDate, reason: "isInvalidDate" });
1098
+ if (this.timePicker) {
1099
+ for (let unit of units) {
1100
+ if (this.isInvalidTime(endDate, unit, "end"))
1101
+ result.endDate.push({ old: endDate, reason: "isInvalidTime", unit });
1102
+ }
1103
+ }
1104
+ if (result.startDate.length == 0 && result.endDate.length == 0)
1105
+ return null;
1106
+ if (dipatch) {
1107
+ let newValues = { startDate, endDate };
1108
+ const event = this.triggerEvent(this.#events.onViolate, { violation: result, newDate: newValues });
1109
+ if (event.defaultPrevented) {
1110
+ result.newDate = event.newDate;
1111
+ return result;
1112
+ }
1113
+ return result;
1114
+ } else {
1115
+ return result;
1116
+ }
1117
+ }
1118
+ /* #region Rendering */
1119
+ /**
1120
+ * Updates the picker when calendar is initiated or any date has been selected.
1121
+ * Could be useful after running {@link #DateRangePicker+setStartDate|setStartDate} or {@link #DateRangePicker+setEndDate|setRange}
1122
+ * @param {boolean} monthChange - If `true` then monthView changed
1123
+ * @emits "beforeRenderTimePicker"
1124
+ */
1125
+ updateView(monthChange) {
1126
+ if (this.timePicker) {
1127
+ this.triggerEvent(this.#events.onBeforeRenderTimePicker);
1128
+ this.renderTimePicker("start");
1129
+ this.renderTimePicker("end");
1130
+ this.container.querySelector(".calendar-time.end-time select").disabled = !this.#endDate;
1131
+ this.container.querySelector(".calendar-time.end-time select").classList.toggle("disabled", !this.#endDate);
1132
+ }
1133
+ this.updateLabel();
1134
+ this.updateMonthsInView();
1135
+ this.updateCalendars(monthChange);
1136
+ this.setApplyBtnState();
1137
+ }
1138
+ /**
1139
+ * Shows calendar months based on selected date values
1140
+ * @private
1141
+ */
1142
+ updateMonthsInView() {
1143
+ if (this.#endDate) {
1144
+ if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month && (this.#startDate.hasSame(this.leftCalendar.month, "month") || this.#startDate.hasSame(this.rightCalendar.month, "month")) && (this.#endDate.hasSame(this.leftCalendar.month, "month") || this.#endDate.hasSame(this.rightCalendar.month, "month")))
1145
+ return;
1146
+ this.leftCalendar.month = this.#startDate.startOf("month");
1147
+ if (!this.singleMonthView) {
1148
+ if (!this.linkedCalendars && !this.#endDate.hasSame(this.#startDate, "month")) {
1149
+ this.rightCalendar.month = this.#endDate.startOf("month");
1150
+ } else {
1151
+ this.rightCalendar.month = this.#startDate.startOf("month").plus({ month: 1 });
1152
+ }
1153
+ }
1154
+ } else {
1155
+ if (!this.#startDate && this.initalMonth) {
1156
+ this.leftCalendar.month = this.initalMonth;
1157
+ if (!this.singleMonthView)
1158
+ this.rightCalendar.month = this.initalMonth.plus({ month: 1 });
1159
+ } else {
1160
+ if (!this.leftCalendar.month.hasSame(this.#startDate, "month") && !this.rightCalendar.month.hasSame(this.#startDate, "month")) {
1161
+ this.leftCalendar.month = this.#startDate.startOf("month");
1162
+ this.rightCalendar.month = this.#startDate.startOf("month").plus({ month: 1 });
1163
+ }
1164
+ }
1165
+ }
1166
+ if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && !this.singleMonthView && this.rightCalendar.month > this.maxDate) {
1167
+ this.rightCalendar.month = this.maxDate.startOf("month");
1168
+ this.leftCalendar.month = this.maxDate.startOf("month").minus({ month: 1 });
1169
+ }
1170
+ }
1171
+ /**
1172
+ * Updates the selected day value from calendar with selected time values
1173
+ * @emits "beforeRenderCalendar"
1174
+ * @emits "monthViewChange"
1175
+ * @param {boolean} monthChange - If `true` then monthView changed
1176
+ * @private
1177
+ */
1178
+ updateCalendars(monthChange) {
1179
+ if (this.timePicker) {
1180
+ var hour, minute, second;
1181
+ if (this.#endDate) {
1182
+ hour = parseInt(this.container.querySelector(".start-time .hourselect").value, 10);
1183
+ if (isNaN(hour))
1184
+ hour = parseInt(this.container.querySelector(".start-time .hourselect option:last-child").value, 10);
1185
+ minute = 0;
1186
+ if (this.timePickerOpts.showMinutes) {
1187
+ minute = parseInt(this.container.querySelector(".start-time .minuteselect").value, 10);
1188
+ if (isNaN(minute))
1189
+ minute = parseInt(this.container.querySelector(".start-time .minuteselect option:last-child").value, 10);
1190
+ }
1191
+ second = 0;
1192
+ if (this.timePickerOpts.showSeconds) {
1193
+ second = parseInt(this.container.querySelector(".start-time .secondselect").value, 10);
1194
+ if (isNaN(second))
1195
+ second = parseInt(this.container.querySelector(".start-time .secondselect option:last-child").value, 10);
1196
+ }
1197
+ } else {
1198
+ hour = parseInt(this.container.querySelector(".end-time .hourselect").value, 10);
1199
+ if (isNaN(hour))
1200
+ hour = parseInt(this.container.querySelector(".end-time .hourselect option:last-child").value, 10);
1201
+ minute = 0;
1202
+ if (this.timePickerOpts.showMinutes) {
1203
+ minute = parseInt(this.container.querySelector(".end-time .minuteselect").value, 10);
1204
+ if (isNaN(minute))
1205
+ minute = parseInt(this.container.querySelector(".end-time .minuteselect option:last-child").value, 10);
1206
+ }
1207
+ second = 0;
1208
+ if (this.timePickerOpts.showSeconds) {
1209
+ second = parseInt(this.container.querySelector(".end-time .secondselect").value, 10);
1210
+ if (isNaN(second))
1211
+ second = parseInt(this.container.querySelector(".end-time .secondselect option:last-child").value, 10);
1212
+ }
1213
+ }
1214
+ this.leftCalendar.month = this.leftCalendar.month.set({ hour, minute, second });
1215
+ if (!this.singleMonthView)
1216
+ this.rightCalendar.month = this.rightCalendar.month.set({ hour, minute, second });
1217
+ } else {
1218
+ this.leftCalendar.month = this.leftCalendar.month.set({ hour: 0, minute: 0, second: 0 });
1219
+ if (!this.singleMonthView)
1220
+ this.rightCalendar.month = this.rightCalendar.month.set({ hour: 0, minute: 0, second: 0 });
1221
+ }
1222
+ this.triggerEvent(this.#events.onBeforeRenderCalendar);
1223
+ this.renderCalendar("left");
1224
+ this.renderCalendar("right");
1225
+ if (monthChange)
1226
+ this.triggerEvent(this.#events.onMonthViewChange);
1227
+ this.container.querySelectorAll(".ranges li").forEach((el) => {
1228
+ el.classList.remove("active");
1229
+ });
1230
+ if (this.#endDate == null) return;
1231
+ this.calculateChosenLabel();
1232
+ }
1233
+ /**
1234
+ * Renders the calendar month
1235
+ * @private
1236
+ */
1237
+ renderCalendar(side) {
1238
+ if (side === "right" && this.singleMonthView)
1239
+ return;
1240
+ var calendar = side === "left" ? this.leftCalendar : this.rightCalendar;
1241
+ if (calendar.month == null && !this.#startDate && this.initalMonth)
1242
+ calendar.month = this.initalMonth.startOf("month");
1243
+ const firstDay = calendar.month.startOf("month");
1244
+ const lastDay = calendar.month.endOf("month").startOf("day");
1245
+ var theDate = calendar.month.startOf("month").minus({ day: 1 });
1246
+ const time = { hour: calendar.month.hour, minute: calendar.month.minute, second: calendar.month.second };
1247
+ var calendar = [];
1248
+ calendar.firstDay = firstDay;
1249
+ calendar.lastDay = lastDay;
1250
+ for (var i = 0; i < 6; i++)
1251
+ calendar[i] = [];
1252
+ while (theDate.weekday != this.locale.firstDay)
1253
+ theDate = theDate.minus({ day: 1 });
1254
+ for (let col = 0, row = -1; col < 42; col++, theDate = theDate.plus({ day: 1 })) {
1255
+ if (col % 7 === 0)
1256
+ row++;
1257
+ calendar[row][col % 7] = theDate.set(time);
1258
+ }
1259
+ if (side === "left") {
1260
+ this.leftCalendar.calendar = calendar;
1261
+ } else {
1262
+ this.rightCalendar.calendar = calendar;
1263
+ }
1264
+ var minDate = side === "left" ? this.minDate : this.#startDate;
1265
+ var maxDate = this.maxDate;
1266
+ var html = "<tr>";
1267
+ if (this.showWeekNumbers || this.showISOWeekNumbers)
1268
+ html += "<th></th>";
1269
+ if ((!minDate || minDate < calendar.firstDay) && (!this.linkedCalendars || side === "left")) {
1270
+ html += '<th class="prev available"><span></span></th>';
1271
+ } else {
1272
+ html += "<th></th>";
1273
+ }
1274
+ var dateHtml = `${this.locale.monthNames[calendar.firstDay.month - 1]} ${calendar.firstDay.year}`;
1275
+ if (this.showDropdowns) {
1276
+ const maxYear = (maxDate && maxDate.year) ?? this.maxYear;
1277
+ const minYear = (minDate && minDate.year) ?? this.minYear;
1278
+ let div = this.externalStyle === "bulma" ? '<div class="select is-small mr-1">' : "";
1279
+ var monthHtml = `${div}<select class="monthselect">`;
1280
+ for (var m = 1; m <= 12; m++) {
1281
+ monthHtml += `<option value="${m}"${m === calendar.firstDay.month ? " selected" : ""}`;
1282
+ if (minDate && calendar.firstDay.set({ month: m }) < minDate.startOf("month") || maxDate && calendar.firstDay.set({ month: m }) > maxDate.endOf("month"))
1283
+ monthHtml += ` disabled`;
1284
+ monthHtml += `>${this.locale.monthNames[m - 1]}</option>`;
1285
+ }
1286
+ monthHtml += "</select>";
1287
+ if (this.externalStyle === "bulma")
1288
+ monthHtml += "</div>";
1289
+ div = this.externalStyle === "bulma" ? '<div class="select is-small ml-1">' : "";
1290
+ var yearHtml = `${div}<select class="yearselect">`;
1291
+ for (var y = minYear; y <= maxYear; y++)
1292
+ yearHtml += `<option value="${y}"${y === calendar.firstDay.year ? " selected" : ""}>${y}</option>`;
1293
+ yearHtml += "</select>";
1294
+ if (this.externalStyle === "bulma")
1295
+ yearHtml += "</div>";
1296
+ dateHtml = monthHtml + yearHtml;
1297
+ }
1298
+ html += '<th colspan="5" class="month">' + dateHtml + "</th>";
1299
+ if ((!maxDate || maxDate > calendar.lastDay.endOf("day")) && (!this.linkedCalendars || side === "right" || this.singleDatePicker || this.singleMonthView)) {
1300
+ html += '<th class="next available"><span></span></th>';
1301
+ } else {
1302
+ html += "<th></th>";
1303
+ }
1304
+ html += "</tr>";
1305
+ html += "<tr>";
1306
+ if (this.showWeekNumbers || this.showISOWeekNumbers)
1307
+ html += `<th class="week">${this.locale.weekLabel}</th>`;
1308
+ for (let [index, dayOfWeek] of this.locale.daysOfWeek.entries()) {
1309
+ html += "<th";
1310
+ if (this.weekendDayClasses && this.weekendDayClasses.length && luxon.Info.getWeekendWeekdays().includes(index + 1))
1311
+ html += ` class="${this.weekendDayClasses}"`;
1312
+ html += `>${dayOfWeek}</th>`;
1313
+ }
1314
+ html += "</tr>";
1315
+ this.container.querySelector(`.drp-calendar.${side} .calendar-table thead`).innerHTML = html;
1316
+ html = "";
1317
+ if (this.#endDate == null && this.maxSpan) {
1318
+ var maxLimit = this.#startDate.plus(this.maxSpan).endOf("day");
1319
+ if (!maxDate || maxLimit < maxDate) {
1320
+ maxDate = maxLimit;
1321
+ }
1322
+ }
1323
+ var minLimit;
1324
+ if (this.#endDate == null && this.minSpan)
1325
+ minLimit = this.#startDate.plus(this.minSpan).startOf("day");
1326
+ for (let row = 0; row < 6; row++) {
1327
+ html += "<tr>";
1328
+ if (this.showISOWeekNumbers)
1329
+ html += `<td class="week">${calendar[row][0].weekNumber}</td>`;
1330
+ else if (this.showWeekNumbers)
1331
+ html += `<td class="week">${calendar[row][0].localWeekNumber}</td>`;
1332
+ for (let col = 0; col < 7; col++) {
1333
+ var classes = [];
1334
+ if (this.todayClasses && this.todayClasses.length && calendar[row][col].hasSame(luxon.DateTime.now(), "day"))
1335
+ classes.push(this.todayClasses);
1336
+ if (this.weekendClasses && this.weekendClasses.length && luxon.Info.getWeekendWeekdays().includes(calendar[row][col].weekday))
1337
+ classes.push(this.weekendClasses);
1338
+ if (calendar[row][col].month != calendar[1][1].month)
1339
+ classes.push("off", "ends");
1340
+ if (this.minDate && calendar[row][col].startOf("day") < this.minDate.startOf("day"))
1341
+ classes.push("off", "disabled");
1342
+ if (maxDate && calendar[row][col].startOf("day") > maxDate.startOf("day"))
1343
+ classes.push("off", "disabled");
1344
+ if (minLimit && calendar[row][col].startOf("day") > this.#startDate.startOf("day") && calendar[row][col].startOf("day") < minLimit.startOf("day"))
1345
+ classes.push("off", "disabled");
1346
+ if (this.isInvalidDate(calendar[row][col]))
1347
+ classes.push("off", "disabled");
1348
+ if (this.#startDate != null && calendar[row][col].hasSame(this.#startDate, "day"))
1349
+ classes.push("active", "start-date");
1350
+ if (this.#endDate != null && calendar[row][col].hasSame(this.#endDate, "day"))
1351
+ classes.push("active", "end-date");
1352
+ if (this.#endDate != null && calendar[row][col] > this.#startDate && calendar[row][col] < this.#endDate)
1353
+ classes.push("in-range");
1354
+ var isCustom = this.isCustomDate(calendar[row][col]);
1355
+ if (isCustom !== false)
1356
+ typeof isCustom === "string" ? classes.push(isCustom) : classes.push(...isCustom);
1357
+ if (!classes.includes("disabled"))
1358
+ classes.push("available");
1359
+ html += `<td class="${classes.join(" ")}" data-title="r${row}c${col}">${calendar[row][col].day}</td>`;
1360
+ }
1361
+ html += "</tr>";
1362
+ }
1363
+ this.container.querySelector(`.drp-calendar.${side} .calendar-table tbody`).innerHTML = html;
1364
+ }
1365
+ /**
1366
+ * Renders the time pickers
1367
+ * @private
1368
+ * @emits "beforeRenderTimePicker"
1369
+ */
1370
+ renderTimePicker(side) {
1371
+ if (side === "end" && !this.#endDate) return;
1372
+ var selected, minLimit, minDate, maxDate = this.maxDate;
1373
+ let html = "";
1374
+ if (this.showWeekNumbers || this.showISOWeekNumbers)
1375
+ html += "<th></th>";
1376
+ if (this.maxSpan && (!this.maxDate || this.#startDate.plus(this.maxSpan) < this.maxDate))
1377
+ maxDate = this.#startDate.plus(this.maxSpan);
1378
+ if (this.minSpan && side === "end")
1379
+ minLimit = this.#startDate.plus(this.defaultSpan ?? this.minSpan);
1380
+ if (side === "start") {
1381
+ selected = this.#startDate;
1382
+ minDate = this.minDate;
1383
+ } else if (side === "end") {
1384
+ selected = this.#endDate;
1385
+ minDate = this.#startDate;
1386
+ let timeSelector = this.container.querySelector(".drp-calendar .calendar-time.end-time");
1387
+ if (timeSelector.innerHTML != "") {
1388
+ selected = selected.set({
1389
+ hour: !isNaN(selected.hour) ? selected.hour : timeSelector.querySelector(".hourselect option:selected").value,
1390
+ minute: !isNaN(selected.minute) ? selected.minute : timeSelector.querySelector(".minuteselect option:selected").value,
1391
+ second: !isNaN(selected.second) ? selected.second : timeSelector.querySelector(".secondselect option:selected").value
1392
+ });
1393
+ }
1394
+ if (selected < this.#startDate)
1395
+ selected = this.#startDate;
1396
+ if (maxDate && selected > maxDate)
1397
+ selected = maxDate;
1398
+ }
1399
+ html += `<th colspan="7">`;
1400
+ if (this.externalStyle === "bulma")
1401
+ html += '<div class="select is-small mx-1">';
1402
+ html += '<select class="hourselect">';
1403
+ const ampm = selected.toFormat("a", { locale: "en-US" });
1404
+ let start = 0;
1405
+ if (!this.timePicker24Hour)
1406
+ start = ampm === "AM" ? 1 : 13;
1407
+ for (var i = start; i <= start + 23; i += this.timePickerOpts.hourStep) {
1408
+ let time = selected.set({ hour: i % 24 });
1409
+ let disabled = false;
1410
+ if (minDate && time.set({ minute: 59 }) < minDate)
1411
+ disabled = true;
1412
+ if (maxDate && time.set({ minute: 0 }) > maxDate)
1413
+ disabled = true;
1414
+ if (minLimit && time.endOf("hour") < minLimit)
1415
+ disabled = true;
1416
+ if (!disabled && this.isInvalidTime(time, this.singleDatePicker ? null : side, "hour"))
1417
+ disabled = true;
1418
+ if (this.timePicker24Hour) {
1419
+ if (!disabled && i == selected.hour) {
1420
+ html += `<option value="${i}" selected>${i}</option>`;
1421
+ } else if (disabled) {
1422
+ html += `<option value="${i}" disabled class="disabled">${i}</option>`;
1423
+ } else {
1424
+ html += `<option value="${i}">${i}</option>`;
1425
+ }
1426
+ } else {
1427
+ const i_12 = luxon.DateTime.fromFormat(`${i % 24}`, "H").toFormat("h");
1428
+ const i_ampm = luxon.DateTime.fromFormat(`${i % 24}`, "H").toFormat("a", { locale: "en-US" });
1429
+ if (ampm == i_ampm) {
1430
+ if (!disabled && i == selected.hour) {
1431
+ html += `<option ampm="${i_ampm}" value="${i % 24}" selected>${i_12}</option>`;
1432
+ } else if (disabled) {
1433
+ html += `<option ampm="${i_ampm}" value="${i % 24}" disabled class="disabled">${i_12}</option>`;
1434
+ } else {
1435
+ html += `<option ampm="${i_ampm}" value="${i % 24}">${i_12}</option>`;
1436
+ }
1437
+ } else {
1438
+ html += `<option ampm="${i_ampm}" hidden="hidden" value="${i % 24}">${i_12}</option>`;
1439
+ }
1440
+ }
1441
+ }
1442
+ html += "</select>";
1443
+ if (this.externalStyle === "bulma")
1444
+ html += "</div>";
1445
+ if (this.timePickerOpts.showMinutes) {
1446
+ html += " : ";
1447
+ if (this.externalStyle === "bulma")
1448
+ html += '<div class="select is-small mx-1">';
1449
+ html += '<select class="minuteselect">';
1450
+ for (var i = 0; i < 60; i += this.timePickerOpts.minuteStep) {
1451
+ var padded = i < 10 ? "0" + i : i;
1452
+ let time = selected.set({ minute: i });
1453
+ let disabled = false;
1454
+ if (minDate && time.set({ second: 59 }) < minDate)
1455
+ disabled = true;
1456
+ if (maxDate && time.set({ second: 0 }) > maxDate)
1457
+ disabled = true;
1458
+ if (minLimit && time.endOf("minute") < minLimit)
1459
+ disabled = true;
1460
+ if (!disabled && this.isInvalidTime(time, this.singleDatePicker ? null : side, "minute"))
1461
+ disabled = true;
1462
+ if (selected.minute == i && !disabled) {
1463
+ html += `<option value="${i}" selected>${padded}</option>`;
1464
+ } else if (disabled) {
1465
+ html += `<option value="${i}" disabled class="disabled">${padded}</option>`;
1466
+ } else {
1467
+ html += `<option value="${i}">${padded}</option>`;
1468
+ }
1469
+ }
1470
+ html += "</select>";
1471
+ if (this.externalStyle === "bulma")
1472
+ html += "</div>";
1473
+ }
1474
+ if (this.timePickerOpts.showSeconds) {
1475
+ html += " : ";
1476
+ if (this.externalStyle === "bulma")
1477
+ html += '<div class="select is-small mx-1">';
1478
+ html += '<select class="secondselect">';
1479
+ for (var i = 0; i < 60; i += this.timePickerOpts.secondStep) {
1480
+ var padded = i < 10 ? "0" + i : i;
1481
+ let time = selected.set({ second: i });
1482
+ let disabled = false;
1483
+ if (minDate && time < minDate)
1484
+ disabled = true;
1485
+ if (maxDate && time > maxDate)
1486
+ disabled = true;
1487
+ if (minLimit && time < minLimit)
1488
+ disabled = true;
1489
+ if (!disabled && this.isInvalidTime(time, this.singleDatePicker ? null : side, "second"))
1490
+ disabled = true;
1491
+ if (selected.second == i && !disabled) {
1492
+ html += `<option value="${i}" selected>${padded}</option>`;
1493
+ } else if (disabled) {
1494
+ html += `<option value="${i}" disabled class="disabled">${padded}</option>`;
1495
+ } else {
1496
+ html += `<option value="${i}">${padded}</option>`;
1497
+ }
1498
+ }
1499
+ html += "</select>";
1500
+ if (this.externalStyle === "bulma")
1501
+ html += "</div>";
1502
+ }
1503
+ if (!this.timePicker24Hour) {
1504
+ if (this.externalStyle === "bulma")
1505
+ html += '<div class="select is-small mx-1">';
1506
+ html += '<select class="ampmselect">';
1507
+ var am_html = "";
1508
+ var pm_html = "";
1509
+ let disabled = false;
1510
+ if (minDate && selected.startOf("day") < minDate)
1511
+ disabled = true;
1512
+ if (maxDate && selected.endOf("day") > maxDate)
1513
+ disabled = true;
1514
+ if (minLimit && selected.startOf("day") < minLimit)
1515
+ disabled = true;
1516
+ if (disabled) {
1517
+ am_html = ' disabled class="disabled "';
1518
+ pm_html = ' disabled class="disabled"';
1519
+ } else {
1520
+ if (this.isInvalidTime(selected, this.singleDatePicker ? null : side, "ampm")) {
1521
+ if (selected.toFormat("a", { locale: "en-US" }) === "AM") {
1522
+ pm_html = ' disabled class="disabled"';
1523
+ } else {
1524
+ am_html = ' disabled class="disabled"';
1525
+ }
1526
+ }
1527
+ }
1528
+ html += `<option value="AM"${am_html}`;
1529
+ if (selected.toFormat("a", { locale: "en-US" }) === "AM")
1530
+ html += " selected";
1531
+ html += `>${luxon.Info.meridiems()[0]}</option><option value="PM"${pm_html}`;
1532
+ if (selected.toFormat("a", { locale: "en-US" }) === "PM")
1533
+ html += " selected";
1534
+ html += `>${luxon.Info.meridiems()[1]}</option>`;
1535
+ html += "</select>";
1536
+ if (this.externalStyle === "bulma")
1537
+ html += "</div>";
1538
+ }
1539
+ html += "</div></th>";
1540
+ this.container.querySelector(`.drp-calendar .calendar-time.${side}-time`).innerHTML = html;
1541
+ }
1542
+ /**
1543
+ * Disable the `Apply` button if no date value is selected
1544
+ * @private
1545
+ */
1546
+ setApplyBtnState() {
1547
+ const state = this.singleDatePicker || this.#endDate && this.#startDate <= this.#endDate;
1548
+ this.container.querySelector("button.applyBtn").disabled = !state;
1549
+ }
1550
+ /* #endregion */
1551
+ /* #region Move/Show/Hide */
1552
+ /**
1553
+ * Place the picker at the right place in the document
1554
+ */
1555
+ move() {
1556
+ let parentOffset = { top: 0, left: 0 };
1557
+ let containerTop;
1558
+ let containerLeft;
1559
+ let drops = this.drops;
1560
+ let parentRightEdge = window.innerWidth;
1561
+ if (!this.parentEl.matches("body")) {
1562
+ parentOffset = {
1563
+ top: offset(this.parentEl).top - this.parentEl.scrollTop(),
1564
+ left: offset(this.parentEl).left - this.parentEl.scrollLeft()
1565
+ };
1566
+ parentRightEdge = this.parentEl[0].clientWidth + offset(this.parentEl).left;
1567
+ }
1568
+ switch (this.drops) {
1569
+ case "auto":
1570
+ containerTop = offset(this.element).top + outerHeight(this.element) - parentOffset.top;
1571
+ if (containerTop + outerHeight(this.container) >= this.parentEl.scrollHeight) {
1572
+ containerTop = offset(this.element).top - outerHeight(this.container) - parentOffset.top;
1573
+ drops = "up";
1574
+ }
1575
+ break;
1576
+ case "up":
1577
+ containerTop = offset(this.element).top - outerHeight(this.container) - parentOffset.top;
1578
+ break;
1579
+ case "down":
1580
+ containerTop = offset(this.element).top + outerHeight(this.element) - parentOffset.top;
1581
+ break;
1582
+ default:
1583
+ console.error(`Option drops '${drops}' not defined`);
1584
+ break;
1585
+ }
1586
+ for (const [key2, value] of Object.entries({ top: 0, left: 0, right: "auto" }))
1587
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1588
+ const containerWidth = outerWidth(this.container);
1589
+ this.container.classList.toggle("drop-up", drops === "up");
1590
+ switch (this.opens) {
1591
+ case "left":
1592
+ const containerRight = parentRightEdge - offset(this.element).left - outerWidth(this.element);
1593
+ if (containerWidth + containerRight > window.innerWidth) {
1594
+ for (const [key2, value] of Object.entries({ top: containerTop, right: "auto", left: 9 }))
1595
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1596
+ } else {
1597
+ for (const [key2, value] of Object.entries({ top: containerTop, right: containerRight, left: "auto" }))
1598
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1599
+ }
1600
+ break;
1601
+ case "center":
1602
+ containerLeft = offset(this.element).left - parentOffset.left + outerWidth(this.element) / 2 - containerWidth / 2;
1603
+ if (containerLeft < 0) {
1604
+ for (const [key2, value] of Object.entries({ top: containerTop, right: "auto", left: 9 }))
1605
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1606
+ } else if (containerLeft + containerWidth > window.innerWidth) {
1607
+ for (const [key2, value] of Object.entries({ top: containerTop, left: "auto", right: 0 }))
1608
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1609
+ } else {
1610
+ for (const [key2, value] of Object.entries({ top: containerTop, left: containerLeft, right: "auto" }))
1611
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1612
+ }
1613
+ break;
1614
+ case "right":
1615
+ containerLeft = offset(this.element).left - parentOffset.left;
1616
+ if (containerLeft + containerWidth > window.innerWidth) {
1617
+ for (const [key2, value] of Object.entries({ top: containerTop, left: "auto", right: 0 }))
1618
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1619
+ } else {
1620
+ for (const [key2, value] of Object.entries({ top: `${containerTop}px`, left: containerLeft, right: "auto" }))
1621
+ this.container.style[key2] = typeof value === "number" && value > 0 ? `${value}px` : value;
1622
+ }
1623
+ break;
1624
+ default:
1625
+ console.error(`Option opens '${this.opens}' not defined`);
1626
+ break;
1627
+ }
1628
+ }
1629
+ /**
1630
+ * Shows the picker
1631
+ * @emits "show"
1632
+ */
1633
+ show() {
1634
+ if (this.isShowing) return;
1635
+ document.addEventListener("mousedown", this.#outsideClickProxy);
1636
+ document.addEventListener("touchend", this.#outsideClickProxy);
1637
+ document.addEventListener("click", this.#dropdownClickWrapper);
1638
+ document.addEventListener("focusin", this.#outsideClickProxy);
1639
+ window.addEventListener("resize", this.#onResizeProxy);
1640
+ this.oldStartDate = this.#startDate;
1641
+ this.oldEndDate = this.#endDate;
1642
+ this.updateView(false);
1643
+ this.container.style.display = "block";
1644
+ this.move();
1645
+ this.triggerEvent(this.#events.onShow);
1646
+ this.isShowing = true;
1647
+ }
1648
+ /**
1649
+ * Hides the picker
1650
+ * @emits "beforeHide"
1651
+ * @emits "hide"
1652
+ */
1653
+ hide() {
1654
+ if (!this.isShowing) return;
1655
+ if (!this.#endDate) {
1656
+ this.#startDate = this.oldStartDate;
1657
+ this.#endDate = this.oldEndDate;
1658
+ }
1659
+ if (!this.#startDate.equals(this.oldStartDate) || !this.#endDate.equals(this.oldEndDate))
1660
+ this.callback(this.startDate, this.endDate, this.chosenLabel);
1661
+ this.updateElement();
1662
+ const event = this.triggerEvent(this.#events.onBeforeHide);
1663
+ if (event.defaultPrevented)
1664
+ return;
1665
+ document.removeEventListener("mousedown", this.#outsideClickProxy);
1666
+ document.removeEventListener("touchend", this.#outsideClickProxy);
1667
+ document.removeEventListener("focusin", this.#outsideClickProxy);
1668
+ document.removeEventListener("click", this.#dropdownClickWrapper);
1669
+ window.removeEventListener("resize", this.#onResizeProxy);
1670
+ this.container.style.display = "none";
1671
+ this.triggerEvent(this.#events.onHide);
1672
+ this.isShowing = false;
1673
+ }
1674
+ /**
1675
+ * Toggles visibility of the picker
1676
+ */
1677
+ toggle() {
1678
+ if (this.isShowing) {
1679
+ this.hide();
1680
+ } else {
1681
+ this.show();
1682
+ }
1683
+ }
1684
+ /**
1685
+ * Shows calendar when user selects "Custom Ranges"
1686
+ * @emits "showCalendar"
1687
+ */
1688
+ showCalendars() {
1689
+ this.container.classList.add("show-calendar");
1690
+ this.move();
1691
+ this.triggerEvent(this.#events.onShowCalendar);
1692
+ }
1693
+ /**
1694
+ * Hides calendar when user selects a predefined range
1695
+ * @emits "hideCalendar"
1696
+ */
1697
+ hideCalendars() {
1698
+ this.container.classList.remove("show-calendar");
1699
+ this.triggerEvent(this.#events.onHideCalendar);
1700
+ }
1701
+ /* #endregion */
1702
+ /* #region Handle mouse related events */
1703
+ /**
1704
+ * Closes the picker when user clicks outside
1705
+ * @param {external:Event} e - The Event target
1706
+ * @emits "outsideClick"
1707
+ * @private
1708
+ */
1709
+ outsideClick(e) {
1710
+ const target = e.target;
1711
+ function closest2(el, selector) {
1712
+ let parent = el.parentElement;
1713
+ while (parent) {
1714
+ if (parent == selector)
1715
+ return parent;
1716
+ parent = parent.parentElement;
1717
+ }
1718
+ return null;
1719
+ }
1720
+ if (
1721
+ // ie modal dialog fix
1722
+ e.type === "focusin" || closest2(target, this.element) || closest2(target, this.container) || target.closest(".calendar-table")
1723
+ ) return;
1724
+ const event = this.triggerEvent(this.#events.onOutsideClick);
1725
+ if (event.defaultPrevented)
1726
+ return;
1727
+ if (this.onOutsideClick === "cancel") {
1728
+ this.#startDate = this.oldStartDate;
1729
+ this.#endDate = this.oldEndDate;
1730
+ }
1731
+ this.hide();
1732
+ }
1733
+ /**
1734
+ * Move calendar to previous month
1735
+ * @param {external:Event} e - The Event target
1736
+ * @private
1737
+ */
1738
+ clickPrev(e) {
1739
+ let cal = e.target.closest(".drp-calendar");
1740
+ if (cal.classList.contains("left")) {
1741
+ this.leftCalendar.month = this.leftCalendar.month.minus({ month: 1 });
1742
+ if (this.linkedCalendars && !this.singleMonthView)
1743
+ this.rightCalendar.month = this.rightCalendar.month.minus({ month: 1 });
1744
+ } else {
1745
+ this.rightCalendar.month = this.rightCalendar.month.minus({ month: 1 });
1746
+ }
1747
+ this.updateCalendars(true);
1748
+ }
1749
+ /**
1750
+ * Move calendar to next month
1751
+ * @param {external:Event} e - The Event target
1752
+ * @private
1753
+ */
1754
+ clickNext(e) {
1755
+ let cal = e.target.closest(".drp-calendar");
1756
+ if (cal.classList.contains("left")) {
1757
+ this.leftCalendar.month = this.leftCalendar.month.plus({ month: 1 });
1758
+ } else {
1759
+ this.rightCalendar.month = this.rightCalendar.month.plus({ month: 1 });
1760
+ if (this.linkedCalendars)
1761
+ this.leftCalendar.month = this.leftCalendar.month.plus({ month: 1 });
1762
+ }
1763
+ this.updateCalendars(true);
1764
+ }
1765
+ /**
1766
+ * User hovers over date values
1767
+ * @param {external:Event} e - The Event target
1768
+ * @private
1769
+ */
1770
+ hoverDate(e) {
1771
+ if (!e.target.classList.contains("available")) return;
1772
+ let title = e.target.dataset.title;
1773
+ const row = title.substring(1, 2);
1774
+ const col = title.substring(3, 4);
1775
+ const cal = e.target(closest, ".drp-calendar");
1776
+ var date = cal.classList.contains("left") ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1777
+ const leftCalendar = this.leftCalendar;
1778
+ const rightCalendar = this.rightCalendar;
1779
+ const startDate = this.#startDate;
1780
+ const initalMonth = this.initalMonth;
1781
+ if (!this.#endDate) {
1782
+ this.container.querySelectorAll(".drp-calendar tbody td").forEach((el) => {
1783
+ if (el.classList.contains("week")) return;
1784
+ const title2 = el.dataset.title;
1785
+ const row2 = title2.substring(1, 2);
1786
+ const col2 = title2.substring(3, 4);
1787
+ const cal2 = el.closest(".drp-calendar");
1788
+ const dt = cal2.classList.contains("left") ? leftCalendar.calendar[row2][col2] : rightCalendar.calendar[row2][col2];
1789
+ if (!startDate && initalMonth) {
1790
+ el.classList.remove("in-range");
1791
+ } else {
1792
+ el.classList.toggle("in-range", dt > startDate && dt < date || dt.hasSame(date, "day"));
1793
+ }
1794
+ });
1795
+ }
1796
+ }
1797
+ /**
1798
+ * User hovers over ranges
1799
+ * @param {external:Event} e - The Event target
1800
+ * @private
1801
+ */
1802
+ hoverRange(e) {
1803
+ const label = e.target.dataset.rangeKey;
1804
+ const previousDates = [this.#startDate, this.#endDate];
1805
+ const dates = this.ranges[label] ?? [this.#startDate, this.#endDate];
1806
+ const leftCalendar = this.leftCalendar;
1807
+ const rightCalendar = this.rightCalendar;
1808
+ this.container.querySelectorAll(".drp-calendar tbody td").forEach((el) => {
1809
+ if (el.classList.contains("week")) return;
1810
+ const title = el.dataset.ttitle;
1811
+ const row = title.substring(1, 2);
1812
+ const col = title.substring(3, 4);
1813
+ const cal = el.closest(".drp-calendar");
1814
+ const dt = cal.classList.contains("left") ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col];
1815
+ el.classList.toggle("start-hover", dt.hasSame(dates[0], "day"));
1816
+ el.classList.toggle("start-date", dt.hasSame(previousDates[0], "day"));
1817
+ el.classList.toggle("end-hover", dt.hasSame(dates[1], "day"));
1818
+ el.classList.toggle("end-date", previousDates[1] != null && dt.hasSame(previousDates[1], "day"));
1819
+ el.classList.toggle("range-hover", dt.startOf("day") >= dates[0].startOf("day") && dt.startOf("day") <= dates[1].startOf("day"));
1820
+ el.classList.toggle("in-range", dt.startOf("day") >= previousDates[0].startOf("day") && previousDates[1] != null && dt.startOf("day") <= previousDates[1].startOf("day"));
1821
+ });
1822
+ }
1823
+ /**
1824
+ * User leave ranges, remove hightlight from dates
1825
+ * @private
1826
+ */
1827
+ leaveRange() {
1828
+ this.container.querySelectorAll(".drp-calendar tbody td").forEach((el) => {
1829
+ if (el.classList.contains("week")) return;
1830
+ el.classList.remove("start-hover");
1831
+ el.classList.remove("end-hover");
1832
+ el.classList.remove("range-hover");
1833
+ });
1834
+ }
1835
+ /* #endregion */
1836
+ /* #region Select values by Mouse */
1837
+ /**
1838
+ * Set date values after user selected a date
1839
+ * @param {external:Event} e - The Event target
1840
+ * @private
1841
+ */
1842
+ clickRange(e) {
1843
+ let label = e.target.getAttribute("data-range-key");
1844
+ this.chosenLabel = label;
1845
+ if (label == this.locale.customRangeLabel) {
1846
+ this.showCalendars();
1847
+ } else {
1848
+ let newDate = this.ranges[label];
1849
+ const monthChange = !this.#startDate.hasSame(newDate[0], "month") || !this.#endDate.hasSame(newDate[1], "month");
1850
+ this.#startDate = newDate[0];
1851
+ this.#endDate = newDate[1];
1852
+ if (!this.timePicker) {
1853
+ this.#startDate.startOf("day");
1854
+ this.#endDate.endOf("day");
1855
+ }
1856
+ if (!this.alwaysShowCalendars)
1857
+ this.hideCalendars();
1858
+ const event = this.triggerEvent(this.#events.onBeforeHide);
1859
+ if (event.defaultPrevented)
1860
+ this.updateView(monthChange);
1861
+ this.clickApply();
1862
+ }
1863
+ }
1864
+ /**
1865
+ * User clicked a date
1866
+ * @param {external:Event} e - The Event target
1867
+ * @emits "dateChange"
1868
+ * @private
1869
+ */
1870
+ clickDate(e) {
1871
+ if (!e.target.classList.contains("available")) return;
1872
+ let title = e.target.dataset.title;
1873
+ let row = title.substring(1, 2);
1874
+ let col = title.substring(3, 4);
1875
+ let cal = e.target.closest(".drp-calendar");
1876
+ let date = cal.classList.contains("left") ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1877
+ let side;
1878
+ if (this.#endDate || !this.#startDate || date < this.#startDate.startOf("day")) {
1879
+ if (this.timePicker) {
1880
+ let hour = parseInt(this.container.querySelector(".start-time .hourselect").value, 10);
1881
+ if (isNaN(hour))
1882
+ hour = parseInt(this.container.querySelector(".start-time .hourselect option:last-child").value, 10);
1883
+ let minute = 0;
1884
+ if (this.timePickerOpts.showMinutes) {
1885
+ minute = parseInt(this.container.querySelector(".start-time .minuteselect").value, 10);
1886
+ if (isNaN(minute))
1887
+ minute = parseInt(this.container.querySelector(".start-time .minuteselect option:last-child").value, 10);
1888
+ }
1889
+ let second = 0;
1890
+ if (this.timePickerOpts.showSeconds) {
1891
+ second = parseInt(this.container.querySelector(".start-time .secondselect").value, 10);
1892
+ if (isNaN(second))
1893
+ second = parseInt(this.container.querySelector(".start-time .secondselect option:last-child").value, 10);
1894
+ }
1895
+ date = date.set({ hour, minute, second });
1896
+ } else {
1897
+ date = date.startOf("day");
1898
+ }
1899
+ this.#endDate = null;
1900
+ this.#startDate = date;
1901
+ side = "start";
1902
+ } else if (!this.#endDate && date < this.#startDate) {
1903
+ this.#endDate = this.#startDate;
1904
+ side = "end";
1905
+ } else {
1906
+ if (this.timePicker) {
1907
+ let hour = parseInt(this.container.querySelector(".end-time .hourselect").value, 10);
1908
+ if (isNaN(hour))
1909
+ hour = parseInt(this.container.querySelector(".end-time .hourselect option:last-child").value, 10);
1910
+ let minute = 0;
1911
+ if (this.timePickerOpts.showMinutes) {
1912
+ minute = parseInt(this.container.querySelector(".end-time .minuteselect").value, 10);
1913
+ if (isNaN(minute))
1914
+ minute = parseInt(this.container.querySelector(".end-time .minuteselect option:last-child").value, 10);
1915
+ }
1916
+ let second = 0;
1917
+ if (this.timePickerOpts.showSeconds) {
1918
+ second = parseInt(this.container.querySelector(".end-time .secondselect").value, 10);
1919
+ if (isNaN(second))
1920
+ second = parseInt(this.container.querySelector(".end-time .secondselect option:last-child").value, 10);
1921
+ }
1922
+ date = date.set({ hour, minute, second });
1923
+ } else {
1924
+ date = date.endOf("day");
1925
+ }
1926
+ this.#endDate = date;
1927
+ if (this.autoApply) {
1928
+ this.calculateChosenLabel();
1929
+ this.clickApply();
1930
+ }
1931
+ side = "end";
1932
+ }
1933
+ if (this.singleDatePicker) {
1934
+ this.#endDate = this.#startDate;
1935
+ if (!this.timePicker && this.autoApply)
1936
+ this.clickApply();
1937
+ side = null;
1938
+ }
1939
+ this.updateView(false);
1940
+ e.stopPropagation();
1941
+ if (this.autoUpdateInput)
1942
+ this.updateElement();
1943
+ this.triggerEvent(this.#events.onDateChange, { side });
1944
+ }
1945
+ /**
1946
+ * Hightlight selected predefined range in calendar
1947
+ * @private
1948
+ */
1949
+ calculateChosenLabel() {
1950
+ if (Object.keys(this.ranges).length === 0)
1951
+ return;
1952
+ let customRange = true;
1953
+ let unit = this.timePicker ? "hour" : "day";
1954
+ if (this.timePicker) {
1955
+ if (this.timePickerOpts.showMinutes) {
1956
+ unit = "minute";
1957
+ } else if (this.timePickerOpts.showSeconds) {
1958
+ unit = "second";
1959
+ }
1960
+ }
1961
+ for (const [key2, [start, end]] of Object.entries(this.ranges)) {
1962
+ if (this.#startDate.startOf(unit).equals(start.startOf(unit)) && this.#endDate.startOf(unit).equals(end.startOf(unit))) {
1963
+ customRange = false;
1964
+ const range = this.container.querySelector(`.ranges li[data-range-key="${key2}"]`);
1965
+ this.chosenLabel = key2;
1966
+ range.classList.add("active");
1967
+ break;
1968
+ }
1969
+ }
1970
+ if (customRange) {
1971
+ if (this.showCustomRangeLabel) {
1972
+ const range = this.container.querySelector(".ranges li:last-child");
1973
+ this.chosenLabel = range.dataset.rangeKey;
1974
+ range.classList.add("active");
1975
+ } else {
1976
+ this.chosenLabel = null;
1977
+ }
1978
+ this.showCalendars();
1979
+ }
1980
+ }
1981
+ /**
1982
+ * User clicked a time
1983
+ * @param {external:Event} e - The Event target
1984
+ * @emits "timeChange"
1985
+ * @private
1986
+ */
1987
+ timeChanged(e) {
1988
+ const time = e.target.closest(".calendar-time");
1989
+ const side = time.classList.contains("start-time") ? "start" : "end";
1990
+ var hour = parseInt(time.querySelector(".hourselect").value, 10);
1991
+ if (isNaN(hour))
1992
+ hour = parseInt(time.querySelector(".hourselect option:last-child").value, 10);
1993
+ if (!this.timePicker24Hour) {
1994
+ const ampm = time.querySelector(".ampmselect").value;
1995
+ if (ampm == null)
1996
+ time.querySelector(".ampmselect option:last-child").value;
1997
+ if (ampm != luxon.DateTime.fromFormat(`${hour}`, "H").toFormat("a", { locale: "en-US" })) {
1998
+ time.querySelectorAll(".hourselect > option").forEach((el) => {
1999
+ el.hidden = !el.hidden;
2000
+ });
2001
+ const h = luxon.DateTime.fromFormat(`${hour}`, "H").toFormat("h");
2002
+ hour = luxon.DateTime.fromFormat(`${h}${ampm}`, "ha", { locale: "en-US" }).hour;
2003
+ }
2004
+ }
2005
+ var minute = 0;
2006
+ if (this.timePickerOpts.showMinutes) {
2007
+ minute = parseInt(time.querySelector(".minuteselect").value, 10);
2008
+ if (isNaN(minute))
2009
+ minute = parseInt(time.querySelector(".minuteselect option:last-child").value, 10);
2010
+ }
2011
+ var second = 0;
2012
+ if (this.timePickerOpts.showSeconds) {
2013
+ second = parseInt(time.querySelector(".secondselect").value, 10);
2014
+ if (isNaN(second))
2015
+ second = parseInt(time.querySelector(".secondselect option:last-child").value, 10);
2016
+ }
2017
+ if (side === "start") {
2018
+ if (this.#startDate)
2019
+ this.#startDate = this.#startDate.set({ hour, minute, second });
2020
+ if (this.singleDatePicker) {
2021
+ this.#endDate = this.#startDate;
2022
+ } else if (this.#endDate && this.#endDate.hasSame(this.#startDate, "day") && this.#endDate < this.#startDate) {
2023
+ this.#endDate = this.#startDate;
2024
+ }
2025
+ } else if (this.#endDate) {
2026
+ this.#endDate = this.#endDate.set({ hour, minute, second });
2027
+ }
2028
+ this.updateCalendars(false);
2029
+ this.setApplyBtnState();
2030
+ this.triggerEvent(this.#events.onBeforeRenderTimePicker);
2031
+ this.renderTimePicker("start");
2032
+ this.renderTimePicker("end");
2033
+ if (this.autoUpdateInput)
2034
+ this.updateElement();
2035
+ this.triggerEvent(this.#events.onTimeChange, { side: this.singleDatePicker ? null : side });
2036
+ }
2037
+ /**
2038
+ * Calender month moved
2039
+ * @param {external:Event} e - The Event target
2040
+ * @private
2041
+ */
2042
+ monthOrYearChanged(e) {
2043
+ const isLeft = e.target.closest(".drp-calendar").classList.contains("left");
2044
+ const leftOrRight = isLeft ? "left" : "right";
2045
+ const cal = this.container.querySelector(`.drp-calendar.${leftOrRight}`);
2046
+ let month = parseInt(cal.querySelector(".monthselect").value, 10);
2047
+ let year = cal.querySelector(".yearselect").value;
2048
+ let monthChange = false;
2049
+ if (!isLeft) {
2050
+ if (year < this.#startDate.year || year == this.#startDate.year && month < this.#startDate.month) {
2051
+ month = this.#startDate.month;
2052
+ year = this.#startDate.year;
2053
+ }
2054
+ }
2055
+ if (this.minDate) {
2056
+ if (year < this.minDate.year || year == this.minDate.year && month < this.minDate.month) {
2057
+ month = this.minDate.month;
2058
+ year = this.minDate.year;
2059
+ }
2060
+ }
2061
+ if (this.maxDate) {
2062
+ if (year > this.maxDate.year || year == this.maxDate.year && month > this.maxDate.month) {
2063
+ month = this.maxDate.month;
2064
+ year = this.maxDate.year;
2065
+ }
2066
+ }
2067
+ if (isLeft) {
2068
+ monthChange = !luxon.DateTime.fromObject({ year, month }).hasSame(this.leftCalendar.month, "month");
2069
+ this.leftCalendar.month = this.leftCalendar.month.set({ year, month });
2070
+ if (this.linkedCalendars)
2071
+ this.rightCalendar.month = this.leftCalendar.month.plus({ month: 1 });
2072
+ } else {
2073
+ monthChange = !luxon.DateTime.fromObject({ year, month }).hasSame(this.leftCalendar.month, "month");
2074
+ this.rightCalendar.month = this.rightCalendar.month.set({ year, month });
2075
+ if (this.linkedCalendars)
2076
+ this.leftCalendar.month = this.rightCalendar.month.minus({ month: 1 });
2077
+ }
2078
+ this.updateCalendars(monthChange);
2079
+ }
2080
+ /**
2081
+ * User clicked `Apply` button
2082
+ * @emits "apply"
2083
+ * @private
2084
+ */
2085
+ clickApply() {
2086
+ this.hide();
2087
+ this.triggerEvent(this.#events.onApply);
2088
+ }
2089
+ /**
2090
+ * User clicked `Cancel` button
2091
+ * @emits "cancel"
2092
+ * @private
2093
+ */
2094
+ clickCancel() {
2095
+ this.#startDate = this.oldStartDate;
2096
+ this.#endDate = this.oldEndDate;
2097
+ this.hide();
2098
+ this.triggerEvent(this.#events.onCancel);
2099
+ }
2100
+ /* #endregion */
2101
+ /**
2102
+ * Update the picker with value from `<input>` element.<br>
2103
+ * Input values must be given in format of `locale.format`. Invalid values are handles by `violate` Event
2104
+ * @emits "inputChange"
2105
+ * @private
2106
+ */
2107
+ elementChanged() {
2108
+ if (!this.isInputText) return;
2109
+ if (!this.element.value.length) return;
2110
+ const format = typeof this.locale.format === "string" ? this.locale.format : luxon.DateTime.parseFormatForOpts(this.locale.format);
2111
+ const dateString = this.element.value.split(this.locale.separator);
2112
+ let monthChange = false;
2113
+ if (this.singleDatePicker) {
2114
+ let newDate = luxon.DateTime.fromFormat(this.element.value, format, { locale: luxon.DateTime.now().locale });
2115
+ const oldDate = this.#startDate;
2116
+ if (!newDate.isValid || oldDate.equals(newDate))
2117
+ return;
2118
+ const violations = this.validateInput([newDate, null], true);
2119
+ if (violations != null) {
2120
+ if (violations.newDate != null) {
2121
+ newDate = violations.newDate.startDate;
2122
+ } else {
2123
+ return;
2124
+ }
2125
+ }
2126
+ monthChange = !this.#startDate.hasSame(newDate, "month");
2127
+ this.#startDate = newDate;
2128
+ this.#endDate = this.#startDate;
2129
+ if (!this.timePicker) {
2130
+ this.#startDate = this.#startDate.startOf("day");
2131
+ this.#endDate = this.#endDate.endOf("day");
2132
+ }
2133
+ } else if (!this.singleDatePicker && dateString.length === 2) {
2134
+ const newDate = [0, 1].map((i) => luxon.DateTime.fromFormat(dateString[i], format, { locale: luxon.DateTime.now().locale }));
2135
+ const oldDate = [this.#startDate, this.#endDate];
2136
+ if (!newDate[0].isValid || !newDate[1].isValid || (oldDate[0].equals(newDate[0]) && oldDate[1].equals(newDate[1]) || newDate[0] > newDate[1]))
2137
+ return;
2138
+ const violations = this.validateInput([newDate[0], newDate[1]], true);
2139
+ if (violations != null) {
2140
+ if (violations.newDate != null) {
2141
+ newDate[0] = violations.newDate.startDate;
2142
+ newDate[1] = violations.newDate.endDate;
2143
+ } else {
2144
+ return;
2145
+ }
2146
+ }
2147
+ monthChange = !this.#startDate.hasSame(newDate[0], "month") || !this.#endDate.hasSame(newDate[1], "month");
2148
+ this.#startDate = newDate[0];
2149
+ this.#endDate = newDate[1];
2150
+ if (!this.timePicker) {
2151
+ this.#startDate = this.#startDate.startOf("day");
2152
+ this.#endDate = this.#endDate.endOf("day");
2153
+ }
2154
+ } else {
2155
+ return;
2156
+ }
2157
+ this.updateView(monthChange);
2158
+ this.updateElement();
2159
+ this.triggerEvent(this.#events.onInputChange);
2160
+ }
2161
+ /**
2162
+ * Handles key press, IE 11 compatibility
2163
+ * @param {external:Event} e - The Event target
2164
+ * @private
2165
+ */
2166
+ keydown(e) {
2167
+ if ([9, 11].includes(e.keyCode))
2168
+ this.hide();
2169
+ if (e.keyCode === 27) {
2170
+ e.preventDefault();
2171
+ e.stopPropagation();
2172
+ this.hide();
2173
+ }
2174
+ }
2175
+ /**
2176
+ * Update attached `<input>` element with selected value
2177
+ * @emits external:change
2178
+ */
2179
+ updateElement() {
2180
+ if (this.#startDate == null && this.initalMonth)
2181
+ return;
2182
+ if (this.isInputText) {
2183
+ let newValue = this.formatDate(this.#startDate);
2184
+ if (!this.singleDatePicker) {
2185
+ newValue += this.locale.separator;
2186
+ if (this.#endDate)
2187
+ newValue += this.formatDate(this.#endDate);
2188
+ }
2189
+ this.updateAltInput();
2190
+ if (newValue !== this.element.value) {
2191
+ this.element.value = newValue;
2192
+ this.element.dispatchEvent(new Event("change", { bubbles: true }));
2193
+ }
2194
+ } else {
2195
+ this.updateAltInput();
2196
+ }
2197
+ }
2198
+ /**
2199
+ * Update altInput `<input>` element with selected value
2200
+ */
2201
+ updateAltInput() {
2202
+ if (this.altInput == null)
2203
+ return;
2204
+ if (this.altFormat == null) {
2205
+ let precision = "day";
2206
+ if (this.timePicker) {
2207
+ if (this.timePickerOpts.showSeconds) {
2208
+ precision = "second";
2209
+ } else if (this.timePickerOpts.showMinutes) {
2210
+ precision = "minute";
2211
+ } else {
2212
+ precision = "hour";
2213
+ }
2214
+ }
2215
+ const startDate = this.#startDate.toISO({ format: "basic", precision, includeOffset: false });
2216
+ (this.singleDatePicker ? this.altInput : this.altInput[0]).value = startDate;
2217
+ if (!this.singleDatePicker && this.#endDate) {
2218
+ const endDate = this.#endDate.toISO({ format: "basic", precision, includeOffset: false });
2219
+ this.altInput[1].value = endDate;
2220
+ }
2221
+ } else {
2222
+ const startDate = typeof this.altFormat === "function" ? this.altFormat(this.#startDate) : this.formatDate(this.#startDate, this.altFormat);
2223
+ (this.singleDatePicker ? this.altInput : this.altInput[0]).value = startDate;
2224
+ if (!this.singleDatePicker && this.#endDate) {
2225
+ const endDate = typeof this.altFormat === "function" ? this.altFormat(this.#endDate) : this.formatDate(this.#endDate, this.altFormat);
2226
+ this.altInput[1].value = endDate;
2227
+ }
2228
+ }
2229
+ }
2230
+ /**
2231
+ * Removes the picker from document
2232
+ */
2233
+ remove() {
2234
+ this.element.removeEventListener("click", this.#showProxy);
2235
+ this.element.removeEventListener("focus", this.#showProxy);
2236
+ this.element.removeEventListener("keyup", this.#elementChangedProxy);
2237
+ this.element.removeEventListener("keydown", this.#keydownProxy);
2238
+ this.element.removeEventListener("click", this.#toggleProxy);
2239
+ this.element.removeEventListener("keydown", this.#toggleProxy);
2240
+ this.container.remove();
2241
+ }
2242
+ /**
2243
+ * Helper function to dispatch events
2244
+ * @param {object} ev - Event template from this.#events
2245
+ * @param {...object} args - Additional parameters if needed
2246
+ */
2247
+ triggerEvent(ev, ...args) {
2248
+ if (args.length === 0) {
2249
+ const event = new DateRangePickerEvent(this, ev);
2250
+ this.element.dispatchEvent(event);
2251
+ return event;
2252
+ } else {
2253
+ const event = new DateRangePickerEvent(this, ev, ...args);
2254
+ this.element.dispatchEvent(event);
2255
+ return event;
2256
+ }
2257
+ }
2258
+ /**
2259
+ * Helper function to add eventListener similar to jQuery .on( events [, selector ] [, data ] )
2260
+ * @param {string} element - Query selector of element where listener is added
2261
+ * @param {string} eventName - Name of the event
2262
+ * @param {string} selector - Query selector string to filter the descendants of the element
2263
+ * @param {function} delegate - Handler data
2264
+ * @private
2265
+ */
2266
+ addListener(element, eventName, selector, delegate) {
2267
+ this.container.querySelectorAll(element).forEach((el) => {
2268
+ el.addEventListener(eventName, function(event) {
2269
+ const target = event.target.closest(selector);
2270
+ if (target && el.contains(target))
2271
+ delegate.call(target, event);
2272
+ });
2273
+ });
2274
+ }
2275
+ }
2276
+ function createElementFromHTML(html) {
2277
+ const template = document.createElement("template");
2278
+ template.innerHTML = html.trim();
2279
+ return template.content.firstElementChild;
2280
+ }
2281
+ class DateRangePickerEvent extends Event {
2282
+ #picker;
2283
+ constructor(drp, ev, args = {}) {
2284
+ let param = {};
2285
+ if (ev.param)
2286
+ param = typeof ev.param === "function" ? ev.param() : ev.param;
2287
+ param = { ...param, ...args };
2288
+ super(ev.type, param);
2289
+ this.#picker = drp;
2290
+ for (const [key2, value] of Object.entries(param)) {
2291
+ if (Object.getOwnPropertyNames(Event.prototype).includes(key2)) continue;
2292
+ this[key2] = value;
2293
+ }
2294
+ }
2295
+ get picker() {
2296
+ return this.#picker;
2297
+ }
2298
+ }
2299
+ function offset(el) {
2300
+ const rect = el.getBoundingClientRect();
2301
+ return {
2302
+ top: rect.top + window.scrollY,
2303
+ left: rect.left + window.scrollX
2304
+ };
2305
+ }
2306
+ function outerWidth(el, withMargin = false) {
2307
+ if (!withMargin)
2308
+ return el.offsetWidth;
2309
+ const style = getComputedStyle(el);
2310
+ return el.offsetWidth + parseFloat(style.marginLeft) + parseFloat(style.marginRight);
2311
+ }
2312
+ function outerHeight(el, withMargin = false) {
2313
+ if (!withMargin)
2314
+ return el.offsetHeight;
2315
+ const style = getComputedStyle(el);
2316
+ return el.offsetHeight + parseFloat(style.marginTop) + parseFloat(style.marginBottom);
2317
+ }
2318
+ function daterangepicker(elements, options, callback) {
2319
+ if (typeof elements === "string")
2320
+ return daterangepicker(document.querySelectorAll(elements), options, callback);
2321
+ if (elements instanceof HTMLElement)
2322
+ elements = [elements];
2323
+ if (elements instanceof NodeList || elements instanceof HTMLCollection)
2324
+ elements = Array.from(elements);
2325
+ if (elements == null)
2326
+ return new DateRangePicker(null, options || {}, callback);
2327
+ elements.forEach((el) => {
2328
+ if (el._daterangepicker && typeof el._daterangepicker.remove === "function")
2329
+ el._daterangepicker.remove();
2330
+ el._daterangepicker = new DateRangePicker(el, options || {}, callback);
2331
+ });
2332
+ return elements.length === 1 ? elements[0] : elements;
2333
+ }
2334
+ function getDateRangePicker(target) {
2335
+ if (typeof target === "string")
2336
+ target = document.querySelector(target);
2337
+ return target instanceof HTMLElement ? target._daterangepicker : void 0;
2338
+ }
2339
+ if (window.jQuery?.fn) {
2340
+ jQuery.fn.daterangepicker = function(options, callback) {
2341
+ return this.each(function() {
2342
+ daterangepicker(this, options, callback);
2343
+ });
2344
+ };
2345
+ }
2346
+ function registerJqueryPlugin(jq) {
2347
+ if (!jq?.fn) return;
2348
+ jq.fn.daterangepicker = function(options, callback) {
2349
+ return this.each(function() {
2350
+ daterangepicker(this, options, callback);
2351
+ });
2352
+ };
2353
+ }
2354
+ Object.defineProperty(window, "jQuery", {
2355
+ configurable: true,
2356
+ set(value) {
2357
+ this._jQuery = value;
2358
+ registerJqueryPlugin(value);
2359
+ },
2360
+ get() {
2361
+ return this._jQuery;
2362
+ }
2363
+ });
2364
+ exports.DateRangePicker = DateRangePicker;
2365
+ exports.daterangepicker = daterangepicker;
2366
+ exports.getDateRangePicker = getDateRangePicker;
2367
+ //# sourceMappingURL=daterangepicker.cjs.map