@kne/react-calendar 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,11 @@ import { useState, useLayoutEffect, useMemo, useEffect, useCallback, useRef } fr
2
2
  import dayjs from 'dayjs';
3
3
  import customParseFormat from 'dayjs/plugin/customParseFormat';
4
4
  import useControlValue from '@kne/use-control-value';
5
- import { Popover, Space, Button, Badge, List, Tag, Card, Spin, Empty, Tooltip, Calendar } from 'antd';
5
+ import { Popover, Space, Button, Badge, List, Tag, Card, Spin, Empty, Tooltip, Alert, Drawer, Calendar } from 'antd';
6
6
  import { createWithIntlProvider, useIntl } from '@kne/react-intl';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import FormInfo, { Input, TextArea, Form, SubmitButton } from '@kne/form-info';
9
+ import { useIsMobile } from '@kne/responsive-utils';
9
10
 
10
11
  function _extends() {
11
12
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
@@ -29,16 +30,19 @@ const locale$1 = {
29
30
  'PanelTitle.dateLabel': '{year}年{monthName}{day}日',
30
31
  'PanelTitle.eventCount': '共 {count} 个日程',
31
32
  'ViewPanel.empty': '暂无日程',
32
- 'SchedulePanel.selectFreeSlot': '选择空闲时间段',
33
- 'SchedulePanel.newSchedule': '新增日程:{range}',
33
+ 'SchedulePanel.selectFreeSlot': '点击或拖拽空白处选择时间',
34
34
  'SchedulePanel.cancel': '取消',
35
35
  'SchedulePanel.save': '保存',
36
- 'SlotItem.disabled': '不可选',
37
- 'SlotItem.occupied': '已占用',
36
+ 'SchedulePanel.newScheduleTitle': '新增日程',
37
+ 'SchedulePanel.conflict': '与 {count} 个日程时间冲突',
38
38
  'DefaultFormInner.title': '标题',
39
39
  'DefaultFormInner.titlePlaceholder': '请输入日程标题',
40
40
  'DefaultFormInner.detail': '详情',
41
41
  'DefaultFormInner.detailPlaceholder': '请输入日程详情',
42
+ 'TimeWindow.hours': '{count} 小时',
43
+ 'TimeWindow.minutes': '{count} 分钟',
44
+ 'TimeWindow.editable': '允许编辑',
45
+ 'TimeWindow.locked': '已锁定',
42
46
  'Calendar.month.1': '1月',
43
47
  'Calendar.month.2': '2月',
44
48
  'Calendar.month.3': '3月',
@@ -65,16 +69,19 @@ const locale = {
65
69
  'PanelTitle.dateLabel': '{monthName} {day}, {year}',
66
70
  'PanelTitle.eventCount': '{count} events',
67
71
  'ViewPanel.empty': 'No events',
68
- 'SchedulePanel.selectFreeSlot': 'Select an available time slot',
69
- 'SchedulePanel.newSchedule': 'New event: {range}',
72
+ 'SchedulePanel.selectFreeSlot': 'Click or drag on free time',
70
73
  'SchedulePanel.cancel': 'Cancel',
71
74
  'SchedulePanel.save': 'Save',
72
- 'SlotItem.disabled': 'Unavailable',
73
- 'SlotItem.occupied': 'Occupied',
75
+ 'SchedulePanel.newScheduleTitle': 'New event',
76
+ 'SchedulePanel.conflict': 'Overlaps {count} event(s)',
74
77
  'DefaultFormInner.title': 'Title',
75
78
  'DefaultFormInner.titlePlaceholder': 'Enter event title',
76
79
  'DefaultFormInner.detail': 'Details',
77
80
  'DefaultFormInner.detailPlaceholder': 'Enter event details',
81
+ 'TimeWindow.hours': '{count} h',
82
+ 'TimeWindow.minutes': '{count} min',
83
+ 'TimeWindow.editable': 'Editable',
84
+ 'TimeWindow.locked': 'Locked',
78
85
  'Calendar.month.1': 'Jan',
79
86
  'Calendar.month.2': 'Feb',
80
87
  'Calendar.month.3': 'Mar',
@@ -382,13 +389,13 @@ const getMonthPickerItemLabel = (formatMessage, value, monthIndex) => formatMess
382
389
  monthName: getMonthName(formatMessage, monthIndex)
383
390
  });
384
391
 
385
- var style$4 = {"calendar-header":"kne-react-calendar_X1TYG","calendar-title":"kne-react-calendar_FcQis","calendar-title-part":"kne-react-calendar_LcyYv","header-picker-popover":"kne-react-calendar_T4oWB","picker-panel":"kne-react-calendar_PMS-M","picker-panel-header":"kne-react-calendar_IXQKl","picker-panel-range":"kne-react-calendar_J-Pnh","picker-panel-nav":"kne-react-calendar_Wo74w","picker-panel-nav-icon":"kne-react-calendar_Rwzuj","picker-panel-grid":"kne-react-calendar_rxTOr","picker-cell":"kne-react-calendar_bHuh6","picker-cell-active":"kne-react-calendar_xOfrO","header-actions":"kne-react-calendar_OCJaK","header-today":"kne-react-calendar_SWVUG","header-nav":"kne-react-calendar_BOheC","header-nav-icon":"kne-react-calendar_8IEwS"};
392
+ var style$5 = {"calendar-header":"kne-react-calendar_X1TYG","calendar-title":"kne-react-calendar_FcQis","calendar-title-part":"kne-react-calendar_LcyYv","header-picker-popover":"kne-react-calendar_T4oWB","picker-panel":"kne-react-calendar_PMS-M","picker-panel-header":"kne-react-calendar_IXQKl","picker-panel-range":"kne-react-calendar_J-Pnh","picker-panel-nav":"kne-react-calendar_Wo74w","picker-panel-nav-icon":"kne-react-calendar_Rwzuj","picker-panel-grid":"kne-react-calendar_rxTOr","picker-cell":"kne-react-calendar_bHuh6","picker-cell-active":"kne-react-calendar_xOfrO","header-actions":"kne-react-calendar_OCJaK","header-today":"kne-react-calendar_SWVUG","header-nav":"kne-react-calendar_BOheC","header-nav-icon":"kne-react-calendar_8IEwS"};
386
393
 
387
394
  const HeaderNavIcon = ({
388
395
  direction,
389
396
  className
390
397
  }) => /*#__PURE__*/jsx("svg", {
391
- className: className || style$4['header-nav-icon'],
398
+ className: className || style$5['header-nav-icon'],
392
399
  viewBox: "0 0 16 16",
393
400
  "aria-hidden": true,
394
401
  focusable: "false",
@@ -411,7 +418,7 @@ const PickerPanelNav = ({
411
418
  children
412
419
  }) => /*#__PURE__*/jsx("button", {
413
420
  type: "button",
414
- className: style$4['picker-panel-nav'],
421
+ className: style$5['picker-panel-nav'],
415
422
  "aria-label": label,
416
423
  onClick: onClick,
417
424
  children: children
@@ -429,9 +436,9 @@ const YearPickerPanel = ({
429
436
  length: YEAR_PAGE_SIZE
430
437
  }, (_, index) => pageStart + index), [pageStart]);
431
438
  return /*#__PURE__*/jsxs("div", {
432
- className: style$4['picker-panel'],
439
+ className: style$5['picker-panel'],
433
440
  children: [/*#__PURE__*/jsxs("div", {
434
- className: style$4['picker-panel-header'],
441
+ className: style$5['picker-panel-header'],
435
442
  children: [/*#__PURE__*/jsx(PickerPanelNav, {
436
443
  label: formatMessage({
437
444
  id: 'Header.prevYearPage'
@@ -439,10 +446,10 @@ const YearPickerPanel = ({
439
446
  onClick: () => onPageStartChange(pageStart - YEAR_PAGE_SIZE),
440
447
  children: /*#__PURE__*/jsx(HeaderNavIcon, {
441
448
  direction: "prev",
442
- className: style$4['picker-panel-nav-icon']
449
+ className: style$5['picker-panel-nav-icon']
443
450
  })
444
451
  }), /*#__PURE__*/jsxs("div", {
445
- className: style$4['picker-panel-range'],
452
+ className: style$5['picker-panel-range'],
446
453
  children: [pageStart, "-", pageStart + YEAR_PAGE_SIZE - 1]
447
454
  }), /*#__PURE__*/jsx(PickerPanelNav, {
448
455
  label: formatMessage({
@@ -451,15 +458,15 @@ const YearPickerPanel = ({
451
458
  onClick: () => onPageStartChange(pageStart + YEAR_PAGE_SIZE),
452
459
  children: /*#__PURE__*/jsx(HeaderNavIcon, {
453
460
  direction: "next",
454
- className: style$4['picker-panel-nav-icon']
461
+ className: style$5['picker-panel-nav-icon']
455
462
  })
456
463
  })]
457
464
  }), /*#__PURE__*/jsx("div", {
458
- className: style$4['picker-panel-grid'],
465
+ className: style$5['picker-panel-grid'],
459
466
  children: years.map(year => /*#__PURE__*/jsx("button", {
460
467
  type: "button",
461
- className: classNames(style$4['picker-cell'], {
462
- [style$4['picker-cell-active']]: year === value.year()
468
+ className: classNames(style$5['picker-cell'], {
469
+ [style$5['picker-cell-active']]: year === value.year()
463
470
  }),
464
471
  onClick: () => onSelect(year),
465
472
  children: year
@@ -479,13 +486,13 @@ const MonthPickerPanel = ({
479
486
  length: 12
480
487
  }, (_, index) => index), []);
481
488
  return /*#__PURE__*/jsx("div", {
482
- className: style$4['picker-panel'],
489
+ className: style$5['picker-panel'],
483
490
  children: /*#__PURE__*/jsx("div", {
484
- className: style$4['picker-panel-grid'],
491
+ className: style$5['picker-panel-grid'],
485
492
  children: months.map(month => /*#__PURE__*/jsx("button", {
486
493
  type: "button",
487
- className: classNames(style$4['picker-cell'], {
488
- [style$4['picker-cell-active']]: month === value.month()
494
+ className: classNames(style$5['picker-cell'], {
495
+ [style$5['picker-cell-active']]: month === value.month()
489
496
  }),
490
497
  onClick: () => onSelect(month),
491
498
  children: getMonthPickerItemLabel(formatMessage, value, month)
@@ -500,7 +507,7 @@ const HeaderNavButton = ({
500
507
  children
501
508
  }) => /*#__PURE__*/jsx("button", {
502
509
  type: "button",
503
- className: style$4['header-nav'],
510
+ className: style$5['header-nav'],
504
511
  "aria-label": label,
505
512
  onClick: onClick,
506
513
  children: children
@@ -543,9 +550,9 @@ const Header = ({
543
550
  setMonthOpen(false);
544
551
  };
545
552
  return /*#__PURE__*/jsxs("div", {
546
- className: style$4['calendar-header'],
553
+ className: style$5['calendar-header'],
547
554
  children: [/*#__PURE__*/jsxs("div", {
548
- className: style$4['calendar-title'],
555
+ className: style$5['calendar-title'],
549
556
  children: [/*#__PURE__*/jsx(Popover, {
550
557
  open: yearOpen,
551
558
  onOpenChange: open => {
@@ -556,7 +563,7 @@ const Header = ({
556
563
  },
557
564
  trigger: "click",
558
565
  placement: "bottomLeft",
559
- rootClassName: style$4['header-picker-popover'],
566
+ rootClassName: style$5['header-picker-popover'],
560
567
  content: /*#__PURE__*/jsx(YearPickerPanel, {
561
568
  value: date,
562
569
  pageStart: yearPageStart,
@@ -565,7 +572,7 @@ const Header = ({
565
572
  }),
566
573
  children: /*#__PURE__*/jsx("button", {
567
574
  type: "button",
568
- className: style$4['calendar-title-part'],
575
+ className: style$5['calendar-title-part'],
569
576
  children: yearText
570
577
  })
571
578
  }), /*#__PURE__*/jsx(Popover, {
@@ -578,22 +585,22 @@ const Header = ({
578
585
  },
579
586
  trigger: "click",
580
587
  placement: "bottomLeft",
581
- rootClassName: style$4['header-picker-popover'],
588
+ rootClassName: style$5['header-picker-popover'],
582
589
  content: /*#__PURE__*/jsx(MonthPickerPanel, {
583
590
  value: date,
584
591
  onSelect: handleMonthSelect
585
592
  }),
586
593
  children: /*#__PURE__*/jsx("button", {
587
594
  type: "button",
588
- className: style$4['calendar-title-part'],
595
+ className: style$5['calendar-title-part'],
589
596
  children: monthText
590
597
  })
591
598
  })]
592
599
  }), /*#__PURE__*/jsxs(Space, {
593
600
  size: 4,
594
- className: style$4['header-actions'],
601
+ className: style$5['header-actions'],
595
602
  children: [showTodayButton ? /*#__PURE__*/jsx(Button, {
596
- className: style$4['header-today'],
603
+ className: style$5['header-today'],
597
604
  type: "text",
598
605
  size: "small",
599
606
  onClick: onSelectToday,
@@ -660,7 +667,7 @@ const useStackedLayout = (ref, stackAt) => {
660
667
  return stacked;
661
668
  };
662
669
 
663
- var style$3 = {"date-cell":"kne-react-calendar_-a9AL","date-number":"kne-react-calendar_9s0kZ","date-has-events":"kne-react-calendar_-Bh5f","date-selected":"kne-react-calendar_Lpltl","date-muted":"kne-react-calendar_1xgRD","date-disabled":"kne-react-calendar_YTv16"};
670
+ var style$4 = {"date-cell":"kne-react-calendar_-a9AL","date-number":"kne-react-calendar_9s0kZ","date-has-events":"kne-react-calendar_-Bh5f","date-selected":"kne-react-calendar_Lpltl","date-muted":"kne-react-calendar_1xgRD","date-disabled":"kne-react-calendar_YTv16"};
664
671
 
665
672
  const DateCell = ({
666
673
  date,
@@ -681,14 +688,14 @@ const DateCell = ({
681
688
  return customCell;
682
689
  }
683
690
  return /*#__PURE__*/jsxs("div", {
684
- className: classNames(style$3['date-cell'], {
685
- [style$3['date-selected']]: selected,
686
- [style$3['date-muted']]: !currentMonth && !selected,
687
- [style$3['date-disabled']]: disabled && !selected,
688
- [style$3['date-has-events']]: count > 0 && !selected
691
+ className: classNames(style$4['date-cell'], {
692
+ [style$4['date-selected']]: selected,
693
+ [style$4['date-muted']]: !currentMonth && !selected,
694
+ [style$4['date-disabled']]: disabled && !selected,
695
+ [style$4['date-has-events']]: count > 0 && !selected
689
696
  }),
690
697
  children: [/*#__PURE__*/jsx("div", {
691
- className: style$3['date-number'],
698
+ className: style$4['date-number'],
692
699
  children: date.date()
693
700
  }), count ? /*#__PURE__*/jsx(Badge, {
694
701
  count: count > badgeMaxCount ? `${badgeMaxCount}+` : count,
@@ -714,7 +721,7 @@ const getEventTagLabel = event => {
714
721
  return null;
715
722
  };
716
723
 
717
- var style$2 = {"event-list-item":"kne-react-calendar_cGTpY","event-item":"kne-react-calendar_3gmHY","event-item-head":"kne-react-calendar_fa3Mn","event-item-tag":"kne-react-calendar_yALGf","event-item-time":"kne-react-calendar_1L-dA","event-item-title":"kne-react-calendar_ISKyA","event-item-detail":"kne-react-calendar_HzTU2","event-item-clickable":"kne-react-calendar_iiYOC","event-item-disabled":"kne-react-calendar_3sXqE"};
724
+ var style$3 = {"event-list-item":"kne-react-calendar_cGTpY","event-item":"kne-react-calendar_3gmHY","event-item-head":"kne-react-calendar_fa3Mn","event-item-tag":"kne-react-calendar_yALGf","event-item-time":"kne-react-calendar_1L-dA","event-item-title":"kne-react-calendar_ISKyA","event-item-detail":"kne-react-calendar_HzTU2","event-item-clickable":"kne-react-calendar_iiYOC","event-item-disabled":"kne-react-calendar_3sXqE"};
718
725
 
719
726
  const EventItem = ({
720
727
  event,
@@ -746,11 +753,11 @@ const EventItem = ({
746
753
  };
747
754
  const tagLabel = getEventTagLabel(event);
748
755
  return /*#__PURE__*/jsx(List.Item, {
749
- className: style$2['event-list-item'],
756
+ className: style$3['event-list-item'],
750
757
  children: /*#__PURE__*/jsxs("div", {
751
- className: classNames(style$2['event-item'], {
752
- [style$2['event-item-clickable']]: clickable,
753
- [style$2['event-item-disabled']]: event.disabled
758
+ className: classNames(style$3['event-item'], {
759
+ [style$3['event-item-clickable']]: clickable,
760
+ [style$3['event-item-disabled']]: event.disabled
754
761
  }),
755
762
  role: clickable ? 'button' : undefined,
756
763
  tabIndex: clickable ? 0 : undefined,
@@ -762,27 +769,27 @@ const EventItem = ({
762
769
  }
763
770
  } : undefined,
764
771
  children: [/*#__PURE__*/jsxs("div", {
765
- className: style$2['event-item-head'],
772
+ className: style$3['event-item-head'],
766
773
  children: [/*#__PURE__*/jsx("div", {
767
- className: style$2['event-item-title'],
774
+ className: style$3['event-item-title'],
768
775
  children: event.title
769
776
  }), tagLabel ? /*#__PURE__*/jsx(Tag, {
770
- className: style$2['event-item-tag'],
777
+ className: style$3['event-item-tag'],
771
778
  color: event.color,
772
779
  children: tagLabel
773
780
  }) : null]
774
781
  }), /*#__PURE__*/jsx("div", {
775
- className: style$2['event-item-time'],
782
+ className: style$3['event-item-time'],
776
783
  children: formatRange(event.start, event.end)
777
784
  }), event.detail ? /*#__PURE__*/jsx("div", {
778
- className: style$2['event-item-detail'],
785
+ className: style$3['event-item-detail'],
779
786
  children: event.detail
780
787
  }) : null]
781
788
  })
782
789
  });
783
790
  };
784
791
 
785
- var panelStyle = {"panel-card":"kne-react-calendar_uQsUE","panel-scroll-body":"kne-react-calendar_ujAl5","panel-scroll-content":"kne-react-calendar_t2e0j","panel-spin":"kne-react-calendar_tLux2","panel-spin-container":"kne-react-calendar_iW4mn","panel-title":"kne-react-calendar_C6NB3","panel-scroll-shadow":"kne-react-calendar_3ecxf","panel-scroll-shadow-visible":"kne-react-calendar_TvxhV","detail-card":"kne-react-calendar_3sEou"};
792
+ var panelStyle = {"panel-card":"kne-react-calendar_uQsUE","panel-scroll-body":"kne-react-calendar_ujAl5","panel-scroll-content":"kne-react-calendar_t2e0j","panel-spin":"kne-react-calendar_tLux2","panel-spin-container":"kne-react-calendar_iW4mn","panel-card-expanded":"kne-react-calendar_OzKbW","panel-scroll-body-expanded":"kne-react-calendar_jmktP","panel-title":"kne-react-calendar_C6NB3","panel-scroll-shadow":"kne-react-calendar_3ecxf","panel-scroll-shadow-visible":"kne-react-calendar_TvxhV","detail-card":"kne-react-calendar_3sEou"};
786
793
 
787
794
  const PanelTitle = ({
788
795
  date,
@@ -840,7 +847,8 @@ const ViewPanel = ({
840
847
  loading,
841
848
  renderEventItem,
842
849
  renderPanelHeader,
843
- onEventClick
850
+ onEventClick,
851
+ scrollable: _scrollable = true
844
852
  }) => {
845
853
  const {
846
854
  formatMessage
@@ -862,7 +870,7 @@ const ViewPanel = ({
862
870
  date: date,
863
871
  count: sortedEvents.length
864
872
  }),
865
- className: panelStyle['panel-card'],
873
+ className: classNames(panelStyle['panel-card'], !_scrollable && panelStyle['panel-card-expanded']),
866
874
  children: /*#__PURE__*/jsx(Spin, {
867
875
  spinning: !!loading,
868
876
  classNames: {
@@ -871,11 +879,11 @@ const ViewPanel = ({
871
879
  },
872
880
  children: /*#__PURE__*/jsxs("div", {
873
881
  ref: scrollRef,
874
- className: panelStyle['panel-scroll-body'],
875
- onScroll: onScroll,
876
- children: [/*#__PURE__*/jsx(PanelScrollShadow, {
882
+ className: classNames(panelStyle['panel-scroll-body'], !_scrollable && panelStyle['panel-scroll-body-expanded']),
883
+ onScroll: _scrollable ? onScroll : undefined,
884
+ children: [_scrollable ? /*#__PURE__*/jsx(PanelScrollShadow, {
877
885
  visible: scrolled
878
- }), /*#__PURE__*/jsx("div", {
886
+ }) : null, /*#__PURE__*/jsx("div", {
879
887
  className: panelStyle['panel-scroll-content'],
880
888
  children: sortedEvents.length ? /*#__PURE__*/jsx(List, {
881
889
  dataSource: sortedEvents,
@@ -897,456 +905,1443 @@ const ViewPanel = ({
897
905
  });
898
906
  };
899
907
 
900
- const LONG_PRESS_DELAY = 400;
901
- const SCROLL_CANCEL_THRESHOLD = 12;
902
- const TAP_MOVE_THRESHOLD = 8;
903
- const getPopoverKey = slot => slot ? `form:${slot.key}` : null;
904
- const getSlotIndexFromPoint = (container, clientX, clientY) => {
905
- const slotElements = container == null || container.querySelectorAll == null ? void 0 : container.querySelectorAll('[data-slot-index]');
906
- if (!(slotElements != null && slotElements.length)) {
907
- return null;
908
+ const MINUTE_MS$1 = 60 * 1000;
909
+ const toMs = value => {
910
+ if (value === null || typeof value === 'undefined') {
911
+ return NaN;
908
912
  }
909
- let closestIndex = null;
910
- let closestDistance = Infinity;
911
- for (const candidate of slotElements) {
912
- const rect = candidate.getBoundingClientRect();
913
- const index = Number(candidate.getAttribute('data-slot-index'));
914
- if (Number.isNaN(index)) {
915
- continue;
913
+ if (typeof value === 'number') {
914
+ return value;
915
+ }
916
+ const date = dayjs(value);
917
+ return date.isValid() ? date.valueOf() : NaN;
918
+ };
919
+ const snapToStep = (ms, {
920
+ originMs,
921
+ stepMs,
922
+ mode: _mode = 'round'
923
+ }) => {
924
+ const fn = _mode === 'floor' ? Math.floor : _mode === 'ceil' ? Math.ceil : Math.round;
925
+ return originMs + fn((ms - originMs) / stepMs) * stepMs;
926
+ };
927
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
928
+
929
+ /**
930
+ * Clip events to the day window and lay overlapping ones out side by side.
931
+ * Returns `{ event, startMs, endMs, column, columns }` sorted by start.
932
+ */
933
+ const layoutTimelineEvents = (events, {
934
+ startMs,
935
+ endMs
936
+ }) => {
937
+ const items = (events || []).map(event => {
938
+ const s = Math.max(toMs(event.start), startMs);
939
+ const e = Math.min(toMs(event.end), endMs);
940
+ return {
941
+ event,
942
+ startMs: s,
943
+ endMs: e
944
+ };
945
+ }).filter(item => Number.isFinite(item.startMs) && Number.isFinite(item.endMs) && item.endMs > item.startMs).sort((a, b) => a.startMs - b.startMs || b.endMs - a.endMs);
946
+ let cluster = [];
947
+ let clusterEnd = -Infinity;
948
+ let columnEnds = [];
949
+ const flush = () => {
950
+ cluster.forEach(item => {
951
+ item.columns = columnEnds.length;
952
+ });
953
+ cluster = [];
954
+ columnEnds = [];
955
+ };
956
+ items.forEach(item => {
957
+ if (item.startMs >= clusterEnd) {
958
+ flush();
959
+ clusterEnd = -Infinity;
916
960
  }
917
- if (clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right) {
918
- return index;
961
+ let column = columnEnds.findIndex(end => end <= item.startMs);
962
+ if (column === -1) {
963
+ column = columnEnds.length;
964
+ columnEnds.push(item.endMs);
965
+ } else {
966
+ columnEnds[column] = item.endMs;
919
967
  }
920
- if (clientX < rect.left || clientX > rect.right) {
921
- continue;
968
+ item.column = column;
969
+ cluster.push(item);
970
+ clusterEnd = Math.max(clusterEnd, item.endMs);
971
+ });
972
+ flush();
973
+ return items;
974
+ };
975
+
976
+ /** Merge consecutive disabled slots (same reason) into ranges. */
977
+ const mergeDisabledSlots = slots => {
978
+ const ranges = [];
979
+ (slots || []).forEach(slot => {
980
+ if (slot.status !== 'disabled') {
981
+ return;
982
+ }
983
+ const startMs = toMs(slot.start);
984
+ const endMs = toMs(slot.end);
985
+ const last = ranges[ranges.length - 1];
986
+ if (last && last.endMs === startMs && last.reason === slot.disabledReason) {
987
+ last.endMs = endMs;
988
+ return;
989
+ }
990
+ ranges.push({
991
+ startMs,
992
+ endMs,
993
+ reason: slot.disabledReason
994
+ });
995
+ });
996
+ return ranges;
997
+ };
998
+ const toBlockers = ranges => (ranges || []).map(range => {
999
+ var _range$startMs, _range$endMs;
1000
+ return {
1001
+ startMs: toMs((_range$startMs = range.startMs) != null ? _range$startMs : range.start),
1002
+ endMs: toMs((_range$endMs = range.endMs) != null ? _range$endMs : range.end)
1003
+ };
1004
+ }).filter(range => Number.isFinite(range.startMs) && Number.isFinite(range.endMs) && range.endMs > range.startMs).sort((a, b) => a.startMs - b.startMs);
1005
+
1006
+ /**
1007
+ * The free gap around `ms` between blockers, bounded by the day window.
1008
+ * Returns null when `ms` falls inside a blocker.
1009
+ */
1010
+ const getFreeGap = (ms, blockers, {
1011
+ startMs,
1012
+ endMs
1013
+ }) => {
1014
+ let gapStart = startMs;
1015
+ let gapEnd = endMs;
1016
+ for (const blocker of blockers || []) {
1017
+ if (blocker.startMs <= ms && ms < blocker.endMs) {
1018
+ return null;
922
1019
  }
923
- const centerY = (rect.top + rect.bottom) / 2;
924
- const distance = Math.abs(clientY - centerY);
925
- if (distance < closestDistance) {
926
- closestDistance = distance;
927
- closestIndex = index;
1020
+ if (blocker.endMs <= ms) {
1021
+ gapStart = Math.max(gapStart, blocker.endMs);
1022
+ } else if (blocker.startMs > ms) {
1023
+ gapEnd = Math.min(gapEnd, blocker.startMs);
928
1024
  }
929
1025
  }
930
- return closestIndex;
1026
+ if (ms < startMs || ms >= endMs || gapEnd <= gapStart) {
1027
+ return null;
1028
+ }
1029
+ return {
1030
+ startMs: gapStart,
1031
+ endMs: gapEnd
1032
+ };
931
1033
  };
932
- const clearDocumentTextSelection = () => {
933
- const selection = document.getSelection == null ? void 0 : document.getSelection();
934
- if (selection && !selection.isCollapsed) {
935
- selection.removeAllRanges();
1034
+
1035
+ /** Range created by pressing at `anchorMs` and dragging to `pointerMs`, kept inside `gap`. */
1036
+ const createSelectionRange = ({
1037
+ anchorMs,
1038
+ pointerMs,
1039
+ gap,
1040
+ originMs,
1041
+ stepMs
1042
+ }) => {
1043
+ const anchorStart = clamp(snapToStep(anchorMs, {
1044
+ originMs,
1045
+ stepMs,
1046
+ mode: 'floor'
1047
+ }), gap.startMs, gap.endMs);
1048
+ const anchorEnd = Math.min(anchorStart + stepMs, gap.endMs);
1049
+ const pointer = clamp(pointerMs, gap.startMs, gap.endMs);
1050
+ if (pointer >= anchorStart) {
1051
+ const end = clamp(snapToStep(pointer, {
1052
+ originMs,
1053
+ stepMs,
1054
+ mode: 'ceil'
1055
+ }), anchorEnd, gap.endMs);
1056
+ return {
1057
+ startMs: anchorStart,
1058
+ endMs: end
1059
+ };
936
1060
  }
1061
+ const start = clamp(snapToStep(pointer, {
1062
+ originMs,
1063
+ stepMs,
1064
+ mode: 'floor'
1065
+ }), gap.startMs, anchorStart);
1066
+ return {
1067
+ startMs: start,
1068
+ endMs: anchorEnd
1069
+ };
937
1070
  };
938
- const useScheduleSelectionGesture = ({
939
- slots,
940
- selectedSlots,
941
- setSelectedSlots,
942
- allowMultiSelect,
943
- onSlotRangeChange,
944
- onFreeSlotClick
1071
+
1072
+ /** Default range for a single click: one `unitMs` block that contains the pointer, fitted into `gap`. */
1073
+ const clickSelectionRange = ({
1074
+ pointerMs,
1075
+ gap,
1076
+ originMs,
1077
+ unitMs
1078
+ }) => {
1079
+ const duration = Math.min(unitMs, gap.endMs - gap.startMs);
1080
+ const start = clamp(snapToStep(pointerMs, {
1081
+ originMs,
1082
+ stepMs: unitMs,
1083
+ mode: 'floor'
1084
+ }), gap.startMs, gap.endMs - duration);
1085
+ return {
1086
+ startMs: start,
1087
+ endMs: start + duration
1088
+ };
1089
+ };
1090
+ const moveSelectionRange = ({
1091
+ range,
1092
+ deltaMs,
1093
+ gap,
1094
+ originMs,
1095
+ stepMs
1096
+ }) => {
1097
+ const duration = range.endMs - range.startMs;
1098
+ const start = clamp(snapToStep(range.startMs + deltaMs, {
1099
+ originMs,
1100
+ stepMs
1101
+ }), gap.startMs, gap.endMs - duration);
1102
+ return {
1103
+ startMs: start,
1104
+ endMs: start + duration
1105
+ };
1106
+ };
1107
+ const resizeSelectionRange = ({
1108
+ range,
1109
+ edge,
1110
+ pointerMs,
1111
+ gap,
1112
+ originMs,
1113
+ stepMs,
1114
+ minMs: _minMs = stepMs
945
1115
  }) => {
946
- var _selectedSlots;
1116
+ const snapped = snapToStep(pointerMs, {
1117
+ originMs,
1118
+ stepMs
1119
+ });
1120
+ if (edge === 'start') {
1121
+ const start = clamp(snapped, gap.startMs, range.endMs - _minMs);
1122
+ return {
1123
+ startMs: start,
1124
+ endMs: range.endMs
1125
+ };
1126
+ }
1127
+ const end = clamp(snapped, range.startMs + _minMs, gap.endMs);
1128
+ return {
1129
+ startMs: range.startMs,
1130
+ endMs: end
1131
+ };
1132
+ };
1133
+ const findOverlappingEvents = (range, events) => {
1134
+ if (!range) {
1135
+ return [];
1136
+ }
1137
+ return (events || []).filter(event => toMs(event.start) < range.endMs && toMs(event.end) > range.startMs);
1138
+ };
1139
+ const findOverlappingSlots = (range, slots) => {
1140
+ if (!range) {
1141
+ return [];
1142
+ }
1143
+ return (slots || []).filter(slot => toMs(slot.start) < range.endMs && toMs(slot.end) > range.startMs);
1144
+ };
1145
+
1146
+ dayjs.extend(customParseFormat);
1147
+ const DEFAULT_DURATION = 60;
1148
+ const DEFAULT_STEP = 1;
1149
+ const DEFAULT_DEBOUNCE_WAIT = 300;
1150
+ const DEFAULT_SEPARATOR = ' ~ ';
1151
+ const DEFAULT_FORMAT = ['YYYY-MM-DD HH:mm', 'HH:mm'];
1152
+ const DEFAULT_MIN_VISIBLE_SPAN = 360;
1153
+ const MINUTE_MS = 60 * 1000;
1154
+ const HOUR_MS = 60 * MINUTE_MS;
1155
+ const PARSE_FORMATS = ['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm', 'YYYY-MM-DD'];
1156
+ const parseDateLike = value => {
1157
+ if (value == null || value === '') {
1158
+ return null;
1159
+ }
1160
+ if (dayjs.isDayjs(value)) {
1161
+ return value.isValid() ? value : null;
1162
+ }
1163
+ const direct = dayjs(value);
1164
+ if (direct.isValid()) {
1165
+ return direct;
1166
+ }
1167
+ if (typeof value === 'string') {
1168
+ const parsed = dayjs(value, PARSE_FORMATS, true);
1169
+ return parsed.isValid() ? parsed : null;
1170
+ }
1171
+ return null;
1172
+ };
1173
+ const isEmptyBound = value => value == null || value === '';
1174
+ const defaultDayRange = now => {
1175
+ const day = (parseDateLike(now) || dayjs()).startOf('day');
1176
+ return [day, day.add(1, 'day')];
1177
+ };
1178
+
1179
+ /**
1180
+ * `null` 或 `[null, null]` 表示不限制;单侧为空表示该侧不限制;`undefined` 为当天全天。
1181
+ */
1182
+ const resolveRange = (range, now = dayjs()) => {
1183
+ if (range === null) {
1184
+ return [null, null];
1185
+ }
1186
+ if (!Array.isArray(range)) {
1187
+ return defaultDayRange(now);
1188
+ }
1189
+ const start = isEmptyBound(range[0]) ? null : parseDateLike(range[0]);
1190
+ const end = isEmptyBound(range[1]) ? null : parseDateLike(range[1]);
1191
+ const startInvalid = !isEmptyBound(range[0]) && !start;
1192
+ const endInvalid = !isEmptyBound(range[1]) && !end;
1193
+ if (startInvalid || endInvalid || start && end && !end.isAfter(start)) {
1194
+ return defaultDayRange(now);
1195
+ }
1196
+ return [start, end];
1197
+ };
1198
+ const resolveDuration = (duration, rangeStart, rangeEnd) => {
1199
+ const requested = Number(duration) > 0 ? Number(duration) : DEFAULT_DURATION;
1200
+ if (!rangeStart || !rangeEnd) {
1201
+ return requested;
1202
+ }
1203
+ const rangeMinutes = rangeEnd.diff(rangeStart, 'minute');
1204
+ if (rangeMinutes > 0 && requested > rangeMinutes) {
1205
+ return rangeMinutes;
1206
+ }
1207
+ return requested;
1208
+ };
1209
+ const resolveStep = step => Number(step) > 0 ? Number(step) : DEFAULT_STEP;
1210
+ const resolveVisibleSpan = ({
1211
+ visibleSpan,
1212
+ duration,
1213
+ baseDuration: _baseDuration = duration,
1214
+ rangeStart,
1215
+ rangeEnd
1216
+ }) => {
1217
+ const minimum = duration * 1.5;
1218
+ let span = Number(visibleSpan) > 0 ? Number(visibleSpan) : Math.max(_baseDuration * 6, DEFAULT_MIN_VISIBLE_SPAN);
1219
+ if (rangeStart && rangeEnd && !(Number(visibleSpan) > 0)) {
1220
+ span = Math.min(span, rangeEnd.diff(rangeStart, 'minute'));
1221
+ }
1222
+ return Math.max(span, minimum);
1223
+ };
1224
+ const snapMs = (ms, stepMs, mode = 'round') => {
1225
+ const anchor = dayjs(ms).startOf('day').valueOf();
1226
+ const units = (ms - anchor) / stepMs;
1227
+ const snapped = mode === 'floor' ? Math.floor(units) : mode === 'ceil' ? Math.ceil(units) : Math.round(units);
1228
+ return anchor + snapped * stepMs;
1229
+ };
1230
+ const clampWindowStart = (startInput, {
1231
+ rangeStart,
1232
+ rangeEnd,
1233
+ duration,
1234
+ step,
1235
+ snap: _snap = true,
1236
+ now
1237
+ }) => {
1238
+ const durationMs = duration * MINUTE_MS;
1239
+ const minMs = rangeStart ? rangeStart.valueOf() : -Infinity;
1240
+ const maxMs = rangeEnd ? rangeEnd.valueOf() - durationMs : Infinity;
1241
+ if (maxMs <= minMs) {
1242
+ return dayjs(minMs);
1243
+ }
1244
+ const fallback = rangeStart || parseDateLike(now) || dayjs();
1245
+ const start = parseDateLike(startInput) || fallback;
1246
+ const stepMs = step * MINUTE_MS;
1247
+ let target = _snap ? snapMs(start.valueOf(), stepMs) : start.valueOf();
1248
+ if (target > maxMs) {
1249
+ target = _snap ? snapMs(maxMs, stepMs, 'floor') : maxMs;
1250
+ }
1251
+ if (target < minMs) {
1252
+ target = _snap ? snapMs(minMs, stepMs, 'ceil') : minMs;
1253
+ }
1254
+ return dayjs(Math.min(Math.max(target, minMs), maxMs));
1255
+ };
1256
+ const createTimeWindow = ({
1257
+ start,
1258
+ duration,
1259
+ range,
1260
+ step,
1261
+ now,
1262
+ snap: _snap2 = true
1263
+ } = {}) => {
1264
+ const [rangeStart, rangeEnd] = resolveRange(range, now);
1265
+ const resolvedDuration = resolveDuration(duration, rangeStart, rangeEnd);
1266
+ const resolvedStep = resolveStep(step);
1267
+ const windowStart = clampWindowStart(start == null ? null : start, {
1268
+ rangeStart,
1269
+ rangeEnd,
1270
+ duration: resolvedDuration,
1271
+ step: resolvedStep,
1272
+ snap: _snap2,
1273
+ now
1274
+ });
1275
+ return {
1276
+ start: windowStart,
1277
+ end: windowStart.add(resolvedDuration, 'minute'),
1278
+ rangeStart,
1279
+ rangeEnd,
1280
+ duration: resolvedDuration,
1281
+ step: resolvedStep
1282
+ };
1283
+ };
1284
+
1285
+ /**
1286
+ * 拖动窗口一侧边界:另一侧保持不动,拖动侧按 step 吸附,并受最短 / 最长时长与范围约束。
1287
+ */
1288
+ const resizeTimeWindow = ({
1289
+ edge,
1290
+ pointerMs,
1291
+ startMs,
1292
+ endMs,
1293
+ step,
1294
+ rangeStart,
1295
+ rangeEnd,
1296
+ minDuration,
1297
+ maxDuration
1298
+ }) => {
1299
+ const stepMs = resolveStep(step) * MINUTE_MS;
1300
+ const minMs = (Number(minDuration) > 0 ? Number(minDuration) : resolveStep(step)) * MINUTE_MS;
1301
+ const maxMs = Number(maxDuration) > 0 ? Number(maxDuration) * MINUTE_MS : Infinity;
1302
+ const rangeStartMs = rangeStart ? dayjs(rangeStart).valueOf() : -Infinity;
1303
+ const rangeEndMs = rangeEnd ? dayjs(rangeEnd).valueOf() : Infinity;
1304
+ if (edge === 'start') {
1305
+ let _next = snapMs(pointerMs, stepMs);
1306
+ _next = Math.min(_next, endMs - minMs);
1307
+ _next = Math.max(_next, endMs - maxMs, rangeStartMs);
1308
+ return {
1309
+ startMs: _next,
1310
+ duration: (endMs - _next) / MINUTE_MS
1311
+ };
1312
+ }
1313
+ let next = snapMs(pointerMs, stepMs);
1314
+ next = Math.max(next, startMs + minMs);
1315
+ next = Math.min(next, startMs + maxMs, rangeEndMs);
1316
+ return {
1317
+ startMs,
1318
+ duration: (next - startMs) / MINUTE_MS
1319
+ };
1320
+ };
1321
+ const toWindowValue = window => [window.start.toDate(), window.end.toDate()];
1322
+ const getViewport = ({
1323
+ startMs,
1324
+ duration
1325
+ }, visibleSpanMinutes) => {
1326
+ const spanMs = visibleSpanMinutes * MINUTE_MS;
1327
+ const centerMs = startMs + duration * MINUTE_MS / 2;
1328
+ return {
1329
+ startMs: centerMs - spanMs / 2,
1330
+ endMs: centerMs + spanMs / 2,
1331
+ spanMs
1332
+ };
1333
+ };
1334
+ const wheelDeltaToPx = ({
1335
+ delta,
1336
+ deltaMode: _deltaMode = 0,
1337
+ trackWidth
1338
+ }) => {
1339
+ if (_deltaMode === 1) {
1340
+ return delta * 16;
1341
+ }
1342
+ if (_deltaMode === 2) {
1343
+ return delta * (trackWidth > 0 ? trackWidth : 1);
1344
+ }
1345
+ return delta;
1346
+ };
1347
+
1348
+ /**
1349
+ * 把滚轮像素累加为整步数:步数与刻度尺比例一致,单次离散滚动至少移动一步。
1350
+ */
1351
+ const consumeWheelSteps = ({
1352
+ accumulatedPx,
1353
+ deltaPx,
1354
+ pxPerStep,
1355
+ discrete
1356
+ }) => {
1357
+ const sameDirection = accumulatedPx === 0 || Math.sign(accumulatedPx) === Math.sign(deltaPx);
1358
+ const total = (sameDirection ? accumulatedPx : 0) + deltaPx;
1359
+ const perStep = pxPerStep > 0 ? pxPerStep : 1;
1360
+ let steps = Math.trunc(total / perStep) || 0;
1361
+ let rest = total - steps * perStep;
1362
+ if (steps === 0 && discrete && deltaPx !== 0) {
1363
+ steps = Math.sign(deltaPx);
1364
+ rest = 0;
1365
+ }
1366
+ return {
1367
+ steps,
1368
+ accumulatedPx: rest
1369
+ };
1370
+ };
1371
+ const DEFAULT_INERTIA_TIME_CONSTANT = 325;
1372
+ const VELOCITY_WINDOW_MS = 120;
1373
+
1374
+ /**
1375
+ * `true` 为默认强度 1,数字为强度倍数,`false` / `0` 关闭。返回滑行衰减时间常数(ms),0 表示关闭。
1376
+ */
1377
+ const resolveInertia = inertia => {
1378
+ if (inertia === false || inertia == null) {
1379
+ return inertia == null ? DEFAULT_INERTIA_TIME_CONSTANT : 0;
1380
+ }
1381
+ if (inertia === true) {
1382
+ return DEFAULT_INERTIA_TIME_CONSTANT;
1383
+ }
1384
+ const strength = Number(inertia);
1385
+ return strength > 0 ? DEFAULT_INERTIA_TIME_CONSTANT * strength : 0;
1386
+ };
1387
+
1388
+ /**
1389
+ * samples: [{ t, ms }],按时间顺序;返回最近一段内每毫秒移动的时间量。
1390
+ */
1391
+ const estimateVelocity = (samples, now) => {
1392
+ if (!samples || samples.length < 2) {
1393
+ return 0;
1394
+ }
1395
+ const latest = samples[samples.length - 1];
1396
+ const reference = now == null ? latest.t : now;
1397
+ if (reference - latest.t > VELOCITY_WINDOW_MS / 2) {
1398
+ return 0;
1399
+ }
1400
+ const recent = samples.filter(sample => latest.t - sample.t <= VELOCITY_WINDOW_MS);
1401
+ if (recent.length < 2) {
1402
+ return 0;
1403
+ }
1404
+ const first = recent[0];
1405
+ const elapsed = latest.t - first.t;
1406
+ return elapsed > 0 ? (latest.ms - first.ms) / elapsed : 0;
1407
+ };
1408
+ const timeAtClientX = ({
1409
+ clientX,
1410
+ rectLeft,
1411
+ rectWidth,
1412
+ rangeStartMs,
1413
+ rangeEndMs,
1414
+ grabOffsetMs: _grabOffsetMs = 0
1415
+ }) => {
1416
+ const span = Math.max(rangeEndMs - rangeStartMs, 1);
1417
+ const width = rectWidth > 0 ? rectWidth : 1;
1418
+ const ratio = Math.min(Math.max((clientX - rectLeft) / width, 0), 1);
1419
+ return rangeStartMs + ratio * span - _grabOffsetMs;
1420
+ };
1421
+ const formatTimeWindowParts = ({
1422
+ start,
1423
+ end,
1424
+ format: _format = DEFAULT_FORMAT
1425
+ }) => {
1426
+ const startDate = parseDateLike(start);
1427
+ const endDate = parseDateLike(end);
1428
+ if (!startDate || !endDate || typeof _format === 'function') {
1429
+ return null;
1430
+ }
1431
+ const formats = Array.isArray(_format) ? _format : [_format, _format];
1432
+ const startText = startDate.format(formats[0] || DEFAULT_FORMAT[0]);
1433
+ let endFormat = formats[1] || formats[0] || DEFAULT_FORMAT[1];
1434
+ const endAtMidnight = endDate.valueOf() === endDate.startOf('day').valueOf();
1435
+ if (_format === DEFAULT_FORMAT && !endAtMidnight && !endDate.isSame(startDate, 'day')) {
1436
+ endFormat = 'MM-DD HH:mm';
1437
+ }
1438
+ return [startText, endDate.format(endFormat)];
1439
+ };
1440
+ const formatTimeWindow = ({
1441
+ start,
1442
+ end,
1443
+ format: _format2 = DEFAULT_FORMAT,
1444
+ separator: _separator = DEFAULT_SEPARATOR
1445
+ }) => {
1446
+ if (typeof _format2 === 'function') {
1447
+ const startDate = parseDateLike(start);
1448
+ const endDate = parseDateLike(end);
1449
+ return startDate && endDate ? _format2(startDate.toDate(), endDate.toDate()) : '';
1450
+ }
1451
+ const parts = formatTimeWindowParts({
1452
+ start,
1453
+ end,
1454
+ format: _format2
1455
+ });
1456
+ return parts ? `${parts[0]}${_separator}${parts[1]}` : '';
1457
+ };
1458
+ const LABEL_INTERVALS = [1, 2, 3, 4, 6, 8, 12, 24];
1459
+ const pickLabelEvery = (pxPerHour, minLabelPx) => {
1460
+ if (!(pxPerHour > 0)) {
1461
+ return 3;
1462
+ }
1463
+ return LABEL_INTERVALS.find(every => pxPerHour * every >= minLabelPx) || 24;
1464
+ };
1465
+ const TICK_MINUTES = [1, 2, 5, 10, 15, 30, 60];
1466
+ const MIN_TICK_GAP_PX = 7;
1467
+ const pickTickMinutes = (pxPerHour, labelEvery) => {
1468
+ const fit = TICK_MINUTES.find(minutes => pxPerHour * minutes / 60 >= MIN_TICK_GAP_PX);
1469
+ return fit || labelEvery * 60;
1470
+ };
1471
+
1472
+ /**
1473
+ * 只生成当前视窗 [viewStart, viewEnd] 内的刻度;`outOfRange` 标出超出可选范围的刻度。
1474
+ */
1475
+ const buildScaleTicks = (viewStart, viewEnd, {
1476
+ trackWidth,
1477
+ rangeStart,
1478
+ rangeEnd,
1479
+ minLabelPx: _minLabelPx = 52
1480
+ } = {}) => {
1481
+ const viewStartMs = dayjs(viewStart).valueOf();
1482
+ const viewEndMs = dayjs(viewEnd).valueOf();
1483
+ const span = viewEndMs - viewStartMs;
1484
+ if (span <= 0) {
1485
+ return [];
1486
+ }
1487
+ const pxPerHour = trackWidth > 0 ? trackWidth * HOUR_MS / span : 0;
1488
+ const labelEvery = trackWidth > 0 ? pickLabelEvery(pxPerHour, _minLabelPx) : span / HOUR_MS <= 6 ? 1 : 3;
1489
+ const tickMinutes = trackWidth > 0 ? pickTickMinutes(pxPerHour, labelEvery) : 60;
1490
+ const tickMs = tickMinutes * MINUTE_MS;
1491
+ const rangeStartMs = rangeStart ? dayjs(rangeStart).valueOf() : -Infinity;
1492
+ const rangeEndMs = rangeEnd ? dayjs(rangeEnd).valueOf() : Infinity;
1493
+ const ticks = [];
1494
+ let cursorMs = snapMs(viewStartMs, tickMs, 'ceil');
1495
+ while (cursorMs <= viewEndMs) {
1496
+ const cursor = dayjs(cursorMs);
1497
+ const major = cursor.minute() === 0;
1498
+ const midnight = major && cursor.hour() === 0;
1499
+ const minute = cursor.minute();
1500
+ const level = midnight ? 'day' : major ? 'hour' : minute === 30 ? 'half' : minute % 15 === 0 && tickMinutes < 15 ? 'quarter' : 'minute';
1501
+ ticks.push({
1502
+ key: cursorMs,
1503
+ ratio: (cursorMs - viewStartMs) / span,
1504
+ major,
1505
+ level,
1506
+ label: midnight ? cursor.format('MM-DD') : cursor.format('HH:mm'),
1507
+ showLabel: major && cursor.hour() % labelEvery === 0,
1508
+ outOfRange: cursorMs < rangeStartMs || cursorMs > rangeEndMs
1509
+ });
1510
+ cursorMs = snapMs(cursorMs + tickMs, tickMs);
1511
+ }
1512
+ return ticks;
1513
+ };
1514
+ const formatDurationParts = minutes => {
1515
+ const total = Math.max(Math.round(minutes), 0);
1516
+ return {
1517
+ hours: Math.floor(total / 60),
1518
+ minutes: total % 60
1519
+ };
1520
+ };
1521
+
1522
+ var style$2 = {"root":"kne-react-calendar_0I96A","root-large":"kne-react-calendar_eGwaj","root-small":"kne-react-calendar_h50YK","header":"kne-react-calendar_oV0dZ","label":"kne-react-calendar_ES-Jt","separator":"kne-react-calendar_TWzY7","actions":"kne-react-calendar_FiXT7","switch":"kne-react-calendar_uBaQO","switch-on":"kne-react-calendar_c1XaN","duration":"kne-react-calendar_UnlvI","scale":"kne-react-calendar_2q4r3","track":"kne-react-calendar_Psk8T","root-dragging":"kne-react-calendar_lDK6o","ticks":"kne-react-calendar_RAKOV","tick":"kne-react-calendar_0-sBN","tick-minute":"kne-react-calendar_KR68F","tick-quarter":"kne-react-calendar_KG7-G","tick-half":"kne-react-calendar_cxGhs","tick-hour":"kne-react-calendar_eLF-c","tick-day":"kne-react-calendar_bVU--","labels":"kne-react-calendar_ahayU","tick-out":"kne-react-calendar_Crt1q","track-wheel":"kne-react-calendar_6YDSw","cylinder":"kne-react-calendar_zo6Jy","shade":"kne-react-calendar_7IH3i","shade-start":"kne-react-calendar_jm61m","shade-end":"kne-react-calendar_H6JJo","window":"kne-react-calendar_FpwgD","window-resizable":"kne-react-calendar_zx86H","window-fixed":"kne-react-calendar_e3Rls","edge":"kne-react-calendar_XWaYx","edge-start":"kne-react-calendar_Lk0dI","edge-end":"kne-react-calendar_-iQoy","edge-active":"kne-react-calendar_U0aKK","root-resizing":"kne-react-calendar_59sUX","window-compact":"kne-react-calendar_kvOXQ","tick-label":"kne-react-calendar_4LHqw","tick-label-day":"kne-react-calendar_S5S1g","root-locked":"kne-react-calendar_A9Acq"};
1523
+
1524
+ const GRIP_MIN_WIDTH = 28;
1525
+ const CLICK_MOVE_TOLERANCE = 4;
1526
+ const DISCRETE_WHEEL_DELTA = 50;
1527
+ const VISUAL_EASE_MS = 70;
1528
+ const WHEEL_IDLE_MS = 140;
1529
+ const WHEEL_NOTCH_EASE_MS = 90;
1530
+ const WHEEL_INERTIA_SCALE = 0.6;
1531
+ const sameInstant = (left, right) => {
1532
+ const leftDate = parseDateLike(left);
1533
+ const rightDate = parseDateLike(right);
1534
+ if (!leftDate || !rightDate) {
1535
+ return false;
1536
+ }
1537
+ return Math.abs(leftDate.valueOf() - rightDate.valueOf()) < 1000;
1538
+ };
1539
+ const readWindowStart = value => {
1540
+ if (!Array.isArray(value)) {
1541
+ return null;
1542
+ }
1543
+ return parseDateLike(value[0]);
1544
+ };
1545
+ const isDiscreteWheel = (event, deltaPx) => {
1546
+ if (event.deltaMode !== 0) {
1547
+ return true;
1548
+ }
1549
+ const legacy = event.wheelDeltaY || event.wheelDeltaX;
1550
+ if (legacy && Math.abs(legacy) % 120 === 0) {
1551
+ return true;
1552
+ }
1553
+ return Math.abs(deltaPx) >= DISCRETE_WHEEL_DELTA;
1554
+ };
1555
+ const useElementWidth = ref => {
1556
+ const [width, setWidth] = useState(0);
1557
+ useEffect(() => {
1558
+ const node = ref.current;
1559
+ if (!node) {
1560
+ return undefined;
1561
+ }
1562
+ setWidth(node.getBoundingClientRect().width);
1563
+ if (typeof ResizeObserver === 'undefined') {
1564
+ return undefined;
1565
+ }
1566
+ const observer = new ResizeObserver(entries => {
1567
+ const entry = entries[0];
1568
+ if (entry) {
1569
+ setWidth(entry.contentRect.width);
1570
+ }
1571
+ });
1572
+ observer.observe(node);
1573
+ return () => observer.disconnect();
1574
+ }, [ref]);
1575
+ return width;
1576
+ };
1577
+ const TimeWindowInner = ({
1578
+ value,
1579
+ defaultValue,
1580
+ onChange,
1581
+ duration,
1582
+ range,
1583
+ step,
1584
+ visibleSpan,
1585
+ debounceWait: _debounceWait = DEFAULT_DEBOUNCE_WAIT,
1586
+ format: _format = DEFAULT_FORMAT,
1587
+ separator: _separator = DEFAULT_SEPARATOR,
1588
+ showDuration: _showDuration = true,
1589
+ size: _size = 'default',
1590
+ inertia: _inertia = true,
1591
+ resizable: _resizable = true,
1592
+ wheelEffect: _wheelEffect = true,
1593
+ lockable: _lockable = false,
1594
+ locked,
1595
+ defaultLocked: _defaultLocked = false,
1596
+ onLockedChange,
1597
+ minDuration,
1598
+ maxDuration,
1599
+ className,
1600
+ style: styleProp
1601
+ }) => {
1602
+ const {
1603
+ formatMessage
1604
+ } = useIntl();
1605
+ const isControlled = typeof value !== 'undefined';
1606
+ const [innerValue, setInnerValue] = useState(() => {
1607
+ if (Array.isArray(defaultValue) && readWindowStart(defaultValue)) {
1608
+ return defaultValue;
1609
+ }
1610
+ return toWindowValue(createTimeWindow({
1611
+ duration,
1612
+ range,
1613
+ step
1614
+ }));
1615
+ });
1616
+ const [draftStart, setDraftStart] = useState(null);
1617
+ const [draftDuration, setDraftDuration] = useState(null);
947
1618
  const [dragging, setDragging] = useState(false);
948
- const [activePopoverKey, setActivePopoverKey] = useState(null);
949
- const slotsRef = useRef(null);
950
- const draggingRef = useRef(false);
951
- const dragStartIndexRef = useRef(null);
952
- const longPressTimerRef = useRef(null);
953
- const touchStateRef = useRef(null);
954
- const slotsDataRef = useRef(slots);
955
- const selectedSlotsRef = useRef(selectedSlots);
956
- const allowMultiSelectRef = useRef(allowMultiSelect);
957
- const suppressTriggerUntilRef = useRef(0);
958
- const suppressMouseUntilRef = useRef(0);
959
- const scrollLockRef = useRef(null);
960
- const handlersRef = useRef({});
961
- slotsDataRef.current = slots;
962
- selectedSlotsRef.current = selectedSlots;
963
- allowMultiSelectRef.current = allowMultiSelect;
964
- const selectedKeys = useMemo(() => new Set(selectedSlots.map(slot => slot.key)), [selectedSlots]);
965
- const selectedAnchorKey = (_selectedSlots = selectedSlots[selectedSlots.length - 1]) == null ? void 0 : _selectedSlots.key;
966
- const formPopoverKey = selectedAnchorKey ? `form:${selectedAnchorKey}` : null;
967
- const clearLongPress = useCallback(() => {
968
- if (longPressTimerRef.current) {
969
- window.clearTimeout(longPressTimerRef.current);
970
- longPressTimerRef.current = null;
1619
+ const [innerLocked, setInnerLocked] = useState(_defaultLocked);
1620
+ const lockedControlled = typeof locked === 'boolean';
1621
+ const isLocked = _lockable && (lockedControlled ? locked : innerLocked);
1622
+ const [resizeView, setResizeView] = useState(null);
1623
+ const resizeRef = useRef(null);
1624
+ const resizeEdge = resizeView ? resizeView.edge : null;
1625
+ const rootRef = useRef(null);
1626
+ const trackRef = useRef(null);
1627
+ const dragRef = useRef(null);
1628
+ const wheelAccumRef = useRef(0);
1629
+ const interactingRef = useRef(false);
1630
+ const timerRef = useRef(null);
1631
+ const pendingRef = useRef(null);
1632
+ const onChangeRef = useRef(onChange);
1633
+ const onLockedChangeRef = useRef(onLockedChange);
1634
+ const isLockedRef = useRef(isLocked);
1635
+ onLockedChangeRef.current = onLockedChange;
1636
+ isLockedRef.current = isLocked;
1637
+ const trackWidth = useElementWidth(trackRef);
1638
+ const committedValue = isControlled ? value : innerValue;
1639
+ const committedStart = readWindowStart(committedValue);
1640
+ const committedMs = committedStart ? committedStart.valueOf() : null;
1641
+ const anchorDay = committedStart ? committedStart.startOf('day').valueOf() : undefined;
1642
+ const draftMs = draftStart ? draftStart.valueOf() : null;
1643
+ const committedEnd = Array.isArray(committedValue) ? parseDateLike(committedValue[1]) : null;
1644
+ const committedEndMs = committedEnd ? committedEnd.valueOf() : null;
1645
+ const committedDuration = committedStart && committedEnd && committedEnd.isAfter(committedStart) ? committedEnd.diff(committedStart, 'minute', true) : null;
1646
+ const activeDuration = draftDuration != null ? draftDuration : committedDuration != null ? committedDuration : duration;
1647
+ const initialDurationRef = useRef(committedDuration || DEFAULT_DURATION);
1648
+ const optionsRef = useRef(null);
1649
+ onChangeRef.current = onChange;
1650
+ optionsRef.current = {
1651
+ duration: activeDuration,
1652
+ range,
1653
+ step,
1654
+ now: anchorDay
1655
+ };
1656
+ const baseWindow = useMemo(() => {
1657
+ const start = draftMs == null ? committedMs : draftMs;
1658
+ return createTimeWindow({
1659
+ start,
1660
+ duration: activeDuration,
1661
+ range,
1662
+ step,
1663
+ now: anchorDay
1664
+ });
1665
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1666
+ }, [anchorDay, committedMs, draftMs, activeDuration, step, JSON.stringify(range === undefined ? 'default' : range)]);
1667
+ const visibleSpanMinutes = resolveVisibleSpan({
1668
+ visibleSpan,
1669
+ duration: baseWindow.duration,
1670
+ baseDuration: Number(duration) > 0 ? Number(duration) : initialDurationRef.current,
1671
+ rangeStart: baseWindow.rangeStart,
1672
+ rangeEnd: baseWindow.rangeEnd
1673
+ });
1674
+ const baseStartMs = baseWindow.start.valueOf();
1675
+ const rangeStartMs = baseWindow.rangeStart ? baseWindow.rangeStart.valueOf() : null;
1676
+ const rangeEndMs = baseWindow.rangeEnd ? baseWindow.rangeEnd.valueOf() : null;
1677
+ const durationMs = baseWindow.duration * 60 * 1000;
1678
+ const [visualMs, setVisualMs] = useState(null);
1679
+ const visualRef = useRef(null);
1680
+ const visualTargetRef = useRef(null);
1681
+ const frameRef = useRef(null);
1682
+ const lastFrameRef = useRef(0);
1683
+ const wheelActiveRef = useRef(false);
1684
+ const wheelIdleRef = useRef(null);
1685
+ const easeRef = useRef(VISUAL_EASE_MS);
1686
+ const glidingRef = useRef(false);
1687
+ const wheelBaseRef = useRef(null);
1688
+ const wheelSamplesRef = useRef([]);
1689
+ const wheelDirectionRef = useRef(0);
1690
+ const inertiaTau = resolveInertia(_inertia);
1691
+ const inertiaRef = useRef(inertiaTau);
1692
+ inertiaRef.current = inertiaTau;
1693
+ const clampVisual = ms => {
1694
+ let next = ms;
1695
+ if (rangeEndMs != null) {
1696
+ next = Math.min(next, rangeEndMs - durationMs);
971
1697
  }
972
- }, []);
973
- const unlockContainerScroll = useCallback(() => {
974
- const container = slotsRef.current;
975
- if (!container || scrollLockRef.current == null) {
976
- return;
1698
+ if (rangeStartMs != null) {
1699
+ next = Math.max(next, rangeStartMs);
977
1700
  }
978
- const scrollTop = scrollLockRef.current;
979
- container.style.overflow = '';
980
- container.style.touchAction = '';
981
- container.scrollTop = scrollTop;
982
- scrollLockRef.current = null;
983
- }, []);
984
- const lockContainerScroll = useCallback(() => {
985
- const container = slotsRef.current;
986
- if (!container || scrollLockRef.current != null) {
1701
+ return next;
1702
+ };
1703
+ const clampVisualRef = useRef(clampVisual);
1704
+ clampVisualRef.current = clampVisual;
1705
+ const runFrame = time => {
1706
+ const target = visualTargetRef.current;
1707
+ const current = visualRef.current == null ? target : visualRef.current;
1708
+ const dt = lastFrameRef.current ? Math.min(time - lastFrameRef.current, 64) : 16;
1709
+ lastFrameRef.current = time;
1710
+ const diff = target - current;
1711
+ const settled = Math.abs(diff) < 1000;
1712
+ const next = settled ? target : current + diff * (1 - Math.exp(-dt / easeRef.current));
1713
+ visualRef.current = next;
1714
+ setVisualMs(next);
1715
+ if (glidingRef.current) {
1716
+ moveToRef.current(next);
1717
+ }
1718
+ if (settled) {
1719
+ frameRef.current = null;
1720
+ lastFrameRef.current = 0;
1721
+ easeRef.current = VISUAL_EASE_MS;
1722
+ glidingRef.current = false;
987
1723
  return;
988
1724
  }
989
- scrollLockRef.current = container.scrollTop;
990
- container.style.overflow = 'hidden';
991
- container.style.touchAction = 'none';
992
- }, []);
993
- const resetGestureState = useCallback(() => {
994
- clearLongPress();
995
- const container = slotsRef.current;
996
- const touchState = touchStateRef.current;
997
- if (container && (touchState == null ? void 0 : touchState.pointerId) != null && container.hasPointerCapture != null && container.hasPointerCapture(touchState.pointerId)) {
998
- try {
999
- container.releasePointerCapture(touchState.pointerId);
1000
- } catch (_unused) {
1001
- // Ignore capture release errors on stale pointers.
1002
- }
1725
+ frameRef.current = requestAnimationFrame(runFrame);
1726
+ };
1727
+ const stopGlide = () => {
1728
+ if (!glidingRef.current) {
1729
+ return false;
1003
1730
  }
1004
- draggingRef.current = false;
1005
- dragStartIndexRef.current = null;
1006
- touchStateRef.current = null;
1007
- container == null || container.classList.remove('schedule-calendar-touch-dragging');
1008
- unlockContainerScroll();
1009
- setDragging(false);
1010
- }, [clearLongPress, unlockContainerScroll]);
1011
- const selectSlots = useCallback(nextSlots => {
1012
- selectedSlotsRef.current = nextSlots;
1013
- if (!nextSlots.length) {
1014
- setActivePopoverKey(null);
1015
- resetGestureState();
1016
- }
1017
- setSelectedSlots(nextSlots);
1018
- onSlotRangeChange == null || onSlotRangeChange(nextSlots, {
1019
- action: nextSlots.length ? 'select' : 'clear'
1020
- });
1021
- }, [onSlotRangeChange, resetGestureState, setSelectedSlots]);
1022
- const getSelectableRange = useCallback((startIndex, endIndex) => {
1023
- const [start, end] = [Math.min(startIndex, endIndex), Math.max(startIndex, endIndex)];
1024
- return slotsDataRef.current.slice(start, end + 1).filter(slot => slot.status === 'free');
1025
- }, []);
1026
- const showFormPopoverForSlots = useCallback(slotsList => {
1027
- const anchorSlot = slotsList[slotsList.length - 1];
1028
- if (anchorSlot) {
1029
- setActivePopoverKey(getPopoverKey(anchorSlot));
1731
+ glidingRef.current = false;
1732
+ wheelBaseRef.current = null;
1733
+ wheelSamplesRef.current = [];
1734
+ wheelDirectionRef.current = 0;
1735
+ easeRef.current = VISUAL_EASE_MS;
1736
+ if (frameRef.current) {
1737
+ cancelAnimationFrame(frameRef.current);
1738
+ frameRef.current = null;
1739
+ lastFrameRef.current = 0;
1030
1740
  }
1031
- }, []);
1032
- const startSlotSelection = useCallback((slot, index, {
1033
- showPopover: _showPopover = true,
1034
- lockScroll: _lockScroll = false
1035
- } = {}) => {
1036
- var _slotsRef$current;
1037
- clearDocumentTextSelection();
1038
- setActivePopoverKey(getPopoverKey(slot));
1039
- draggingRef.current = true;
1040
- dragStartIndexRef.current = index;
1041
- (_slotsRef$current = slotsRef.current) == null || _slotsRef$current.classList.add('schedule-calendar-touch-dragging');
1042
- if (_lockScroll) {
1043
- lockContainerScroll();
1044
- }
1045
- setDragging(true);
1046
- selectSlots([slot]);
1047
- if (!_showPopover) {
1048
- suppressTriggerUntilRef.current = Date.now() + 700;
1049
- setActivePopoverKey(null);
1050
- }
1051
- onFreeSlotClick == null || onFreeSlotClick(slot);
1052
- }, [lockContainerScroll, onFreeSlotClick, selectSlots]);
1053
- const finishSelection = useCallback(({
1054
- fromTouch: _fromTouch = false
1741
+ visualTargetRef.current = visualRef.current;
1742
+ return true;
1743
+ };
1744
+ const glideTo = (ms, easeMs = inertiaTau) => {
1745
+ const snapped = createTimeWindow(_extends({
1746
+ start: ms
1747
+ }, optionsRef.current)).start.valueOf();
1748
+ glidingRef.current = true;
1749
+ easeRef.current = easeMs;
1750
+ setVisualTarget(snapped);
1751
+ };
1752
+ const setVisualTarget = (ms, {
1753
+ immediate: _immediate = false
1055
1754
  } = {}) => {
1056
- const hadSelection = selectedSlotsRef.current.length > 0;
1057
- resetGestureState();
1058
- if (hadSelection) {
1059
- if (_fromTouch) {
1060
- suppressTriggerUntilRef.current = Date.now() + 500;
1755
+ const target = clampVisualRef.current(ms);
1756
+ visualTargetRef.current = target;
1757
+ if (_immediate || typeof requestAnimationFrame === 'undefined') {
1758
+ if (frameRef.current) {
1759
+ cancelAnimationFrame(frameRef.current);
1760
+ frameRef.current = null;
1761
+ lastFrameRef.current = 0;
1061
1762
  }
1062
- showFormPopoverForSlots(selectedSlotsRef.current);
1063
- }
1064
- }, [resetGestureState, showFormPopoverForSlots]);
1065
- const updateSelectionRange = useCallback(index => {
1066
- const startIndex = dragStartIndexRef.current;
1067
- if (startIndex === null || !allowMultiSelectRef.current) {
1763
+ visualRef.current = target;
1764
+ setVisualMs(target);
1068
1765
  return;
1069
1766
  }
1070
- const nextSlots = getSelectableRange(startIndex, index);
1071
- selectSlots(nextSlots);
1072
- }, [getSelectableRange, selectSlots]);
1073
- const shouldIgnoreMouseSelectionEvent = useCallback(() => {
1074
- return !!touchStateRef.current || Date.now() < suppressMouseUntilRef.current;
1767
+ if (!frameRef.current) {
1768
+ frameRef.current = requestAnimationFrame(runFrame);
1769
+ }
1770
+ };
1771
+ const setVisualTargetRef = useRef(setVisualTarget);
1772
+ setVisualTargetRef.current = setVisualTarget;
1773
+ const glideToRef = useRef(glideTo);
1774
+ glideToRef.current = glideTo;
1775
+ const stopGlideRef = useRef(stopGlide);
1776
+ stopGlideRef.current = stopGlide;
1777
+ useEffect(() => () => {
1778
+ if (frameRef.current) {
1779
+ cancelAnimationFrame(frameRef.current);
1780
+ }
1781
+ clearTimeout(wheelIdleRef.current);
1075
1782
  }, []);
1076
- const handleMouseDown = useCallback((event, slot, index) => {
1077
- if (shouldIgnoreMouseSelectionEvent()) {
1783
+ useEffect(() => {
1784
+ if (dragRef.current || resizeRef.current || wheelActiveRef.current || glidingRef.current) {
1078
1785
  return;
1079
1786
  }
1080
- if (slot.status !== 'free') {
1787
+ if (visualRef.current == null) {
1788
+ visualRef.current = baseStartMs;
1789
+ visualTargetRef.current = baseStartMs;
1081
1790
  return;
1082
1791
  }
1083
- event.preventDefault();
1084
- startSlotSelection(slot, index);
1085
- }, [shouldIgnoreMouseSelectionEvent, startSlotSelection]);
1086
- const handleMouseEnter = useCallback(index => {
1087
- if (shouldIgnoreMouseSelectionEvent() || !draggingRef.current) {
1088
- return;
1792
+ setVisualTargetRef.current(baseStartMs);
1793
+ }, [baseStartMs]);
1794
+ const displayStartMs = resizeView || visualMs == null ? baseStartMs : visualMs;
1795
+ const viewport = resizeView || getViewport({
1796
+ startMs: displayStartMs,
1797
+ duration: baseWindow.duration
1798
+ }, visibleSpanMinutes);
1799
+ const viewRef = useRef(null);
1800
+ const baseWindowRef = useRef(baseWindow);
1801
+ baseWindowRef.current = baseWindow;
1802
+ viewRef.current = viewport;
1803
+ const ticks = useMemo(() => buildScaleTicks(viewport.startMs, viewport.endMs, {
1804
+ trackWidth,
1805
+ rangeStart: rangeStartMs,
1806
+ rangeEnd: rangeEndMs
1807
+ }), [viewport.startMs, viewport.endMs, trackWidth, rangeStartMs, rangeEndMs]);
1808
+ const toPercent = ms => (ms - viewport.startMs) / viewport.spanMs * 100;
1809
+ const width = durationMs / viewport.spanMs * 100;
1810
+ const left = toPercent(displayStartMs);
1811
+ const windowPx = width / 100 * trackWidth;
1812
+ const leftShade = rangeStartMs != null ? Math.min(Math.max(toPercent(rangeStartMs), 0), 100) : 0;
1813
+ const rightShade = rangeEndMs != null ? Math.min(Math.max(100 - toPercent(rangeEndMs), 0), 100) : 0;
1814
+ const label = useMemo(() => {
1815
+ if (typeof _format === 'function') {
1816
+ return _format(baseWindow.start.toDate(), baseWindow.end.toDate());
1089
1817
  }
1090
- updateSelectionRange(index);
1091
- }, [shouldIgnoreMouseSelectionEvent, updateSelectionRange]);
1092
- const handleMouseUp = useCallback(() => {
1093
- if (shouldIgnoreMouseSelectionEvent()) {
1094
- return;
1818
+ const [startText, endText] = formatTimeWindowParts({
1819
+ start: baseWindow.start,
1820
+ end: baseWindow.end,
1821
+ format: _format
1822
+ });
1823
+ return /*#__PURE__*/jsxs(Fragment, {
1824
+ children: [/*#__PURE__*/jsx("span", {
1825
+ children: startText
1826
+ }), /*#__PURE__*/jsx("span", {
1827
+ className: style$2.separator,
1828
+ children: _separator
1829
+ }), /*#__PURE__*/jsx("span", {
1830
+ children: endText
1831
+ })]
1832
+ });
1833
+ }, [baseWindow.end, baseWindow.start, _format, _separator]);
1834
+ const ariaValueText = formatTimeWindow({
1835
+ start: baseWindow.start,
1836
+ end: baseWindow.end,
1837
+ format: typeof _format === 'function' ? DEFAULT_FORMAT : _format,
1838
+ separator: typeof _separator === 'string' ? _separator : DEFAULT_SEPARATOR
1839
+ });
1840
+ const durationText = useMemo(() => {
1841
+ const {
1842
+ hours,
1843
+ minutes
1844
+ } = formatDurationParts(baseWindow.duration);
1845
+ const parts = [];
1846
+ if (hours) {
1847
+ parts.push(formatMessage({
1848
+ id: 'TimeWindow.hours'
1849
+ }, {
1850
+ count: hours
1851
+ }));
1095
1852
  }
1096
- finishSelection();
1097
- }, [finishSelection, shouldIgnoreMouseSelectionEvent]);
1098
- const handleMouseLeave = useCallback(() => {
1099
- if (shouldIgnoreMouseSelectionEvent()) {
1100
- return;
1853
+ if (minutes || !hours) {
1854
+ parts.push(formatMessage({
1855
+ id: 'TimeWindow.minutes'
1856
+ }, {
1857
+ count: minutes
1858
+ }));
1101
1859
  }
1102
- finishSelection();
1103
- }, [finishSelection, shouldIgnoreMouseSelectionEvent]);
1104
- const handlePopoverOpenChange = useCallback((open, currentPopoverKey, {
1105
- isForm: _isForm = false
1106
- } = {}) => {
1107
- if (Date.now() < suppressTriggerUntilRef.current) {
1108
- return;
1860
+ return parts.join(' ');
1861
+ }, [baseWindow.duration, formatMessage]);
1862
+ const clearTimer = () => {
1863
+ if (timerRef.current) {
1864
+ clearTimeout(timerRef.current);
1865
+ timerRef.current = null;
1109
1866
  }
1110
- if (!open && _isForm) {
1111
- selectSlots([]);
1867
+ };
1868
+ useEffect(() => clearTimer, []);
1869
+ useEffect(() => {
1870
+ if (interactingRef.current || draftMs == null) {
1112
1871
  return;
1113
1872
  }
1114
- setActivePopoverKey(prevKey => {
1115
- if (open) {
1116
- return currentPopoverKey;
1873
+ const pending = Array.isArray(pendingRef.current) ? pendingRef.current : null;
1874
+ if (pending && sameInstant(committedMs, pending[0]) && sameInstant(committedEndMs, pending[1])) {
1875
+ pendingRef.current = null;
1876
+ setDraftStart(null);
1877
+ setDraftDuration(null);
1878
+ }
1879
+ }, [committedMs, committedEndMs, draftMs]);
1880
+ const publish = nextWindow => {
1881
+ const nextValue = toWindowValue(nextWindow);
1882
+ baseWindowRef.current = nextWindow;
1883
+ pendingRef.current = nextValue;
1884
+ setDraftStart(nextWindow.start);
1885
+ setDraftDuration(nextWindow.duration);
1886
+ clearTimer();
1887
+ timerRef.current = setTimeout(() => {
1888
+ timerRef.current = null;
1889
+ if (!isControlled) {
1890
+ setInnerValue(nextValue);
1117
1891
  }
1118
- return prevKey === currentPopoverKey ? null : prevKey;
1119
- });
1120
- }, [selectSlots]);
1121
- handlersRef.current = {
1122
- clearLongPress,
1123
- finishSelection,
1124
- onFreeSlotClick,
1125
- resetGestureState,
1126
- selectSlots,
1127
- showFormPopoverForSlots,
1128
- startSlotSelection,
1129
- updateSelectionRange
1892
+ if (onChangeRef.current) {
1893
+ onChangeRef.current(nextValue);
1894
+ }
1895
+ }, _debounceWait);
1130
1896
  };
1897
+ const moveTo = startInput => {
1898
+ const current = baseWindowRef.current;
1899
+ const nextWindow = createTimeWindow(_extends({
1900
+ start: startInput
1901
+ }, optionsRef.current));
1902
+ if (nextWindow.start.valueOf() === current.start.valueOf()) {
1903
+ return;
1904
+ }
1905
+ publish(nextWindow);
1906
+ };
1907
+ const moveToRef = useRef(moveTo);
1908
+ moveToRef.current = moveTo;
1909
+ const durationPropRef = useRef(duration);
1131
1910
  useEffect(() => {
1132
- const container = slotsRef.current;
1133
- if (!container) {
1134
- return undefined;
1911
+ if (durationPropRef.current === duration) {
1912
+ return;
1135
1913
  }
1136
- const releasePointerCapture = pointerId => {
1137
- if (pointerId == null || !(container.hasPointerCapture != null && container.hasPointerCapture(pointerId))) {
1138
- return;
1139
- }
1140
- try {
1141
- container.releasePointerCapture(pointerId);
1142
- } catch (_unused2) {
1143
- // Ignore capture release errors on stale pointers.
1144
- }
1914
+ durationPropRef.current = duration;
1915
+ publish(createTimeWindow(_extends({}, optionsRef.current, {
1916
+ start: baseWindowRef.current.start,
1917
+ duration
1918
+ })));
1919
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1920
+ }, [duration]);
1921
+ const pointerTimeAt = (clientX, view) => {
1922
+ const rect = trackRef.current ? trackRef.current.getBoundingClientRect() : {
1923
+ left: 0,
1924
+ width: 1
1145
1925
  };
1146
- const clearPendingTouch = () => {
1147
- handlersRef.current.clearLongPress();
1148
- touchStateRef.current = null;
1926
+ return timeAtClientX({
1927
+ clientX,
1928
+ rectLeft: rect.left,
1929
+ rectWidth: rect.width,
1930
+ rangeStartMs: view.startMs,
1931
+ rangeEndMs: view.endMs
1932
+ });
1933
+ };
1934
+ const startResize = (event, edge) => {
1935
+ const view = viewRef.current;
1936
+ const current = baseWindowRef.current;
1937
+ const edgeMs = edge === 'start' ? current.start.valueOf() : current.end.valueOf();
1938
+ resizeRef.current = {
1939
+ pointerId: event.pointerId,
1940
+ edge,
1941
+ view,
1942
+ grabOffsetMs: pointerTimeAt(event.clientX, view) - edgeMs
1149
1943
  };
1150
- const isStillOnSameSlot = (touchState, clientX, clientY) => {
1151
- const currentIndex = getSlotIndexFromPoint(container, clientX, clientY);
1152
- return currentIndex === touchState.index;
1944
+ interactingRef.current = true;
1945
+ setResizeView(_extends({}, view, {
1946
+ edge
1947
+ }));
1948
+ try {
1949
+ event.currentTarget.setPointerCapture(event.pointerId);
1950
+ } catch (error) {
1951
+ // pointer may already be released
1952
+ }
1953
+ };
1954
+ const moveResize = event => {
1955
+ const resize = resizeRef.current;
1956
+ const current = baseWindowRef.current;
1957
+ const next = resizeTimeWindow({
1958
+ edge: resize.edge,
1959
+ pointerMs: pointerTimeAt(event.clientX, resize.view) - resize.grabOffsetMs,
1960
+ startMs: current.start.valueOf(),
1961
+ endMs: current.end.valueOf(),
1962
+ step: current.step,
1963
+ rangeStart: current.rangeStart,
1964
+ rangeEnd: current.rangeEnd,
1965
+ minDuration,
1966
+ maxDuration
1967
+ });
1968
+ if (next.startMs === current.start.valueOf() && next.duration === current.duration) {
1969
+ return;
1970
+ }
1971
+ publish(createTimeWindow(_extends({}, optionsRef.current, {
1972
+ start: next.startMs,
1973
+ duration: next.duration
1974
+ })));
1975
+ };
1976
+ const endResize = () => {
1977
+ const resize = resizeRef.current;
1978
+ resizeRef.current = null;
1979
+ interactingRef.current = false;
1980
+ const current = baseWindowRef.current;
1981
+ const centerMs = resize.view.startMs + resize.view.spanMs / 2;
1982
+ const frozenStart = centerMs - current.duration * 60 * 1000 / 2;
1983
+ visualRef.current = frozenStart;
1984
+ setVisualMs(frozenStart);
1985
+ setResizeView(null);
1986
+ setVisualTargetRef.current(current.start.valueOf());
1987
+ };
1988
+ const onPointerDown = event => {
1989
+ if (event.button != null && event.button !== 0) {
1990
+ return;
1991
+ }
1992
+ const braked = stopGlideRef.current();
1993
+ const edgeNode = _resizable && event.target && event.target.closest ? event.target.closest('[data-edge]') : null;
1994
+ if (edgeNode) {
1995
+ startResize(event, edgeNode.getAttribute('data-edge'));
1996
+ return;
1997
+ }
1998
+ dragRef.current = {
1999
+ pointerId: event.pointerId,
2000
+ startX: event.clientX,
2001
+ originMs: visualRef.current == null ? baseWindowRef.current.start.valueOf() : visualRef.current,
2002
+ moved: false,
2003
+ braked,
2004
+ samples: []
1153
2005
  };
1154
- const shouldCancelPendingGesture = (touchState, clientX, clientY) => {
1155
- const moveX = Math.abs(clientX - touchState.x);
1156
- const moveY = Math.abs(clientY - touchState.y);
1157
- if (moveY > SCROLL_CANCEL_THRESHOLD) {
1158
- return true;
1159
- }
1160
- if (!isStillOnSameSlot(touchState, clientX, clientY)) {
1161
- return true;
1162
- }
1163
- if (!allowMultiSelectRef.current && (moveX > TAP_MOVE_THRESHOLD || moveY > TAP_MOVE_THRESHOLD)) {
1164
- return true;
2006
+ interactingRef.current = true;
2007
+ try {
2008
+ event.currentTarget.setPointerCapture(event.pointerId);
2009
+ } catch (error) {
2010
+ // pointer may already be released
2011
+ }
2012
+ };
2013
+ const onPointerMove = event => {
2014
+ if (resizeRef.current) {
2015
+ if (resizeRef.current.pointerId === event.pointerId) {
2016
+ moveResize(event);
1165
2017
  }
1166
- return false;
2018
+ return;
2019
+ }
2020
+ const drag = dragRef.current;
2021
+ if (!drag || drag.pointerId !== event.pointerId) {
2022
+ return;
2023
+ }
2024
+ const dx = event.clientX - drag.startX;
2025
+ if (!drag.moved && Math.abs(dx) < CLICK_MOVE_TOLERANCE) {
2026
+ return;
2027
+ }
2028
+ if (!drag.moved) {
2029
+ drag.moved = true;
2030
+ setDragging(true);
2031
+ }
2032
+ const rect = trackRef.current ? trackRef.current.getBoundingClientRect() : {
2033
+ width: 1
1167
2034
  };
1168
- const activateLongPress = touchState => {
1169
- var _slotsRef$current2;
1170
- if (!isStillOnSameSlot(touchState, touchState.lastX, touchState.lastY)) {
1171
- return;
2035
+ const deltaMs = dx / (rect.width || 1) * viewRef.current.spanMs;
2036
+ const rawMs = drag.originMs - deltaMs;
2037
+ drag.samples.push({
2038
+ t: event.timeStamp,
2039
+ ms: rawMs
2040
+ });
2041
+ if (drag.samples.length > 12) {
2042
+ drag.samples.shift();
2043
+ }
2044
+ moveToRef.current(rawMs);
2045
+ setVisualTargetRef.current(rawMs, {
2046
+ immediate: true
2047
+ });
2048
+ };
2049
+ const endDrag = event => {
2050
+ if (resizeRef.current) {
2051
+ if (!event || resizeRef.current.pointerId === event.pointerId) {
2052
+ endResize();
1172
2053
  }
1173
- touchState.longPressed = true;
1174
- touchState.active = true;
1175
- clearDocumentTextSelection();
1176
- try {
1177
- container.setPointerCapture(touchState.pointerId);
1178
- } catch (_unused3) {
1179
- // Continue when capture is unavailable.
2054
+ return;
2055
+ }
2056
+ const drag = dragRef.current;
2057
+ if (!drag || event && drag.pointerId !== event.pointerId) {
2058
+ return;
2059
+ }
2060
+ dragRef.current = null;
2061
+ interactingRef.current = false;
2062
+ setDragging(false);
2063
+ if (drag.moved) {
2064
+ const velocity = inertiaRef.current && event ? estimateVelocity(drag.samples, event.timeStamp) : 0;
2065
+ const projection = velocity * inertiaRef.current;
2066
+ if (Math.abs(projection) >= baseWindowRef.current.step * 60 * 1000) {
2067
+ glideToRef.current(visualRef.current + projection);
2068
+ } else {
2069
+ setVisualTargetRef.current(baseWindowRef.current.start.valueOf());
1180
2070
  }
1181
- (_slotsRef$current2 = slotsRef.current) == null || _slotsRef$current2.classList.add('schedule-calendar-touch-dragging');
1182
- handlersRef.current.startSlotSelection(touchState.slot, touchState.index, {
1183
- showPopover: false,
1184
- lockScroll: true
2071
+ }
2072
+ if (!drag.moved && drag.braked) {
2073
+ moveToRef.current(drag.originMs);
2074
+ setVisualTargetRef.current(baseWindowRef.current.start.valueOf());
2075
+ return;
2076
+ }
2077
+ if (!drag.moved && event && event.type === 'pointerup') {
2078
+ const rect = trackRef.current ? trackRef.current.getBoundingClientRect() : {
2079
+ left: 0,
2080
+ width: 1
2081
+ };
2082
+ const view = viewRef.current;
2083
+ const clickedMs = timeAtClientX({
2084
+ clientX: event.clientX,
2085
+ rectLeft: rect.left,
2086
+ rectWidth: rect.width,
2087
+ rangeStartMs: view.startMs,
2088
+ rangeEndMs: view.endMs
1185
2089
  });
2090
+ const current = baseWindowRef.current;
2091
+ moveToRef.current(clickedMs - current.duration * 60 * 1000 / 2);
2092
+ }
2093
+ };
2094
+ const onKeyDown = event => {
2095
+ stopGlideRef.current();
2096
+ const current = baseWindowRef.current;
2097
+ const stepMinutes = current.step;
2098
+ const moves = {
2099
+ ArrowLeft: -stepMinutes,
2100
+ ArrowDown: -stepMinutes,
2101
+ ArrowRight: stepMinutes,
2102
+ ArrowUp: stepMinutes,
2103
+ PageDown: -current.duration,
2104
+ PageUp: current.duration
1186
2105
  };
1187
- const handlePointerDown = event => {
1188
- var _event$target;
1189
- if (event.pointerType === 'mouse' || event.isPrimary === false) {
1190
- return;
1191
- }
1192
- const slotElement = (_event$target = event.target) == null || _event$target.closest == null ? void 0 : _event$target.closest('[data-slot-index]');
1193
- if (!slotElement || !container.contains(slotElement)) {
2106
+ if (event.key === 'Home' && current.rangeStart) {
2107
+ event.preventDefault();
2108
+ moveToRef.current(current.rangeStart);
2109
+ return;
2110
+ }
2111
+ if (event.key === 'End' && current.rangeEnd) {
2112
+ event.preventDefault();
2113
+ moveToRef.current(current.rangeEnd);
2114
+ return;
2115
+ }
2116
+ if (!(event.key in moves)) {
2117
+ return;
2118
+ }
2119
+ event.preventDefault();
2120
+ const minutes = event.shiftKey ? Math.sign(moves[event.key]) * Math.max(60, stepMinutes) : moves[event.key];
2121
+ moveToRef.current(current.start.add(minutes, 'minute'));
2122
+ };
2123
+ useEffect(() => {
2124
+ const node = rootRef.current;
2125
+ if (!node) {
2126
+ return undefined;
2127
+ }
2128
+ const onWheel = event => {
2129
+ if (isLockedRef.current) {
1194
2130
  return;
1195
2131
  }
1196
- const index = Number(slotElement.getAttribute('data-slot-index'));
1197
- const slot = slotsDataRef.current[index];
1198
- if (Number.isNaN(index) || (slot == null ? void 0 : slot.status) !== 'free') {
2132
+ const dominant = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
2133
+ if (!dominant) {
1199
2134
  return;
1200
2135
  }
1201
- handlersRef.current.resetGestureState();
1202
- touchStateRef.current = {
1203
- pointerId: event.pointerId,
1204
- x: event.clientX,
1205
- y: event.clientY,
1206
- lastX: event.clientX,
1207
- lastY: event.clientY,
1208
- slot,
1209
- index,
1210
- moved: false,
1211
- longPressed: false,
1212
- active: false,
1213
- selectionTriggered: false
1214
- };
1215
- if (!allowMultiSelectRef.current) {
1216
- return;
1217
- }
1218
- longPressTimerRef.current = window.setTimeout(() => {
1219
- const touchState = touchStateRef.current;
1220
- if (!touchState || touchState.pointerId !== event.pointerId || touchState.moved) {
2136
+ event.preventDefault();
2137
+ const current = baseWindowRef.current;
2138
+ const rectWidth = trackRef.current ? trackRef.current.getBoundingClientRect().width : node.clientWidth;
2139
+ const deltaPx = wheelDeltaToPx({
2140
+ delta: dominant,
2141
+ deltaMode: event.deltaMode,
2142
+ trackWidth: rectWidth
2143
+ });
2144
+ const stepMs = current.step * 60 * 1000;
2145
+ const pxPerStep = rectWidth * stepMs / viewRef.current.spanMs;
2146
+ const discrete = isDiscreteWheel(event, deltaPx);
2147
+ const {
2148
+ steps,
2149
+ accumulatedPx
2150
+ } = consumeWheelSteps({
2151
+ accumulatedPx: wheelAccumRef.current,
2152
+ deltaPx,
2153
+ pxPerStep,
2154
+ discrete
2155
+ });
2156
+ wheelAccumRef.current = accumulatedPx;
2157
+ wheelActiveRef.current = true;
2158
+ clearTimeout(wheelIdleRef.current);
2159
+ wheelIdleRef.current = setTimeout(() => {
2160
+ wheelActiveRef.current = false;
2161
+ wheelAccumRef.current = 0;
2162
+ wheelBaseRef.current = null;
2163
+ wheelSamplesRef.current = [];
2164
+ wheelDirectionRef.current = 0;
2165
+ if (!glidingRef.current) {
2166
+ setVisualTargetRef.current(baseWindowRef.current.start.valueOf());
2167
+ }
2168
+ }, WHEEL_IDLE_MS);
2169
+ if (discrete && inertiaRef.current) {
2170
+ if (!steps) {
1221
2171
  return;
1222
2172
  }
1223
- activateLongPress(touchState);
1224
- longPressTimerRef.current = null;
1225
- }, LONG_PRESS_DELAY);
1226
- };
1227
- const handlePointerMove = event => {
1228
- const touchState = touchStateRef.current;
1229
- if (!touchState || event.pointerId !== touchState.pointerId) {
1230
- return;
1231
- }
1232
- touchState.lastX = event.clientX;
1233
- touchState.lastY = event.clientY;
1234
- if (!touchState.active) {
1235
- if (shouldCancelPendingGesture(touchState, event.clientX, event.clientY)) {
1236
- touchState.moved = true;
1237
- clearPendingTouch();
2173
+ const samples = wheelSamplesRef.current;
2174
+ if (wheelDirectionRef.current !== Math.sign(steps)) {
2175
+ samples.length = 0;
2176
+ wheelBaseRef.current = null;
2177
+ wheelDirectionRef.current = Math.sign(steps);
1238
2178
  }
1239
- return;
1240
- }
1241
- event.preventDefault();
1242
- event.stopPropagation();
1243
- clearDocumentTextSelection();
1244
- const index = getSlotIndexFromPoint(container, event.clientX, event.clientY);
1245
- if (index !== null) {
1246
- handlersRef.current.updateSelectionRange(index);
1247
- }
1248
- };
1249
- const handlePointerUp = event => {
1250
- const touchState = touchStateRef.current;
1251
- if (!touchState || event.pointerId !== touchState.pointerId) {
1252
- return;
1253
- }
1254
- releasePointerCapture(event.pointerId);
1255
- suppressMouseUntilRef.current = Date.now() + 700;
1256
- if (touchState.active) {
1257
- event.preventDefault();
1258
- touchState.selectionTriggered = true;
1259
- const index = getSlotIndexFromPoint(container, event.clientX, event.clientY);
1260
- if (index !== null) {
1261
- handlersRef.current.updateSelectionRange(index);
1262
- }
1263
- suppressTriggerUntilRef.current = Date.now() + 500;
1264
- handlersRef.current.finishSelection({
1265
- fromTouch: true
2179
+ const from = wheelBaseRef.current == null ? current.start.valueOf() : wheelBaseRef.current;
2180
+ const base = createTimeWindow(_extends({
2181
+ start: from + steps * stepMs
2182
+ }, optionsRef.current)).start.valueOf();
2183
+ wheelBaseRef.current = base;
2184
+ samples.push({
2185
+ t: event.timeStamp,
2186
+ ms: base
1266
2187
  });
2188
+ if (samples.length > 8) {
2189
+ samples.shift();
2190
+ }
2191
+ const projection = estimateVelocity(samples, event.timeStamp) * inertiaRef.current * WHEEL_INERTIA_SCALE;
2192
+ let target = base + projection;
2193
+ const lastTarget = visualTargetRef.current;
2194
+ if (samples.length > 1 && lastTarget != null && (target - lastTarget) * wheelDirectionRef.current < 0) {
2195
+ target = lastTarget;
2196
+ }
2197
+ glideToRef.current(target, projection ? inertiaRef.current : WHEEL_NOTCH_EASE_MS);
1267
2198
  return;
1268
- }
1269
- handlersRef.current.clearLongPress();
1270
- const quickTap = !touchState.moved && !touchState.longPressed && Math.abs(event.clientX - touchState.x) <= TAP_MOVE_THRESHOLD && Math.abs(event.clientY - touchState.y) <= TAP_MOVE_THRESHOLD && isStillOnSameSlot(touchState, event.clientX, event.clientY);
1271
- const {
1272
- slot
1273
- } = touchState;
1274
- touchStateRef.current = null;
1275
- if (quickTap) {
1276
- touchState.selectionTriggered = true;
1277
- suppressTriggerUntilRef.current = Date.now() + 500;
1278
- handlersRef.current.selectSlots([slot]);
1279
- handlersRef.current.showFormPopoverForSlots([slot]);
1280
- handlersRef.current.onFreeSlotClick == null || handlersRef.current.onFreeSlotClick(slot);
1281
- return;
1282
- }
1283
- handlersRef.current.resetGestureState();
1284
- };
1285
- const handleContextMenu = event => {
1286
- var _touchStateRef$curren;
1287
- if ((_touchStateRef$curren = touchStateRef.current) != null && _touchStateRef$curren.active || draggingRef.current) {
1288
- event.preventDefault();
1289
- }
1290
- };
1291
- const handleSelectStart = event => {
1292
- var _touchStateRef$curren2;
1293
- if (!((_touchStateRef$curren2 = touchStateRef.current) != null && _touchStateRef$curren2.active) && !draggingRef.current) {
1294
- return;
1295
- }
1296
- event.preventDefault();
1297
- };
1298
- const handleTouchMove = event => {
1299
- var _touchStateRef$curren3;
1300
- if (!draggingRef.current && !((_touchStateRef$curren3 = touchStateRef.current) != null && _touchStateRef$curren3.active)) {
1301
- return;
1302
- }
1303
- event.preventDefault();
1304
- };
1305
- const touchOptions = {
1306
- passive: false,
1307
- capture: true
1308
- };
1309
- container.addEventListener('pointerdown', handlePointerDown, {
1310
- passive: true
1311
- });
1312
- container.addEventListener('pointermove', handlePointerMove, {
1313
- passive: false
1314
- });
1315
- container.addEventListener('pointerup', handlePointerUp, {
1316
- passive: false
1317
- });
1318
- container.addEventListener('pointercancel', handlePointerUp, {
2199
+ }
2200
+ if (steps) {
2201
+ moveToRef.current(current.start.valueOf() + steps * stepMs);
2202
+ }
2203
+ const residualMs = pxPerStep > 0 ? accumulatedPx / pxPerStep * stepMs : 0;
2204
+ setVisualTargetRef.current(baseWindowRef.current.start.valueOf() + residualMs);
2205
+ };
2206
+ node.addEventListener('wheel', onWheel, {
1319
2207
  passive: false
1320
2208
  });
1321
- container.addEventListener('touchmove', handleTouchMove, touchOptions);
1322
- container.addEventListener('contextmenu', handleContextMenu);
1323
- container.addEventListener('selectstart', handleSelectStart);
1324
- return () => {
1325
- var _touchStateRef$curren4;
1326
- releasePointerCapture((_touchStateRef$curren4 = touchStateRef.current) == null ? void 0 : _touchStateRef$curren4.pointerId);
1327
- handlersRef.current.clearLongPress();
1328
- handlersRef.current.resetGestureState();
1329
- longPressTimerRef.current = null;
1330
- container.removeEventListener('touchmove', handleTouchMove, touchOptions);
1331
- };
2209
+ return () => node.removeEventListener('wheel', onWheel);
1332
2210
  }, []);
1333
- return {
1334
- activePopoverKey,
1335
- dragging,
1336
- formPopoverKey,
1337
- selectedAnchorKey,
1338
- selectedKeys,
1339
- setActivePopoverKey,
1340
- selectSlots,
1341
- slotsRef,
1342
- finishSelection,
1343
- handleMouseDown,
1344
- handleMouseEnter,
1345
- handleMouseUp,
1346
- handleMouseLeave,
1347
- handlePopoverOpenChange
2211
+ const toggleLocked = () => {
2212
+ const next = !isLockedRef.current;
2213
+ if (next) {
2214
+ stopGlideRef.current();
2215
+ }
2216
+ if (!lockedControlled) {
2217
+ setInnerLocked(next);
2218
+ }
2219
+ if (onLockedChangeRef.current) {
2220
+ onLockedChangeRef.current(next);
2221
+ }
1348
2222
  };
2223
+ const labelTicks = ticks.filter(tick => tick.showLabel);
2224
+ const ariaBounds = {};
2225
+ if (rangeStartMs != null) {
2226
+ ariaBounds['aria-valuemin'] = rangeStartMs;
2227
+ }
2228
+ if (rangeEndMs != null) {
2229
+ ariaBounds['aria-valuemax'] = rangeEndMs - durationMs;
2230
+ }
2231
+ return /*#__PURE__*/jsxs("div", {
2232
+ ref: rootRef,
2233
+ className: classNames(style$2.root, style$2[`root-${_size}`], className, {
2234
+ [style$2['root-dragging']]: dragging,
2235
+ [style$2['root-resizing']]: !!resizeView,
2236
+ [style$2['root-locked']]: isLocked
2237
+ }),
2238
+ style: styleProp,
2239
+ children: [/*#__PURE__*/jsxs("div", {
2240
+ className: style$2.header,
2241
+ children: [/*#__PURE__*/jsx("div", {
2242
+ className: style$2.label,
2243
+ children: label
2244
+ }), /*#__PURE__*/jsxs("div", {
2245
+ className: style$2.actions,
2246
+ children: [_lockable ? /*#__PURE__*/jsx("button", {
2247
+ type: "button",
2248
+ role: "switch",
2249
+ "aria-checked": !isLocked,
2250
+ "aria-label": formatMessage({
2251
+ id: isLocked ? 'TimeWindow.locked' : 'TimeWindow.editable'
2252
+ }),
2253
+ className: classNames(style$2.switch, {
2254
+ [style$2['switch-on']]: !isLocked
2255
+ }),
2256
+ onClick: toggleLocked,
2257
+ children: /*#__PURE__*/jsx("i", {})
2258
+ }) : null, _showDuration ? /*#__PURE__*/jsx("span", {
2259
+ className: style$2.duration,
2260
+ children: durationText
2261
+ }) : null]
2262
+ })]
2263
+ }), isLocked ? null : /*#__PURE__*/jsxs("div", {
2264
+ className: style$2.scale,
2265
+ children: [/*#__PURE__*/jsxs("div", _extends({
2266
+ ref: trackRef,
2267
+ className: classNames(style$2.track, {
2268
+ [style$2['track-wheel']]: _wheelEffect
2269
+ }),
2270
+ role: "slider",
2271
+ tabIndex: 0
2272
+ }, ariaBounds, {
2273
+ "aria-valuenow": baseWindow.start.valueOf(),
2274
+ "aria-valuetext": ariaValueText,
2275
+ onKeyDown: onKeyDown,
2276
+ onPointerDown: onPointerDown,
2277
+ onPointerMove: onPointerMove,
2278
+ onPointerUp: endDrag,
2279
+ onPointerCancel: endDrag,
2280
+ children: [leftShade > 0 ? /*#__PURE__*/jsx("span", {
2281
+ className: classNames(style$2.shade, style$2['shade-start']),
2282
+ style: {
2283
+ width: `${leftShade}%`
2284
+ }
2285
+ }) : null, rightShade > 0 ? /*#__PURE__*/jsx("span", {
2286
+ className: classNames(style$2.shade, style$2['shade-end']),
2287
+ style: {
2288
+ width: `${rightShade}%`
2289
+ }
2290
+ }) : null, /*#__PURE__*/jsx("div", {
2291
+ className: style$2.ticks,
2292
+ children: ticks.map(tick => /*#__PURE__*/jsx("span", {
2293
+ className: classNames(style$2.tick, style$2[`tick-${tick.level}`], {
2294
+ [style$2['tick-out']]: tick.outOfRange
2295
+ }),
2296
+ style: {
2297
+ left: `${tick.ratio * 100}%`
2298
+ }
2299
+ }, tick.key))
2300
+ }), _wheelEffect ? /*#__PURE__*/jsx("span", {
2301
+ className: style$2.cylinder,
2302
+ "aria-hidden": "true"
2303
+ }) : null, /*#__PURE__*/jsx("span", {
2304
+ className: classNames(style$2.window, {
2305
+ [style$2['window-compact']]: windowPx < GRIP_MIN_WIDTH,
2306
+ [style$2['window-resizable']]: _resizable,
2307
+ [style$2['window-fixed']]: !_resizable
2308
+ }),
2309
+ style: {
2310
+ left: `${left}%`,
2311
+ width: `${width}%`
2312
+ },
2313
+ children: _resizable ? /*#__PURE__*/jsxs(Fragment, {
2314
+ children: [/*#__PURE__*/jsx("span", {
2315
+ "data-edge": "start",
2316
+ "aria-hidden": "true",
2317
+ className: classNames(style$2.edge, style$2['edge-start'], {
2318
+ [style$2['edge-active']]: resizeEdge === 'start'
2319
+ })
2320
+ }), /*#__PURE__*/jsx("span", {
2321
+ "data-edge": "end",
2322
+ "aria-hidden": "true",
2323
+ className: classNames(style$2.edge, style$2['edge-end'], {
2324
+ [style$2['edge-active']]: resizeEdge === 'end'
2325
+ })
2326
+ })]
2327
+ }) : null
2328
+ })]
2329
+ })), /*#__PURE__*/jsx("div", {
2330
+ className: style$2.labels,
2331
+ children: labelTicks.map(tick => /*#__PURE__*/jsx("span", {
2332
+ className: classNames(style$2['tick-label'], style$2[`tick-label-${tick.level}`], {
2333
+ [style$2['tick-out']]: tick.outOfRange
2334
+ }),
2335
+ style: {
2336
+ left: `${tick.ratio * 100}%`
2337
+ },
2338
+ children: tick.label
2339
+ }, tick.key))
2340
+ })]
2341
+ })]
2342
+ });
1349
2343
  };
2344
+ const TimeWindow = withLocale(TimeWindowInner);
1350
2345
 
1351
2346
  const DefaultFormInner = () => {
1352
2347
  const {
@@ -1377,186 +2372,620 @@ const DefaultFormInner = () => {
1377
2372
  });
1378
2373
  };
1379
2374
 
1380
- var style$1 = {"slots":"kne-react-calendar_Ftp-V","slots-content":"kne-react-calendar_ArIB7","slots-dragging":"kne-react-calendar_zOHks","slot-item":"kne-react-calendar_fkkFH","slot-dot":"kne-react-calendar_XjojI","slot-time":"kne-react-calendar_343hW","slot-title":"kne-react-calendar_mkuiI","slot-free":"kne-react-calendar_7kzzQ","slot-occupied":"kne-react-calendar_gdNm7","slot-disabled":"kne-react-calendar_2LDxX","slot-selected":"kne-react-calendar_TYAi6","slot-tag":"kne-react-calendar_-xuUf","form-popover":"kne-react-calendar_W-I5J","schedule-popover":"kne-react-calendar_rVLO8","form-popover-title":"kne-react-calendar_mMpFQ","slot-popover-content":"kne-react-calendar_xYptY","slot-popover-title":"kne-react-calendar_oagF-","form-actions":"kne-react-calendar_4NPv0"};
2375
+ var style$1 = {"kne-responsive-boundary":"kne-react-calendar_U1upa","kne-responsive-scroll":"kne-react-calendar_vKocI","slots":"kne-react-calendar_Ftp-V","timeline":"kne-react-calendar_EaUED","timeline-axis":"kne-react-calendar_X2W6x","timeline-axis-label":"kne-react-calendar_NvxDW","timeline-grid":"kne-react-calendar_gTav1","timeline-grid-dragging":"kne-react-calendar_4qf3-","timeline-line":"kne-react-calendar_Tp2ES","timeline-line-hour":"kne-react-calendar_M5-iS","timeline-disabled":"kne-react-calendar_-g-DR","timeline-disabled-reason":"kne-react-calendar_3vFqg","timeline-event":"kne-react-calendar_Wd-9L","timeline-event-title":"kne-react-calendar_WKbMK","timeline-event-time":"kne-react-calendar_K98yc","timeline-event-compact":"kne-react-calendar_WrUNC","timeline-event-disabled":"kne-react-calendar_C4d2-","timeline-ghost":"kne-react-calendar_0T5mO","timeline-now":"kne-react-calendar_AzaVv","timeline-popover-anchor":"kne-react-calendar_WtBzl","timeline-selection":"kne-react-calendar_z-uOR","timeline-selection-dragging":"kne-react-calendar_MbeJu","timeline-selection-label":"kne-react-calendar_7nYLD","timeline-selection-compact":"kne-react-calendar_CpsWx","timeline-selection-badge":"kne-react-calendar_NqiWA","timeline-selection-conflict":"kne-react-calendar_XQQ3E","timeline-selection-edge":"kne-react-calendar_s2EvF","timeline-selection-edge-start":"kne-react-calendar_PcIon","timeline-selection-edge-end":"kne-react-calendar_nS8Mr","form-popover":"kne-react-calendar_W-I5J","schedule-popover":"kne-react-calendar_rVLO8","form-popover-title":"kne-react-calendar_mMpFQ","form-sheet":"kne-react-calendar_n-MRC","form-sheet-content":"kne-react-calendar_ZaEkY","form-actions":"kne-react-calendar_4NPv0","form-conflict":"kne-react-calendar_PpU92","form-time-window":"kne-react-calendar_Wd4qh","event-detail":"kne-react-calendar_Dc-t3","event-detail-clickable":"kne-react-calendar_C3waU","event-detail-title":"kne-react-calendar_FkbSR","event-detail-disabled":"kne-react-calendar_UsltM","event-detail-bar":"kne-react-calendar_ZETY4","event-detail-body":"kne-react-calendar_v9RjG","event-detail-time":"kne-react-calendar_NT-kw","event-detail-text":"kne-react-calendar_vOb62","event-detail-tag":"kne-react-calendar_rs8bh"};
1381
2376
 
1382
- const SlotEvents = ({
1383
- slot,
2377
+ const EventDetail = ({
2378
+ event,
1384
2379
  date,
1385
2380
  onEventClick
1386
- }) => /*#__PURE__*/jsx("div", {
1387
- className: style$1['slot-popover-content'],
1388
- children: /*#__PURE__*/jsx(List, {
1389
- size: "small",
1390
- dataSource: slot.events,
1391
- renderItem: event => /*#__PURE__*/jsx(EventItem, {
1392
- event: event,
1393
- date: date,
1394
- mode: "schedule",
1395
- onEventClick: onEventClick
1396
- })
1397
- })
1398
- });
2381
+ }) => {
2382
+ const clickable = !event.disabled && (typeof event.onClick === 'function' || typeof onEventClick === 'function');
2383
+ const handleClick = () => {
2384
+ if (typeof event.onClick === 'function') {
2385
+ event.onClick(event, {
2386
+ date,
2387
+ mode: 'schedule'
2388
+ });
2389
+ return;
2390
+ }
2391
+ onEventClick == null || onEventClick(event, {
2392
+ date,
2393
+ mode: 'schedule'
2394
+ });
2395
+ };
2396
+ const tagLabel = getEventTagLabel(event);
2397
+ return /*#__PURE__*/jsxs("div", {
2398
+ className: classNames(style$1['event-detail'], {
2399
+ [style$1['event-detail-clickable']]: clickable,
2400
+ [style$1['event-detail-disabled']]: event.disabled
2401
+ }),
2402
+ style: event.color ? {
2403
+ '--timeline-event-color': event.color
2404
+ } : undefined,
2405
+ role: clickable ? 'button' : undefined,
2406
+ tabIndex: clickable ? 0 : undefined,
2407
+ onClick: clickable ? handleClick : undefined,
2408
+ onKeyDown: clickable ? keyEvent => {
2409
+ if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
2410
+ keyEvent.preventDefault();
2411
+ handleClick();
2412
+ }
2413
+ } : undefined,
2414
+ children: [/*#__PURE__*/jsx("span", {
2415
+ className: style$1['event-detail-bar']
2416
+ }), /*#__PURE__*/jsxs("div", {
2417
+ className: style$1['event-detail-body'],
2418
+ children: [/*#__PURE__*/jsx("div", {
2419
+ className: style$1['event-detail-title'],
2420
+ children: event.title
2421
+ }), /*#__PURE__*/jsx("div", {
2422
+ className: style$1['event-detail-time'],
2423
+ children: formatRange(event.start, event.end)
2424
+ }), event.detail ? /*#__PURE__*/jsx("div", {
2425
+ className: style$1['event-detail-text'],
2426
+ children: event.detail
2427
+ }) : null, tagLabel ? /*#__PURE__*/jsx(Tag, {
2428
+ className: style$1['event-detail-tag'],
2429
+ color: event.color,
2430
+ children: tagLabel
2431
+ }) : null]
2432
+ })]
2433
+ });
2434
+ };
1399
2435
 
1400
- const SlotItem = ({
1401
- slot,
2436
+ const DRAG_THRESHOLD_PX = 4;
2437
+ const TOUCH_SLOP_PX = 8;
2438
+ const LONG_PRESS_MS = 350;
2439
+ const Timeline = ({
1402
2440
  date,
1403
- selected,
2441
+ day,
2442
+ hourHeight,
2443
+ timeUnit,
2444
+ timeStep,
2445
+ eventItems,
2446
+ disabledRanges,
2447
+ blockers,
2448
+ conflicts,
2449
+ allowMultiSelect,
2450
+ selection,
2451
+ dragging,
1404
2452
  formPopover,
1405
2453
  popoverOpen,
2454
+ popoverPlacement,
2455
+ popoverAnchor,
1406
2456
  onPopoverOpenChange,
1407
- onContextMenu,
1408
- onMouseDown,
1409
- onPointerDown,
1410
- onMouseEnter,
1411
- onMouseUp,
1412
- onClick,
2457
+ onSelectionDrag,
2458
+ onSelectionCommit,
2459
+ onFreeSlotClick,
1413
2460
  onEventClick,
1414
- renderTimeSlot,
1415
- renderOccupiedSlot
2461
+ renderEventBlock,
2462
+ renderOccupiedSlot,
2463
+ interactionRef,
2464
+ isMobile
1416
2465
  }) => {
1417
- const {
1418
- formatMessage
1419
- } = useIntl();
1420
- const content = renderTimeSlot == null ? void 0 : renderTimeSlot(slot, {
1421
- selected
1422
- });
1423
- if (content) {
1424
- const customNode = /*#__PURE__*/jsx("div", {
1425
- "data-slot-index": slot.index,
1426
- "data-slot-status": slot.status,
1427
- onContextMenu: onContextMenu,
1428
- onMouseDown: onMouseDown,
1429
- onPointerDown: onPointerDown,
1430
- onMouseEnter: onMouseEnter,
1431
- onMouseUp: onMouseUp,
1432
- onClick: onClick,
1433
- children: content
1434
- });
1435
- if (formPopover) {
1436
- return /*#__PURE__*/jsx(Popover, {
1437
- rootClassName: style$1['schedule-popover'],
1438
- open: popoverOpen,
1439
- onOpenChange: onPopoverOpenChange,
1440
- content: formPopover,
1441
- trigger: "click",
1442
- placement: "bottom",
1443
- autoAdjustOverflow: true,
1444
- children: customNode
2466
+ const gridRef = useRef(null);
2467
+ const gestureRef = useRef(null);
2468
+ const suppressClickRef = useRef(false);
2469
+ const touchCommitRef = useRef(0);
2470
+ const [hover, setHover] = useState(null);
2471
+ const [activeEventKey, setActiveEventKey] = useState(null);
2472
+ const pxPerMs = hourHeight / (60 * MINUTE_MS$1);
2473
+ const height = (day.endMs - day.startMs) * pxPerMs;
2474
+ const unitMs = timeUnit * MINUTE_MS$1;
2475
+ const stepMs = timeStep * MINUTE_MS$1;
2476
+ const latest = useRef({});
2477
+ latest.current = {
2478
+ day,
2479
+ blockers,
2480
+ unitMs,
2481
+ stepMs,
2482
+ allowMultiSelect,
2483
+ selection,
2484
+ onSelectionDrag,
2485
+ onSelectionCommit,
2486
+ onFreeSlotClick,
2487
+ pxPerMs
2488
+ };
2489
+ const toY = ms => (ms - day.startMs) * pxPerMs;
2490
+ const lines = useMemo(() => {
2491
+ const list = [];
2492
+ for (let ms = day.startMs; ms <= day.endMs; ms += unitMs) {
2493
+ list.push({
2494
+ ms,
2495
+ hour: dayjs(ms).minute() === 0
1445
2496
  });
1446
2497
  }
1447
- return customNode;
1448
- }
1449
- const wrapWithPopover = node => {
1450
- if (formPopover) {
1451
- return /*#__PURE__*/jsx(Popover, {
1452
- rootClassName: style$1['schedule-popover'],
1453
- open: popoverOpen,
1454
- onOpenChange: onPopoverOpenChange,
1455
- content: formPopover,
1456
- trigger: "click",
1457
- placement: "bottom",
1458
- autoAdjustOverflow: true,
1459
- children: node
2498
+ return list;
2499
+ }, [day.endMs, day.startMs, unitMs]);
2500
+ const labels = useMemo(() => {
2501
+ const list = [];
2502
+ let cursor = dayjs(day.startMs);
2503
+ if (cursor.minute() !== 0) {
2504
+ list.push(day.startMs);
2505
+ cursor = cursor.add(1, 'hour').startOf('hour');
2506
+ }
2507
+ for (; cursor.valueOf() <= day.endMs; cursor = cursor.add(1, 'hour')) {
2508
+ list.push(cursor.valueOf());
2509
+ }
2510
+ if (list[list.length - 1] !== day.endMs) {
2511
+ list.push(day.endMs);
2512
+ }
2513
+ return list;
2514
+ }, [day.endMs, day.startMs]);
2515
+ const [now, setNow] = useState(() => Date.now());
2516
+ useEffect(() => {
2517
+ const timer = setInterval(() => setNow(Date.now()), 60 * 1000);
2518
+ return () => clearInterval(timer);
2519
+ }, []);
2520
+ const showNow = now >= day.startMs && now <= day.endMs;
2521
+ const msAtClientY = clientY => {
2522
+ const rect = gridRef.current.getBoundingClientRect();
2523
+ const {
2524
+ day: currentDay,
2525
+ pxPerMs: currentPx
2526
+ } = latest.current;
2527
+ return Math.min(Math.max(currentDay.startMs + (clientY - rect.top) / currentPx, currentDay.startMs), currentDay.endMs - 1);
2528
+ };
2529
+ const finishGesture = () => {
2530
+ const gesture = gestureRef.current;
2531
+ if (!gesture) {
2532
+ return;
2533
+ }
2534
+ clearTimeout(gesture.timer);
2535
+ window.removeEventListener('pointermove', gesture.onMove);
2536
+ window.removeEventListener('pointerup', gesture.onUp);
2537
+ window.removeEventListener('pointercancel', gesture.onCancel);
2538
+ gestureRef.current = null;
2539
+ };
2540
+ useEffect(() => finishGesture, []);
2541
+ useEffect(() => {
2542
+ const grid = gridRef.current;
2543
+ if (!grid) {
2544
+ return undefined;
2545
+ }
2546
+ const preventScroll = event => {
2547
+ var _gestureRef$current;
2548
+ if ((_gestureRef$current = gestureRef.current) != null && _gestureRef$current.active) {
2549
+ event.preventDefault();
2550
+ }
2551
+ };
2552
+ const preventGhostClick = event => {
2553
+ if (Date.now() - touchCommitRef.current < 300) {
2554
+ event.preventDefault();
2555
+ }
2556
+ };
2557
+ grid.addEventListener('touchmove', preventScroll, {
2558
+ passive: false
2559
+ });
2560
+ grid.addEventListener('touchend', preventGhostClick, {
2561
+ passive: false
2562
+ });
2563
+ return () => {
2564
+ grid.removeEventListener('touchmove', preventScroll);
2565
+ grid.removeEventListener('touchend', preventGhostClick);
2566
+ };
2567
+ }, []);
2568
+ const handlePointerDown = event => {
2569
+ var _gridRef$current;
2570
+ if (!((_gridRef$current = gridRef.current) != null && _gridRef$current.contains(event.target))) {
2571
+ return;
2572
+ }
2573
+ if (interactionRef) {
2574
+ interactionRef.current = Date.now();
2575
+ }
2576
+ if (event.pointerType === 'mouse' && event.button !== 0) {
2577
+ return;
2578
+ }
2579
+ const {
2580
+ day: currentDay,
2581
+ blockers: currentBlockers,
2582
+ selection: currentSelection,
2583
+ allowMultiSelect: multi
2584
+ } = latest.current;
2585
+ const edgeNode = event.target.closest('[data-selection-edge]');
2586
+ const onSelection = event.target.closest('[data-timeline-selection]');
2587
+ const eventNode = event.target.closest('[data-timeline-event]');
2588
+ const pointerMs = msAtClientY(event.clientY);
2589
+ let gesture;
2590
+ if (edgeNode && currentSelection && multi) {
2591
+ gesture = {
2592
+ type: 'resize',
2593
+ edge: edgeNode.dataset.selectionEdge,
2594
+ active: true
2595
+ };
2596
+ } else if (onSelection && currentSelection) {
2597
+ gesture = {
2598
+ type: 'move',
2599
+ active: true
2600
+ };
2601
+ } else {
2602
+ const gap = getFreeGap(pointerMs, currentBlockers, currentDay);
2603
+ if (!gap) {
2604
+ return;
2605
+ }
2606
+ gesture = {
2607
+ type: 'create',
2608
+ gap,
2609
+ active: event.pointerType === 'mouse',
2610
+ onEvent: !!eventNode
2611
+ };
2612
+ }
2613
+ if (gesture.type !== 'create') {
2614
+ const ownGap = getFreeGap(currentSelection.startMs, currentBlockers, currentDay) || currentDay;
2615
+ gesture.gap = ownGap;
2616
+ gesture.range0 = currentSelection;
2617
+ event.preventDefault();
2618
+ }
2619
+ Object.assign(gesture, {
2620
+ pointerId: event.pointerId,
2621
+ pointerType: event.pointerType,
2622
+ startX: event.clientX,
2623
+ startY: event.clientY,
2624
+ startMs: pointerMs,
2625
+ moved: false
2626
+ });
2627
+ setHover(null);
2628
+ gesture.onMove = moveEvent => {
2629
+ if (moveEvent.pointerId !== gesture.pointerId) {
2630
+ return;
2631
+ }
2632
+ const dx = moveEvent.clientX - gesture.startX;
2633
+ const dy = moveEvent.clientY - gesture.startY;
2634
+ if (!gesture.active) {
2635
+ if (Math.hypot(dx, dy) > TOUCH_SLOP_PX) {
2636
+ finishGesture();
2637
+ }
2638
+ return;
2639
+ }
2640
+ if (!gesture.moved && Math.abs(dy) < DRAG_THRESHOLD_PX) {
2641
+ return;
2642
+ }
2643
+ const {
2644
+ stepMs: step,
2645
+ day: d,
2646
+ allowMultiSelect: canResize
2647
+ } = latest.current;
2648
+ const ms = msAtClientY(moveEvent.clientY);
2649
+ let next;
2650
+ if (gesture.type === 'create') {
2651
+ if (!canResize) {
2652
+ return;
2653
+ }
2654
+ next = createSelectionRange({
2655
+ anchorMs: gesture.startMs,
2656
+ pointerMs: ms,
2657
+ gap: gesture.gap,
2658
+ originMs: d.startMs,
2659
+ stepMs: step
2660
+ });
2661
+ } else if (gesture.type === 'move') {
2662
+ next = moveSelectionRange({
2663
+ range: gesture.range0,
2664
+ deltaMs: ms - gesture.startMs,
2665
+ gap: gesture.gap,
2666
+ originMs: d.startMs,
2667
+ stepMs: step
2668
+ });
2669
+ } else {
2670
+ next = resizeSelectionRange({
2671
+ range: gesture.range0,
2672
+ edge: gesture.edge,
2673
+ pointerMs: ms,
2674
+ gap: gesture.gap,
2675
+ originMs: d.startMs,
2676
+ stepMs: step
2677
+ });
2678
+ }
2679
+ gesture.moved = true;
2680
+ gesture.range = next;
2681
+ latest.current.onSelectionDrag(next);
2682
+ };
2683
+ gesture.onUp = upEvent => {
2684
+ if (upEvent.pointerId !== gesture.pointerId) {
2685
+ return;
2686
+ }
2687
+ finishGesture();
2688
+ if (gesture.pointerType !== 'mouse') {
2689
+ touchCommitRef.current = Date.now();
2690
+ }
2691
+ const {
2692
+ unitMs: unit,
2693
+ day: d,
2694
+ onSelectionCommit: commit,
2695
+ onFreeSlotClick: onFreeClick
2696
+ } = latest.current;
2697
+ if (gesture.moved) {
2698
+ suppressClickRef.current = true;
2699
+ setTimeout(() => {
2700
+ suppressClickRef.current = false;
2701
+ }, 0);
2702
+ commit(gesture.range);
2703
+ return;
2704
+ }
2705
+ if (gesture.type !== 'create') {
2706
+ commit(gesture.range0);
2707
+ return;
2708
+ }
2709
+ if (gesture.onEvent) {
2710
+ return;
2711
+ }
2712
+ const range = clickSelectionRange({
2713
+ pointerMs: gesture.startMs,
2714
+ gap: gesture.gap,
2715
+ originMs: d.startMs,
2716
+ unitMs: unit
2717
+ });
2718
+ const slotStart = snapToStep(gesture.startMs, {
2719
+ originMs: d.startMs,
2720
+ stepMs: unit,
2721
+ mode: 'floor'
2722
+ });
2723
+ onFreeClick == null || onFreeClick({
2724
+ start: new Date(slotStart),
2725
+ end: new Date(Math.min(slotStart + unit, d.endMs))
1460
2726
  });
2727
+ commit(range);
2728
+ };
2729
+ gesture.onCancel = cancelEvent => {
2730
+ if (cancelEvent.pointerId !== gesture.pointerId) {
2731
+ return;
2732
+ }
2733
+ const wasMoved = gesture.moved;
2734
+ finishGesture();
2735
+ if (wasMoved) {
2736
+ latest.current.onSelectionCommit(gesture.range);
2737
+ }
2738
+ };
2739
+ if (!gesture.active) {
2740
+ gesture.timer = setTimeout(() => {
2741
+ gesture.active = true;
2742
+ navigator.vibrate == null || navigator.vibrate(10);
2743
+ const {
2744
+ day: d,
2745
+ unitMs: unit
2746
+ } = latest.current;
2747
+ const range = clickSelectionRange({
2748
+ pointerMs: gesture.startMs,
2749
+ gap: gesture.gap,
2750
+ originMs: d.startMs,
2751
+ unitMs: unit
2752
+ });
2753
+ gesture.moved = true;
2754
+ gesture.range = range;
2755
+ gesture.startMs = range.startMs;
2756
+ latest.current.onSelectionDrag(range);
2757
+ }, LONG_PRESS_MS);
1461
2758
  }
1462
- if (slot.status === 'occupied') {
1463
- return /*#__PURE__*/jsx(Popover, {
1464
- rootClassName: style$1['schedule-popover'],
1465
- open: popoverOpen,
1466
- onOpenChange: onPopoverOpenChange,
1467
- content: /*#__PURE__*/jsx(SlotEvents, {
1468
- slot: slot,
1469
- date: date,
1470
- onEventClick: onEventClick
1471
- }),
1472
- trigger: "click",
1473
- placement: "bottom",
1474
- autoAdjustOverflow: true,
1475
- children: node
2759
+ finishGesture();
2760
+ gestureRef.current = gesture;
2761
+ window.addEventListener('pointermove', gesture.onMove);
2762
+ window.addEventListener('pointerup', gesture.onUp);
2763
+ window.addEventListener('pointercancel', gesture.onCancel);
2764
+ };
2765
+ const handleHover = event => {
2766
+ var _gridRef$current2;
2767
+ if (event.pointerType !== 'mouse' || gestureRef.current || !((_gridRef$current2 = gridRef.current) != null && _gridRef$current2.contains(event.target))) {
2768
+ return;
2769
+ }
2770
+ if (event.target.closest('[data-timeline-selection]')) {
2771
+ setHover(null);
2772
+ return;
2773
+ }
2774
+ const ms = msAtClientY(event.clientY);
2775
+ const gap = getFreeGap(ms, blockers, day);
2776
+ if (!gap) {
2777
+ setHover(null);
2778
+ return;
2779
+ }
2780
+ const range = clickSelectionRange({
2781
+ pointerMs: ms,
2782
+ gap,
2783
+ originMs: day.startMs,
2784
+ unitMs
2785
+ });
2786
+ setHover(current => current && current.startMs === range.startMs && current.endMs === range.endMs ? current : range);
2787
+ };
2788
+ const renderEventContent = item => {
2789
+ const {
2790
+ event
2791
+ } = item;
2792
+ if (renderEventBlock) {
2793
+ return renderEventBlock(event, {
2794
+ start: new Date(item.startMs),
2795
+ end: new Date(item.endMs)
1476
2796
  });
1477
2797
  }
1478
- if (slot.disabledReason) {
1479
- return /*#__PURE__*/jsx(Tooltip, {
1480
- title: slot.disabledReason,
1481
- children: node
2798
+ if (renderOccupiedSlot) {
2799
+ return renderOccupiedSlot({
2800
+ start: new Date(item.startMs),
2801
+ end: new Date(item.endMs),
2802
+ status: 'occupied',
2803
+ events: [event]
2804
+ }, {
2805
+ events: [event]
1482
2806
  });
1483
2807
  }
1484
- return node;
2808
+ return /*#__PURE__*/jsxs(Fragment, {
2809
+ children: [/*#__PURE__*/jsx("div", {
2810
+ className: style$1['timeline-event-title'],
2811
+ children: event.title
2812
+ }), /*#__PURE__*/jsx("div", {
2813
+ className: style$1['timeline-event-time'],
2814
+ children: formatRange(event.start, event.end)
2815
+ })]
2816
+ });
1485
2817
  };
1486
- const slotClassName = classNames(style$1['slot-item'], style$1[`slot-${slot.status}`], {
1487
- [style$1['slot-selected']]: selected
1488
- });
1489
- const slotChildren = /*#__PURE__*/jsxs(Fragment, {
1490
- children: [/*#__PURE__*/jsx("span", {
1491
- className: style$1['slot-dot']
1492
- }), /*#__PURE__*/jsx("span", {
1493
- className: style$1['slot-time'],
1494
- children: formatRange(slot.start, slot.end)
1495
- }), slot.status === 'disabled' ? /*#__PURE__*/jsx(Tag, {
1496
- className: style$1['slot-tag'],
1497
- children: formatMessage({
1498
- id: 'SlotItem.disabled'
1499
- })
1500
- }) : null, slot.status === 'occupied' ? /*#__PURE__*/jsx(Tag, {
1501
- className: style$1['slot-tag'],
1502
- color: "red",
1503
- children: formatMessage({
1504
- id: 'SlotItem.occupied'
1505
- })
1506
- }) : null, slot.status === 'occupied' && renderOccupiedSlot ? renderOccupiedSlot(slot, {
1507
- events: slot.events
1508
- }) : null, slot.status === 'occupied' && !renderOccupiedSlot ? /*#__PURE__*/jsx("span", {
1509
- className: style$1['slot-title'],
1510
- children: slot.events.map(event => event.title).join('、')
2818
+ const selectionBlock = selection ? /*#__PURE__*/jsxs("div", {
2819
+ "data-timeline-selection": "",
2820
+ className: classNames(style$1['timeline-selection'], {
2821
+ [style$1['timeline-selection-dragging']]: dragging,
2822
+ [style$1['timeline-selection-conflict']]: conflicts.length > 0,
2823
+ [style$1['timeline-selection-compact']]: (selection.endMs - selection.startMs) * pxPerMs < 34
2824
+ }),
2825
+ style: {
2826
+ top: toY(selection.startMs),
2827
+ height: Math.max((selection.endMs - selection.startMs) * pxPerMs, 6)
2828
+ },
2829
+ children: [allowMultiSelect ? /*#__PURE__*/jsx("span", {
2830
+ "data-selection-edge": "start",
2831
+ className: classNames(style$1['timeline-selection-edge'], style$1['timeline-selection-edge-start'])
2832
+ }) : null, /*#__PURE__*/jsxs("div", {
2833
+ className: style$1['timeline-selection-label'],
2834
+ children: [/*#__PURE__*/jsx("span", {
2835
+ children: formatRange(selection.startMs, selection.endMs)
2836
+ }), conflicts.length ? /*#__PURE__*/jsx("span", {
2837
+ className: style$1['timeline-selection-badge'],
2838
+ children: conflicts.length
2839
+ }) : null]
2840
+ }), allowMultiSelect ? /*#__PURE__*/jsx("span", {
2841
+ "data-selection-edge": "end",
2842
+ className: classNames(style$1['timeline-selection-edge'], style$1['timeline-selection-edge-end'])
1511
2843
  }) : null]
2844
+ }) : null;
2845
+ return /*#__PURE__*/jsxs("div", {
2846
+ className: style$1['timeline'],
2847
+ style: {
2848
+ height
2849
+ },
2850
+ children: [/*#__PURE__*/jsx("div", {
2851
+ className: style$1['timeline-axis'],
2852
+ "aria-hidden": true,
2853
+ children: labels.map(ms => /*#__PURE__*/jsx("span", {
2854
+ className: style$1['timeline-axis-label'],
2855
+ style: {
2856
+ top: toY(ms)
2857
+ },
2858
+ children: dayjs(ms).format(TIME_FORMAT)
2859
+ }, ms))
2860
+ }), /*#__PURE__*/jsxs("div", {
2861
+ ref: gridRef,
2862
+ className: classNames(style$1['timeline-grid'], {
2863
+ [style$1['timeline-grid-dragging']]: dragging
2864
+ }),
2865
+ onPointerDown: handlePointerDown,
2866
+ onPointerMove: handleHover,
2867
+ onPointerLeave: () => setHover(null),
2868
+ onContextMenu: event => {
2869
+ var _gridRef$current3;
2870
+ if ((_gridRef$current3 = gridRef.current) != null && _gridRef$current3.contains(event.target)) {
2871
+ event.preventDefault();
2872
+ }
2873
+ },
2874
+ onClickCapture: event => {
2875
+ var _gridRef$current4;
2876
+ if (suppressClickRef.current && (_gridRef$current4 = gridRef.current) != null && _gridRef$current4.contains(event.target)) {
2877
+ suppressClickRef.current = false;
2878
+ event.stopPropagation();
2879
+ }
2880
+ },
2881
+ children: [lines.map(line => /*#__PURE__*/jsx("span", {
2882
+ className: classNames(style$1['timeline-line'], {
2883
+ [style$1['timeline-line-hour']]: line.hour
2884
+ }),
2885
+ style: {
2886
+ top: toY(line.ms)
2887
+ }
2888
+ }, line.ms)), disabledRanges.map(range => {
2889
+ const block = /*#__PURE__*/jsx("div", {
2890
+ className: style$1['timeline-disabled'],
2891
+ style: {
2892
+ top: toY(range.startMs),
2893
+ height: (range.endMs - range.startMs) * pxPerMs
2894
+ },
2895
+ children: range.reason && (range.endMs - range.startMs) * pxPerMs >= 22 ? /*#__PURE__*/jsx("span", {
2896
+ className: style$1['timeline-disabled-reason'],
2897
+ children: range.reason
2898
+ }) : null
2899
+ }, range.startMs);
2900
+ return range.reason ? /*#__PURE__*/jsx(Tooltip, {
2901
+ title: range.reason,
2902
+ children: block
2903
+ }, range.startMs) : block;
2904
+ }), eventItems.map(item => {
2905
+ const key = `${item.event.id}-${item.startMs}`;
2906
+ const blockHeight = (item.endMs - item.startMs) * pxPerMs;
2907
+ return /*#__PURE__*/jsx(Popover, {
2908
+ arrow: false,
2909
+ trigger: "click",
2910
+ placement: isMobile ? 'bottom' : 'left',
2911
+ rootClassName: style$1['schedule-popover'],
2912
+ open: activeEventKey === key,
2913
+ onOpenChange: open => setActiveEventKey(open ? key : null),
2914
+ content: /*#__PURE__*/jsx(EventDetail, {
2915
+ event: item.event,
2916
+ date: date,
2917
+ onEventClick: onEventClick
2918
+ }),
2919
+ children: /*#__PURE__*/jsx("div", {
2920
+ "data-timeline-event": "",
2921
+ className: classNames(style$1['timeline-event'], {
2922
+ [style$1['timeline-event-disabled']]: item.event.disabled,
2923
+ [style$1['timeline-event-compact']]: blockHeight < 34
2924
+ }),
2925
+ style: _extends({
2926
+ top: toY(item.startMs),
2927
+ height: Math.max(blockHeight - 2, 4),
2928
+ left: `calc(${item.column / item.columns * 100}% + 2px)`,
2929
+ width: `calc(${100 / item.columns}% - 4px)`
2930
+ }, item.event.color ? {
2931
+ '--timeline-event-color': item.event.color
2932
+ } : null),
2933
+ children: renderEventContent(item)
2934
+ })
2935
+ }, key);
2936
+ }), hover && !selection ? /*#__PURE__*/jsxs("div", {
2937
+ className: style$1['timeline-ghost'],
2938
+ style: {
2939
+ top: toY(hover.startMs),
2940
+ height: (hover.endMs - hover.startMs) * pxPerMs
2941
+ },
2942
+ children: ["+ ", dayjs(hover.startMs).format(TIME_FORMAT)]
2943
+ }) : null, showNow ? /*#__PURE__*/jsx("span", {
2944
+ className: style$1['timeline-now'],
2945
+ style: {
2946
+ top: toY(now)
2947
+ }
2948
+ }) : null, selectionBlock, formPopover && popoverAnchor ? /*#__PURE__*/jsx(Popover, {
2949
+ arrow: false,
2950
+ trigger: "click",
2951
+ placement: popoverPlacement,
2952
+ rootClassName: style$1['schedule-popover'],
2953
+ open: popoverOpen && !dragging,
2954
+ onOpenChange: onPopoverOpenChange,
2955
+ content: formPopover,
2956
+ destroyOnHidden: true,
2957
+ children: /*#__PURE__*/jsx("span", {
2958
+ "aria-hidden": true,
2959
+ className: style$1['timeline-popover-anchor'],
2960
+ style: {
2961
+ top: toY(popoverAnchor.startMs),
2962
+ height: (popoverAnchor.endMs - popoverAnchor.startMs) * pxPerMs
2963
+ }
2964
+ })
2965
+ }) : null]
2966
+ })]
1512
2967
  });
1513
- const slotHandlers = {
1514
- onContextMenu,
1515
- onMouseDown,
1516
- onPointerDown,
1517
- onMouseEnter,
1518
- onMouseUp,
1519
- onClick
1520
- };
1521
- const inner = slot.status === 'disabled' ? /*#__PURE__*/jsx("button", {
1522
- type: "button",
1523
- className: slotClassName,
1524
- "data-slot-index": slot.index,
1525
- "data-slot-status": slot.status,
1526
- disabled: true,
1527
- onContextMenu: onContextMenu,
1528
- onMouseDown: onMouseDown,
1529
- onPointerDown: onPointerDown,
1530
- onMouseEnter: onMouseEnter,
1531
- onMouseUp: onMouseUp,
1532
- onClick: onClick,
1533
- children: slotChildren
1534
- }) : /*#__PURE__*/jsx("div", _extends({
1535
- role: "button",
1536
- tabIndex: 0,
1537
- className: slotClassName,
1538
- "data-slot-index": slot.index,
1539
- "data-slot-status": slot.status
1540
- }, slotHandlers, {
1541
- children: slotChildren
1542
- }));
1543
- return wrapWithPopover(inner);
1544
2968
  };
1545
2969
 
1546
2970
  const SchedulePanel = ({
1547
2971
  date,
1548
2972
  slots,
2973
+ events: dayEvents,
2974
+ dayStart,
2975
+ dayEnd,
2976
+ timeUnit,
2977
+ timeStep,
2978
+ hourHeight,
2979
+ allowConflict,
1549
2980
  loading,
1550
2981
  allowMultiSelect,
1551
- selectedSlots,
1552
- setSelectedSlots,
1553
2982
  onSlotRangeChange,
1554
2983
  onFreeSlotClick,
1555
2984
  onCreate,
1556
2985
  formInner,
1557
2986
  formProps,
1558
2987
  defaultFormValues,
1559
- renderTimeSlot,
2988
+ renderEventBlock,
1560
2989
  renderOccupiedSlot,
1561
2990
  renderPanelHeader,
1562
2991
  onEventClick
@@ -1564,56 +2993,129 @@ const SchedulePanel = ({
1564
2993
  const {
1565
2994
  formatMessage
1566
2995
  } = useIntl();
1567
- const events = useMemo(() => sortEventsByStart(dedupeEventsById(slots.flatMap(slot => slot.events || []))), [slots]);
2996
+ const isMobile = useIsMobile();
2997
+ const scrollRef = useRef(null);
2998
+ const interactionRef = useRef(0);
2999
+ const openedAtRef = useRef(0);
3000
+ const [selection, setSelection] = useState(null);
3001
+ const [dragging, setDragging] = useState(false);
3002
+ const [popoverOpen, setPopoverOpen] = useState(false);
3003
+ const [popoverPlacement, setPopoverPlacement] = useState('left');
3004
+ const [popoverAnchor, setPopoverAnchor] = useState(null);
3005
+ const day = useMemo(() => ({
3006
+ startMs: parseDayTime(date, dayStart).valueOf(),
3007
+ endMs: parseDayTime(date, dayEnd).valueOf()
3008
+ }), [date, dayEnd, dayStart]);
3009
+ const events = useMemo(() => sortEventsByStart(dayEvents || []), [dayEvents]);
3010
+ const eventItems = useMemo(() => layoutTimelineEvents(events, day), [day, events]);
3011
+ const disabledRanges = useMemo(() => mergeDisabledSlots(slots), [slots]);
3012
+ const blockers = useMemo(() => toBlockers(allowConflict ? disabledRanges : [...disabledRanges, ...eventItems]), [allowConflict, disabledRanges, eventItems]);
3013
+ const conflicts = useMemo(() => findOverlappingEvents(selection, eventItems.map(item => item.event)), [eventItems, selection]);
3014
+ const gap = selection && getFreeGap(selection.startMs, blockers, day) || day;
3015
+ const windowRange = useMemo(() => [new Date(gap.startMs), new Date(gap.endMs)], [gap.startMs, gap.endMs]);
1568
3016
  const panelHeader = renderPanelHeader == null ? void 0 : renderPanelHeader({
1569
3017
  date: date.toDate(),
1570
3018
  mode: 'schedule',
1571
3019
  events
1572
3020
  });
1573
- const {
1574
- activePopoverKey,
1575
- dragging,
1576
- formPopoverKey,
1577
- selectedAnchorKey,
1578
- selectedKeys,
1579
- selectSlots,
1580
- slotsRef,
1581
- handleMouseDown,
1582
- handleMouseEnter,
1583
- handleMouseUp,
1584
- handleMouseLeave,
1585
- handlePopoverOpenChange
1586
- } = useScheduleSelectionGesture({
1587
- slots,
1588
- selectedSlots,
1589
- setSelectedSlots,
1590
- allowMultiSelect,
1591
- onSlotRangeChange,
1592
- onFreeSlotClick
1593
- });
1594
3021
  const {
1595
3022
  scrolled,
1596
3023
  onScroll
1597
- } = usePanelScrollShadow(date.format(DATE_FORMAT), slotsRef);
1598
- const formContext = {
3024
+ } = usePanelScrollShadow(date.format(DATE_FORMAT), scrollRef);
3025
+ const getFormContext = range => ({
1599
3026
  date: date.toDate(),
1600
- slots: selectedSlots
3027
+ start: range ? new Date(range.startMs) : null,
3028
+ end: range ? new Date(range.endMs) : null,
3029
+ slots: findOverlappingSlots(range, slots),
3030
+ conflicts: findOverlappingEvents(range, eventItems.map(item => item.event))
3031
+ });
3032
+ const emitRange = range => {
3033
+ if (range) {
3034
+ onSlotRangeChange == null || onSlotRangeChange(findOverlappingSlots(range, slots), {
3035
+ action: 'select',
3036
+ start: new Date(range.startMs),
3037
+ end: new Date(range.endMs)
3038
+ });
3039
+ } else {
3040
+ onSlotRangeChange == null || onSlotRangeChange([], {
3041
+ action: 'clear'
3042
+ });
3043
+ }
3044
+ };
3045
+ const clearSelection = () => {
3046
+ setPopoverOpen(false);
3047
+ setPopoverAnchor(null);
3048
+ setDragging(false);
3049
+ if (selection) {
3050
+ setSelection(null);
3051
+ emitRange(null);
3052
+ }
1601
3053
  };
1602
- const formPopover = selectedSlots.length && !dragging ? /*#__PURE__*/jsxs("div", {
1603
- className: style$1['form-popover'],
3054
+ const commitSelection = range => {
3055
+ setDragging(false);
3056
+ if (!range) {
3057
+ return;
3058
+ }
3059
+ if (!popoverOpen) {
3060
+ var _scrollRef$current;
3061
+ const stacked = !!((_scrollRef$current = scrollRef.current) != null && _scrollRef$current.closest('[data-schedule-layout="stacked"]'));
3062
+ setPopoverPlacement(stacked ? 'bottom' : 'left');
3063
+ setPopoverAnchor(range);
3064
+ }
3065
+ setSelection(range);
3066
+ openedAtRef.current = Date.now();
3067
+ setPopoverOpen(true);
3068
+ emitRange(range);
3069
+ };
3070
+ const formContext = getFormContext(selection);
3071
+ const formPopover = selection ? /*#__PURE__*/jsxs("div", {
3072
+ className: classNames(style$1['form-popover'], {
3073
+ [style$1['form-sheet-content']]: isMobile
3074
+ }),
1604
3075
  children: [/*#__PURE__*/jsx("div", {
1605
3076
  className: style$1['form-popover-title'],
1606
3077
  children: formatMessage({
1607
- id: 'SchedulePanel.newSchedule'
1608
- }, {
1609
- range: formatRange(selectedSlots[0].start, selectedSlots[selectedSlots.length - 1].end)
3078
+ id: 'SchedulePanel.newScheduleTitle'
1610
3079
  })
1611
- }), /*#__PURE__*/jsxs(Form, _extends({}, formProps, {
3080
+ }), /*#__PURE__*/jsx(TimeWindow, {
3081
+ className: style$1['form-time-window'],
3082
+ size: isMobile ? 'default' : 'small',
3083
+ value: [new Date(selection.startMs), new Date(selection.endMs)],
3084
+ range: windowRange,
3085
+ step: timeStep,
3086
+ minDuration: timeStep,
3087
+ resizable: allowMultiSelect,
3088
+ debounceWait: 120,
3089
+ format: "HH:mm",
3090
+ separator: " - ",
3091
+ onChange: value => {
3092
+ if (!value) {
3093
+ return;
3094
+ }
3095
+ const range = {
3096
+ startMs: value[0].getTime(),
3097
+ endMs: value[1].getTime()
3098
+ };
3099
+ setSelection(range);
3100
+ emitRange(range);
3101
+ }
3102
+ }), conflicts.length ? /*#__PURE__*/jsx(Alert, {
3103
+ className: style$1['form-conflict'],
3104
+ type: "warning",
3105
+ showIcon: true,
3106
+ title: `${formatMessage({
3107
+ id: 'SchedulePanel.conflict'
3108
+ }, {
3109
+ count: conflicts.length
3110
+ })}:${conflicts.map(event => event.title).join('、')}`
3111
+ }) : null, /*#__PURE__*/jsxs(Form, _extends({}, formProps, isMobile ? {
3112
+ size: 'large'
3113
+ } : null, {
1612
3114
  data: getDefaultFormValues(defaultFormValues, formContext),
1613
3115
  onSubmit: async values => {
1614
3116
  try {
1615
- await (onCreate == null ? void 0 : onCreate(values, formContext));
1616
- selectSlots([]);
3117
+ await (onCreate == null ? void 0 : onCreate(values, getFormContext(selection)));
3118
+ clearSelection();
1617
3119
  } catch (_unused) {
1618
3120
  // Keep the form open when creation fails.
1619
3121
  }
@@ -1621,7 +3123,7 @@ const SchedulePanel = ({
1621
3123
  children: [formInner || /*#__PURE__*/jsx(DefaultFormInner, {}), /*#__PURE__*/jsxs("div", {
1622
3124
  className: style$1['form-actions'],
1623
3125
  children: [/*#__PURE__*/jsx(Button, {
1624
- onClick: () => selectSlots([]),
3126
+ onClick: clearSelection,
1625
3127
  children: formatMessage({
1626
3128
  id: 'SchedulePanel.cancel'
1627
3129
  })
@@ -1633,7 +3135,7 @@ const SchedulePanel = ({
1633
3135
  })]
1634
3136
  }))]
1635
3137
  }) : null;
1636
- return /*#__PURE__*/jsx(Card, {
3138
+ return /*#__PURE__*/jsxs(Card, {
1637
3139
  bordered: false,
1638
3140
  title: panelHeader || /*#__PURE__*/jsx(PanelTitle, {
1639
3141
  date: date,
@@ -1642,60 +3144,82 @@ const SchedulePanel = ({
1642
3144
  })
1643
3145
  }),
1644
3146
  className: panelStyle['panel-card'],
1645
- children: /*#__PURE__*/jsx(Spin, {
3147
+ children: [/*#__PURE__*/jsx(Spin, {
1646
3148
  spinning: !!loading,
1647
3149
  classNames: {
1648
3150
  root: panelStyle['panel-spin'],
1649
3151
  container: panelStyle['panel-spin-container']
1650
3152
  },
1651
3153
  children: /*#__PURE__*/jsxs("div", {
1652
- ref: slotsRef,
1653
- className: classNames(style$1['slots'], {
1654
- [style$1['slots-dragging']]: dragging
1655
- }),
3154
+ ref: scrollRef,
3155
+ className: style$1['slots'],
1656
3156
  onScroll: onScroll,
1657
- onMouseUp: handleMouseUp,
1658
- onMouseLeave: handleMouseLeave,
1659
3157
  children: [/*#__PURE__*/jsx(PanelScrollShadow, {
1660
3158
  visible: scrolled
1661
- }), /*#__PURE__*/jsx("div", {
1662
- className: style$1['slots-content'],
1663
- children: slots.map((slot, index) => {
1664
- const slotWithIndex = _extends({}, slot, {
1665
- index
1666
- });
1667
- const occupiedPopoverKey = `occupied:${slot.key}`;
1668
- const currentPopoverKey = slot.key === selectedAnchorKey && formPopover ? formPopoverKey : slot.status === 'occupied' ? occupiedPopoverKey : null;
1669
- return /*#__PURE__*/jsx(SlotItem, {
1670
- slot: slotWithIndex,
1671
- date: date.toDate(),
1672
- selected: selectedKeys.has(slot.key),
1673
- formPopover: slot.key === selectedAnchorKey ? formPopover : null,
1674
- popoverOpen: !!currentPopoverKey && activePopoverKey === currentPopoverKey,
1675
- onPopoverOpenChange: open => handlePopoverOpenChange(open, currentPopoverKey, {
1676
- isForm: currentPopoverKey === formPopoverKey
1677
- }),
1678
- renderTimeSlot: renderTimeSlot,
1679
- renderOccupiedSlot: renderOccupiedSlot,
1680
- onEventClick: onEventClick,
1681
- onContextMenu: event => {
1682
- if (slot.status === 'free') {
1683
- event.preventDefault();
1684
- }
1685
- },
1686
- onMouseDown: event => handleMouseDown(event, slot, index),
1687
- onMouseEnter: () => handleMouseEnter(index),
1688
- onMouseUp: handleMouseUp,
1689
- onClick: undefined
1690
- }, slot.key);
1691
- })
3159
+ }), /*#__PURE__*/jsx(Timeline, {
3160
+ date: date.toDate(),
3161
+ day: day,
3162
+ hourHeight: hourHeight,
3163
+ timeUnit: timeUnit,
3164
+ timeStep: timeStep,
3165
+ eventItems: eventItems,
3166
+ disabledRanges: disabledRanges,
3167
+ blockers: blockers,
3168
+ conflicts: conflicts,
3169
+ allowMultiSelect: allowMultiSelect,
3170
+ selection: selection,
3171
+ dragging: dragging,
3172
+ formPopover: isMobile ? null : formPopover,
3173
+ isMobile: isMobile,
3174
+ popoverOpen: popoverOpen,
3175
+ popoverPlacement: popoverPlacement,
3176
+ popoverAnchor: popoverAnchor,
3177
+ interactionRef: interactionRef,
3178
+ onPopoverOpenChange: open => {
3179
+ if (open) {
3180
+ setPopoverOpen(true);
3181
+ return;
3182
+ }
3183
+ if (Date.now() - interactionRef.current < 400) {
3184
+ return;
3185
+ }
3186
+ clearSelection();
3187
+ },
3188
+ onSelectionDrag: range => {
3189
+ setDragging(true);
3190
+ setSelection(range);
3191
+ },
3192
+ onSelectionCommit: commitSelection,
3193
+ onFreeSlotClick: slot => onFreeSlotClick == null ? void 0 : onFreeSlotClick(_extends({}, slot, {
3194
+ key: `${date.format(DATE_FORMAT)}-${slot.start.getTime()}`,
3195
+ date: date.format(DATE_FORMAT),
3196
+ status: 'free',
3197
+ events: []
3198
+ })),
3199
+ onEventClick: onEventClick,
3200
+ renderEventBlock: renderEventBlock,
3201
+ renderOccupiedSlot: renderOccupiedSlot
1692
3202
  })]
1693
3203
  })
1694
- })
3204
+ }), isMobile ? /*#__PURE__*/jsx(Drawer, {
3205
+ placement: "bottom",
3206
+ size: "auto",
3207
+ closable: false,
3208
+ rootClassName: style$1['form-sheet'],
3209
+ open: !!selection && popoverOpen && !dragging,
3210
+ onClose: () => {
3211
+ if (Date.now() - openedAtRef.current < 500) {
3212
+ return;
3213
+ }
3214
+ clearSelection();
3215
+ },
3216
+ destroyOnHidden: true,
3217
+ children: formPopover
3218
+ }) : null]
1695
3219
  });
1696
3220
  };
1697
3221
 
1698
- var style = {"schedule-calendar-shell":"kne-react-calendar_csu63","schedule-calendar":"kne-react-calendar_5p1TK","calendar-side":"kne-react-calendar_nd7DK","panel-side":"kne-react-calendar_rWvHE","schedule-calendar-stacked":"kne-react-calendar_zGXr5"};
3222
+ var style = {"kne-responsive-boundary":"kne-react-calendar_gh14z","kne-responsive-scroll":"kne-react-calendar_Kfn6L","schedule-calendar-shell":"kne-react-calendar_csu63","schedule-calendar":"kne-react-calendar_5p1TK","calendar-side":"kne-react-calendar_nd7DK","panel-side":"kne-react-calendar_rWvHE","schedule-calendar-event-list-expanded":"kne-react-calendar_Av0q4","schedule-calendar-stacked":"kne-react-calendar_zGXr5"};
1699
3223
 
1700
3224
  dayjs.extend(customParseFormat);
1701
3225
  const buildMonthRuleCache = ({
@@ -1733,10 +3257,13 @@ const ScheduleCalendarInner = props => {
1733
3257
  timeUnit = 30,
1734
3258
  dayStart = '09:00',
1735
3259
  dayEnd = '18:00',
3260
+ timeStep = 5,
3261
+ hourHeight = 56,
3262
+ allowConflict = false,
1736
3263
  allowMultiSelect = mode === 'schedule',
1737
3264
  renderDateCell,
1738
3265
  renderEventItem,
1739
- renderTimeSlot,
3266
+ renderEventBlock,
1740
3267
  renderOccupiedSlot,
1741
3268
  renderPanelHeader,
1742
3269
  formInner,
@@ -1750,13 +3277,15 @@ const ScheduleCalendarInner = props => {
1750
3277
  className,
1751
3278
  style: propsStyle,
1752
3279
  stackAt = LAYOUT_STACK_BREAKPOINT,
3280
+ eventListScrollable = true,
1753
3281
  calendarProps,
1754
3282
  panelProps
1755
3283
  } = props;
1756
3284
  const rootRef = useRef(null);
1757
3285
  const calendarRef = useRef(null);
1758
3286
  const stacked = useStackedLayout(rootRef, stackAt);
1759
- const stackedLayoutStyle = useStackedPanelLayout(rootRef, calendarRef, stacked);
3287
+ const eventListExpanded = mode === 'view' && !eventListScrollable;
3288
+ const stackedLayoutStyle = useStackedPanelLayout(rootRef, calendarRef, stacked && !eventListExpanded);
1760
3289
  const [value, setValue] = useControlValue(props);
1761
3290
  const selectedDate = useMemo(() => toDayjs(value) || dayjs(), [value]);
1762
3291
  const [current, setCurrent] = useControlledState({
@@ -1765,7 +3294,6 @@ const ScheduleCalendarInner = props => {
1765
3294
  onChange: props.onCurrentChange
1766
3295
  });
1767
3296
  const currentDate = useMemo(() => toDayjs(current) || selectedDate, [current, selectedDate]);
1768
- const [selectedSlots, setSelectedSlots] = useState([]);
1769
3297
  const eventGroup = useMemo(() => groupEventsByDate(events), [events]);
1770
3298
  const selectedEvents = useMemo(() => eventGroup[selectedDate.format(DATE_FORMAT)] || [], [eventGroup, selectedDate]);
1771
3299
  const ruleContext = useMemo(() => ({
@@ -1801,7 +3329,6 @@ const ScheduleCalendarInner = props => {
1801
3329
  const nextDate = date.startOf('day').toDate();
1802
3330
  setValue(nextDate, info);
1803
3331
  onDateChange == null || onDateChange(nextDate, info);
1804
- setSelectedSlots([]);
1805
3332
  };
1806
3333
  const handleCurrentChange = (date, info) => {
1807
3334
  setCurrent(date.startOf('month').toDate(), info);
@@ -1837,9 +3364,9 @@ const ScheduleCalendarInner = props => {
1837
3364
  style: propsStyle,
1838
3365
  children: /*#__PURE__*/jsxs("div", {
1839
3366
  ref: rootRef,
1840
- className: classNames(style['schedule-calendar'], stacked && style['schedule-calendar-stacked'], className),
3367
+ className: classNames(style['schedule-calendar'], stacked && style['schedule-calendar-stacked'], eventListExpanded && style['schedule-calendar-event-list-expanded'], className),
1841
3368
  "data-schedule-layout": stacked ? 'stacked' : 'row',
1842
- style: stacked ? stackedLayoutStyle : undefined,
3369
+ style: stacked && !eventListExpanded ? stackedLayoutStyle : undefined,
1843
3370
  children: [/*#__PURE__*/jsx("div", {
1844
3371
  ref: calendarRef,
1845
3372
  className: style['calendar-side'],
@@ -1876,27 +3403,33 @@ const ScheduleCalendarInner = props => {
1876
3403
  children: mode === 'schedule' ? /*#__PURE__*/jsx(SchedulePanel, {
1877
3404
  date: selectedDate,
1878
3405
  slots: slots,
3406
+ events: selectedEvents,
3407
+ dayStart: dayStart,
3408
+ dayEnd: dayEnd,
3409
+ timeUnit: timeUnit,
3410
+ timeStep: timeStep,
3411
+ hourHeight: hourHeight,
3412
+ allowConflict: allowConflict,
1879
3413
  loading: loading,
1880
3414
  allowMultiSelect: allowMultiSelect,
1881
- selectedSlots: selectedSlots,
1882
- setSelectedSlots: setSelectedSlots,
1883
3415
  onSlotRangeChange: onSlotRangeChange,
1884
3416
  onFreeSlotClick: onFreeSlotClick,
1885
3417
  onCreate: onCreate,
1886
3418
  formInner: formInner,
1887
3419
  formProps: formProps,
1888
3420
  defaultFormValues: defaultFormValues,
1889
- renderTimeSlot: renderTimeSlot,
3421
+ renderEventBlock: renderEventBlock,
1890
3422
  renderOccupiedSlot: renderOccupiedSlot,
1891
3423
  renderPanelHeader: renderPanelHeader,
1892
3424
  onEventClick: onEventClick
1893
- }) : /*#__PURE__*/jsx(ViewPanel, {
3425
+ }, selectedDate.format(DATE_FORMAT)) : /*#__PURE__*/jsx(ViewPanel, {
1894
3426
  date: selectedDate,
1895
3427
  events: selectedEvents,
1896
3428
  loading: loading,
1897
3429
  renderEventItem: renderEventItem,
1898
3430
  renderPanelHeader: renderPanelHeader,
1899
- onEventClick: onEventClick
3431
+ onEventClick: onEventClick,
3432
+ scrollable: eventListScrollable
1900
3433
  })
1901
3434
  }))]
1902
3435
  })
@@ -1904,5 +3437,5 @@ const ScheduleCalendarInner = props => {
1904
3437
  };
1905
3438
  const ScheduleCalendar = withLocale(ScheduleCalendarInner);
1906
3439
 
1907
- export { ScheduleCalendar as ReactCalendar, ScheduleCalendar, dedupeEventsById, ScheduleCalendar as default, generateTimeSlots, groupEventsByDate, normalizeEvent, sortEventsByStart };
3440
+ export { ScheduleCalendar as ReactCalendar, ScheduleCalendar, TimeWindow, dedupeEventsById, ScheduleCalendar as default, generateTimeSlots, groupEventsByDate, normalizeEvent, sortEventsByStart };
1908
3441
  //# sourceMappingURL=index.modern.js.map