@rolldate/mcp 1.0.0 → 1.3.1
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.
- package/AGENTS.md +12 -3
- package/README.md +39 -12
- package/bin/rolldate-mcp.js +2 -2
- package/package.json +6 -3
- package/src/catalog-events.js +241 -0
- package/src/catalog.js +81 -10
- package/src/index.js +121 -97
- package/src/products.js +73 -0
- package/templates/AGENTS.md +12 -3
- package/templates/rolldate-mcp.mdc +17 -4
- package/vendor/rolldate/rolldate.css +394 -151
- package/vendor/rolldate/rolldate.js +1298 -661
- package/vendor/rolldate/rolldate.min.css +1 -1
- package/vendor/rolldate/rolldate.min.js +1 -1
- package/vendor/rolldate-events/rolldate-events.css +1 -0
- package/vendor/rolldate-events/rolldate-events.mjs +2 -0
|
@@ -6,9 +6,6 @@
|
|
|
6
6
|
var RollDate = (function () {
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
function getTranslation(text) {
|
|
10
|
-
return text
|
|
11
|
-
}
|
|
12
9
|
function getDecade(year) {
|
|
13
10
|
return Math.floor(year / 10) * 10
|
|
14
11
|
}
|
|
@@ -453,50 +450,59 @@ var RollDate = (function () {
|
|
|
453
450
|
this.startWeekFromMonday = options.startWeekFromMonday;
|
|
454
451
|
this.monthsNames = options.monthsNames;
|
|
455
452
|
this.monthsShortNames = options.monthsShortNames;
|
|
453
|
+
this.weekDays = Array.isArray(options.weekDays) && options.weekDays.length === 7
|
|
454
|
+
? options.weekDays
|
|
455
|
+
: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
456
456
|
this.enableTime = Boolean(options.enableTime);
|
|
457
|
-
this.hasFooter = Boolean(options.
|
|
457
|
+
this.hasFooter = Boolean(options.footerButtons && options.footerButtons.length);
|
|
458
458
|
|
|
459
459
|
this.init();
|
|
460
460
|
}
|
|
461
461
|
|
|
462
462
|
init() {
|
|
463
|
+
const timePanelHtml = this.enableTime
|
|
464
|
+
? '<div class="RollDate__time-separator" aria-hidden="true"></div><div class="RollDate__time-panel"><div class="RollDate__time"></div></div>'
|
|
465
|
+
: '';
|
|
466
|
+
|
|
463
467
|
const footerHtml = this.hasFooter ? `
|
|
464
468
|
<div class="RollDate__footer">
|
|
465
|
-
${this.enableTime ? '<div class="RollDate__time"></div>' : ''}
|
|
466
469
|
<div class="RollDate__footer__buttons"></div>
|
|
467
470
|
</div>
|
|
468
471
|
` : '';
|
|
469
472
|
|
|
470
473
|
this.$container.innerHTML = `
|
|
471
|
-
<div class="
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
<div class="
|
|
474
|
+
<div class="RollDate__content">
|
|
475
|
+
<div class="RollDate__calendar">
|
|
476
|
+
<div class="RollDate__header">
|
|
477
|
+
<div class="RollDate__calendar__switcher">
|
|
478
|
+
<div class="RollDate__header__year"></div>
|
|
479
|
+
<div class="RollDate__header__month"></div>
|
|
480
|
+
</div>
|
|
481
|
+
<div class="RollDate__calendar__buttons">
|
|
482
|
+
<button class="RollDate__calendar__button" data-direction="prev">
|
|
483
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 61 55" width="55" height="50">
|
|
484
|
+
<path id="Форма 2" fill-rule="evenodd" class="s0" d="m52.76 54.67l-44.73 0.12c-6.16 0.02-10.02-6.64-6.96-11.98l22.26-38.79c3.07-5.35 10.76-5.37 13.86-0.04l22.47 38.67c3.09 5.33-0.74 12.01-6.9 12.02z"/>
|
|
485
|
+
</svg>
|
|
486
|
+
</button>
|
|
487
|
+
<button class="RollDate__calendar__button" data-direction="next">
|
|
488
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 61 55" width="55" height="50">
|
|
489
|
+
<path id="Форма 2" fill-rule="evenodd" class="s0" d="m52.76 54.67l-44.73 0.12c-6.16 0.02-10.02-6.64-6.96-11.98l22.26-38.79c3.07-5.35 10.76-5.37 13.86-0.04l22.47 38.67c3.09 5.33-0.74 12.01-6.9 12.02z" />
|
|
490
|
+
</svg>
|
|
491
|
+
</button>
|
|
492
|
+
</div>
|
|
475
493
|
</div>
|
|
476
|
-
<div class="
|
|
477
|
-
|
|
478
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 61 55" width="55" height="50">
|
|
479
|
-
<path id="Форма 2" fill-rule="evenodd" class="s0" d="m52.76 54.67l-44.73 0.12c-6.16 0.02-10.02-6.64-6.96-11.98l22.26-38.79c3.07-5.35 10.76-5.37 13.86-0.04l22.47 38.67c3.09 5.33-0.74 12.01-6.9 12.02z"/>
|
|
480
|
-
</svg>
|
|
481
|
-
</button>
|
|
482
|
-
<button class="RollDate__calendar__button" data-direction="next">
|
|
483
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 61 55" width="55" height="50">
|
|
484
|
-
<path id="Форма 2" fill-rule="evenodd" class="s0" d="m52.76 54.67l-44.73 0.12c-6.16 0.02-10.02-6.64-6.96-11.98l22.26-38.79c3.07-5.35 10.76-5.37 13.86-0.04l22.47 38.67c3.09 5.33-0.74 12.01-6.9 12.02z" />
|
|
485
|
-
</svg>
|
|
486
|
-
</button>
|
|
494
|
+
<div class="RollDate__calendar__header">
|
|
495
|
+
${this.#dayHeaders()}
|
|
487
496
|
</div>
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
<div class="RollDate__calendar__scrollblock">
|
|
495
|
-
<div class="RollDate__calendar__days"></div>
|
|
496
|
-
<div class="RollDate__calendar__months"></div>
|
|
497
|
-
<div class="RollDate__calendar__years"></div>
|
|
497
|
+
<div class="RollDate__calendar__body">
|
|
498
|
+
<div class="RollDate__calendar__scrollblock">
|
|
499
|
+
<div class="RollDate__calendar__days"></div>
|
|
500
|
+
<div class="RollDate__calendar__months"></div>
|
|
501
|
+
<div class="RollDate__calendar__years"></div>
|
|
502
|
+
</div>
|
|
498
503
|
</div>
|
|
499
504
|
</div>
|
|
505
|
+
${timePanelHtml}
|
|
500
506
|
</div>
|
|
501
507
|
${footerHtml}
|
|
502
508
|
`;
|
|
@@ -509,48 +515,77 @@ var RollDate = (function () {
|
|
|
509
515
|
}
|
|
510
516
|
|
|
511
517
|
#dayHeaders() {
|
|
512
|
-
const weekDays =
|
|
518
|
+
const weekDays = this.weekDays;
|
|
513
519
|
let header = '';
|
|
514
520
|
const startIndex = Number(this.startWeekFromMonday);
|
|
515
521
|
for (let i = startIndex; i < 7 + startIndex; i++) {
|
|
516
|
-
header += `<div class="RollDate__calendar__header__weekday">${
|
|
522
|
+
header += `<div class="RollDate__calendar__header__weekday">${weekDays[i % 7]}</div>`;
|
|
517
523
|
}
|
|
518
524
|
return header
|
|
519
525
|
}
|
|
520
526
|
|
|
521
|
-
|
|
527
|
+
#highlightDots(colors = [], maxVisible = 5) {
|
|
528
|
+
if (!colors.length) return ''
|
|
529
|
+
|
|
530
|
+
const visible = colors.slice(0, maxVisible);
|
|
531
|
+
const extra = colors.length - maxVisible;
|
|
532
|
+
const many = visible.length > 3;
|
|
533
|
+
const dotClass = many
|
|
534
|
+
? 'RollDate__calendar__day-dot RollDate__calendar__day-dot--compact'
|
|
535
|
+
: 'RollDate__calendar__day-dot';
|
|
536
|
+
|
|
537
|
+
const dots = visible.map(color => {
|
|
538
|
+
if (color) {
|
|
539
|
+
return `<span class="${dotClass} RollDate__calendar__day-dot--custom" style="--rd-highlight-dot:${color}"></span>`
|
|
540
|
+
}
|
|
541
|
+
return `<span class="${dotClass}"></span>`
|
|
542
|
+
}).join('');
|
|
543
|
+
|
|
544
|
+
const more = extra > 0
|
|
545
|
+
? `<span class="RollDate__calendar__day-dot RollDate__calendar__day-dot--more">+${extra}</span>`
|
|
546
|
+
: '';
|
|
547
|
+
|
|
548
|
+
return `<span class="RollDate__calendar__day-dots" aria-hidden="true">${dots}${more}</span>`
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
dates(array, selectedDates = [], selectType = 'single', getHighlight = () => null) {
|
|
522
552
|
let datesHtml = '';
|
|
523
553
|
const today = new Date();
|
|
524
554
|
|
|
525
555
|
for (let i = 0; i < array.length; i++) {
|
|
526
556
|
const date = array[i].date;
|
|
527
557
|
const disabledClass = array[i].disabled ? 'RollDate__calendar__day--disabled' : '';
|
|
558
|
+
const highlight = getHighlight(date);
|
|
559
|
+
const highlightDots = highlight ? this.#highlightDots(highlight.colors) : '';
|
|
528
560
|
const isToday = date.toDateString() === today.toDateString();
|
|
529
561
|
|
|
530
562
|
// Resolve selection class for current date cell.
|
|
531
563
|
let selectionClass = '';
|
|
532
564
|
if (selectType === 'single' || selectType === 'multi') {
|
|
565
|
+
const dayStamp = date.getTime();
|
|
533
566
|
const isSelected = selectedDates.some(
|
|
534
|
-
d => d.
|
|
567
|
+
d => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime() === dayStamp
|
|
535
568
|
);
|
|
536
569
|
if (isSelected) selectionClass = 'RollDate__calendar__day--selected';
|
|
537
570
|
} else if (selectType === 'range' && selectedDates.length === 2) {
|
|
538
571
|
const [start, end] = selectedDates;
|
|
539
572
|
const time = date.getTime();
|
|
540
|
-
|
|
573
|
+
const startStamp = new Date(start.getFullYear(), start.getMonth(), start.getDate()).getTime();
|
|
574
|
+
const endStamp = new Date(end.getFullYear(), end.getMonth(), end.getDate()).getTime();
|
|
575
|
+
if (time === startStamp) {
|
|
541
576
|
selectionClass = 'RollDate__calendar__day--range-first';
|
|
542
|
-
} else if (time ===
|
|
577
|
+
} else if (time === endStamp) {
|
|
543
578
|
selectionClass = 'RollDate__calendar__day--range-last';
|
|
544
|
-
} else if (time >
|
|
579
|
+
} else if (time > startStamp && time < endStamp) {
|
|
545
580
|
selectionClass = 'RollDate__calendar__day--range-selected';
|
|
546
581
|
}
|
|
547
582
|
}
|
|
548
583
|
|
|
549
|
-
datesHtml += `<div class="RollDate__calendar__day ${disabledClass} ${isToday ? 'RollDate__calendar__day--today' : ''} ${selectionClass}"
|
|
584
|
+
datesHtml += `<div class="RollDate__calendar__day ${disabledClass} ${isToday ? 'RollDate__calendar__day--today' : ''} ${selectionClass}"
|
|
550
585
|
data-bind='["year", "month"]'
|
|
551
586
|
data-year="${date.getFullYear()}"
|
|
552
587
|
data-month="${date.getMonth()}"
|
|
553
|
-
data-day="${date.getDate()}">${date.getDate()}</div>`;
|
|
588
|
+
data-day="${date.getDate()}">${date.getDate()}${highlightDots}</div>`;
|
|
554
589
|
}
|
|
555
590
|
|
|
556
591
|
return datesHtml
|
|
@@ -610,502 +645,531 @@ var RollDate = (function () {
|
|
|
610
645
|
}
|
|
611
646
|
}
|
|
612
647
|
|
|
613
|
-
class Scroll {
|
|
614
|
-
#minScroll = null
|
|
615
|
-
#EDGE_TOP = 25
|
|
616
|
-
#EDGE_BOTTOM = 75
|
|
617
|
-
#offset
|
|
618
|
-
#baseOffset = 0
|
|
619
|
-
#blocked
|
|
620
|
-
#boundWheelHandler
|
|
621
|
-
#boundTouchStartHandler
|
|
622
|
-
#boundTouchMoveHandler
|
|
623
|
-
#boundTouchEndHandler
|
|
624
|
-
#isWheelAnimating = false
|
|
625
|
-
#edgeTriggeredInCurrentWheel = false
|
|
626
|
-
#lastEdgeTriggerAt = 0
|
|
627
|
-
#isTouchDragging = false
|
|
628
|
-
#touchLastY = 0
|
|
629
|
-
#touchLastTime = 0
|
|
630
|
-
#
|
|
631
|
-
#
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
this.$
|
|
640
|
-
this
|
|
641
|
-
this.
|
|
642
|
-
this.
|
|
643
|
-
this.
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
this
|
|
649
|
-
this.#
|
|
650
|
-
this.#
|
|
651
|
-
this.#
|
|
652
|
-
this
|
|
653
|
-
this.$body.addEventListener('
|
|
654
|
-
this.$body.addEventListener('
|
|
655
|
-
this.$body.addEventListener('
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
e.
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
this.#
|
|
695
|
-
this.#
|
|
696
|
-
this.#
|
|
697
|
-
this.#
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
this.#
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
this.#
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
this.
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
this.#
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
this.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (this.#
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
this
|
|
852
|
-
this
|
|
853
|
-
}
|
|
854
|
-
|
|
648
|
+
class Scroll {
|
|
649
|
+
#minScroll = null
|
|
650
|
+
#EDGE_TOP = 25
|
|
651
|
+
#EDGE_BOTTOM = 75
|
|
652
|
+
#offset
|
|
653
|
+
#baseOffset = 0
|
|
654
|
+
#blocked
|
|
655
|
+
#boundWheelHandler
|
|
656
|
+
#boundTouchStartHandler
|
|
657
|
+
#boundTouchMoveHandler
|
|
658
|
+
#boundTouchEndHandler
|
|
659
|
+
#isWheelAnimating = false
|
|
660
|
+
#edgeTriggeredInCurrentWheel = false
|
|
661
|
+
#lastEdgeTriggerAt = 0
|
|
662
|
+
#isTouchDragging = false
|
|
663
|
+
#touchLastY = 0
|
|
664
|
+
#touchLastTime = 0
|
|
665
|
+
#touchVelocity = 0
|
|
666
|
+
#momentumId = 0
|
|
667
|
+
#animId = 0
|
|
668
|
+
|
|
669
|
+
constructor(body, methods = {
|
|
670
|
+
dominant: () => console.error('Function "dominant" is not found'),
|
|
671
|
+
updatePeriod: () => console.error('Function "updatePeriod" is not found')
|
|
672
|
+
}) {
|
|
673
|
+
|
|
674
|
+
this.$body = body;
|
|
675
|
+
this.$scroll_block = this.$body.querySelector('.RollDate__calendar__scrollblock');
|
|
676
|
+
this.dominant = methods.dominant;
|
|
677
|
+
this.updatePeriod = methods.updatePeriod;
|
|
678
|
+
this.offset = 0;
|
|
679
|
+
this.init();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
init() {
|
|
683
|
+
this.#boundWheelHandler = this.wheelHandler.bind(this);
|
|
684
|
+
this.$body.addEventListener('wheel', this.#boundWheelHandler, { passive: false });
|
|
685
|
+
this.#boundTouchStartHandler = this.touchStartHandler.bind(this);
|
|
686
|
+
this.#boundTouchMoveHandler = this.touchMoveHandler.bind(this);
|
|
687
|
+
this.#boundTouchEndHandler = this.touchEndHandler.bind(this);
|
|
688
|
+
this.$body.addEventListener('touchstart', this.#boundTouchStartHandler, { passive: true });
|
|
689
|
+
this.$body.addEventListener('touchmove', this.#boundTouchMoveHandler, { passive: false });
|
|
690
|
+
this.$body.addEventListener('touchend', this.#boundTouchEndHandler, { passive: true });
|
|
691
|
+
this.$body.addEventListener('touchcancel', this.#boundTouchEndHandler, { passive: true });
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
wheelHandler(e) {
|
|
695
|
+
e.preventDefault();
|
|
696
|
+
e.stopPropagation();
|
|
697
|
+
this.#cancelMomentum();
|
|
698
|
+
|
|
699
|
+
const rawDelta = e.deltaY * 0.85;
|
|
700
|
+
const steps = Math.abs(rawDelta) > 50 ? 20 : 1;
|
|
701
|
+
const stepSize = rawDelta / steps;
|
|
702
|
+
const currentWheelDirection = rawDelta > 0 ? 'down' : 'up';
|
|
703
|
+
this.#isWheelAnimating = true;
|
|
704
|
+
this.#edgeTriggeredInCurrentWheel = false;
|
|
705
|
+
|
|
706
|
+
let i = 0;
|
|
707
|
+
const animate = () => {
|
|
708
|
+
if (i >= steps) {
|
|
709
|
+
this.#isWheelAnimating = false;
|
|
710
|
+
this.#edgeTriggeredInCurrentWheel = false;
|
|
711
|
+
return
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
this.offset -= stepSize;
|
|
715
|
+
|
|
716
|
+
requestAnimationFrame(() => {
|
|
717
|
+
this.dominant();
|
|
718
|
+
this.checkEdge(currentWheelDirection);
|
|
719
|
+
i++;
|
|
720
|
+
animate();
|
|
721
|
+
});
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
animate();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
touchStartHandler(e) {
|
|
728
|
+
if (!e.touches || e.touches.length !== 1) return
|
|
729
|
+
this.#cancelMomentum();
|
|
730
|
+
this.#isTouchDragging = true;
|
|
731
|
+
this.#touchLastY = e.touches[0].clientY;
|
|
732
|
+
this.#touchLastTime = performance.now();
|
|
733
|
+
this.#touchVelocity = 0;
|
|
734
|
+
this.#edgeTriggeredInCurrentWheel = false;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
touchMoveHandler(e) {
|
|
738
|
+
if (!this.#isTouchDragging || !e.touches || e.touches.length !== 1) return
|
|
739
|
+
e.preventDefault();
|
|
740
|
+
|
|
741
|
+
const currentY = e.touches[0].clientY;
|
|
742
|
+
const deltaY = currentY - this.#touchLastY;
|
|
743
|
+
const now = performance.now();
|
|
744
|
+
const dt = now - this.#touchLastTime;
|
|
745
|
+
if (dt > 0 && dt < 120) {
|
|
746
|
+
this.#touchVelocity = this.#touchVelocity * 0.65 + (deltaY / dt) * 0.35;
|
|
747
|
+
}
|
|
748
|
+
this.#touchLastY = currentY;
|
|
749
|
+
this.#touchLastTime = now;
|
|
750
|
+
|
|
751
|
+
if (Math.abs(deltaY) < 1) return
|
|
752
|
+
|
|
753
|
+
this.offset += deltaY;
|
|
754
|
+
this.dominant();
|
|
755
|
+
|
|
756
|
+
const direction = deltaY < 0 ? 'down' : 'up';
|
|
757
|
+
this.checkEdge(direction);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
touchEndHandler() {
|
|
761
|
+
this.#isTouchDragging = false;
|
|
762
|
+
this.#edgeTriggeredInCurrentWheel = false;
|
|
763
|
+
|
|
764
|
+
const v = this.#touchVelocity * 16;
|
|
765
|
+
if (Math.abs(v) < 0.4) return
|
|
766
|
+
|
|
767
|
+
let velocity = v;
|
|
768
|
+
const step = () => {
|
|
769
|
+
if (Math.abs(velocity) < 0.35) {
|
|
770
|
+
this.#momentumId = 0;
|
|
771
|
+
return
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const prevOffset = this.#offset;
|
|
775
|
+
this.offset += velocity;
|
|
776
|
+
if (this.#offset === prevOffset) {
|
|
777
|
+
this.#momentumId = 0;
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
this.dominant();
|
|
782
|
+
const direction = velocity < 0 ? 'down' : 'up';
|
|
783
|
+
this.checkEdge(direction);
|
|
784
|
+
|
|
785
|
+
velocity *= 0.92;
|
|
786
|
+
this.#momentumId = requestAnimationFrame(step);
|
|
787
|
+
};
|
|
788
|
+
this.#momentumId = requestAnimationFrame(step);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
#cancelMomentum() {
|
|
792
|
+
if (!this.#momentumId) return
|
|
793
|
+
cancelAnimationFrame(this.#momentumId);
|
|
794
|
+
this.#momentumId = 0;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
#cancelAnimate() {
|
|
798
|
+
if (!this.#animId) return
|
|
799
|
+
cancelAnimationFrame(this.#animId);
|
|
800
|
+
this.#animId = 0;
|
|
801
|
+
this.blocked = false;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
animateTo(targetOffset, opts = {}) {
|
|
805
|
+
this.#cancelMomentum();
|
|
806
|
+
this.#cancelAnimate();
|
|
807
|
+
|
|
808
|
+
const duration = opts.duration ?? 220;
|
|
809
|
+
const start = this.#offset;
|
|
810
|
+
const min = this.minScroll;
|
|
811
|
+
const target = Math.max(Math.min(targetOffset, 0), min);
|
|
812
|
+
|
|
813
|
+
if (Math.abs(target - start) < 1) {
|
|
814
|
+
this.offset = target;
|
|
815
|
+
this.dominant();
|
|
816
|
+
opts.onComplete?.();
|
|
817
|
+
return Promise.resolve()
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
this.blocked = true;
|
|
821
|
+
const startTime = performance.now();
|
|
822
|
+
|
|
823
|
+
return new Promise(resolve => {
|
|
824
|
+
const step = (now) => {
|
|
825
|
+
const t = Math.min(1, (now - startTime) / duration);
|
|
826
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
827
|
+
this.offset = start + (target - start) * eased;
|
|
828
|
+
|
|
829
|
+
if (t < 1) {
|
|
830
|
+
this.#animId = requestAnimationFrame(step);
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
this.offset = target;
|
|
835
|
+
this.blocked = false;
|
|
836
|
+
this.dominant();
|
|
837
|
+
this.#animId = 0;
|
|
838
|
+
opts.onComplete?.();
|
|
839
|
+
resolve();
|
|
840
|
+
};
|
|
841
|
+
this.#animId = requestAnimationFrame(step);
|
|
842
|
+
})
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
apply() {
|
|
846
|
+
this.$scroll_block.style.transform = `translateY(${this.offset + this.#baseOffset}px)`;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
get offset() { return this.#offset }
|
|
850
|
+
set offset(value) {
|
|
851
|
+
|
|
852
|
+
this.#offset = value;
|
|
853
|
+
|
|
854
|
+
if ( this.#minScroll !== null ) {
|
|
855
|
+
if (value >= 0) this.#offset = 0;
|
|
856
|
+
else if (value <= this.#minScroll) this.#offset = this.#minScroll;
|
|
857
|
+
}
|
|
858
|
+
this.apply();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
get blocked() { return this.#blocked }
|
|
862
|
+
set blocked(value) { this.#blocked = value; }
|
|
863
|
+
get minScroll() {
|
|
864
|
+
if (this.#minScroll === null) this.checkMinScroll();
|
|
865
|
+
return this.#minScroll ?? 0
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
checkEdge(direction) {
|
|
869
|
+
if (this.#minScroll === null) {
|
|
870
|
+
this.checkMinScroll();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (this.#minScroll === 0 || this.blocked) return
|
|
874
|
+
if (this.#isWheelAnimating && this.#edgeTriggeredInCurrentWheel) return
|
|
875
|
+
|
|
876
|
+
const percent = Math.abs(this.offset / this.#minScroll) * 100;
|
|
877
|
+
const now = Date.now();
|
|
878
|
+
const EDGE_COOLDOWN_MS = 60;
|
|
879
|
+
|
|
880
|
+
if (now - this.#lastEdgeTriggerAt < EDGE_COOLDOWN_MS) return
|
|
881
|
+
|
|
882
|
+
if (percent < this.#EDGE_TOP) {
|
|
883
|
+
if (direction !== 'up') return
|
|
884
|
+
this.#lastEdgeTriggerAt = now;
|
|
885
|
+
this.#edgeTriggeredInCurrentWheel = true;
|
|
886
|
+
this.blocked = true;
|
|
887
|
+
this.updatePeriod('up');
|
|
888
|
+
} else if (percent > this.#EDGE_BOTTOM) {
|
|
889
|
+
if (direction !== 'down') return
|
|
890
|
+
this.#lastEdgeTriggerAt = now;
|
|
891
|
+
this.#edgeTriggeredInCurrentWheel = true;
|
|
892
|
+
this.blocked = true;
|
|
893
|
+
this.updatePeriod('down');
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
checkMinScroll() {
|
|
898
|
+
const scrollHeight = this.$scroll_block.clientHeight;
|
|
899
|
+
const bodyHeight = this.$body.clientHeight;
|
|
900
|
+
this.#minScroll = scrollHeight > bodyHeight ? -(scrollHeight - bodyHeight) : 0;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
setBaseOffset(value = 0) {
|
|
904
|
+
this.#baseOffset = value;
|
|
905
|
+
this.apply();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
resetMinScroll() {
|
|
909
|
+
this.#minScroll = null;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
destroy() {
|
|
913
|
+
this.#cancelMomentum();
|
|
914
|
+
this.#cancelAnimate();
|
|
915
|
+
if (this.#boundWheelHandler) {
|
|
916
|
+
this.$body.removeEventListener('wheel', this.#boundWheelHandler);
|
|
917
|
+
}
|
|
918
|
+
if (this.#boundTouchStartHandler) {
|
|
919
|
+
this.$body.removeEventListener('touchstart', this.#boundTouchStartHandler);
|
|
920
|
+
}
|
|
921
|
+
if (this.#boundTouchMoveHandler) {
|
|
922
|
+
this.$body.removeEventListener('touchmove', this.#boundTouchMoveHandler);
|
|
923
|
+
}
|
|
924
|
+
if (this.#boundTouchEndHandler) {
|
|
925
|
+
this.$body.removeEventListener('touchend', this.#boundTouchEndHandler);
|
|
926
|
+
this.$body.removeEventListener('touchcancel', this.#boundTouchEndHandler);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
855
929
|
}
|
|
856
930
|
|
|
857
|
-
class Virtualizer {
|
|
858
|
-
constructor(context) {
|
|
859
|
-
this.ctx = context;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (this.ctx.data.
|
|
882
|
-
this.ctx.scroll.blocked = false;
|
|
883
|
-
return
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
new Date(
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
new Date(
|
|
932
|
-
this.ctx
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
newDates.
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
this.ctx.
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
this.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
const newHeight = block.scrollHeight;
|
|
1096
|
-
const heightDelta = oldHeight - newHeight;
|
|
1097
|
-
this.ctx.scroll.checkMinScroll();
|
|
1098
|
-
|
|
1099
|
-
if (isUp) this.ctx.scroll.offset -= heightDelta;
|
|
1100
|
-
else this.ctx.scroll.offset += heightDelta;
|
|
1101
|
-
}
|
|
931
|
+
class Virtualizer {
|
|
932
|
+
constructor(context) {
|
|
933
|
+
this.ctx = context;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
update(direction) {
|
|
937
|
+
const period = this.ctx.period;
|
|
938
|
+
const isUp = direction === 'up';
|
|
939
|
+
|
|
940
|
+
// 🔑 Точна перевірка меж
|
|
941
|
+
if (isUp) {
|
|
942
|
+
if (period === 'year') {
|
|
943
|
+
if (this.ctx.data.up_date.getFullYear() <= this.ctx.options.minDate.getFullYear()) {
|
|
944
|
+
this.ctx.scroll.blocked = false;
|
|
945
|
+
return
|
|
946
|
+
}
|
|
947
|
+
} else {
|
|
948
|
+
if (this.ctx.data.up_date <= this.ctx.options.minDate) {
|
|
949
|
+
this.ctx.scroll.blocked = false;
|
|
950
|
+
return
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
} else {
|
|
954
|
+
if (period === 'year') {
|
|
955
|
+
if (this.ctx.data.down_date.getFullYear() >= this.ctx.options.maxDate.getFullYear()) {
|
|
956
|
+
this.ctx.scroll.blocked = false;
|
|
957
|
+
return
|
|
958
|
+
}
|
|
959
|
+
} else {
|
|
960
|
+
if (this.ctx.data.down_date >= this.ctx.options.maxDate) {
|
|
961
|
+
this.ctx.scroll.blocked = false;
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const config = {
|
|
968
|
+
day: {
|
|
969
|
+
count: 35,
|
|
970
|
+
getNewItems: (upDate, downDate) => {
|
|
971
|
+
const newDates = [];
|
|
972
|
+
if (isUp) {
|
|
973
|
+
let newFirst = new Date(upDate);
|
|
974
|
+
newFirst.setDate(upDate.getDate() - 35);
|
|
975
|
+
if (newFirst < this.ctx.options.minDate) {
|
|
976
|
+
const minDate = this.ctx.options.minDate;
|
|
977
|
+
newFirst = adjustToWeekStart(
|
|
978
|
+
new Date(minDate.getFullYear(), minDate.getMonth(), 1),
|
|
979
|
+
this.ctx.options.startWeekFromMonday
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
for (let d = new Date(newFirst); d < upDate; d.setDate(d.getDate() + 1)) {
|
|
983
|
+
newDates.push({
|
|
984
|
+
date: new Date(d),
|
|
985
|
+
disabled: this.ctx?.options?.minDate > new Date(d) || false
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
return { items: newDates, newBound: newFirst }
|
|
989
|
+
} else {
|
|
990
|
+
let newLast = new Date(downDate);
|
|
991
|
+
newLast.setDate(downDate.getDate() + 35);
|
|
992
|
+
const firstNew = new Date(downDate);
|
|
993
|
+
firstNew.setDate(downDate.getDate() + 1);
|
|
994
|
+
|
|
995
|
+
if (newLast > this.ctx.options.maxDate) {
|
|
996
|
+
const maxDate = this.ctx.options.maxDate;
|
|
997
|
+
newLast = adjustToWeekEnd(
|
|
998
|
+
new Date(maxDate.getFullYear(), maxDate.getMonth() + 1, 0),
|
|
999
|
+
this.ctx.options.startWeekFromMonday
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
for (let d = firstNew; d <= newLast; d.setDate(d.getDate() + 1)) {
|
|
1004
|
+
newDates.push({
|
|
1005
|
+
date: new Date(d),
|
|
1006
|
+
disabled: this.ctx?.options?.maxDate < new Date(d) || false
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
return { items: newDates, newBound: newLast }
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
render: dates => this.ctx.render.dates(
|
|
1013
|
+
dates,
|
|
1014
|
+
this.ctx.selectedDates,
|
|
1015
|
+
this.ctx.options.selectType
|
|
1016
|
+
),
|
|
1017
|
+
block: this.ctx.dom.$days_block
|
|
1018
|
+
},
|
|
1019
|
+
month: {
|
|
1020
|
+
count: 36,
|
|
1021
|
+
ctx: this.ctx,
|
|
1022
|
+
getNewItems: function (upDate, downDate) {
|
|
1023
|
+
const newDates = [];
|
|
1024
|
+
const isMonthDisabled = (year, month) => {
|
|
1025
|
+
const date = new Date(year, month, 1);
|
|
1026
|
+
const monthEnd = new Date(year, month + 1, 0);
|
|
1027
|
+
if (this.ctx.options.minDate && monthEnd < this.ctx.options.minDate) return true
|
|
1028
|
+
if (this.ctx.options.maxDate && date > this.ctx.options.maxDate) return true
|
|
1029
|
+
return false
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
if (isUp) {
|
|
1033
|
+
// Генеруємо повними роками (кратно 12), щоб Jan завжди був ліворуч у сітці.
|
|
1034
|
+
let curr = new Date(upDate);
|
|
1035
|
+
for (let i = 0; i < this.count; i++) {
|
|
1036
|
+
curr.setMonth(curr.getMonth() - 1);
|
|
1037
|
+
newDates.unshift({
|
|
1038
|
+
date: new Date(curr.getFullYear(), curr.getMonth(), 1),
|
|
1039
|
+
disabled: isMonthDisabled(curr.getFullYear(), curr.getMonth())
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
const newBound = newDates.length
|
|
1043
|
+
? newDates[0].date
|
|
1044
|
+
: upDate;
|
|
1045
|
+
return { items: newDates, newBound }
|
|
1046
|
+
} else {
|
|
1047
|
+
// Генеруємо повними роками (кратно 12), щоб структура рядків не "плавала".
|
|
1048
|
+
let curr = new Date(downDate);
|
|
1049
|
+
curr.setMonth(curr.getMonth() + 1);
|
|
1050
|
+
for (let i = 0; i < this.count; i++) {
|
|
1051
|
+
newDates.push({
|
|
1052
|
+
date: new Date(curr.getFullYear(), curr.getMonth(), 1),
|
|
1053
|
+
disabled: isMonthDisabled(curr.getFullYear(), curr.getMonth())
|
|
1054
|
+
});
|
|
1055
|
+
curr.setMonth(curr.getMonth() + 1);
|
|
1056
|
+
}
|
|
1057
|
+
const newBound = newDates.length
|
|
1058
|
+
? newDates[newDates.length - 1].date
|
|
1059
|
+
: downDate;
|
|
1060
|
+
return { items: newDates, newBound }
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
render: months => this.ctx.render.months(months),
|
|
1064
|
+
block: this.ctx.dom.$months_block
|
|
1065
|
+
},
|
|
1066
|
+
year: {
|
|
1067
|
+
count: 16,
|
|
1068
|
+
ctx: this.ctx,
|
|
1069
|
+
getNewItems: function (upDate, downDate) {
|
|
1070
|
+
const newDates = [];
|
|
1071
|
+
if (isUp) {
|
|
1072
|
+
// 🔑 Генерація СТРОГО перед upDate.getFullYear()
|
|
1073
|
+
const startYear = upDate.getFullYear() - this.count;
|
|
1074
|
+
for (let y = startYear; y < upDate.getFullYear(); y++) {
|
|
1075
|
+
if (y < this.ctx.options.minDate.getFullYear()) continue
|
|
1076
|
+
newDates.push({
|
|
1077
|
+
date: new Date(y, 0, 1),
|
|
1078
|
+
disabled: this.ctx.options.minDate.getFullYear() > y
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
const newBound = newDates.length
|
|
1082
|
+
? newDates[0].date
|
|
1083
|
+
: upDate;
|
|
1084
|
+
return { items: newDates, newBound }
|
|
1085
|
+
} else {
|
|
1086
|
+
// 🔑 Генерація СТРОГО після downDate.getFullYear()
|
|
1087
|
+
const startYear = downDate.getFullYear() + 1;
|
|
1088
|
+
for (let y = startYear; y < startYear + this.count; y++) {
|
|
1089
|
+
if (y > this.ctx.options.maxDate.getFullYear()) break
|
|
1090
|
+
newDates.push({
|
|
1091
|
+
date: new Date(y, 0, 1),
|
|
1092
|
+
disabled: this.ctx.options.maxDate.getFullYear() < y
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
const newBound = newDates.length
|
|
1096
|
+
? newDates[newDates.length - 1].date
|
|
1097
|
+
: downDate;
|
|
1098
|
+
return { items: newDates, newBound }
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
render: years => this.ctx.render.years(years),
|
|
1102
|
+
block: this.ctx.dom.$years_block
|
|
1103
|
+
}
|
|
1104
|
+
}[period];
|
|
1105
|
+
|
|
1106
|
+
if (!config) return
|
|
1107
|
+
|
|
1108
|
+
const { items, newBound } = config.getNewItems(this.ctx.data.up_date, this.ctx.data.down_date);
|
|
1109
|
+
|
|
1110
|
+
if (items.length) {
|
|
1111
|
+
config.block.insertAdjacentHTML(
|
|
1112
|
+
isUp ? 'afterbegin' : 'beforeend',
|
|
1113
|
+
config.render(items)
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
this.ctx.data[`${direction}_date`] = newBound;
|
|
1118
|
+
this.ctx.observe.un();
|
|
1119
|
+
const trimSize = period === 'year'
|
|
1120
|
+
? Math.min(4, items.length)
|
|
1121
|
+
: items.length;
|
|
1122
|
+
this.trim(direction, trimSize);
|
|
1123
|
+
this.ctx.observe.on(period);
|
|
1124
|
+
this.ctx.scroll.blocked = false;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
trim(direction, remove) {
|
|
1128
|
+
const period = this.ctx.period;
|
|
1129
|
+
const block = this.ctx.dom[`$${period}s_block`];
|
|
1130
|
+
const isUp = direction === 'up';
|
|
1131
|
+
const minItemsInDom = period === 'year' ? 28 : 0;
|
|
1132
|
+
|
|
1133
|
+
const oldHeight = block.scrollHeight;
|
|
1134
|
+
const items = Array.from(block.querySelectorAll(`.RollDate__calendar__${period}`));
|
|
1135
|
+
|
|
1136
|
+
if (!remove || items.length <= remove * 2) return
|
|
1137
|
+
if (minItemsInDom && (items.length - remove) < minItemsInDom) return
|
|
1138
|
+
|
|
1139
|
+
if (isUp) {
|
|
1140
|
+
items.slice(-remove).forEach(item => item.remove());
|
|
1141
|
+
} else {
|
|
1142
|
+
items.slice(0, remove).forEach(item => item.remove());
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Перераховуємо межі тільки з поточного DOM після trim, щоб уникнути "миготіння" і зациклення дозагрузки.
|
|
1146
|
+
const currentItems = Array.from(block.querySelectorAll(`.RollDate__calendar__${period}`));
|
|
1147
|
+
const firstItem = currentItems[0];
|
|
1148
|
+
const lastItem = currentItems[currentItems.length - 1];
|
|
1149
|
+
const toDate = (el) => {
|
|
1150
|
+
if (!el) return null
|
|
1151
|
+
const year = Number(el.dataset.year);
|
|
1152
|
+
const month = Number(el.dataset.month) || 0;
|
|
1153
|
+
const day = period === 'day' ? Number(el.dataset.day) : 1;
|
|
1154
|
+
return new Date(year, month, day)
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
const newUpDate = toDate(firstItem);
|
|
1158
|
+
const newDownDate = toDate(lastItem);
|
|
1159
|
+
if (newUpDate) this.ctx.data.up_date = newUpDate;
|
|
1160
|
+
if (newDownDate) this.ctx.data.down_date = newDownDate;
|
|
1161
|
+
|
|
1162
|
+
const newHeight = block.scrollHeight;
|
|
1163
|
+
const heightDelta = oldHeight - newHeight;
|
|
1164
|
+
this.ctx.scroll.checkMinScroll();
|
|
1165
|
+
|
|
1166
|
+
if (isUp) this.ctx.scroll.offset -= heightDelta;
|
|
1167
|
+
else this.ctx.scroll.offset += heightDelta;
|
|
1168
|
+
}
|
|
1102
1169
|
}
|
|
1103
1170
|
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
const ARROW_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 61 55" width="55" height="50" aria-hidden="true">
|
|
1107
|
-
<path fill-rule="evenodd" d="m52.76 54.67l-44.73 0.12c-6.16 0.02-10.02-6.64-6.96-11.98l22.26-38.79c3.07-5.35 10.76-5.37 13.86-0.04l22.47 38.67c3.09 5.33-0.74 12.01-6.9 12.02z"/>
|
|
1108
|
-
</svg>`;
|
|
1171
|
+
const DEFAULT_ITEM_HEIGHT = 28;
|
|
1172
|
+
const DEFAULT_VISIBLE = 3;
|
|
1109
1173
|
|
|
1110
1174
|
class TimePicker {
|
|
1111
1175
|
#hours = 0
|
|
@@ -1114,7 +1178,6 @@ var RollDate = (function () {
|
|
|
1114
1178
|
#period = 'AM'
|
|
1115
1179
|
#minuteStep = 1
|
|
1116
1180
|
#columns = []
|
|
1117
|
-
#displayEl = null
|
|
1118
1181
|
|
|
1119
1182
|
constructor(root, options = {}) {
|
|
1120
1183
|
this.root = root;
|
|
@@ -1142,13 +1205,89 @@ var RollDate = (function () {
|
|
|
1142
1205
|
return h === 0 ? 12 : h
|
|
1143
1206
|
}
|
|
1144
1207
|
|
|
1145
|
-
#normalizeMinute(value) {
|
|
1146
|
-
const step = this.#minuteStep;
|
|
1147
|
-
const m = Math.round(Number(value) / step) * step;
|
|
1148
|
-
return Math.min(59, Math.max(0, Number.isNaN(m) ? 0 : m))
|
|
1208
|
+
#normalizeMinute(value) {
|
|
1209
|
+
const step = this.#minuteStep;
|
|
1210
|
+
const m = Math.round(Number(value) / step) * step;
|
|
1211
|
+
return Math.min(59, Math.max(0, Number.isNaN(m) ? 0 : m))
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
#readMetrics(viewport) {
|
|
1215
|
+
const container = viewport.closest('.RollDate__container');
|
|
1216
|
+
const cs = container ? getComputedStyle(container) : getComputedStyle(viewport);
|
|
1217
|
+
const itemH = parseFloat(cs.getPropertyValue('--rd-time-item-h')) || DEFAULT_ITEM_HEIGHT;
|
|
1218
|
+
const visible = parseInt(cs.getPropertyValue('--rd-time-visible'), 10) || DEFAULT_VISIBLE;
|
|
1219
|
+
const pad = Math.floor((visible - 1) / 2) * itemH;
|
|
1220
|
+
return { itemH, visible, pad }
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
#build() {
|
|
1224
|
+
this.root.innerHTML = `
|
|
1225
|
+
<div class="RollDate__time__picker"></div>
|
|
1226
|
+
<div class="RollDate__time__period">
|
|
1227
|
+
<div class="RollDate__time__segmented RollDate__time__segmented--period" data-role="period">
|
|
1228
|
+
<button type="button" class="RollDate__time__segment-btn" data-period="AM">AM</button>
|
|
1229
|
+
<button type="button" class="RollDate__time__segment-btn" data-period="PM">PM</button>
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
`;
|
|
1233
|
+
|
|
1234
|
+
if (this.#use12Hour) {
|
|
1235
|
+
this.root.classList.add('RollDate__time--12h');
|
|
1236
|
+
}
|
|
1237
|
+
this.#bindToolbar();
|
|
1238
|
+
this.#buildPickerColumns();
|
|
1239
|
+
this.#syncToolbar();
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
#bindToolbar() {
|
|
1243
|
+
this.root.querySelector('[data-role="period"]')?.addEventListener('click', (e) => {
|
|
1244
|
+
const btn = e.target.closest('[data-period]');
|
|
1245
|
+
if (!btn || !this.#use12Hour) return
|
|
1246
|
+
this.#setPeriod(btn.dataset.period);
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
#setPeriod(period) {
|
|
1251
|
+
if (period !== 'AM' && period !== 'PM') return
|
|
1252
|
+
if (this.#period === period) return
|
|
1253
|
+
|
|
1254
|
+
this.#readColumns();
|
|
1255
|
+
const was = this.#period;
|
|
1256
|
+
this.#period = period;
|
|
1257
|
+
|
|
1258
|
+
if (was === 'AM' && period === 'PM' && this.#hours < 12) {
|
|
1259
|
+
this.#hours += 12;
|
|
1260
|
+
} else if (was === 'PM' && period === 'AM' && this.#hours >= 12) {
|
|
1261
|
+
this.#hours -= 12;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
this.#columns.find(c => c.unit === 'hour')?.scrollToValue(this.#displayHour(), true);
|
|
1265
|
+
this.#syncToolbar();
|
|
1266
|
+
hapticTick(this.hapticFeedback);
|
|
1267
|
+
this.#commitTime();
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
#syncToolbar() {
|
|
1271
|
+
this.root.classList.toggle('RollDate__time--12h', this.#use12Hour);
|
|
1272
|
+
|
|
1273
|
+
this.root.querySelectorAll('[data-period]').forEach((btn) => {
|
|
1274
|
+
const active = btn.dataset.period === this.#period;
|
|
1275
|
+
btn.classList.toggle('RollDate__time__segment-btn--active', active);
|
|
1276
|
+
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
#destroyColumns() {
|
|
1281
|
+
this.#columns.forEach((column) => column._resizeObserver?.disconnect());
|
|
1282
|
+
this.#columns = [];
|
|
1149
1283
|
}
|
|
1150
1284
|
|
|
1151
|
-
#
|
|
1285
|
+
#buildPickerColumns() {
|
|
1286
|
+
this.#destroyColumns();
|
|
1287
|
+
|
|
1288
|
+
const picker = this.root.querySelector('.RollDate__time__picker');
|
|
1289
|
+
if (!picker) return
|
|
1290
|
+
|
|
1152
1291
|
const hourValues = this.#use12Hour
|
|
1153
1292
|
? Array.from({ length: 12 }, (_, i) => i + 1)
|
|
1154
1293
|
: Array.from({ length: 24 }, (_, i) => i);
|
|
@@ -1158,68 +1297,36 @@ var RollDate = (function () {
|
|
|
1158
1297
|
minuteValues.push(m);
|
|
1159
1298
|
}
|
|
1160
1299
|
|
|
1161
|
-
|
|
1162
|
-
<div class="RollDate__time__picker">
|
|
1300
|
+
picker.innerHTML = `
|
|
1163
1301
|
<div class="RollDate__time__field" data-unit="hour">
|
|
1164
1302
|
<div class="RollDate__time__column">
|
|
1165
|
-
<button type="button" class="RollDate__time__arrow RollDate__time__arrow--prev" data-dir="prev" aria-label="Earlier hour">${ARROW_SVG}</button>
|
|
1166
1303
|
<div class="RollDate__time__viewport">
|
|
1167
1304
|
<div class="RollDate__time__list"></div>
|
|
1168
1305
|
</div>
|
|
1169
|
-
<button type="button" class="RollDate__time__arrow RollDate__time__arrow--next" data-dir="next" aria-label="Later hour">${ARROW_SVG}</button>
|
|
1170
1306
|
</div>
|
|
1171
1307
|
</div>
|
|
1172
1308
|
<span class="RollDate__time__sep">:</span>
|
|
1173
1309
|
<div class="RollDate__time__field" data-unit="minute">
|
|
1174
1310
|
<div class="RollDate__time__column">
|
|
1175
|
-
<button type="button" class="RollDate__time__arrow RollDate__time__arrow--prev" data-dir="prev" aria-label="Earlier minute">${ARROW_SVG}</button>
|
|
1176
|
-
<div class="RollDate__time__viewport">
|
|
1177
|
-
<div class="RollDate__time__list"></div>
|
|
1178
|
-
</div>
|
|
1179
|
-
<button type="button" class="RollDate__time__arrow RollDate__time__arrow--next" data-dir="next" aria-label="Later minute">${ARROW_SVG}</button>
|
|
1180
|
-
</div>
|
|
1181
|
-
</div>
|
|
1182
|
-
${this.#use12Hour ? `
|
|
1183
|
-
<div class="RollDate__time__field RollDate__time__field--period" data-unit="period">
|
|
1184
|
-
<div class="RollDate__time__column">
|
|
1185
|
-
<button type="button" class="RollDate__time__arrow RollDate__time__arrow--prev" data-dir="prev" aria-label="Earlier period">${ARROW_SVG}</button>
|
|
1186
1311
|
<div class="RollDate__time__viewport">
|
|
1187
1312
|
<div class="RollDate__time__list"></div>
|
|
1188
1313
|
</div>
|
|
1189
|
-
<button type="button" class="RollDate__time__arrow RollDate__time__arrow--next" data-dir="next" aria-label="Later period">${ARROW_SVG}</button>
|
|
1190
1314
|
</div>
|
|
1191
|
-
</div>` : ''}
|
|
1192
1315
|
</div>
|
|
1193
|
-
<div class="RollDate__time__display" aria-live="polite"></div>
|
|
1194
1316
|
`;
|
|
1195
1317
|
|
|
1196
|
-
this.#
|
|
1197
|
-
if (this.#use12Hour) {
|
|
1198
|
-
this.root.classList.add('RollDate__time--12h');
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
this.#columns.push(this.#createColumn('hour', hourValues, this.#displayHour()));
|
|
1318
|
+
this.#columns.push(this.#createColumn('hour', hourValues, this.#use12Hour ? this.#displayHour() : this.#hours));
|
|
1202
1319
|
this.#columns.push(this.#createColumn('minute', minuteValues, this.#minutes));
|
|
1203
|
-
if (this.#use12Hour) {
|
|
1204
|
-
this.#columns.push(this.#createColumn('period', ['AM', 'PM'], this.#period));
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
this.#updateDisplay();
|
|
1208
1320
|
}
|
|
1209
1321
|
|
|
1210
|
-
#
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (this.#use12Hour) {
|
|
1214
|
-
return `${String(this.#displayHour()).padStart(2, '0')}:${minutes} ${this.#period}`
|
|
1215
|
-
}
|
|
1216
|
-
return `${hours}:${minutes}`
|
|
1322
|
+
#commitTime() {
|
|
1323
|
+
this.#readColumns();
|
|
1324
|
+
this.onChange(this.getTime());
|
|
1217
1325
|
}
|
|
1218
1326
|
|
|
1219
|
-
#
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
1222
|
-
}
|
|
1327
|
+
#parseItemValue(unit, raw) {
|
|
1328
|
+
if (unit === 'period') return raw
|
|
1329
|
+
return Number(raw)
|
|
1223
1330
|
}
|
|
1224
1331
|
|
|
1225
1332
|
#createColumn(unit, values, initial) {
|
|
@@ -1234,22 +1341,28 @@ var RollDate = (function () {
|
|
|
1234
1341
|
return `<div class="RollDate__time__item" data-value="${value}">${label}</div>`
|
|
1235
1342
|
}).join('');
|
|
1236
1343
|
|
|
1237
|
-
list.style.paddingTop = `${ITEM_HEIGHT}px`;
|
|
1238
|
-
list.style.paddingBottom = `${ITEM_HEIGHT}px`;
|
|
1239
|
-
|
|
1240
1344
|
const column = {
|
|
1241
1345
|
unit,
|
|
1242
1346
|
viewport,
|
|
1243
1347
|
list,
|
|
1244
1348
|
values,
|
|
1245
|
-
offset: 0
|
|
1349
|
+
offset: 0,
|
|
1350
|
+
getMetrics: () => this.#readMetrics(viewport)
|
|
1246
1351
|
};
|
|
1247
1352
|
|
|
1353
|
+
const applyPadding = () => {
|
|
1354
|
+
const { pad } = column.getMetrics();
|
|
1355
|
+
list.style.paddingTop = `${pad}px`;
|
|
1356
|
+
list.style.paddingBottom = `${pad}px`;
|
|
1357
|
+
};
|
|
1358
|
+
applyPadding();
|
|
1359
|
+
|
|
1248
1360
|
column.updateActive = () => {
|
|
1249
1361
|
const index = column.indexFromOffset();
|
|
1250
1362
|
if (column._lastIndex !== index) {
|
|
1251
1363
|
if (column._lastIndex !== undefined) {
|
|
1252
1364
|
hapticTick(this.hapticFeedback);
|
|
1365
|
+
this.#commitTime();
|
|
1253
1366
|
}
|
|
1254
1367
|
column._lastIndex = index;
|
|
1255
1368
|
}
|
|
@@ -1259,7 +1372,8 @@ var RollDate = (function () {
|
|
|
1259
1372
|
};
|
|
1260
1373
|
|
|
1261
1374
|
column.indexFromOffset = () => {
|
|
1262
|
-
const
|
|
1375
|
+
const { itemH } = column.getMetrics();
|
|
1376
|
+
const raw = Math.round(-column.offset / itemH);
|
|
1263
1377
|
return Math.min(values.length - 1, Math.max(0, raw))
|
|
1264
1378
|
};
|
|
1265
1379
|
|
|
@@ -1272,46 +1386,51 @@ var RollDate = (function () {
|
|
|
1272
1386
|
};
|
|
1273
1387
|
|
|
1274
1388
|
column.snap = () => {
|
|
1389
|
+
const { itemH } = column.getMetrics();
|
|
1275
1390
|
const index = column.indexFromOffset();
|
|
1276
|
-
column.offset = -index *
|
|
1391
|
+
column.offset = -index * itemH;
|
|
1392
|
+
column._lastIndex = index;
|
|
1277
1393
|
column.apply(true);
|
|
1278
|
-
this.#
|
|
1279
|
-
this.#updateDisplay();
|
|
1280
|
-
this.onChange(this.getTime());
|
|
1394
|
+
this.#commitTime();
|
|
1281
1395
|
};
|
|
1282
1396
|
|
|
1283
1397
|
column.scrollToValue = (value, animate = true) => {
|
|
1398
|
+
const { itemH } = column.getMetrics();
|
|
1284
1399
|
const index = values.indexOf(value);
|
|
1285
1400
|
if (index < 0) return
|
|
1286
|
-
column.offset = -index *
|
|
1401
|
+
column.offset = -index * itemH;
|
|
1402
|
+
column._lastIndex = index;
|
|
1287
1403
|
column.apply(animate);
|
|
1288
1404
|
};
|
|
1289
1405
|
|
|
1290
|
-
column.
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
column.
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
this.onChange(this.getTime());
|
|
1406
|
+
column.scrollToIndex = (index, animate = true) => {
|
|
1407
|
+
const { itemH } = column.getMetrics();
|
|
1408
|
+
const clamped = Math.min(values.length - 1, Math.max(0, index));
|
|
1409
|
+
column.offset = -clamped * itemH;
|
|
1410
|
+
column._lastIndex = clamped;
|
|
1411
|
+
column.apply(animate);
|
|
1297
1412
|
};
|
|
1298
1413
|
|
|
1299
1414
|
column.scrollToValue(initial, false);
|
|
1300
1415
|
this.#bindWheel(column);
|
|
1301
1416
|
this.#bindTouch(column);
|
|
1302
|
-
this.#
|
|
1417
|
+
this.#bindItemClick(column);
|
|
1418
|
+
column._resizeObserver = new ResizeObserver(() => {
|
|
1419
|
+
applyPadding();
|
|
1420
|
+
column.scrollToIndex(column.indexFromOffset(), false);
|
|
1421
|
+
});
|
|
1422
|
+
column._resizeObserver.observe(viewport);
|
|
1303
1423
|
|
|
1304
1424
|
return column
|
|
1305
1425
|
}
|
|
1306
1426
|
|
|
1307
|
-
#
|
|
1308
|
-
|
|
1309
|
-
e.
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
column.stepBy(1);
|
|
1427
|
+
#bindItemClick(column) {
|
|
1428
|
+
column.list.addEventListener('click', (e) => {
|
|
1429
|
+
const item = e.target.closest('.RollDate__time__item');
|
|
1430
|
+
if (!item) return
|
|
1431
|
+
const value = this.#parseItemValue(column.unit, item.dataset.value);
|
|
1432
|
+
column.scrollToValue(value, true);
|
|
1433
|
+
this.#commitTime();
|
|
1315
1434
|
});
|
|
1316
1435
|
}
|
|
1317
1436
|
|
|
@@ -1319,9 +1438,10 @@ var RollDate = (function () {
|
|
|
1319
1438
|
column.viewport.addEventListener('wheel', (e) => {
|
|
1320
1439
|
e.preventDefault();
|
|
1321
1440
|
e.stopPropagation();
|
|
1441
|
+
const { itemH } = column.getMetrics();
|
|
1322
1442
|
column.list.style.transition = 'none';
|
|
1323
1443
|
column.offset -= e.deltaY * 0.35;
|
|
1324
|
-
const min = -(column.values.length - 1) *
|
|
1444
|
+
const min = -(column.values.length - 1) * itemH;
|
|
1325
1445
|
column.offset = Math.max(min, Math.min(0, column.offset));
|
|
1326
1446
|
column.list.style.transform = `translateY(${column.offset}px)`;
|
|
1327
1447
|
column.updateActive();
|
|
@@ -1344,8 +1464,9 @@ var RollDate = (function () {
|
|
|
1344
1464
|
column.viewport.addEventListener('touchmove', (e) => {
|
|
1345
1465
|
if (!e.touches || e.touches.length !== 1) return
|
|
1346
1466
|
e.preventDefault();
|
|
1467
|
+
const { itemH } = column.getMetrics();
|
|
1347
1468
|
column.offset = startOffset + (e.touches[0].clientY - startY);
|
|
1348
|
-
const min = -(column.values.length - 1) *
|
|
1469
|
+
const min = -(column.values.length - 1) * itemH;
|
|
1349
1470
|
column.offset = Math.max(min, Math.min(0, column.offset));
|
|
1350
1471
|
column.list.style.transform = `translateY(${column.offset}px)`;
|
|
1351
1472
|
column.updateActive();
|
|
@@ -1357,13 +1478,12 @@ var RollDate = (function () {
|
|
|
1357
1478
|
#readColumns() {
|
|
1358
1479
|
const hourCol = this.#columns.find(c => c.unit === 'hour');
|
|
1359
1480
|
const minuteCol = this.#columns.find(c => c.unit === 'minute');
|
|
1360
|
-
|
|
1481
|
+
if (!hourCol || !minuteCol) return
|
|
1361
1482
|
|
|
1362
1483
|
const hourValue = hourCol.getValue();
|
|
1363
1484
|
this.#minutes = minuteCol.getValue();
|
|
1364
1485
|
|
|
1365
|
-
if (this.#use12Hour
|
|
1366
|
-
this.#period = periodCol.getValue();
|
|
1486
|
+
if (this.#use12Hour) {
|
|
1367
1487
|
if (this.#period === 'AM') {
|
|
1368
1488
|
this.#hours = hourValue === 12 ? 0 : hourValue;
|
|
1369
1489
|
} else {
|
|
@@ -1384,16 +1504,15 @@ var RollDate = (function () {
|
|
|
1384
1504
|
if (this.#use12Hour) {
|
|
1385
1505
|
this.#period = this.#hours >= 12 ? 'PM' : 'AM';
|
|
1386
1506
|
this.#columns.find(c => c.unit === 'hour')?.scrollToValue(this.#displayHour());
|
|
1387
|
-
this.#columns.find(c => c.unit === 'period')?.scrollToValue(this.#period);
|
|
1388
1507
|
} else {
|
|
1389
1508
|
this.#columns.find(c => c.unit === 'hour')?.scrollToValue(this.#hours);
|
|
1390
1509
|
}
|
|
1391
1510
|
this.#columns.find(c => c.unit === 'minute')?.scrollToValue(this.#minutes);
|
|
1392
|
-
this.#
|
|
1511
|
+
this.#syncToolbar();
|
|
1393
1512
|
}
|
|
1394
1513
|
|
|
1395
1514
|
destroy() {
|
|
1396
|
-
this.#
|
|
1515
|
+
this.#destroyColumns();
|
|
1397
1516
|
this.root.innerHTML = '';
|
|
1398
1517
|
}
|
|
1399
1518
|
}
|
|
@@ -1403,10 +1522,12 @@ var RollDate = (function () {
|
|
|
1403
1522
|
|
|
1404
1523
|
#wheelHandler
|
|
1405
1524
|
#viewNumber = 0
|
|
1525
|
+
#arrowAnimating = false
|
|
1406
1526
|
#viewPeriodNames = ['day', 'month', 'year']
|
|
1407
1527
|
#selectedDates = []
|
|
1408
1528
|
#firstOpen = true
|
|
1409
1529
|
#disabledDateStamps = new Set()
|
|
1530
|
+
#highlightDateMap = new Map()
|
|
1410
1531
|
#docClickHandler = null
|
|
1411
1532
|
#openTriggers = []
|
|
1412
1533
|
|
|
@@ -1422,12 +1543,36 @@ var RollDate = (function () {
|
|
|
1422
1543
|
}
|
|
1423
1544
|
|
|
1424
1545
|
#normalizeDateInput(dateLike) {
|
|
1546
|
+
if (dateLike instanceof Date && !Number.isNaN(dateLike.getTime())) {
|
|
1547
|
+
return new Date(dateLike.getFullYear(), dateLike.getMonth(), dateLike.getDate())
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
if (typeof dateLike === 'string') {
|
|
1551
|
+
const clean = dateLike.trim();
|
|
1552
|
+
if (!clean) return null
|
|
1553
|
+
|
|
1554
|
+
const valueSep = clean.match(/[-/.]/)?.[0];
|
|
1555
|
+
const formatSep = this.options.dateFormat.match(/[-/.]/)?.[0];
|
|
1556
|
+
const format = valueSep && formatSep && valueSep !== formatSep
|
|
1557
|
+
? 'auto'
|
|
1558
|
+
: this.options.dateFormat;
|
|
1559
|
+
|
|
1560
|
+
const date = parseDate(clean, format);
|
|
1561
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
1562
|
+
const fallback = parseDate(clean, 'auto');
|
|
1563
|
+
if (!(fallback instanceof Date) || Number.isNaN(fallback.getTime())) return null
|
|
1564
|
+
return new Date(fallback.getFullYear(), fallback.getMonth(), fallback.getDate())
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1425
1570
|
const date = checkDateFormat(dateLike, this.options.dateFormat);
|
|
1426
1571
|
if (!(date instanceof Date) || Number.isNaN(date.getTime())) return null
|
|
1427
1572
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
1428
1573
|
}
|
|
1429
1574
|
|
|
1430
|
-
#
|
|
1575
|
+
#buildDateStampSet(dates = []) {
|
|
1431
1576
|
const set = new Set();
|
|
1432
1577
|
dates.forEach(dateLike => {
|
|
1433
1578
|
const normalized = this.#normalizeDateInput(dateLike);
|
|
@@ -1436,6 +1581,85 @@ var RollDate = (function () {
|
|
|
1436
1581
|
return set
|
|
1437
1582
|
}
|
|
1438
1583
|
|
|
1584
|
+
#buildDisabledDateSet(dates = []) {
|
|
1585
|
+
return this.#buildDateStampSet(dates)
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
#buildHighlightDateMap(dates = []) {
|
|
1589
|
+
const map = new Map();
|
|
1590
|
+
if (!Array.isArray(dates)) return map
|
|
1591
|
+
|
|
1592
|
+
dates.forEach(entry => {
|
|
1593
|
+
const item = this.#normalizeHighlightEntry(entry);
|
|
1594
|
+
if (!item) return
|
|
1595
|
+
|
|
1596
|
+
const existing = map.get(item.stamp) || [];
|
|
1597
|
+
map.set(item.stamp, existing.concat(item.colors));
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
return map
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
#normalizeHighlightEntry(entry) {
|
|
1604
|
+
if (entry == null) return null
|
|
1605
|
+
|
|
1606
|
+
if (typeof entry === 'string' || entry instanceof Date) {
|
|
1607
|
+
const normalized = this.#normalizeDateInput(entry);
|
|
1608
|
+
if (!normalized) return null
|
|
1609
|
+
return {
|
|
1610
|
+
stamp: this.#toDateStamp(normalized),
|
|
1611
|
+
colors: [null]
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (typeof entry === 'object' && entry.date != null) {
|
|
1616
|
+
const normalized = this.#normalizeDateInput(entry.date);
|
|
1617
|
+
if (!normalized) return null
|
|
1618
|
+
const stamp = this.#toDateStamp(normalized);
|
|
1619
|
+
|
|
1620
|
+
if (Array.isArray(entry.colors)) {
|
|
1621
|
+
const colors = entry.colors.map(color => {
|
|
1622
|
+
if (color == null || color === '') return null
|
|
1623
|
+
return this.#sanitizeHighlightColor(color)
|
|
1624
|
+
}).filter(color => color !== undefined);
|
|
1625
|
+
|
|
1626
|
+
if (colors.length) return { stamp, colors }
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if (entry.color != null && entry.color !== '') {
|
|
1630
|
+
const color = this.#sanitizeHighlightColor(entry.color);
|
|
1631
|
+
if (color) return { stamp, colors: [color] }
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
return { stamp, colors: [null] }
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
return null
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
#sanitizeHighlightColor(color) {
|
|
1641
|
+
if (typeof color !== 'string') return null
|
|
1642
|
+
const value = color.trim();
|
|
1643
|
+
if (!value) return null
|
|
1644
|
+
|
|
1645
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)) return value
|
|
1646
|
+
if (/^(rgb|rgba|hsl|hsla)\([^)]+\)$/i.test(value)) return value
|
|
1647
|
+
if (/^var\(--[\w-]+\)$/.test(value)) return value
|
|
1648
|
+
|
|
1649
|
+
return null
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
#highlightMapToOptionsArray() {
|
|
1653
|
+
return [...this.#highlightDateMap.entries()].map(([stamp, colors]) => {
|
|
1654
|
+
const date = new Date(stamp);
|
|
1655
|
+
|
|
1656
|
+
if (colors.length === 1 && colors[0] === null) return date
|
|
1657
|
+
if (colors.length === 1 && colors[0]) return { date, color: colors[0] }
|
|
1658
|
+
|
|
1659
|
+
return { date, colors }
|
|
1660
|
+
})
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1439
1663
|
#disableInputAssist(input) {
|
|
1440
1664
|
if (!input || input.tagName !== 'INPUT') return
|
|
1441
1665
|
input.setAttribute('autocomplete', 'off');
|
|
@@ -1448,6 +1672,16 @@ var RollDate = (function () {
|
|
|
1448
1672
|
return this.#disabledDateStamps.has(this.#toDateStamp(date))
|
|
1449
1673
|
}
|
|
1450
1674
|
|
|
1675
|
+
#isDateHighlighted(date) {
|
|
1676
|
+
return this.#highlightDateMap.has(this.#toDateStamp(date))
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
#getHighlightMeta(date) {
|
|
1680
|
+
const colors = this.#highlightDateMap.get(this.#toDateStamp(date));
|
|
1681
|
+
if (!colors?.length) return null
|
|
1682
|
+
return { colors }
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1451
1685
|
#notifySelectionChange() {
|
|
1452
1686
|
if (this.options.selectType === 'single') {
|
|
1453
1687
|
this.options.selectDate(this.#selectedDates[0] || null);
|
|
@@ -1499,6 +1733,11 @@ var RollDate = (function () {
|
|
|
1499
1733
|
const btn = document.createElement('button');
|
|
1500
1734
|
btn.type = 'button';
|
|
1501
1735
|
btn.className = 'RollDate__footer__button';
|
|
1736
|
+
if (cfg.variant === 'primary') {
|
|
1737
|
+
btn.classList.add('RollDate__footer__button--primary');
|
|
1738
|
+
} else if (cfg.variant === 'secondary') {
|
|
1739
|
+
btn.classList.add('RollDate__footer__button--secondary');
|
|
1740
|
+
}
|
|
1502
1741
|
btn.textContent = cfg.text;
|
|
1503
1742
|
btn.addEventListener('click', (e) => {
|
|
1504
1743
|
e.preventDefault();
|
|
@@ -1523,6 +1762,69 @@ var RollDate = (function () {
|
|
|
1523
1762
|
}
|
|
1524
1763
|
}
|
|
1525
1764
|
|
|
1765
|
+
#mountRangePresets() {
|
|
1766
|
+
const presets = Array.isArray(this.options.rangePresets)
|
|
1767
|
+
? this.options.rangePresets
|
|
1768
|
+
: [];
|
|
1769
|
+
|
|
1770
|
+
if (this.options.selectType !== 'range' || !presets.length) return
|
|
1771
|
+
|
|
1772
|
+
let bar = this.$container.querySelector('.RollDate__presets');
|
|
1773
|
+
if (!bar) {
|
|
1774
|
+
bar = document.createElement('div');
|
|
1775
|
+
bar.className = 'RollDate__presets';
|
|
1776
|
+
const footer = this.$container.querySelector('.RollDate__footer');
|
|
1777
|
+
const calendar = this.$container.querySelector('.RollDate__calendar');
|
|
1778
|
+
if (footer) footer.before(bar);
|
|
1779
|
+
else if (calendar) calendar.after(bar);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
bar.innerHTML = '';
|
|
1783
|
+
presets.forEach(preset => {
|
|
1784
|
+
if (!preset?.label || typeof preset.getRange !== 'function') return
|
|
1785
|
+
|
|
1786
|
+
const btn = document.createElement('button');
|
|
1787
|
+
btn.type = 'button';
|
|
1788
|
+
btn.className = 'RollDate__presets__button';
|
|
1789
|
+
btn.textContent = preset.label;
|
|
1790
|
+
btn.addEventListener('click', (e) => {
|
|
1791
|
+
e.preventDefault();
|
|
1792
|
+
e.stopPropagation();
|
|
1793
|
+
this.#applyRangePreset(preset);
|
|
1794
|
+
});
|
|
1795
|
+
bar.append(btn);
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
if (bar.childElementCount) {
|
|
1799
|
+
this.$container.classList.add('RollDate__has-presets');
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
#applyRangePreset(preset) {
|
|
1804
|
+
const result = preset.getRange(this);
|
|
1805
|
+
if (!Array.isArray(result) || result.length < 2) return
|
|
1806
|
+
|
|
1807
|
+
const normalized = result
|
|
1808
|
+
.slice(0, 2)
|
|
1809
|
+
.map(dateLike => this.#normalizeDateInput(dateLike))
|
|
1810
|
+
.filter(Boolean)
|
|
1811
|
+
.map(date => this.#clampDateToRange(date, this.options.minDate, this.options.maxDate))
|
|
1812
|
+
.filter(date => !this.#isDateDisabled(date));
|
|
1813
|
+
|
|
1814
|
+
if (normalized.length < 2) return
|
|
1815
|
+
|
|
1816
|
+
let [start, end] = normalized;
|
|
1817
|
+
if (start > end) [start, end] = [end, start];
|
|
1818
|
+
|
|
1819
|
+
this.#selectedDates = [
|
|
1820
|
+
this.#applyTimeToDate(start),
|
|
1821
|
+
this.#applyTimeToDate(end)
|
|
1822
|
+
];
|
|
1823
|
+
this.#notifySelectionChange();
|
|
1824
|
+
this.#updateInputValue();
|
|
1825
|
+
this.#paintRangeSelection();
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1526
1828
|
#initTimePicker() {
|
|
1527
1829
|
const start = this.options.startDate;
|
|
1528
1830
|
this.timePicker = new TimePicker(this.dom.$time, {
|
|
@@ -1567,7 +1869,7 @@ var RollDate = (function () {
|
|
|
1567
1869
|
|
|
1568
1870
|
const baseOptions = {
|
|
1569
1871
|
mode: 'auto',
|
|
1570
|
-
theme: '
|
|
1872
|
+
theme: 'main',
|
|
1571
1873
|
startWeekFromMonday: true,
|
|
1572
1874
|
selectType: 'single',
|
|
1573
1875
|
monthsNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
@@ -1579,17 +1881,20 @@ var RollDate = (function () {
|
|
|
1579
1881
|
onViewChange: () => {},
|
|
1580
1882
|
onHoverDate: () => {},
|
|
1581
1883
|
disabledDates: [],
|
|
1884
|
+
highlightDates: [],
|
|
1582
1885
|
closeOnSelect: true,
|
|
1583
1886
|
enableTime: false,
|
|
1584
1887
|
use12Hour: false,
|
|
1585
1888
|
timeStep: 1,
|
|
1889
|
+
timePosition: 'right',
|
|
1586
1890
|
footerButtons: [],
|
|
1891
|
+
rangePresets: [],
|
|
1587
1892
|
hapticFeedback: true,
|
|
1588
1893
|
...options
|
|
1589
1894
|
};
|
|
1590
1895
|
|
|
1591
1896
|
if (baseOptions.theme === 'default') {
|
|
1592
|
-
baseOptions.theme = '
|
|
1897
|
+
baseOptions.theme = 'main';
|
|
1593
1898
|
}
|
|
1594
1899
|
|
|
1595
1900
|
const resolvedLocale = baseOptions.locale ||
|
|
@@ -1626,6 +1931,7 @@ var RollDate = (function () {
|
|
|
1626
1931
|
this.options.maxDate
|
|
1627
1932
|
);
|
|
1628
1933
|
this.#disabledDateStamps = this.#buildDisabledDateSet(this.options.disabledDates);
|
|
1934
|
+
this.#highlightDateMap = this.#buildHighlightDateMap(this.options.highlightDates);
|
|
1629
1935
|
if (this.#isDateDisabled(this.options.startDate)) {
|
|
1630
1936
|
this.options.startDate = new Date(this.options.minDate);
|
|
1631
1937
|
}
|
|
@@ -1668,8 +1974,10 @@ var RollDate = (function () {
|
|
|
1668
1974
|
|
|
1669
1975
|
if (this.options.enableTime) {
|
|
1670
1976
|
this.$container.classList.add('RollDate__has-time');
|
|
1977
|
+
const timePosition = this.options.timePosition === 'bottom' ? 'bottom' : 'right';
|
|
1978
|
+
this.$container.classList.add(`RollDate__time-position_${timePosition}`);
|
|
1671
1979
|
}
|
|
1672
|
-
if (this.options.footerButtons?.length
|
|
1980
|
+
if (this.options.footerButtons?.length) {
|
|
1673
1981
|
this.$container.classList.add('RollDate__has-footer');
|
|
1674
1982
|
}
|
|
1675
1983
|
|
|
@@ -1698,6 +2006,7 @@ var RollDate = (function () {
|
|
|
1698
2006
|
};
|
|
1699
2007
|
|
|
1700
2008
|
this.#initFooterButtons();
|
|
2009
|
+
this.#mountRangePresets();
|
|
1701
2010
|
if (this.options.enableTime && this.dom.$time) {
|
|
1702
2011
|
this.#initTimePicker();
|
|
1703
2012
|
}
|
|
@@ -1758,7 +2067,30 @@ var RollDate = (function () {
|
|
|
1758
2067
|
this.$container.style.zIndex = '10000';
|
|
1759
2068
|
}
|
|
1760
2069
|
|
|
1761
|
-
#updateView() {
|
|
2070
|
+
#updateView(viewNumberOrOptions, maybeOptions) {
|
|
2071
|
+
let options = {};
|
|
2072
|
+
|
|
2073
|
+
if (typeof viewNumberOrOptions === 'object' && viewNumberOrOptions !== null) {
|
|
2074
|
+
options = viewNumberOrOptions;
|
|
2075
|
+
} else {
|
|
2076
|
+
if (typeof viewNumberOrOptions === 'number') {
|
|
2077
|
+
this.#viewNumber = viewNumberOrOptions;
|
|
2078
|
+
}
|
|
2079
|
+
if (typeof maybeOptions === 'object' && maybeOptions !== null) {
|
|
2080
|
+
options = maybeOptions;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
const preserveScroll = options.preserveScroll === true;
|
|
2085
|
+
const savedOffset = preserveScroll ? this.scroll.offset : null;
|
|
2086
|
+
const animateScroll = options.animate === true && Number(options.shift);
|
|
2087
|
+
const onScrollComplete = typeof options.onScrollComplete === 'function'
|
|
2088
|
+
? options.onScrollComplete
|
|
2089
|
+
: null;
|
|
2090
|
+
const finishScroll = () => {
|
|
2091
|
+
onScrollComplete?.();
|
|
2092
|
+
};
|
|
2093
|
+
|
|
1762
2094
|
this.#updateHeader();
|
|
1763
2095
|
this.scroll.blocked = false;
|
|
1764
2096
|
this.observe.un(this.period);
|
|
@@ -1770,7 +2102,8 @@ var RollDate = (function () {
|
|
|
1770
2102
|
this.dom.$days_block.innerHTML = this.render.dates(
|
|
1771
2103
|
days,
|
|
1772
2104
|
this.#selectedDates,
|
|
1773
|
-
this.options.selectType
|
|
2105
|
+
this.options.selectType,
|
|
2106
|
+
date => this.#getHighlightMeta(date)
|
|
1774
2107
|
);
|
|
1775
2108
|
break
|
|
1776
2109
|
|
|
@@ -1779,41 +2112,68 @@ var RollDate = (function () {
|
|
|
1779
2112
|
this.dom[`$${this.period}s_block`].innerHTML = this.render[`${this.period}s`](dates);
|
|
1780
2113
|
}
|
|
1781
2114
|
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2115
|
+
const applyScroll = () => {
|
|
2116
|
+
const $scrollBlock = this.dom.$body.querySelector('.RollDate__calendar__scrollblock');
|
|
2117
|
+
const bodyHeight = this.dom.$body.clientHeight;
|
|
2118
|
+
const blockHeight = $scrollBlock.clientHeight;
|
|
2119
|
+
|
|
2120
|
+
if (blockHeight <= bodyHeight) {
|
|
2121
|
+
this.scroll.setBaseOffset(bodyHeight - blockHeight);
|
|
2122
|
+
this.scroll.offset = 0;
|
|
2123
|
+
finishScroll();
|
|
2124
|
+
return
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
this.scroll.setBaseOffset(0);
|
|
2128
|
+
this.scroll.resetMinScroll();
|
|
2129
|
+
|
|
2130
|
+
if (preserveScroll && savedOffset != null) {
|
|
2131
|
+
this.scroll.offset = Math.max(savedOffset, this.scroll.minScroll);
|
|
2132
|
+
finishScroll();
|
|
2133
|
+
return
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
const selectors = {
|
|
2137
|
+
day: `.RollDate__calendar__day[data-year="${this.data.current_year}"][data-month="${this.data.current_month}"]`,
|
|
2138
|
+
month: `.RollDate__calendar__month[data-year="${this.data.current_year}"][data-month="0"]`,
|
|
2139
|
+
year: `.RollDate__calendar__year[data-decade="${this.data.current_decade}"][data-year="${this.data.current_year}"]`
|
|
1793
2140
|
};
|
|
1794
|
-
$firstEl = this.$container.querySelector(
|
|
1795
|
-
|
|
2141
|
+
let $firstEl = this.$container.querySelector(selectors[this.period]);
|
|
2142
|
+
if (!$firstEl) {
|
|
2143
|
+
const fallbacks = {
|
|
2144
|
+
day: `.RollDate__calendar__day[data-year="${this.data.current_year}"]`,
|
|
2145
|
+
month: `.RollDate__calendar__month[data-year="${this.data.current_year}"]`,
|
|
2146
|
+
year: `.RollDate__calendar__year[data-decade="${this.data.current_decade}"]`
|
|
2147
|
+
};
|
|
2148
|
+
$firstEl = this.$container.querySelector(fallbacks[this.period]);
|
|
2149
|
+
}
|
|
1796
2150
|
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
const blockHeight = $scrollBlock.clientHeight;
|
|
2151
|
+
if (!$firstEl) {
|
|
2152
|
+
finishScroll();
|
|
2153
|
+
return
|
|
2154
|
+
}
|
|
1802
2155
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
}
|
|
2156
|
+
const containerRect = $scrollBlock.getBoundingClientRect();
|
|
2157
|
+
const firstRect = $firstEl.getBoundingClientRect();
|
|
2158
|
+
const firstRectOffset = -(firstRect.top - containerRect.top);
|
|
2159
|
+
const targetOffset = Math.max(firstRectOffset, this.scroll.minScroll);
|
|
1808
2160
|
|
|
1809
|
-
|
|
1810
|
-
const
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2161
|
+
if (animateScroll) {
|
|
2162
|
+
const slide = Math.min(bodyHeight * 0.38, 140);
|
|
2163
|
+
const startOffset = Math.max(
|
|
2164
|
+
Math.min(targetOffset + (options.shift > 0 ? slide : -slide), 0),
|
|
2165
|
+
this.scroll.minScroll
|
|
2166
|
+
);
|
|
2167
|
+
this.scroll.offset = startOffset;
|
|
2168
|
+
this.scroll.animateTo(targetOffset, { onComplete: finishScroll });
|
|
2169
|
+
return
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
this.scroll.offset = targetOffset;
|
|
2173
|
+
finishScroll();
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
setTimeout(applyScroll, 0);
|
|
1817
2177
|
|
|
1818
2178
|
this.observe.on(this.period, item => {
|
|
1819
2179
|
const cond = JSON.parse(item.dataset.bind).every(data => Number(item.dataset[data]) === this.data[`current_${data}`]);
|
|
@@ -1967,9 +2327,10 @@ var RollDate = (function () {
|
|
|
1967
2327
|
}
|
|
1968
2328
|
|
|
1969
2329
|
if (this.options.selectType === 'range') {
|
|
2330
|
+
const clickStamp = this.#toDateStamp(date);
|
|
1970
2331
|
const isClickInRange = this.#selectedDates.length === 2 &&
|
|
1971
|
-
|
|
1972
|
-
|
|
2332
|
+
clickStamp >= this.#toDateStamp(this.#selectedDates[0]) &&
|
|
2333
|
+
clickStamp <= this.#toDateStamp(this.#selectedDates[1]);
|
|
1973
2334
|
|
|
1974
2335
|
if (this.#selectedDates.length === 2 && !isClickInRange) {
|
|
1975
2336
|
this.#clearRangeSelection();
|
|
@@ -1980,7 +2341,8 @@ var RollDate = (function () {
|
|
|
1980
2341
|
$day.classList.add('RollDate__calendar__day--range-first');
|
|
1981
2342
|
} else {
|
|
1982
2343
|
const firstDate = this.#selectedDates[0];
|
|
1983
|
-
|
|
2344
|
+
const firstStamp = this.#toDateStamp(firstDate);
|
|
2345
|
+
if (clickStamp > firstStamp) {
|
|
1984
2346
|
this.#selectedDates = [firstDate, date];
|
|
1985
2347
|
|
|
1986
2348
|
this.$container.querySelectorAll('[data-day]').forEach(dayEl => {
|
|
@@ -1989,17 +2351,17 @@ var RollDate = (function () {
|
|
|
1989
2351
|
Number(dayEl.dataset.month),
|
|
1990
2352
|
Number(dayEl.dataset.day)
|
|
1991
2353
|
);
|
|
1992
|
-
const
|
|
2354
|
+
const stamp = this.#toDateStamp(dayDate);
|
|
1993
2355
|
|
|
1994
|
-
if (
|
|
2356
|
+
if (stamp === firstStamp) {
|
|
1995
2357
|
dayEl.classList.add('RollDate__calendar__day--range-first');
|
|
1996
|
-
} else if (
|
|
2358
|
+
} else if (stamp === clickStamp) {
|
|
1997
2359
|
dayEl.classList.add('RollDate__calendar__day--range-last');
|
|
1998
|
-
} else if (
|
|
2360
|
+
} else if (stamp > firstStamp && stamp < clickStamp) {
|
|
1999
2361
|
dayEl.classList.add('RollDate__calendar__day--range-selected');
|
|
2000
2362
|
}
|
|
2001
2363
|
});
|
|
2002
|
-
} else if (
|
|
2364
|
+
} else if (clickStamp < firstStamp) {
|
|
2003
2365
|
this.#selectedDates = [date];
|
|
2004
2366
|
$day.classList.add('RollDate__calendar__day--range-first');
|
|
2005
2367
|
|
|
@@ -2017,12 +2379,13 @@ var RollDate = (function () {
|
|
|
2017
2379
|
}
|
|
2018
2380
|
|
|
2019
2381
|
if (this.options.selectType === 'multi') {
|
|
2382
|
+
const clickStamp = this.#toDateStamp(date);
|
|
2020
2383
|
const isSelected = this.#selectedDates.some(
|
|
2021
|
-
d => d
|
|
2384
|
+
d => this.#toDateStamp(d) === clickStamp
|
|
2022
2385
|
);
|
|
2023
2386
|
if (isSelected) {
|
|
2024
2387
|
this.#selectedDates = this.#selectedDates.filter(
|
|
2025
|
-
d => d
|
|
2388
|
+
d => this.#toDateStamp(d) !== clickStamp
|
|
2026
2389
|
);
|
|
2027
2390
|
$day.classList.remove('RollDate__calendar__day--selected');
|
|
2028
2391
|
} else {
|
|
@@ -2068,6 +2431,8 @@ var RollDate = (function () {
|
|
|
2068
2431
|
e.preventDefault();
|
|
2069
2432
|
e.stopPropagation();
|
|
2070
2433
|
|
|
2434
|
+
if (this.#arrowAnimating) return
|
|
2435
|
+
|
|
2071
2436
|
const direction = e.currentTarget?.dataset?.direction;
|
|
2072
2437
|
if (direction !== 'prev' && direction !== 'next') return
|
|
2073
2438
|
const shift = direction === 'prev' ? -1 : 1;
|
|
@@ -2104,8 +2469,15 @@ var RollDate = (function () {
|
|
|
2104
2469
|
this.data.current_decade = targetDecade;
|
|
2105
2470
|
}
|
|
2106
2471
|
|
|
2472
|
+
this.#arrowAnimating = true;
|
|
2107
2473
|
hapticTick(this.options.hapticFeedback !== false);
|
|
2108
|
-
this.#updateView(this.#viewNumber
|
|
2474
|
+
this.#updateView(this.#viewNumber, {
|
|
2475
|
+
animate: true,
|
|
2476
|
+
shift,
|
|
2477
|
+
onScrollComplete: () => {
|
|
2478
|
+
this.#arrowAnimating = false;
|
|
2479
|
+
}
|
|
2480
|
+
});
|
|
2109
2481
|
});
|
|
2110
2482
|
});
|
|
2111
2483
|
}
|
|
@@ -2172,6 +2544,20 @@ var RollDate = (function () {
|
|
|
2172
2544
|
return this.#selectedDates
|
|
2173
2545
|
}
|
|
2174
2546
|
|
|
2547
|
+
/** Visible calendar month (updates while scrolling). Month is 0–11. */
|
|
2548
|
+
getViewMonth() {
|
|
2549
|
+
return {
|
|
2550
|
+
year: this.data.current_year,
|
|
2551
|
+
month: this.data.current_month
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
/** First day of the month currently shown in the calendar. */
|
|
2556
|
+
getViewDate() {
|
|
2557
|
+
const { year, month } = this.getViewMonth();
|
|
2558
|
+
return new Date(year, month, 1)
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2175
2561
|
setDisabledDates(dates = []) {
|
|
2176
2562
|
this.options.disabledDates = Array.isArray(dates) ? dates : [];
|
|
2177
2563
|
this.#disabledDateStamps = this.#buildDisabledDateSet(this.options.disabledDates);
|
|
@@ -2201,6 +2587,251 @@ var RollDate = (function () {
|
|
|
2201
2587
|
return normalized ? this.#isDateDisabled(normalized) : false
|
|
2202
2588
|
}
|
|
2203
2589
|
|
|
2590
|
+
setHighlightDates(dates = []) {
|
|
2591
|
+
this.options.highlightDates = Array.isArray(dates) ? dates : [];
|
|
2592
|
+
this.#highlightDateMap = this.#buildHighlightDateMap(this.options.highlightDates);
|
|
2593
|
+
this.#updateView(this.#viewNumber);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
highlightDate(dateLike, color) {
|
|
2597
|
+
const normalized = this.#normalizeDateInput(dateLike);
|
|
2598
|
+
if (!normalized) return
|
|
2599
|
+
|
|
2600
|
+
const stamp = this.#toDateStamp(normalized);
|
|
2601
|
+
const existing = this.#highlightDateMap.get(stamp) || [];
|
|
2602
|
+
const nextColor = color == null || color === ''
|
|
2603
|
+
? null
|
|
2604
|
+
: this.#sanitizeHighlightColor(color);
|
|
2605
|
+
|
|
2606
|
+
if (color != null && color !== '' && nextColor === undefined) return
|
|
2607
|
+
|
|
2608
|
+
existing.push(nextColor ?? null);
|
|
2609
|
+
this.#highlightDateMap.set(stamp, existing);
|
|
2610
|
+
this.options.highlightDates = this.#highlightMapToOptionsArray();
|
|
2611
|
+
this.#updateView(this.#viewNumber);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
unhighlightDate(dateLike, color) {
|
|
2615
|
+
const normalized = this.#normalizeDateInput(dateLike);
|
|
2616
|
+
if (!normalized) return
|
|
2617
|
+
|
|
2618
|
+
const stamp = this.#toDateStamp(normalized);
|
|
2619
|
+
const existing = this.#highlightDateMap.get(stamp);
|
|
2620
|
+
if (!existing?.length) return
|
|
2621
|
+
|
|
2622
|
+
if (color === undefined) {
|
|
2623
|
+
this.#highlightDateMap.delete(stamp);
|
|
2624
|
+
} else {
|
|
2625
|
+
const target = color == null || color === ''
|
|
2626
|
+
? null
|
|
2627
|
+
: this.#sanitizeHighlightColor(color);
|
|
2628
|
+
|
|
2629
|
+
const index = existing.findIndex(item => item === target);
|
|
2630
|
+
if (index === -1) return
|
|
2631
|
+
|
|
2632
|
+
existing.splice(index, 1);
|
|
2633
|
+
if (existing.length) {
|
|
2634
|
+
this.#highlightDateMap.set(stamp, existing);
|
|
2635
|
+
} else {
|
|
2636
|
+
this.#highlightDateMap.delete(stamp);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
this.options.highlightDates = this.#highlightMapToOptionsArray();
|
|
2641
|
+
this.#updateView(this.#viewNumber);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
isDateHighlighted(dateLike) {
|
|
2645
|
+
const normalized = this.#normalizeDateInput(dateLike);
|
|
2646
|
+
return normalized ? this.#isDateHighlighted(normalized) : false
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
getHighlightColors(dateLike) {
|
|
2650
|
+
const normalized = this.#normalizeDateInput(dateLike);
|
|
2651
|
+
if (!normalized) return []
|
|
2652
|
+
return [...(this.#highlightDateMap.get(this.#toDateStamp(normalized)) || [])]
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
getHighlightColor(dateLike) {
|
|
2656
|
+
return this.getHighlightColors(dateLike).find(color => color != null) ?? null
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
goToDate(dateLike) {
|
|
2660
|
+
const normalized = this.#normalizeDateInput(dateLike);
|
|
2661
|
+
if (!normalized) return false
|
|
2662
|
+
|
|
2663
|
+
const clamped = this.#clampDateToRange(
|
|
2664
|
+
normalized,
|
|
2665
|
+
this.options.minDate,
|
|
2666
|
+
this.options.maxDate
|
|
2667
|
+
);
|
|
2668
|
+
|
|
2669
|
+
this.data.current_year = clamped.getFullYear();
|
|
2670
|
+
this.data.current_month = clamped.getMonth();
|
|
2671
|
+
this.data.current_decade = getDecade(clamped.getFullYear());
|
|
2672
|
+
|
|
2673
|
+
if (this.#viewNumber !== 0) {
|
|
2674
|
+
this.#viewNumber = 0;
|
|
2675
|
+
for (const period of this.#viewPeriodNames) {
|
|
2676
|
+
this.$container.classList.remove('RollDate__calendar__type--' + period + 's');
|
|
2677
|
+
}
|
|
2678
|
+
this.$container.classList.add('RollDate__calendar__type--days');
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
this.#updateView(0);
|
|
2682
|
+
return true
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
getValue() {
|
|
2686
|
+
if (this.options.selectType === 'single') {
|
|
2687
|
+
const date = this.#selectedDates[0];
|
|
2688
|
+
return date ? new Date(date.getTime()) : null
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
return this.#selectedDates.map(date => new Date(date.getTime()))
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
setValue(value) {
|
|
2695
|
+
if (value == null || value === '') {
|
|
2696
|
+
this.clearSelection();
|
|
2697
|
+
return true
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
if (this.options.selectType === 'single') {
|
|
2701
|
+
const normalized = this.#normalizeDateInput(value);
|
|
2702
|
+
if (!normalized || this.#isDateDisabled(normalized)) return false
|
|
2703
|
+
this.#selectedDates = [this.#applyTimeToDate(normalized)];
|
|
2704
|
+
} else if (this.options.selectType === 'range') {
|
|
2705
|
+
const raw = Array.isArray(value) ? value : [value];
|
|
2706
|
+
const parsed = raw
|
|
2707
|
+
.map(item => this.#normalizeDateInput(item))
|
|
2708
|
+
.filter(Boolean)
|
|
2709
|
+
.map(date => this.#clampDateToRange(date, this.options.minDate, this.options.maxDate))
|
|
2710
|
+
.filter(date => !this.#isDateDisabled(date));
|
|
2711
|
+
|
|
2712
|
+
if (!parsed.length) return false
|
|
2713
|
+
|
|
2714
|
+
let start = parsed[0];
|
|
2715
|
+
let end = parsed.length > 1 ? parsed[1] : parsed[0];
|
|
2716
|
+
if (start > end) [start, end] = [end, start];
|
|
2717
|
+
|
|
2718
|
+
this.#selectedDates = parsed.length > 1
|
|
2719
|
+
? [this.#applyTimeToDate(start), this.#applyTimeToDate(end)]
|
|
2720
|
+
: [this.#applyTimeToDate(start)];
|
|
2721
|
+
} else {
|
|
2722
|
+
const raw = Array.isArray(value) ? value : [value];
|
|
2723
|
+
const parsed = raw
|
|
2724
|
+
.map(item => this.#normalizeDateInput(item))
|
|
2725
|
+
.filter(Boolean)
|
|
2726
|
+
.map(date => this.#clampDateToRange(date, this.options.minDate, this.options.maxDate))
|
|
2727
|
+
.filter(date => !this.#isDateDisabled(date))
|
|
2728
|
+
.map(date => this.#applyTimeToDate(date));
|
|
2729
|
+
|
|
2730
|
+
if (!parsed.length) return false
|
|
2731
|
+
|
|
2732
|
+
const unique = [];
|
|
2733
|
+
const seen = new Set();
|
|
2734
|
+
parsed.forEach(date => {
|
|
2735
|
+
const stamp = this.#toDateStamp(date);
|
|
2736
|
+
if (seen.has(stamp)) return
|
|
2737
|
+
seen.add(stamp);
|
|
2738
|
+
unique.push(date);
|
|
2739
|
+
});
|
|
2740
|
+
this.#selectedDates = unique;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
this.#paintSelection();
|
|
2744
|
+
this.#notifySelectionChange();
|
|
2745
|
+
this.#updateInputValue();
|
|
2746
|
+
return true
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
#dayElementStamp(dayEl) {
|
|
2750
|
+
return this.#toDateStamp(new Date(
|
|
2751
|
+
Number(dayEl.dataset.year),
|
|
2752
|
+
Number(dayEl.dataset.month),
|
|
2753
|
+
Number(dayEl.dataset.day)
|
|
2754
|
+
))
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
#findDayElement(date) {
|
|
2758
|
+
if (!(date instanceof Date)) return null
|
|
2759
|
+
return this.$container.querySelector(
|
|
2760
|
+
`.RollDate__calendar__day[data-year="${date.getFullYear()}"][data-month="${date.getMonth()}"][data-day="${date.getDate()}"]`
|
|
2761
|
+
)
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
#paintRangeSelection() {
|
|
2765
|
+
this.$container.querySelectorAll('[data-day]').forEach(dayEl => {
|
|
2766
|
+
dayEl.classList.remove(
|
|
2767
|
+
'RollDate__calendar__day--range-first',
|
|
2768
|
+
'RollDate__calendar__day--range-last',
|
|
2769
|
+
'RollDate__calendar__day--range-selected'
|
|
2770
|
+
);
|
|
2771
|
+
});
|
|
2772
|
+
|
|
2773
|
+
if (!this.#selectedDates.length) return
|
|
2774
|
+
|
|
2775
|
+
const firstStamp = this.#toDateStamp(this.#selectedDates[0]);
|
|
2776
|
+
|
|
2777
|
+
if (this.#selectedDates.length === 1) {
|
|
2778
|
+
this.#findDayElement(this.#selectedDates[0])
|
|
2779
|
+
?.classList.add('RollDate__calendar__day--range-first');
|
|
2780
|
+
return
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
const endStamp = this.#toDateStamp(this.#selectedDates[1]);
|
|
2784
|
+
|
|
2785
|
+
this.$container.querySelectorAll('[data-day]').forEach(dayEl => {
|
|
2786
|
+
const stamp = this.#dayElementStamp(dayEl);
|
|
2787
|
+
|
|
2788
|
+
if (stamp === firstStamp) {
|
|
2789
|
+
dayEl.classList.add('RollDate__calendar__day--range-first');
|
|
2790
|
+
} else if (stamp === endStamp) {
|
|
2791
|
+
dayEl.classList.add('RollDate__calendar__day--range-last');
|
|
2792
|
+
} else if (stamp > firstStamp && stamp < endStamp) {
|
|
2793
|
+
dayEl.classList.add('RollDate__calendar__day--range-selected');
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
#paintSingleSelection() {
|
|
2799
|
+
this.$container.querySelectorAll('[data-day]').forEach(dayEl => {
|
|
2800
|
+
dayEl.classList.remove('RollDate__calendar__day--selected');
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
const selected = this.#selectedDates[0];
|
|
2804
|
+
if (selected) {
|
|
2805
|
+
this.#findDayElement(selected)?.classList.add('RollDate__calendar__day--selected');
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
#paintMultiSelection() {
|
|
2810
|
+
const selectedStamps = new Set(this.#selectedDates.map(date => this.#toDateStamp(date)));
|
|
2811
|
+
|
|
2812
|
+
this.$container.querySelectorAll('[data-day]').forEach(dayEl => {
|
|
2813
|
+
if (selectedStamps.has(this.#dayElementStamp(dayEl))) {
|
|
2814
|
+
dayEl.classList.add('RollDate__calendar__day--selected');
|
|
2815
|
+
} else {
|
|
2816
|
+
dayEl.classList.remove('RollDate__calendar__day--selected');
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
#paintSelection() {
|
|
2822
|
+
if (this.options.selectType === 'range') {
|
|
2823
|
+
this.#paintRangeSelection();
|
|
2824
|
+
return
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
if (this.options.selectType === 'multi') {
|
|
2828
|
+
this.#paintMultiSelection();
|
|
2829
|
+
return
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
this.#paintSingleSelection();
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2204
2835
|
#clearRangeSelection() {
|
|
2205
2836
|
this.#selectedDates = [];
|
|
2206
2837
|
this.$container.querySelectorAll('[data-day]').forEach(item => {
|
|
@@ -2371,6 +3002,10 @@ var RollDate = (function () {
|
|
|
2371
3002
|
this.data.current_month = now.getMonth();
|
|
2372
3003
|
this.data.current_decade = getDecade(now.getFullYear());
|
|
2373
3004
|
|
|
3005
|
+
if (this.options.enableTime && this.timePicker) {
|
|
3006
|
+
this.timePicker.setTime(now.getHours(), now.getMinutes());
|
|
3007
|
+
}
|
|
3008
|
+
|
|
2374
3009
|
const selected = this.#applyTimeToDate(now);
|
|
2375
3010
|
|
|
2376
3011
|
if (this.options.selectType === 'single') {
|
|
@@ -2378,7 +3013,8 @@ var RollDate = (function () {
|
|
|
2378
3013
|
} else if (this.options.selectType === 'range') {
|
|
2379
3014
|
this.#selectedDates = [selected];
|
|
2380
3015
|
} else if (this.options.selectType === 'multi') {
|
|
2381
|
-
const
|
|
3016
|
+
const selectedStamp = this.#toDateStamp(selected);
|
|
3017
|
+
const exists = this.#selectedDates.some(d => this.#toDateStamp(d) === selectedStamp);
|
|
2382
3018
|
if (!exists) this.#selectedDates.push(selected);
|
|
2383
3019
|
}
|
|
2384
3020
|
|
|
@@ -2398,9 +3034,10 @@ var RollDate = (function () {
|
|
|
2398
3034
|
this.#clearMultiSelection();
|
|
2399
3035
|
} else {
|
|
2400
3036
|
this.#selectedDates = [];
|
|
3037
|
+
this.$container.querySelectorAll('[data-day].RollDate__calendar__day--selected')
|
|
3038
|
+
.forEach(el => el.classList.remove('RollDate__calendar__day--selected'));
|
|
2401
3039
|
}
|
|
2402
3040
|
|
|
2403
|
-
this.#updateView(this.#viewNumber);
|
|
2404
3041
|
this.#notifySelectionChange();
|
|
2405
3042
|
this.#updateInputValue();
|
|
2406
3043
|
}
|