@tugkanpilka/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.
- package/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/components/Calendar/Calendar.d.ts +12 -0
- package/dist/components/Calendar/Calendar.d.ts.map +1 -0
- package/dist/components/Calendar/Calendar.types.d.ts +33 -0
- package/dist/components/Calendar/Calendar.types.d.ts.map +1 -0
- package/dist/components/Day/Day.d.ts +7 -0
- package/dist/components/Day/Day.d.ts.map +1 -0
- package/dist/components/Day/Day.types.d.ts +40 -0
- package/dist/components/Day/Day.types.d.ts.map +1 -0
- package/dist/components/MonthHeader/MonthHeader.d.ts +12 -0
- package/dist/components/MonthHeader/MonthHeader.d.ts.map +1 -0
- package/dist/components/MonthHeader/MonthHeader.types.d.ts +33 -0
- package/dist/components/MonthHeader/MonthHeader.types.d.ts.map +1 -0
- package/dist/components/MonthList/MonthList.d.ts +12 -0
- package/dist/components/MonthList/MonthList.d.ts.map +1 -0
- package/dist/components/MonthList/MonthList.types.d.ts +31 -0
- package/dist/components/MonthList/MonthList.types.d.ts.map +1 -0
- package/dist/components/Week/Week.d.ts +12 -0
- package/dist/components/Week/Week.d.ts.map +1 -0
- package/dist/components/Week/Week.types.d.ts +35 -0
- package/dist/components/Week/Week.types.d.ts.map +1 -0
- package/dist/components/WeekdayHeader/WeekdayHeader.d.ts +16 -0
- package/dist/components/WeekdayHeader/WeekdayHeader.d.ts.map +1 -0
- package/dist/components/WeekdayHeader/WeekdayHeader.types.d.ts +16 -0
- package/dist/components/WeekdayHeader/WeekdayHeader.types.d.ts.map +1 -0
- package/dist/core/date/date-formatters.d.ts +65 -0
- package/dist/core/date/date-formatters.d.ts.map +1 -0
- package/dist/core/date/index.d.ts +6 -0
- package/dist/core/date/index.d.ts.map +1 -0
- package/dist/core/date/scroll-utils.d.ts +48 -0
- package/dist/core/date/scroll-utils.d.ts.map +1 -0
- package/dist/core/grid/calendar-grid.d.ts +45 -0
- package/dist/core/grid/calendar-grid.d.ts.map +1 -0
- package/dist/core/grid/index.d.ts +6 -0
- package/dist/core/grid/index.d.ts.map +1 -0
- package/dist/core/grid/virtualization.d.ts +38 -0
- package/dist/core/grid/virtualization.d.ts.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/types.d.ts +70 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useCalendarItems.d.ts +64 -0
- package/dist/hooks/useCalendarItems.d.ts.map +1 -0
- package/dist/hooks/useInfiniteScroll.d.ts +21 -0
- package/dist/hooks/useInfiniteScroll.d.ts.map +1 -0
- package/dist/hooks/useScrollToDate.d.ts +25 -0
- package/dist/hooks/useScrollToDate.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +667 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +701 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { memo, useRef, useEffect, Fragment, forwardRef, useCallback } from 'react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import styles from './WeekdayHeader.module.scss';
|
|
5
|
+
import styles$1 from './MonthList.module.scss';
|
|
6
|
+
import styles$2 from './Calendar.module.scss';
|
|
7
|
+
import styles$3 from './Day.module.scss';
|
|
8
|
+
import styles$4 from './Week.module.scss';
|
|
9
|
+
import styles$5 from './MonthHeader.module.scss';
|
|
10
|
+
import { format, isToday, isSameDay } from 'date-fns';
|
|
11
|
+
import { isWeekNumberDecoration } from 'date-range-utils';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default weekday names
|
|
15
|
+
*/
|
|
16
|
+
const WEEKDAY_NAMES = ['M', 'T', 'W', 'T', 'F', 'S', 'S', '#'];
|
|
17
|
+
/**
|
|
18
|
+
* WeekdayHeader component for calendar weekday names
|
|
19
|
+
* Supports render props for customizing weekday name display
|
|
20
|
+
*/
|
|
21
|
+
function WeekdayHeader(props) {
|
|
22
|
+
const { weekdayNames = WEEKDAY_NAMES, renderWeekdayName, className, ...rest } = props;
|
|
23
|
+
// Default render function
|
|
24
|
+
const defaultRenderWeekdayName = (name, index) => (jsx("div", { className: styles.weekdayHeader__item, children: name }, `${name}-${index}`));
|
|
25
|
+
return (jsx("div", { className: className, ...rest, children: jsx("div", { className: styles.weekdayHeader, children: weekdayNames.map((name, index) => renderWeekdayName
|
|
26
|
+
? renderWeekdayName(name, index)
|
|
27
|
+
: defaultRenderWeekdayName(name, index)) }) }));
|
|
28
|
+
}
|
|
29
|
+
var WeekdayHeader$1 = memo(WeekdayHeader);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Core grid utilities for Calendar component
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Default calendar grid configuration
|
|
36
|
+
*/
|
|
37
|
+
const DEFAULT_GRID_CONFIG = {
|
|
38
|
+
columns: 8, // 7 days + 1 week number
|
|
39
|
+
weekNumberColumn: 7, // Week number is in the last column
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Calculates the grid index from row and column
|
|
43
|
+
* @param row - Row index (0-based)
|
|
44
|
+
* @param col - Column index (0-based)
|
|
45
|
+
* @param columns - Number of columns in the grid
|
|
46
|
+
* @returns The calculated grid index
|
|
47
|
+
*/
|
|
48
|
+
function calculateGridIndex(row, col, columns = DEFAULT_GRID_CONFIG.columns) {
|
|
49
|
+
return row * columns + col;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Checks if a grid index represents a week number cell
|
|
53
|
+
* @param index - Grid index to check
|
|
54
|
+
* @param config - Grid configuration (optional, uses defaults)
|
|
55
|
+
* @returns True if the index represents a week number cell
|
|
56
|
+
*/
|
|
57
|
+
function isWeekNumberCell(index, config = DEFAULT_GRID_CONFIG) {
|
|
58
|
+
return index % config.columns === config.weekNumberColumn;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Calculates the row index from a grid index
|
|
62
|
+
* @param index - Grid index
|
|
63
|
+
* @param columns - Number of columns in the grid
|
|
64
|
+
* @returns The row index (0-based)
|
|
65
|
+
*/
|
|
66
|
+
function getRowFromIndex(index, columns = DEFAULT_GRID_CONFIG.columns) {
|
|
67
|
+
return Math.floor(index / columns);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Calculates the column index from a grid index
|
|
71
|
+
* @param index - Grid index
|
|
72
|
+
* @param columns - Number of columns in the grid
|
|
73
|
+
* @returns The column index (0-based)
|
|
74
|
+
*/
|
|
75
|
+
function getColumnFromIndex(index, columns = DEFAULT_GRID_CONFIG.columns) {
|
|
76
|
+
return index % columns;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Calculates the number of rows needed for a given number of items
|
|
80
|
+
* @param itemCount - Total number of items
|
|
81
|
+
* @param columns - Number of columns in the grid
|
|
82
|
+
* @returns The number of rows needed
|
|
83
|
+
*/
|
|
84
|
+
function calculateRowCount(itemCount, columns = DEFAULT_GRID_CONFIG.columns) {
|
|
85
|
+
return Math.ceil(itemCount / columns);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Virtual scrolling utilities for infinite scrolling Calendar
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
92
|
+
* Default viewport configuration
|
|
93
|
+
*/
|
|
94
|
+
const DEFAULT_VIEWPORT_CONFIG = {
|
|
95
|
+
itemHeight: 50, // Default item height in pixels
|
|
96
|
+
overscan: 2, // Render 2 extra items above and below
|
|
97
|
+
totalItems: 0,
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Calculates the visible range of items for virtual scrolling
|
|
101
|
+
* @param scrollTop - Current scroll position
|
|
102
|
+
* @param containerHeight - Height of the scrollable container
|
|
103
|
+
* @param config - Viewport configuration
|
|
104
|
+
* @returns The visible range of items
|
|
105
|
+
*/
|
|
106
|
+
function calculateVisibleRange(scrollTop, containerHeight, config = DEFAULT_VIEWPORT_CONFIG) {
|
|
107
|
+
const { itemHeight, overscan, totalItems } = config;
|
|
108
|
+
// Calculate visible range without overscan
|
|
109
|
+
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight));
|
|
110
|
+
const endIndex = Math.min(totalItems - 1, Math.ceil((scrollTop + containerHeight) / itemHeight));
|
|
111
|
+
// Apply overscan
|
|
112
|
+
const overscanStart = Math.max(0, startIndex - overscan);
|
|
113
|
+
const overscanEnd = Math.min(totalItems - 1, endIndex + overscan);
|
|
114
|
+
return {
|
|
115
|
+
startIndex: overscanStart,
|
|
116
|
+
endIndex: overscanEnd,
|
|
117
|
+
visibleCount: overscanEnd - overscanStart + 1,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Calculates the total height of all items for virtual scrolling
|
|
122
|
+
* @param itemCount - Total number of items
|
|
123
|
+
* @param itemHeight - Height of a single item
|
|
124
|
+
* @returns Total height in pixels
|
|
125
|
+
*/
|
|
126
|
+
function calculateTotalHeight(itemCount, itemHeight = DEFAULT_VIEWPORT_CONFIG.itemHeight) {
|
|
127
|
+
return itemCount * itemHeight;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Calculates the offset for a specific item index in virtual scrolling
|
|
131
|
+
* @param index - Item index
|
|
132
|
+
* @param itemHeight - Height of a single item
|
|
133
|
+
* @returns Offset in pixels
|
|
134
|
+
*/
|
|
135
|
+
function calculateItemOffset(index, itemHeight = DEFAULT_VIEWPORT_CONFIG.itemHeight) {
|
|
136
|
+
return index * itemHeight;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Checks if an item is within the visible range
|
|
140
|
+
* @param itemIndex - Index of the item to check
|
|
141
|
+
* @param range - Current viewport range
|
|
142
|
+
* @returns True if the item is visible
|
|
143
|
+
*/
|
|
144
|
+
function isItemVisible(itemIndex, range) {
|
|
145
|
+
return itemIndex >= range.startIndex && itemIndex <= range.endIndex;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Default month header renderer
|
|
150
|
+
*/
|
|
151
|
+
function defaultMonthHeader(name) {
|
|
152
|
+
return jsx("h2", { children: name });
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* MonthList component for infinite scrolling calendar
|
|
156
|
+
* Handles grid layout and infinite scroll support
|
|
157
|
+
*/
|
|
158
|
+
function MonthList(props) {
|
|
159
|
+
const { months, renderDay, renderWeekNumber, renderMonthHeader = defaultMonthHeader, showWeekNumbers = true, onLoadMore, hasMore = false, isLoading = false, scrollRef, className, ...rest } = props;
|
|
160
|
+
const sentinelRef = useRef(null);
|
|
161
|
+
// Infinite scroll with IntersectionObserver
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!onLoadMore || !hasMore || isLoading)
|
|
164
|
+
return;
|
|
165
|
+
const sentinel = sentinelRef.current;
|
|
166
|
+
if (!sentinel)
|
|
167
|
+
return;
|
|
168
|
+
const observer = new IntersectionObserver(async (entries) => {
|
|
169
|
+
const entry = entries[0];
|
|
170
|
+
if (entry?.isIntersecting && !isLoading) {
|
|
171
|
+
await onLoadMore();
|
|
172
|
+
}
|
|
173
|
+
}, {
|
|
174
|
+
root: scrollRef?.current || null,
|
|
175
|
+
rootMargin: '10px',
|
|
176
|
+
threshold: 0,
|
|
177
|
+
});
|
|
178
|
+
observer.observe(sentinel);
|
|
179
|
+
return () => observer.disconnect();
|
|
180
|
+
}, [onLoadMore, hasMore, isLoading, scrollRef]);
|
|
181
|
+
/**
|
|
182
|
+
* Renders a single calendar item (day or week number)
|
|
183
|
+
*/
|
|
184
|
+
const renderItem = (item, index, sectionIndex) => {
|
|
185
|
+
if (typeof index === 'number' && showWeekNumbers && isWeekNumberCell(index)) {
|
|
186
|
+
return renderWeekNumber?.(item, index, sectionIndex) ?? null;
|
|
187
|
+
}
|
|
188
|
+
return renderDay(item, index, sectionIndex);
|
|
189
|
+
};
|
|
190
|
+
return (jsxs("div", { className: className, ...rest, children: [months.map((month, sectionIndex) => (jsx(Fragment, { children: jsxs("div", { className: styles$1.monthList__month, children: [jsx("div", { className: styles$1.monthList__monthHeader, children: renderMonthHeader(month.title) }), jsx("div", { className: styles$1.monthList__monthDays, children: month.data.map((item, index) => {
|
|
191
|
+
const globalIndex = months
|
|
192
|
+
.slice(0, sectionIndex)
|
|
193
|
+
.reduce((sum, m) => sum + m.data.length, 0) + index;
|
|
194
|
+
return (jsx(Fragment, { children: renderItem(item, globalIndex, sectionIndex) }, `${item.date.toISOString()}-${globalIndex}`));
|
|
195
|
+
}) })] }) }, `month-${sectionIndex}-${month.title}`))), onLoadMore && (jsx("div", { ref: sentinelRef, className: styles$1.monthList__sentinel, children: isLoading && (jsx("div", { className: styles$1.monthList__loader, children: "Loading..." })) }))] }));
|
|
196
|
+
}
|
|
197
|
+
var MonthList$1 = memo(MonthList);
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Calendar component - Main orchestrator
|
|
201
|
+
* Supports infinite scrolling and customizable rendering
|
|
202
|
+
*/
|
|
203
|
+
function Calendar(props) {
|
|
204
|
+
const { className, months, renderDay, renderWeekNumber, renderMonthHeader, showWeekNumbers = true, onLoadMore, hasMore, isLoading, headerClassName, bodyClassName, ...rest } = props;
|
|
205
|
+
const scrollRef = useRef(null);
|
|
206
|
+
const rootClassName = classNames(styles$2.calendar, className);
|
|
207
|
+
const headerClassNames = classNames(styles$2.calendar__header, headerClassName);
|
|
208
|
+
const bodyClassNames = classNames(styles$2.calendar__scrollableBody, bodyClassName);
|
|
209
|
+
return (jsxs("div", { className: rootClassName, ...rest, children: [jsx("div", { className: headerClassNames, children: jsx(WeekdayHeader$1, {}) }), jsx("div", { ref: scrollRef, className: bodyClassNames, children: jsx(MonthList$1, { months: months, renderDay: renderDay, renderWeekNumber: renderWeekNumber, renderMonthHeader: renderMonthHeader, showWeekNumbers: showWeekNumbers, onLoadMore: onLoadMore, hasMore: hasMore, isLoading: isLoading, scrollRef: scrollRef }) })] }));
|
|
210
|
+
}
|
|
211
|
+
var Calendar$1 = memo(Calendar);
|
|
212
|
+
|
|
213
|
+
const DAY_NAMES = [
|
|
214
|
+
'sunday',
|
|
215
|
+
'monday',
|
|
216
|
+
'tuesday',
|
|
217
|
+
'wednesday',
|
|
218
|
+
'thursday',
|
|
219
|
+
'friday',
|
|
220
|
+
'saturday',
|
|
221
|
+
];
|
|
222
|
+
/**
|
|
223
|
+
* Day component for calendar day cells
|
|
224
|
+
* Supports render props for customizing date display and indicator
|
|
225
|
+
*/
|
|
226
|
+
const Day = forwardRef(function Day(props, ref) {
|
|
227
|
+
const { dateInfo, onDateClick, isMarked = false, isRounded = false, isSelected = false, isDotted = false, dotType = 'primary', onClick, renderDate, renderIndicator, className, ...rest } = props;
|
|
228
|
+
const dayName = DAY_NAMES[dateInfo.date.getDay()];
|
|
229
|
+
const rootClassName = classNames(styles$3.day, className, {
|
|
230
|
+
[styles$3['--rounded']]: isRounded,
|
|
231
|
+
[styles$3[`--${dayName}`]]: true,
|
|
232
|
+
});
|
|
233
|
+
const dateClassName = classNames(styles$3.day__date, {
|
|
234
|
+
[styles$3['--marked']]: isMarked,
|
|
235
|
+
[styles$3['--selected']]: isSelected,
|
|
236
|
+
});
|
|
237
|
+
const handleClick = () => {
|
|
238
|
+
onDateClick(dateInfo);
|
|
239
|
+
onClick?.();
|
|
240
|
+
};
|
|
241
|
+
// Default render props
|
|
242
|
+
const defaultRenderDate = () => (jsx("span", { className: styles$3.day__dateText, children: dateInfo.date.getDate() }));
|
|
243
|
+
const defaultRenderIndicator = () => {
|
|
244
|
+
if (!isDotted)
|
|
245
|
+
return null;
|
|
246
|
+
return (jsx("div", { className: classNames(styles$3.day__indicator, {
|
|
247
|
+
[styles$3['--primary']]: dotType === 'primary',
|
|
248
|
+
[styles$3['--secondary']]: dotType === 'secondary',
|
|
249
|
+
}) }));
|
|
250
|
+
};
|
|
251
|
+
return (jsxs("div", { ref: ref, className: rootClassName, onClick: handleClick, "data-date": dateInfo.date.toISOString().split('T')[0], ...rest, children: [jsx("div", { className: dateClassName, children: renderDate ? renderDate(dateInfo.date.getDate()) : defaultRenderDate() }), renderIndicator
|
|
252
|
+
? renderIndicator({ isDotted, dotType })
|
|
253
|
+
: defaultRenderIndicator()] }));
|
|
254
|
+
});
|
|
255
|
+
var Day$1 = memo(Day, (prevProps, nextProps) => prevProps.isSelected === nextProps.isSelected &&
|
|
256
|
+
prevProps.isMarked === nextProps.isMarked &&
|
|
257
|
+
prevProps.isRounded === nextProps.isRounded &&
|
|
258
|
+
prevProps.isDotted === nextProps.isDotted &&
|
|
259
|
+
prevProps.dotType === nextProps.dotType &&
|
|
260
|
+
prevProps.dateInfo.date.getTime() === nextProps.dateInfo.date.getTime());
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Week component for calendar week numbers
|
|
264
|
+
* Supports render props for customizing text display and indicator
|
|
265
|
+
*/
|
|
266
|
+
function Week(props) {
|
|
267
|
+
const { text, isMultiMonth = false, onClick, isDotted = false, isFocused = false, dotType = 'primary', renderText, renderIndicator, className, ...rest } = props;
|
|
268
|
+
const rootClassName = classNames(styles$4.week, className, {
|
|
269
|
+
[styles$4['--multi-month']]: isMultiMonth,
|
|
270
|
+
[styles$4['--focused']]: isFocused,
|
|
271
|
+
});
|
|
272
|
+
// Default render props
|
|
273
|
+
const defaultRenderText = () => (jsx("span", { className: styles$4.week__text, children: text }));
|
|
274
|
+
const defaultRenderIndicator = () => {
|
|
275
|
+
if (!isDotted)
|
|
276
|
+
return null;
|
|
277
|
+
return (jsx("div", { className: classNames(styles$4.week__indicator, {
|
|
278
|
+
[styles$4['--primary']]: dotType === 'primary',
|
|
279
|
+
[styles$4['--secondary']]: dotType === 'secondary',
|
|
280
|
+
}) }));
|
|
281
|
+
};
|
|
282
|
+
return (jsxs("div", { className: rootClassName, onClick: onClick, ...rest, children: [renderText ? renderText(text) : defaultRenderText(), renderIndicator
|
|
283
|
+
? renderIndicator({ isDotted, dotType })
|
|
284
|
+
: defaultRenderIndicator()] }));
|
|
285
|
+
}
|
|
286
|
+
var Week$1 = memo(Week);
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* MonthHeader component for calendar month headers
|
|
290
|
+
* Supports render props for customizing text display and indicator
|
|
291
|
+
*/
|
|
292
|
+
function MonthHeader(props) {
|
|
293
|
+
const { text, onClick, isFocused = false, isDotted = false, dotType = 'primary', renderText, renderIndicator, className, ...rest } = props;
|
|
294
|
+
const rootClassName = classNames(styles$5.monthHeader, className, {
|
|
295
|
+
[styles$5['--focused']]: isFocused,
|
|
296
|
+
});
|
|
297
|
+
// Default render props
|
|
298
|
+
const defaultRenderText = () => (jsx("h3", { className: styles$5.monthHeader__text, children: text }));
|
|
299
|
+
const defaultRenderIndicator = () => {
|
|
300
|
+
if (!isDotted)
|
|
301
|
+
return null;
|
|
302
|
+
return (jsx("div", { className: classNames(styles$5.monthHeader__indicator, {
|
|
303
|
+
[styles$5['--primary']]: dotType === 'primary',
|
|
304
|
+
[styles$5['--secondary']]: dotType === 'secondary',
|
|
305
|
+
}) }));
|
|
306
|
+
};
|
|
307
|
+
return (jsxs("div", { className: rootClassName, onClick: onClick, ...rest, children: [renderText ? renderText(text) : defaultRenderText(), renderIndicator
|
|
308
|
+
? renderIndicator({ isDotted, dotType })
|
|
309
|
+
: defaultRenderIndicator()] }));
|
|
310
|
+
}
|
|
311
|
+
var MonthHeader$1 = memo(MonthHeader);
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Pure date formatting utilities for Calendar component
|
|
315
|
+
*/
|
|
316
|
+
/**
|
|
317
|
+
* Month name to number mapping (0-11)
|
|
318
|
+
*/
|
|
319
|
+
const MONTH_NUMBERS = {
|
|
320
|
+
JANUARY: 0,
|
|
321
|
+
FEBRUARY: 1,
|
|
322
|
+
MARCH: 2,
|
|
323
|
+
APRIL: 3,
|
|
324
|
+
MAY: 4,
|
|
325
|
+
JUNE: 5,
|
|
326
|
+
JULY: 6,
|
|
327
|
+
AUGUST: 7,
|
|
328
|
+
SEPTEMBER: 8,
|
|
329
|
+
OCTOBER: 9,
|
|
330
|
+
NOVEMBER: 10,
|
|
331
|
+
DECEMBER: 11,
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Date format strings
|
|
335
|
+
*/
|
|
336
|
+
const DATE_FORMATS = {
|
|
337
|
+
DAY: 'yyyy-MM-dd',
|
|
338
|
+
WEEK: (year, weekNumber) => `${year}-W${weekNumber}`,
|
|
339
|
+
MONTH: (year, monthNumber) => `${year}-M${monthNumber}`,
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Calendar date formatter utilities
|
|
343
|
+
*/
|
|
344
|
+
const formatCalendarDate = {
|
|
345
|
+
/**
|
|
346
|
+
* Formats a date as a day string (yyyy-MM-dd)
|
|
347
|
+
* @param date - Date to format
|
|
348
|
+
* @returns Formatted date string
|
|
349
|
+
*/
|
|
350
|
+
day: (date) => {
|
|
351
|
+
return format(date, DATE_FORMATS.DAY);
|
|
352
|
+
},
|
|
353
|
+
/**
|
|
354
|
+
* Formats a date as a week string (yyyy-Www)
|
|
355
|
+
* @param date - Date to format
|
|
356
|
+
* @param weekNumber - Week number
|
|
357
|
+
* @returns Formatted week string
|
|
358
|
+
*/
|
|
359
|
+
week: (date, weekNumber) => {
|
|
360
|
+
return DATE_FORMATS.WEEK(date.getFullYear(), weekNumber);
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* Formats a month as a string (yyyy-Mmm)
|
|
364
|
+
* @param year - Year
|
|
365
|
+
* @param monthName - Month name (e.g., "January")
|
|
366
|
+
* @returns Formatted month string
|
|
367
|
+
*/
|
|
368
|
+
month: (year, monthName) => {
|
|
369
|
+
const monthNumber = MONTH_NUMBERS[monthName.toUpperCase()] ?? 0;
|
|
370
|
+
return DATE_FORMATS.MONTH(year, monthNumber + 1);
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* Parses a formatted day string back to a Date
|
|
375
|
+
* @param dateString - Date string in format yyyy-MM-dd
|
|
376
|
+
* @returns Parsed Date object
|
|
377
|
+
*/
|
|
378
|
+
function parseDayString(dateString) {
|
|
379
|
+
return new Date(dateString);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Parses a formatted week string back to year and week number
|
|
383
|
+
* @param weekString - Week string in format yyyy-Www
|
|
384
|
+
* @returns Object with year and weekNumber
|
|
385
|
+
*/
|
|
386
|
+
function parseWeekString(weekString) {
|
|
387
|
+
const match = weekString.match(/^(\d{4})-W(\d+)$/);
|
|
388
|
+
if (!match) {
|
|
389
|
+
throw new Error(`Invalid week string format: ${weekString}`);
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
year: parseInt(match[1], 10),
|
|
393
|
+
weekNumber: parseInt(match[2], 10),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Parses a formatted month string back to year and month number
|
|
398
|
+
* @param monthString - Month string in format yyyy-Mmm
|
|
399
|
+
* @returns Object with year and monthNumber (1-12)
|
|
400
|
+
*/
|
|
401
|
+
function parseMonthString(monthString) {
|
|
402
|
+
const match = monthString.match(/^(\d{4})-M(\d+)$/);
|
|
403
|
+
if (!match) {
|
|
404
|
+
throw new Error(`Invalid month string format: ${monthString}`);
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
year: parseInt(match[1], 10),
|
|
408
|
+
monthNumber: parseInt(match[2], 10),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Scroll position management utilities for Calendar component
|
|
414
|
+
*/
|
|
415
|
+
/**
|
|
416
|
+
* Finds the DOM element for a specific date
|
|
417
|
+
* @param dateString - Formatted date string (yyyy-MM-dd)
|
|
418
|
+
* @param container - Scrollable container element
|
|
419
|
+
* @returns The DOM element for the date, or null if not found
|
|
420
|
+
*/
|
|
421
|
+
function findDateElement(dateString, container) {
|
|
422
|
+
if (!container)
|
|
423
|
+
return null;
|
|
424
|
+
// Try to find element by data attribute
|
|
425
|
+
const element = container.querySelector(`[data-date="${dateString}"]`);
|
|
426
|
+
return element;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Scrolls to a specific date in the calendar
|
|
430
|
+
* @param dateString - Formatted date string (yyyy-MM-dd)
|
|
431
|
+
* @param container - Scrollable container element
|
|
432
|
+
* @param options - Scroll options (behavior, block, inline)
|
|
433
|
+
* @returns Promise that resolves when scrolling is complete
|
|
434
|
+
*/
|
|
435
|
+
function scrollToDate(dateString, container, options = {
|
|
436
|
+
behavior: 'smooth',
|
|
437
|
+
block: 'center',
|
|
438
|
+
inline: 'nearest',
|
|
439
|
+
}) {
|
|
440
|
+
return new Promise((resolve) => {
|
|
441
|
+
const element = findDateElement(dateString, container);
|
|
442
|
+
if (element) {
|
|
443
|
+
element.scrollIntoView(options);
|
|
444
|
+
// Wait for scroll animation to complete
|
|
445
|
+
setTimeout(() => resolve(), 300);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
resolve();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Gets the scroll position of a container
|
|
454
|
+
* @param container - Scrollable container element
|
|
455
|
+
* @returns Current scroll position
|
|
456
|
+
*/
|
|
457
|
+
function getScrollPosition(container) {
|
|
458
|
+
if (!container)
|
|
459
|
+
return null;
|
|
460
|
+
return {
|
|
461
|
+
scrollTop: container.scrollTop,
|
|
462
|
+
scrollLeft: container.scrollLeft,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Sets the scroll position of a container
|
|
467
|
+
* @param container - Scrollable container element
|
|
468
|
+
* @param position - Scroll position to set
|
|
469
|
+
*/
|
|
470
|
+
function setScrollPosition(container, position) {
|
|
471
|
+
if (!container)
|
|
472
|
+
return;
|
|
473
|
+
container.scrollTop = position.scrollTop;
|
|
474
|
+
container.scrollLeft = position.scrollLeft;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Finds the date info for a specific formatted date string in months
|
|
478
|
+
* @param dateString - Formatted date string (yyyy-MM-dd)
|
|
479
|
+
* @param months - Array of month info
|
|
480
|
+
* @returns The date info, or null if not found
|
|
481
|
+
*/
|
|
482
|
+
function findDateInfoInMonths(dateString, months) {
|
|
483
|
+
for (const month of months) {
|
|
484
|
+
for (const dateInfo of month.data) {
|
|
485
|
+
const dateStr = formatCalendarDate.day(dateInfo.date);
|
|
486
|
+
if (dateStr === dateString) {
|
|
487
|
+
return dateInfo;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Calculates the approximate scroll position for a date
|
|
495
|
+
* @param dateString - Formatted date string (yyyy-MM-dd)
|
|
496
|
+
* @param months - Array of month info
|
|
497
|
+
* @param itemHeight - Height of a single item in pixels
|
|
498
|
+
* @returns Approximate scroll position in pixels
|
|
499
|
+
*/
|
|
500
|
+
function calculateDateScrollPosition(dateString, months, itemHeight = 50) {
|
|
501
|
+
let itemIndex = 0;
|
|
502
|
+
for (const month of months) {
|
|
503
|
+
for (const dateInfo of month.data) {
|
|
504
|
+
const dateStr = formatCalendarDate.day(dateInfo.date);
|
|
505
|
+
if (dateStr === dateString) {
|
|
506
|
+
return itemIndex * itemHeight;
|
|
507
|
+
}
|
|
508
|
+
itemIndex++;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return 0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Generic hook for calendar items rendering
|
|
516
|
+
* Provides utility functions to generate props for calendar components
|
|
517
|
+
*/
|
|
518
|
+
/**
|
|
519
|
+
* Generic hook to generate props and utility functions for rendering calendar items
|
|
520
|
+
* Removes business-specific logic (metadata) and makes it generic
|
|
521
|
+
*
|
|
522
|
+
* @param props - Hook configuration
|
|
523
|
+
* @returns Object with memoized functions to get props for different calendar item types
|
|
524
|
+
*/
|
|
525
|
+
function useCalendarItems(props) {
|
|
526
|
+
const { currentDate, setCurrentDate, scrollRef, getIndicatorProps = () => ({}), } = props;
|
|
527
|
+
/**
|
|
528
|
+
* Generates props for a week number cell
|
|
529
|
+
*/
|
|
530
|
+
const getWeekNumberProps = useCallback((dateInfo) => {
|
|
531
|
+
const weekDateFormatted = formatCalendarDate.week(dateInfo.date, dateInfo.weekNumber);
|
|
532
|
+
const indicatorProps = getIndicatorProps(weekDateFormatted);
|
|
533
|
+
const onClick = () => {
|
|
534
|
+
setCurrentDate(weekDateFormatted);
|
|
535
|
+
};
|
|
536
|
+
return {
|
|
537
|
+
text: `W${dateInfo.weekNumber}`,
|
|
538
|
+
key: weekDateFormatted,
|
|
539
|
+
isMultiMonth: isWeekNumberDecoration(dateInfo.date) &&
|
|
540
|
+
dateInfo.date.isMultiMonth,
|
|
541
|
+
onClick,
|
|
542
|
+
...indicatorProps,
|
|
543
|
+
};
|
|
544
|
+
}, [currentDate, setCurrentDate, getIndicatorProps]);
|
|
545
|
+
/**
|
|
546
|
+
* Generates props for a day cell
|
|
547
|
+
*/
|
|
548
|
+
const getDayProps = useCallback((dateInfo) => {
|
|
549
|
+
const { date } = dateInfo;
|
|
550
|
+
const formattedDate = formatCalendarDate.day(date);
|
|
551
|
+
const isCurrentDay = isToday(date);
|
|
552
|
+
const indicatorProps = getIndicatorProps(formattedDate);
|
|
553
|
+
const onDateClick = (info) => {
|
|
554
|
+
setCurrentDate(formatCalendarDate.day(info.date));
|
|
555
|
+
};
|
|
556
|
+
return {
|
|
557
|
+
dateInfo,
|
|
558
|
+
ref: isCurrentDay ? scrollRef : undefined,
|
|
559
|
+
isMarked: isCurrentDay,
|
|
560
|
+
isSelected: isSameDay(date, new Date(currentDate)),
|
|
561
|
+
isRounded: true,
|
|
562
|
+
onDateClick,
|
|
563
|
+
key: formattedDate,
|
|
564
|
+
...indicatorProps,
|
|
565
|
+
};
|
|
566
|
+
}, [currentDate, scrollRef, setCurrentDate, getIndicatorProps]);
|
|
567
|
+
/**
|
|
568
|
+
* Generates props for a month header
|
|
569
|
+
*/
|
|
570
|
+
const getMonthHeaderProps = useCallback((monthName) => {
|
|
571
|
+
const currentYear = new Date().getFullYear();
|
|
572
|
+
const monthFormatted = formatCalendarDate.month(currentYear, monthName);
|
|
573
|
+
const indicatorProps = getIndicatorProps(monthFormatted);
|
|
574
|
+
const onClick = () => {
|
|
575
|
+
setCurrentDate(monthFormatted);
|
|
576
|
+
};
|
|
577
|
+
return {
|
|
578
|
+
text: monthName,
|
|
579
|
+
onClick,
|
|
580
|
+
...indicatorProps,
|
|
581
|
+
};
|
|
582
|
+
}, [currentDate, setCurrentDate, getIndicatorProps]);
|
|
583
|
+
/**
|
|
584
|
+
* Checks if a calendar item represents a week number decoration
|
|
585
|
+
*/
|
|
586
|
+
const isWeekNumber = useCallback((item) => isWeekNumberDecoration(item.date), []);
|
|
587
|
+
return {
|
|
588
|
+
getWeekNumberProps,
|
|
589
|
+
getDayProps,
|
|
590
|
+
getMonthHeaderProps,
|
|
591
|
+
isWeekNumber,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Hook for infinite scrolling support
|
|
597
|
+
*/
|
|
598
|
+
/**
|
|
599
|
+
* Hook for infinite scrolling with IntersectionObserver
|
|
600
|
+
*
|
|
601
|
+
* @param props - Infinite scroll configuration
|
|
602
|
+
*/
|
|
603
|
+
function useInfiniteScroll(props) {
|
|
604
|
+
const { hasMore, isLoading, onLoadMore, rootMargin = '10px', threshold = 0, scrollRef, } = props;
|
|
605
|
+
const sentinelRef = useRef(null);
|
|
606
|
+
const inFlightRef = useRef(false);
|
|
607
|
+
useEffect(() => {
|
|
608
|
+
if (!hasMore || isLoading || inFlightRef.current)
|
|
609
|
+
return;
|
|
610
|
+
const sentinel = sentinelRef.current;
|
|
611
|
+
if (!sentinel)
|
|
612
|
+
return;
|
|
613
|
+
const root = scrollRef?.current || null;
|
|
614
|
+
const observer = new IntersectionObserver(async (entries) => {
|
|
615
|
+
const entry = entries[0];
|
|
616
|
+
if (entry?.isIntersecting && !isLoading && !inFlightRef.current) {
|
|
617
|
+
inFlightRef.current = true;
|
|
618
|
+
try {
|
|
619
|
+
await onLoadMore();
|
|
620
|
+
}
|
|
621
|
+
finally {
|
|
622
|
+
inFlightRef.current = false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}, {
|
|
626
|
+
root,
|
|
627
|
+
rootMargin,
|
|
628
|
+
threshold,
|
|
629
|
+
});
|
|
630
|
+
observer.observe(sentinel);
|
|
631
|
+
return () => {
|
|
632
|
+
observer.disconnect();
|
|
633
|
+
inFlightRef.current = false;
|
|
634
|
+
};
|
|
635
|
+
}, [hasMore, isLoading, onLoadMore, rootMargin, threshold, scrollRef]);
|
|
636
|
+
return { sentinelRef };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Hook for scrolling to a specific date
|
|
641
|
+
*/
|
|
642
|
+
/**
|
|
643
|
+
* Hook for scrolling to a specific date in the calendar
|
|
644
|
+
*
|
|
645
|
+
* @param props - Scroll configuration
|
|
646
|
+
* @returns Function to scroll to a date
|
|
647
|
+
*/
|
|
648
|
+
function useScrollToDate(props = {}) {
|
|
649
|
+
const { scrollRef, scrollOptions } = props;
|
|
650
|
+
const scrollTo = useCallback(async (dateString) => {
|
|
651
|
+
await scrollToDate(dateString, scrollRef?.current ?? null, scrollOptions ?? {
|
|
652
|
+
behavior: 'smooth',
|
|
653
|
+
block: 'center',
|
|
654
|
+
inline: 'nearest',
|
|
655
|
+
});
|
|
656
|
+
}, [scrollRef, scrollOptions]);
|
|
657
|
+
const findElement = useCallback((dateString) => {
|
|
658
|
+
return findDateElement(dateString, scrollRef?.current ?? null);
|
|
659
|
+
}, [scrollRef]);
|
|
660
|
+
return {
|
|
661
|
+
scrollTo,
|
|
662
|
+
findElement,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export { Calendar$1 as Calendar, DATE_FORMATS, DEFAULT_GRID_CONFIG, DEFAULT_VIEWPORT_CONFIG, Day$1 as Day, MONTH_NUMBERS, MonthHeader$1 as MonthHeader, MonthList$1 as MonthList, WEEKDAY_NAMES, Week$1 as Week, WeekdayHeader$1 as WeekdayHeader, calculateDateScrollPosition, calculateGridIndex, calculateItemOffset, calculateRowCount, calculateTotalHeight, calculateVisibleRange, findDateElement, findDateInfoInMonths, formatCalendarDate, getColumnFromIndex, getRowFromIndex, getScrollPosition, isItemVisible, isWeekNumberCell, parseDayString, parseMonthString, parseWeekString, scrollToDate, setScrollPosition, useCalendarItems, useInfiniteScroll, useScrollToDate };
|
|
667
|
+
//# sourceMappingURL=index.esm.js.map
|