react-weekly-planning 1.0.43 → 1.0.44

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/README.md CHANGED
@@ -488,4 +488,37 @@ to create an organization that truly reflects you.
488
488
  };
489
489
  ```
490
490
 
491
+ ### `useIntersectionObserver`
492
+
493
+ - **Description**: Utility hook designed to help virtualize scrolling components. It leverages the Intersection Observer API to detect when an element enters or leaves the viewport, allowing you to mount or unmount heavy DOM elements dynamically for better performance in long lists or large grids.
494
+ - **Parameters**:
495
+ - `ref` (React.RefObject): The ref assigned to the DOM element you want to observe.
496
+ - `options` (IntersectionObserverInit, optional): Configuration object (e.g., `rootMargin`, `threshold`).
497
+ - **Returns**: An object containing `{ entry, height }`. `entry` is the `IntersectionObserverEntry` which can be used to check `isIntersecting`. `height` provides the element's cached height when it is unmounted to maintain scroll position consistency.
498
+
499
+ **Example**:
500
+ ```tsx
501
+ import React, { useRef } from "react";
502
+ import { useIntersectionObserver } from "react-pweekly-planning";
503
+
504
+ const VirtualItem = ({ children }) => {
505
+ const ref = useRef(null);
506
+ const { entry, height } = useIntersectionObserver(ref, {
507
+ rootMargin: "600px",
508
+ threshold: 0
509
+ });
510
+
511
+ const isVisible = !!entry?.isIntersecting;
512
+
513
+ return (
514
+ <div
515
+ ref={ref}
516
+ style={{ minHeight: isVisible ? "auto" : `${height}px` }}
517
+ >
518
+ {isVisible ? children : null}
519
+ </div>
520
+ );
521
+ };
522
+ ```
523
+
491
524
  ---
@@ -13,7 +13,12 @@ const CalendarForWeek = (props) => {
13
13
  const { dailyHours, weekDays } = useCalendarDateState(props.date, props.weekOffset, props.timeZone);
14
14
  const memoizedHeader = useMemo(() => (_jsx("div", { className: "planningCalendarHeader", children: _jsxs("div", { className: `planningCalendarRow ${props.rowsClassName}`, style: Object.assign(Object.assign({}, theadTrStyle), props.rowsStyle), children: [_jsx("div", { className: `dayTh ${props.groupsColsClassName}`, style: Object.assign({}, props.groupsColsStyle), children: _jsx(GroupsHeadContainer, { className: `${props.groupHeadContainerClassName}`, style: props.groupHeadContainerStyle, groupsHeadRender: props.groupsHeadRender }) }), weekDays.map((day, i) => (_jsx("div", { className: `dayCol ${props.daysColsClassName}`, style: Object.assign({}, props.daysColsStyle), children: _jsx(DayContainer, { style: props.dayStyle, className: props.dayClassName, dayIndex: i, dayRender: props.dayRender, day: day.day, dayOfTheMonth: day.dayOfTheMonth, dayMonth: day.dayMonth, dayYear: day.dayYear }) }, i)))] }, "header") })), [weekDays, props.rowsClassName, props.rowsStyle, props.groupsColsClassName, props.groupsColsStyle, props.groupHeadContainerClassName, props.groupHeadContainerStyle, props.groupsHeadRender, props.daysColsClassName, props.daysColsStyle, props.dayStyle, props.dayClassName, props.dayRender]);
15
15
  const offset = useMemo(() => updateOffsetWithDateCalendar(props.date), [props.date]);
16
- return (_jsx("div", { className: "calendarForWeek", style: { position: "relative" }, children: _jsxs("div", { className: `planningCalendar ${props.className}`, style: Object.assign({}, props.style), children: [memoizedHeader, _jsx("div", { className: "planningCalendarBody", children: (_a = props.groups) === null || _a === void 0 ? void 0 : _a.map((group, i) => {
16
+ return (_jsx("div", { className: "calendarForWeek", style: { position: "relative" }, children: _jsxs("div", { className: `planningCalendar ${props.className}`, style: Object.assign({}, props.style), children: [memoizedHeader, _jsx("div", { className: "planningCalendarBody", style: {
17
+ // position: "absolute",
18
+ // width: "100%",
19
+ // // top: `${36 + 5}px`,
20
+ // top: `${36 + 5 + ((65 + 4) * Math.max(rowSliceIndexStart, 0))}px`,
21
+ }, children: (_a = props.groups) === null || _a === void 0 ? void 0 : _a.map((group, i) => {
17
22
  var _a;
18
23
  const scope = hashScope || "week";
19
24
  const groupHash = getHash(offset, group.id);
@@ -9,7 +9,7 @@ function CalendarForDay(props) {
9
9
  const { dailyHours, weekDays } = useCalendarDateState(props.date, props.weekOffset, props.timeZone);
10
10
  const { getTasks, isValidTask, addTask, deleteTask, updateTask, getTask, hashScope, tasks } = useCalendarTaskContext();
11
11
  const currentDay = weekDays[props.dayOffset || 0];
12
- const memoizedHeader = useMemo(() => (currentDay ? (_jsx(DayContainer, { style: props.dayStyle, className: props.dayClassName, dayIndex: props.dayOffset || 0, dayRender: props.dayRender, day: currentDay.day, dayOfTheMonth: currentDay.dayOfTheMonth, dayMonth: currentDay.dayMonth, dayYear: currentDay.dayYear })) : null), [currentDay, props.dayStyle, props.dayClassName, props.dayOffset, props.dayRender]);
12
+ const memoizedHeader = useMemo(() => (currentDay ? (_jsx(DayContainer, { style: props.dayStyle, className: props.dayClassName, dayIndex: props.dayOffset || 0, dayRender: props.dayRender, day: currentDay.day, dayOfTheMonth: currentDay.dayOfTheMonth, dayMonth: currentDay.dayMonth, dayYear: currentDay.dayYear })) : null), [weekDays, props.rowsClassName, props.rowsStyle, props.groupsColsClassName, props.groupsColsStyle, props.groupHeadContainerClassName, props.groupHeadContainerStyle, props.groupsHeadRender, props.daysColsClassName, props.daysColsStyle, props.dayStyle, props.dayClassName, props.dayRender]);
13
13
  return (_jsxs("div", { className: `CalendarTableForDay ${props.className}`, style: Object.assign({ position: "relative" }, props.style), children: [memoizedHeader, _jsx("div", { className: `CalendarTableForDayTasksContainer`, children: props.groups.map((group, i) => (_jsx(VirtualGroupRowDay, { group: group, i: i, props: props, getTasks: getTasks, isValidTask: isValidTask, addTask: addTask, deleteTask: deleteTask, updateTask: updateTask, getTask: getTask, dailyHours: dailyHours, dayOffset: props.dayOffset || 0, hashScope: hashScope || "day", tasks: tasks }, `${group.id}-${i}`))) })] }));
14
14
  }
15
15
  export default memo(CalendarForDay, (prevProps, nextProps) => {
@@ -4,15 +4,15 @@ import { useIntersectionObserver } from "../../hooks/useIntersectionObserver";
4
4
  import TaskContainer from ".";
5
5
  const TaskVirtual = (props) => {
6
6
  const ref = useRef(null);
7
- const entry = useIntersectionObserver(ref, {
7
+ const { entry, height } = useIntersectionObserver(ref, {
8
8
  rootMargin: "200px", // Margin to pre-render tasks before they appear
9
9
  threshold: 0,
10
10
  });
11
11
  const isVisible = !!(entry === null || entry === void 0 ? void 0 : entry.isIntersecting);
12
12
  return (_jsx("div", { ref: ref, style: {
13
- minHeight: isVisible ? "auto" : "40px",
13
+ minHeight: isVisible ? "auto" : `${height}px`,
14
14
  width: "100%",
15
15
  marginBottom: "4px"
16
- }, children: isVisible ? (_jsx(TaskContainer, Object.assign({}, props))) : (_jsx("div", { style: { height: "60px", backgroundColor: "rgba(200, 200, 200, 0.1)", borderRadius: "4px" } })) }));
16
+ }, "data-index": props.index, children: isVisible ? (_jsx(TaskContainer, Object.assign({}, props))) : (_jsx("div", { style: { height: `${height}px`, backgroundColor: "rgba(200, 200, 200, 0.1)", borderRadius: "4px" } })) }));
17
17
  };
18
18
  export default React.memo(TaskVirtual);
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import TaskVirtual from "./TaskContainer/TaskVirtual";
4
+ import AddTask from "./AddTask";
5
+ import { getNewTaskForDropOrPaste, getUniqueId, } from "../lib/utils";
6
+ import { memo } from "react";
7
+ const VirtualGroupCell = ({ group, i, props, getTasks, isValidTask, addTask, deleteTask, getTask, day, hash, tasks, tasksStore, start, end }) => {
8
+ return (_jsx("div", { onDragOver: (e) => e.preventDefault(), onDrop: (event) => {
9
+ const dropInfo = getNewTaskForDropOrPaste(day.positionDay, group.id, getTask, hash);
10
+ if (!dropInfo)
11
+ return;
12
+ if (props.drop === "copy") {
13
+ addTask(Object.assign(Object.assign({}, dropInfo.newTask), { id: uuidv4() }));
14
+ return;
15
+ }
16
+ deleteTask(dropInfo.newTask.draghash, dropInfo.newTask.id);
17
+ addTask(Object.assign(Object.assign({}, dropInfo.newTask), { id: getUniqueId() }));
18
+ }, id: `col-${group.id}day-i`, className: `dayCol ${props.dayColsClassName}`, style: Object.assign(Object.assign({}, props.dayColsStyle), { minHeight: '60px' }), children: _jsxs("div", { style: {
19
+ display: "flex",
20
+ width: "100%",
21
+ height: "100%",
22
+ flexDirection: "column",
23
+ padding: "5px",
24
+ }, children: [tasks.slice(start, end).map((task) => {
25
+ if (task.dayIndex === day.positionDay &&
26
+ task.groupId === group.id && isValidTask(task)) {
27
+ return (_jsx(TaskVirtual, { handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
28
+ }
29
+ else
30
+ return null;
31
+ }), _jsx(AddTask, { addTaskStyle: props.addTaskStyle, addTaskClassName: props.addTaskClassName, currentGroup: group, dayInfo: day, addTaskRender: props.addTaskRender, handleAddTask: props.handleAddTask })] }) }, `col-${group.id}day-i${day.positionDay}`));
32
+ };
33
+ export default memo(VirtualGroupCell);
@@ -7,9 +7,9 @@ import TaskVirtual from "./TaskContainer/TaskVirtual";
7
7
  import AddTask from "./AddTask";
8
8
  import { getHash, getNewTaskForDropOrPaste, getUniqueId, updateOffsetWithDateCalendar, } from "../lib/utils";
9
9
  import { groupTdStyle } from "../lib/slyles";
10
- const VirtualGroupRow = ({ group, i, props, getTasks, isValidTask, addTask, deleteTask, getTask, dailyHours, hashScope, tasks, sumHoursByGroupsCount }) => {
10
+ const VirtualGroupRow = ({ group, i, props, getTasks, isValidTask, addTask, deleteTask, getTask, dailyHours, hashScope, tasks, sumHoursByGroupsCount, }) => {
11
11
  const ref = useRef(null);
12
- const entry = useIntersectionObserver(ref, {
12
+ const { entry, height } = useIntersectionObserver(ref, {
13
13
  rootMargin: "600px",
14
14
  threshold: 0,
15
15
  });
@@ -28,7 +28,7 @@ const VirtualGroupRow = ({ group, i, props, getTasks, isValidTask, addTask, dele
28
28
  };
29
29
  });
30
30
  }, [isVisible, offset, group.id, dailyHours, hashScope, getTasks, isValidTask, tasks]);
31
- return (_jsx("div", { ref: ref, className: `planningCalendarRow ${props.rowsClassName}`, style: Object.assign(Object.assign({}, props.rowsStyle), { minHeight: isVisible ? "auto" : "60px" }), children: isVisible ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `groupCol ${props.groupsColsClassName}`, style: Object.assign(Object.assign({}, groupTdStyle), props.groupsColsStyle), children: _jsx(GroupContainer, { style: props.groupContainerStyle, className: props.groupContainerClassName, groupRender: props.groupRender, currentGroup: group, handleClickGroup: props.handleClickGroup }) }, group.id), cellData.map((cell) => {
31
+ return (_jsx("div", { ref: ref, className: `planningCalendarRow ${props.rowsClassName}`, style: Object.assign(Object.assign({}, props.rowsStyle), { minHeight: isVisible ? "auto" : `${height}px` }), "data-index": i, children: isVisible ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `groupCol ${props.groupsColsClassName}`, style: Object.assign(Object.assign({}, groupTdStyle), props.groupsColsStyle), children: _jsx(GroupContainer, { style: props.groupContainerStyle, className: props.groupContainerClassName, groupRender: props.groupRender, currentGroup: group, handleClickGroup: props.handleClickGroup }) }, group.id), cellData.map((cell) => {
32
32
  return (_jsx("div", { onDragOver: (e) => e.preventDefault(), onDrop: (event) => {
33
33
  const dropInfo = getNewTaskForDropOrPaste(cell.positionDay, group.id, getTask, cell.hash);
34
34
  if (!dropInfo)
@@ -45,15 +45,15 @@ const VirtualGroupRow = ({ group, i, props, getTasks, isValidTask, addTask, dele
45
45
  height: "100%",
46
46
  flexDirection: "column",
47
47
  padding: "5px",
48
- }, children: [cell.tasks.map((task) => {
48
+ }, children: [cell.tasks.map((task, index) => {
49
49
  if (task.dayIndex === cell.positionDay &&
50
50
  task.groupId === group.id && isValidTask(task)) {
51
- return (_jsx(TaskVirtual, { handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
51
+ return (_jsx(TaskVirtual, { index: index, handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
52
52
  }
53
53
  else
54
54
  return null;
55
55
  }), _jsx(AddTask, { addTaskStyle: props.addTaskStyle, addTaskClassName: props.addTaskClassName, currentGroup: group, dayInfo: dailyHours[cell.positionDay], addTaskRender: props.addTaskRender, handleAddTask: props.handleAddTask })] }) }, `col-${group.id}day-i${cell.positionDay}`));
56
- })] })) : (_jsx("div", { style: { height: "60px", width: "100%" } })) }));
56
+ })] })) : (_jsx("div", { style: { height: `${height}px`, width: "100%" } })) }));
57
57
  };
58
58
  export default memo(VirtualGroupRow, (prev, next) => {
59
59
  return (prev.group.id === next.group.id &&
@@ -7,7 +7,7 @@ import AddTask from "./AddTask";
7
7
  import { getHash, getNewTaskForDropOrPaste, getUniqueId, updateOffsetWithDateCalendar, } from "../lib/utils";
8
8
  const VirtualGroupRowDay = ({ group, i, props, getTasks, isValidTask, addTask, deleteTask, updateTask, getTask, dailyHours, dayOffset, hashScope, tasks, }) => {
9
9
  const ref = useRef(null);
10
- const entry = useIntersectionObserver(ref, {
10
+ const { entry, height } = useIntersectionObserver(ref, {
11
11
  rootMargin: "600px",
12
12
  threshold: 0,
13
13
  });
@@ -19,7 +19,7 @@ const VirtualGroupRowDay = ({ group, i, props, getTasks, isValidTask, addTask, d
19
19
  const handleDragOver = (event) => {
20
20
  event.preventDefault();
21
21
  };
22
- return (_jsx("div", { ref: ref, style: Object.assign({ width: "100%", height: "auto", padding: "5px", borderBottom: "1.5px solid #0f52737e", borderRight: "0.74px solid rgba(198, 219, 225, 0.68)", borderLeft: "0.74px solid rgba(198, 219, 225, 0.68)", minHeight: isVisible ? "auto" : "60px" }, props.rowsStyle), className: `CalendarTableForDayRow ${props.rowsClassName}`, children: isVisible ? (_jsxs(_Fragment, { children: [_jsx("div", { style: Object.assign({ width: "auto", height: "auto" }, props.groupsColsStyle), className: props.groupsColsClassName, children: _jsx(GroupContainer, { style: props.groupContainerStyle, className: props.groupContainerClassName, groupRender: props.groupRender, currentGroup: group, handleClickGroup: props.handleClickGroup }) }), _jsxs("div", { className: "CalendarTableForDayGroupTasks", onDragOver: handleDragOver, onDrop: (event) => {
22
+ return (_jsx("div", { ref: ref, style: Object.assign({ width: "100%", height: "auto", padding: "5px", borderBottom: "1.5px solid #0f52737e", borderRight: "0.74px solid rgba(198, 219, 225, 0.68)", borderLeft: "0.74px solid rgba(198, 219, 225, 0.68)", minHeight: isVisible ? "auto" : `${height}px` }, props.rowsStyle), className: `CalendarTableForDayRow ${props.rowsClassName}`, children: isVisible ? (_jsxs(_Fragment, { children: [_jsx("div", { style: Object.assign({ width: "auto", height: "auto" }, props.groupsColsStyle), className: props.groupsColsClassName, children: _jsx(GroupContainer, { style: props.groupContainerStyle, className: props.groupContainerClassName, groupRender: props.groupRender, currentGroup: group, handleClickGroup: props.handleClickGroup }) }), _jsxs("div", { className: "CalendarTableForDayGroupTasks", onDragOver: handleDragOver, onDrop: (event) => {
23
23
  if (!cellTasks)
24
24
  return;
25
25
  const dropInfo = getNewTaskForDropOrPaste(currentDailyHours.positionDay, group.id, getTask, hash[hashScope]);
@@ -30,15 +30,15 @@ const VirtualGroupRowDay = ({ group, i, props, getTasks, isValidTask, addTask, d
30
30
  return;
31
31
  }
32
32
  updateTask(hash[hashScope], dropInfo.newTask.id, dropInfo.newTask);
33
- }, children: [cellTasks.map((task) => {
33
+ }, children: [cellTasks.map((task, index) => {
34
34
  if (task.dayIndex === dayOffset &&
35
35
  task.groupId === group.id &&
36
36
  isValidTask(task)) {
37
- return (_jsx(TaskVirtual, { handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
37
+ return (_jsx(TaskVirtual, { index: index, handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
38
38
  }
39
39
  else
40
40
  return null;
41
- }), _jsx(AddTask, { addTaskStyle: props.addTaskStyle, addTaskClassName: props.addTaskClassName, currentGroup: group, dayInfo: currentDailyHours, addTaskRender: props.addTaskRender, handleAddTask: props.handleAddTask })] })] })) : (_jsx("div", { style: { height: "60px", width: "100%" } })) }));
41
+ }), _jsx(AddTask, { addTaskStyle: props.addTaskStyle, addTaskClassName: props.addTaskClassName, currentGroup: group, dayInfo: currentDailyHours, addTaskRender: props.addTaskRender, handleAddTask: props.handleAddTask })] })] })) : (_jsx("div", { style: { height: `${height}px`, width: "100%" } })) }));
42
42
  };
43
43
  export default memo(VirtualGroupRowDay, (prev, next) => {
44
44
  return (prev.group.id === next.group.id &&
@@ -65,6 +65,6 @@ import CalendarTaskContextProvider from "../contexts/CalendarTaskContext";
65
65
  * @param {string} [props.hoursColsClassName] - Additional class names for the hours columns.
66
66
  */
67
67
  const Calendar = (props) => {
68
- return (_jsx(CalendarTaskContextProvider, { hashScope: "week", children: props.scope === "day" ? _jsx(CalendarForDay, Object.assign({}, props)) : _jsx(CalendarForWeek, Object.assign({}, props)) }));
68
+ return (_jsx(CalendarTaskContextProvider, { hashScope: "day", children: props.scope === "day" ? _jsx(CalendarForDay, Object.assign({}, props)) : _jsx(CalendarForWeek, Object.assign({}, props)) }));
69
69
  };
70
70
  export default Calendar;
@@ -59,7 +59,7 @@
59
59
  display: flex;
60
60
  flex-direction: column;
61
61
  border: 0.74px solid rgba(198, 219, 225, 0.68);
62
-
62
+ border-bottom: none;
63
63
  background-color: white;
64
64
 
65
65
  }
@@ -70,6 +70,8 @@
70
70
  z-index: 200;
71
71
  border-bottom: 1.5px solid #0f52737e;
72
72
  background-color: #fff;
73
+ position: sticky;
74
+ top: 0px;
73
75
  }
74
76
 
75
77
  .planningCalendarRow {
@@ -150,7 +152,8 @@
150
152
  min-height: 80px;
151
153
  }
152
154
 
153
- .calendarForWeek {
155
+ .calendarForWeek,
156
+ .calendarForDay {
154
157
  display: flex;
155
158
  width: 100%;
156
159
  flex: 1;
@@ -159,7 +162,8 @@
159
162
 
160
163
  }
161
164
 
162
- .calendarForWeek-container {
165
+ .calendarForWeek-container,
166
+ .calendarForDay-container {
163
167
  display: flex;
164
168
  flex-direction: column;
165
169
  width: 100%;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext } from "react";
3
3
  import { useCalendarTask } from "../hooks/useCalendarTask";
4
4
  const CalendarTaskContext = createContext({
5
- tasks: { buckets: {}, dataLength: 0, taskCache: {} },
5
+ tasks: { buckets: {}, dataLength: 0, taskCache: {}, maxBucketSize: 0 },
6
6
  addTask: () => { },
7
7
  getTasks: () => [],
8
8
  updateTask: () => { },
@@ -1,8 +1,7 @@
1
- import { useEffect, useState } from "react";
1
+ import { useMemo } from "react";
2
2
  import { calculateWeekDifference, getDateObjectInTimeZone, getDayHourly, getWeekDays, } from "../lib/utils";
3
3
  function useCalendarDateState(date, weekOffset, timeZone) {
4
- const [calendarDateState, setCalendarDateState] = useState({ dailyHours: [], weekDays: [] });
5
- useEffect(() => {
4
+ let calendarDateState = useMemo(() => {
6
5
  const weekOffsetByDate = timeZone
7
6
  ? calculateWeekDifference(getDateObjectInTimeZone(timeZone), timeZone)
8
7
  : calculateWeekDifference(date, timeZone);
@@ -12,7 +11,7 @@ function useCalendarDateState(date, weekOffset, timeZone) {
12
11
  dailyHours: dailyHours,
13
12
  weekDays,
14
13
  };
15
- setCalendarDateState(calData);
14
+ return calData;
16
15
  }, [date, weekOffset]);
17
16
  return Object.assign({}, calendarDateState);
18
17
  }
@@ -2,7 +2,8 @@ import { useEffect, useRef, useState, useMemo } from "react";
2
2
  import { getHash, updateOffsetWithDateCalendar } from "../lib/utils";
3
3
  const STORAGE_KEY = "calendar_tasks";
4
4
  export function useCalendarTask(hashScope, timeZone) {
5
- const tasksRef = useRef({ buckets: {}, dataLength: 0, taskCache: {} });
5
+ const tasksRef = useRef({ buckets: {}, dataLength: 0, taskCache: {}, maxBucketSize: 0 });
6
+ const bucketSizeCountsRef = useRef({});
6
7
  const scheduleCleanRef = useRef(null);
7
8
  const [render, forceRender] = useState(0);
8
9
  const cleanExpiredTasks = () => {
@@ -110,6 +111,7 @@ export function useCalendarTask(hashScope, timeZone) {
110
111
  const bucket = tasksRef.current.buckets[hash];
111
112
  const index = bucket.list.length;
112
113
  bucket.list.push(Object.assign(Object.assign({}, task), { hash }));
114
+ tasksRef.current.maxBucketSize = Math.max(tasksRef.current.maxBucketSize, bucket.list.length);
113
115
  bucket.indexMap[task.id] = index;
114
116
  bucket.sumOfTaskDuration += task.taskEnd - task.taskStart;
115
117
  tasksRef.current.dataLength++;
@@ -148,6 +150,7 @@ export function useCalendarTask(hashScope, timeZone) {
148
150
  }
149
151
  }
150
152
  bucket.list[index] = Object.assign(Object.assign({}, bucket.list[index]), updatedTask);
153
+ tasksRef.current.maxBucketSize = Math.max(tasksRef.current.maxBucketSize, bucket.list.length);
151
154
  saveToStorage();
152
155
  scheduleClean();
153
156
  forceRender((x) => x + 1);
@@ -172,6 +175,7 @@ export function useCalendarTask(hashScope, timeZone) {
172
175
  delete bucket.indexMap[taskId];
173
176
  bucket.sumOfTaskDuration -= oldTAskDuration;
174
177
  tasksRef.current.dataLength--;
178
+ tasksRef.current.maxBucketSize = Math.max(tasksRef.current.maxBucketSize, bucket.list.length);
175
179
  saveToStorage();
176
180
  scheduleClean();
177
181
  forceRender((x) => x + 1);
@@ -1,14 +1,93 @@
1
- import { useState, useEffect } from "react";
2
- export function useContainerScroll(mainContaierRef, cardHeight, gridGap) {
3
- const [sliceIndex, setSliceIndex] = useState(0);
1
+ import { useState, useEffect, useRef } from "react";
2
+ export function useContainerScroll(mainContaierRef, cardHeight, gridContaineRef) {
3
+ const [rowSliceIndexStart, setRowSliceIndexStart] = useState(0);
4
+ const [rowSliceIndexEnd, setRowSliceIndexEnd] = useState(15);
5
+ const [taskSliceIdexStart, setTaskSliceIdexStart] = useState(0);
6
+ const [taskSLiceIdexEnd, setTaskSLiceIdexEnd] = useState(30);
7
+ const [rowHeight, setRowHeight] = useState(0);
8
+ const visibleRows = useRef(new Set());
4
9
  useEffect(() => {
5
10
  const mainContainer = mainContaierRef.current;
11
+ const gridContainer = gridContaineRef.current;
12
+ if (!mainContainer || !gridContainer)
13
+ return;
14
+ const observer = new IntersectionObserver((entries) => {
15
+ entries.forEach((entry) => {
16
+ const row = entry.target;
17
+ const indexAttr = row.getAttribute('data-index');
18
+ if (!indexAttr)
19
+ return;
20
+ const index = parseInt(indexAttr, 10);
21
+ if (entry.isIntersecting) {
22
+ visibleRows.current.add(index);
23
+ }
24
+ else {
25
+ visibleRows.current.delete(index);
26
+ }
27
+ });
28
+ }, {
29
+ root: mainContainer,
30
+ threshold: 0,
31
+ });
32
+ const observedElements = new Set();
33
+ const observeChildren = () => {
34
+ if (!gridContainer.children)
35
+ return;
36
+ for (let i = 0; i < gridContainer.children.length; i++) {
37
+ const element = gridContainer.children[i];
38
+ if (!observedElements.has(element)) {
39
+ observer.observe(element);
40
+ observedElements.add(element);
41
+ }
42
+ }
43
+ };
44
+ const mutationObserver = new MutationObserver(() => {
45
+ observeChildren();
46
+ });
47
+ observeChildren();
48
+ mutationObserver.observe(gridContainer, { childList: true });
6
49
  const handleScroll = () => {
7
- const computeIndex = Math.floor(mainContainer.scrollTop / (cardHeight + gridGap));
8
- setSliceIndex(computeIndex);
50
+ const scrollTop = mainContainer.scrollTop;
51
+ const containerHeight = mainContainer.clientHeight || 800;
52
+ // ---- CALCUL POUR LES ROWS ----
53
+ const baseRowHeight = 72;
54
+ let startRowIndex = Math.floor(scrollTop / baseRowHeight);
55
+ if (visibleRows.current.size > 0) {
56
+ const firstVisibleRowIndex = Math.min(...Array.from(visibleRows.current));
57
+ if (startRowIndex > firstVisibleRowIndex) {
58
+ startRowIndex = firstVisibleRowIndex;
59
+ }
60
+ }
61
+ // Calcul de la fin (avec un buffer de vue de 2 éléments)
62
+ const visibleRowsCount = Math.ceil(containerHeight / baseRowHeight);
63
+ const endRowIndex = startRowIndex + visibleRowsCount + 2;
64
+ // ---- CALCUL POUR LES TASKS ----
65
+ // Base théorique pour une task (environ 40px minimum)
66
+ const baseTaskHeight = 40;
67
+ // Attention : Ceci est une estimation globale. Si chaque ROW scrolle individuellement,
68
+ // cette formule devra être ajustée.
69
+ const startTaskIndex = Math.floor(Math.max(0, scrollTop) / baseTaskHeight);
70
+ const visibleTasksCount = Math.ceil(containerHeight / baseTaskHeight);
71
+ const endTaskIndex = startTaskIndex + visibleTasksCount + 5;
72
+ setRowSliceIndexStart(startRowIndex);
73
+ setRowSliceIndexEnd(endRowIndex);
74
+ setTaskSliceIdexStart(startTaskIndex);
75
+ setTaskSLiceIdexEnd(endTaskIndex);
9
76
  };
10
77
  mainContainer.addEventListener("scroll", handleScroll);
11
- return () => mainContainer.removeEventListener("scroll", handleScroll);
12
- }, [cardHeight, gridGap]);
13
- return { sliceIndex };
78
+ // Exécution initiale
79
+ handleScroll();
80
+ return () => {
81
+ mainContainer.removeEventListener("scroll", handleScroll);
82
+ observer.disconnect();
83
+ mutationObserver.disconnect();
84
+ };
85
+ }, [mainContaierRef, gridContaineRef]);
86
+ return {
87
+ rowSliceIndexStart,
88
+ rowSliceIndexEnd,
89
+ taskSliceIdexStart,
90
+ taskSLiceIdexEnd,
91
+ rowHeight
92
+ };
14
93
  }
@@ -8,17 +8,17 @@ export const useGridContainer = (gridContaineRef) => {
8
8
  useEffect(() => {
9
9
  if (typeof window === "undefined")
10
10
  return;
11
- //si le parent n'a aucun enfant on sort
11
+ // //si le parent n'a aucun enfant on sort
12
12
  if (gridContaineRef.current.hasChildNodes() === false)
13
13
  return;
14
- //la hauteur du premier enfant de la grid
15
- const cardH = gridContaineRef.current.firstChild.clientHeight;
16
- if (cardH !== cardCompH) {
17
- const gridW = gridContaineRef.current.clientWidth;
18
- const cardW = gridContaineRef.current.firstChild.clientWidth;
19
- setCardCompH(cardH);
20
- setItemsByLines(Math.floor(gridW / cardW));
21
- }
14
+ // //la hauteur du premier enfant de la grid
15
+ // const cardH = gridContaineRef.current.firstChild.clientHeight;
16
+ // if (cardH !== cardCompH) {
17
+ // const gridW = gridContaineRef.current.clientWidth;
18
+ // const cardW = gridContaineRef.current.firstChild.clientWidth;
19
+ // setCardCompH(cardH);
20
+ // setItemsByLines(Math.floor(gridW / cardW));
21
+ // }
22
22
  }, [width]);
23
- return { cardCompH, cardRef, itemsByLine };
23
+ return { cardCompH: 65, cardRef, itemsByLine: 1 };
24
24
  };
@@ -1,12 +1,18 @@
1
1
  import { useEffect, useState } from "react";
2
2
  export function useIntersectionObserver(elementRef, { threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false, } = {}) {
3
3
  const [entry, setEntry] = useState();
4
+ const [height, setHeight] = useState(60);
4
5
  const frozen = (entry === null || entry === void 0 ? void 0 : entry.isIntersecting) && freezeOnceVisible;
5
6
  const updateEntry = ([entry]) => {
6
7
  setEntry(entry);
8
+ if (entry.boundingClientRect) {
9
+ if (entry.boundingClientRect.height > 0) {
10
+ setHeight(entry.boundingClientRect.height);
11
+ }
12
+ }
7
13
  };
8
14
  useEffect(() => {
9
- const node = elementRef === null || elementRef === void 0 ? void 0 : elementRef.current; // DOM node
15
+ const node = elementRef === null || elementRef === void 0 ? void 0 : elementRef.current;
10
16
  const hasIOSupport = !!window.IntersectionObserver;
11
17
  if (!hasIOSupport || frozen || !node)
12
18
  return;
@@ -15,5 +21,5 @@ export function useIntersectionObserver(elementRef, { threshold = 0, root = null
15
21
  observer.observe(node);
16
22
  return () => observer.disconnect();
17
23
  }, [elementRef, JSON.stringify(threshold), root, rootMargin, frozen]);
18
- return entry;
24
+ return { entry, height };
19
25
  }
package/dist/index.js CHANGED
@@ -17,3 +17,4 @@ export * from './components/VirtualGroupRowDay';
17
17
  export * from './components/DayContainer';
18
18
  export * from './hooks/useCalendarDateState';
19
19
  export * from './contexts/CalendarTaskContext';
20
+ export * from './hooks/useIntersectionObserver';
@@ -1,4 +1,7 @@
1
1
  import React from "react";
2
2
  import { TaskContainerPropsType } from "../../definitions";
3
- declare const _default: React.MemoExoticComponent<(props: TaskContainerPropsType) => import("react/jsx-runtime").JSX.Element>;
3
+ interface TaskVirtualProps extends TaskContainerPropsType {
4
+ index?: number;
5
+ }
6
+ declare const _default: React.MemoExoticComponent<(props: TaskVirtualProps) => import("react/jsx-runtime").JSX.Element>;
4
7
  export default _default;
@@ -0,0 +1,20 @@
1
+ import { CalendarTablePropsType, GroupFeildsType, TaskType, TasksStore, TasksType, dayInfoType } from "../definitions";
2
+ import React from "react";
3
+ interface VirtualGroupCellProps {
4
+ group: GroupFeildsType;
5
+ i: number;
6
+ props: CalendarTablePropsType;
7
+ getTasks: (hash: string) => TasksType;
8
+ isValidTask: (task: TaskType) => boolean;
9
+ addTask: (task: TaskType) => void;
10
+ deleteTask: (hash: string, taskId: string) => void;
11
+ getTask: (hash: string, taskId: string) => TaskType | undefined;
12
+ day: dayInfoType;
13
+ hash: string;
14
+ tasks: TasksType;
15
+ tasksStore: TasksStore;
16
+ start: number;
17
+ end: number;
18
+ }
19
+ declare const _default: React.NamedExoticComponent<VirtualGroupCellProps>;
20
+ export default _default;
@@ -413,5 +413,6 @@ export type TasksStore = {
413
413
  buckets: Record<string, TaskBucket>;
414
414
  dataLength: number;
415
415
  taskCache: Record<string, Task[]>;
416
+ maxBucketSize: number;
416
417
  };
417
418
  export {};
@@ -1,6 +1,16 @@
1
- import { dailyHoursType, TimeZone, weekDaysType } from "../definitions";
1
+ import { TimeZone } from "../definitions";
2
2
  declare function useCalendarDateState(date: Date, weekOffset: number | undefined, timeZone: TimeZone | undefined): {
3
- weekDays: weekDaysType;
4
- dailyHours: dailyHoursType;
3
+ dailyHours: {
4
+ positionDay: number;
5
+ day: Date;
6
+ start: number;
7
+ end: number;
8
+ }[];
9
+ weekDays: {
10
+ day: string;
11
+ dayMonth: string;
12
+ dayYear: number;
13
+ dayOfTheMonth: number;
14
+ }[];
5
15
  };
6
16
  export default useCalendarDateState;
@@ -4,6 +4,7 @@ export declare function useCalendarTask(hashScope: "week" | "group" | "day", tim
4
4
  buckets: Record<string, import("../definitions").TaskBucket>;
5
5
  dataLength: number;
6
6
  taskCache: Record<string, Task[]>;
7
+ maxBucketSize: number;
7
8
  };
8
9
  addTask: (task: Task) => void;
9
10
  getTasks: (hash: string) => Task[];
@@ -1,3 +1,7 @@
1
- export declare function useContainerScroll(mainContaierRef: React.RefObject<any>, cardHeight: number, gridGap: number): {
2
- sliceIndex: number;
1
+ export declare function useContainerScroll(mainContaierRef: React.RefObject<any>, cardHeight: number, gridContaineRef: React.RefObject<any>): {
2
+ rowSliceIndexStart: number;
3
+ rowSliceIndexEnd: number;
4
+ taskSliceIdexStart: number;
5
+ taskSLiceIdexEnd: number;
6
+ rowHeight: number;
3
7
  };
@@ -1,5 +1,8 @@
1
1
  interface UseIntersectionObserverProps extends IntersectionObserverInit {
2
2
  freezeOnceVisible?: boolean;
3
3
  }
4
- export declare function useIntersectionObserver(elementRef: React.RefObject<Element | null>, { threshold, root, rootMargin, freezeOnceVisible, }?: UseIntersectionObserverProps): IntersectionObserverEntry | undefined;
4
+ export declare function useIntersectionObserver(elementRef: React.RefObject<Element | null>, { threshold, root, rootMargin, freezeOnceVisible, }?: UseIntersectionObserverProps): {
5
+ entry: IntersectionObserverEntry | undefined;
6
+ height: number;
7
+ };
5
8
  export {};
@@ -14,3 +14,4 @@ export * from './components/VirtualGroupRowDay';
14
14
  export * from './components/DayContainer';
15
15
  export * from './hooks/useCalendarDateState';
16
16
  export * from './contexts/CalendarTaskContext';
17
+ export * from './hooks/useIntersectionObserver';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-weekly-planning",
3
- "version": "1.0.43",
3
+ "version": "1.0.44",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/types/index.d.ts",
6
6
  "exports": {
@@ -63,6 +63,10 @@
63
63
  "./components/GroupContainer": {
64
64
  "types": "./dist/types/components/GroupContainer.d.ts",
65
65
  "default": "./dist/components/GroupContainer.js"
66
+ },
67
+ "./hooks/useIntersectionObserver": {
68
+ "types": "./dist/types/hooks/useIntersectionObserver.d.ts",
69
+ "default": "./dist/hooks/useIntersectionObserver.js"
66
70
  }
67
71
  },
68
72
  "files": [