이벤트

차트의 콜백을 앱에 연결하고, 저장이 실패한 편집을 되돌려요.

차트는 확정된 편집을 onTasksChange로 넘기고, 클릭과 선택은 각각의 콜백으로 알려요. 이 콜백들을 앱 상태에 연결하면 앱과 차트가 같은 데이터를 보게 돼요.

모든 편집 받기

드래그와 키보드 편집, 상세 패널의 입력란은 모두 같은 방식으로 확정돼요. 차트가 결과를 자기 쪽 작업 사본에 기록한 뒤, 배열 전체로 onTasksChange를 한 번 호출해요.

ProjectChart.tsx
import { useState } from 'react';
import { ReactGanttChart, type Task } from '@jaeungkim/gantt-chart';
import '@jaeungkim/gantt-chart/style.css';

const initialTasks: Task[] = [
  { id: 'design', name: 'Design', parentId: null, sequence: '1',
    startDate: '2026-03-02', endDate: '2026-03-06' },
  { id: 'build', name: 'Build', parentId: null, sequence: '2',
    startDate: '2026-03-09', endDate: '2026-03-20' },
];

export function ProjectChart() {
  const [tasks, setTasks] = useState<Task[]>(initialTasks);

  return (
    <ReactGanttChart
      tasks={tasks}
      onTasksChange={setTasks}
      showTaskList
      height={420}
    />
  );
}

배열에는 편집한 작업만이 아니라 모든 작업이 들어 있어요. 요약 막대를 드래그하면 하위 트리 전체가 그 한 번의 호출에 담겨요.

onTasksChange는 배열만 받아요. 제스처 종류와 잡은 모서리, 움직인 id 목록은 없어요. 마지막으로 넘긴 배열과 비교해서 무엇이 바뀌었는지 알아내요.

드래그 단계를 한 번도 넘지 않은 제스처는 기록도 호출도 없이 끝나요.

서버에 저장하고 되돌리기

차트는 핸들러가 실행되기 전에 변경을 이미 적용해요. 그래서 요청을 보내는 시점에 화면은 이미 바뀌어 있어요. 낙관적 업데이트가 기본 동작이에요.

<ReactGanttChart
  tasks={tasks}
  onTasksChange={(next) => {
    const previous = tasks;
    setTasks(next);
    save(next).catch(() => setTasks(previous));
  }}
/>

되돌리기는 이전 배열로 setTasks를 한 번 더 호출하는 일이에요. tasks prop은 내용이 차트가 가진 것과 다를 때 우선이라 막대가 원래 자리로 돌아가요.

차트는 방금 넘긴 배열이 tasks로 돌아오면 알아봐요. 그래서 평범한 제어 모드 루프는 화면에 있는 드래그 결과를 덮어쓰지 않아요.

저장하거나 전송하는 일은 차트가 하지 않아요. 데이터를 유지하는 일은 앱의 몫이에요.

클릭과 선택에 반응하기

막대를 클릭하든 작업 목록의 행을 클릭하든 같은 콜백이 같은 인자로 호출돼요. 인자는 작업에 차트가 계산한 레이아웃 필드를 더한 값이에요(작업 타입). 클릭 한 번 안에서 순서는 고정이에요. onTaskClick이 먼저 호출돼요. 그다음 선택이 바뀌고, 마지막으로 상세 패널이 켜져 있으면 열려요.

선택에는 고정된 기본값이 없어요. onTaskSelect를 넘기면 켜지고 상세 패널을 켜도 켜져요. selectable은 켜는 쪽이든 끄는 쪽이든 두 조건보다 우선이에요. selectable={false}면 선택 처리가 핸들러 앞에서 끝나서 onTaskSelect가 호출되지 않아요.

onTaskSelect는 선택된 id가 바뀔 때만 호출되므로 이미 선택된 행을 클릭하면 onTaskClick만 호출돼요. 빈 타임라인을 클릭하면 선택이 풀리고 onTaskSelect(null)이 호출돼요. 같은 곳을 다시 클릭하면 아무것도 호출되지 않아요. 작업 목록의 빈 영역과 날짜 헤더는 선택을 그대로 둬요.

차트는 막대 드래그를 닫는 클릭을 버려요. 그래서 끝난 드래그는 onTaskClick을 호출하지 않아요. 차트가 확인하는 플래그는 스냅된 단계 수가 바뀌었을 때만 세워지므로, 한 단계도 넘지 않은 누름은 여전히 클릭이에요. 작업 목록의 행에는 이런 플래그가 없어서, 순서 바꾸기 그립을 누르면 행이 움직였든 아니든 onTaskClick이 호출되고 행이 선택돼요. 접기 버튼은 클릭을 위로 전달하지 않아서 행을 펼쳐도 선택되지 않아요.

클릭 횟수를 묶는 디바운스는 없어요. 그래서 더블 클릭이 에디터를 열기 전에 싱글 클릭의 작업이 두 번 실행돼요.

onDetailChange는 제어 모드와 비제어 모드 모두에서 패널이 열리고 닫힐 때마다 호출돼요. 호출되는 경로는 제스처와 ref 호출뿐이에요. detailTaskId를 직접 바꿔도 호출되지 않고, tasks에서 빠진 작업의 패널은 이 콜백 없이 닫혀요.

변경이 적용되기 전에 취소하기

onDependencyCreate와 onDependencyDelete, onTaskMove는 변경이 적용되기 전에 실행돼요. 셋 중 하나에서 false를 그대로 반환하면 변경을 버려요. 아무것도 기록하지 않고 onTasksChange도 호출하지 않아요.

셋 다 동기 콜백이에요. 차트가 반환값을 즉시 읽으므로 Promise는 참으로 취급되고 변경이 확정돼요. 서버 검증은 앱의 저장 과정에서 처리해요. 전달되는 값은 의존 관계와 행 순서 바꾸기에서 확인해요.

날짜와 진행률에는 이런 콜백이 없어요. 모든 편집이 지켜야 하는 규칙은 onTasksChange에서 사후에 적용해요.

적용 전에 확인하기

  • 선택은 차트 내부의 id 하나예요. 다중 선택도 selectedTaskId prop도 없으니 onTaskSelect로 직접 추적해요.
  • 컨텍스트 메뉴와 호버, 포커스에는 콜백이 없고 오른쪽 클릭도 알리지 않아요. 리스너를 직접 붙이고 이벤트 대상에서 data-task-id를 읽어요.
  • 드래그 생애 주기 콜백은 없어요. onDragStart나 onDragMove, onDragEnd는 없고 onTasksChange는 제스처가 끝날 때 호출돼요.
  • 스크롤 전환이나 두 번째 손가락으로 브라우저가 취소한 막대 드래그는 원래대로 돌아가고 콜백도 호출되지 않아요. 같은 방식으로 취소된 진행률 드래그는 확정되고 onTasksChange를 호출해요.

콜백 요약

동작호출 순서취소 가능
막대나 행을 클릭onTaskClick, onTaskSelect, 패널이 켜져 있으면 onDetailChange아니요
막대나 행을 더블 클릭onTaskClick 두 번, 그다음 onTaskDoubleClick아니요
빈 타임라인을 클릭onTaskSelect(null), 패널이 열려 있었다면 onDetailChange(null)아니요
막대 이동, 크기 조절, 진행률 드래그, 키보드 편집, 패널 입력란 편집onTasksChange아니요
행을 다른 위치나 상위 작업으로 드래그onTaskMove, 그다음 onTasksChange예
의존 관계 화살표를 그리거나 삭제onDependencyCreate 또는 onDependencyDelete, 그다음 onTasksChange예
버튼이나 ref, 그린 범위로 작업 추가onTaskCreate차트는 아무것도 기록하지 않아요
상위 행을 펼치거나 접기onCollapsedChange아니요
상세 패널을 열거나 닫기onDetailChange아니요
배율이나 표시 범위를 변경onScaleChange, onRangeChange아니요

prop 정의와 기본값은 GanttProps에서 확인해요.