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