inviton-powerduck 0.0.178 → 0.0.180

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.
@@ -260,6 +260,16 @@ import { globalState } from '../../../../app/global-state';
260
260
  },
261
261
  };
262
262
 
263
+ const MS_PER_DAY = 86_400_000;
264
+
265
+ // 1970 01 01 bol štvrtok. Pre weekday v rozsahu 0..6 kde 0 nedeľa:
266
+ const weekdayFromEpochDay = (epochDay: number) => (epochDay + 4) % 7; // 0 št 1 pia 2 so 3 ne 4 po 5 ut 6 st
267
+ // ak chceš 0 nedeľa:
268
+ const weekdaySun0 = (epochDay: number) => (epochDay + 4 + 3) % 7; // posun na nedeľu
269
+
270
+ const toEpochDayUTC = (msUTC: number) => Math.floor(msUTC / MS_PER_DAY);
271
+ const fromEpochDayUTC = (epochDay: number) => epochDay * MS_PER_DAY;
272
+
263
273
  const dateNow = () => Temporal.Now.plainDateTimeISO();
264
274
  const ensureTemporal = (date: Temporal.PlainDateTime | number | string): Temporal.PlainDateTime => {
265
275
  if (date == null) {
@@ -432,6 +442,7 @@ import { globalState } from '../../../../app/global-state';
432
442
  customValueLabel: string
433
443
  customValues: any[]
434
444
  getCellHtml?: (args: DaterangePickerArgsCustomCellRenderArgs) => string;
445
+ enableSwipeDebug: boolean; // Enable mouse dragging to simulate touch swipe (for debugging)
435
446
  showDateFilter?: (date: Temporal.PlainDateTime, day: number) => string | number
436
447
  }
437
448
 
@@ -440,7 +451,21 @@ import { globalState } from '../../../../app/global-state';
440
451
  opt = {} as any
441
452
  }
442
453
 
454
+ const getStartMs = () => {
455
+ if (opt.start) {
456
+ return opt.start[utcEpochMilliseconds]();
457
+ }
443
458
 
459
+ return 0;
460
+ }
461
+
462
+ const getEndMs = () => {
463
+ if (opt.end) {
464
+ return opt.end[utcEpochMilliseconds]();
465
+ }
466
+
467
+ return 0;
468
+ }
444
469
 
445
470
  opt = $.extend(
446
471
  true,
@@ -508,6 +533,7 @@ import { globalState } from '../../../../app/global-state';
508
533
  customArrowNextSymbol: null,
509
534
  monthSelect: false,
510
535
  yearSelect: false,
536
+ enableSwipeDebug: location.href.includes('localhost'), // Enable mouse dragging to simulate touch swipe (for debugging)
511
537
  },
512
538
  opt,
513
539
  );
@@ -531,6 +557,10 @@ import { globalState } from '../../../../app/global-state';
531
557
  singleMonth = ($(window).width() < 480) as any;
532
558
  }
533
559
 
560
+ if (($(window).width() < 480)) {
561
+ singleMonth = true;
562
+ }
563
+
534
564
  if (singleMonth) {
535
565
  opt.stickyMonths = false;
536
566
  }
@@ -573,22 +603,6 @@ import { globalState } from '../../../../app/global-state';
573
603
  open(0);
574
604
  }
575
605
 
576
- const getStartMs = () => {
577
- if (opt.start) {
578
- return opt.start[utcEpochMilliseconds]();
579
- }
580
-
581
- return 0;
582
- }
583
-
584
- const getEndMs = () => {
585
- if (opt.end) {
586
- return opt.end[utcEpochMilliseconds]();
587
- }
588
-
589
- return 0;
590
- }
591
-
592
606
  // expose some api
593
607
  $(this).data('dateRangePicker', {
594
608
  setStart(d1) {
@@ -652,7 +666,7 @@ import { globalState } from '../../../../app/global-state';
652
666
  return this;
653
667
 
654
668
  function isModalDisplayMode() {
655
- return (box != null && box.hasClass('date-range-picker-modalmode')) || $(window).width() <= 992;
669
+ return (box != null && box.hasClass('date-range-picker-modalmode'))
656
670
  }
657
671
 
658
672
  function IsOwnDatePickerClicked(evt, selfObj) {
@@ -827,6 +841,302 @@ import { globalState } from '../../../../app/global-state';
827
841
  showSelectedDays();
828
842
  }
829
843
 
844
+ // Add swipe support for mobile date navigation with preview
845
+ // Debug: Set enableSwipeDebug: true in options to enable mouse dragging simulation
846
+ const enableSwipe = opt.isTouchDevice || opt.enableSwipeDebug;
847
+
848
+ if (enableSwipe) {
849
+ let touchStartX = 0;
850
+ let touchStartY = 0;
851
+ let touchEndX = 0;
852
+ let touchEndY = 0;
853
+ let isSwiping = false;
854
+ let swipeDirection: 'left' | 'right' | null = null;
855
+ const monthWrapperInner = box.find('.month-wrapper-inner');
856
+ let previewMonthContainer: JQuery<HTMLElement> = null;
857
+
858
+ const createPreviewMonth = (direction: 'left' | 'right') => {
859
+ // Clean up any existing preview
860
+ if (previewMonthContainer) {
861
+ previewMonthContainer.remove();
862
+ previewMonthContainer = null;
863
+ }
864
+
865
+ let previewDate: Temporal.PlainDateTime;
866
+
867
+ if (direction === 'right') {
868
+ // Swiping right shows previous month
869
+ previewDate = prevMonth(opt.month1);
870
+ if (isMonthOutOfBounds(previewDate)) return;
871
+ } else {
872
+ // Swipe left shows next month
873
+ previewDate = opt.singleDate || opt.singleMonth
874
+ ? nextMonth(opt.month1)
875
+ : nextMonth(opt.month2);
876
+ if (isMonthOutOfBounds(previewDate)) return;
877
+ }
878
+
879
+ // Clone the entire month-wrapper-inner to get exact same structure
880
+ previewMonthContainer = monthWrapperInner.clone();
881
+ previewMonthContainer.removeClass().addClass('swipe-preview-month-wrapper-inner month-wrapper-inner-common');
882
+
883
+ // Update only the first month's content (since we're showing single month preview)
884
+ const monthName = nameMonth(previewDate.month);
885
+ previewMonthContainer.find('.month1 .month-name').html(`${monthName}${opt.hideYearInMonthName ? '' : ` ${previewDate.year}`}`);
886
+ previewMonthContainer.find('.month1 tbody').html(createMonthHTML(previewDate));
887
+
888
+ // Hide month2 if it exists in the clone
889
+ previewMonthContainer.find('.month2').hide();
890
+ previewMonthContainer.find('.gap').hide();
891
+
892
+ // Calculate exact pixel position to avoid gaps
893
+ const containerWidth = monthWrapperInner.outerWidth(true); // Include margins
894
+ const leftPosition = direction === 'right' ? -containerWidth : containerWidth;
895
+
896
+ // Position preview based on swipe direction - use exact pixel positioning
897
+ previewMonthContainer.css({
898
+ position: 'absolute',
899
+ top: monthWrapperInner.position().top + 'px',
900
+ left: leftPosition + 'px',
901
+ width: monthWrapperInner.outerWidth() + 'px',
902
+ pointerEvents: 'none',
903
+ transform: 'translateX(0)', // Start with no transform offset
904
+ 'margin-top': monthWrapperInner.css('margin-top'),
905
+ 'margin-bottom': monthWrapperInner.css('margin-bottom'),
906
+ 'margin-left': '0',
907
+ 'margin-right': '0'
908
+ });
909
+
910
+ // Append as sibling to monthWrapperInner (same parent)
911
+ monthWrapperInner.parent().append(previewMonthContainer);
912
+ };
913
+
914
+ // Setup container for swipe - add overflow hidden and relative positioning
915
+ const monthWrapper = box.find('.month-wrapper');
916
+ monthWrapper.css({
917
+ 'overflow': 'hidden',
918
+ 'position': 'relative'
919
+ });
920
+
921
+ // Helper function to handle start event (touch or mouse)
922
+ const handleDragStart = (clientX: number, clientY: number) => {
923
+ touchStartX = clientX;
924
+ touchStartY = clientY;
925
+ touchEndX = clientX;
926
+ touchEndY = clientY;
927
+ isSwiping = false;
928
+ swipeDirection = null;
929
+
930
+ // Remove transition for immediate response
931
+ monthWrapperInner.css('transition', 'none');
932
+ if (previewMonthContainer) {
933
+ previewMonthContainer.css('transition', 'none');
934
+ }
935
+ };
936
+
937
+ box.find('.month-wrapper').on('touchstart', (e) => {
938
+ const touch = e.touches?.[0] || (e as any).originalEvent?.touches?.[0];
939
+ if (touch) {
940
+ handleDragStart(touch.clientX, touch.clientY);
941
+ }
942
+ });
943
+
944
+ // Mouse drag support (debug mode)
945
+ if (opt.enableSwipeDebug) {
946
+ let isMouseDown = false;
947
+
948
+ box.find('.month-wrapper').on('mousedown', (e) => {
949
+ isMouseDown = true;
950
+ handleDragStart(e.clientX, e.clientY);
951
+ e.preventDefault(); // Prevent text selection
952
+ });
953
+
954
+ $(document).on('mouseup', () => {
955
+ if (isMouseDown && isSwiping) {
956
+ isMouseDown = false;
957
+ // Trigger the same end logic as touchend
958
+ box.find('.month-wrapper').trigger('dragend');
959
+ }
960
+ isMouseDown = false;
961
+ });
962
+
963
+ box.find('.month-wrapper').on('mousemove', (e) => {
964
+ if (!isMouseDown) return;
965
+
966
+ touchEndX = e.clientX;
967
+ touchEndY = e.clientY;
968
+
969
+ const deltaX = touchEndX - touchStartX;
970
+ const deltaY = touchEndY - touchStartY;
971
+ const absDeltaX = Math.abs(deltaX);
972
+ const absDeltaY = Math.abs(deltaY);
973
+
974
+ // Only apply visual feedback if horizontal swipe is dominant
975
+ if (absDeltaX > absDeltaY && absDeltaX > 10) {
976
+ if (!isSwiping) {
977
+ isSwiping = true;
978
+ swipeDirection = deltaX > 0 ? 'right' : 'left';
979
+ createPreviewMonth(swipeDirection);
980
+ }
981
+
982
+ // Apply transform to show dragging effect - both move at same speed
983
+ monthWrapperInner.css('transform', `translateX(${deltaX}px)`);
984
+
985
+ // Move preview container in sync at the same speed
986
+ if (previewMonthContainer) {
987
+ const baseOffset = swipeDirection === 'right' ? '-100%' : '100%';
988
+ previewMonthContainer.css({
989
+ 'left': baseOffset,
990
+ 'transform': `translateX(${deltaX}px)`
991
+ });
992
+ }
993
+
994
+ e.preventDefault();
995
+ }
996
+ });
997
+ }
998
+
999
+ box.find('.month-wrapper').on('touchmove', (e) => {
1000
+ const touch = e.touches?.[0] || (e as any).originalEvent?.touches?.[0];
1001
+ if (touch) {
1002
+ touchEndX = touch.clientX;
1003
+ touchEndY = touch.clientY;
1004
+
1005
+ const deltaX = touchEndX - touchStartX;
1006
+ const deltaY = touchEndY - touchStartY;
1007
+ const absDeltaX = Math.abs(deltaX);
1008
+ const absDeltaY = Math.abs(deltaY);
1009
+
1010
+ // Only apply visual feedback if horizontal swipe is dominant
1011
+ if (absDeltaX > absDeltaY && absDeltaX > 10) {
1012
+ if (!isSwiping) {
1013
+ isSwiping = true;
1014
+ swipeDirection = deltaX > 0 ? 'right' : 'left';
1015
+ createPreviewMonth(swipeDirection);
1016
+ }
1017
+
1018
+ // Apply transform to show dragging effect - both move at same speed
1019
+ monthWrapperInner.css('transform', `translateX(${deltaX}px)`);
1020
+
1021
+ // Move preview container in sync at the same speed
1022
+ if (previewMonthContainer) {
1023
+ // Preview starts at -100% (left) or 100% (right) of container width
1024
+ // As we drag, it moves the same distance as the main calendar
1025
+ const baseOffset = swipeDirection === 'right' ? '-100%' : '100%';
1026
+ previewMonthContainer.css({
1027
+ 'left': baseOffset,
1028
+ 'transform': `translateX(${deltaX}px)`
1029
+ });
1030
+ }
1031
+
1032
+ // Prevent default scrolling when swiping horizontally
1033
+ e.preventDefault();
1034
+ }
1035
+ }
1036
+ });
1037
+
1038
+ // Shared drag end logic for both touch and mouse
1039
+ const handleDragEnd = () => {
1040
+ const deltaX = touchEndX - touchStartX;
1041
+ const deltaY = touchEndY - touchStartY;
1042
+ const absDeltaX = Math.abs(deltaX);
1043
+ const absDeltaY = Math.abs(deltaY);
1044
+
1045
+ // Calculate if user dragged past 40% of the width
1046
+ const containerWidth = monthWrapperInner.width();
1047
+ const dragThreshold = containerWidth * 0.4;
1048
+ const shouldChangeMonth = isSwiping && absDeltaX > dragThreshold && absDeltaX > absDeltaY;
1049
+
1050
+ if (shouldChangeMonth) {
1051
+ // Add smooth transition for the slide completion
1052
+ monthWrapperInner.css('transition', 'transform 0.3s ease-out');
1053
+ if (previewMonthContainer) {
1054
+ previewMonthContainer.css('transition', 'transform 0.3s ease-out');
1055
+ }
1056
+
1057
+ // Complete the slide animation
1058
+ const fullDistance = deltaX > 0 ? containerWidth : -containerWidth;
1059
+ monthWrapperInner.css('transform', `translateX(${fullDistance}px)`);
1060
+ if (previewMonthContainer) {
1061
+ previewMonthContainer.css('transform', `translateX(${fullDistance}px)`);
1062
+ }
1063
+
1064
+ // After animation, change the month and reset
1065
+ setTimeout(() => {
1066
+ if (deltaX > 0) {
1067
+ // Swipe right - go to previous month
1068
+ const prevButton = box.find('.month1 .prev');
1069
+ if (prevButton.length > 0) {
1070
+ if (!opt.stickyMonths) {
1071
+ gotoPrevMonth(prevButton[0]);
1072
+ } else {
1073
+ gotoPrevMonth_stickily(prevButton[0]);
1074
+ }
1075
+ }
1076
+ } else {
1077
+ // Swipe left - go to next month
1078
+ const nextButton = opt.singleDate || opt.singleMonth
1079
+ ? box.find('.month1 .next')
1080
+ : box.find('.month2 .next');
1081
+ if (nextButton.length > 0) {
1082
+ if (!opt.stickyMonths) {
1083
+ gotoNextMonth(nextButton[0]);
1084
+ } else {
1085
+ gotoNextMonth_stickily(nextButton[0]);
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ // Clean up
1091
+ monthWrapperInner.css({
1092
+ 'transition': 'none',
1093
+ 'transform': 'translateX(0)'
1094
+ });
1095
+ if (previewMonthContainer) {
1096
+ previewMonthContainer.remove();
1097
+ previewMonthContainer = null;
1098
+ }
1099
+ }, 300);
1100
+ } else {
1101
+ // Snap back to original position
1102
+ monthWrapperInner.css('transition', 'transform 0.3s ease-out');
1103
+ monthWrapperInner.css('transform', 'translateX(0)');
1104
+
1105
+ if (previewMonthContainer) {
1106
+ previewMonthContainer.css('transition', 'transform 0.3s ease-out');
1107
+ previewMonthContainer.css('transform', 'translateX(0)');
1108
+
1109
+ setTimeout(() => {
1110
+ if (previewMonthContainer) {
1111
+ previewMonthContainer.remove();
1112
+ previewMonthContainer = null;
1113
+ }
1114
+ }, 300);
1115
+ }
1116
+ }
1117
+
1118
+ // Reset values
1119
+ touchStartX = 0;
1120
+ touchStartY = 0;
1121
+ touchEndX = 0;
1122
+ touchEndY = 0;
1123
+ isSwiping = false;
1124
+ swipeDirection = null;
1125
+ };
1126
+
1127
+ // Attach touchend handler
1128
+ box.find('.month-wrapper').on('touchend', () => {
1129
+ handleDragEnd();
1130
+ });
1131
+
1132
+ // Attach dragend handler for mouse (debug mode)
1133
+ if (opt.enableSwipeDebug) {
1134
+ box.find('.month-wrapper').on('dragend', () => {
1135
+ handleDragEnd();
1136
+ });
1137
+ }
1138
+ }
1139
+
830
1140
  box.attr('unselectable', 'on')
831
1141
  .css('user-select', 'none')
832
1142
  .bind('selectstart', (e) => {
@@ -1001,12 +1311,7 @@ import { globalState } from '../../../../app/global-state';
1001
1311
 
1002
1312
  const winWidth = $(window).width();
1003
1313
  const timeEnabled = opt.time.enabled == true;
1004
-
1005
- if (winWidth <= 992 && opt.alwaysOpen != true) {
1006
- setModalDisplayMode();
1007
- } else {
1008
- setDisplayMode();
1009
- }
1314
+ setDisplayMode();
1010
1315
 
1011
1316
  if ($(window).height() < 550 && winWidth < DaterangePickerConfig.calendarsOneColThreshold && !timeEnabled) {
1012
1317
  singleMonth = true;
@@ -1516,6 +1821,76 @@ import { globalState } from '../../../../app/global-state';
1516
1821
  return true;
1517
1822
  }
1518
1823
 
1824
+ function isValidTimeDate(date: Date) {
1825
+ // --- lokálne inliny bez sub-metód ---
1826
+ // prevod PlainDate na epochDay (UTC) bez časovej zóny
1827
+ const epochDay = (y, m, d) => Math.floor(Date.UTC(y, m - 1, d) / MS_PER_DAY);
1828
+
1829
+ // pomocné vyčítanie čísla dňa z Temporal.PlainDateTime-like objektu
1830
+ const d_epoch = epochDay(date.getFullYear(), date.getMonth() + 1, date.getDate());
1831
+
1832
+ // hranice
1833
+ const hasStartDate = !!opt.startDate;
1834
+ const hasEndDate = !!opt.endDate;
1835
+ const startDate_epoch = hasStartDate ? epochDay(opt.startDate.year, opt.startDate.month, opt.startDate.day) : 0;
1836
+ const endDate_epoch = hasEndDate ? epochDay(opt.endDate.year, opt.endDate.month, opt.endDate.day) : 0;
1837
+
1838
+ // 1) mimo absolútnych hraníc
1839
+ if (hasStartDate && d_epoch < startDate_epoch) return false;
1840
+ if (hasEndDate && d_epoch > endDate_epoch) return false;
1841
+
1842
+ // Ak ešte nie je zvolený koniec a nie je singleDate, platia ďalšie pravidlá
1843
+ if (opt.start && !opt.end && !opt.singleDate) {
1844
+ const s = opt.start;
1845
+ const s_epoch = epochDay(s.year, s.month, s.day);
1846
+
1847
+ // 2) minDays / maxDays (počítané ako rozdiel v celých dňoch)
1848
+ if (opt.maxDays > 0) {
1849
+ const diff = Math.abs(d_epoch - s_epoch);
1850
+ if (diff > opt.maxDays) return false;
1851
+ }
1852
+ if (opt.minDays > 0) {
1853
+ const diff = Math.abs(d_epoch - s_epoch);
1854
+ if (diff < opt.minDays) return false;
1855
+ }
1856
+
1857
+ // 3) forward / backward
1858
+ if (opt.selectForward && d_epoch < s_epoch) return false;
1859
+ if (opt.selectBackward && d_epoch > s_epoch) return false;
1860
+
1861
+ // 4) disabled days cez beforeShowDay medzi start a date
1862
+ // Prechádzame smerom k start po 1 dni, ale bez volania ak je to zbytočné.
1863
+ if (opt.beforeShowDay && typeof opt.beforeShowDay === 'function') {
1864
+ // krok +1 alebo -1 podľa smeru
1865
+ const step = d_epoch === s_epoch ? 0 : (d_epoch > s_epoch ? -1 : +1);
1866
+ if (step !== 0) {
1867
+ let cur = d_epoch;
1868
+ // pôvodná logika mala while (countDays(...) > 1):
1869
+ // tu to čítaj ako "kým je medzi nimi aspoň 2-dňová vzdialenosť"
1870
+ while (Math.abs(cur - s_epoch) > 1) {
1871
+ cur += step;
1872
+
1873
+ // z cur vyrobíme PlainDateTime len vtedy keď ho naozaj voláme
1874
+ // inferuj rok-mesiac-deň cez dočasný Date, bez extra helperov
1875
+ const ms = cur * MS_PER_DAY;
1876
+ const tmp = new Date(ms);
1877
+ const y = tmp.getUTCFullYear();
1878
+ const m = tmp.getUTCMonth() + 1;
1879
+ const d = tmp.getUTCDate();
1880
+
1881
+ // PlainDateTime s nulovým časom pre kompatibilitu s existujúcim hookom
1882
+ const pdt = Temporal.PlainDateTime.from({ year: y, month: m, day: d, hour: 0, minute: 0, second: 0, millisecond: 0 });
1883
+
1884
+ const arr: any = opt.beforeShowDay(pdt);
1885
+ if (arr && arr[0] === false) return false;
1886
+ }
1887
+ }
1888
+ }
1889
+ }
1890
+
1891
+ return true;
1892
+ }
1893
+
1519
1894
  function updateSelectableRange() {
1520
1895
  box.find('.day.invalid.tmp').removeClass('tmp invalid').addClass('valid');
1521
1896
  if (opt.start && !opt.end) {
@@ -1835,54 +2210,69 @@ import { globalState } from '../../../../app/global-state';
1835
2210
  }
1836
2211
 
1837
2212
  function showSelectedDays() {
1838
- if (!opt.start && !opt.end) { return; }
1839
-
1840
- box.find('.day').each(function (this: any) {
1841
- let time = parseInt($(this).attr('time'));
1842
- let start = opt.start;
1843
- let end = opt.end;
1844
- if (opt.time.enabled) {
1845
- time = startOfDay(time)[utcEpochMilliseconds]();
1846
- start = startOfDay(start);
1847
- end = startOfDay(end);
1848
- }
1849
-
1850
- if (
1851
- (opt.start && opt.end && end[utcEpochMilliseconds]() >= time && start[utcEpochMilliseconds]() <= time)
1852
- || (opt.start && !opt.end && DateUtils.formatDate(start, 'yyyyMMdd') == DateUtils.formatDate(time, 'yyyyMMdd'))
1853
- ) {
1854
- $(this).addClass('checked');
1855
- } else {
1856
- $(this).removeClass('checked');
1857
- }
1858
-
1859
- // add first-date-selected class name to the first date selected
1860
- if (opt.start && DateUtils.formatDate(start, 'yyyyMMdd') == DateUtils.formatDate(time, 'yyyyMMdd')) {
1861
- $(this).addClass('first-date-selected');
2213
+ const tz = 'UTC';
2214
+ const toStartOfDayMs = (dt: Temporal.PlainDateTime) =>
2215
+ dt.with({ hour: 0, minute: 0, second: 0, millisecond: 0 })
2216
+ .toZonedDateTime(tz).epochMilliseconds;
2217
+
2218
+ const toEndOfDayMs = (dt: Temporal.PlainDateTime) =>
2219
+ dt.with({ hour: 23, minute: 59, second: 59, millisecond: 999 })
2220
+ .toZonedDateTime(tz).epochMilliseconds;
2221
+
2222
+ const hasStart = !!opt.start;
2223
+ const hasEnd = !!opt.end;
2224
+ const startDayStartMs = hasStart ? toStartOfDayMs(opt.start) : NaN;
2225
+ const startDayEndMs = hasStart ? toEndOfDayMs(opt.start) : NaN;
2226
+ const endDayStartMs = hasEnd ? toStartOfDayMs(opt.end) : NaN;
2227
+ const endDayEndMs = hasEnd ? toEndOfDayMs(opt.end) : NaN;
2228
+
2229
+ const rangeStartMs = hasStart ? startDayStartMs : NaN;
2230
+ const rangeEndMs = hasEnd ? endDayEndMs : startDayEndMs; // ak nie je end, berie sa len deň start
2231
+
2232
+ const $days = box.find('.day');
2233
+ for (let i = 0; i < $days.length; i++) {
2234
+ const el = $days[i];
2235
+ const timeAttr = (el as HTMLElement).getAttribute('time');
2236
+ if (!timeAttr) continue;
2237
+
2238
+ const timeMs = Number(timeAttr); // očakáva sa epoch ms
2239
+
2240
+ const isInRange =
2241
+ hasStart && hasEnd
2242
+ ? (timeMs >= rangeStartMs && timeMs <= rangeEndMs)
2243
+ : hasStart
2244
+ ? (timeMs >= startDayStartMs && timeMs <= startDayEndMs)
2245
+ : false;
2246
+
2247
+ const isStartDay = hasStart && timeMs >= startDayStartMs && timeMs <= startDayEndMs;
2248
+ const isEndDay = hasEnd && timeMs >= endDayStartMs && timeMs <= endDayEndMs;
2249
+
2250
+ const $el = $(el);
2251
+ $el.toggleClass('checked', isInRange || isStartDay || isEndDay);
2252
+ $el.toggleClass('first-date-selected', isStartDay);
2253
+ $el.toggleClass('last-date-selected', isEndDay);
2254
+ }
2255
+
2256
+ // ——— výpočet pre .week-number ———
2257
+ const startWeekNum = Number(opt.startWeek);
2258
+ const $weeks = box.find('.week-number');
2259
+ for (let i = 0; i < $weeks.length; i++) {
2260
+ const el = $weeks[i];
2261
+ const w = Number((el as HTMLElement).getAttribute('data-start-time'));
2262
+ if (w === startWeekNum) {
2263
+ $(el).addClass('week-number-selected');
1862
2264
  } else {
1863
- $(this).removeClass('first-date-selected');
1864
- }
1865
-
1866
- // add last-date-selected
1867
- if (opt.end && DateUtils.formatDate(end, 'yyyyMMdd') == DateUtils.formatDate(time, 'yyyyMMdd')) {
1868
- $(this).addClass('last-date-selected');
1869
- } else {
1870
- $(this).removeClass('last-date-selected');
1871
- }
1872
- });
1873
-
1874
- box.find('.week-number').each(function (this: any) {
1875
- if ($(this).attr('data-start-time') == opt.startWeek) {
1876
- $(this).addClass('week-number-selected');
2265
+ $(el).removeClass('week-number-selected');
1877
2266
  }
1878
- });
2267
+ }
1879
2268
  }
1880
2269
 
1881
2270
  function showMonth(date: Temporal.PlainDateTime, month: 'month1' | 'month2') {
1882
2271
  const monthName = nameMonth(date.month);
1883
2272
  var selectableMonthElement = generateSelectableMonthElement(date, month);
1884
2273
  var selectableYearElement = generateSelectableYearElement(date, month);
1885
- box.find(`.${month} .month-name`).html(`${opt.monthSelect ? selectableMonthElement : monthName}${opt.hideYearInMonthName ? '' : ` ${opt.yearSelect ? selectableYearElement : date.year}`}`);
2274
+ const html = `${opt.monthSelect ? selectableMonthElement : monthName}${opt.hideYearInMonthName ? '' : ` ${opt.yearSelect ? selectableYearElement : date.year}`}`;
2275
+ box.find(`.${month} .month-name`).first().html(html);
1886
2276
  box.find(`.${month} tbody`).html(createMonthHTML(date));
1887
2277
  opt[month] = date;
1888
2278
  updateSelectableRange();
@@ -1982,7 +2372,8 @@ import { globalState } from '../../../../app/global-state';
1982
2372
  return data;
1983
2373
  }
1984
2374
  function generateSelect(name: string, data: Array<{ value: number, text: string | number, selected: boolean, disabled: boolean }>): string {
1985
- var select = '<span class="select-wrapper"><select class="drp-date-switch month-name ' + name + '" name="' + name + '">';
2375
+ const selectedItem = data.find(p => p.selected == true);
2376
+ var select = '<span class="select-wrapper">' + (selectedItem != null ? '<span class="select-wrapper-selected-month">' + selectedItem.text + '</span>' : '') + '<select class="drp-date-switch month-name ' + name + '" name="' + name + '">';
1986
2377
  var current: string | number = '';
1987
2378
 
1988
2379
  for (var i = 0, l = data.length; i < l; i++) {
@@ -2087,13 +2478,13 @@ import { globalState } from '../../../../app/global-state';
2087
2478
  return;
2088
2479
  }
2089
2480
 
2090
- let afterAnimFired = false
2481
+ let afterAnimFired = false
2091
2482
  const afterAnim = function () {
2092
- if (afterAnimFired) {
2093
- return;
2094
- }
2483
+ if (afterAnimFired) {
2484
+ return;
2485
+ }
2095
2486
 
2096
- afterAnimFired = true;
2487
+ afterAnimFired = true;
2097
2488
  box.hide();
2098
2489
  $(self).data('date-picker-opened', false);
2099
2490
  $(self).trigger('datepicker-closed', {
@@ -2110,8 +2501,8 @@ import { globalState } from '../../../../app/global-state';
2110
2501
  box.addClass('pd-dropdown-close-animation');
2111
2502
  const timeout = setTimeout(afterAnim, 450)
2112
2503
  box.one('animationend', () => {
2113
- clearTimeout(timeout)
2114
- afterAnim()
2504
+ clearTimeout(timeout)
2505
+ afterAnim()
2115
2506
  });
2116
2507
  }
2117
2508
 
@@ -2204,7 +2595,7 @@ import { globalState } from '../../../../app/global-state';
2204
2595
  let arrowNext = '&gt;';
2205
2596
  if (opt.customArrowNextSymbol) { arrowNext = opt.customArrowNextSymbol; }
2206
2597
 
2207
- html += `<div class="month-wrapper"><div class="month-wrapper-inner">`
2598
+ html += `<div class="month-wrapper"><div class="month-wrapper-inner month-wrapper-inner-common">`
2208
2599
  + ` <table class="month1" cellspacing="0" border="0" cellpadding="0">`
2209
2600
  + ` <thead>`
2210
2601
  + ` <tr class="caption">`
@@ -2445,98 +2836,148 @@ import { globalState } from '../../../../app/global-state';
2445
2836
  }
2446
2837
 
2447
2838
  function createMonthHTML(d: Temporal.PlainDateTime) {
2448
- const days: DaterangePickerArgsCustomCellRenderDay[] = [];
2449
- d = d.with({ day: 1 });
2450
- const lastMonth = d.subtract({ days: 1 });
2451
- const now = dateNow();
2452
-
2453
- let dayOfWeek = getJsDateDay(d);
2454
- if ((dayOfWeek === 0) && (opt.startOfWeek === 'monday')) {
2455
- // add one week
2456
- dayOfWeek = 7;
2457
- }
2458
-
2459
- if (dayOfWeek > 0) {
2460
- for (let i: number = dayOfWeek; i > 0; i--) {
2461
- let day = d.subtract({ milliseconds: 86400000 * i });
2462
- let valid = isValidTime(day);
2463
- if (opt.startDate && compare_day(day, opt.startDate) < 0) {
2464
- valid = false;
2465
- } else if (opt.endDate && compare_day(day, opt.endDate) > 0) {
2466
- valid = false;
2467
- }
2839
+ // vstup prevedieme len raz na UTC Date bez časovej zóny
2840
+ // beriem prvý deň v mesiaci
2841
+ const y = d.year, m = d.month; // 1..12
2842
+ const firstMs = Date.UTC(y, m - 1, 1);
2843
+ const firstEpochDay = toEpochDayUTC(firstMs);
2844
+
2845
+ // start týždňa
2846
+ const weekStartsMonday = opt.startOfWeek === 'monday';
2847
+ const dowSun0 = weekdaySun0(firstEpochDay); // 0 nedeľa 1 pondelok ...
2848
+ const backDays = weekStartsMonday ? ((dowSun0 + 6) % 7) : dowSun0;
2849
+ const gridStartEpochDay = firstEpochDay - backDays;
2850
+
2851
+ // hranice iba ako čísla dní
2852
+ const hasStart = !!opt.startDate;
2853
+ const hasEnd = !!opt.endDate;
2854
+ const startEpochDay = hasStart ? toEpochDayUTC(Date.UTC(opt.startDate.year, opt.startDate.month - 1, opt.startDate.day)) : 0;
2855
+ const endEpochDay = hasEnd ? toEpochDayUTC(Date.UTC(opt.endDate.year, opt.endDate.month - 1, opt.endDate.day)) : 0;
2856
+
2857
+ // dnes
2858
+ const now = dateNow(); // očakávam Temporal.PlainDateTime
2859
+ const nowEpochDay = toEpochDayUTC(Date.UTC(now.year, now.month - 1, now.day));
2860
+
2861
+ // pomocná mutovateľná Date v UTC. Nepoužívaj ju na porovnávanie. Iba ak potrebuješ time alebo hooky
2862
+ const tmp = new Date(firstMs);
2863
+
2864
+ // zisti mesiac pre typ bunky rýchlo
2865
+ // posledný deň mesiaca
2866
+ const nextMonthMs = Date.UTC(y, m, 1);
2867
+ const thisMonthEndEpochDay = toEpochDayUTC(nextMonthMs - 1); // posledný deň
2868
+
2869
+ let sawToMonth = false;
2870
+ let weekCount = 0;
2871
+ const html: string[] = [];
2468
2872
 
2469
- days.push({
2470
- date: day,
2471
- type: 'lastMonth',
2472
- day: day.day,
2473
- time: day[utcEpochMilliseconds](),
2474
- valid,
2475
- });
2476
- }
2477
- }
2873
+ for (let week = 0; week < 6; week++) {
2874
+ const rowStart = gridStartEpochDay + week * 7;
2478
2875
 
2479
- const toMonth = d.month;
2480
- for (let i: number = 0; i < 40; i++) {
2481
- let today = d.add({ milliseconds: (86400000 * i) });
2482
- let valid = isValidTime(today);
2483
- if (opt.startDate && compare_day(today, opt.startDate) < 0) {
2484
- valid = false;
2485
- } else if (opt.endDate && compare_day(today, opt.endDate) > 0) {
2486
- valid = false;
2487
- }
2876
+ // ak prvá bunka riadku je po mesiaci a už sme mali toMonth ukonči
2877
+ if (week > 0 && rowStart > thisMonthEndEpochDay && sawToMonth) break;
2488
2878
 
2489
- days.push({
2490
- date: today,
2491
- type: today.month == toMonth ? 'toMonth' : 'nextMonth',
2492
- day: today.day,
2493
- time: today[utcEpochMilliseconds](),
2494
- valid,
2495
- });
2496
- }
2879
+ html.push('<tr>');
2497
2880
 
2498
- const html = [];
2499
- for (let week = 0; week < 6; week++) {
2500
- if (days[week * 7].type == 'nextMonth') {
2501
- break;
2502
- }
2881
+ for (let wd = 0; wd < 7; wd++) {
2882
+ const epochDay = rowStart + wd;
2503
2883
 
2504
- html.push('<tr>');
2884
+ // čísla na rýchle rozhodnutia
2885
+ const inMonth = epochDay >= firstEpochDay && epochDay <= thisMonthEndEpochDay;
2886
+ if (inMonth) sawToMonth = true;
2505
2887
 
2506
- for (var day: any = 0; day < 7; day++) {
2507
- const _day: any = (opt.startOfWeek == 'monday') ? day + 1 : day;
2508
- const today = days[week * 7 + _day];
2509
- const highlightToday = (Temporal.PlainDateTime.compare(today.date.toPlainDate(), now.toPlainDate()) == 0);
2510
- today.extraClass = '';
2511
- today.tooltip = '';
2512
-
2513
- if (today.valid && opt.beforeShowDay && typeof opt.beforeShowDay == 'function') {
2514
- const _r = opt.beforeShowDay(today.date);
2515
- today.valid = _r[0];
2516
- today.extraClass = _r[1] || '';
2517
- today.tooltip = _r[2] || '';
2518
- if (today.tooltip !== '') { today.extraClass += ' has-tooltip '; }
2888
+ const type = inMonth ? 'toMonth' : (epochDay < firstEpochDay ? 'lastMonth' : 'nextMonth');
2889
+
2890
+ // validácia bez Temporal
2891
+ let valid = true;
2892
+ if (hasStart && epochDay < startEpochDay) valid = false;
2893
+ if (valid && hasEnd && epochDay > endEpochDay) valid = false;
2894
+ if (valid) {
2895
+ // pre isValidTime potrebujeme skutočný čas. Použijeme tmp iba raz
2896
+ tmp.setTime(fromEpochDayUTC(epochDay));
2897
+ // ak vieš spraviť rýchlu vetvu pre isValidTime z čísel, urob to. Inak toto:
2898
+ valid = isValidTimeDate(tmp);
2519
2899
  }
2520
2900
 
2521
- const todayDivAttr = {
2522
- 'time': today.time,
2523
- 'data-tooltip': today.tooltip,
2524
- 'class': `day ${today.type} ${today.extraClass} ${today.valid ? 'valid' : 'invalid'} ${highlightToday ? 'real-today' : ''}`,
2525
- };
2901
+ // week number bunku vlož ešte pred TD dňa
2902
+ if (wd === 0 && opt.showWeekNumbers) {
2903
+ tmp.setTime(fromEpochDayUTC(epochDay));
2904
+ const wn = opt.getWeekNumber ? opt.getWeekNumber(Temporal.PlainDate.from({
2905
+ year: tmp.getUTCFullYear(),
2906
+ month: tmp.getUTCMonth() + 1,
2907
+ day: tmp.getUTCDate()
2908
+ }) as any) : '';
2909
+ html.push('<td><div class="week-number" data-start-time="', String(tmp.getTime()), '">', String(wn), '</div></td>');
2910
+ }
2526
2911
 
2527
- if (day === 0 && opt.showWeekNumbers) {
2528
- html.push(`<td><div class="week-number" data-start-time="${today.time}">${opt.getWeekNumber(today.date)}</div></td>`);
2912
+ // hook beforeShowDay len ak valid
2913
+ let extraClass = '';
2914
+ let tooltip = '';
2915
+ if (valid && opt.beforeShowDay) {
2916
+ // vytvor Temporal iba tu kde to naozaj treba
2917
+ const tpd = Temporal.PlainDate.from(epochDayToYMD(epochDay));
2918
+ const tdt = Temporal.PlainDateTime.from({ ...tpd, hour: 0, minute: 0, second: 0, millisecond: 0 });
2919
+ const _r = opt.beforeShowDay(tdt);
2920
+ valid = !!_r[0];
2921
+ if (_r[1]) extraClass = _r[1];
2922
+ if (_r[2]) tooltip = _r[2];
2529
2923
  }
2924
+ if (tooltip !== '') extraClass += ' has-tooltip';
2925
+
2926
+ const isToday = epochDay === nowEpochDay;
2927
+
2928
+ // priprava údajov pre TD a vnútro
2929
+ // tmp nastavíme na koniec dňa aby bol time konzistentný ak to tak používaš
2930
+ tmp.setTime(fromEpochDayUTC(epochDay));
2931
+ const ms = tmp.getTime();
2932
+ const dayNumber = utcDayOfMonth(tmp);
2933
+
2934
+ const divClass =
2935
+ 'day ' + type +
2936
+ (extraClass ? ' ' + extraClass : '') +
2937
+ (valid ? ' valid' : ' invalid') +
2938
+ (isToday ? ' real-today' : '');
2939
+
2940
+ const tdAttrs = attributesCallbacks({}, opt.dayTdAttrs, {
2941
+ date: null, // ak front nepotrebuje Temporal ušetríš. Inak si sem vieš dohnať PlainDate len na klikanie
2942
+ type,
2943
+ day: dayNumber,
2944
+ time: ms,
2945
+ valid,
2946
+ extraClass,
2947
+ tooltip
2948
+ } as any);
2949
+
2950
+ const inner = getInnerDayHtml(
2951
+ { 'time': String(ms), 'data-tooltip': tooltip, 'class': divClass },
2952
+ { day: dayNumber, type, time: ms, valid } as any
2953
+ );
2530
2954
 
2531
- html.push(`<td ${attributesCallbacks(
2532
- {},
2533
- opt.dayTdAttrs,
2534
- today,
2535
- )}>${getInnerDayHtml(todayDivAttr, today)}</td>`);
2955
+ html.push('<td ', tdAttrs, '>', inner, '</td>');
2536
2956
  }
2957
+
2958
+ weekCount += 1;
2537
2959
  html.push('</tr>');
2538
2960
  }
2961
+
2962
+ while (weekCount < 6) {
2963
+ html.push('<tr><td colspan="7"><div color="transparent" class="day" time="-1" style="visibility:hidden;">42</div></td></tr>');
2964
+ weekCount += 1;
2965
+ }
2966
+
2539
2967
  return html.join('');
2968
+
2969
+ // pomôcky
2970
+ function utcDayOfMonth(dt: Date) { return dt.getUTCDate(); }
2971
+
2972
+ function epochDayToYMD(epochDay: number) {
2973
+ const ms = fromEpochDayUTC(epochDay);
2974
+ tmp.setTime(ms);
2975
+ return {
2976
+ year: tmp.getUTCFullYear(),
2977
+ month: tmp.getUTCMonth() + 1,
2978
+ day: tmp.getUTCDate()
2979
+ };
2980
+ }
2540
2981
  }
2541
2982
 
2542
2983
  function getInnerDayHtml(attributes: any, today: any) {