@wernfried/daterangepicker 4.15.0 → 4.16.0

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,11 +1,14 @@
1
1
  import { DateTime, Duration, Info, Settings } from "luxon";
2
2
  import { $ } from "jquery";
3
3
  class DateRangePicker {
4
+ #startDate = null;
5
+ #endDate = null;
6
+ #setRange = false;
4
7
  constructor(element, options, cb) {
5
8
  this.parentEl = "body";
6
9
  this.element = $(element);
7
- this.startDate = DateTime.now().startOf("day");
8
- this.endDate = DateTime.now().endOf("day");
10
+ this.#startDate = DateTime.now().startOf("day");
11
+ this.#endDate = DateTime.now().plus({ day: 1 }).startOf("day");
9
12
  this.minDate = null;
10
13
  this.maxDate = null;
11
14
  this.maxSpan = null;
@@ -21,6 +24,7 @@ class DateRangePicker {
21
24
  this.showWeekNumbers = false;
22
25
  this.showISOWeekNumbers = false;
23
26
  this.showCustomRangeLabel = true;
27
+ this.showLabel = !$(this.element).is("input:text");
24
28
  this.timePicker = false;
25
29
  const usesMeridiems = new Intl.DateTimeFormat(DateTime.now().locale, { hour: "numeric" }).resolvedOptions();
26
30
  this.timePicker24Hour = !usesMeridiems.hour12;
@@ -179,7 +183,8 @@ class DateRangePicker {
179
183
  "showCustomRangeLabel",
180
184
  "alwaysShowCalendars",
181
185
  "autoApply",
182
- "autoUpdateInput"
186
+ "autoUpdateInput",
187
+ "showLabel"
183
188
  ]) {
184
189
  if (typeof options[key2] === "boolean")
185
190
  this[key2] = options[key2];
@@ -304,51 +309,52 @@ class DateRangePicker {
304
309
  }
305
310
  }
306
311
  }
307
- if (!this.timePicker) {
308
- if (this.minDate)
309
- this.minDate = this.minDate.startOf("day");
310
- if (this.maxDate)
311
- this.maxDate = this.maxDate.endOf("day");
312
- }
313
- if (typeof options.startDate === "undefined" && typeof options.endDate === "undefined") {
314
- if ($(this.element).is(":text")) {
315
- let start, end;
316
- const val = $(this.element).val();
317
- if (val != "") {
312
+ if ($(this.element).is("input:text")) {
313
+ const val = $(this.element).val();
314
+ if (val != "") {
315
+ const format = typeof this.locale.format === "string" ? this.locale.format : DateTime.parseFormatForOpts(this.locale.format);
316
+ if (this.singleDatePicker && typeof options.startDate === "undefined") {
317
+ const start = DateTime.fromFormat(val, format, { locale: DateTime.now().locale });
318
+ if (start.isValid) {
319
+ this.#startDate = start;
320
+ } else {
321
+ console.error(`Value "${val}" in <input> is not a valid string: ${start.invalidExplanation}`);
322
+ }
323
+ } else if (!this.singleDatePicker && typeof options.startDate === "undefined" && typeof options.endDate === "undefined") {
318
324
  const split = val.split(this.locale.separator);
319
- const format = typeof this.locale.format === "string" ? this.locale.format : DateTime.parseFormatForOpts(this.locale.format);
320
325
  if (split.length === 2) {
321
- start = DateTime.fromFormat(split[0], format, { locale: DateTime.now().locale });
322
- end = DateTime.fromFormat(split[1], format, { locale: DateTime.now().locale });
323
- } else if (this.singleDatePicker) {
324
- start = DateTime.fromFormat(val, format, { locale: DateTime.now().locale });
325
- end = DateTime.fromFormat(val, format, { locale: DateTime.now().locale });
326
- }
327
- if (start.isValid && end.isValid) {
328
- this.setStartDate(start, false);
329
- this.setEndDate(end, false);
330
- } else {
331
- if (this.singleDatePicker)
332
- console.error(`Value in <input> is not a valid string: ${start.invalidExplanation}`);
333
- else
326
+ const start = DateTime.fromFormat(split[0], format, { locale: DateTime.now().locale });
327
+ const end = DateTime.fromFormat(split[1], format, { locale: DateTime.now().locale });
328
+ if (start.isValid && end.isValid) {
329
+ this.#startDate = start;
330
+ this.#endDate = end;
331
+ } else {
334
332
  console.error(`Value in <input> is not a valid string: ${start.invalidExplanation} - ${end.invalidExplanation}`);
333
+ }
334
+ } else {
335
+ console.error(`Value "${val}" in <input> is not a valid string`);
335
336
  }
336
337
  }
337
338
  }
338
339
  }
340
+ if (!this.timePicker) {
341
+ if (this.minDate)
342
+ this.minDate = this.minDate.startOf("day");
343
+ if (this.maxDate)
344
+ this.maxDate = this.maxDate.endOf("day");
345
+ }
339
346
  if (this.singleDatePicker) {
340
- this.endDate = this.startDate;
341
- } else if (this.endDate < this.startDate) {
342
- this.endDate = this.startDate;
343
- console.warn(`Set 'endDate' to ${this - this.logDate(endDate)} because it was earlier than 'startDate'`);
347
+ this.#endDate = this.#startDate;
348
+ } else if (this.#endDate < this.#startDate) {
349
+ console.error(`Option 'endDate' ${this.#endDate} must not be earlier than 'startDate' ${this.#startDate}`);
344
350
  }
345
351
  if (["function", "string"].includes(typeof options.altFormat))
346
352
  this.altFormat = options.altFormat;
347
353
  if (typeof options.altInput === "string" || Array.isArray(options.altInput)) {
348
354
  if (this.singleDatePicker && typeof options.altInput === "string") {
349
- this.altInput = $(options.altInput).is("input") ? options.altInput : null;
355
+ this.altInput = $(options.altInput).is("input:text") ? options.altInput : null;
350
356
  } else if (!this.singleDatePicker && Array.isArray(options.altInput) && options.altInput.length === 2) {
351
- this.altInput = options.altInput.every((x) => typeof x === "string" && $(x).is("input")) ? options.altInput : null;
357
+ this.altInput = options.altInput.every((x) => typeof x === "string" && $(x).is("input:text")) ? options.altInput : null;
352
358
  } else {
353
359
  const note = `Value of "altInput" must be ` + (this.singleDatePicker ? "a string" : "an array of two string elements");
354
360
  console.error(`Option 'altInput' ${JSON.stringify(options.altInput)} is not valid
@@ -357,12 +363,34 @@ class DateRangePicker {
357
363
  }
358
364
  if (options.warnings !== void 0)
359
365
  console.warn(`Option 'warnings' not used anymore. Listen to event 'violated.daterangepicker'`);
360
- if (!this.startDate && this.initalMonth) {
361
- this.endDate = null;
366
+ if (!this.#startDate && this.initalMonth) {
367
+ this.#endDate = null;
362
368
  if (this.timePicker)
363
369
  console.error(`Option 'initalMonth' works only with 'timePicker: false'`);
364
370
  } else {
365
- this.validateInput();
371
+ const violations = this.validateInput(null, false);
372
+ if (violations != null) {
373
+ let vio = violations.startDate.violations;
374
+ if (vio.length > 0) {
375
+ if (vio.some((x) => x.reason.startsWith("isInvalid"))) {
376
+ console.error(`Value of startDate "${this.#startDate}" violates ${vio.find((x) => x.reason.startsWith("isInvalid")).reason}`);
377
+ } else {
378
+ const newDate = vio.filter((x) => x.new != null).at(-1).new;
379
+ this.#startDate = newDate;
380
+ }
381
+ }
382
+ if (!this.singleDatePicker) {
383
+ vio = violations.endDate.violations.filter((x) => x.new != null);
384
+ if (vio.length > 0) {
385
+ if (vio.some((x) => x.reason.startsWith("isInvalid"))) {
386
+ console.error(`Value of endDate "${this.#endDate}" violates ${vio.find((x) => x.reason.startsWith("isInvalid")).reason}`);
387
+ } else {
388
+ const newDate = vio.filter((x) => x.new != null).at(-1).new;
389
+ this.#endDate = newDate;
390
+ }
391
+ }
392
+ }
393
+ }
366
394
  }
367
395
  if (typeof options.opens === "string") {
368
396
  if (["left", "right", "center"].includes(options.opens))
@@ -421,20 +449,10 @@ class DateRangePicker {
421
449
  }
422
450
  if (start == null || end == null)
423
451
  continue;
424
- const validRange = this.validateInput([range, start, end]);
425
- if (validRange[2].startDate.violations.map((x) => x.reason).some((x) => ["minDate", "maxDate", "minSpan", "maxSpan"].includes(x))) {
426
- const vio = validRange[2].startDate.violations.map((x) => x.reason).filter((x) => ["minDate", "maxDate", "minSpan", "maxSpan"].includes(x));
427
- console.error(`Option ranges['${range}'] is not valid, violating ${vio.join(",")}`);
428
- } else if (validRange[2].endDate.violations.map((x) => x.reason).some((x) => ["minDate", "maxDate", "minSpan", "maxSpan"].includes(x))) {
429
- const vio = validRange[2].endDate.violations.map((x) => x.reason).filter((x) => ["minDate", "maxDate", "minSpan", "maxSpan"].includes(x));
430
- console.error(`Option ranges['${range}'] is not valid, violating ${vio.join(",")}`);
431
- } else {
432
- options.ranges[range] = [validRange[0], validRange[1]];
433
- var elem = document.createElement("textarea");
434
- elem.innerHTML = range;
435
- var rangeHtml = elem.value;
436
- this.ranges[rangeHtml] = [validRange[0], validRange[1]];
437
- }
452
+ options.ranges[range] = [start, end];
453
+ var elem = document.createElement("textarea");
454
+ elem.innerHTML = range;
455
+ this.ranges[elem.value] = [start, end];
438
456
  }
439
457
  var list = "<ul>";
440
458
  for (let range in this.ranges) {
@@ -451,10 +469,10 @@ class DateRangePicker {
451
469
  this.callback = cb;
452
470
  }
453
471
  if (!this.timePicker) {
454
- if (this.startDate)
455
- this.startDate = this.startDate.startOf("day");
456
- if (this.endDate)
457
- this.endDate = this.endDate.endOf("day");
472
+ if (this.#startDate)
473
+ this.#startDate = this.#startDate.startOf("day");
474
+ if (this.#endDate)
475
+ this.#endDate = this.#endDate.endOf("day");
458
476
  this.container.find(".calendar-time").hide();
459
477
  }
460
478
  if (this.timePicker && this.autoApply)
@@ -496,130 +514,137 @@ class DateRangePicker {
496
514
  }
497
515
  this.updateElement();
498
516
  }
517
+ get startDate() {
518
+ return this.#startDate;
519
+ }
520
+ get endDate() {
521
+ return this.singleDatePicker ? null : this.#endDate;
522
+ }
523
+ set startDate(val) {
524
+ this.#startDate = val;
525
+ }
526
+ set endDate(val) {
527
+ this.#endDate = val;
528
+ }
499
529
  /**
500
- * Sets the date range picker's currently selected start date to the provided date.<br/>
501
- * `startDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or
502
- * a string matching `locale.format`.
503
- * The value of the attached `<input>` element is also updated.
504
- * Date value is rounded to match option `timePickerStepSize` unless skipped by `violated.daterangepicker` event handler.<br/>
505
- * If the `startDate` does not fall into `minDate` and `maxDate` then `startDate` is shifted unless skipped by `violated.daterangepicker` event handler.
506
- * @param {external:DateTime|external:Date|string} startDate - startDate to be set
507
- * @param {boolean} isValid=false - If `true` then the `startDate` is not checked against `minDate` and `maxDate`<br/>
508
- * Use this option only if you are sure about the value you put in.
509
- * @throws `RangeError` for invalid date values.
510
- * @example const DateTime = luxon.DateTime;
530
+ * Sets the date range picker's currently selected start date to the provided date.<br>
531
+ * `startDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
532
+ * Invalid date values are handled by {@link #DateRangePicker+violated|violated} Event
533
+ * @param {external:DateTime|external:Date|string} startDate - startDate to be set. In case of ranges, the current `endDate` is used.
534
+ * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
535
+ * @returns {InputViolation} - Object of violations or `null` if no violation have been found
536
+ * @example
511
537
  * const drp = $('#picker').data('daterangepicker');
512
538
  * drp.setStartDate(DateTime.now().startOf('hour'));
513
539
  */
514
- setStartDate(startDate, isValid = false) {
515
- if (isValid === void 0 || !isValid) {
516
- if (typeof startDate === "object") {
517
- if (DateTime.isDateTime(startDate) && startDate.isValid) {
518
- this.startDate = startDate;
519
- } else if (startDate instanceof Date) {
520
- this.startDate = DateTime.fromJSDate(startDate);
521
- } else {
522
- throw RangeError(`The 'startDate' must be a luxon.DateTime or Date or string`);
523
- }
524
- } else if (typeof startDate === "string") {
525
- const format = typeof this.locale.format === "string" ? this.locale.format : DateTime.parseFormatForOpts(this.locale.format);
526
- if (DateTime.fromISO(startDate).isValid) {
527
- this.startDate = DateTime.fromISO(startDate);
528
- } else if (DateTime.fromFormat(startDate, format, { locale: DateTime.now().locale }).isValid) {
529
- this.startDate = DateTime.fromFormat(startDate, format, { locale: DateTime.now().locale });
530
- } else {
531
- const invalid = DateTime.fromFormat(startDate, format, { locale: DateTime.now().locale }).invalidExplanation;
532
- throw RangeError(`The 'startDate' is not a valid string: ${invalid}`);
533
- }
540
+ setStartDate(startDate, updateView = true) {
541
+ if (!this.singleDatePicker)
542
+ return setRange(startDate, this.#endDate, updateView);
543
+ const oldDate = this.#startDate;
544
+ let newDate = this.parseDate(startDate);
545
+ if (newDate.equals(oldDate))
546
+ return null;
547
+ const violations = this.validateInput([newDate, null], true);
548
+ if (violations != null) {
549
+ if (violations.newDate != null) {
550
+ newDate = violations.newDate.startDate;
551
+ } else {
552
+ return violations;
534
553
  }
535
- } else {
536
- this.startDate = startDate;
537
554
  }
538
- if (isValid === void 0 || !isValid)
539
- this.validateInput();
540
- if (!this.singleDatePicker && !this.endDate) {
541
- if (this.locale.durationFormat)
542
- this.container.find(".drp-duration-label").html("");
543
- const empty = `<span>${this.formatDate(this.startDate)}</span>`;
544
- this.container.find(".drp-selected").html(this.formatDate(this.startDate) + this.locale.separator + empty);
545
- }
546
- if (!this.isShowing)
547
- this.updateElement();
548
- this.updateMonthsInView();
555
+ this.#startDate = newDate;
556
+ this.#endDate = this.#startDate;
557
+ this.updateElement();
558
+ if (updateView)
559
+ this.updateView();
560
+ return violations;
549
561
  }
550
562
  /**
551
- * Sets the date range picker's currently selected end date to the provided date.<br/>
552
- * `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or
553
- * a string matching`locale.format`.
554
- * The value of the attached `<input>` element is also updated.
555
- * Date value is rounded to match option `timePickerStepSize` unless skipped by `violated.daterangepicker` event handler.<br/>
556
- * If the `endDate` does not fall into `minDate` and `maxDate` or into `minSpan` and `maxSpan`
557
- * then `endDate` is shifted unless skipped by `violated.daterangepicker` event handler
558
- * @param {external:DateTime|external:Date|string} endDate - endDate to be set
559
- * @param {boolean} isValid=false - If `true` then the `endDate` is not checked against `minDate`, `maxDate` and `minSpan`, `maxSpan`<br/>
560
- * Use this option only if you are sure about the value you put in.
561
- * @throws `RangeError` for invalid date values.
562
- * @example const drp = $('#picker').data('daterangepicker');
563
- * drp.setEndDate('2025-03-28T18:30:00');
563
+ * Sets the date range picker's currently selected start date to the provided date.<br>
564
+ * `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
565
+ * Invalid date values are handled by {@link #DateRangePicker+violated|violated} Event
566
+ * @param {external:DateTime|external:Date|string} endDate - endDate to be set. In case of ranges, the current `startDate` is used.
567
+ * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
568
+ * @returns {InputViolation} - Object of violations or `null` if no violation have been found
569
+ * @example
570
+ * const drp = $('#picker').data('daterangepicker');
571
+ * drp.setEndDate(DateTime.now().startOf('hour'));
564
572
  */
565
- setEndDate(endDate2, isValid = false) {
566
- if (isValid === void 0 || !isValid) {
567
- if (typeof endDate2 === "object") {
568
- if (DateTime.isDateTime(endDate2) && endDate2.isValid) {
569
- this.endDate = endDate2;
570
- } else if (endDate2 instanceof Date) {
571
- this.endDate = DateTime.fromJSDate(endDate2);
572
- } else {
573
- throw RangeError(`The 'endDate' must be a luxon.DateTime or Date or string`);
574
- }
575
- } else if (typeof endDate2 === "string") {
576
- const format = typeof this.locale.format === "string" ? this.locale.format : DateTime.parseFormatForOpts(this.locale.format);
577
- if (DateTime.fromISO(endDate2).isValid) {
578
- this.endDate = DateTime.fromISO(endDate2);
579
- } else if (DateTime.fromFormat(endDate2, format, { locale: DateTime.now().locale }).isValid) {
580
- this.endDate = DateTime.fromFormat(endDate2, format, { locale: DateTime.now().locale });
581
- } else {
582
- const invalid = DateTime.fromFormat(endDate2, format, { locale: DateTime.now().locale }).invalidExplanation;
583
- throw RangeError(`The 'endDate' is not a valid string: ${invalid}`);
584
- }
585
- }
586
- } else {
587
- this.endDate = endDate2;
588
- }
589
- if (isValid === void 0 || !isValid)
590
- this.validateInput();
591
- this.previousRightTime = this.endDate;
592
- this.updateDurationLabel();
593
- if (!this.singleDatePicker)
594
- this.container.find(".drp-selected").html(this.formatDate(this.startDate) + this.locale.separator + this.formatDate(this.endDate));
595
- if (!this.isShowing)
596
- this.updateElement();
597
- this.updateMonthsInView();
573
+ setEndDate(endDate, updateView = true) {
574
+ return this.singleDatePicker ? null : setRange(this.#startDate, endDate, updateView);
598
575
  }
599
576
  /**
600
- * Shortcut for {@link #DateRangePicker+setStartDate|setStartDate} and {@link #DateRangePicker+setEndDate|setEndDate}
577
+ * Sets the date range picker's currently selected start date to the provided date.<br>
578
+ * `startDate` and `endDate` must be a `luxon.DateTime` or `Date` or `string` according to {@link ISO-8601} or a string matching `locale.format`.<br>
579
+ * Invalid date values are handled by {@link #DateRangePicker+violated|violated} Event
601
580
  * @param {external:DateTime|external:Date|string} startDate - startDate to be set
602
581
  * @param {external:DateTime|external:Date|string} endDate - endDate to be set
603
- * @param {boolean} isValid=false - If `true` then the `startDate` and `endDate` are not checked against `minDate`, `maxDate` and `minSpan`, `maxSpan`<br/>
604
- * Use this option only if you are sure about the value you put in.
605
- * @throws `RangeError` for invalid date values.
606
- * @example const DateTime = luxon.DateTime;
582
+ * @param {boolean} updateView=true - If `true`, then calendar UI is updated to new value. Otherwise only internal values are set.
583
+ * @returns {InputViolation} - Object of violations or `null` if no violation have been found
584
+ * @example
607
585
  * const drp = $('#picker').data('daterangepicker');
608
- * drp.setPeriod(DateTime.now().startOf('week'), DateTime.now().startOf('week').plus({days: 10}));
586
+ * drp.setRange(DateTime.now().startOf('hour'), DateTime.now().endOf('day'));
609
587
  */
610
- setPeriod(startDate, endDate2, isValid = false) {
611
- if (this.singleDatePicker) {
612
- this.setStartDate(startDate, isValid);
613
- } else {
614
- this.setStartDate(startDate, true);
615
- this.setEndDate(endDate2, true);
616
- if (!isValid)
617
- this.validateInput();
588
+ setRange(startDate, endDate, updateView = true) {
589
+ if (this.singleDatePicker)
590
+ return;
591
+ if (!this.#endDate)
592
+ this.#endDate = this.#startDate;
593
+ const oldDate = [this.#startDate, this.#endDate];
594
+ let newDate = [this.parseDate(startDate), this.parseDate(endDate)];
595
+ if (oldDate[0].equals(newDate[0]) && oldDate[1].equals(newDate[1]) || newDate[1] > newDate[0])
596
+ return;
597
+ const violations = this.validateInput([newDate[0], newDate[1]], true);
598
+ if (violations != null) {
599
+ if (violations.newDate != null) {
600
+ newDate[0] = violations.newDate.startDate;
601
+ newDate[1] = violations.newDate.endDate;
602
+ } else {
603
+ return violations;
604
+ }
605
+ }
606
+ this.#startDate = newDate[0];
607
+ this.#endDate = newDate[1];
608
+ this.updateElement();
609
+ if (updateView)
610
+ this.updateView();
611
+ return violations;
612
+ }
613
+ /**
614
+ * Parse date value
615
+ * @param {sting|external:DateTime|Date} value - The value to be parsed
616
+ * @returns {external:DateTime} - DateTime object
617
+ */
618
+ parseDate(value) {
619
+ if (typeof value === "object") {
620
+ if (DateTime.isDateTime(value) && value.isValid) {
621
+ return value;
622
+ } else if (value instanceof Date) {
623
+ return DateTime.fromJSDate(value);
624
+ } else {
625
+ throw RangeError(`Value must be a luxon.DateTime or Date or string`);
626
+ }
627
+ } else if (typeof value === "string") {
628
+ const format = typeof this.locale.format === "string" ? this.locale.format : DateTime.parseFormatForOpts(this.locale.format);
629
+ if (DateTime.fromISO(value).isValid) {
630
+ return DateTime.fromISO(value);
631
+ } else if (DateTime.fromFormat(value, format, { locale: DateTime.now().locale }).isValid) {
632
+ return DateTime.fromFormat(value, format, { locale: DateTime.now().locale });
633
+ } else {
634
+ const invalid = DateTime.fromFormat(value, format, { locale: DateTime.now().locale }).invalidExplanation;
635
+ throw RangeError(`Value is not a valid string: ${invalid}`);
636
+ }
618
637
  }
619
638
  }
620
639
  logDate(date) {
621
640
  return this.timePicker ? date.toISO({ suppressMilliseconds: true }) : date.toISODate();
622
641
  }
642
+ /**
643
+ * Format a DateTime object
644
+ * @param {external:DateTime} date - The DateTime to format
645
+ * @param {object|string} format=this.locale.format - The format option
646
+ * @returns {string} - Formatted date string
647
+ */
623
648
  formatDate(date, format = this.locale.format) {
624
649
  if (typeof format === "object") {
625
650
  return date.toLocaleString(format);
@@ -632,17 +657,30 @@ class DateRangePicker {
632
657
  }
633
658
  }
634
659
  }
635
- updateDurationLabel() {
660
+ /**
661
+ * Set Duration Label to selected range (if used) and selected dates
662
+ * @private
663
+ */
664
+ updateLabel() {
665
+ if (this.showLabel) {
666
+ let text = this.formatDate(this.#startDate);
667
+ if (!this.singleDatePicker) {
668
+ text += this.locale.separator;
669
+ if (this.#endDate)
670
+ text += this.formatDate(this.#endDate);
671
+ }
672
+ this.container.find(".drp-selected").html(text);
673
+ }
636
674
  if (this.singleDatePicker || this.locale.durationFormat == null)
637
675
  return;
638
- if (!this.endDate) {
676
+ if (!this.#endDate) {
639
677
  this.container.find(".drp-duration-label").html("");
640
678
  return;
641
679
  }
642
680
  if (typeof this.locale.durationFormat === "function") {
643
- this.container.find(".drp-duration-label").html(this.locale.durationFormat(this.startDate, this.endDate));
681
+ this.container.find(".drp-duration-label").html(this.locale.durationFormat(this.#startDate, this.#endDate));
644
682
  } else {
645
- let duration = this.endDate.plus({ milliseconds: 1 }).diff(this.startDate).rescale().set({ milliseconds: 0 });
683
+ let duration = this.#endDate.plus({ milliseconds: 1 }).diff(this.#startDate).rescale().set({ milliseconds: 0 });
646
684
  if (!this.timePicker)
647
685
  duration = duration.set({ seconds: 0, minutes: 0, hours: 0 });
648
686
  duration = duration.removeZeros();
@@ -654,31 +692,90 @@ class DateRangePicker {
654
692
  }
655
693
  }
656
694
  /**
695
+ * Emitted when the date is changed through `<input>` element or via {@link #DateRangePicker+setStartDate|setStartDate} or
696
+ * {@link #DateRangePicker+setRange|setRange} and date is not valid due to.<br>
697
+ * `minDate`, `maxDate`, `minSpan`, `maxSpan`, `invalidDate` and `invalidTime` constraints.<br>
698
+ * Event is only triggered when date string is valid and date value is changing<br>
699
+ * @event
700
+ * @name "violated.daterangepicker"
701
+ * @param {Object} this - The event object
702
+ * @param {DateRangePicker} picker - The daterangepicker object
703
+ * @param {InputViolation} result - The violation object, see example of `validateInput()`
704
+ * @param {Object} newDate - Object of {startDate, endDate}
705
+ * @return {boolean}=undefined - If handler returns `true`, then values from `newDate` object are used
706
+ * @example
707
+ *
708
+ * $('#picker').daterangepicker({
709
+ * startDate: DateTime.now(),
710
+ * // allow only dates from current year
711
+ * minDate: DateTime.now().startOf('year'),
712
+ * manDate: DateTime.now().endOf('year'),
713
+ * singleDatePicker: true,
714
+ * locale: {
715
+ * format: DateTime.DATETIME_SHORT
716
+ * }
717
+ * }).on('violated.daterangepicker', (ev, picker, result, newDate) => {
718
+ * newDate.startDate = DateTime.now().minus({ days: 3 }).startOf('day');
719
+ * return true;
720
+ * });
721
+ *
722
+ * // Try to set date outside permitted range at <input> elemet
723
+ * $('#picker').val(DateTime.now().minus({ years: 10 })).toLocaleString(DateTime.DATETIME_SHORT).trigger('keyup');
724
+
725
+ * // Try to set date outside permitted range by code
726
+ * const drp = $('#picker').data('daterangepicker').setStartDate(DateTime.now().minus({ years: 10 })
727
+ *
728
+ * -> Calendar selects and shows "today - 3 days"
729
+ */
730
+ /**
657
731
  * @typedef InputViolation
658
732
  * @type {Object}
659
733
  * @property {external:DateTime} startDate - Violation of startDate
660
- * @property {external:DateTime|undefined} endDate - Violation of endDate
661
- * @property {Array} reason - The constraint which violates the input
734
+ * @property {external:DateTime|undefined}? endDate - Violation of endDate, if existing
735
+ * @property {Array} violations - The constraints which violates the input
736
+ * @property {Array} reason - The type/reson of violation
662
737
  * @property {external:DateTime} old - Old value startDate/endDate
663
- * @property {external:DateTime} new - Corrected value of startDate/endDate
738
+ * @property {external:DateTime}? new - Corrected value of startDate/endDate if existing
664
739
  */
665
740
  /**
666
- * Validate `startDate` and `endDate` or `range` against `timePickerStepSize`, `minDate`, `maxDate`,
667
- * `minSpan`, `maxSpan`, `invalidDate` and `invalidTime` and corrects them, if needed.
668
- * Correction can be skipped by returning `true` at event listener for `violated.daterangepicker`
669
- * @param {Array} [range] - Used to check prefefined range instead of `startDate` and `endDate` => `[name, startDate, endDate]`
670
- * When set, then function does not modify anything, just returning corrected range.
741
+ * Validate `startDate` and `endDate` against `timePickerStepSize`, `minDate`, `maxDate`,
742
+ * `minSpan`, `maxSpan`, `invalidDate` and `invalidTime`.
743
+ * @param {Array} [startDate, endDate] - Range to be checked, defaults to current `startDate` and `endDate`
744
+ * @param {boolean} dipatch=false - If 'true' then event "violated.daterangepicker" is dispated.<br>
745
+ * If eventHandler returns `true`, then `null` is returned, otherwiese the object of violations.
671
746
  * @emits "violated.daterangepicker"
672
- * @returns {Array|null} - Corrected range as array of `[startDate, endDate]` when `range` is defined
747
+ * @returns {InputViolation|null} - Object of violations and corrected values or `null` if no violation have been found
673
748
  * @example
674
- * validateInput([DateTime.fromISO('2025-02-03'), DateTime.fromISO('2025-02-25')]) =>
675
- * [ DateTime.fromISO('2025-02-05'), DateTime.fromISO('2025-02-20'), { startDate: { violations: [{old: ..., new: ..., reasson: 'minDate'}] } } ]
749
+ * options => {
750
+ * minDate: DateTime.now().minus({months: 3}).startOf('day'),
751
+ * maxDate: DateTime.now().minus({day: 3}).startOf('day'),
752
+ * minSpan: Duration.fromObject({days: 7}),
753
+ * maxSpan: Duration.fromObject({days: 70}),
754
+ * timePickerStepSize: Duration.fromObject({hours: 1})
755
+ * }
756
+ * const result = validateInput(DateTime.now(), DateTime.now().plus({day: 3}));
757
+ *
758
+ * result => {
759
+ * startDate: {
760
+ * violations: [
761
+ * { old: "2026-03-13T10:35:52", reason: "timePickerStepSize", new: "2026-03-13T11:00:00" },
762
+ * { old: "2026-03-13T11:00:00", reason: "maxDate", new: "2026-03-10T00:00:00" }
763
+ * ]
764
+ * },
765
+ * endDate: {
766
+ * violations: [
767
+ * { old: "2026-03-16T10:35:52", reason: "stepSize", new: "2026-03-16T11:00:00" },
768
+ * { old: "2026-03-16T11:00:00", reason: "maxDate", new: "2026-03-10T00:00:00" },
769
+ * { old: "2026-03-10T00:00:00", reason: "minSpan", new: "2026-03-17T00:00:00" }
770
+ * ]
771
+ * }
772
+ * }
676
773
  */
677
- validateInput(range) {
678
- let startDate = range === void 0 ? this.startDate : range[1];
679
- let endDate2 = range === void 0 ? this.endDate : range[2];
680
- if (!startDate)
681
- return;
774
+ validateInput(range, dipatch = false) {
775
+ let startDate = range == null ? this.#startDate : range[0];
776
+ let endDate = range == null ? this.#endDate : range[1];
777
+ if (startDate == null)
778
+ return null;
682
779
  let result = { startDate: { violations: [] } };
683
780
  let violation = { old: startDate, reason: this.timePicker ? "timePickerStepSize" : "timePicker" };
684
781
  if (this.timePicker) {
@@ -718,104 +815,107 @@ class DateRangePicker {
718
815
  units.push("ampm");
719
816
  }
720
817
  if (this.isInvalidDate(startDate))
721
- result.startDate.violations.push({ old: startDate, new: startDate, reason: "isInvalidDate" });
818
+ result.startDate.violations.push({ old: startDate, reason: "isInvalidDate" });
722
819
  if (this.timePicker) {
723
820
  for (let unit of units) {
724
821
  if (this.isInvalidTime(startDate, unit, "start"))
725
- result.startDate.violations.push({ old: startDate, new: startDate, reason: "isInvalidTime", unit });
822
+ result.startDate.violations.push({ old: startDate, reason: "isInvalidTime", unit });
726
823
  }
727
824
  }
728
825
  if (this.singleDatePicker) {
729
- endDate2 = startDate;
730
- if (range === void 0) {
731
- if (result.startDate.violations.length > 0) {
732
- if (!this.element.triggerHandler("violated.daterangepicker", [this, result])) {
733
- this.startDate = startDate;
734
- this.endDate = endDate2;
735
- }
826
+ if (result.startDate.violations.length == 0)
827
+ return null;
828
+ if (dipatch) {
829
+ let newValues = { startDate };
830
+ const ret = this.element.triggerHandler("violated.daterangepicker", [this, result, newValues]);
831
+ if (ret) {
832
+ result.newDate = newValues;
833
+ return result;
736
834
  }
737
- return;
835
+ return result;
738
836
  } else {
739
- return [startDate, endDate2, result];
837
+ return result;
740
838
  }
741
839
  }
742
- if (endDate2 == null)
743
- return;
840
+ if (endDate == null)
841
+ return null;
744
842
  result.endDate = { violations: [] };
745
- violation = { old: endDate2, reason: this.timePicker ? "stepSize" : "timePicker" };
843
+ violation = { old: endDate, reason: this.timePicker ? "stepSize" : "timePicker" };
746
844
  if (this.timePicker) {
747
845
  const secs = this.timePickerStepSize.as("seconds");
748
- endDate2 = DateTime.fromSeconds(secs * Math.round(endDate2.toSeconds() / secs));
846
+ endDate = DateTime.fromSeconds(secs * Math.round(endDate.toSeconds() / secs));
749
847
  } else {
750
- endDate2 = endDate2.endOf("day");
848
+ endDate = endDate.endOf("day");
751
849
  }
752
- violation.new = endDate2;
850
+ violation.new = endDate;
753
851
  if (!violation.new.equals(violation.old))
754
852
  result.endDate.violations.push(violation);
755
- if (this.maxDate && endDate2 > this.maxDate) {
756
- violation = { old: endDate2, reason: "maxDate" };
757
- endDate2 = endDate2.minus({ seconds: Math.trunc(endDate2.diff(this.maxDate).as("seconds") / shiftStep) * shiftStep });
758
- if (endDate2 > this.maxDate)
759
- endDate2 = endDate2.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
760
- violation.new = endDate2;
853
+ if (this.maxDate && endDate > this.maxDate) {
854
+ violation = { old: endDate, reason: "maxDate" };
855
+ endDate = endDate.minus({ seconds: Math.trunc(endDate.diff(this.maxDate).as("seconds") / shiftStep) * shiftStep });
856
+ if (endDate > this.maxDate)
857
+ endDate = endDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
858
+ violation.new = endDate;
761
859
  if (!violation.new.equals(violation.old))
762
860
  result.endDate.violations.push(violation);
763
- } else if (this.minDate && endDate2 < this.minDate) {
764
- violation = { old: endDate2, reason: "minDate" };
765
- endDate2 = endDate2.plus({ seconds: Math.trunc(this.minDate.diff(endDate2).as("seconds") / shiftStep) * shiftStep });
766
- if (endDate2 < this.minDate)
767
- endDate2 = endDate2.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
768
- violation.new = endDate2;
861
+ } else if (this.minDate && endDate < this.minDate) {
862
+ violation = { old: endDate, reason: "minDate" };
863
+ endDate = endDate.plus({ seconds: Math.trunc(this.minDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
864
+ if (endDate < this.minDate)
865
+ endDate = endDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
866
+ violation.new = endDate;
769
867
  if (!violation.new.equals(violation.old))
770
868
  result.endDate.violations.push(violation);
771
869
  }
772
870
  if (this.maxSpan) {
773
871
  const maxDate = startDate.plus(this.maxSpan);
774
- if (endDate2 > maxDate) {
775
- violation = { old: endDate2, reason: "maxSpan" };
776
- endDate2 = endDate2.minus({ seconds: Math.trunc(maxDate.diff(endDate2).as("seconds") / shiftStep) * shiftStep });
777
- if (endDate2 > maxDate)
778
- endDate2 = endDate2.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
779
- violation.new = endDate2;
872
+ if (endDate > maxDate) {
873
+ violation = { old: endDate, reason: "maxSpan" };
874
+ endDate = endDate.minus({ seconds: Math.trunc(maxDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
875
+ if (endDate > maxDate)
876
+ endDate = endDate.minus(this.timePicker ? this.timePickerStepSize : { days: 1 });
877
+ violation.new = endDate;
780
878
  if (!violation.new.equals(violation.old))
781
879
  result.endDate.violations.push(violation);
782
880
  }
783
881
  }
784
882
  if (this.minSpan) {
785
883
  const minDate = startDate.plus(this.defaultSpan ?? this.minSpan);
786
- if (endDate2 < minDate) {
787
- violation = { old: endDate2, reason: "minSpan" };
788
- endDate2 = endDate2.plus({ seconds: Math.trunc(minDate.diff(endDate2).as("seconds") / shiftStep) * shiftStep });
789
- if (endDate2 < minDate)
790
- endDate2 = endDate2.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
791
- violation.new = endDate2;
884
+ if (endDate < minDate) {
885
+ violation = { old: endDate, reason: "minSpan" };
886
+ endDate = endDate.plus({ seconds: Math.trunc(minDate.diff(endDate).as("seconds") / shiftStep) * shiftStep });
887
+ if (endDate < minDate)
888
+ endDate = endDate.plus(this.timePicker ? this.timePickerStepSize : { days: 1 });
889
+ violation.new = endDate;
792
890
  if (!violation.new.equals(violation.old))
793
891
  result.endDate.violations.push(violation);
794
892
  }
795
893
  }
796
- if (this.isInvalidDate(endDate2))
797
- result.endDate.violations.push({ old: endDate2, new: endDate2, reason: "isInvalidDate" });
894
+ if (this.isInvalidDate(endDate))
895
+ result.endDate.violations.push({ old: endDate, reason: "isInvalidDate" });
798
896
  if (this.timePicker) {
799
897
  for (let unit of units) {
800
- if (this.isInvalidTime(endDate2, unit, "end"))
801
- result.endDate.violations.push({ old: endDate2, new: endDate2, reason: "isInvalidTime", unit });
898
+ if (this.isInvalidTime(endDate, unit, "end"))
899
+ result.endDate.violations.push({ old: endDate, reason: "isInvalidTime", unit });
802
900
  }
803
901
  }
804
- if (range === void 0) {
805
- if (result.startDate.violations.length > 0 || result.endDate.violations.length > 0) {
806
- if (!this.element.triggerHandler("violated.daterangepicker", [this, result])) {
807
- this.startDate = startDate;
808
- this.endDate = endDate2;
809
- }
902
+ if (result.startDate.violations.length == 0 && result.endDate.violations.length == 0)
903
+ return null;
904
+ if (dipatch) {
905
+ let newValues = { startDate, endDate };
906
+ const ret = this.element.triggerHandler("violated.daterangepicker", [this, result, newValues]);
907
+ if (ret) {
908
+ result.newDate = newValues;
909
+ return result;
810
910
  }
811
- return;
911
+ return result;
812
912
  } else {
813
- return [startDate, endDate2, result];
913
+ return result;
814
914
  }
815
915
  }
816
916
  /**
817
917
  * Updates the picker when calendar is initiated or any date has been selected.
818
- * Could be useful after running {@link #DateRangePicker+setStartDate|setStartDate} or {@link #DateRangePicker+setEndDate|setEndDate}
918
+ * Could be useful after running {@link #DateRangePicker+setStartDate|setStartDate} or {@link #DateRangePicker+setEndDate|setRange}
819
919
  * @emits "beforeRenderTimePicker.daterangepicker"
820
920
  */
821
921
  updateView() {
@@ -823,44 +923,42 @@ class DateRangePicker {
823
923
  this.element.trigger("beforeRenderTimePicker.daterangepicker", this);
824
924
  this.renderTimePicker("start");
825
925
  this.renderTimePicker("end");
826
- if (!this.endDate) {
926
+ if (!this.#endDate) {
827
927
  this.container.find(".calendar-time.end-time select").prop("disabled", true).addClass("disabled");
828
928
  } else {
829
929
  this.container.find(".calendar-time.end-time select").prop("disabled", false).removeClass("disabled");
830
930
  }
831
931
  }
832
- this.updateDurationLabel();
833
- if (this.startDate && this.endDate)
834
- this.container.find(".drp-selected").html(this.formatDate(this.startDate) + this.locale.separator + this.formatDate(this.endDate));
932
+ this.updateLabel();
835
933
  this.updateMonthsInView();
836
934
  this.updateCalendars();
837
- this.updateFormInputs();
935
+ this.setApplyBtnState();
838
936
  }
839
937
  /**
840
938
  * Shows calendar months based on selected date values
841
939
  * @private
842
940
  */
843
941
  updateMonthsInView() {
844
- if (this.endDate) {
845
- 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")))
942
+ if (this.#endDate) {
943
+ 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")))
846
944
  return;
847
- this.leftCalendar.month = this.startDate.startOf("month");
945
+ this.leftCalendar.month = this.#startDate.startOf("month");
848
946
  if (!this.singleMonthView) {
849
- if (!this.linkedCalendars && !this.endDate.hasSame(this.startDate, "month")) {
850
- this.rightCalendar.month = this.endDate.startOf("month");
947
+ if (!this.linkedCalendars && !this.#endDate.hasSame(this.#startDate, "month")) {
948
+ this.rightCalendar.month = this.#endDate.startOf("month");
851
949
  } else {
852
- this.rightCalendar.month = this.startDate.startOf("month").plus({ month: 1 });
950
+ this.rightCalendar.month = this.#startDate.startOf("month").plus({ month: 1 });
853
951
  }
854
952
  }
855
953
  } else {
856
- if (!this.startDate && this.initalMonth) {
954
+ if (!this.#startDate && this.initalMonth) {
857
955
  this.leftCalendar.month = this.initalMonth;
858
956
  if (!this.singleMonthView)
859
957
  this.rightCalendar.month = this.initalMonth.plus({ month: 1 });
860
958
  } else {
861
- if (!this.leftCalendar.month.hasSame(this.startDate, "month") && !this.rightCalendar.month.hasSame(this.startDate, "month")) {
862
- this.leftCalendar.month = this.startDate.startOf("month");
863
- this.rightCalendar.month = this.startDate.startOf("month").plus({ month: 1 });
959
+ if (!this.leftCalendar.month.hasSame(this.#startDate, "month") && !this.rightCalendar.month.hasSame(this.#startDate, "month")) {
960
+ this.leftCalendar.month = this.#startDate.startOf("month");
961
+ this.rightCalendar.month = this.#startDate.startOf("month").plus({ month: 1 });
864
962
  }
865
963
  }
866
964
  }
@@ -877,7 +975,7 @@ class DateRangePicker {
877
975
  updateCalendars() {
878
976
  if (this.timePicker) {
879
977
  var hour, minute, second;
880
- if (this.endDate) {
978
+ if (this.#endDate) {
881
979
  hour = parseInt(this.container.find(".start-time .hourselect").val(), 10);
882
980
  if (isNaN(hour))
883
981
  hour = parseInt(this.container.find(".start-time .hourselect option:last").val(), 10);
@@ -922,7 +1020,7 @@ class DateRangePicker {
922
1020
  this.renderCalendar("left");
923
1021
  this.renderCalendar("right");
924
1022
  this.container.find(".ranges li").removeClass("active");
925
- if (this.endDate == null) return;
1023
+ if (this.#endDate == null) return;
926
1024
  this.calculateChosenLabel();
927
1025
  }
928
1026
  /**
@@ -933,7 +1031,7 @@ class DateRangePicker {
933
1031
  if (side === "right" && this.singleMonthView)
934
1032
  return;
935
1033
  var calendar = side === "left" ? this.leftCalendar : this.rightCalendar;
936
- if (calendar.month == null && !this.startDate && this.initalMonth)
1034
+ if (calendar.month == null && !this.#startDate && this.initalMonth)
937
1035
  calendar.month = this.initalMonth.startOf("month");
938
1036
  const firstDay = calendar.month.startOf("month");
939
1037
  const lastDay = calendar.month.endOf("month").startOf("day");
@@ -956,7 +1054,7 @@ class DateRangePicker {
956
1054
  } else {
957
1055
  this.rightCalendar.calendar = calendar;
958
1056
  }
959
- var minDate = side === "left" ? this.minDate : this.startDate;
1057
+ var minDate = side === "left" ? this.minDate : this.#startDate;
960
1058
  var maxDate = this.maxDate;
961
1059
  var html = "<tr>";
962
1060
  if (this.showWeekNumbers || this.showISOWeekNumbers)
@@ -1010,15 +1108,15 @@ class DateRangePicker {
1010
1108
  html += "</tr>";
1011
1109
  this.container.find(".drp-calendar." + side + " .calendar-table thead").html(html);
1012
1110
  html = "";
1013
- if (this.endDate == null && this.maxSpan) {
1014
- var maxLimit = this.startDate.plus(this.maxSpan).endOf("day");
1111
+ if (this.#endDate == null && this.maxSpan) {
1112
+ var maxLimit = this.#startDate.plus(this.maxSpan).endOf("day");
1015
1113
  if (!maxDate || maxLimit < maxDate) {
1016
1114
  maxDate = maxLimit;
1017
1115
  }
1018
1116
  }
1019
1117
  var minLimit;
1020
- if (this.endDate == null && this.minSpan)
1021
- minLimit = this.startDate.plus(this.minSpan).startOf("day");
1118
+ if (this.#endDate == null && this.minSpan)
1119
+ minLimit = this.#startDate.plus(this.minSpan).startOf("day");
1022
1120
  for (let row = 0; row < 6; row++) {
1023
1121
  html += "<tr>";
1024
1122
  if (this.showISOWeekNumbers)
@@ -1037,15 +1135,15 @@ class DateRangePicker {
1037
1135
  classes.push("off", "disabled");
1038
1136
  if (maxDate && calendar[row][col].startOf("day") > maxDate.startOf("day"))
1039
1137
  classes.push("off", "disabled");
1040
- if (minLimit && calendar[row][col].startOf("day") > this.startDate.startOf("day") && calendar[row][col].startOf("day") < minLimit.startOf("day"))
1138
+ if (minLimit && calendar[row][col].startOf("day") > this.#startDate.startOf("day") && calendar[row][col].startOf("day") < minLimit.startOf("day"))
1041
1139
  classes.push("off", "disabled");
1042
1140
  if (this.isInvalidDate(calendar[row][col]))
1043
1141
  classes.push("off", "disabled");
1044
- if (this.startDate != null && calendar[row][col].hasSame(this.startDate, "day"))
1142
+ if (this.#startDate != null && calendar[row][col].hasSame(this.#startDate, "day"))
1045
1143
  classes.push("active", "start-date");
1046
- if (this.endDate != null && calendar[row][col].hasSame(this.endDate, "day"))
1144
+ if (this.#endDate != null && calendar[row][col].hasSame(this.#endDate, "day"))
1047
1145
  classes.push("active", "end-date");
1048
- if (this.endDate != null && calendar[row][col] > this.startDate && calendar[row][col] < this.endDate)
1146
+ if (this.#endDate != null && calendar[row][col] > this.#startDate && calendar[row][col] < this.#endDate)
1049
1147
  classes.push("in-range");
1050
1148
  var isCustom = this.isCustomDate(calendar[row][col]);
1051
1149
  if (isCustom !== false) {
@@ -1075,21 +1173,21 @@ class DateRangePicker {
1075
1173
  * @emits "beforeRenderTimePicker.daterangepicker"
1076
1174
  */
1077
1175
  renderTimePicker(side) {
1078
- if (side === "end" && !this.endDate) return;
1176
+ if (side === "end" && !this.#endDate) return;
1079
1177
  var selected, minLimit, minDate, maxDate = this.maxDate;
1080
1178
  let html = "";
1081
1179
  if (this.showWeekNumbers || this.showISOWeekNumbers)
1082
1180
  html += "<th></th>";
1083
- if (this.maxSpan && (!this.maxDate || this.startDate.plus(this.maxSpan) < this.maxDate))
1084
- maxDate = this.startDate.plus(this.maxSpan);
1181
+ if (this.maxSpan && (!this.maxDate || this.#startDate.plus(this.maxSpan) < this.maxDate))
1182
+ maxDate = this.#startDate.plus(this.maxSpan);
1085
1183
  if (this.minSpan && side === "end")
1086
- minLimit = this.startDate.plus(this.defaultSpan ?? this.minSpan);
1184
+ minLimit = this.#startDate.plus(this.defaultSpan ?? this.minSpan);
1087
1185
  if (side === "start") {
1088
- selected = this.startDate;
1186
+ selected = this.#startDate;
1089
1187
  minDate = this.minDate;
1090
1188
  } else if (side === "end") {
1091
- selected = this.endDate;
1092
- minDate = this.startDate;
1189
+ selected = this.#endDate;
1190
+ minDate = this.#startDate;
1093
1191
  var timeSelector = this.container.find(".drp-calendar .calendar-time.end-time");
1094
1192
  if (timeSelector.html() != "") {
1095
1193
  selected = selected.set({
@@ -1098,8 +1196,8 @@ class DateRangePicker {
1098
1196
  second: !isNaN(selected.second) ? selected.second : timeSelector.find(".secondselect option:selected").val()
1099
1197
  });
1100
1198
  }
1101
- if (selected < this.startDate)
1102
- selected = this.startDate;
1199
+ if (selected < this.#startDate)
1200
+ selected = this.#startDate;
1103
1201
  if (maxDate && selected > maxDate)
1104
1202
  selected = maxDate;
1105
1203
  }
@@ -1250,8 +1348,8 @@ class DateRangePicker {
1250
1348
  * Disable the `Apply` button if no date value is selected
1251
1349
  * @private
1252
1350
  */
1253
- updateFormInputs() {
1254
- if (this.singleDatePicker || this.endDate && this.startDate <= this.endDate) {
1351
+ setApplyBtnState() {
1352
+ if (this.singleDatePicker || this.#endDate && this.#startDate <= this.#endDate) {
1255
1353
  this.container.find("button.applyBtn").prop("disabled", false);
1256
1354
  } else {
1257
1355
  this.container.find("button.applyBtn").prop("disabled", true);
@@ -1359,9 +1457,8 @@ class DateRangePicker {
1359
1457
  $(window).on("resize.daterangepicker", function(e) {
1360
1458
  this.move(e);
1361
1459
  }.bind(this));
1362
- this.oldStartDate = this.startDate;
1363
- this.oldEndDate = this.endDate;
1364
- this.previousRightTime = this.endDate;
1460
+ this.oldStartDate = this.#startDate;
1461
+ this.oldEndDate = this.#endDate;
1365
1462
  this.updateView();
1366
1463
  this.container.show();
1367
1464
  this.move();
@@ -1375,11 +1472,11 @@ class DateRangePicker {
1375
1472
  */
1376
1473
  hide() {
1377
1474
  if (!this.isShowing) return;
1378
- if (!this.endDate) {
1379
- this.startDate = this.oldStartDate;
1380
- this.endDate = this.oldEndDate;
1475
+ if (!this.#endDate) {
1476
+ this.#startDate = this.oldStartDate;
1477
+ this.#endDate = this.oldEndDate;
1381
1478
  }
1382
- if (this.startDate != this.oldStartDate || this.endDate != this.oldEndDate)
1479
+ if (this.#startDate != this.oldStartDate || this.#endDate != this.oldEndDate)
1383
1480
  this.callback(this.startDate, this.endDate, this.chosenLabel);
1384
1481
  this.updateElement();
1385
1482
  if (this.element.triggerHandler("beforeHide.daterangepicker", this))
@@ -1413,8 +1510,8 @@ class DateRangePicker {
1413
1510
  e.type === "focusin" || target.closest(this.element).length || target.closest(this.container).length || target.closest(".calendar-table").length
1414
1511
  ) return;
1415
1512
  if (this.onOutsideClick === "cancel") {
1416
- this.startDate = this.oldStartDate;
1417
- this.endDate = this.oldEndDate;
1513
+ this.#startDate = this.oldStartDate;
1514
+ this.#endDate = this.oldEndDate;
1418
1515
  }
1419
1516
  this.hide();
1420
1517
  this.element.trigger("outsideClick.daterangepicker", this);
@@ -1448,11 +1545,11 @@ class DateRangePicker {
1448
1545
  this.showCalendars();
1449
1546
  } else {
1450
1547
  var dates = this.ranges[label];
1451
- this.startDate = dates[0];
1452
- this.endDate = dates[1];
1548
+ this.#startDate = dates[0];
1549
+ this.#endDate = dates[1];
1453
1550
  if (!this.timePicker) {
1454
- this.startDate.startOf("day");
1455
- this.endDate.endOf("day");
1551
+ this.#startDate.startOf("day");
1552
+ this.#endDate.endOf("day");
1456
1553
  }
1457
1554
  if (!this.alwaysShowCalendars)
1458
1555
  this.hideCalendars();
@@ -1507,9 +1604,9 @@ class DateRangePicker {
1507
1604
  var date = cal.hasClass("left") ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1508
1605
  const leftCalendar = this.leftCalendar;
1509
1606
  const rightCalendar = this.rightCalendar;
1510
- const startDate = this.startDate;
1607
+ const startDate = this.#startDate;
1511
1608
  const initalMonth = this.initalMonth;
1512
- if (!this.endDate) {
1609
+ if (!this.#endDate) {
1513
1610
  this.container.find(".drp-calendar tbody td").each(function(index, el) {
1514
1611
  if ($(el).hasClass("week")) return;
1515
1612
  const title2 = $(el).attr("data-title");
@@ -1536,8 +1633,8 @@ class DateRangePicker {
1536
1633
  */
1537
1634
  hoverRange(e) {
1538
1635
  const label = e.target.getAttribute("data-range-key");
1539
- const previousDates = [this.startDate, this.endDate];
1540
- const dates = this.ranges[label] ?? [this.startDate, this.endDate];
1636
+ const previousDates = [this.#startDate, this.#endDate];
1637
+ const dates = this.ranges[label] ?? [this.#startDate, this.#endDate];
1541
1638
  const leftCalendar = this.leftCalendar;
1542
1639
  const rightCalendar = this.rightCalendar;
1543
1640
  this.container.find(".drp-calendar tbody td").each(function(index, el) {
@@ -1597,7 +1694,7 @@ class DateRangePicker {
1597
1694
  var cal = $(e.target).parents(".drp-calendar");
1598
1695
  var date = cal.hasClass("left") ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1599
1696
  let side;
1600
- if (this.endDate || !this.startDate || date < this.startDate.startOf("day")) {
1697
+ if (this.#endDate || !this.#startDate || date < this.#startDate.startOf("day")) {
1601
1698
  if (this.timePicker) {
1602
1699
  let hour = parseInt(this.container.find(".start-time .hourselect").val(), 10);
1603
1700
  if (isNaN(hour))
@@ -1618,11 +1715,11 @@ class DateRangePicker {
1618
1715
  } else {
1619
1716
  date = date.startOf("day");
1620
1717
  }
1621
- this.endDate = null;
1622
- this.setStartDate(date, true);
1718
+ this.#endDate = null;
1719
+ this.#startDate = date;
1623
1720
  side = "start";
1624
- } else if (!this.endDate && date < this.startDate) {
1625
- this.setEndDate(this.startDate, true);
1721
+ } else if (!this.#endDate && date < this.#startDate) {
1722
+ this.#endDate = this.#startDate;
1626
1723
  side = "end";
1627
1724
  } else {
1628
1725
  if (this.timePicker) {
@@ -1645,7 +1742,7 @@ class DateRangePicker {
1645
1742
  } else {
1646
1743
  date = date.endOf("day");
1647
1744
  }
1648
- this.setEndDate(date, true);
1745
+ this.#endDate = date;
1649
1746
  if (this.autoApply) {
1650
1747
  this.calculateChosenLabel();
1651
1748
  this.clickApply();
@@ -1653,7 +1750,7 @@ class DateRangePicker {
1653
1750
  side = "end";
1654
1751
  }
1655
1752
  if (this.singleDatePicker) {
1656
- this.setEndDate(this.startDate, true);
1753
+ this.#endDate = this.#startDate;
1657
1754
  if (!this.timePicker && this.autoApply)
1658
1755
  this.clickApply();
1659
1756
  side = null;
@@ -1680,7 +1777,7 @@ class DateRangePicker {
1680
1777
  unit = "second";
1681
1778
  }
1682
1779
  }
1683
- if (this.startDate.startOf(unit).equals(this.ranges[range][0].startOf(unit)) && this.endDate.startOf(unit).equals(this.ranges[range][1].startOf(unit))) {
1780
+ if (this.#startDate.startOf(unit).equals(this.ranges[range][0].startOf(unit)) && this.#endDate.startOf(unit).equals(this.ranges[range][1].startOf(unit))) {
1684
1781
  customRange = false;
1685
1782
  this.chosenLabel = this.container.find(".ranges li:eq(" + i + ")").addClass("active").attr("data-range-key");
1686
1783
  break;
@@ -1711,8 +1808,8 @@ class DateRangePicker {
1711
1808
  * @private
1712
1809
  */
1713
1810
  clickCancel() {
1714
- this.startDate = this.oldStartDate;
1715
- this.endDate = this.oldEndDate;
1811
+ this.#startDate = this.oldStartDate;
1812
+ this.#endDate = this.oldEndDate;
1716
1813
  this.hide();
1717
1814
  this.element.trigger("cancel.daterangepicker", this);
1718
1815
  }
@@ -1726,9 +1823,9 @@ class DateRangePicker {
1726
1823
  var month = parseInt(cal.find(".monthselect").val(), 10);
1727
1824
  var year = cal.find(".yearselect").val();
1728
1825
  if (!isLeft) {
1729
- if (year < this.startDate.year || year == this.startDate.year && month < this.startDate.month) {
1730
- month = this.startDate.month;
1731
- year = this.startDate.year;
1826
+ if (year < this.#startDate.year || year == this.#startDate.year && month < this.#startDate.month) {
1827
+ month = this.#startDate.month;
1828
+ year = this.#startDate.year;
1732
1829
  }
1733
1830
  }
1734
1831
  if (this.minDate) {
@@ -1793,21 +1890,18 @@ class DateRangePicker {
1793
1890
  second = parseInt(time.find(".secondselect option:last").val(), 10);
1794
1891
  }
1795
1892
  if (side === "start") {
1796
- if (this.startDate) {
1797
- let start = this.startDate.set({ hour, minute, second });
1798
- this.setStartDate(start, true);
1799
- }
1893
+ if (this.#startDate)
1894
+ this.#startDate = this.#startDate.set({ hour, minute, second });
1800
1895
  if (this.singleDatePicker) {
1801
- this.endDate = this.startDate;
1802
- } else if (this.endDate && this.endDate.hasSame(this.startDate, "day") && this.endDate < this.startDate) {
1803
- this.setEndDate(this.startDate, true);
1896
+ this.#endDate = this.#startDate;
1897
+ } else if (this.#endDate && this.#endDate.hasSame(this.#startDate, "day") && this.#endDate < this.#startDate) {
1898
+ this.#endDate = this.#startDate;
1804
1899
  }
1805
- } else if (this.endDate) {
1806
- let end = this.endDate.set({ hour, minute, second });
1807
- this.setEndDate(end, true);
1900
+ } else if (this.#endDate) {
1901
+ this.#endDate = this.#endDate.set({ hour, minute, second });
1808
1902
  }
1809
1903
  this.updateCalendars();
1810
- this.updateFormInputs();
1904
+ this.setApplyBtnState();
1811
1905
  this.element.trigger("beforeRenderTimePicker.daterangepicker", this);
1812
1906
  this.renderTimePicker("start");
1813
1907
  this.renderTimePicker("end");
@@ -1816,36 +1910,53 @@ class DateRangePicker {
1816
1910
  this.element.trigger("timeChange.daterangepicker", [this, this.singleDatePicker ? null : side]);
1817
1911
  }
1818
1912
  /**
1819
- * Update the picker with value from attached `<input>` element.
1820
- * Error is written to console if input string cannot be parsed as a valid date/range
1821
- * @param {external:jQuery} e - The Event target
1913
+ * Update the picker with value from `<input>` element.<br>
1914
+ * Input values must be given in format of `locale.format`. Invalid values are handles by `violated.daterangepicker` Event
1822
1915
  * @emits "inputChanged.daterangepicker"
1823
1916
  * @private
1824
1917
  */
1825
- elementChanged(e) {
1826
- if (!this.element.is("input")) return;
1918
+ elementChanged() {
1919
+ if (!this.element.is("input:text")) return;
1827
1920
  if (!this.element.val().length) return;
1828
- const dateString = this.element.val().split(this.locale.separator);
1829
- var start = null, end = null;
1830
1921
  const format = typeof this.locale.format === "string" ? this.locale.format : DateTime.parseFormatForOpts(this.locale.format);
1831
- if (dateString.length === 2) {
1832
- start = DateTime.fromFormat(dateString[0], format, { locale: DateTime.now().locale });
1833
- end = DateTime.fromFormat(dateString[1], format, { locale: DateTime.now().locale });
1834
- }
1835
- if (this.singleDatePicker || start === null || end === null) {
1836
- start = DateTime.fromFormat(this.element.val(), format, { locale: DateTime.now().locale });
1837
- end = start;
1838
- }
1839
- if (!start.isValid || !end.isValid) {
1922
+ const dateString = this.element.val().split(this.locale.separator);
1923
+ if (this.singleDatePicker) {
1924
+ let newDate = DateTime.fromFormat(this.element.val(), format, { locale: DateTime.now().locale });
1925
+ const oldDate = this.#startDate;
1926
+ if (!newDate.isValid || oldDate.equals(newDate))
1927
+ return;
1928
+ const violations = this.validateInput([newDate, null], true);
1929
+ if (violations != null) {
1930
+ if (violations.newDate != null) {
1931
+ newDate = violations.newDate.startDate;
1932
+ } else {
1933
+ return null;
1934
+ }
1935
+ }
1936
+ this.#startDate = newDate;
1937
+ this.#endDate = this.#startDate;
1938
+ } else if (!this.singleDatePicker && dateString.length === 2) {
1939
+ const newDate = [1, 2].map((i) => DateTime.fromFormat(dateString[i], format, { locale: DateTime.now().locale }));
1940
+ const oldDate = [this.#startDate, this.#endDate];
1941
+ if (!newDate[0].isValid || !newDate[1].isValid || (oldDate[0].equals(newDate[0]) && oldDate[1].equals(newDate[1]) || newDate[1] > newDate[0]))
1942
+ return;
1943
+ const violations = this.validateInput([newDate[0], newDate[1]], true);
1944
+ if (violations != null) {
1945
+ if (violations.newDate != null) {
1946
+ newDate[0] = violations.newDate.startDate;
1947
+ newDate[1] = violations.newDate.endDate;
1948
+ } else {
1949
+ return;
1950
+ }
1951
+ }
1952
+ this.#startDate = newDate[0];
1953
+ this.#endDate = newDate[1];
1954
+ } else {
1840
1955
  return;
1841
1956
  }
1842
- const trigger = this.startDate != start || !this.singleDatePicker && this.endDate != end;
1843
- this.setStartDate(start, false);
1844
- this.setEndDate(end, false);
1845
1957
  this.updateView();
1846
- this.updateAltInput();
1847
- if (trigger)
1848
- this.element.trigger("inputChanged.daterangepicker", this);
1958
+ this.updateElement();
1959
+ this.element.trigger("inputChanged.daterangepicker", this);
1849
1960
  }
1850
1961
  /**
1851
1962
  * Handles key press, IE 11 compatibility
@@ -1867,14 +1978,14 @@ class DateRangePicker {
1867
1978
  * @emits external:change
1868
1979
  */
1869
1980
  updateElement() {
1870
- if (this.startDate == null && this.initalMonth)
1981
+ if (this.#startDate == null && this.initalMonth)
1871
1982
  return;
1872
- if (this.element.is("input")) {
1873
- let newValue = this.formatDate(this.startDate);
1983
+ if (this.element.is("input:text")) {
1984
+ let newValue = this.formatDate(this.#startDate);
1874
1985
  if (!this.singleDatePicker) {
1875
1986
  newValue += this.locale.separator;
1876
- if (this.endDate)
1877
- newValue += this.formatDate(this.endDate);
1987
+ if (this.#endDate)
1988
+ newValue += this.formatDate(this.#endDate);
1878
1989
  }
1879
1990
  this.updateAltInput();
1880
1991
  if (newValue !== this.element.val())
@@ -1889,7 +2000,7 @@ class DateRangePicker {
1889
2000
  updateAltInput() {
1890
2001
  if (this.altInput == null)
1891
2002
  return;
1892
- if (!this.singleDatePicker && !this.endDate)
2003
+ if (this.singleDatePicker)
1893
2004
  $(this.altInput[1]).val(null);
1894
2005
  if (this.altFormat == null) {
1895
2006
  let precision = "day";
@@ -1902,26 +2013,18 @@ class DateRangePicker {
1902
2013
  precision = "hour";
1903
2014
  }
1904
2015
  }
1905
- const startDate = this.startDate.toISO({ format: "basic", precision, includeOffset: false });
1906
- if (this.singleDatePicker) {
1907
- $(this.altInput).val(startDate);
1908
- } else {
1909
- $(this.altInput[0]).val(startDate);
1910
- if (this.endDate) {
1911
- const endDate2 = this.endDate.toISO({ format: "basic", precision, includeOffset: false });
1912
- $(this.altInput[1]).val(endDate2);
1913
- }
2016
+ const startDate = this.#startDate.toISO({ format: "basic", precision, includeOffset: false });
2017
+ $(this.singleDatePicker ? this.altInput : this.altInput[0]).val(startDate);
2018
+ if (!this.singleDatePicker && this.#endDate) {
2019
+ const endDate = this.#endDate.toISO({ format: "basic", precision, includeOffset: false });
2020
+ $(this.altInput[1]).val(endDate);
1914
2021
  }
1915
2022
  } else {
1916
- const startDate = typeof this.altFormat === "function" ? this.altFormat(this.startDate) : this.formatDate(this.startDate, this.altFormat);
1917
- if (this.singleDatePicker) {
1918
- $(this.altInput).val(startDate);
1919
- } else {
1920
- $(this.altInput[0]).val(startDate);
1921
- if (this.endDate) {
1922
- const endDate2 = typeof this.altFormat === "function" ? this.altFormat(this.endDate) : this.formatDate(this.endDate, this.altFormat);
1923
- $(this.altInput[1]).val(endDate2);
1924
- }
2023
+ const startDate = typeof this.altFormat === "function" ? this.altFormat(this.#startDate) : this.formatDate(this.#startDate, this.altFormat);
2024
+ $(this.singleDatePicker ? this.altInput : this.altInput[0]).val(startDate);
2025
+ if (!this.singleDatePicker && this.#endDate) {
2026
+ const endDate = typeof this.altFormat === "function" ? this.altFormat(this.#endDate) : this.formatDate(this.#endDate, this.altFormat);
2027
+ $(this.altInput[1]).val(endDate);
1925
2028
  }
1926
2029
  }
1927
2030
  }