@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.
Files changed (59) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +310 -0
  3. package/dist/components/Calendar/Calendar.d.ts +12 -0
  4. package/dist/components/Calendar/Calendar.d.ts.map +1 -0
  5. package/dist/components/Calendar/Calendar.types.d.ts +33 -0
  6. package/dist/components/Calendar/Calendar.types.d.ts.map +1 -0
  7. package/dist/components/Day/Day.d.ts +7 -0
  8. package/dist/components/Day/Day.d.ts.map +1 -0
  9. package/dist/components/Day/Day.types.d.ts +40 -0
  10. package/dist/components/Day/Day.types.d.ts.map +1 -0
  11. package/dist/components/MonthHeader/MonthHeader.d.ts +12 -0
  12. package/dist/components/MonthHeader/MonthHeader.d.ts.map +1 -0
  13. package/dist/components/MonthHeader/MonthHeader.types.d.ts +33 -0
  14. package/dist/components/MonthHeader/MonthHeader.types.d.ts.map +1 -0
  15. package/dist/components/MonthList/MonthList.d.ts +12 -0
  16. package/dist/components/MonthList/MonthList.d.ts.map +1 -0
  17. package/dist/components/MonthList/MonthList.types.d.ts +31 -0
  18. package/dist/components/MonthList/MonthList.types.d.ts.map +1 -0
  19. package/dist/components/Week/Week.d.ts +12 -0
  20. package/dist/components/Week/Week.d.ts.map +1 -0
  21. package/dist/components/Week/Week.types.d.ts +35 -0
  22. package/dist/components/Week/Week.types.d.ts.map +1 -0
  23. package/dist/components/WeekdayHeader/WeekdayHeader.d.ts +16 -0
  24. package/dist/components/WeekdayHeader/WeekdayHeader.d.ts.map +1 -0
  25. package/dist/components/WeekdayHeader/WeekdayHeader.types.d.ts +16 -0
  26. package/dist/components/WeekdayHeader/WeekdayHeader.types.d.ts.map +1 -0
  27. package/dist/core/date/date-formatters.d.ts +65 -0
  28. package/dist/core/date/date-formatters.d.ts.map +1 -0
  29. package/dist/core/date/index.d.ts +6 -0
  30. package/dist/core/date/index.d.ts.map +1 -0
  31. package/dist/core/date/scroll-utils.d.ts +48 -0
  32. package/dist/core/date/scroll-utils.d.ts.map +1 -0
  33. package/dist/core/grid/calendar-grid.d.ts +45 -0
  34. package/dist/core/grid/calendar-grid.d.ts.map +1 -0
  35. package/dist/core/grid/index.d.ts +6 -0
  36. package/dist/core/grid/index.d.ts.map +1 -0
  37. package/dist/core/grid/virtualization.d.ts +38 -0
  38. package/dist/core/grid/virtualization.d.ts.map +1 -0
  39. package/dist/core/index.d.ts +7 -0
  40. package/dist/core/index.d.ts.map +1 -0
  41. package/dist/core/types.d.ts +70 -0
  42. package/dist/core/types.d.ts.map +1 -0
  43. package/dist/hooks/index.d.ts +10 -0
  44. package/dist/hooks/index.d.ts.map +1 -0
  45. package/dist/hooks/useCalendarItems.d.ts +64 -0
  46. package/dist/hooks/useCalendarItems.d.ts.map +1 -0
  47. package/dist/hooks/useInfiniteScroll.d.ts +21 -0
  48. package/dist/hooks/useInfiniteScroll.d.ts.map +1 -0
  49. package/dist/hooks/useScrollToDate.d.ts +25 -0
  50. package/dist/hooks/useScrollToDate.d.ts.map +1 -0
  51. package/dist/index.d.ts +22 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.esm.js +667 -0
  54. package/dist/index.esm.js.map +1 -0
  55. package/dist/index.js +701 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/types/index.d.ts +14 -0
  58. package/dist/types/index.d.ts.map +1 -0
  59. 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