@kne/react-calendar 1.0.6 → 1.1.1

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