@kne/react-calendar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1721 @@
1
+ import { useState, useLayoutEffect, useMemo, useEffect, useCallback, useRef } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
4
+ import useControlValue from '@kne/use-control-value';
5
+ import { Popover, Space, Button, Badge, List, Tag, Card, Spin, Empty, Tooltip, Calendar } from 'antd';
6
+ import { createWithIntlProvider, useIntl } from '@kne/react-intl';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
+ import FormInfo, { Input, TextArea, Form, SubmitButton } from '@kne/form-info';
9
+
10
+ function _extends() {
11
+ return _extends = Object.assign ? Object.assign.bind() : function (n) {
12
+ for (var e = 1; e < arguments.length; e++) {
13
+ var t = arguments[e];
14
+ for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
15
+ }
16
+ return n;
17
+ }, _extends.apply(null, arguments);
18
+ }
19
+
20
+ const locale$1 = {
21
+ 'Header.today': '今天',
22
+ 'Header.prevMonth': '上一月',
23
+ 'Header.nextMonth': '下一月',
24
+ 'Header.prevYearPage': '上一组年份',
25
+ 'Header.nextYearPage': '下一组年份',
26
+ 'Header.yearLabel': '{year}年',
27
+ 'Header.monthLabel': '{monthName}',
28
+ 'Header.monthPickerItem': '{monthName}',
29
+ 'PanelTitle.dateLabel': '{year}年{monthName}{day}日',
30
+ 'PanelTitle.eventCount': '共 {count} 个日程',
31
+ 'ViewPanel.empty': '暂无日程',
32
+ 'SchedulePanel.selectFreeSlot': '选择空闲时间段',
33
+ 'SchedulePanel.newSchedule': '新增日程:{range}',
34
+ 'SchedulePanel.cancel': '取消',
35
+ 'SchedulePanel.save': '保存',
36
+ 'SlotItem.disabled': '不可选',
37
+ 'SlotItem.occupied': '已占用',
38
+ 'DefaultFormInner.title': '标题',
39
+ 'DefaultFormInner.titlePlaceholder': '请输入日程标题',
40
+ 'DefaultFormInner.detail': '详情',
41
+ 'DefaultFormInner.detailPlaceholder': '请输入日程详情',
42
+ 'Calendar.month.1': '1月',
43
+ 'Calendar.month.2': '2月',
44
+ 'Calendar.month.3': '3月',
45
+ 'Calendar.month.4': '4月',
46
+ 'Calendar.month.5': '5月',
47
+ 'Calendar.month.6': '6月',
48
+ 'Calendar.month.7': '7月',
49
+ 'Calendar.month.8': '8月',
50
+ 'Calendar.month.9': '9月',
51
+ 'Calendar.month.10': '10月',
52
+ 'Calendar.month.11': '11月',
53
+ 'Calendar.month.12': '12月'
54
+ };
55
+
56
+ const locale = {
57
+ 'Header.today': 'Today',
58
+ 'Header.prevMonth': 'Previous month',
59
+ 'Header.nextMonth': 'Next month',
60
+ 'Header.prevYearPage': 'Previous years',
61
+ 'Header.nextYearPage': 'Next years',
62
+ 'Header.yearLabel': '{year}',
63
+ 'Header.monthLabel': '{monthName}',
64
+ 'Header.monthPickerItem': '{monthName}',
65
+ 'PanelTitle.dateLabel': '{monthName} {day}, {year}',
66
+ 'PanelTitle.eventCount': '{count} events',
67
+ 'ViewPanel.empty': 'No events',
68
+ 'SchedulePanel.selectFreeSlot': 'Select an available time slot',
69
+ 'SchedulePanel.newSchedule': 'New event: {range}',
70
+ 'SchedulePanel.cancel': 'Cancel',
71
+ 'SchedulePanel.save': 'Save',
72
+ 'SlotItem.disabled': 'Unavailable',
73
+ 'SlotItem.occupied': 'Occupied',
74
+ 'DefaultFormInner.title': 'Title',
75
+ 'DefaultFormInner.titlePlaceholder': 'Enter event title',
76
+ 'DefaultFormInner.detail': 'Details',
77
+ 'DefaultFormInner.detailPlaceholder': 'Enter event details',
78
+ 'Calendar.month.1': 'Jan',
79
+ 'Calendar.month.2': 'Feb',
80
+ 'Calendar.month.3': 'Mar',
81
+ 'Calendar.month.4': 'Apr',
82
+ 'Calendar.month.5': 'May',
83
+ 'Calendar.month.6': 'Jun',
84
+ 'Calendar.month.7': 'Jul',
85
+ 'Calendar.month.8': 'Aug',
86
+ 'Calendar.month.9': 'Sep',
87
+ 'Calendar.month.10': 'Oct',
88
+ 'Calendar.month.11': 'Nov',
89
+ 'Calendar.month.12': 'Dec'
90
+ };
91
+
92
+ const withLocale = createWithIntlProvider({
93
+ defaultLocale: 'zh-CN',
94
+ messages: {
95
+ 'zh-CN': locale$1,
96
+ 'en-US': locale
97
+ },
98
+ namespace: 'react-calendar'
99
+ });
100
+
101
+ const DATE_FORMAT = 'YYYY-MM-DD';
102
+ const TIME_FORMAT = 'HH:mm';
103
+ /** Stack panels vertically when container width is below this value (px). */
104
+ const LAYOUT_STACK_BREAKPOINT = 560;
105
+ /** Minimum panel viewport height in stacked layout (px), aligned with root min-height. */
106
+ const LAYOUT_MIN_HEIGHT = 560;
107
+
108
+ const measureHeight = node => {
109
+ if (!node) {
110
+ return 0;
111
+ }
112
+ return Math.ceil(node.getBoundingClientRect().height);
113
+ };
114
+ const useStackedPanelLayout = (rootRef, calendarRef, stacked) => {
115
+ const [layoutStyle, setLayoutStyle] = useState(undefined);
116
+ useLayoutEffect(() => {
117
+ if (!stacked) {
118
+ setLayoutStyle(undefined);
119
+ return undefined;
120
+ }
121
+ const root = rootRef.current;
122
+ const calendar = calendarRef.current;
123
+ if (!root || !calendar) {
124
+ return undefined;
125
+ }
126
+ const update = () => {
127
+ const calendarHeight = measureHeight(calendar);
128
+ const panelViewportHeight = Math.max(calendarHeight, LAYOUT_MIN_HEIGHT);
129
+ const totalHeight = calendarHeight + panelViewportHeight;
130
+ setLayoutStyle({
131
+ height: totalHeight,
132
+ '--schedule-panel-top': `${calendarHeight}px`,
133
+ '--schedule-panel-height': `${panelViewportHeight}px`
134
+ });
135
+ };
136
+ update();
137
+ if (typeof ResizeObserver === 'undefined') {
138
+ window.addEventListener('resize', update);
139
+ return () => window.removeEventListener('resize', update);
140
+ }
141
+ const observer = new ResizeObserver(update);
142
+ observer.observe(calendar);
143
+ observer.observe(root);
144
+ return () => observer.disconnect();
145
+ }, [calendarRef, rootRef, stacked]);
146
+ return layoutStyle;
147
+ };
148
+
149
+ const classNames = (...list) => list.flatMap(item => {
150
+ if (!item) {
151
+ return [];
152
+ }
153
+ if (typeof item === 'string') {
154
+ return [item];
155
+ }
156
+ return Object.keys(item).filter(key => item[key]);
157
+ }).join(' ');
158
+ const toDayjs = value => {
159
+ if (!value) {
160
+ return null;
161
+ }
162
+ const date = dayjs(value);
163
+ return date.isValid() ? date : null;
164
+ };
165
+ const parseDayTime = (date, time) => {
166
+ const day = toDayjs(date) || dayjs();
167
+ const parsed = dayjs(`${day.format(DATE_FORMAT)} ${time}`, `${DATE_FORMAT} ${TIME_FORMAT}`);
168
+ return parsed.isValid() ? parsed : day.startOf('day');
169
+ };
170
+ const formatRange = (start, end) => `${dayjs(start).format(TIME_FORMAT)} - ${dayjs(end).format(TIME_FORMAT)}`;
171
+ const getDefaultFormValues = (defaultFormValues, ctx) => {
172
+ if (typeof defaultFormValues === 'function') {
173
+ return defaultFormValues(ctx);
174
+ }
175
+ return _extends({
176
+ title: '',
177
+ detail: ''
178
+ }, defaultFormValues || {});
179
+ };
180
+ const sortEventsByStart = events => [...(events || [])].sort((a, b) => {
181
+ var _toDayjs$valueOf, _toDayjs, _toDayjs$valueOf2, _toDayjs2;
182
+ const aStart = (_toDayjs$valueOf = (_toDayjs = toDayjs(a.start)) == null ? void 0 : _toDayjs.valueOf()) != null ? _toDayjs$valueOf : 0;
183
+ const bStart = (_toDayjs$valueOf2 = (_toDayjs2 = toDayjs(b.start)) == null ? void 0 : _toDayjs2.valueOf()) != null ? _toDayjs$valueOf2 : 0;
184
+ return aStart - bStart;
185
+ });
186
+ const dedupeEventsById = events => {
187
+ const map = new Map();
188
+ (events || []).forEach(event => {
189
+ map.set(event.id, event);
190
+ });
191
+ return [...map.values()];
192
+ };
193
+ const useControlledState = ({
194
+ value,
195
+ defaultValue,
196
+ onChange
197
+ }) => {
198
+ const props = {};
199
+ if (typeof value !== 'undefined') {
200
+ props.value = value;
201
+ }
202
+ if (typeof defaultValue !== 'undefined') {
203
+ props.defaultValue = defaultValue;
204
+ }
205
+ if (onChange) {
206
+ props.onChange = onChange;
207
+ }
208
+ return useControlValue(props);
209
+ };
210
+
211
+ const getRuleDisabled = (result, defaultReason) => {
212
+ if (!result) {
213
+ return null;
214
+ }
215
+ if (typeof result === 'boolean') {
216
+ return result ? {
217
+ disabled: true,
218
+ reason: defaultReason
219
+ } : null;
220
+ }
221
+ if (result.disabled) {
222
+ return {
223
+ disabled: true,
224
+ reason: result.reason || defaultReason
225
+ };
226
+ }
227
+ return null;
228
+ };
229
+ const checkRule = ({
230
+ date,
231
+ slot,
232
+ mode,
233
+ disabledDate,
234
+ disabledDateTime,
235
+ availableDateTime
236
+ }) => {
237
+ if (disabledDate != null && disabledDate(date.toDate())) {
238
+ return {
239
+ disabled: true
240
+ };
241
+ }
242
+ const ctx = _extends({
243
+ date: date.toDate(),
244
+ mode
245
+ }, slot ? {
246
+ slot: {
247
+ start: slot.start.toDate(),
248
+ end: slot.end.toDate()
249
+ }
250
+ } : {});
251
+ const disabledResult = getRuleDisabled(disabledDateTime == null ? void 0 : disabledDateTime(ctx));
252
+ if (disabledResult) {
253
+ return disabledResult;
254
+ }
255
+ if (availableDateTime && !availableDateTime(ctx)) {
256
+ return {
257
+ disabled: true
258
+ };
259
+ }
260
+ return null;
261
+ };
262
+
263
+ const normalizeEvent = event => {
264
+ const start = toDayjs(event.start || event.date);
265
+ const end = toDayjs(event.end || event.start || event.date);
266
+ return _extends({}, event, {
267
+ start,
268
+ end: end && start && !end.isAfter(start) ? start.add(1, 'minute') : end
269
+ });
270
+ };
271
+ const groupEventsByDate = events => {
272
+ return (events || []).reduce((result, event) => {
273
+ const normalized = normalizeEvent(event);
274
+ if (!normalized.start || !normalized.end) {
275
+ return result;
276
+ }
277
+ const startOfDay = normalized.start.startOf('day');
278
+ const endOfDay = normalized.end.startOf('day');
279
+ let current = startOfDay;
280
+ while (current.isBefore(endOfDay) || current.isSame(endOfDay, 'day')) {
281
+ const key = current.format(DATE_FORMAT);
282
+ result[key] = result[key] || [];
283
+ result[key].push(normalized);
284
+ current = current.add(1, 'day');
285
+ }
286
+ return result;
287
+ }, {});
288
+ };
289
+ const getOverlappingEvents = (slotStart, slotEnd, sortedEvents) => {
290
+ const matched = [];
291
+ for (const event of sortedEvents) {
292
+ if (!event.end.isAfter(slotStart)) {
293
+ continue;
294
+ }
295
+ if (!event.start.isBefore(slotEnd)) {
296
+ break;
297
+ }
298
+ matched.push(event);
299
+ }
300
+ return matched;
301
+ };
302
+ const generateTimeSlots = ({
303
+ date,
304
+ events,
305
+ timeUnit: _timeUnit = 30,
306
+ dayStart: _dayStart = '09:00',
307
+ dayEnd: _dayEnd = '18:00',
308
+ disabledDate,
309
+ disabledDateTime,
310
+ availableDateTime,
311
+ mode: _mode = 'schedule'
312
+ }) => {
313
+ const selectedDate = toDayjs(date) || dayjs();
314
+ const startAt = parseDayTime(selectedDate, _dayStart);
315
+ const endAt = parseDayTime(selectedDate, _dayEnd);
316
+ if (!startAt.isBefore(endAt)) {
317
+ return [];
318
+ }
319
+ const normalizedEvents = (events || []).map(normalizeEvent).filter(event => event.start && event.end).sort((a, b) => a.start.valueOf() - b.start.valueOf());
320
+ const slots = [];
321
+ let current = startAt;
322
+ while (current.isBefore(endAt)) {
323
+ const slotStart = current;
324
+ const slotEnd = slotStart.add(_timeUnit, 'minute');
325
+ const matchedEvents = getOverlappingEvents(slotStart, slotEnd, normalizedEvents);
326
+ const baseSlot = {
327
+ key: `${slotStart.format(DATE_FORMAT)}-${slotStart.format(TIME_FORMAT)}`,
328
+ date: slotStart.format(DATE_FORMAT),
329
+ start: slotStart,
330
+ end: slotEnd
331
+ };
332
+ const ruleResult = checkRule({
333
+ date: selectedDate,
334
+ slot: baseSlot,
335
+ mode: _mode,
336
+ disabledDate,
337
+ disabledDateTime,
338
+ availableDateTime
339
+ });
340
+ slots.push(_extends({}, baseSlot, {
341
+ start: slotStart.toDate(),
342
+ end: slotEnd.toDate(),
343
+ status: ruleResult ? 'disabled' : matchedEvents.length > 0 ? 'occupied' : 'free',
344
+ disabledReason: ruleResult == null ? void 0 : ruleResult.reason,
345
+ events: matchedEvents
346
+ }));
347
+ current = slotEnd;
348
+ }
349
+ return slots;
350
+ };
351
+
352
+ const toDay = value => toDayjs(value) || dayjs();
353
+ const getMonthName = (formatMessage, monthIndex) => formatMessage({
354
+ id: `Calendar.month.${monthIndex + 1}`
355
+ });
356
+ const getPanelDateLabel = (formatMessage, date) => {
357
+ const d = toDay(date);
358
+ const monthIndex = d.month();
359
+ return formatMessage({
360
+ id: 'PanelTitle.dateLabel'
361
+ }, {
362
+ year: d.year(),
363
+ month: monthIndex + 1,
364
+ day: d.date(),
365
+ monthName: getMonthName(formatMessage, monthIndex)
366
+ });
367
+ };
368
+ const getHeaderMonthLabel = (formatMessage, date) => {
369
+ const d = toDay(date);
370
+ const monthIndex = d.month();
371
+ return formatMessage({
372
+ id: 'Header.monthLabel'
373
+ }, {
374
+ month: monthIndex + 1,
375
+ monthName: getMonthName(formatMessage, monthIndex)
376
+ });
377
+ };
378
+ const getMonthPickerItemLabel = (formatMessage, value, monthIndex) => formatMessage({
379
+ id: 'Header.monthPickerItem'
380
+ }, {
381
+ month: monthIndex + 1,
382
+ monthName: getMonthName(formatMessage, monthIndex)
383
+ });
384
+
385
+ var style$4 = {"calendar-header":"kne-react-calendar_X1TYG","calendar-title":"kne-react-calendar_FcQis","calendar-title-part":"kne-react-calendar_LcyYv","header-picker-popover":"kne-react-calendar_T4oWB","picker-panel":"kne-react-calendar_PMS-M","picker-panel-header":"kne-react-calendar_IXQKl","picker-panel-range":"kne-react-calendar_J-Pnh","picker-panel-nav":"kne-react-calendar_Wo74w","picker-panel-nav-icon":"kne-react-calendar_Rwzuj","picker-panel-grid":"kne-react-calendar_rxTOr","picker-cell":"kne-react-calendar_bHuh6","picker-cell-active":"kne-react-calendar_xOfrO","header-actions":"kne-react-calendar_OCJaK","header-today":"kne-react-calendar_SWVUG","header-nav":"kne-react-calendar_BOheC","header-nav-icon":"kne-react-calendar_8IEwS"};
386
+
387
+ const HeaderNavIcon = ({
388
+ direction,
389
+ className
390
+ }) => /*#__PURE__*/jsx("svg", {
391
+ className: className || style$4['header-nav-icon'],
392
+ viewBox: "0 0 16 16",
393
+ "aria-hidden": true,
394
+ focusable: "false",
395
+ children: /*#__PURE__*/jsx("path", {
396
+ d: direction === 'prev' ? 'M10 4 L6 8 L10 12' : 'M6 4 L10 8 L6 12',
397
+ fill: "none",
398
+ stroke: "currentColor",
399
+ strokeWidth: "1.8",
400
+ strokeLinecap: "round",
401
+ strokeLinejoin: "round"
402
+ })
403
+ });
404
+
405
+ const YEAR_PAGE_SIZE = 12;
406
+ const getYearPageStart = year => Math.floor(year / YEAR_PAGE_SIZE) * YEAR_PAGE_SIZE;
407
+
408
+ const PickerPanelNav = ({
409
+ label,
410
+ onClick,
411
+ children
412
+ }) => /*#__PURE__*/jsx("button", {
413
+ type: "button",
414
+ className: style$4['picker-panel-nav'],
415
+ "aria-label": label,
416
+ onClick: onClick,
417
+ children: children
418
+ });
419
+ const YearPickerPanel = ({
420
+ value,
421
+ pageStart,
422
+ onPageStartChange,
423
+ onSelect
424
+ }) => {
425
+ const {
426
+ formatMessage
427
+ } = useIntl();
428
+ const years = useMemo(() => Array.from({
429
+ length: YEAR_PAGE_SIZE
430
+ }, (_, index) => pageStart + index), [pageStart]);
431
+ return /*#__PURE__*/jsxs("div", {
432
+ className: style$4['picker-panel'],
433
+ children: [/*#__PURE__*/jsxs("div", {
434
+ className: style$4['picker-panel-header'],
435
+ children: [/*#__PURE__*/jsx(PickerPanelNav, {
436
+ label: formatMessage({
437
+ id: 'Header.prevYearPage'
438
+ }),
439
+ onClick: () => onPageStartChange(pageStart - YEAR_PAGE_SIZE),
440
+ children: /*#__PURE__*/jsx(HeaderNavIcon, {
441
+ direction: "prev",
442
+ className: style$4['picker-panel-nav-icon']
443
+ })
444
+ }), /*#__PURE__*/jsxs("div", {
445
+ className: style$4['picker-panel-range'],
446
+ children: [pageStart, "-", pageStart + YEAR_PAGE_SIZE - 1]
447
+ }), /*#__PURE__*/jsx(PickerPanelNav, {
448
+ label: formatMessage({
449
+ id: 'Header.nextYearPage'
450
+ }),
451
+ onClick: () => onPageStartChange(pageStart + YEAR_PAGE_SIZE),
452
+ children: /*#__PURE__*/jsx(HeaderNavIcon, {
453
+ direction: "next",
454
+ className: style$4['picker-panel-nav-icon']
455
+ })
456
+ })]
457
+ }), /*#__PURE__*/jsx("div", {
458
+ className: style$4['picker-panel-grid'],
459
+ children: years.map(year => /*#__PURE__*/jsx("button", {
460
+ type: "button",
461
+ className: classNames(style$4['picker-cell'], {
462
+ [style$4['picker-cell-active']]: year === value.year()
463
+ }),
464
+ onClick: () => onSelect(year),
465
+ children: year
466
+ }, year))
467
+ })]
468
+ });
469
+ };
470
+
471
+ const MonthPickerPanel = ({
472
+ value,
473
+ onSelect
474
+ }) => {
475
+ const {
476
+ formatMessage
477
+ } = useIntl();
478
+ const months = useMemo(() => Array.from({
479
+ length: 12
480
+ }, (_, index) => index), []);
481
+ return /*#__PURE__*/jsx("div", {
482
+ className: style$4['picker-panel'],
483
+ children: /*#__PURE__*/jsx("div", {
484
+ className: style$4['picker-panel-grid'],
485
+ children: months.map(month => /*#__PURE__*/jsx("button", {
486
+ type: "button",
487
+ className: classNames(style$4['picker-cell'], {
488
+ [style$4['picker-cell-active']]: month === value.month()
489
+ }),
490
+ onClick: () => onSelect(month),
491
+ children: getMonthPickerItemLabel(formatMessage, value, month)
492
+ }, month))
493
+ })
494
+ });
495
+ };
496
+
497
+ const HeaderNavButton = ({
498
+ label,
499
+ onClick,
500
+ children
501
+ }) => /*#__PURE__*/jsx("button", {
502
+ type: "button",
503
+ className: style$4['header-nav'],
504
+ "aria-label": label,
505
+ onClick: onClick,
506
+ children: children
507
+ });
508
+ const Header = ({
509
+ current,
510
+ showTodayButton,
511
+ onCurrentChange,
512
+ onSelectToday
513
+ }) => {
514
+ const {
515
+ formatMessage
516
+ } = useIntl();
517
+ const date = toDayjs(current) || dayjs();
518
+ const [yearOpen, setYearOpen] = useState(false);
519
+ const [monthOpen, setMonthOpen] = useState(false);
520
+ const [yearPageStart, setYearPageStart] = useState(() => getYearPageStart(date.year()));
521
+ const currentYear = date.year();
522
+ const yearText = formatMessage({
523
+ id: 'Header.yearLabel'
524
+ }, {
525
+ year: date.year()
526
+ });
527
+ const monthText = getHeaderMonthLabel(formatMessage, date);
528
+ useEffect(() => {
529
+ if (yearOpen) {
530
+ setYearPageStart(getYearPageStart(currentYear));
531
+ }
532
+ }, [yearOpen, currentYear]);
533
+ const handleYearSelect = year => {
534
+ onCurrentChange(date.year(year), {
535
+ source: 'picker'
536
+ });
537
+ setYearOpen(false);
538
+ };
539
+ const handleMonthSelect = month => {
540
+ onCurrentChange(date.month(month), {
541
+ source: 'picker'
542
+ });
543
+ setMonthOpen(false);
544
+ };
545
+ return /*#__PURE__*/jsxs("div", {
546
+ className: style$4['calendar-header'],
547
+ children: [/*#__PURE__*/jsxs("div", {
548
+ className: style$4['calendar-title'],
549
+ children: [/*#__PURE__*/jsx(Popover, {
550
+ open: yearOpen,
551
+ onOpenChange: open => {
552
+ setYearOpen(open);
553
+ if (open) {
554
+ setMonthOpen(false);
555
+ }
556
+ },
557
+ trigger: "click",
558
+ placement: "bottomLeft",
559
+ rootClassName: style$4['header-picker-popover'],
560
+ content: /*#__PURE__*/jsx(YearPickerPanel, {
561
+ value: date,
562
+ pageStart: yearPageStart,
563
+ onPageStartChange: setYearPageStart,
564
+ onSelect: handleYearSelect
565
+ }),
566
+ children: /*#__PURE__*/jsx("button", {
567
+ type: "button",
568
+ className: style$4['calendar-title-part'],
569
+ children: yearText
570
+ })
571
+ }), /*#__PURE__*/jsx(Popover, {
572
+ open: monthOpen,
573
+ onOpenChange: open => {
574
+ setMonthOpen(open);
575
+ if (open) {
576
+ setYearOpen(false);
577
+ }
578
+ },
579
+ trigger: "click",
580
+ placement: "bottomLeft",
581
+ rootClassName: style$4['header-picker-popover'],
582
+ content: /*#__PURE__*/jsx(MonthPickerPanel, {
583
+ value: date,
584
+ onSelect: handleMonthSelect
585
+ }),
586
+ children: /*#__PURE__*/jsx("button", {
587
+ type: "button",
588
+ className: style$4['calendar-title-part'],
589
+ children: monthText
590
+ })
591
+ })]
592
+ }), /*#__PURE__*/jsxs(Space, {
593
+ size: 4,
594
+ className: style$4['header-actions'],
595
+ children: [showTodayButton ? /*#__PURE__*/jsx(Button, {
596
+ className: style$4['header-today'],
597
+ type: "text",
598
+ size: "small",
599
+ onClick: onSelectToday,
600
+ children: formatMessage({
601
+ id: 'Header.today'
602
+ })
603
+ }) : null, /*#__PURE__*/jsx(HeaderNavButton, {
604
+ label: formatMessage({
605
+ id: 'Header.prevMonth'
606
+ }),
607
+ onClick: () => onCurrentChange(date.subtract(1, 'month'), {
608
+ source: 'prevMonth'
609
+ }),
610
+ children: /*#__PURE__*/jsx(HeaderNavIcon, {
611
+ direction: "prev"
612
+ })
613
+ }), /*#__PURE__*/jsx(HeaderNavButton, {
614
+ label: formatMessage({
615
+ id: 'Header.nextMonth'
616
+ }),
617
+ onClick: () => onCurrentChange(date.add(1, 'month'), {
618
+ source: 'nextMonth'
619
+ }),
620
+ children: /*#__PURE__*/jsx(HeaderNavIcon, {
621
+ direction: "next"
622
+ })
623
+ })]
624
+ })]
625
+ });
626
+ };
627
+
628
+ const readWidth = node => {
629
+ if (!node) {
630
+ return 0;
631
+ }
632
+ return node.getBoundingClientRect().width;
633
+ };
634
+ const useStackedLayout = (ref, stackAt) => {
635
+ const [stacked, setStacked] = useState(false);
636
+ useLayoutEffect(() => {
637
+ const node = ref.current;
638
+ if (!node) {
639
+ return;
640
+ }
641
+ setStacked(readWidth(node) < stackAt);
642
+ }, [ref, stackAt]);
643
+ useEffect(() => {
644
+ const node = ref.current;
645
+ if (!node) {
646
+ return undefined;
647
+ }
648
+ const update = () => {
649
+ setStacked(readWidth(node) < stackAt);
650
+ };
651
+ update();
652
+ if (typeof ResizeObserver === 'undefined') {
653
+ window.addEventListener('resize', update);
654
+ return () => window.removeEventListener('resize', update);
655
+ }
656
+ const observer = new ResizeObserver(() => update());
657
+ observer.observe(node);
658
+ return () => observer.disconnect();
659
+ }, [ref, stackAt]);
660
+ return stacked;
661
+ };
662
+
663
+ var style$3 = {"date-cell":"kne-react-calendar_-a9AL","date-number":"kne-react-calendar_9s0kZ","date-has-events":"kne-react-calendar_-Bh5f","date-selected":"kne-react-calendar_Lpltl","date-muted":"kne-react-calendar_1xgRD","date-disabled":"kne-react-calendar_YTv16"};
664
+
665
+ const DateCell = ({
666
+ date,
667
+ dayEvents,
668
+ selected,
669
+ currentMonth,
670
+ disabled,
671
+ badgeMaxCount,
672
+ renderDateCell
673
+ }) => {
674
+ const count = dayEvents.length;
675
+ const customCell = renderDateCell == null ? void 0 : renderDateCell(date.toDate(), {
676
+ events: dayEvents,
677
+ selected,
678
+ currentMonth
679
+ });
680
+ if (customCell) {
681
+ return customCell;
682
+ }
683
+ return /*#__PURE__*/jsxs("div", {
684
+ className: classNames(style$3['date-cell'], {
685
+ [style$3['date-selected']]: selected,
686
+ [style$3['date-muted']]: !currentMonth && !selected,
687
+ [style$3['date-disabled']]: disabled && !selected,
688
+ [style$3['date-has-events']]: count > 0 && !selected
689
+ }),
690
+ children: [/*#__PURE__*/jsx("div", {
691
+ className: style$3['date-number'],
692
+ children: date.date()
693
+ }), count ? /*#__PURE__*/jsx(Badge, {
694
+ count: count > badgeMaxCount ? `${badgeMaxCount}+` : count,
695
+ size: "small"
696
+ }) : null]
697
+ });
698
+ };
699
+
700
+ const getEventTagLabel = event => {
701
+ if (!event.raw || typeof event.raw !== 'object') {
702
+ return null;
703
+ }
704
+ const {
705
+ label,
706
+ type
707
+ } = event.raw;
708
+ if (typeof label === 'string' && label) {
709
+ return label;
710
+ }
711
+ if (typeof type === 'string' && type) {
712
+ return type;
713
+ }
714
+ return null;
715
+ };
716
+
717
+ var style$2 = {"event-list-item":"kne-react-calendar_cGTpY","event-item":"kne-react-calendar_3gmHY","event-item-head":"kne-react-calendar_fa3Mn","event-item-tag":"kne-react-calendar_yALGf","event-item-time":"kne-react-calendar_1L-dA","event-item-title":"kne-react-calendar_ISKyA","event-item-detail":"kne-react-calendar_HzTU2","event-item-clickable":"kne-react-calendar_iiYOC","event-item-disabled":"kne-react-calendar_3sXqE"};
718
+
719
+ const EventItem = ({
720
+ event,
721
+ date,
722
+ mode,
723
+ renderEventItem,
724
+ onEventClick
725
+ }) => {
726
+ if (renderEventItem) {
727
+ return renderEventItem(event, {
728
+ date,
729
+ mode,
730
+ onEventClick
731
+ });
732
+ }
733
+ const clickable = !event.disabled && (typeof event.onClick === 'function' || typeof onEventClick === 'function');
734
+ const handleClick = () => {
735
+ if (typeof event.onClick === 'function') {
736
+ event.onClick(event, {
737
+ date,
738
+ mode
739
+ });
740
+ return;
741
+ }
742
+ onEventClick == null || onEventClick(event, {
743
+ date,
744
+ mode
745
+ });
746
+ };
747
+ const tagLabel = getEventTagLabel(event);
748
+ return /*#__PURE__*/jsx(List.Item, {
749
+ className: style$2['event-list-item'],
750
+ children: /*#__PURE__*/jsxs("div", {
751
+ className: classNames(style$2['event-item'], {
752
+ [style$2['event-item-clickable']]: clickable,
753
+ [style$2['event-item-disabled']]: event.disabled
754
+ }),
755
+ role: clickable ? 'button' : undefined,
756
+ tabIndex: clickable ? 0 : undefined,
757
+ onClick: clickable ? handleClick : undefined,
758
+ onKeyDown: clickable ? eventKey => {
759
+ if (eventKey.key === 'Enter' || eventKey.key === ' ') {
760
+ eventKey.preventDefault();
761
+ handleClick();
762
+ }
763
+ } : undefined,
764
+ children: [/*#__PURE__*/jsxs("div", {
765
+ className: style$2['event-item-head'],
766
+ children: [/*#__PURE__*/jsx("div", {
767
+ className: style$2['event-item-title'],
768
+ children: event.title
769
+ }), tagLabel ? /*#__PURE__*/jsx(Tag, {
770
+ className: style$2['event-item-tag'],
771
+ color: event.color,
772
+ children: tagLabel
773
+ }) : null]
774
+ }), /*#__PURE__*/jsx("div", {
775
+ className: style$2['event-item-time'],
776
+ children: formatRange(event.start, event.end)
777
+ }), event.detail ? /*#__PURE__*/jsx("div", {
778
+ className: style$2['event-item-detail'],
779
+ children: event.detail
780
+ }) : null]
781
+ })
782
+ });
783
+ };
784
+
785
+ var panelStyle = {"panel-card":"kne-react-calendar_uQsUE","panel-scroll-body":"kne-react-calendar_ujAl5","panel-scroll-content":"kne-react-calendar_t2e0j","panel-spin":"kne-react-calendar_tLux2","panel-spin-container":"kne-react-calendar_iW4mn","panel-title":"kne-react-calendar_C6NB3","panel-scroll-shadow":"kne-react-calendar_3ecxf","panel-scroll-shadow-visible":"kne-react-calendar_TvxhV","detail-card":"kne-react-calendar_3sEou"};
786
+
787
+ const PanelTitle = ({
788
+ date,
789
+ subtitle,
790
+ count
791
+ }) => {
792
+ const {
793
+ formatMessage
794
+ } = useIntl();
795
+ const dateText = getPanelDateLabel(formatMessage, date);
796
+ const countText = typeof count === 'number' ? formatMessage({
797
+ id: 'PanelTitle.eventCount'
798
+ }, {
799
+ count
800
+ }) : null;
801
+ return /*#__PURE__*/jsxs("div", {
802
+ className: panelStyle['panel-title'],
803
+ children: [/*#__PURE__*/jsx("span", {
804
+ children: dateText
805
+ }), subtitle || countText ? /*#__PURE__*/jsx("span", {
806
+ children: subtitle || countText
807
+ }) : null]
808
+ });
809
+ };
810
+
811
+ const PanelScrollShadow = ({
812
+ visible
813
+ }) => /*#__PURE__*/jsx("div", {
814
+ className: classNames(panelStyle['panel-scroll-shadow'], {
815
+ [panelStyle['panel-scroll-shadow-visible']]: visible
816
+ }),
817
+ "aria-hidden": true
818
+ });
819
+
820
+ const usePanelScrollShadow = (resetKey, scrollRef) => {
821
+ const [scrolled, setScrolled] = useState(false);
822
+ useEffect(() => {
823
+ if (scrollRef != null && scrollRef.current) {
824
+ scrollRef.current.scrollTop = 0;
825
+ }
826
+ setScrolled(false);
827
+ }, [resetKey, scrollRef]);
828
+ const onScroll = useCallback(event => {
829
+ setScrolled(event.currentTarget.scrollTop > 0);
830
+ }, []);
831
+ return {
832
+ scrolled,
833
+ onScroll
834
+ };
835
+ };
836
+
837
+ const ViewPanel = ({
838
+ date,
839
+ events,
840
+ loading,
841
+ renderEventItem,
842
+ renderPanelHeader,
843
+ onEventClick
844
+ }) => {
845
+ const {
846
+ formatMessage
847
+ } = useIntl();
848
+ const sortedEvents = useMemo(() => sortEventsByStart(events), [events]);
849
+ const panelHeader = renderPanelHeader == null ? void 0 : renderPanelHeader({
850
+ date: date.toDate(),
851
+ mode: 'view',
852
+ events: sortedEvents
853
+ });
854
+ const scrollRef = useRef(null);
855
+ const {
856
+ scrolled,
857
+ onScroll
858
+ } = usePanelScrollShadow(date.format(DATE_FORMAT), scrollRef);
859
+ return /*#__PURE__*/jsx(Card, {
860
+ bordered: false,
861
+ title: panelHeader || /*#__PURE__*/jsx(PanelTitle, {
862
+ date: date,
863
+ count: sortedEvents.length
864
+ }),
865
+ className: panelStyle['panel-card'],
866
+ children: /*#__PURE__*/jsx(Spin, {
867
+ spinning: !!loading,
868
+ classNames: {
869
+ root: panelStyle['panel-spin'],
870
+ container: panelStyle['panel-spin-container']
871
+ },
872
+ children: /*#__PURE__*/jsxs("div", {
873
+ ref: scrollRef,
874
+ className: panelStyle['panel-scroll-body'],
875
+ onScroll: onScroll,
876
+ children: [/*#__PURE__*/jsx(PanelScrollShadow, {
877
+ visible: scrolled
878
+ }), /*#__PURE__*/jsx("div", {
879
+ className: panelStyle['panel-scroll-content'],
880
+ children: sortedEvents.length ? /*#__PURE__*/jsx(List, {
881
+ dataSource: sortedEvents,
882
+ renderItem: event => /*#__PURE__*/jsx(EventItem, {
883
+ event: event,
884
+ date: date.toDate(),
885
+ mode: "view",
886
+ renderEventItem: renderEventItem,
887
+ onEventClick: onEventClick
888
+ })
889
+ }) : /*#__PURE__*/jsx(Empty, {
890
+ description: formatMessage({
891
+ id: 'ViewPanel.empty'
892
+ })
893
+ })
894
+ })]
895
+ })
896
+ })
897
+ });
898
+ };
899
+
900
+ const LONG_PRESS_DELAY = 360;
901
+ const MOVE_THRESHOLD = 18;
902
+ const getPopoverKey = slot => slot ? `form:${slot.key}` : null;
903
+ const getSlotElementFromTouch = (container, touch) => {
904
+ const element = document.elementFromPoint(touch.clientX, touch.clientY);
905
+ const slotElement = element == null || element.closest == null ? void 0 : element.closest('[data-slot-index]');
906
+ if (!slotElement || !(container != null && container.contains(slotElement))) {
907
+ return null;
908
+ }
909
+ return slotElement;
910
+ };
911
+ const getTouchPoint = event => {
912
+ var _event$changedTouches, _event$touches;
913
+ return ((_event$changedTouches = event.changedTouches) == null ? void 0 : _event$changedTouches[0]) || ((_event$touches = event.touches) == null ? void 0 : _event$touches[0]);
914
+ };
915
+ const useScheduleSelectionGesture = ({
916
+ slots,
917
+ selectedSlots,
918
+ setSelectedSlots,
919
+ allowMultiSelect,
920
+ onSlotRangeChange,
921
+ onFreeSlotClick
922
+ }) => {
923
+ var _selectedSlots;
924
+ const [dragging, setDragging] = useState(false);
925
+ const [activePopoverKey, setActivePopoverKey] = useState(null);
926
+ const slotsRef = useRef(null);
927
+ const draggingRef = useRef(false);
928
+ const dragStartIndexRef = useRef(null);
929
+ const longPressTimerRef = useRef(null);
930
+ const touchStateRef = useRef(null);
931
+ const slotsDataRef = useRef(slots);
932
+ const selectedSlotsRef = useRef(selectedSlots);
933
+ const suppressTriggerUntilRef = useRef(0);
934
+ slotsDataRef.current = slots;
935
+ selectedSlotsRef.current = selectedSlots;
936
+ const selectedKeys = useMemo(() => new Set(selectedSlots.map(slot => slot.key)), [selectedSlots]);
937
+ const selectedAnchorKey = (_selectedSlots = selectedSlots[selectedSlots.length - 1]) == null ? void 0 : _selectedSlots.key;
938
+ const formPopoverKey = selectedAnchorKey ? `form:${selectedAnchorKey}` : null;
939
+ const clearLongPress = useCallback(() => {
940
+ if (longPressTimerRef.current) {
941
+ window.clearTimeout(longPressTimerRef.current);
942
+ longPressTimerRef.current = null;
943
+ }
944
+ }, []);
945
+ const selectSlots = useCallback(nextSlots => {
946
+ selectedSlotsRef.current = nextSlots;
947
+ if (!nextSlots.length) {
948
+ setActivePopoverKey(null);
949
+ }
950
+ setSelectedSlots(nextSlots);
951
+ onSlotRangeChange == null || onSlotRangeChange(nextSlots, {
952
+ action: nextSlots.length ? 'select' : 'clear'
953
+ });
954
+ }, [onSlotRangeChange, setSelectedSlots]);
955
+ const getSelectableRange = useCallback((startIndex, endIndex) => {
956
+ const [start, end] = [Math.min(startIndex, endIndex), Math.max(startIndex, endIndex)];
957
+ return slotsDataRef.current.slice(start, end + 1).filter(slot => slot.status === 'free');
958
+ }, []);
959
+ const showFormPopoverForSlots = useCallback(slotsList => {
960
+ const anchorSlot = slotsList[slotsList.length - 1];
961
+ if (anchorSlot) {
962
+ setActivePopoverKey(getPopoverKey(anchorSlot));
963
+ }
964
+ }, []);
965
+ const startSlotSelection = useCallback((slot, index, {
966
+ showPopover: _showPopover = true
967
+ } = {}) => {
968
+ var _slotsRef$current;
969
+ setActivePopoverKey(getPopoverKey(slot));
970
+ draggingRef.current = true;
971
+ dragStartIndexRef.current = index;
972
+ (_slotsRef$current = slotsRef.current) == null || _slotsRef$current.classList.add('schedule-calendar-touch-dragging');
973
+ setDragging(true);
974
+ selectSlots([slot]);
975
+ if (!_showPopover) {
976
+ suppressTriggerUntilRef.current = Date.now() + 700;
977
+ setActivePopoverKey(null);
978
+ }
979
+ onFreeSlotClick == null || onFreeSlotClick(slot);
980
+ }, [onFreeSlotClick, selectSlots]);
981
+ const finishSelection = useCallback(({
982
+ fromTouch: _fromTouch = false
983
+ } = {}) => {
984
+ var _slotsRef$current2;
985
+ clearLongPress();
986
+ draggingRef.current = false;
987
+ dragStartIndexRef.current = null;
988
+ (_slotsRef$current2 = slotsRef.current) == null || _slotsRef$current2.classList.remove('schedule-calendar-touch-dragging');
989
+ setDragging(false);
990
+ if (selectedSlotsRef.current.length) {
991
+ if (_fromTouch) {
992
+ suppressTriggerUntilRef.current = Date.now() + 500;
993
+ }
994
+ showFormPopoverForSlots(selectedSlotsRef.current);
995
+ }
996
+ }, [clearLongPress, showFormPopoverForSlots]);
997
+ const updateSelectionRange = useCallback(index => {
998
+ const startIndex = dragStartIndexRef.current;
999
+ if (startIndex === null || !allowMultiSelect) {
1000
+ return;
1001
+ }
1002
+ const nextSlots = getSelectableRange(startIndex, index);
1003
+ selectSlots(nextSlots);
1004
+ }, [allowMultiSelect, getSelectableRange, selectSlots]);
1005
+ const handleMouseDown = useCallback((event, slot, index) => {
1006
+ if (slot.status !== 'free') {
1007
+ return;
1008
+ }
1009
+ event.preventDefault();
1010
+ startSlotSelection(slot, index);
1011
+ }, [startSlotSelection]);
1012
+ const handleMouseEnter = useCallback(index => {
1013
+ if (!draggingRef.current) {
1014
+ return;
1015
+ }
1016
+ updateSelectionRange(index);
1017
+ }, [updateSelectionRange]);
1018
+ const handlePopoverOpenChange = useCallback((open, currentPopoverKey, {
1019
+ isForm: _isForm = false
1020
+ } = {}) => {
1021
+ if (Date.now() < suppressTriggerUntilRef.current) {
1022
+ return;
1023
+ }
1024
+ if (!open && _isForm) {
1025
+ selectSlots([]);
1026
+ return;
1027
+ }
1028
+ setActivePopoverKey(prevKey => {
1029
+ if (open) {
1030
+ return currentPopoverKey;
1031
+ }
1032
+ return prevKey === currentPopoverKey ? null : prevKey;
1033
+ });
1034
+ }, [selectSlots]);
1035
+ useEffect(() => {
1036
+ const container = slotsRef.current;
1037
+ if (!container) {
1038
+ return undefined;
1039
+ }
1040
+ const clearTouchState = () => {
1041
+ clearLongPress();
1042
+ if (!draggingRef.current) {
1043
+ var _slotsRef$current3;
1044
+ (_slotsRef$current3 = slotsRef.current) == null || _slotsRef$current3.classList.remove('schedule-calendar-touch-dragging');
1045
+ }
1046
+ touchStateRef.current = null;
1047
+ };
1048
+ const handleTouchStart = event => {
1049
+ if (event.touches.length !== 1) {
1050
+ return;
1051
+ }
1052
+ const touch = event.touches[0];
1053
+ const slotElement = getSlotElementFromTouch(container, touch);
1054
+ if (!slotElement) {
1055
+ return;
1056
+ }
1057
+ const index = Number(slotElement.getAttribute('data-slot-index'));
1058
+ const slot = slotsDataRef.current[index];
1059
+ if (Number.isNaN(index) || (slot == null ? void 0 : slot.status) !== 'free') {
1060
+ return;
1061
+ }
1062
+ clearTouchState();
1063
+ touchStateRef.current = {
1064
+ identifier: touch.identifier,
1065
+ x: touch.clientX,
1066
+ y: touch.clientY,
1067
+ slot,
1068
+ index,
1069
+ moved: false,
1070
+ longPressed: false,
1071
+ active: false
1072
+ };
1073
+ longPressTimerRef.current = window.setTimeout(() => {
1074
+ var _slotsRef$current4;
1075
+ const touchState = touchStateRef.current;
1076
+ if (!touchState) {
1077
+ return;
1078
+ }
1079
+ touchState.longPressed = true;
1080
+ touchState.active = true;
1081
+ (_slotsRef$current4 = slotsRef.current) == null || _slotsRef$current4.classList.add('schedule-calendar-touch-dragging');
1082
+ startSlotSelection(slot, index, {
1083
+ showPopover: false
1084
+ });
1085
+ longPressTimerRef.current = null;
1086
+ }, LONG_PRESS_DELAY);
1087
+ };
1088
+ const handleTouchMove = event => {
1089
+ const touchState = touchStateRef.current;
1090
+ if (!touchState) {
1091
+ return;
1092
+ }
1093
+ const touch = Array.from(event.touches).find(item => item.identifier === touchState.identifier) || event.touches[0];
1094
+ if (!touch) {
1095
+ return;
1096
+ }
1097
+ if (!touchState.active) {
1098
+ const moveX = Math.abs(touch.clientX - touchState.x);
1099
+ const moveY = Math.abs(touch.clientY - touchState.y);
1100
+ if (moveX > MOVE_THRESHOLD || moveY > MOVE_THRESHOLD) {
1101
+ touchState.moved = true;
1102
+ clearTouchState();
1103
+ }
1104
+ return;
1105
+ }
1106
+ event.preventDefault();
1107
+ const slotElement = getSlotElementFromTouch(container, touch);
1108
+ if (!slotElement) {
1109
+ return;
1110
+ }
1111
+ const index = Number(slotElement.getAttribute('data-slot-index'));
1112
+ if (!Number.isNaN(index)) {
1113
+ updateSelectionRange(index);
1114
+ }
1115
+ };
1116
+ const handleTouchEnd = event => {
1117
+ const touchState = touchStateRef.current;
1118
+ if (!touchState) {
1119
+ return;
1120
+ }
1121
+ const touch = Array.from(event.changedTouches || []).find(item => item.identifier === touchState.identifier) || getTouchPoint(event);
1122
+ if (!touch) {
1123
+ clearTouchState();
1124
+ return;
1125
+ }
1126
+ if (touchState.active) {
1127
+ event.preventDefault();
1128
+ suppressTriggerUntilRef.current = Date.now() + 500;
1129
+ finishSelection({
1130
+ fromTouch: true
1131
+ });
1132
+ touchStateRef.current = null;
1133
+ return;
1134
+ }
1135
+ const quickTap = !touchState.longPressed && !touchState.moved;
1136
+ clearTouchState();
1137
+ if (quickTap) {
1138
+ suppressTriggerUntilRef.current = Date.now() + 500;
1139
+ selectSlots([touchState.slot]);
1140
+ showFormPopoverForSlots([touchState.slot]);
1141
+ onFreeSlotClick == null || onFreeSlotClick(touchState.slot);
1142
+ }
1143
+ };
1144
+ const handleContextMenu = event => {
1145
+ if (touchStateRef.current || draggingRef.current) {
1146
+ event.preventDefault();
1147
+ }
1148
+ };
1149
+ container.addEventListener('touchstart', handleTouchStart, {
1150
+ passive: true
1151
+ });
1152
+ container.addEventListener('touchmove', handleTouchMove, {
1153
+ passive: false
1154
+ });
1155
+ container.addEventListener('touchend', handleTouchEnd, {
1156
+ passive: false
1157
+ });
1158
+ container.addEventListener('touchcancel', handleTouchEnd, {
1159
+ passive: false
1160
+ });
1161
+ container.addEventListener('contextmenu', handleContextMenu);
1162
+ return () => {
1163
+ clearTouchState();
1164
+ container.removeEventListener('touchstart', handleTouchStart);
1165
+ container.removeEventListener('touchmove', handleTouchMove);
1166
+ container.removeEventListener('touchend', handleTouchEnd);
1167
+ container.removeEventListener('touchcancel', handleTouchEnd);
1168
+ container.removeEventListener('contextmenu', handleContextMenu);
1169
+ };
1170
+ }, [clearLongPress, finishSelection, onFreeSlotClick, selectSlots, showFormPopoverForSlots, startSlotSelection, updateSelectionRange]);
1171
+ return {
1172
+ activePopoverKey,
1173
+ dragging,
1174
+ formPopoverKey,
1175
+ selectedAnchorKey,
1176
+ selectedKeys,
1177
+ setActivePopoverKey,
1178
+ selectSlots,
1179
+ slotsRef,
1180
+ finishSelection,
1181
+ handleMouseDown,
1182
+ handleMouseEnter,
1183
+ handlePopoverOpenChange
1184
+ };
1185
+ };
1186
+
1187
+ const DefaultFormInner = () => {
1188
+ const {
1189
+ formatMessage
1190
+ } = useIntl();
1191
+ return /*#__PURE__*/jsx(FormInfo, {
1192
+ column: 1,
1193
+ list: [/*#__PURE__*/jsx(Input, {
1194
+ name: "title",
1195
+ label: formatMessage({
1196
+ id: 'DefaultFormInner.title'
1197
+ }),
1198
+ rule: "REQ",
1199
+ placeholder: formatMessage({
1200
+ id: 'DefaultFormInner.titlePlaceholder'
1201
+ }),
1202
+ block: true
1203
+ }), /*#__PURE__*/jsx(TextArea, {
1204
+ name: "detail",
1205
+ label: formatMessage({
1206
+ id: 'DefaultFormInner.detail'
1207
+ }),
1208
+ placeholder: formatMessage({
1209
+ id: 'DefaultFormInner.detailPlaceholder'
1210
+ }),
1211
+ block: true
1212
+ })]
1213
+ });
1214
+ };
1215
+
1216
+ 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"};
1217
+
1218
+ const SlotEvents = ({
1219
+ slot,
1220
+ date,
1221
+ onEventClick
1222
+ }) => /*#__PURE__*/jsx("div", {
1223
+ className: style$1['slot-popover-content'],
1224
+ children: /*#__PURE__*/jsx(List, {
1225
+ size: "small",
1226
+ dataSource: slot.events,
1227
+ renderItem: event => /*#__PURE__*/jsx(EventItem, {
1228
+ event: event,
1229
+ date: date,
1230
+ mode: "schedule",
1231
+ onEventClick: onEventClick
1232
+ })
1233
+ })
1234
+ });
1235
+
1236
+ const SlotItem = ({
1237
+ slot,
1238
+ date,
1239
+ selected,
1240
+ formPopover,
1241
+ popoverOpen,
1242
+ onPopoverOpenChange,
1243
+ onContextMenu,
1244
+ onMouseDown,
1245
+ onPointerDown,
1246
+ onMouseEnter,
1247
+ onMouseUp,
1248
+ onClick,
1249
+ onEventClick,
1250
+ renderTimeSlot,
1251
+ renderOccupiedSlot
1252
+ }) => {
1253
+ const {
1254
+ formatMessage
1255
+ } = useIntl();
1256
+ const content = renderTimeSlot == null ? void 0 : renderTimeSlot(slot, {
1257
+ selected
1258
+ });
1259
+ if (content) {
1260
+ const customNode = /*#__PURE__*/jsx("div", {
1261
+ "data-slot-index": slot.index,
1262
+ onContextMenu: onContextMenu,
1263
+ onMouseDown: onMouseDown,
1264
+ onPointerDown: onPointerDown,
1265
+ onMouseEnter: onMouseEnter,
1266
+ onMouseUp: onMouseUp,
1267
+ onClick: onClick,
1268
+ children: content
1269
+ });
1270
+ if (formPopover) {
1271
+ return /*#__PURE__*/jsx(Popover, {
1272
+ rootClassName: style$1['schedule-popover'],
1273
+ open: popoverOpen,
1274
+ onOpenChange: onPopoverOpenChange,
1275
+ content: formPopover,
1276
+ trigger: "click",
1277
+ placement: "bottom",
1278
+ autoAdjustOverflow: true,
1279
+ children: customNode
1280
+ });
1281
+ }
1282
+ return customNode;
1283
+ }
1284
+ const wrapWithPopover = node => {
1285
+ if (formPopover) {
1286
+ return /*#__PURE__*/jsx(Popover, {
1287
+ rootClassName: style$1['schedule-popover'],
1288
+ open: popoverOpen,
1289
+ onOpenChange: onPopoverOpenChange,
1290
+ content: formPopover,
1291
+ trigger: "click",
1292
+ placement: "bottom",
1293
+ autoAdjustOverflow: true,
1294
+ children: node
1295
+ });
1296
+ }
1297
+ if (slot.status === 'occupied') {
1298
+ return /*#__PURE__*/jsx(Popover, {
1299
+ rootClassName: style$1['schedule-popover'],
1300
+ open: popoverOpen,
1301
+ onOpenChange: onPopoverOpenChange,
1302
+ content: /*#__PURE__*/jsx(SlotEvents, {
1303
+ slot: slot,
1304
+ date: date,
1305
+ onEventClick: onEventClick
1306
+ }),
1307
+ trigger: "click",
1308
+ placement: "bottom",
1309
+ autoAdjustOverflow: true,
1310
+ children: node
1311
+ });
1312
+ }
1313
+ if (slot.disabledReason) {
1314
+ return /*#__PURE__*/jsx(Tooltip, {
1315
+ title: slot.disabledReason,
1316
+ children: node
1317
+ });
1318
+ }
1319
+ return node;
1320
+ };
1321
+ const inner = /*#__PURE__*/jsxs("button", {
1322
+ type: "button",
1323
+ className: classNames(style$1['slot-item'], style$1[`slot-${slot.status}`], {
1324
+ [style$1['slot-selected']]: selected
1325
+ }),
1326
+ "data-slot-index": slot.index,
1327
+ disabled: slot.status === 'disabled',
1328
+ onContextMenu: onContextMenu,
1329
+ onMouseDown: onMouseDown,
1330
+ onPointerDown: onPointerDown,
1331
+ onMouseEnter: onMouseEnter,
1332
+ onMouseUp: onMouseUp,
1333
+ onClick: onClick,
1334
+ children: [/*#__PURE__*/jsx("span", {
1335
+ className: style$1['slot-dot']
1336
+ }), /*#__PURE__*/jsx("span", {
1337
+ className: style$1['slot-time'],
1338
+ children: formatRange(slot.start, slot.end)
1339
+ }), slot.status === 'disabled' ? /*#__PURE__*/jsx(Tag, {
1340
+ className: style$1['slot-tag'],
1341
+ children: formatMessage({
1342
+ id: 'SlotItem.disabled'
1343
+ })
1344
+ }) : null, slot.status === 'occupied' ? /*#__PURE__*/jsx(Tag, {
1345
+ className: style$1['slot-tag'],
1346
+ color: "red",
1347
+ children: formatMessage({
1348
+ id: 'SlotItem.occupied'
1349
+ })
1350
+ }) : null, slot.status === 'occupied' && renderOccupiedSlot ? renderOccupiedSlot(slot, {
1351
+ events: slot.events
1352
+ }) : null, slot.status === 'occupied' && !renderOccupiedSlot ? /*#__PURE__*/jsx("span", {
1353
+ className: style$1['slot-title'],
1354
+ children: slot.events.map(event => event.title).join('、')
1355
+ }) : null]
1356
+ });
1357
+ return wrapWithPopover(inner);
1358
+ };
1359
+
1360
+ const SchedulePanel = ({
1361
+ date,
1362
+ slots,
1363
+ loading,
1364
+ allowMultiSelect,
1365
+ selectedSlots,
1366
+ setSelectedSlots,
1367
+ onSlotRangeChange,
1368
+ onFreeSlotClick,
1369
+ onCreate,
1370
+ formInner,
1371
+ formProps,
1372
+ defaultFormValues,
1373
+ renderTimeSlot,
1374
+ renderOccupiedSlot,
1375
+ renderPanelHeader,
1376
+ onEventClick
1377
+ }) => {
1378
+ const {
1379
+ formatMessage
1380
+ } = useIntl();
1381
+ const events = useMemo(() => sortEventsByStart(dedupeEventsById(slots.flatMap(slot => slot.events || []))), [slots]);
1382
+ const panelHeader = renderPanelHeader == null ? void 0 : renderPanelHeader({
1383
+ date: date.toDate(),
1384
+ mode: 'schedule',
1385
+ events
1386
+ });
1387
+ const {
1388
+ activePopoverKey,
1389
+ dragging,
1390
+ formPopoverKey,
1391
+ selectedAnchorKey,
1392
+ selectedKeys,
1393
+ selectSlots,
1394
+ slotsRef,
1395
+ finishSelection,
1396
+ handleMouseDown,
1397
+ handleMouseEnter,
1398
+ handlePopoverOpenChange
1399
+ } = useScheduleSelectionGesture({
1400
+ slots,
1401
+ selectedSlots,
1402
+ setSelectedSlots,
1403
+ allowMultiSelect,
1404
+ onSlotRangeChange,
1405
+ onFreeSlotClick
1406
+ });
1407
+ const {
1408
+ scrolled,
1409
+ onScroll
1410
+ } = usePanelScrollShadow(date.format(DATE_FORMAT), slotsRef);
1411
+ const formContext = {
1412
+ date: date.toDate(),
1413
+ slots: selectedSlots
1414
+ };
1415
+ const formPopover = selectedSlots.length && !dragging ? /*#__PURE__*/jsxs("div", {
1416
+ className: style$1['form-popover'],
1417
+ children: [/*#__PURE__*/jsx("div", {
1418
+ className: style$1['form-popover-title'],
1419
+ children: formatMessage({
1420
+ id: 'SchedulePanel.newSchedule'
1421
+ }, {
1422
+ range: formatRange(selectedSlots[0].start, selectedSlots[selectedSlots.length - 1].end)
1423
+ })
1424
+ }), /*#__PURE__*/jsxs(Form, _extends({}, formProps, {
1425
+ data: getDefaultFormValues(defaultFormValues, formContext),
1426
+ onSubmit: async values => {
1427
+ try {
1428
+ await (onCreate == null ? void 0 : onCreate(values, formContext));
1429
+ selectSlots([]);
1430
+ } catch (_unused) {
1431
+ // Keep the form open when creation fails.
1432
+ }
1433
+ },
1434
+ children: [formInner || /*#__PURE__*/jsx(DefaultFormInner, {}), /*#__PURE__*/jsxs("div", {
1435
+ className: style$1['form-actions'],
1436
+ children: [/*#__PURE__*/jsx(Button, {
1437
+ onClick: () => selectSlots([]),
1438
+ children: formatMessage({
1439
+ id: 'SchedulePanel.cancel'
1440
+ })
1441
+ }), /*#__PURE__*/jsx(SubmitButton, {
1442
+ children: formatMessage({
1443
+ id: 'SchedulePanel.save'
1444
+ })
1445
+ })]
1446
+ })]
1447
+ }))]
1448
+ }) : null;
1449
+ return /*#__PURE__*/jsx(Card, {
1450
+ bordered: false,
1451
+ title: panelHeader || /*#__PURE__*/jsx(PanelTitle, {
1452
+ date: date,
1453
+ subtitle: formatMessage({
1454
+ id: 'SchedulePanel.selectFreeSlot'
1455
+ })
1456
+ }),
1457
+ className: panelStyle['panel-card'],
1458
+ children: /*#__PURE__*/jsx(Spin, {
1459
+ spinning: !!loading,
1460
+ classNames: {
1461
+ root: panelStyle['panel-spin'],
1462
+ container: panelStyle['panel-spin-container']
1463
+ },
1464
+ children: /*#__PURE__*/jsxs("div", {
1465
+ ref: slotsRef,
1466
+ className: classNames(style$1['slots'], {
1467
+ [style$1['slots-dragging']]: dragging
1468
+ }),
1469
+ onScroll: onScroll,
1470
+ onMouseUp: () => finishSelection(),
1471
+ onMouseLeave: () => finishSelection(),
1472
+ children: [/*#__PURE__*/jsx(PanelScrollShadow, {
1473
+ visible: scrolled
1474
+ }), /*#__PURE__*/jsx("div", {
1475
+ className: style$1['slots-content'],
1476
+ children: slots.map((slot, index) => {
1477
+ const slotWithIndex = _extends({}, slot, {
1478
+ index
1479
+ });
1480
+ const occupiedPopoverKey = `occupied:${slot.key}`;
1481
+ const currentPopoverKey = slot.key === selectedAnchorKey && formPopover ? formPopoverKey : slot.status === 'occupied' ? occupiedPopoverKey : null;
1482
+ return /*#__PURE__*/jsx(SlotItem, {
1483
+ slot: slotWithIndex,
1484
+ date: date.toDate(),
1485
+ selected: selectedKeys.has(slot.key),
1486
+ formPopover: slot.key === selectedAnchorKey ? formPopover : null,
1487
+ popoverOpen: !!currentPopoverKey && activePopoverKey === currentPopoverKey,
1488
+ onPopoverOpenChange: open => handlePopoverOpenChange(open, currentPopoverKey, {
1489
+ isForm: currentPopoverKey === formPopoverKey
1490
+ }),
1491
+ renderTimeSlot: renderTimeSlot,
1492
+ renderOccupiedSlot: renderOccupiedSlot,
1493
+ onEventClick: onEventClick,
1494
+ onContextMenu: event => {
1495
+ if (slot.status === 'free') {
1496
+ event.preventDefault();
1497
+ }
1498
+ },
1499
+ onMouseDown: event => handleMouseDown(event, slot, index),
1500
+ onMouseEnter: () => handleMouseEnter(index),
1501
+ onMouseUp: () => finishSelection(),
1502
+ onClick: undefined
1503
+ }, slot.key);
1504
+ })
1505
+ })]
1506
+ })
1507
+ })
1508
+ });
1509
+ };
1510
+
1511
+ var style = {"schedule-calendar-shell":"kne-react-calendar_csu63","schedule-calendar":"kne-react-calendar_5p1TK","calendar-side":"kne-react-calendar_nd7DK","panel-side":"kne-react-calendar_rWvHE","schedule-calendar-stacked":"kne-react-calendar_zGXr5"};
1512
+
1513
+ dayjs.extend(customParseFormat);
1514
+ const buildMonthRuleCache = ({
1515
+ currentDate,
1516
+ mode,
1517
+ disabledDate,
1518
+ disabledDateTime,
1519
+ availableDateTime
1520
+ }) => {
1521
+ const cache = new Map();
1522
+ const monthStart = currentDate.startOf('month');
1523
+ const monthEnd = currentDate.endOf('month');
1524
+ let cursor = monthStart;
1525
+ while (cursor.isBefore(monthEnd) || cursor.isSame(monthEnd, 'day')) {
1526
+ cache.set(cursor.format(DATE_FORMAT), checkRule({
1527
+ date: cursor,
1528
+ mode,
1529
+ disabledDate,
1530
+ disabledDateTime,
1531
+ availableDateTime
1532
+ }));
1533
+ cursor = cursor.add(1, 'day');
1534
+ }
1535
+ return cache;
1536
+ };
1537
+ const ScheduleCalendarInner = props => {
1538
+ const {
1539
+ mode = 'view',
1540
+ events = [],
1541
+ loading,
1542
+ disabledDate,
1543
+ disabledDateTime,
1544
+ availableDateTime,
1545
+ badgeMaxCount = 9,
1546
+ timeUnit = 30,
1547
+ dayStart = '09:00',
1548
+ dayEnd = '18:00',
1549
+ allowMultiSelect = mode === 'schedule',
1550
+ renderDateCell,
1551
+ renderEventItem,
1552
+ renderTimeSlot,
1553
+ renderOccupiedSlot,
1554
+ renderPanelHeader,
1555
+ formInner,
1556
+ formProps,
1557
+ defaultFormValues,
1558
+ onDateChange,
1559
+ onEventClick,
1560
+ onFreeSlotClick,
1561
+ onSlotRangeChange,
1562
+ onCreate,
1563
+ className,
1564
+ style: propsStyle,
1565
+ stackAt = LAYOUT_STACK_BREAKPOINT,
1566
+ calendarProps,
1567
+ panelProps
1568
+ } = props;
1569
+ const rootRef = useRef(null);
1570
+ const calendarRef = useRef(null);
1571
+ const stacked = useStackedLayout(rootRef, stackAt);
1572
+ const stackedLayoutStyle = useStackedPanelLayout(rootRef, calendarRef, stacked);
1573
+ const [value, setValue] = useControlValue(props);
1574
+ const selectedDate = useMemo(() => toDayjs(value) || dayjs(), [value]);
1575
+ const [current, setCurrent] = useControlledState({
1576
+ value: props.current,
1577
+ defaultValue: props.defaultCurrent || selectedDate,
1578
+ onChange: props.onCurrentChange
1579
+ });
1580
+ const currentDate = useMemo(() => toDayjs(current) || selectedDate, [current, selectedDate]);
1581
+ const [selectedSlots, setSelectedSlots] = useState([]);
1582
+ const eventGroup = useMemo(() => groupEventsByDate(events), [events]);
1583
+ const selectedEvents = useMemo(() => eventGroup[selectedDate.format(DATE_FORMAT)] || [], [eventGroup, selectedDate]);
1584
+ const ruleContext = useMemo(() => ({
1585
+ mode,
1586
+ disabledDate,
1587
+ disabledDateTime,
1588
+ availableDateTime
1589
+ }), [availableDateTime, disabledDate, disabledDateTime, mode]);
1590
+ const monthRuleCache = useMemo(() => buildMonthRuleCache(_extends({
1591
+ currentDate
1592
+ }, ruleContext)), [currentDate, ruleContext]);
1593
+ const getDateRule = useCallback(date => {
1594
+ const cached = monthRuleCache.get(date.format(DATE_FORMAT));
1595
+ if (cached !== undefined) {
1596
+ return cached;
1597
+ }
1598
+ return checkRule(_extends({
1599
+ date
1600
+ }, ruleContext));
1601
+ }, [monthRuleCache, ruleContext]);
1602
+ const slots = useMemo(() => generateTimeSlots({
1603
+ date: selectedDate,
1604
+ events: selectedEvents,
1605
+ timeUnit,
1606
+ dayStart,
1607
+ dayEnd,
1608
+ disabledDate,
1609
+ disabledDateTime,
1610
+ availableDateTime,
1611
+ mode
1612
+ }), [availableDateTime, dayEnd, dayStart, disabledDate, disabledDateTime, mode, selectedDate, selectedEvents, timeUnit]);
1613
+ const handleSelectDate = (date, info) => {
1614
+ const nextDate = date.startOf('day').toDate();
1615
+ setValue(nextDate, info);
1616
+ onDateChange == null || onDateChange(nextDate, info);
1617
+ setSelectedSlots([]);
1618
+ };
1619
+ const handleCurrentChange = (date, info) => {
1620
+ setCurrent(date.startOf('month').toDate(), info);
1621
+ };
1622
+ const handleGoToday = () => {
1623
+ const today = dayjs().startOf('day');
1624
+ if (getDateRule(today)) {
1625
+ return;
1626
+ }
1627
+ handleSelectDate(today, {
1628
+ source: 'today'
1629
+ });
1630
+ handleCurrentChange(today, {
1631
+ source: 'today'
1632
+ });
1633
+ };
1634
+ const fullCellRender = date => {
1635
+ const key = date.format(DATE_FORMAT);
1636
+ const dayEvents = eventGroup[key] || [];
1637
+ const disabled = !!getDateRule(date);
1638
+ return /*#__PURE__*/jsx(DateCell, {
1639
+ date: date,
1640
+ dayEvents: dayEvents,
1641
+ selected: date.isSame(selectedDate, 'day'),
1642
+ currentMonth: date.isSame(currentDate, 'month'),
1643
+ disabled: disabled,
1644
+ badgeMaxCount: badgeMaxCount,
1645
+ renderDateCell: renderDateCell
1646
+ });
1647
+ };
1648
+ return /*#__PURE__*/jsx("div", {
1649
+ className: style['schedule-calendar-shell'],
1650
+ style: propsStyle,
1651
+ children: /*#__PURE__*/jsxs("div", {
1652
+ ref: rootRef,
1653
+ className: classNames(style['schedule-calendar'], stacked && style['schedule-calendar-stacked'], className),
1654
+ "data-schedule-layout": stacked ? 'stacked' : 'row',
1655
+ style: stacked ? stackedLayoutStyle : undefined,
1656
+ children: [/*#__PURE__*/jsx("div", {
1657
+ ref: calendarRef,
1658
+ className: style['calendar-side'],
1659
+ children: /*#__PURE__*/jsx(Calendar, _extends({}, calendarProps, {
1660
+ fullscreen: false,
1661
+ value: currentDate,
1662
+ headerRender: () => {
1663
+ const today = dayjs().startOf('day');
1664
+ const todayDisabled = !!getDateRule(today);
1665
+ return /*#__PURE__*/jsx(Header, {
1666
+ current: currentDate,
1667
+ showTodayButton: !todayDisabled && (!selectedDate.isSame(today, 'day') || !currentDate.isSame(today, 'month')),
1668
+ onCurrentChange: handleCurrentChange,
1669
+ onSelectToday: handleGoToday
1670
+ });
1671
+ },
1672
+ fullCellRender: fullCellRender,
1673
+ disabledDate: date => !!getDateRule(date),
1674
+ onSelect: date => {
1675
+ if (getDateRule(date)) {
1676
+ return;
1677
+ }
1678
+ handleSelectDate(date, {
1679
+ source: 'date'
1680
+ });
1681
+ },
1682
+ onPanelChange: date => handleCurrentChange(date, {
1683
+ source: 'picker'
1684
+ })
1685
+ }))
1686
+ }), /*#__PURE__*/jsx("div", _extends({
1687
+ className: style['panel-side']
1688
+ }, panelProps, {
1689
+ children: mode === 'schedule' ? /*#__PURE__*/jsx(SchedulePanel, {
1690
+ date: selectedDate,
1691
+ slots: slots,
1692
+ loading: loading,
1693
+ allowMultiSelect: allowMultiSelect,
1694
+ selectedSlots: selectedSlots,
1695
+ setSelectedSlots: setSelectedSlots,
1696
+ onSlotRangeChange: onSlotRangeChange,
1697
+ onFreeSlotClick: onFreeSlotClick,
1698
+ onCreate: onCreate,
1699
+ formInner: formInner,
1700
+ formProps: formProps,
1701
+ defaultFormValues: defaultFormValues,
1702
+ renderTimeSlot: renderTimeSlot,
1703
+ renderOccupiedSlot: renderOccupiedSlot,
1704
+ renderPanelHeader: renderPanelHeader,
1705
+ onEventClick: onEventClick
1706
+ }) : /*#__PURE__*/jsx(ViewPanel, {
1707
+ date: selectedDate,
1708
+ events: selectedEvents,
1709
+ loading: loading,
1710
+ renderEventItem: renderEventItem,
1711
+ renderPanelHeader: renderPanelHeader,
1712
+ onEventClick: onEventClick
1713
+ })
1714
+ }))]
1715
+ })
1716
+ });
1717
+ };
1718
+ const ScheduleCalendar = withLocale(ScheduleCalendarInner);
1719
+
1720
+ export { ScheduleCalendar as ReactCalendar, ScheduleCalendar, dedupeEventsById, ScheduleCalendar as default, generateTimeSlots, groupEventsByDate, normalizeEvent, sortEventsByStart };
1721
+ //# sourceMappingURL=index.modern.js.map