react-timelane 0.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 (72) hide show
  1. package/.github/workflows/static.yml +55 -0
  2. package/README.md +54 -0
  3. package/docs/README.md +54 -0
  4. package/docs/eslint.config.js +28 -0
  5. package/docs/index.html +12 -0
  6. package/docs/package-lock.json +5101 -0
  7. package/docs/package.json +35 -0
  8. package/docs/src/App.css +5 -0
  9. package/docs/src/App.tsx +59 -0
  10. package/docs/src/assets/react.svg +1 -0
  11. package/docs/src/components/AllocationComponent.tsx +82 -0
  12. package/docs/src/components/Timeline.tsx +183 -0
  13. package/docs/src/constants.ts +42 -0
  14. package/docs/src/hooks/useLocalStorage.ts +21 -0
  15. package/docs/src/main.tsx +9 -0
  16. package/docs/src/models/Allocation.ts +11 -0
  17. package/docs/src/models/AllocationId.ts +1 -0
  18. package/docs/src/models/Resource.ts +8 -0
  19. package/docs/src/models/ResourceId.ts +1 -0
  20. package/docs/src/vite-env.d.ts +1 -0
  21. package/docs/tsconfig.json +27 -0
  22. package/docs/vite.config.ts +8 -0
  23. package/eslint.config.js +28 -0
  24. package/index.html +13 -0
  25. package/package.json +43 -0
  26. package/src/components/Timeline.scss +297 -0
  27. package/src/components/TimelineAside.tsx +81 -0
  28. package/src/components/TimelineBackground.tsx +53 -0
  29. package/src/components/TimelineBody.tsx +54 -0
  30. package/src/components/TimelineHeader/DaysHeader.tsx +44 -0
  31. package/src/components/TimelineHeader/MonthsHeader.tsx +62 -0
  32. package/src/components/TimelineHeader/TimelineHeader.tsx +64 -0
  33. package/src/components/TimelineHeader/WeeksHeader.tsx +63 -0
  34. package/src/components/TimelineHeader/index.ts +9 -0
  35. package/src/components/TimelineHeader/renderingUtils.tsx +57 -0
  36. package/src/components/TimelineSelectionLayer.tsx +179 -0
  37. package/src/components/TimelineSettingsContext.tsx +27 -0
  38. package/src/components/TimelineSettingsProvider.tsx +24 -0
  39. package/src/components/TimelineWrapper.tsx +51 -0
  40. package/src/components/core/CoreItem/CoreItemComponent.tsx +69 -0
  41. package/src/components/core/CoreItem/DragResizeComponent.tsx +180 -0
  42. package/src/components/core/CoreSwimlane/AvailableSpaceIndicator.tsx +156 -0
  43. package/src/components/core/CoreSwimlane/CoreSwimlane.tsx +245 -0
  44. package/src/components/core/CoreSwimlane/DropPreview.tsx +30 -0
  45. package/src/components/core/CoreSwimlane/DropTarget.tsx +83 -0
  46. package/src/components/core/CoreSwimlane/OverlapIndicator.tsx +22 -0
  47. package/src/components/core/CoreSwimlane/utils.ts +375 -0
  48. package/src/components/core/utils.ts +154 -0
  49. package/src/components/layout/TimelineLayout.tsx +93 -0
  50. package/src/components/layout/layout.scss +107 -0
  51. package/src/global.d.ts +9 -0
  52. package/src/hooks/useScroll.tsx +71 -0
  53. package/src/hooks/useTimelineContext.tsx +6 -0
  54. package/src/index.ts +15 -0
  55. package/src/types/AvailableSpace.ts +6 -0
  56. package/src/types/CoreItem.ts +23 -0
  57. package/src/types/DateBounds.ts +4 -0
  58. package/src/types/Dimensions.ts +4 -0
  59. package/src/types/GrabInfo.ts +6 -0
  60. package/src/types/Grid.ts +6 -0
  61. package/src/types/ItemId.ts +1 -0
  62. package/src/types/OffsetBounds.ts +4 -0
  63. package/src/types/Pixels.ts +4 -0
  64. package/src/types/Position.ts +4 -0
  65. package/src/types/Rectangle.ts +6 -0
  66. package/src/types/SwimlaneId.ts +1 -0
  67. package/src/types/SwimlaneT.ts +6 -0
  68. package/src/types/TimeRange.ts +4 -0
  69. package/src/types/TimelineSettings.ts +11 -0
  70. package/src/types/index.ts +15 -0
  71. package/tsconfig.json +32 -0
  72. package/vite.config.ts +32 -0
@@ -0,0 +1,63 @@
1
+ import { MouseEvent, ReactElement } from "react";
2
+ import { Pixels, TimeRange } from "../../types";
3
+ import {
4
+ differenceInCalendarDays,
5
+ eachWeekOfInterval,
6
+ isSunday,
7
+ nextSunday,
8
+ } from "date-fns";
9
+ import { renderWeekHeader } from "./renderingUtils";
10
+
11
+ interface WeeksHeaderProps {
12
+ range: TimeRange;
13
+ pixels: Pixels;
14
+ setFocusedDay?: (day: Date | null) => void;
15
+ render?: (firstDay: Date, lastDay: Date) => ReactElement;
16
+ onWeekClick?: (params: {
17
+ firstDay: Date;
18
+ lastDay: Date;
19
+ e: MouseEvent;
20
+ }) => void;
21
+ }
22
+
23
+ export function WeeksHeader({
24
+ range,
25
+ pixels,
26
+ setFocusedDay = () => {},
27
+ render = renderWeekHeader,
28
+ onWeekClick = () => undefined,
29
+ }: WeeksHeaderProps) {
30
+ return (
31
+ <div className="timeline-header-weeks">
32
+ {eachWeekOfInterval(range, { weekStartsOn: 1 }).map((firstDay, index) => {
33
+ if (firstDay < range.start) {
34
+ firstDay = range.start;
35
+ }
36
+
37
+ let lastDay = isSunday(firstDay) ? firstDay : nextSunday(firstDay);
38
+
39
+ if (lastDay > range.end) {
40
+ lastDay = range.end;
41
+ }
42
+
43
+ const numberOfDays = differenceInCalendarDays(lastDay, firstDay) + 1;
44
+
45
+ return (
46
+ <div
47
+ key={index}
48
+ className="timeline-header-week-label"
49
+ style={{
50
+ width: `${pixels.pixelsPerDay * numberOfDays}px`,
51
+ }}
52
+ onClick={(e) => {
53
+ onWeekClick({ firstDay, lastDay, e });
54
+ setFocusedDay(firstDay);
55
+ }}
56
+ >
57
+ {render(firstDay, lastDay)}
58
+ </div>
59
+ );
60
+ })}
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,9 @@
1
+ export { DaysHeader } from "./DaysHeader";
2
+ export { WeeksHeader } from "./WeeksHeader";
3
+ export { MonthsHeader } from "./MonthsHeader";
4
+ export { TimelineHeader } from "./TimelineHeader";
5
+ export {
6
+ renderDayHeader,
7
+ renderMonthHeader,
8
+ renderWeekHeader,
9
+ } from "./renderingUtils";
@@ -0,0 +1,57 @@
1
+ import { differenceInCalendarDays, format } from "date-fns";
2
+ import { ReactElement } from "react";
3
+
4
+ export function renderMonthHeader(firstDay: Date, lastDay: Date): ReactElement {
5
+ const numberOfDays = differenceInCalendarDays(lastDay, firstDay) + 1;
6
+
7
+ return <>{numberOfDays > 4 ? format(firstDay, "LLLL yyyy") : <></>}</>;
8
+ }
9
+
10
+ export function renderWeekHeader(firstDay: Date, lastDay: Date): ReactElement {
11
+ const numberOfDays = differenceInCalendarDays(lastDay, firstDay) + 1;
12
+
13
+ const title = `KW ${format(firstDay, "w")} (${format(
14
+ firstDay,
15
+ "MM-dd"
16
+ )} - ${format(lastDay, "MM-dd")}`;
17
+
18
+ return (
19
+ <div title={title}>
20
+ {numberOfDays > 2 ? <>KW {format(firstDay, "w")}</> : <></>}
21
+ </div>
22
+ );
23
+ // return (
24
+ // <Tooltip
25
+ // arrow
26
+ // title={
27
+ // <div className="timeline-header-tooltip timeline-header-week-tooltip">
28
+ // KW {format(firstDay, "w")} ({format(firstDay, "MM-dd")} -{" "}
29
+ // {format(lastDay, "MM-dd")}
30
+ // </div>
31
+ // }
32
+ // disableInteractive
33
+ // >
34
+ // <div>{numberOfDays > 2 ? <>KW {format(firstDay, "w")}</> : <></>}</div>
35
+ // </Tooltip>
36
+ // );
37
+ }
38
+
39
+ export function renderDayHeader(day: Date): ReactElement {
40
+ const title = `${format(day, "yyyy-MM-dd")}`;
41
+
42
+ return <div title={title}>{format(day, "d")}</div>;
43
+
44
+ // return (
45
+ // <Tooltip
46
+ // arrow
47
+ // title={
48
+ // <div className="timeline-header-tooltip timeline-header-day-tooltip">
49
+ // {format(day, "yyyy-MM-dd")}
50
+ // </div>
51
+ // }
52
+ // disableInteractive
53
+ // >
54
+ // <div>{format(day, "d")}</div>
55
+ // </Tooltip>
56
+ // );
57
+ }
@@ -0,0 +1,179 @@
1
+ import { PropsWithChildren, useEffect, useRef, useState } from "react";
2
+ import { ItemId, Rectangle } from "../types";
3
+ import { doOverlap } from "./core/CoreSwimlane/utils";
4
+ import { MouseEvent } from "react";
5
+ import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
6
+
7
+ interface TimelineSelectionLayerProps {
8
+ onSelect: (selection: number[]) => void;
9
+ }
10
+
11
+ export function TimelineSelectionLayer({
12
+ children,
13
+ onSelect,
14
+ }: PropsWithChildren<TimelineSelectionLayerProps>) {
15
+ const [selectedItemIds, setSelectedItemIds] = useState<ItemId[]>([]);
16
+
17
+ const callbackRef = useRef<EventListener | null>(null);
18
+
19
+ const [mouseDownPos, setMouseDownPos] = useState<{
20
+ x: number;
21
+ y: number;
22
+ } | null>(null);
23
+
24
+ const [selectionRect, setSelectionRect] = useState<{
25
+ x: number;
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ } | null>(null);
30
+
31
+ useEffect(() => {
32
+ return monitorForElements({
33
+ onDragStart: (args) => {
34
+ setSelectedItemIds([args.source.data.id as number]);
35
+ },
36
+ });
37
+ }, []);
38
+
39
+ useEffect(() => {
40
+ onSelect(selectedItemIds);
41
+ }, [onSelect, selectedItemIds]);
42
+
43
+ function handleMouseDown(e: MouseEvent) {
44
+ if (!e.shiftKey && !e.ctrlKey) {
45
+ setSelectedItemIds([]);
46
+ }
47
+ setMouseDownPos({ x: e.clientX, y: e.clientY });
48
+ }
49
+
50
+ function handleMouseMove(e: MouseEvent) {
51
+ if (mouseDownPos) {
52
+ const selectedRect = {
53
+ x: Math.min(e.clientX, mouseDownPos.x),
54
+ y: Math.min(e.clientY, mouseDownPos.y),
55
+ width: Math.abs(e.clientX - mouseDownPos.x),
56
+ height: Math.abs(e.clientY - mouseDownPos.y),
57
+ };
58
+
59
+ const rect = e.currentTarget.getBoundingClientRect();
60
+ setSelectionRect({
61
+ ...selectedRect,
62
+ x: selectedRect.x - rect.left,
63
+ y: selectedRect.y - rect.top,
64
+ });
65
+
66
+ document.querySelectorAll(".timeline-drop-target").forEach((row) => {
67
+ if (doOverlap(row.getBoundingClientRect(), selectedRect)) {
68
+ row.querySelectorAll(".timeline-item").forEach((item) => {
69
+ if (doOverlap(item.getBoundingClientRect(), selectedRect)) {
70
+ item.classList.add("timeline-item-marked");
71
+ } else {
72
+ item.classList.remove("timeline-item-marked");
73
+ }
74
+ });
75
+ }
76
+ });
77
+
78
+ if (
79
+ (selectedRect.width > 5 || selectedRect.height > 5) &&
80
+ !callbackRef.current
81
+ ) {
82
+ callbackRef.current = captureClick;
83
+ window.addEventListener("click", captureClick, true);
84
+ }
85
+ }
86
+ }
87
+
88
+ function handleMouseUp(e: MouseEvent) {
89
+ if (mouseDownPos && selectionRect) {
90
+ e.stopPropagation();
91
+ const selectedRect = {
92
+ x: Math.min(e.clientX, mouseDownPos.x),
93
+ y: Math.min(e.clientY, mouseDownPos.y),
94
+ width: Math.abs(e.clientX - mouseDownPos.x),
95
+ height: Math.abs(e.clientY - mouseDownPos.y),
96
+ };
97
+
98
+ const ids: ItemId[] = Array.from(
99
+ document.querySelectorAll(".timeline-drop-target")
100
+ )
101
+ .filter((row) => doOverlap(row.getBoundingClientRect(), selectedRect))
102
+ .map((row) => Array.from(row.querySelectorAll(".timeline-item")))
103
+ .flatMap((elements) => elements)
104
+ .filter((el) => doOverlap(el.getBoundingClientRect(), selectedRect))
105
+ .map((el) => {
106
+ if (el instanceof HTMLElement) {
107
+ return Number.parseInt(el.dataset.timelineItemId || "-1");
108
+ } else {
109
+ return -1;
110
+ }
111
+ })
112
+ .filter((id) => id > 0);
113
+
114
+ if (ids.length > 0) {
115
+ if (e.shiftKey || e.ctrlKey) {
116
+ setSelectedItemIds((prev) => {
117
+ return Array.from(new Set([...prev, ...ids]));
118
+ });
119
+ } else {
120
+ setSelectedItemIds(ids);
121
+ }
122
+ }
123
+
124
+ document.querySelectorAll(".timeline-item-marked").forEach((el) => {
125
+ el.classList.remove("timeline-item-marked");
126
+ });
127
+ }
128
+
129
+ setMouseDownPos(null);
130
+ setSelectionRect(null);
131
+
132
+ requestAnimationFrame(() => {
133
+ if (callbackRef.current && callbackRef.current !== null) {
134
+ window.removeEventListener("click", callbackRef.current, true);
135
+ callbackRef.current = null;
136
+ }
137
+ });
138
+ }
139
+
140
+ return (
141
+ <div
142
+ onMouseDown={handleMouseDown}
143
+ onMouseMove={handleMouseMove}
144
+ onMouseUp={handleMouseUp}
145
+ style={{
146
+ position: "relative",
147
+ }}
148
+ >
149
+ {selectionRect && <MouseSelectionIndicator rect={selectionRect} />}
150
+ {children}
151
+ </div>
152
+ );
153
+ }
154
+
155
+ const captureClick = (e: any) => {
156
+ e.preventDefault();
157
+ e.stopPropagation();
158
+ };
159
+
160
+ interface MouseSelectionIndicatorProps {
161
+ rect: Rectangle;
162
+ }
163
+
164
+ function MouseSelectionIndicator({ rect }: MouseSelectionIndicatorProps) {
165
+ return (
166
+ <div
167
+ id="mouse-selection-indicator"
168
+ style={{
169
+ border: "1px dashed blue",
170
+ position: "absolute",
171
+ top: `${rect.y}px`,
172
+ left: `${rect.x}px`,
173
+ width: `${rect.width}px`,
174
+ height: `${rect.height}px`,
175
+ zIndex: 1000000,
176
+ }}
177
+ ></div>
178
+ );
179
+ }
@@ -0,0 +1,27 @@
1
+ import { createContext, Dispatch, SetStateAction } from "react";
2
+ import { TimelineSettings } from "../types/TimelineSettings";
3
+
4
+ const defaultTimelineSettings: TimelineSettings = {
5
+ start: new Date(2025, 1, 1),
6
+ end: new Date(2025, 5, 1),
7
+ pixelsPerDay: 30,
8
+ pixelsPerResource: 100,
9
+ showMonths: true,
10
+ showWeeks: true,
11
+ showDays: true,
12
+ allowOverlaps: true,
13
+ focusedDate: null,
14
+ };
15
+
16
+ export interface TimelineContextOuter {
17
+ settings: TimelineSettings;
18
+ setSettings: Dispatch<SetStateAction<TimelineSettings>>;
19
+ }
20
+
21
+ const defaultContext: TimelineContextOuter = {
22
+ settings: defaultTimelineSettings,
23
+ setSettings: () => undefined,
24
+ };
25
+
26
+ export const TimelineSettingsContext =
27
+ createContext<TimelineContextOuter>(defaultContext);
@@ -0,0 +1,24 @@
1
+ import { PropsWithChildren, useEffect, useState } from "react";
2
+ import { TimelineSettings } from "../types/TimelineSettings";
3
+ import { TimelineSettingsContext } from "./TimelineSettingsContext";
4
+
5
+ interface TimelineSettingsProviderProps {
6
+ settings: TimelineSettings;
7
+ }
8
+
9
+ export const TimelineSettingsProvider = ({
10
+ settings: _settings,
11
+ children,
12
+ }: PropsWithChildren<TimelineSettingsProviderProps>) => {
13
+ const [settings, setSettings] = useState<TimelineSettings>(_settings);
14
+
15
+ useEffect(() => {
16
+ setSettings((prev) => ({ ...prev, _settings }));
17
+ }, [_settings]);
18
+
19
+ return (
20
+ <TimelineSettingsContext.Provider value={{ settings, setSettings }}>
21
+ {children}
22
+ </TimelineSettingsContext.Provider>
23
+ );
24
+ };
@@ -0,0 +1,51 @@
1
+ import { PropsWithChildren } from "react";
2
+ import { TimelineSettingsProvider } from "./TimelineSettingsProvider";
3
+ import { SwimlaneT } from "../types";
4
+ import "./Timeline.scss";
5
+
6
+ interface TimelineWrapperProps {
7
+ focusedDay?: Date | null;
8
+ focusedSwimlane?: SwimlaneT | null;
9
+ start?: Date;
10
+ end?: Date;
11
+ pixelsPerDay?: number;
12
+ pixelsPerResource?: number;
13
+ showMonths?: boolean;
14
+ showWeeks?: boolean;
15
+ showDays?: boolean;
16
+ allowOverlaps?: boolean;
17
+ focusedDate?: Date | null;
18
+ }
19
+
20
+ export default function TimelineWrapper({
21
+ children,
22
+ start = new Date(2025, 1, 1),
23
+ end = new Date(2025, 5, 1),
24
+ pixelsPerDay = 30,
25
+ pixelsPerResource = 100,
26
+ showMonths = true,
27
+ showWeeks = true,
28
+ showDays = true,
29
+ allowOverlaps = true,
30
+ focusedDate = null,
31
+ }: PropsWithChildren<TimelineWrapperProps>) {
32
+ return (
33
+ <div className="timeline">
34
+ <TimelineSettingsProvider
35
+ settings={{
36
+ start,
37
+ end,
38
+ pixelsPerDay,
39
+ pixelsPerResource,
40
+ showDays,
41
+ showMonths,
42
+ showWeeks,
43
+ allowOverlaps,
44
+ focusedDate,
45
+ }}
46
+ >
47
+ {children}
48
+ </TimelineSettingsProvider>
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,69 @@
1
+ import DragResizeComponent from "./DragResizeComponent";
2
+ import {
3
+ getDropTargetDimensions,
4
+ getItemRectangle as getItemRectangle,
5
+ getUpdatedItem as getUpdatedItem,
6
+ } from "../utils";
7
+ import { PropsWithChildren } from "react";
8
+ import {
9
+ CoreItem,
10
+ Position,
11
+ Rectangle,
12
+ SwimlaneT,
13
+ TimelineSettings,
14
+ } from "../../../types";
15
+
16
+ interface CoreItemComponentProps<T> {
17
+ item: CoreItem<T>;
18
+ swimlane: SwimlaneT;
19
+ settings: TimelineSettings;
20
+ onDragStart: (grabPosition: Position, relativeGrabPosition: Position) => void;
21
+ onDrop: () => void;
22
+ onDrag: () => void;
23
+ onUpdate: (updatedItem: CoreItem<T>) => void;
24
+ onResizeStart: () => void;
25
+ }
26
+
27
+ export default function CoreItemComponent<T>({
28
+ swimlane,
29
+ item,
30
+ settings,
31
+ children,
32
+ onDragStart,
33
+ onDrag,
34
+ onDrop,
35
+ onUpdate,
36
+ onResizeStart,
37
+ }: PropsWithChildren<CoreItemComponentProps<T>>) {
38
+ const rectangle = getItemRectangle(item, swimlane, settings, settings);
39
+ const boundingRectangle = getDropTargetDimensions(
40
+ swimlane,
41
+ settings,
42
+ settings
43
+ );
44
+
45
+ return (
46
+ <DragResizeComponent<T>
47
+ item={item}
48
+ rectangle={rectangle}
49
+ boundingRectangle={boundingRectangle}
50
+ onDrag={onDrag}
51
+ onDragStart={onDragStart}
52
+ onDrop={onDrop}
53
+ onUpdate={(rectangle: Rectangle) => {
54
+ const updatedItem: CoreItem<T> = getUpdatedItem(
55
+ item,
56
+ swimlane,
57
+ rectangle,
58
+ settings,
59
+ settings
60
+ );
61
+
62
+ onUpdate(updatedItem);
63
+ }}
64
+ onResizeStart={onResizeStart}
65
+ >
66
+ {children}
67
+ </DragResizeComponent>
68
+ );
69
+ }
@@ -0,0 +1,180 @@
1
+ import { PropsWithChildren, useEffect, useRef, useState } from "react";
2
+ import invariant from "tiny-invariant";
3
+ import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
4
+ import { getGrabPosition } from "../utils";
5
+ import { Resizable } from "re-resizable";
6
+ import { Position, Rectangle, Dimensions, CoreItem } from "../../../types";
7
+
8
+ interface DragResizeComponentProps<T> {
9
+ item: CoreItem<T>;
10
+ rectangle: Rectangle;
11
+ boundingRectangle: Dimensions;
12
+ onDragStart: (grabPosition: Position, relativeGrabPosition: Position) => void;
13
+ onDrag: () => void;
14
+ onDrop: () => void;
15
+ onUpdate: (rectangle: Rectangle) => void;
16
+ onResizeStart: () => void;
17
+ }
18
+
19
+ /**
20
+ * takes care of pixel-level drag-and-drop and resizing.
21
+ * will emit every event through event handlers.
22
+ * @param param0 t
23
+ * @returns
24
+ */
25
+ export default function DragResizeComponent<T>({
26
+ item,
27
+ rectangle,
28
+ boundingRectangle,
29
+ children,
30
+ onDrag,
31
+ onDragStart,
32
+ onDrop,
33
+ onUpdate,
34
+ onResizeStart,
35
+ }: PropsWithChildren<DragResizeComponentProps<T>>) {
36
+ const ref = useRef<HTMLDivElement>(null);
37
+ const handleRef = useRef<HTMLDivElement>(null);
38
+
39
+ const [tmpRectangle, setTmpRectangle] = useState<Rectangle>(rectangle);
40
+
41
+ useEffect(() => {
42
+ setTmpRectangle(rectangle);
43
+ }, [rectangle]);
44
+
45
+ useEffect(() => {
46
+ invariant(ref.current);
47
+ invariant(handleRef.current);
48
+
49
+ return draggable({
50
+ element: ref.current,
51
+ dragHandle: handleRef.current,
52
+ onDragStart: ({ source, location }) => {
53
+ const grabPos = getGrabPosition(source, location);
54
+ onDragStart(grabPos.absolute, grabPos.relative);
55
+ },
56
+ onDrag: () => {
57
+ onDrag();
58
+ },
59
+
60
+ onDrop: () => {
61
+ onDrop();
62
+ },
63
+ getInitialData: () => ({ ...item }),
64
+ });
65
+ }, [item, onDrag, onDragStart, onDrop]);
66
+
67
+ return (
68
+ <div
69
+ id={`timeline-item-${item.id}`}
70
+ className="timeline-item"
71
+ data-timeline-item-id={item.id}
72
+ ref={ref}
73
+ style={{
74
+ width: `${tmpRectangle.width}px`,
75
+ height: `${tmpRectangle.height}px`,
76
+ minWidth: `${tmpRectangle.width}px`,
77
+ minHeight: `${tmpRectangle.height}px`,
78
+ maxWidth: `${tmpRectangle.width}px`,
79
+ maxHeight: `${tmpRectangle.height}px`,
80
+ top: `${tmpRectangle.y}px`,
81
+ left: `${tmpRectangle.x}px`,
82
+ }}
83
+ onMouseDown={(e) => {
84
+ e.stopPropagation();
85
+ }}
86
+ onClick={(e) => {
87
+ e.stopPropagation();
88
+ }}
89
+ >
90
+ <Resizable
91
+ enable={{
92
+ right: true,
93
+ left: true,
94
+ top: true,
95
+ bottom: true,
96
+ }}
97
+ handleClasses={{
98
+ left: "timeline-item-resize-handle timeline-item-resize-handle-left",
99
+ right:
100
+ "timeline-item-resize-handle timeline-item-resize-handle-right",
101
+ }}
102
+ size={{
103
+ width: tmpRectangle.width,
104
+ height: tmpRectangle.height,
105
+ }}
106
+ onResizeStart={() => {
107
+ onResizeStart();
108
+ }}
109
+ onResize={(e, direction, ref, d) => {
110
+ setTmpRectangle((el) => {
111
+ if (direction === "right") {
112
+ return {
113
+ ...el,
114
+ width: rectangle.width + d.width,
115
+ };
116
+ } else if (direction === "left") {
117
+ return {
118
+ ...el,
119
+ x: rectangle.x - d.width,
120
+ width: rectangle.width + d.width,
121
+ };
122
+ } else if (direction === "bottom") {
123
+ return {
124
+ ...el,
125
+ height: Math.min(
126
+ rectangle.height + d.height,
127
+ boundingRectangle.height - rectangle.y
128
+ ),
129
+ };
130
+ } else if (direction === "top") {
131
+ return {
132
+ ...el,
133
+ y: rectangle.y - d.height,
134
+ height: rectangle.height + d.height,
135
+ };
136
+ } else {
137
+ return el;
138
+ }
139
+ });
140
+ }}
141
+ onResizeStop={(_, direction, ref, d) => {
142
+ let newRectangle = rectangle;
143
+ if (direction === "right") {
144
+ newRectangle = {
145
+ ...rectangle,
146
+ width: rectangle.width + d.width,
147
+ };
148
+ } else if (direction === "left") {
149
+ newRectangle = {
150
+ ...rectangle,
151
+ x: rectangle.x - d.width,
152
+ width: rectangle.width + d.width,
153
+ };
154
+ } else if (direction === "bottom") {
155
+ newRectangle = {
156
+ ...rectangle,
157
+ height: Math.min(
158
+ rectangle.height + d.height,
159
+ boundingRectangle.height - rectangle.y
160
+ ),
161
+ };
162
+ } else if (direction === "top") {
163
+ newRectangle = {
164
+ ...rectangle,
165
+ y: rectangle.y - d.height,
166
+ height: rectangle.height + d.height,
167
+ };
168
+ }
169
+
170
+ onUpdate(newRectangle);
171
+ }}
172
+ style={{ overflow: "hidden" }}
173
+ >
174
+ <div className="timeline-item-drag-handle" ref={handleRef}>
175
+ {children}
176
+ </div>
177
+ </Resizable>
178
+ </div>
179
+ );
180
+ }