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