그리드 잠금과 조작 이벤트
전체 editable과 위젯 locked를 바꾸고 이동·크기 변경·commit 이벤트를 관찰합니다.
직접 확인할 동작
섹션 제목: “직접 확인할 동작”- A 제목을 드래그합니다.
beforeMove:a뒤에 변경 snapshot의commit, 마지막에afterMove:a가 표시됩니다. - B는 처음부터
locked: true입니다. B를 드래그해도 배치가 바뀌지 않습니다. - 전체 잠금 후에는 A도 이동하거나 크기를 바꿀 수 없습니다. 전체 잠금 해제 후 A의 조작이 다시 가능합니다.
- B 잠금 해제로 B도 조작할 수 있게 합니다.
- A의 오른쪽 아래 resize 손잡이를 움직여
beforeResize:a와afterResize:a를 확인합니다. 변경 배치는 JSON에도 반영됩니다. - 이벤트 지우기는 로그만 비우며 초기화는 초기 배치·잠금 설정을 복원합니다.
두 잠금의 범위
섹션 제목: “두 잠금의 범위”| 설정 | 적용 범위 |
|---|---|
editable={false} |
이 Grid의 포인터 이동·크기 변경을 모두 막습니다. |
widget.locked |
해당 위젯에 개별 제약을 적용합니다. |
commands.updateWidget |
소비자가 소유한 React 위젯 정의를 변경합니다. |
잠금은 사용자 조작 제약이며 애플리케이션 상태 변경 명령을 금지하는 권한 체계가 아닙니다. editable과 위젯 잠금이 모두 허용해야 조작 가능합니다.
이벤트와 상태 연결
섹션 제목: “이벤트와 상태 연결”onBeforeMove, onAfterMove, onBeforeResize, onAfterResize는 { id, layout }를 받는 알림입니다. 반환형이 void이므로 before callback에서 false를 반환하여 취소하는 API가 아닙니다. 차단은 조작 전에 상태 설정으로 반영합니다.
onLayoutCommit은 snapshot을 commands.applyLayoutSnapshot에 전달하여 React 상태를 갱신합니다. 동일 snapshot commit은 억제되므로 매 pointer 입력마다 commit이 한 번 발생한다고 가정하지 않습니다. 초기 엔진 동기화의 commit이 로그에 들어올 수 있으므로 비교 전에 이벤트를 지우십시오. 로그는 최근 16개만 유지합니다.
이 예제는 시작·종료와 commit을 표시합니다. 프레임 단위 onMove/onResize, 픽셀 크기의 onWidgetResizeFrame, command 관찰용 onLayoutMutation은 별도 용도입니다.
관련 문서
섹션 제목: “관련 문서”위젯 조작 가이드 · 이벤트 가이드 · 상태 명령 · Grid ref
직접 실행
섹션 제목: “직접 실행”import { useState } from 'react';import { DashboardGrid, useDashboardGrid } from 'comins-grid-layout';import 'gridstack/dist/gridstack.min.css';import 'comins-grid-layout/styles.css';const engineOptions = { cellHeight: 60, margin: 6, float: true, animate: false, alwaysShowResizeHandle: true };export default function GridLockEventsDemo({ lang = 'ko' }: { lang?: string }) { const ko = lang === 'ko'; const [editable, setEditable] = useState(true); const [events, setEvents] = useState<string[]>([]); const grid = useDashboardGrid({ initialColumns: 12, initialWidgets: [ { id: 'a', title: 'A', layout: { id: 'a', x: 0, y: 0, w: 4, h: 2 } }, { id: 'b', title: 'B', locked: true, layout: { id: 'b', x: 7, y: 0, w: 4, h: 2 } }, ] }); const log = (name: string, id = '') => setEvents(current => [...current, `${name}${id ? ':' + id : ''}`].slice(-16)); const locked = grid.widgets.find(widget => widget.id === 'b')?.locked === true; return <div className="demo grid-demo" data-demo="grid-lock-events"> <div className="demo-toolbar"> <button onClick={() => setEditable(value => !value)}>{editable ? (ko ? '전체 잠금' : 'Lock all') : (ko ? '전체 잠금 해제' : 'Unlock all')}</button> <button onClick={() => grid.commands.updateWidget('b', { locked: !locked })}>{locked ? (ko ? 'B 잠금 해제' : 'Unlock B') : (ko ? 'B 잠금' : 'Lock B')}</button> <button onClick={() => setEvents([])}>{ko ? '이벤트 지우기' : 'Clear events'}</button> <button onClick={() => { grid.commands.resetLayout(); setEditable(true); setEvents([]); }}>{ko ? '초기화' : 'Reset'}</button> </div> <DashboardGrid widgets={grid.widgets} columns={grid.columns} editable={editable} showControls={false} engineOptions={engineOptions} onLayoutCommit={snapshot => { grid.commands.applyLayoutSnapshot(snapshot); log('commit'); }} onBeforeMove={event => log('beforeMove', event.id)} onAfterMove={event => log('afterMove', event.id)} onBeforeResize={event => log('beforeResize', event.id)} onAfterResize={event => log('afterResize', event.id)} renderWidget={widget => <div className="widget-content">{widget.id === 'b' && locked ? (ko ? '잠금 상태' : 'Locked') : (ko ? '이동·크기 변경 가능' : 'Move and resize')}</div>} /> <p role="status">editable: {String(editable)} · B.locked: {String(locked)}</p> <pre data-lock-events>{JSON.stringify(events, null, 2)}</pre> <details><summary>{ko ? '현재 배치' : 'Current layout'}</summary><pre data-lock-layout>{JSON.stringify(grid.commands.serializeLayout(), null, 2)}</pre></details> </div>;}