Featuring Design System
Reference

Extending the grid

모든 override seam 한 장 카탈로그 + veto 패턴(onCellKeyDown/onCellPointerDown) + 내장 클립보드 재사용.

확장의 두 가지 길

이 그리드는 "상태와 라우팅만 소유하고 렌더는 위임"하는 얇은 인프라입니다. 그래서 확장은 두 갈래입니다.

  • 정책 seam — 훅 옵션과 column.meta동작의 규칙을 바꿉니다. 선택을 같은 컬럼으로 제약하거나(transformSelection), 특정 셀만 편집 가능하게 하거나(isCellEditable), fill 값을 직접 계산합니다(getFillValue). 그리드의 키/포인터 라우팅은 그대로 두고, 그 안에서 무엇을 허용하고 어떻게 계산하는지만 바꿉니다.
  • veto 패턴onCellKeyDown/onCellPointerDown으로 그리드 내장 처리 자체를 가로챕니다. preventGridDefault()를 부르면 그 키/포인터에 대한 내장 동작(내비·편집·클립보드·선택 시작)을 전부 끄고, 소비자가 통째로 대체합니다.

먼저 모든 seam을 한 장으로 모은 카탈로그를, 그다음 veto 패턴과 클립보드 재사용을 다룹니다.

Override seam 카탈로그

영역별로 정리한 전수 표입니다. 각 seam의 상세 동작과 더 많은 예제는 연결된 딥 페이지를 참고하세요.

useDataGrid — 뷰 상태

View state에서 다룹니다. loading/errorRoot prop이 아니라 useDataGrid 옵션입니다.

Prop

Type

selection — 선택 정책

Selection model에서 다룹니다. 모든 정책은 useDataGridSelection(table, options)의 옵션입니다.

Prop

Type

editing — 편집 라이프사이클

Editing model에서 다룹니다. useDataGridEditing(table, options)의 옵션입니다.

Prop

Type

fillHandle — 자동 채우기

Clipboard & Fill에서 다룹니다. useDataGridFillHandle(table, options)의 옵션입니다.

Prop

Type

scrollToActive — 활성 셀 추적 스크롤

useDataGridScrollToActive(table, options)의 옵션입니다. 옵트인 — 호출하지 않으면 비용 0(effect 미등록).

Prop

Type

column.meta — 컬럼 단위 동작

Cell merging·Editing·View state에서 다룹니다. TanStack ColumnMeta에 declaration merging으로 얹힌 키입니다 — column.meta에 그대로 적습니다.

Prop

Type

Root — veto · ARIA · 크기

<DataGrid.Root>의 prop입니다. onCellKeyDown/onCellPointerDownveto 패턴의 진입점입니다.

Prop

Type

클립보드 헬퍼 — 내장 직렬화 재사용

패키지 루트에서 export하는 순수 함수입니다. 커스텀 클립보드/키 리맵에서 그리드 내장과 동일한 규칙을 재사용할 때 씁니다.

Prop

Type

veto 패턴 — 내장 처리 가로채기

정책 seam이 규칙을 바꾸는 거라면, veto는 그리드가 그 키/포인터를 처리하는 것 자체를 끕니다. onCellKeyDown/onCellPointerDown은 그리드 내장 처리가 돌기 전에 호출되고, 콜백 안에서 ctx.preventGridDefault()를 부르면 그 이벤트에 대한 내장 동작이 전부 취소됩니다. react-data-grid의 onCellKeyDown + preventGridDefault 패턴과 같습니다.

두 콜백 모두 포털 자손에서 올라온 이벤트는 제외됩니다. 소비자가 editor를 Popover/Modal로 띄우면 그쪽 input의 native 처리에 맡깁니다(시스템 가드 우선).

키 리맵 — onCellKeyDown

ctx{ focused, preventGridDefault }입니다. focused는 현재 활성 셀 좌표(없으면 null), preventGridDefault()를 부르면 그 키에 대한 내비·편집 진입·클립보드·undo를 전부 끕니다. 아래는 Enter를 "편집 진입" 대신 "행 상세 열기"로 리맵하는 예입니다.

import { useDataGrid, useDataGridSelection, useDataGridEditing, DataGrid } from '@featuring-corp/data-grid';

function Grid({ data }: { data: Row[] }) {
	const table = useDataGrid<Row>({ data, columns, getCoreRowModel: getCoreRowModel(), getRowId: (r) => r.id });
	const selection = useDataGridSelection(table);
	const editing = useDataGridEditing(table, { selection, onCellEdit });

	return (
		<DataGrid.Root
			table={table}
			selection={selection}
			editing={editing}
			onCellKeyDown={(e, { focused, preventGridDefault }) => {
				if (e.key === 'Enter' && focused) {
					// 그리드 내장 Enter(편집 진입)를 취소하고 소비자 동작으로 대체.
					preventGridDefault();
					e.preventDefault();
					openRowDetail(focused.rowId);
				}
			}}
		>
			<DataGrid.Header />
			<DataGrid.Body />
		</DataGrid.Root>
	);
}

preventGridDefault()를 부르지 않으면 그리드 내장 처리가 평소대로 이어집니다 — 즉 특정 키만 가로채고 나머지는 그대로 두는 게 기본 사용 패턴입니다.

포인터 가로채기 — onCellPointerDown (행 reorder 드래그)

ctx{ preventGridDefault }이고, 두 번째 인자로 눌린 셀 좌표 coord가 옵니다. preventGridDefault()를 부르면 그 pointerdown에 대한 셀 선택 시작·드래그·grid focus가 전부 취소됩니다 — 특정 컬럼(예: 드래그 핸들 컬럼)에서만 자체 인터랙션을 구현할 때 씁니다.

<DataGrid.Root
	table={table}
	selection={selection}
	onCellPointerDown={(e, coord, { preventGridDefault }) => {
		// 'dragHandle' 컬럼에서 누르면 셀 선택 대신 행 reorder 드래그를 시작.
		if (coord.columnId === 'dragHandle') {
			preventGridDefault(); // 셀 선택/드래그/focus 취소
			startRowDrag(coord.rowId, e);
		}
	}}
>
	<DataGrid.Header />
	<DataGrid.Body />
</DataGrid.Root>

커스텀 클립보드 / 키 리맵

내장 복사를 리맵하거나, "선택 영역 내보내기" 버튼을 따로 만들 때, 그리드 내장과 동일한 직렬화 규칙을 재사용합니다. getActiveRange로 대상 range를 고르고(비연속 다중선택에서 active range), serializeRangeToTsv로 TSV를 만듭니다 — meta.getCopyValue 존중·control 컬럼 제외·병합 covered 셀 빈칸 처리가 그대로 적용됩니다. 역방향(붙여넣기 파싱)은 parseTsv입니다.

import {
	useDataGridSelection,
	getActiveRange,
	serializeRangeToTsv,
	parseTsv,
	type DataGridSelection,
	type DataGridInstance,
} from '@featuring-corp/data-grid';

// "선택 영역을 TSV로 내보내기" — 내장 Cmd+C와 동일 규칙 재사용.
function exportSelection<T>(table: DataGridInstance<T>, selection: DataGridSelection): string | null {
	const range = getActiveRange(selection);
	if (!range) return null;
	return serializeRangeToTsv(table, range); // getCopyValue/control 제외/병합 처리 동일
}

// 붙여넣을 TSV를 2D 배열로 파싱(serializeRangeToTsv의 역연산).
const matrix = parseTsv('a\tb\nc\td'); // → [['a','b'], ['c','d']]

onCellKeyDown과 묶으면 키 리맵도 내장 규칙으로 처리할 수 있습니다 — 예를 들어 Cmd+Shift+C를 "표시값 복사"로 추가하되 직렬화는 serializeRangeToTsv에 맡깁니다.

<DataGrid.Root
	table={table}
	selection={selection}
	editing={editing}
	onCellKeyDown={(e, { preventGridDefault }) => {
		if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'c') {
			preventGridDefault();
			e.preventDefault();
			const tsv = exportSelection(table, selection);
			if (tsv) navigator.clipboard.writeText(tsv);
		}
	}}
>
	{/* ... */}
</DataGrid.Root>

커스텀 에디터 / 셀

에디터 UI는 그리드가 소유하지 않습니다 — column.cell 함수가 직접 렌더합니다. 그리드와 소비자 에디터의 유일한 접점은 useDataGridCellEdit 훅입니다. 이 헤드리스 escape hatch로 현재 셀의 편집 상태(isEditing/initialValue/trigger/ prefillChar/saveStatus)를 읽고 commit/cancel을 호출합니다 — TextInput·Select·DatePicker 어느 것이든 연결할 수 있습니다.

자세한 사용법과 패키지 기본 <DataGrid.TextEditor>(IME·키·blur 내장)는 Editing model에서 다룹니다.

degradation contract — 소비자 콜백 throw 처리

소비자 콜백이 throw할 때 그리드가 어떻게 강등하는지는 콜백이 어디서 호출되는가에 따라 갈립니다.

  • 이벤트 핸들러 경로(가드됨) — transformSelection·isCellSelectable·onCellEdit·onBulkEdit·onPaste·equals· parseValue·getFillValue·meta.fill·doubleClickAction 등은 키/포인터/paste 이벤트 핸들러 안에서 호출됩니다. 여기서 동기 throw가 새면 호스트 앱이 크래시하므로, 그리드가 격리해 안전하게 강등하고(거부/무시/해당 편집 fail) 개발 모드에서 1회 경고합니다. 비동기 검증은 throw 대신 반환 Promise를 reject 하세요(onCellEdit의 record-on-confirm).
  • 렌더 경로(소비자 책임) — meta.cell·meta.skeleton·meta.colSpan/rowSpan처럼 React 렌더 중 호출되는 콜백은 그리드가 try/catch로 가둘 수 없습니다(렌더 트리에서 던지면 React가 트리를 unmount). 이쪽은 소비자가 React error boundary로 감싸 처리해야 합니다.
이벤트 핸들러 콜백 throw  → 그리드가 격리 → 안전 강등 + dev 경고
렌더 경로 콜백 throw       → 소비자 error boundary 책임

다음 단계