@vmscloud/moz-ui-grid-react 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,8 @@
7
7
  ## 목차
8
8
 
9
9
  - [기본 플랫 그리드](#기본-플랫-그리드)
10
+ - [행 식별 정책과 자동 ID](#행-식별-정책과-자동-id)
11
+ - [숫자 제약과 날짜시간 편집기](#숫자-제약과-날짜시간-편집기)
10
12
  - [grid.rows 사용 예제](#gridrows-사용-예제)
11
13
  - [grid.columns 사용 예제](#gridcolumns-사용-예제)
12
14
  - [grid.changes 사용 예제](#gridchanges-사용-예제)
@@ -47,6 +49,7 @@
47
49
  ```typescript
48
50
  const grid = new PureSheet(container, {
49
51
  mode: 'flat',
52
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
50
53
  fields: [
51
54
  // id = 컬럼 **식별자**(가리킬 때: 정렬/필터 상태, 폭, 저장 레이아웃).
52
55
  // field = 원본 데이터 **키**(읽을 때: row[field]). 생략하면 id 와 같은 값으로 정규화.
@@ -78,6 +81,104 @@ grid.destroy();
78
81
 
79
82
  ---
80
83
 
84
+ ### 행 식별 정책과 자동 ID
85
+
86
+ `keyFields`의 암묵적 `['id']` 기본값은 없습니다. 원본에 `id` 속성이 있어도 업무 키로 쓰려면 `keyFields: ['id']`를 명시해야 합니다. 컬럼의 `FieldDef.id`는 컬럼 식별자이지 행 키 설정이 아닙니다.
87
+
88
+ | 설정 | 동작 |
89
+ |---|---|
90
+ | `keyFields: ['employeeNo']` | 명시한 업무 키로 식별. 잘못된 키를 자동 ID로 대체하지 않음 |
91
+ | `keyFields` 생략 + `enableReferCache: true` | 내부 자동 ID 사용. 업무 데이터에 합성 키 필드를 추가할 필요 없음 |
92
+ | `keyFields` 생략 + 캐시 생략/`false` | 초기화 오류. 키를 명시하거나 캐시를 켜야 함 |
93
+ | `keyFields: []` | 초기화 오류. 자동 ID를 원하면 속성 자체를 생략 |
94
+
95
+ 명시적 업무 키는 캐시 없이 사용할 수 있지만, ID로 행 데이터를 찾는 API 등 참조 캐시가 필요한 경로는 `enableReferCache: true`를 설정해야 합니다. 대용량에서 캐시를 끈다면 업무 키를 명시하고 지원되는 sourceIndex 기반 API를 사용하세요.
96
+
97
+ ```typescript
98
+ // 업무 키가 있는 경우 — employeeNo는 실제 데이터 필드다.
99
+ const keyedGrid = new PureSheet(container, {
100
+ keyFields: ['employeeNo'],
101
+ enableReferCache: true,
102
+ fields: [{ id: 'name', dataType: 'string', header: '이름' }],
103
+ data: [{ employeeNo: 'E-001', name: '김민수' }],
104
+ });
105
+ await keyedGrid.waitForReady();
106
+ const employeeId = keyedGrid.rows.getByIndex(0).id;
107
+ await keyedGrid.changes.updateCellById(employeeId, 'name', '김민수 수정');
108
+ ```
109
+
110
+ ```typescript
111
+ // 업무 키가 없는 경우 — 같은 내용의 행도 서로 다른 행으로 식별한다.
112
+ const grid = new PureSheet(container, {
113
+ enableReferCache: true, // keyFields 생략: 자동 ID
114
+ fields: [
115
+ { id: 'name', dataType: 'string', header: '이름' },
116
+ { id: 'quantity', dataType: 'number', header: '수량' },
117
+ ],
118
+ data: [{ name: '부품', quantity: 1 }, { name: '부품', quantity: 1 }],
119
+ editable: true,
120
+ rowSelection: { mode: 'multi' },
121
+ });
122
+ await grid.waitForReady();
123
+
124
+ // 원본 객체의 id나 내부 문자열 형식을 추측하지 않고 공개 API의 반환 ID를 사용한다.
125
+ const rowId = grid.rows.getByIndex(1).id;
126
+ grid.rows.selectRowsByIds([rowId]);
127
+ await grid.changes.updateCellById(rowId, 'quantity', 2);
128
+ console.log(grid.rows.getById(rowId).data);
129
+
130
+ const addedId = await grid.changes.addRow({ name: '신규 부품', quantity: 1 });
131
+ await grid.changes.updateCellById(addedId, 'quantity', 3);
132
+ await grid.changes.commit();
133
+ // commit 후에도 반환 ID로 같은 행을 조작한다.
134
+ await grid.changes.deleteRowById(addedId);
135
+ await grid.changes.undeleteRowById(addedId);
136
+ console.log(grid.rows.getById(addedId).data);
137
+ ```
138
+
139
+ 자동 ID는 현재 데이터 수명 안에서 재사용하는 식별자입니다. 정렬·필터·편집·commit·Undo/Redo에 사용하되, `loadData()`로 전체 데이터를 교체한 뒤에는 공개 API에서 다시 얻으세요. 서버의 영속 업무 키나 다음 로드의 ID로 저장하지 마세요. 행 객체 API에는 그리드가 반환한 객체를 사용하고, 동일한 값을 가진 새 객체를 만들어 같은 행이라고 가정하지 마세요.
140
+
141
+ > 아래 API 목록의 `'row-1'`, `'user-5'`, `1` 등은 **실제로 얻은 rowId를 대신 적은 예시 값**입니다. 자동 ID 모드에서는 반환 ID로 바꿔 사용하세요. 업무 키가 필요한 트리 관계나 Excel upsert/충돌 매칭은 별도로 명시적 키를 구성해야 합니다. 신규 행의 업무 키를 생략한 CRUD 예제는 자동 ID 모드를 전제로 합니다.
142
+
143
+ ---
144
+
145
+ ### 숫자 제약과 날짜시간 편집기
146
+
147
+ `id`는 컬럼 식별자, `field`는 원본 데이터 키, `dataType`은 데이터 타입, `editor`는 편집 UI 설정입니다. 날짜시간도 `dataType: 'date'`를 쓰며 `datetime`은 **editor 타입**입니다.
148
+
149
+ ```typescript
150
+ import { PureSheet, type FieldDef } from '@vmscloud/moz-ui-grid-core';
151
+
152
+ const fields: FieldDef[] = [
153
+ {
154
+ id: 'quantity', field: 'quantity', header: '수량', dataType: 'number',
155
+ editor: { type: 'number', min: 0, max: 100, step: 0.5 },
156
+ },
157
+ {
158
+ id: 'scheduledAt', field: 'scheduledAt', header: '예약 시각', dataType: 'date',
159
+ editor: { type: 'datetime' },
160
+ mask: { type: 'date', pattern: 'YYYY-MM-DD HH:mm:ss' },
161
+ },
162
+ ];
163
+ const grid = new PureSheet(container, {
164
+ fields,
165
+ enableReferCache: true,
166
+ editable: true,
167
+ timeZone: 'Asia/Seoul',
168
+ data: [
169
+ { quantity: 1.5, scheduledAt: new Date('2026-10-19T00:30:00Z') },
170
+ { quantity: 0, scheduledAt: null },
171
+ ],
172
+ });
173
+ await grid.waitForReady();
174
+ ```
175
+
176
+ - 숫자의 `min`/`max`는 경계를 포함하며, `step`은 `min`(생략 시 0)을 기준으로 하는 양수 간격입니다. 생략한 제약은 적용되지 않습니다. 범위/간격을 벗어난 입력은 에디터 커밋에서 오류로 표시되며 자동으로 잘라내거나 반올림하지 않습니다. 빈 입력은 `null`입니다. 이 옵션을 모든 프로그램적 mutation의 검증 규칙으로 간주하지 마세요.
177
+ - `datetime`은 `datetime-local` 입력을 사용하지만 커밋 값은 HTML 문자열이 아닌 **`Date | null`**입니다. 위 예제의 최초 표시/편집 시각은 서울 기준 `2026-10-19 09:30:00`이며, 시간대 설정을 생략하면 브라우저 로컬 시간대를 사용합니다. 서버에서 받은 ISO 문자열을 이 계약으로 쓰려면 데이터 준비 단계에서 `Date`로 변환하세요.
178
+ - 기존 `editor: { type: 'date' }`는 날짜 전용 입력과 **문자열 커밋** 동작을 유지합니다. `editor: { type: 'datepicker', dateFormat: 'yyyy-MM-dd' }`의 `dateFormat`도 유지됩니다. 이는 아래 XLSX export에서 제거된 옵션과 별개이며, `mask.pattern`의 `YYYY-MM-DD` 토큰과 혼동하지 마세요.
179
+
180
+ ---
181
+
81
182
  ### grid.rows 사용 예제
82
183
 
83
184
  ```typescript
@@ -174,15 +275,20 @@ const deletedCount = await grid.rows.deleteSelectedRows();
174
275
  const updatedCount = await grid.rows.updateSelectedRows('status', 'approved');
175
276
  const discardedCount = await grid.rows.discardSelectedRows();
176
277
 
177
- // ── 데이터 행 고정 ──
178
- grid.rows.pinTopById('row-1'); // ID로 기존 행을 상단 고정
179
- grid.rows.pinTopByIndex(0); // sourceIndex로 기존 행을 상단 고정
180
- grid.rows.pinBottomById('row-2'); // ID로 기존 행을 하단 고정
181
- grid.rows.pinBottomByIndex(1); // sourceIndex로 기존 행을 하단 고정
278
+ // ── 데이터 행 고정 (본문 행을 고정 영역으로 옮김 — setProps({ pinned })와 동일, 본문에서는 빠짐) ──
279
+ grid.rows.pinTopById('row-1'); // ID로 기존 행을 상단 고정 영역으로 옮김
280
+ grid.rows.pinTopByIndex(0); // sourceIndex로 기존 행을 상단 고정 영역으로 옮김
281
+ grid.rows.pinBottomById('row-2'); // ID로 기존 행을 하단 고정 영역으로 옮김
282
+ grid.rows.pinBottomByIndex(1); // sourceIndex로 기존 행을 하단 고정 영역으로 옮김
283
+ grid.rows.unpinById('row-1'); // 본문 원래 위치로 복귀
182
284
 
183
- // ── 구조적 행 고정 ──
184
- const pinnedRow = grid.rows.addPinnedTop({ id: 'summary', label: '요약', amount: 9999 });
185
- grid.rows.addPinnedBottom({ id: 'footer', label: '합계' });
285
+ // ── 구조적 행 고정 (RowConfig: 데이터는 data 안에 지정) ──
286
+ const pinnedRow = grid.rows.addPinnedTop({
287
+ id: 'summary', structural: true, data: { label: '요약', amount: 9999 },
288
+ });
289
+ grid.rows.addPinnedBottom({
290
+ id: 'footer', structural: true, data: { label: '합계' },
291
+ });
186
292
 
187
293
  // ── 고정 관리 ──
188
294
  grid.rows.unpinById('summary');
@@ -464,6 +570,8 @@ grid.on('rowGroup:checkbox', (e) => console.log(e.groupId, e.checked, e.memberCo
464
570
 
465
571
  ### grid.tree 사용 예제
466
572
 
573
+ 아래 관계/노드 ID 예제는 생성 시 `keyFields: ['id']`, `enableReferCache: true`를 설정하고 실제 `id`/`parentId` 데이터를 로드한 그리드를 전제로 합니다. `treeConfig.idField`만 지정한다고 행 업무 키의 암묵적 기본값이 복원되지는 않습니다.
574
+
467
575
  ```typescript
468
576
  // ── 트리 설정 (flat 구조 — parentId 기반) ──
469
577
  await grid.tree.setConfig({
@@ -548,6 +656,8 @@ const config = grid.tree.getConfig();
548
656
  ```typescript
549
657
  // ── 트리 드래그 reorder 활성화 (treeConfig + reorderableRows) ──
550
658
  const grid = new PureSheet(container, {
659
+ keyFields: ['id'], // 트리 관계에 쓰는 업무 키를 명시
660
+ enableReferCache: true,
551
661
  treeConfig: { idField: 'id', parentField: 'parentId', treeColumn: 'name' },
552
662
  dragDropConfig: {
553
663
  reorderableRows: true, // treeConfig와 함께 → 트리 인식 드래그 자동 활성
@@ -705,7 +815,7 @@ const series = await grid.pivot.getSeriesData(); // 차트용 시리즈
705
815
  await grid.masterDetail.setConfig({
706
816
  expandColumn: 'name',
707
817
  singleExpand: false,
708
- keepExpandedOnDataChange: false,
818
+ keepExpandedOnDataChange: true, // 기본값 — 정렬·필터·재조회(같은 keyFields 키) 뒤에도 펼친 채 유지. false 면 모두 접힘
709
819
  keepMasterDetailRows: true,
710
820
  keepMasterDetailRowsCount: 10,
711
821
  minHeight: 100,
@@ -888,6 +998,63 @@ grid.headers.expandAtLevel(0); // level 0 그룹만 펼치기
888
998
 
889
999
  ### grid.io 사용 예제
890
1000
 
1001
+ 업무 키로 기존 행을 매칭하는 import 예제(`upsert`, append 충돌 처리 등)는 생성 시 `keyFields`를 명시한 그리드를 사용하세요. 자동 ID는 Excel 파일에 담긴 업무 키가 아니므로 재가져오기에서 같은 행을 식별하는 키로 간주하면 안 됩니다.
1002
+
1003
+ #### 컬럼별 XLSX 날짜 서식과 구조적 고정 행
1004
+
1005
+ 공개 `toExcel`/`saveExcelToFile` 옵션의 `dateFormat`은 제거되었습니다. 날짜 표시를 보존하려면 각 `dataType: 'date'` 필드에 구조화된 `mask`를 지정하고 `preserveFormatting: true`를 사용하세요. 날짜 편집기의 `dateFormat`과는 별개입니다.
1006
+
1007
+ ```typescript
1008
+ import { PureSheet, type FieldDef } from '@vmscloud/moz-ui-grid-core';
1009
+
1010
+ const fields: FieldDef[] = [
1011
+ { id: 'name', header: '항목', dataType: 'string' },
1012
+ {
1013
+ id: 'day', header: '기준일', dataType: 'date',
1014
+ mask: { type: 'date', pattern: 'YYYY-MM-DD' },
1015
+ },
1016
+ {
1017
+ id: 'recordedAt', header: '기록 시각', dataType: 'date',
1018
+ mask: { type: 'date', pattern: 'YYYY-MM-DD HH:mm:ss' },
1019
+ },
1020
+ ];
1021
+ const grid = new PureSheet(container, {
1022
+ fields,
1023
+ enableReferCache: true,
1024
+ timeZone: 'Asia/Seoul',
1025
+ data: [{
1026
+ name: '입고',
1027
+ day: new Date('2026-10-19T00:00:00+09:00'),
1028
+ recordedAt: new Date('2026-10-19T09:30:45+09:00'),
1029
+ }],
1030
+ });
1031
+ await grid.waitForReady();
1032
+ grid.rows.addPinnedTop({
1033
+ id: 'export-note', structural: true, data: { name: '서울 기준 입고 내역' },
1034
+ });
1035
+
1036
+ const blob = await grid.io.toExcel({
1037
+ scope: 'all',
1038
+ preserveFormatting: true,
1039
+ structuralPinnedRows: 'include',
1040
+ });
1041
+ // day → yyyy-mm-dd, recordedAt → yyyy-mm-dd hh:mm:ss (Excel numFmt).
1042
+ // blob 다운로드는 아래 기본 export 예제와 동일하다.
1043
+
1044
+ // 직접 파일 저장도 같은 컬럼별 서식/고정 행 옵션을 사용한다.
1045
+ await grid.io.saveExcelToFile({
1046
+ suggestedName: 'receipts.xlsx',
1047
+ preserveFormatting: true,
1048
+ structuralPinnedRows: 'include',
1049
+ });
1050
+ ```
1051
+
1052
+ - `preserveFormatting` 기본값은 `false`입니다. 생략/`false`, 날짜 mask 없음 또는 지원하지 않는 날짜 mask는 기본 Excel 날짜 서식 `yyyy-mm-dd`를 사용합니다. 지원 불가 패턴은 `PS005` 경고를 냅니다. 표시 서식은 날짜 값의 시간 정보를 잘라내는 옵션이 아닙니다.
1053
+ - `structuralPinnedRows` 기본값은 `'exclude'`입니다. `'include'`는 **상단 고정 → 본문 → 하단 고정** 순서로 구조적 행의 현재 typed 집계값과 원시 라벨 데이터를 내보냅니다. `scope: 'selected'`에서는 선택된 고정 행만 포함합니다.
1054
+ - 이 옵션은 원본(`exportSource: true`)·피벗 export 및 CSV에는 적용되지 않습니다. DOM/`formatRow` 결과나 집계 formatter가 만든 화면 문자열을 직렬화하는 옵션도 아닙니다.
1055
+
1056
+ #### 기타 IO 작업
1057
+
891
1058
  ```typescript
892
1059
  // ── 1) 기본 export (메모리 Blob) ──
893
1060
  const blob = await grid.io.toExcel({
@@ -1722,6 +1889,7 @@ import {
1722
1889
  } from '@vmscloud/moz-ui-grid-core';
1723
1890
 
1724
1891
  const grid = new PureSheet(el, {
1892
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1725
1893
  fields,
1726
1894
  // 옛 저장본을 현재 스키마로 올린다. core 는 호출 시점·순서만 책임지고,
1727
1895
  // 어떤 버전에서 무엇을 바꿀지는 스키마를 아는 소비처가 정한다.
@@ -1781,17 +1949,20 @@ moz-ui-grid-core는 행 선택과 셀 선택을 **독립된 두 옵션**(`rowSel
1781
1949
  ```typescript
1782
1950
  // ── 행 전용 (단순 list 그리드) ──
1783
1951
  new PureSheet(container, {
1952
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1784
1953
  rowSelection: { mode: 'multi' },
1785
1954
  });
1786
1955
  // trigger 미지정 → 자동 'cellClick' (셀 클릭으로 행 선택)
1787
1956
 
1788
1957
  // ── 셀 전용 (Excel 스타일) ──
1789
1958
  new PureSheet(container, {
1959
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1790
1960
  cellSelection: { mode: 'range' },
1791
1961
  });
1792
1962
 
1793
1963
  // ── 셀+행 분리: 행 헤더 클릭으로 행 선택 (정합성 보호) ──
1794
1964
  new PureSheet(container, {
1965
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1795
1966
  rowSelection: { mode: 'multi' }, // trigger 자동 = 'rowHeader'
1796
1967
  cellSelection: { mode: 'range' },
1797
1968
  });
@@ -1799,18 +1970,21 @@ new PureSheet(container, {
1799
1970
 
1800
1971
  // ── 체크박스 트리거 ──
1801
1972
  new PureSheet(container, {
1973
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1802
1974
  rowSelection: { mode: 'multi', trigger: 'checkbox' },
1803
1975
  });
1804
1976
  // _rowHeader 컬럼 자동 주입 + CSS-only 체크박스 자동 렌더.
1805
1977
 
1806
1978
  // ── 트리거 복수 지정 (행 헤더 + 체크박스) ──
1807
1979
  new PureSheet(container, {
1980
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1808
1981
  rowSelection: { mode: 'multi', trigger: ['rowHeader', 'checkbox'] },
1809
1982
  cellSelection: { mode: 'range' },
1810
1983
  });
1811
1984
 
1812
1985
  // ── 셀 클릭이 행도 선택 (legacy 'all' 동작) ──
1813
1986
  new PureSheet(container, {
1987
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1814
1988
  rowSelection: { mode: 'multi', trigger: 'cellClick' }, // 명시 지정
1815
1989
  cellSelection: { mode: 'range' },
1816
1990
  });
@@ -1839,6 +2013,7 @@ AG Grid의 `ctrlASelectsRows` 옵션과 같은 의미.
1839
2013
  ```typescript
1840
2014
  // 기본 — 셀만 전체 선택 (AG Grid와 동일)
1841
2015
  new PureSheet(container, {
2016
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1842
2017
  rowSelection: { mode: 'multi' },
1843
2018
  cellSelection: { mode: 'range' },
1844
2019
  // rowSelection.includeInSelectAll: false (기본)
@@ -1846,6 +2021,7 @@ new PureSheet(container, {
1846
2021
 
1847
2022
  // Ctrl+A로 행도 함께 fill
1848
2023
  new PureSheet(container, {
2024
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1849
2025
  rowSelection: { mode: 'multi', includeInSelectAll: true },
1850
2026
  cellSelection: { mode: 'range' },
1851
2027
  });
@@ -1865,6 +2041,7 @@ new PureSheet(container, {
1865
2041
  ```typescript
1866
2042
  // 기본 (Policy A — 엄격 분리)
1867
2043
  new PureSheet(container, {
2044
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1868
2045
  rowSelection: { mode: 'multi' },
1869
2046
  cellSelection: { mode: 'range' },
1870
2047
  // cellSelectionImpliesRows: false (기본)
@@ -1873,6 +2050,7 @@ new PureSheet(container, {
1873
2050
 
1874
2051
  // Policy B — Excel/Google Sheets 동작
1875
2052
  new PureSheet(container, {
2053
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
1876
2054
  rowSelection: { mode: 'multi' },
1877
2055
  cellSelection: { mode: 'range' },
1878
2056
  cellSelectionImpliesRows: true,
@@ -2132,6 +2310,7 @@ const chartField = {
2132
2310
 
2133
2311
  ```typescript
2134
2312
  new PureSheet(el, {
2313
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
2135
2314
  fields,
2136
2315
  rowSelection: { mode: 'multi', trigger: 'checkbox' },
2137
2316
  rowHeader: {
@@ -2201,6 +2380,7 @@ grid.formatRow.removeHandler('highlight');
2201
2380
 
2202
2381
  ```typescript
2203
2382
  new PureSheet(el, {
2383
+ enableReferCache: true, // keyFields 생략: 내부 자동 ID 사용
2204
2384
  fields,
2205
2385
  formatHeader: (ctx) => {
2206
2386
  // 정렬된 컬럼에 data-* 마커