행 드래그와 트리
행을 다른 부모나 다른 그리드로 옮기고, 펼칠 때 자식을 불러오고, 두 그리드를 함께 스크롤합니다.
이 데모의 코드
import { createGrid, type Row } from '@freegridjs/core';
import { separator, text, toggle, tr, type DemoContext } from '../ui';
export default function mount(ctx: DemoContext) {
const { lang } = ctx;
const h = (en: string, ko: string) => tr(lang, { en, ko });
// two grids side by side
ctx.host.style.display = 'grid';
ctx.host.style.gridTemplateColumns = 'repeat(auto-fit, minmax(300px, 1fr))';
ctx.host.style.gap = '16px';
const left = document.createElement('div');
const right = document.createElement('div');
for (const el of [left, right]) el.style.cssText = 'height: 100%; min-height: 0';
ctx.host.style.gridAutoRows = '100%';
ctx.host.append(left, right);
const tasks: Row[] = [
{ id: 1, name: h('Website launch', '웹사이트 오픈'), parent: null, folder: true },
{ id: 2, name: h('Design', '디자인'), parent: 1, folder: true },
{ id: 3, name: h('Build', '개발'), parent: 1, folder: true },
{ id: 4, name: h('Logo', '로고'), parent: 2 },
// children of this one come from the "server" on first expand
{ id: 5, name: h('Archive (loads on expand)', '보관함 (펼칠 때 불러옴)'), parent: null, folder: true, lazy: true },
];
let nextId = 100;
const columns = [{ field: 'name', header: h('Tasks', '작업'), flex: 1 }];
const tree = createGrid(left, {
columns, data: tasks, locale: lang, getRowId: r => String(r.id), groupDefaultExpanded: -1,
rowDrag: {
group: 'tasks',
// only folders take children
canDrop: ({ target, position }) => position !== 'inside' || !!target?.folder,
},
treeData: {
getParentId: r => r.parent,
setParentId: (row, parentId) => { row.parent = parentId; },
hasChildren: r => !!r.lazy,
// pretend server: answers after 600 ms
loadChildren: row => new Promise<Row[]>(resolve => setTimeout(() => resolve([
{ id: nextId++, name: h('Old banner', '예전 배너'), parent: row.id },
{ id: nextId++, name: h('2025 report', '2025 보고서'), parent: row.id },
]), 600)),
},
});
const backlog = createGrid(right, {
columns: [{ field: 'name', header: h('Backlog', '백로그'), flex: 1 }],
data: Array.from({ length: 30 }, (_, i) => ({ id: 200 + i, name: `${h('Idea', '아이디어')} ${i + 1}`, parent: null })),
locale: lang, getRowId: r => String(r.id),
rowDrag: { group: 'tasks' },
});
for (const [g, name] of [[tree, h('tasks', '작업')], [backlog, h('backlog', '백로그')]] as const) {
g.on('rowDrop', e => ctx.log(`rowDrop (${name}) → "${e.row.name}" ${e.position} ${e.target ? `"${e.target.name}"` : h('end', '끝')}${e.fromOtherGrid ? h(' from the other grid', ' (다른 그리드에서)') : ''}`));
}
tree.on('childrenLoaded', e => ctx.log(`childrenLoaded → ${e.rows.length}`));
text(ctx, h('Drag a row by its handle: onto the middle of a folder to move it inside, onto an edge to put it before or after. Drag between the two grids too.',
'손잡이로 행을 끌어 보세요. 폴더 가운데에 놓으면 그 안으로, 위·아래 가장자리에 놓으면 앞이나 뒤로 갑니다. 두 그리드 사이로도 옮길 수 있습니다.'));
separator(ctx);
let unlink: (() => void) | null = null;
toggle(ctx, h('Scroll both grids together', '두 그리드 함께 스크롤'), false, on => {
if (on) unlink = tree.syncScroll(backlog, 'vertical');
else unlink?.();
ctx.log(`syncScroll: ${on}`);
});
return tree;
}