add onSelect event & change and delete event fix
This commit is contained in:
parent
4852c2645d
commit
270131067c
@ -79,16 +79,20 @@ const App = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const sleep = (milliseconds: number) => {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
};
|
||||
let onTaskChange = (task: Task) => {
|
||||
console.log("On date change Id:" + task.id);
|
||||
};
|
||||
|
||||
let onTaskDelete = (task: Task) => {
|
||||
const conf = window.confirm("Are you sure?");
|
||||
if (!conf) throw "No del Id:" + task.id;
|
||||
const conf = window.confirm("Are you sure about " + task.name + " ?");
|
||||
return conf;
|
||||
};
|
||||
|
||||
let onProgressChange = (task: Task) => {
|
||||
let onProgressChange = async (task: Task) => {
|
||||
await sleep(5000);
|
||||
console.log("On progress change Id:" + task.id);
|
||||
};
|
||||
|
||||
@ -96,6 +100,10 @@ const App = () => {
|
||||
alert("On Double Click event Id:" + task.id);
|
||||
};
|
||||
|
||||
let onSelect = (task: Task, isSelected: boolean) => {
|
||||
console.log(task.name + " has " + (isSelected ? "selected" : "unselected"));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ViewSwitcher
|
||||
@ -111,6 +119,7 @@ const App = () => {
|
||||
onTaskDelete={onTaskDelete}
|
||||
onProgressChange={onProgressChange}
|
||||
onDoubleClick={onDblClick}
|
||||
onSelect={onSelect}
|
||||
listCellWidth={isChecked ? "155px" : ""}
|
||||
columnWidth={columnWidth}
|
||||
/>
|
||||
@ -122,6 +131,7 @@ const App = () => {
|
||||
onTaskDelete={onTaskDelete}
|
||||
onProgressChange={onProgressChange}
|
||||
onDoubleClick={onDblClick}
|
||||
onSelect={onSelect}
|
||||
listCellWidth={isChecked ? "155px" : ""}
|
||||
ganttHeight={300}
|
||||
columnWidth={columnWidth}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gantt-task-react",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"description": "Interactive Gantt Chart for React with TypeScript.",
|
||||
"author": "MaTeMaTuK <maksym.vikarii@gmail.com>",
|
||||
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",
|
||||
|
||||
@ -17,7 +17,7 @@ export type BarProps = {
|
||||
isDateChangeable: boolean;
|
||||
isDelete: boolean;
|
||||
onEventStart: (
|
||||
event: React.MouseEvent | React.KeyboardEvent,
|
||||
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
|
||||
action: GanttContentMoveAction,
|
||||
selectedTask: BarTask
|
||||
) => any;
|
||||
@ -43,9 +43,6 @@ export const Bar: React.FC<BarProps> = ({
|
||||
return (
|
||||
<g
|
||||
className={styles.barWrapper}
|
||||
onDoubleClick={e => {
|
||||
onEventStart(e, "dblclick", task);
|
||||
}}
|
||||
tabIndex={0}
|
||||
onKeyDown={e => {
|
||||
switch (e.key) {
|
||||
@ -62,8 +59,17 @@ export const Bar: React.FC<BarProps> = ({
|
||||
onMouseLeave={e => {
|
||||
onEventStart(e, "mouseleave", task);
|
||||
}}
|
||||
onFocus={() => setIsSelected(true)}
|
||||
onBlur={() => setIsSelected(false)}
|
||||
onDoubleClick={e => {
|
||||
onEventStart(e, "dblclick", task);
|
||||
}}
|
||||
onFocus={e => {
|
||||
setIsSelected(true);
|
||||
onEventStart(e, "select", task);
|
||||
}}
|
||||
onBlur={e => {
|
||||
setIsSelected(false);
|
||||
onEventStart(e, "unselect", task);
|
||||
}}
|
||||
>
|
||||
<BarDisplay
|
||||
x={task.x1}
|
||||
|
||||
@ -41,6 +41,7 @@ export const Gantt: React.SFC<GanttProps> = ({
|
||||
onProgressChange,
|
||||
onDoubleClick,
|
||||
onTaskDelete,
|
||||
onSelect,
|
||||
}) => {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [ganttTasks, setGanttTasks] = useState<Task[]>(tasks);
|
||||
@ -98,8 +99,10 @@ export const Gantt: React.SFC<GanttProps> = ({
|
||||
};
|
||||
|
||||
const handleScrollX = (event: SyntheticEvent<HTMLDivElement>) => {
|
||||
if (scrollX !== event.currentTarget.scrollLeft)
|
||||
if (scrollX !== event.currentTarget.scrollLeft && !ignoreScrollEvent) {
|
||||
setScrollX(event.currentTarget.scrollLeft);
|
||||
}
|
||||
setIgnoreScrollEvent(false);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -195,6 +198,7 @@ export const Gantt: React.SFC<GanttProps> = ({
|
||||
onProgressChange,
|
||||
onDoubleClick,
|
||||
onTaskDelete,
|
||||
onSelect,
|
||||
TooltipContent,
|
||||
};
|
||||
|
||||
|
||||
@ -9,13 +9,15 @@ import {
|
||||
BarMoveAction,
|
||||
} from "../../helpers/bar-helper";
|
||||
import { Tooltip } from "../other/tooltip";
|
||||
import { isKeyboardEvent } from "../../helpers/other-helper";
|
||||
import { isKeyboardEvent, isMouseEvent } from "../../helpers/other-helper";
|
||||
|
||||
export type GanttContentMoveAction =
|
||||
| "mouseenter"
|
||||
| "mouseleave"
|
||||
| "delete"
|
||||
| "dblclick"
|
||||
| "select"
|
||||
| "unselect"
|
||||
| BarMoveAction;
|
||||
export type BarEvent = {
|
||||
selectedTask?: BarTask;
|
||||
@ -73,6 +75,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
onProgressChange,
|
||||
onDoubleClick,
|
||||
onTaskDelete,
|
||||
onSelect,
|
||||
TooltipContent,
|
||||
}) => {
|
||||
const point = svg?.current?.createSVGPoint();
|
||||
@ -80,6 +83,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
action: "",
|
||||
});
|
||||
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
||||
const [failedTask, setFailedTask] = useState<BarTask | null>(null);
|
||||
const [xStep, setXStep] = useState(0);
|
||||
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
@ -126,48 +130,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
barBackgroundSelectedColor,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Method is Start point of task change
|
||||
*/
|
||||
const handleBarEventStart = async (
|
||||
event: React.MouseEvent | React.KeyboardEvent,
|
||||
action: GanttContentMoveAction,
|
||||
selectedTask: BarTask
|
||||
) => {
|
||||
if (isKeyboardEvent(event)) {
|
||||
if (action === "delete") {
|
||||
if (onTaskDelete) {
|
||||
await onTaskDelete(selectedTask);
|
||||
const newTasks = barTasks.filter(t => t.id !== selectedTask.id);
|
||||
onTasksChange(newTasks);
|
||||
}
|
||||
}
|
||||
} else if (action === "mouseenter") {
|
||||
if (!barEvent.action) {
|
||||
setBarEvent({ action, selectedTask, originalTask: selectedTask });
|
||||
}
|
||||
} else if (action === "mouseleave") {
|
||||
if (barEvent.action === "mouseenter") {
|
||||
setBarEvent({ action: "" });
|
||||
}
|
||||
} else if (action === "move") {
|
||||
if (!svg?.current || !point) return;
|
||||
point.x = event.clientX;
|
||||
const cursor = point.matrixTransform(
|
||||
svg.current.getScreenCTM()?.inverse()
|
||||
// on failed task update
|
||||
useEffect(() => {
|
||||
if (failedTask) {
|
||||
const newTasks = barTasks.map(t =>
|
||||
t.id === failedTask.id ? failedTask : t
|
||||
);
|
||||
setInitEventX1Delta(cursor.x - selectedTask.x1);
|
||||
setBarEvent({ action, selectedTask, originalTask: selectedTask });
|
||||
} else if (action === "dblclick") {
|
||||
!!onDoubleClick && onDoubleClick(selectedTask);
|
||||
} else {
|
||||
setBarEvent({
|
||||
action,
|
||||
selectedTask,
|
||||
originalTask: selectedTask,
|
||||
});
|
||||
onTasksChange(newTasks);
|
||||
setFailedTask(null);
|
||||
}
|
||||
};
|
||||
}, [failedTask, barTasks]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = async (event: MouseEvent) => {
|
||||
@ -220,25 +192,46 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
originalTask.end !== changedTask.end ||
|
||||
originalTask.progress !== changedTask.progress;
|
||||
|
||||
if (
|
||||
(action === "move" || action === "end" || action === "start") &&
|
||||
onDateChange &&
|
||||
isNotLikeOriginal
|
||||
) {
|
||||
await onDateChange(changedTask);
|
||||
} else if (onProgressChange && isNotLikeOriginal) {
|
||||
await onProgressChange(changedTask);
|
||||
}
|
||||
|
||||
// remove listeners
|
||||
svg.current.removeEventListener("mousemove", handleMouseMove);
|
||||
svg.current.removeEventListener("mouseup", handleMouseUp);
|
||||
setBarEvent({ action: "" });
|
||||
setIsMoving(false);
|
||||
const newTasks = barTasks.map(t =>
|
||||
t.id === changedTask.id ? changedTask : t
|
||||
);
|
||||
onTasksChange(newTasks);
|
||||
|
||||
svg.current.removeEventListener("mousemove", handleMouseMove);
|
||||
svg.current.removeEventListener("mouseup", handleMouseUp);
|
||||
setBarEvent({ action: "" });
|
||||
setIsMoving(false);
|
||||
// custom operation start
|
||||
let operationSuccess = true;
|
||||
if (
|
||||
(action === "move" || action === "end" || action === "start") &&
|
||||
onDateChange &&
|
||||
isNotLikeOriginal
|
||||
) {
|
||||
try {
|
||||
const result = await onDateChange(changedTask);
|
||||
if (result !== undefined) {
|
||||
operationSuccess = result;
|
||||
}
|
||||
} catch (error) {
|
||||
operationSuccess = false;
|
||||
}
|
||||
} else if (onProgressChange && isNotLikeOriginal) {
|
||||
try {
|
||||
const result = await onProgressChange(changedTask);
|
||||
if (result !== undefined) {
|
||||
operationSuccess = result;
|
||||
}
|
||||
} catch (error) {
|
||||
operationSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If operation is failed - return old state
|
||||
if (!operationSuccess) {
|
||||
setFailedTask(originalTask);
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
@ -265,6 +258,67 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
isMoving,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Method is Start point of task change
|
||||
*/
|
||||
const handleBarEventStart = async (
|
||||
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
|
||||
action: GanttContentMoveAction,
|
||||
selectedTask: BarTask
|
||||
) => {
|
||||
// Keyboard events
|
||||
if (isKeyboardEvent(event)) {
|
||||
if (action === "delete") {
|
||||
if (onTaskDelete) {
|
||||
try {
|
||||
const result = await onTaskDelete(selectedTask);
|
||||
if (result !== undefined && result) {
|
||||
const newTasks = barTasks.filter(t => t.id !== selectedTask.id);
|
||||
onTasksChange(newTasks);
|
||||
!!onSelect && onSelect(selectedTask, false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error on Delete. " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!isMouseEvent(event)) {
|
||||
if (action === "select") {
|
||||
!!onSelect && onSelect(selectedTask, true);
|
||||
} else if (action === "unselect") {
|
||||
!!onSelect && onSelect(selectedTask, false);
|
||||
}
|
||||
}
|
||||
// Mouse Events
|
||||
else if (action === "mouseenter") {
|
||||
if (!barEvent.action) {
|
||||
setBarEvent({ action, selectedTask, originalTask: selectedTask });
|
||||
}
|
||||
} else if (action === "mouseleave") {
|
||||
if (barEvent.action === "mouseenter") {
|
||||
setBarEvent({ action: "" });
|
||||
}
|
||||
} else if (action === "dblclick") {
|
||||
!!onDoubleClick && onDoubleClick(selectedTask);
|
||||
}
|
||||
// Change task event start
|
||||
else if (action === "move") {
|
||||
if (!svg?.current || !point) return;
|
||||
point.x = event.clientX;
|
||||
const cursor = point.matrixTransform(
|
||||
svg.current.getScreenCTM()?.inverse()
|
||||
);
|
||||
setInitEventX1Delta(cursor.x - selectedTask.x1);
|
||||
setBarEvent({ action, selectedTask, originalTask: selectedTask });
|
||||
} else {
|
||||
setBarEvent({
|
||||
action,
|
||||
selectedTask,
|
||||
originalTask: selectedTask,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<g className="content">
|
||||
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
|
||||
|
||||
@ -2,11 +2,17 @@ import { BarTask } from "../types/bar-task";
|
||||
import { Task } from "../types/public-types";
|
||||
|
||||
export function isKeyboardEvent(
|
||||
event: React.MouseEvent | React.KeyboardEvent
|
||||
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent
|
||||
): event is React.KeyboardEvent {
|
||||
return (event as React.KeyboardEvent).key !== undefined;
|
||||
}
|
||||
|
||||
export function isMouseEvent(
|
||||
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent
|
||||
): event is React.MouseEvent {
|
||||
return (event as React.MouseEvent).clientX !== undefined;
|
||||
}
|
||||
|
||||
export function isBarTask(task: Task | BarTask): task is BarTask {
|
||||
return (task as BarTask).x1 !== undefined;
|
||||
}
|
||||
|
||||
@ -30,10 +30,32 @@ export interface EventOption {
|
||||
* Time step value for date changes.
|
||||
*/
|
||||
timeStep?: number;
|
||||
/**
|
||||
* Invokes on bar select on unselect.
|
||||
*/
|
||||
onSelect?: (task: Task, isSelected: boolean) => void;
|
||||
/**
|
||||
* Invokes on bar double click.
|
||||
*/
|
||||
onDoubleClick?: (task: Task) => void;
|
||||
onDateChange?: (task: Task) => void | Promise<any>;
|
||||
onProgressChange?: (task: Task) => void | Promise<any>;
|
||||
onTaskDelete?: (task: Task) => void | Promise<any>;
|
||||
/**
|
||||
* Invokes on end and start time change. Chart undoes operation if method return false or error.
|
||||
*/
|
||||
onDateChange?: (
|
||||
task: Task
|
||||
) => void | boolean | Promise<void> | Promise<boolean>;
|
||||
/**
|
||||
* Invokes on progress change. Chart undoes operation if method return false or error.
|
||||
*/
|
||||
onProgressChange?: (
|
||||
task: Task
|
||||
) => void | boolean | Promise<void> | Promise<boolean>;
|
||||
/**
|
||||
* Invokes on delete selected task. Chart undoes operation if method return false or error.
|
||||
*/
|
||||
onTaskDelete?: (
|
||||
task: Task
|
||||
) => void | boolean | Promise<void> | Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface DisplayOption {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user