@wernfried/daterangepicker 5.2.4 → 5.2.6

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