@vmscloud/moz-ui-grid-react 0.1.26 → 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.
@@ -1,7 +1,7 @@
1
1
  # MozGrid React 레퍼런스 (Props · 이벤트 · Slot · 인스턴스 API)
2
2
 
3
3
  > 패키지: `@vmscloud/moz-ui-grid-react`
4
- > 마지막 업데이트: 2026-06-24
4
+ > 검증 기준: `0481bd5` (main 리베이스 후 소스)
5
5
 
6
6
  > 📋 이 문서는 **React 래퍼 표면만** 다룹니다. 그리드 본체(코어) 설정·타입·이벤트 페이로드는 [reference.md](reference.md)를 참조하세요.
7
7
  > 📋 사용 예제 코드는 [examples.md](examples.md)를 참조하세요.
@@ -12,8 +12,8 @@
12
12
  import { MozGrid, MozGridConfigProvider, useMozGridConfig, type MozGridConfig } from '@vmscloud/moz-ui-grid-react';
13
13
 
14
14
  <MozGridConfigProvider config={{ locale: 'ko', timeZone: 'Asia/Seoul' }}>
15
- <MozGrid coreConfig={{ fields, data }} />
16
- <MozGrid coreConfig={{ fields, data, timeZone: 'UTC' }} locale="en" />
15
+ <MozGrid coreConfig={{ enableReferCache: true, fields, data }} />
16
+ <MozGrid coreConfig={{ enableReferCache: true, fields, data, timeZone: 'UTC' }} locale="en" />
17
17
  </MozGridConfigProvider>
18
18
  ```
19
19
 
@@ -33,6 +33,7 @@ import { MozGrid, MozGridConfigProvider, useMozGridConfig, type MozGridConfig }
33
33
  - [타입 재노출](#타입-재노출)
34
34
  - [Props](#props)
35
35
  - [coreConfig (controlled core 설정)](#coreconfig-controlled-core-설정)
36
+ - [초기화·편집·XLSX 설정 전달](#초기화편집xlsx-설정-전달)
36
37
  - [WrapperExtras (래퍼 전용 옵션)](#wrapperextras-래퍼-전용-옵션)
37
38
  - [템플릿 맵 (cellTemplates / headerTemplates / editorTemplates)](#템플릿-맵-celltemplates--headertemplates--editortemplates)
38
39
  - [이벤트 콜백 props](#이벤트-콜백-props)
@@ -46,6 +47,7 @@ import { MozGrid, MozGridConfigProvider, useMozGridConfig, type MozGridConfig }
46
47
  - [EmptyStateConfig (emptyState)](#emptystateconfig-emptystate)
47
48
  - [PivotSourceField (sourceFields)](#pivotsourcefield-sourcefields)
48
49
  - [chartMap / ChartSeriesConfig](#chartmap--chartseriesconfig)
50
+ - [chartConfig / PivotChartConfig (내장 피벗 차트 설정)](#chartconfig--pivotchartconfig-내장-피벗-차트-설정)
49
51
  - [locale](#locale)
50
52
  - [Legacy exports](#legacy-exports)
51
53
  - [관련 문서](#관련-문서)
@@ -62,11 +64,12 @@ import { MozGrid, MozGridConfigProvider, useMozGridConfig, type MozGridConfig }
62
64
  - `coreConfig` prop은 코어 설정(`PureSheetConfig`)을 평탄화한 객체이며, 본 문서에서는 필드별 상세를 다루지 않고 **[reference.md](reference.md)로 위임**합니다.
63
65
  - 본 문서는 **래퍼 표면**(래퍼 전용 옵션, 템플릿, 슬롯, 이벤트 콜백, 인스턴스 핸들)만 문서화합니다.
64
66
 
65
- 패키지 진입점(`src/index.ts`)은 `MozGrid` 관련 3개의 신규 심볼과, `./legacy`의 전체 재노출(비파괴)로 구성됩니다.
67
+ 패키지 진입점은 `MozGrid`와 핸들/props 타입, 설정·locale Provider/Context/훅, context-wrapper 훅/Context 및 `GridChrome`/`PureSheet` 타입을 내보냅니다. 아래는 주요 export 일부입니다. 현재 진입점은 `./legacy`를 재노출하지 않습니다.
66
68
 
67
69
  ```ts
68
70
  export { MozGrid, type MozGridProps, type MozGridHandle } from './MozGrid';
69
- export * from './legacy';
71
+ export type { GridChrome } from '@vmscloud/moz-ui-grid-wrapper';
72
+ export type { PureSheet } from '@vmscloud/moz-ui-grid-core';
70
73
  ```
71
74
 
72
75
  > ⚠️ `MozGridCoreProps` / `WrapperExtras` 같은 래퍼 타입은 **이 패키지에서 재노출되지 않습니다.** 필요하면 `@vmscloud/moz-ui-grid-wrapper`에서 직접 import 하세요(아래 코드 참조).
@@ -99,9 +102,9 @@ pnpm add @vmscloud/moz-ui-grid-react react react-dom
99
102
  import '@vmscloud/moz-ui-grid-wrapper/style.css';
100
103
  ```
101
104
 
102
- > ⚠️ 코어 그리드 본체 스타일(`.ps-*` 클래스)은 **래퍼 패키지의 `style.css`에만** 포함됩니다 — 새 `MozGrid`는 바닐라 `GridChrome`을 마운트하고 호스트 스타일은 inline이라, **위 한 줄이면 충분**합니다(래퍼는 react 패키지의 런타임 의존성).
105
+ > ⚠️ 코어 그리드 본체 스타일(`.ps-*` 클래스)은 **래퍼 통합 스타일에** 포함됩니다 — 새 `MozGrid`는 바닐라 `GridChrome`을 마운트하고 호스트 스타일은 inline이라, **위 한 줄이면 충분**합니다(래퍼는 react 패키지의 런타임 의존성).
103
106
  >
104
- > 참고: `@vmscloud/moz-ui-grid-react/style.css` 서브패스도 존재하지만 이는 주로 **legacy React 컴포넌트**(radix 기반)용입니다. 새 `MozGrid`만 쓴다면 import하지 않아도 됩니다.
107
+ > `@vmscloud/moz-ui-grid-react/style.css`도 코어 + 래퍼 통합 스타일을 포함합니다. React 패키지 엔트리가 wrapper 통합 CSS를 빌드에 포함하므로, 위 wrapper 스타일 대신 이 서브패스를 사용할 수도 있습니다.
105
108
 
106
109
  ### 모듈 포맷
107
110
 
@@ -128,6 +131,7 @@ function Example() {
128
131
  // coreConfig는 controlled — 내부 변경(피벗/그룹/옵션 모달)이 onCoreConfigChange로 역류한다
129
132
  const [coreConfig, setCoreConfig] = useState<MozGridCoreProps>({
130
133
  mode: 'flat',
134
+ keyFields: ['id'], // 업무 키를 명시 (원본 id 필드가 자동 선택되지는 않음)
131
135
  fields: [
132
136
  // id = 컬럼 식별자(가리킬 때), field = 원본 데이터 키(읽을 때, 생략 시 id).
133
137
  { id: 'name', header: '이름', dataType: 'string', width: 200 },
@@ -150,7 +154,7 @@ function Example() {
150
154
  useContextMenu
151
155
  onReady={(grid: PureSheet, chrome: GridChrome) => {
152
156
  // grid === 코어 PureSheet (reference.md), chrome === wrapper GridChrome
153
- chrome.setPivotBarVisible(true); // 피벗 옵션 바를 처음부터 켜 두기
157
+ chrome.setToolbarVisible(false); // 툴바를 처음부터 접기(펼치기 토글은 유지)
154
158
  }}
155
159
  onRowClick={(payload) => console.log('row:click', payload)}
156
160
  onHeaderFilterClick={(payload) => console.log('필터 아이콘 클릭', payload)}
@@ -185,7 +189,8 @@ type MozGridProps = WrapperExtras & {
185
189
  cellTemplates?: Record<string, ComponentType<CellSlotProps>>;
186
190
  headerTemplates?: Record<string, ComponentType<HeaderSlotProps>>;
187
191
  editorTemplates?: Record<string, ComponentType<EditorSlotProps>>;
188
- // 7개 ReactNode 슬롯 props + 34개 on* 이벤트 콜백 props (아래 표 참조)
192
+ groupTemplate?: ComponentType<GroupSlotProps>;
193
+ // 8개 셸 슬롯 props + on* 이벤트 콜백 props (아래 표 참조)
189
194
  };
190
195
  ```
191
196
 
@@ -213,9 +218,32 @@ type MozGridCoreProps =
213
218
  - 셀/헤더/에디터 React 템플릿은 `coreConfig.fields`에 병합됩니다(아래 [템플릿 맵](#템플릿-맵-celltemplates--headertemplates--editortemplates) 참조). 이 병합은 **raw `fields` 참조가 변경될 때만** 수행됩니다(불필요한 `updateFields` 방지).
214
219
  - `locale`(래퍼 prop)은 생성 시점에 코어 설정에도 접혀 들어갑니다(코어 PureSheet 생성 시 필요).
215
220
 
221
+ ### 초기화·편집·XLSX 설정 전달
222
+
223
+ - **행 식별 설정은 필수**입니다. 업무 키가 있으면 `coreConfig.keyFields`를 명시하고, 키 없이 시작하려면 `coreConfig.enableReferCache: true`를 설정하세요(기본 `false`). 두 설정 모두 생략하거나 `keyFields: []`를 전달하면 초기화할 수 없습니다. 원본 `id` 필드를 자동으로 키로 선택하지 않습니다. 자동 ID 수명과 행 객체 참조 계약은 [코어 Config](reference.md#config-설정)를 참조하세요.
224
+ - 내장 숫자/날짜시간 편집기는 별도 래퍼 prop이 아니라 `coreConfig.fields[].editor`로 전달합니다. 아래 설정을 기존 `coreConfig`에 병합하세요.
225
+
226
+ ```ts
227
+ import type { MozGridCoreProps } from '@vmscloud/moz-ui-grid-wrapper';
228
+ // coreConfig의 일부 — data/keyFields 등은 기존 설정 유지
229
+ const editingConfig: MozGridCoreProps = {
230
+ editable: true,
231
+ timeZone: 'Asia/Seoul',
232
+ fields: [
233
+ { id: 'quantity', dataType: 'number', editor: { type: 'number', min: 0, max: 100, step: 0.5 } },
234
+ { id: 'scheduledAt', dataType: 'date', editor: { type: 'datetime' },
235
+ mask: { type: 'date', pattern: 'YYYY-MM-DD HH:mm:ss' } },
236
+ ],
237
+ };
238
+ ```
239
+
240
+ `datetime`은 **에디터 타입**이며 날짜 필드의 `dataType`은 `'date'`입니다. 숫자 경계/step 검증, `Date | null` 커밋, 시간대 처리는 [EditorConfig](reference.md#editorconfig) 및 [날짜 시간대 계약](reference.md#날짜-시간대-계약-timezone)을 따릅니다. 필드 변경은 새 `fields` 배열로 전달하세요.
241
+
242
+ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을 전달합니다. 예: `{ clientExportOptions: { preserveFormatting: true } }`. 래퍼 `ExcelController`는 기본 `true`이며, 코어 `grid.io`를 직접 호출할 때 기본은 `false`입니다. 고정 행(`includePinnedRows`)도 같습니다: 컨텍스트 메뉴 XLSX/CSV는 화면에 보이는 고정 행(구조·데이터 고정)을 화면 순서로 포함하고(`true`), 끄려면 `clientExportOptions: { includePinnedRows: false }`, 코어 직접 호출 기본은 `false`입니다. 날짜 Excel 서식은 위와 같은 구조화된 `mask`에서 변환됩니다. **export 옵션 `dateFormat`은 제거**되었으므로 전달하지 마세요(에디터 `dateFormat`과는 별개). 지원 패턴·fallback·원본 날짜 값 계약은 [Excel Import/Export](reference.md#gridio--excel-importexport-api)를 참조하세요.
243
+
216
244
  ### WrapperExtras (래퍼 전용 옵션)
217
245
 
218
- `WrapperExtras`는 그리드가 런타임에 변경하지 않는 **래퍼 전용 입력**으로, `coreConfig`에 흡수되지 않고 **개별(uncontrolled) props**로 전달됩니다. `WrapperExtras`는 `WrapperUIOptions`(12개 UI 플래그)를 상속합니다.
246
+ `WrapperExtras`는 그리드가 런타임에 변경하지 않는 **래퍼 전용 입력**으로, `coreConfig`에 흡수되지 않고 **개별(uncontrolled) props**로 전달됩니다. `WrapperExtras`는 `WrapperUIOptions`(UI 플래그)를 상속합니다.
219
247
 
220
248
  > 미설정 UI 플래그는 React가 `undefined`로 전달하며, 래퍼 `resolveUIOptions`가 `DEFAULT_UI_OPTIONS`를 적용합니다(Vue와 달리 React는 미설정 boolean을 `false`로 강제 변환하지 않음 — 그래서 Vue 의 `withDefaults` 같은 복원이 필요 없습니다).
221
249
 
@@ -236,15 +264,23 @@ type MozGridCoreProps =
236
264
  | `useCustomCell` | `boolean` | `true` | 조건부 서식(커스텀 셀) 활성화 |
237
265
  | `useContextMenu` | `boolean` | `true` | 컨텍스트 메뉴 활성화 |
238
266
  | `useBuiltinTooltip` | `boolean` | `true` | 내장 셀/헤더 툴팁 렌더러 사용. `false`면 wrapper가 툴팁을 그리지 않음 — 코어 `cell:tooltip` 이벤트는 그대로 발행되므로 소비처가 `onReady`의 `grid.on('cell:tooltip', …)`로 직접 렌더 가능(발행은 `tooltipMode`로 제어) |
267
+ | `toolbarConfig` | `{ filter?: { disabled?: boolean; hidden?: boolean } }` | — | 툴바 **고급 필터 버튼만** 제어. `filter.disabled`: 버튼은 보이되 비활성(실제 비활성 = 필드 없음 || `disabled`). `filter.hidden`: 필터 버튼만 제거, 나머지 버튼 배치 유지. 다른 툴바 버튼·헤더 필터 아이콘(`showFilterIcon`)·컨텍스트 메뉴(`contextMenuConfig.useFilter`)·걸린 필터 조건은 영향 없음. 비활성으로 바뀌어도 열린 고급 필터 창은 닫지 않음. 객체 단위 교체(생략한 키는 `false`). 런타임 변경 지원(값 비교 — 매 렌더 새 객체 리터럴이어도 같은 값이면 no-op). 화면 제어 입력이라 프리셋에 저장되지 않음 |
239
268
  | `usePivot` | `boolean` | `false` | 피벗 패널 버튼 표시 |
240
269
  | `usePivotBar` | `boolean` | `true` | **피벗 옵션 바(툴박스 아래 행/열/값 한 줄) 사용 여부** — 피벗 모드 전용 (ITSM-2026-002154). 아래 주석 참조 |
241
270
  | `useChart` | `boolean` | `false` | 피벗 차트 버튼 표시 (피벗 모드 전용). 생성 시에만 전달되며 **런타임 변경 경로 없음** |
242
271
  | `loading` | `boolean` | `false` | 로딩 오버레이 강제 표시(호스트 비동기 상태). `false`라도 내부 비동기 작업(`activity !== 'idle'` 이며 검색 아님) 중에는 스피너가 자동 표시됨 |
243
272
 
273
+ > **툴바 필터 버튼만 막기** (예: 노드 필터 중에는 고급 필터 진입 차단)
274
+ > ```tsx
275
+ > <MozGrid coreConfig={cfg} toolbarConfig={{ filter: { disabled: isNodeFiltered } }} />
276
+ > // 버튼 자체를 없애려면 toolbarConfig={{ filter: { hidden: true } }}
277
+ > ```
278
+
279
+
244
280
  > **`usePivotBar` — "지금 보이는가"가 아니라 "이 화면에서 바를 쓰는가" (ITSM-2026-002154)**
245
281
  > 표시/숨김은 사용자가 런타임에 바꾸는 **상태**이므로 prop 이 아니라 `GridChrome.setPivotBarVisible()` 이 소유합니다. 초기에 켜 두려면 `onReady` 의 2번째 인자로 오는 chrome 에서 호출하세요:
246
282
  > ```tsx
247
- > <MozGrid usePivotBar onReady={(grid, chrome) => chrome.setPivotBarVisible(true)} />
283
+ > <MozGrid coreConfig={coreConfig} usePivotBar onReady={(grid, chrome) => chrome.setPivotBarVisible(true)} />
248
284
  > ```
249
285
  >
250
286
  > 🚨 **`false` 는 바를 숨기는 것이 아니라 피벗의 정렬·필터 진입점을 함께 제거합니다:** 피벗은 헤더 클릭으로 정렬하지 않고(헤더 클릭이 바를 켜서 정렬 지점을 안내), 피벗 헤더에는 필터 아이콘이 없어 값 목록 필터의 앵커가 바 아이템의 아이콘뿐입니다. 그래서 기본값이 `true` 이며 `false` 는 "피벗을 읽기 전용 표로만 쓴다"는 선언에 가깝습니다.
@@ -254,16 +290,17 @@ type MozGridCoreProps =
254
290
  | 속성 | 타입 | 기본값 | 설명 |
255
291
  |------|------|--------|------|
256
292
  | `height` | `string \| number` | `'100%'` | 호스트 컨테이너 높이. `number`→`${h}px`, `string`→그대로. **어댑터 스타일 전용** — 코어/셸 옵션에 전달되지 않음 |
257
- | `locale` | `string` | `'ko'` | UI 언어. `'ko' \| 'en' \| 'ja' \| 'zh'` 또는 접두 형식(`'en-US'` 등). **생성 시 1회만 적용 — 런타임 전환 미지원**. 코어 설정과 래퍼 옵션 양쪽에 전달됨. [locale](#locale) 참조 |
293
+ | `locale` | `string` | `'ko'` | UI 언어. `'ko' \| 'en' \| 'ja' \| 'zh'` 또는 접두 형식(`'en-US'` 등). 변경 시 어댑터가 상태 보존 재생성하여 적용. 코어 설정과 래퍼 옵션 양쪽에 전달됨. [locale](#locale) 참조 |
258
294
  | `sourceFields` | `PivotSourceField[]` | `undefined` | 피벗 배치 후보 필드 카탈로그(래퍼 전용, 코어 미전달). [PivotSourceField](#pivotsourcefield-sourcefields) 참조 |
259
295
  | `contextMenuConfig` | `IContextMenuConfig` | `undefined` | 컨텍스트 메뉴 설정(빌트인 항목 토글 + customMenu 커스텀 항목). [IContextMenuConfig](#icontextmenuconfig-contextmenuconfig) 참조 |
260
296
  | `emptyState` | `EmptyStateConfig` | `undefined` (미설정 시 i18n 기본 적용) | 빈 상태 오버레이 설정. `empty-state` 슬롯과 조합 가능. [EmptyStateConfig](#emptystateconfig-emptystate) 참조 |
261
297
  | `useMultipleChart` | `boolean` | `true` | 피벗 차트: Multiple(행별 미니 차트) 모드 |
262
298
  | `chartIncludeGrandTotal` | `boolean` | `false` | 피벗 차트: 총합(Grand Total) 카테고리 포함 |
263
299
  | `chartMap` | `Record<string, ChartSeriesConfig>` | `undefined` | 피벗 차트: 시리즈별 type/color 설정. [chartMap](#chartmap--chartseriesconfig) 참조 |
300
+ | `chartConfig` | `PivotChartConfig` | `undefined` | 내장 피벗 차트 설정(`transformData`/`xAxisLabelFormatter`/`accumulate`/제외 목록). [chartConfig](#chartconfig--pivotchartconfig-내장-피벗-차트-설정) 참조 |
264
301
 
265
302
  > `WrapperExtras`에는 `detail`/디테일-템플릿 키가 **없습니다**(코어/래퍼 어디에도 해당 필드 없음).
266
- > UI 플래그는 생성 시 `buildChromeOptions`로, 런타임 변경 시 `setUIOptions`로 GridChrome에 전달됩니다(`setUIOptions` 가 처리하는 플래그는 useToolBox·useToolBoxSetting·useExtendFooter·useSummaryFooter·useSort·useCustomCell·useContextMenu·useBuiltinTooltip·usePivot·**usePivotBar**·loading 11종). `useChart`/`sourceFields`/`useMultipleChart`/`chartIncludeGrandTotal`/`chartMap`/`emptyState` 는 런타임 변경 경로 없음 — 생성 시에만 적용.
303
+ > UI 플래그는 생성 시 `buildChromeOptions`로, 런타임 변경 시 `setUIOptions`로 GridChrome에 전달됩니다(`setUIOptions` 가 처리하는 플래그는 useToolBox·useToolBoxSetting·useExtendFooter·useSummaryFooter·useSort·useCustomCell·useContextMenu·useBuiltinTooltip·toolbarConfig·usePivot·**usePivotBar**·useRowGroupSettings·useFieldSettings·useFormatSettings·useLayoutSettings·useAggregateSettings·loading). `useChart`/`sourceFields`/`useMultipleChart`/`chartIncludeGrandTotal`/`chartMap`/`emptyState` 는 런타임 변경 경로 없음 — 생성 시에만 적용. `chartConfig` 는 전용 `useEffect` 로 `chrome.setChartConfig` 에 전파됩니다(함수 identity 비교).
267
304
  >
268
305
  > ⚠️ **`contextMenuConfig` 는 이제 런타임 전파됩니다** (ITSM-2026-002154). 이전에는 생성 시 스냅샷만 남아, 소비자가 `customMenu`/`exportFileName` 을 비동기로 확정하는 경우(메뉴 이름 lookup 응답 후 등) 그대로 유실됐습니다. 어댑터가 전용 `useEffect` 로 `chrome.setContextMenuConfig(props.contextMenuConfig)` 를 호출합니다.
269
306
 
@@ -280,6 +317,10 @@ type MozGridCoreProps =
280
317
  내부적으로 어댑터는 매번 새 `templateRegistry`(`createTemplateRegistry`)를 만들어 각 엔트리를 `{ type, fieldId, component }`로 등록하고 `buildFieldDefs(rawFields, registry, renderer)`를 실행합니다. 이 변환은 **raw `fields` 참조가 변경될 때만** 수행됩니다(메모이즈). 템플릿 컴포넌트가 받는 props 타입은 [템플릿 컴포넌트 props 타입](#템플릿-컴포넌트-props-타입)을 참조하세요.
281
318
 
282
319
  ```tsx
320
+ import type { ComponentProps } from 'react';
321
+ import type { MozGridProps } from '@vmscloud/moz-ui-grid-react';
322
+ type CellSlotProps = ComponentProps<NonNullable<MozGridProps['cellTemplates']>[string]>;
323
+
283
324
  function StatusCell({ value, rowData, columnId, rowId }: CellSlotProps) {
284
325
  return <span className={`badge badge-${value}`}>{String(value)}</span>;
285
326
  }
@@ -290,6 +331,25 @@ function StatusCell({ value, rowData, columnId, rowId }: CellSlotProps) {
290
331
  />;
291
332
  ```
292
333
 
334
+ ### 그룹 템플릿 (groupTemplate)
335
+
336
+ | 속성 | 타입 | 기본값 | 설명 |
337
+ |------|------|--------|------|
338
+ | `groupTemplate` | `ComponentType<GroupSlotProps>` | `undefined` | 그룹 헤더 템플릿 (Vue `<GroupTemplate>` 대응). 보이는 **그룹 배너 안**에 마운트되어 배너의 라벨·건수·집계 자리를 대체합니다. 펼침/접힘 토글 버튼은 코어가 유지하고, 배너 본문 클릭은 그룹 토글로 동작합니다(`rowGroupConfig.selectable: true`면 본문 클릭은 그룹 선택, 토글은 ▾ 버튼만). 템플릿 요소의 이벤트(툴팁 등)는 템플릿이 그대로 받습니다. |
339
+
340
+ - 래퍼 예약 `formatRow` 키 `_groupTemplate`으로 등록됩니다(첫 렌더 전). prop을 바꾸거나 제거하면 재생성 없이 핸들러를 교체·제거하고 `grid.refresh()`를 요청합니다. 제거하면 기본 배너로 돌아갑니다.
341
+ - 배너 호스트마다 React root 1개를 유지하고, 접기/펼치기·건수 변화는 props 갱신으로 반영합니다. 배너가 스크롤/그룹 해제/데이터 갱신으로 사라지면 해당 root를 정리합니다.
342
+ - 배너가 생기지 않는 컬럼 구성(가운데 컬럼이 모두 `unmergeOnGroupRow`)에서는 그룹행 요소에 마운트됩니다.
343
+ - 템플릿에서 ▼/▶를 직접 그리지 마세요. 토글 버튼이 이미 있어 두 번 보입니다.
344
+
345
+ ```tsx
346
+ function ReasonGroup({ groupValue, count }: GroupSlotProps) {
347
+ return <><strong title={describe(groupValue)}>{t(String(groupValue))}</strong> ({count})</>;
348
+ }
349
+
350
+ <MozGrid coreConfig={coreConfig} groupTemplate={ReasonGroup} />;
351
+ ```
352
+
293
353
  ---
294
354
 
295
355
  ## 이벤트 콜백 props
@@ -303,9 +363,9 @@ function StatusCell({ value, rowData, columnId, rowId }: CellSlotProps) {
303
363
  - 핸들러는 인스턴스별입니다. 직접 등록한 핸들러는 각 `onReady`에서 다시 등록하세요. 제거는 이후 호출만 멈추며, 소비자가 DOM에 남긴 임의 텍스트·스타일을 역으로 복원하지 않습니다. 상태에 따라 스타일을 적용/해제하는 대칭적인 콜백을 사용하세요.
304
364
 
305
365
 
306
- 모든 상호작용 콜백은 `on*` props입니다. `onReady`/`onDataLoaded`를 제외한 **32개**는 코어 PureSheet 이벤트를 `EVENT_CALLBACKS` 표를 통해 `chrome.on(event, ...)`으로 일반 포워딩합니다(총 **34개 이벤트 콜백** props). `onDataLoaded` 는 `chrome.on('data:loaded', …)` 로 별도 배선되고, `onReady` 는 어댑터가 직접 호출합니다.
366
+ 모든 상호작용 콜백은 `on*` props입니다. `onReady`/`onDataLoaded`를 제외한 콜백은 `EVENT_CALLBACKS`를 통해 코어 이벤트를 포워딩합니다. `onDataLoaded`는 전용 구독, `onReady`는 어댑터가 직접 호출합니다.
307
367
 
308
- > **페이로드 형태는 모두 코어 이벤트 페이로드입니다 — [reference.md 이벤트](reference.md#이벤트) 참조.** props 시그니처상 `(payload: unknown) => void`로 노출되지만 실제 페이로드는 해당 코어 이벤트의 형태를 따릅니다.
368
+ > **페이로드 형태는 모두 코어 이벤트 페이로드입니다 — [reference.md 이벤트](reference.md#이벤트) 참조.** `onDataChanged`/`onScroll`/`onEndReached` 외에는 주로 `(payload: unknown) => void`로 노출되지만 실제 페이로드는 해당 코어 이벤트의 형태를 따릅니다.
309
369
 
310
370
  | 콜백 prop | 시그니처 | 대응 코어 이벤트 | 설명 |
311
371
  |-----------|----------|------------------|------|
@@ -351,7 +411,7 @@ function StatusCell({ value, rowData, columnId, rowId }: CellSlotProps) {
351
411
 
352
412
  ## Slot props (ReactNode)
353
413
 
354
- React에는 명명 슬롯이 없으므로, GridChrome의 7개 셸 슬롯 지점을 **`ReactNode` props**로 노출합니다(Vue 명명 슬롯과 1:1). 각 슬롯은 코어 DOM 컨테이너 안에 React 컴포넌트를 마운트하고 언마운트 정리 함수를 반환합니다.
414
+ React에는 명명 슬롯이 없으므로, GridChrome의 8개 셸 슬롯 지점을 **`ReactNode` props**로 노출합니다(Vue 명명 슬롯과 1:1). 각 슬롯은 코어 DOM 컨테이너 안에 React 컴포넌트를 마운트하고 언마운트 정리 함수를 반환합니다.
355
415
 
356
416
  | 슬롯 prop | 타입 | 대응 슬롯명 | 설명 |
357
417
  |-----------|------|-------------|------|
@@ -362,6 +422,7 @@ React에는 명명 슬롯이 없으므로, GridChrome의 7개 셸 슬롯 지점
362
422
  | `notification` | `ReactNode` | `notification` | 툴바와 본문 사이 영역. **수동 영역일 뿐, React에는 뒤에 동작하는 excel/notification contract 없음**([Vue와의 비대칭](#vue와의-비대칭-주의) 참조) |
363
423
  | `footer` | `ReactNode` | `footer` | 푸터 커스텀 영역 |
364
424
  | `emptyStateContent` | `ReactNode` | `empty-state` | 빈 상태 customState 영역(Vue `#empty-state`) |
425
+ | `optionMenuItems` | `ReactNode \| ((props: WrapperOptionMenuItemsProps) => ReactNode)` | `option-menu-items` | 보기 설정(톱니바퀴) 메뉴 Main 화면 끝(마지막 구분선 아래) 소비처 항목. 정적 노드 **또는** `{ close, mode }`를 받는 **render-prop**. MozGrid가 렌더될 때마다 최신 prop으로 재렌더 |
365
426
 
366
427
  `tool-items` 슬롯은 Vue scoped slot과 동일하게 동적 props를 받는 **render-prop**을 지원합니다.
367
428
 
@@ -381,6 +442,40 @@ interface WrapperToolItemsProps {
381
442
  />;
382
443
  ```
383
444
 
445
+ `option-menu-items`(`optionMenuItems`)는 `{ close, mode }`를 받습니다.
446
+
447
+ ```tsx
448
+ interface WrapperOptionMenuItemsProps {
449
+ close: () => void; // 보기 설정 메뉴를 동기로 닫음 — 직후 다른 팝업을 열어도 메뉴가 다시 열리지 않음
450
+ mode: 'flat' | 'pivot'; // 플랫/피벗 '보기 설정' 구분
451
+ }
452
+
453
+ // PSI: 메뉴를 닫고 다음 tick 에 컬럼 선택 팝업 열기
454
+ <MozGrid
455
+ coreConfig={coreConfig}
456
+ optionMenuItems={({ close }) => (
457
+ <div className="mzc-pvo__item" onClick={() => { close(); queueMicrotask(() => setPickerOpen(true)); }}>
458
+ 컬럼 선택
459
+ </div>
460
+ )}
461
+ />;
462
+
463
+ // PA: 메뉴 안에서 KPI MultiSelect (선택 상태는 부모 state — MozGrid 렌더마다 반영)
464
+ <MozGrid
465
+ coreConfig={coreConfig}
466
+ optionMenuItems={
467
+ <div className="kpi-setting-container">
468
+ <span>KPI 설정</span>
469
+ <MultiSelect width={150} itemsSource={kpiItemSource} value={kpiItems} onChange={setKpiItems} />
470
+ </div>
471
+ }
472
+ />;
473
+ ```
474
+
475
+ - 플랫·피벗 공통. 피벗 하위 화면(레이아웃/값 필드 설정)에서는 숨겨지고 Main으로 돌아오면 다시 보입니다. 톱니바퀴가 있을 때만(`useToolBox`·`useToolBoxSetting`) 마운트됩니다.
476
+ - 슬롯 React root는 **상주**합니다. 메뉴를 닫았다 열어도 같은 인스턴스(내부 state)가 유지되고, 툴박스 재조립 때만 정리 후 다시 마운트됩니다.
477
+ - 슬롯 안 클릭은 메뉴를 닫지 않습니다. body로 portal되는 드롭다운은 **슬롯 안 트리거가 `aria-controls`/`aria-owns`로 그 목록의 `id`를 가리킬 때만** 안쪽으로 판정합니다. 이 연결이 없는 portal 클릭은 바깥 클릭으로 처리되어 메뉴가 닫힙니다.
478
+
384
479
  ---
385
480
 
386
481
  ## 인스턴스 API (MozGridHandle ref)
@@ -413,12 +508,18 @@ type MozGridHandle = GridChrome & { chrome: GridChrome | null };
413
508
  | `getToolItemsProps` | `getToolItemsProps(): WrapperToolItemsProps` | tool-items 슬롯의 동적 props `{ disabled, hasItem }` |
414
509
  | `setPivotBarVisible` | `setPivotBarVisible(visible: boolean): void` | **피벗 옵션 바 표시/숨김** — 표시 여부는 prop(`usePivotBar`)이 아니라 이 메서드가 소유. 초기에 켜 두려면 `onReady` 2번째 인자로 호출 |
415
510
  | `isPivotBarVisible` | `isPivotBarVisible(): boolean` | 피벗 옵션 바 표시 상태 조회 |
511
+ | `setToolbarVisible` | `setToolbarVisible(visible: boolean): void` | 툴바 내용 접기/펼치기. `false`여도 펼치기 토글은 남음. `useToolBox: false`이면 no-op |
512
+ | `isToolbarVisible` | `isToolbarVisible(): boolean` | 툴바 내용 펼침 상태. 툴박스가 없으면 `false` |
416
513
  | `setGroupPanelVisible` | `setGroupPanelVisible(visible: boolean): void` | 그룹 패널 표시/숨김 |
417
514
  | `getViewOptions` | `getViewOptions(): WrapperViewOptions` | 뷰 상태 `{ toolbarVisible, footerVisible, groupPanelVisible }` |
418
515
  | `destroy` | `destroy(): void` | 모든 셸/컨트롤러/구독/슬롯/코어 그리드 해제 및 root DOM 제거(멱등). 어댑터가 unmount 시 자동 호출 |
419
516
 
420
517
  > ⚠️ `usePivotBar` 를 `setUIOptions` 로 껐다 다시 켜면 `PivotBar` 가 새로 생성되므로 **표시 상태는 기본값(숨김)으로 돌아갑니다.** 표시 여부의 소유자는 `setPivotBarVisible` 이므로, 켠 뒤 보이게 하려면 소비처가 그 메서드를 다시 호출해야 합니다.
421
518
 
519
+ > 툴바는 기본적으로 펼쳐집니다. 처음부터 접으려면 `onReady={(_grid, chrome) => chrome.setToolbarVisible(false)}`를 사용하세요. 새 초기 prop은 필요하지 않습니다. 접기는 툴바 내용(하단 슬롯 포함)을 숨기고 그룹 패널·피벗 바도 일시 숨기지만, 툴박스와 펼치기 토글은 유지합니다. `useToolBox: false`는 툴박스 자체를 제거하는 별도 설정입니다. 이때 setter는 no-op, getter는 `false`이며, 다시 `useToolBox: true`로 만들면 기본 펼침 상태로 시작합니다. `useSort` 등 다른 UI 플래그로 툴바만 재조립할 때는 기존 접기/펼치기 상태가 유지됩니다. 실제 펼침 상태 변경마다 보기 설정 통지는 한 번 발생하며 같은 값을 재설정하면 통지하지 않습니다.
520
+ >
521
+ > 이 상태는 현재 GridChrome 인스턴스에 속합니다. 코어 옵션 변경으로 전체 인스턴스가 재생성되면 필요한 초기 접기 설정을 각 `onReady`에서 다시 적용하세요.
522
+
422
523
  > GridChrome에는 public `off` 메서드가 **없습니다**. `on`/`onConfigChange`가 반환한 `Unsubscribe`를 호출하거나, `destroy()`가 추적된 모든 구독을 정리합니다.
423
524
 
424
525
  ### 주요 public 컨트롤러/필드
@@ -472,7 +573,7 @@ const off = gridRef.current?.on('cell:change', (p) => console.log(p));
472
573
 
473
574
  ```tsx
474
575
  // MozGridCoreProps 는 @vmscloud/moz-ui-grid-wrapper 에서 import.
475
- const [coreConfig, setCoreConfig] = useState<MozGridCoreProps>({ mode: 'flat', fields, data });
576
+ const [coreConfig, setCoreConfig] = useState<MozGridCoreProps>({ mode: 'flat', enableReferCache: true, fields, data });
476
577
 
477
578
  <MozGrid
478
579
  coreConfig={coreConfig}
@@ -491,19 +592,19 @@ React 어댑터는 Vue 어댑터와 다음 표면이 **다릅니다**. 마이그
491
592
  | 항목 | Vue | React |
492
593
  |------|-----|-------|
493
594
  | **Excel 라이프사이클(excelHooks)** | `excelHooks`를 완전 배선 — export start/progress/complete/error/cancel, import complete/error를 notification store에 자동 push | **미배선.** `buildChromeOptions`가 `excelHooks` 없이 옵션을 구성하며, `ExcelLifecycleHooks`를 import하지 않음(능력은 GridChromeOptions에 존재하나 React가 미사용) |
494
- | **notification store contract** | `provideGridNotification` / `useGridNotificationStore`로 host 주입 store를 읽어 excel 토스트 자동화 | `notification`은 **수동 ReactNode 슬롯**일 뿐. `MozGrid.tsx`에 `useGridNotificationStore` import 없음(legacy contract 파일은 존재하나 새 컴포넌트가 사용 안 함) |
595
+ | **notification store contract** | `provideGridNotification` / `useGridNotificationStore`로 host 주입 store를 읽어 excel 토스트 자동화 | `notification`은 **수동 ReactNode 슬롯**일 뿐. `MozGrid.tsx`에 `useGridNotificationStore` import 없음(legacy contract 파일의 존재와 public export는 별개) |
495
596
  | **userLayout contract** | 제공 | **미제공** |
496
- | **useGridPreset (프리셋/레이아웃 직렬화 훅)** | 제공 | **미제공** |
597
+ | **프리셋 참여** | 내부 `useGridPreset`으로 통합. `name` + 호스트 계약 사용, `usePreset: false`로 생성 시 opt-out(훅 자체는 미-export) | `usePreset` prop 및 프리셋 통합 **미제공** |
497
598
 
498
599
  **실무적 결론**: React에서는 Excel import/export 진행/오류 토스트가 host notification store로 자동 push되지 **않습니다**. 필요 시 `notification` ReactNode 슬롯 + 인스턴스 핸들/이벤트로 직접 배선해야 합니다. Vue에서는 notification store만 제공하면 excel 라이프사이클 토스트가 자동화됩니다.
499
600
 
500
- > 참고: React에서 host-주입 가능한 contract는 **notification contract**(legacy)뿐이며, 이는 `MozGrid` 신규 컴포넌트가 아니라 [Legacy exports](#legacy-exports)의 일부입니다. locale은 contract가 아니라 Context(`MozConfigProvider`/`useMozLocale`)로 제공됩니다.
601
+ > React 루트에는 Vue의 notification/userLayout 계약이나 `useGridPreset`, `usePreset` prop이 없습니다. Provider/Context도 이 문서의 현재 공개 export만 사용하세요.
501
602
 
502
603
  ---
503
604
 
504
605
  ## 템플릿 컴포넌트 props 타입
505
606
 
506
- [템플릿 맵](#템플릿-맵-celltemplates--headertemplates--editortemplates)에 등록한 컴포넌트가 React props로 받는 타입입니다(`src/types/slots.ts`). 이 타입들은 `./legacy`를 통해 패키지 루트에서도 재노출됩니다.
607
+ [템플릿 맵](#템플릿-맵-celltemplates--headertemplates--editortemplates)에 등록한 컴포넌트가 React props로 받는 타입입니다(`src/types/slots.ts`). `CellSlotProps`/`HeaderSlotProps`/`EditorSlotProps`는 루트의 named export가 아닙니다. 위 예처럼 `MozGridProps`의 템플릿 맵에서 컴포넌트 props 타입을 추론하세요.
507
608
 
508
609
  ### CellSlotProps
509
610
 
@@ -512,7 +613,9 @@ React 어댑터는 Vue 어댑터와 다음 표면이 **다릅니다**. 마이그
512
613
  | `value` | `unknown` | 셀 값 |
513
614
  | `rowData` | `Record<string, unknown>` | 행 데이터 |
514
615
  | `columnId` | `string` | 컬럼 ID |
515
- | `rowId` | `string \| number` (optional) | 행 ID |
616
+ | `rowId` | `string \| number` (optional) | 업무 행 ID — `grid.rows.getById(rowId)`로 해석되는 값(`cell:click` payload의 `rowId`와 같음). 단일 `keyFields`는 `String(값)`, 키 없으면 자동 ID. 그룹행·구조행에서는 없음 (코어 [CellRendererParams](reference.md#셀-렌더러-lifecycle-포함)) |
617
+
618
+ > **행 헤더 렌더러**: 행 헤더(`_rowHeader`)는 템플릿 맵 대상이 아닙니다. `coreConfig.rowHeader.cellRenderer`에 코어 렌더러 함수(Simple 또는 Lifecycle `(container, params) => { update, destroy }` — 안에서 `createRoot`)를 직접 넘기며, 그룹행에도 그리려면 `includeGroupRows: true`(→ 코어 [RowHeaderConfig](reference.md#rowheaderconfig))를 함께 지정합니다.
516
619
 
517
620
  ### HeaderSlotProps
518
621
 
@@ -533,21 +636,14 @@ React 어댑터는 Vue 어댑터와 다음 표면이 **다릅니다**. 마이그
533
636
 
534
637
  ### DetailSlotProps / GroupSlotProps (참고)
535
638
 
536
- `MozGrid`에는 detail/group 템플릿 **맵 prop이 없습니다**. 두 타입은 타입으로 재노출되며, legacy 흐름에서 레지스트리 `type:'detail'`/`type:'group'`로 사용됩니다.
639
+ `MozGrid`에는 detail 템플릿 prop이 없습니다. group 템플릿은 [`groupTemplate`](#그룹-템플릿-grouptemplate) prop으로 받습니다. 두 타입과 `CellSlotProps`/`HeaderSlotProps`/`EditorSlotProps`는 패키지 진입점에서 타입으로 export됩니다.
537
640
 
538
641
  | 타입 | 필드 |
539
642
  |------|------|
540
643
  | `DetailSlotProps` | `{ parentData: Record<string, unknown>; dataIndex: number }` |
541
644
  | `GroupSlotProps` | `{ groupKey: string; groupValue: unknown; level: number; count: number; isExpanded: boolean }` |
542
645
 
543
- ### 레지스트리 타입 (고급)
544
-
545
- | 타입 | 설명 |
546
- |------|------|
547
- | `TemplateEntry` | `{ type: 'cell'\|'header'\|'editor'\|'detail'\|'group'; fieldId?: string; component?: ComponentType<any>; props?: Record<string, unknown> }` |
548
- | `TemplateRegistry` | `register(entry)` / `unregister(type, fieldId?)` / `get(type, fieldId?)` / `getAll(type)`. **비반응형** — register/unregister 후 PureSheet 재빌드 필요(그리드 `fields` prop 참조 변경 시 자동) |
549
-
550
- 브릿지 유틸(고급 용도, legacy 재노출): `ReactRenderer`(+`ContextWrapper` 타입), `createTemplateRegistry`, `buildFieldDefs`.
646
+ 내부 registry/renderer를 공개 API로 가정하지 마세요.
551
647
 
552
648
  ---
553
649
 
@@ -565,7 +661,7 @@ React 어댑터는 Vue 어댑터와 다음 표면이 **다릅니다**. 마이그
565
661
  | `useSort` | `boolean?` | 정렬 항목 |
566
662
  | `useFilter` | `boolean?` | 필터 항목 |
567
663
  | `useCustomCell` | `boolean?` | 조건부 서식 항목 |
568
- | `useBulkEditColumn` | `boolean?` | 컬럼 일괄 편집 항목 |
664
+ | `useBulkEditColumn` | `boolean?` | 컬럼 일괄 편집 항목. 숫자 컬럼의 일괄 편집 입력은 `FieldDef.editor`의 `min`/`max`/`step`(`type: 'number'`)을 따르고, 범위·간격에 맞지 않는 값은 셀 편집과 같이 [확인]이 비활성됩니다(자동 보정 없음, 빈 값은 `null`). `step` 미선언 시 기존 5 단위 |
569
665
  | `useDataCopy` | `boolean?` | 데이터 복사 항목 |
570
666
  | `useDataWithHeaderCopy` | `boolean?` | 헤더 포함 복사 항목 |
571
667
  | `useAutoSizeColumns` | `boolean?` | 컬럼 자동 크기 항목 |
@@ -634,116 +730,183 @@ interface ChartSeriesConfig {
634
730
  | `type` | `'bar' \| 'line' \| 'lineSymbols' \| 'area'` (optional) | 시리즈별 차트 타입 |
635
731
  | `color` | `string?` | 시리즈별 색상(미설정 시 팔레트 기본) |
636
732
 
637
- > `ChartSeriesConfig`/`ChartType`은 **래퍼 main index에서는 재노출되지 않으며**(차트 유틸 모듈 내부에 정의) `chartMap`의 값 타입으로만 도달 가능합니다. 단, react 패키지의 **legacy 배럴에서는 두 타입을 모두 재노출**합니다(`export { type ChartType, type ChartSeriesConfig } from '.../chartUtils'`).
733
+ > `ChartSeriesConfig`/`ChartType`은 React 루트 named export가 아닙니다. `NonNullable<MozGridProps['chartMap']>[string]`으로 값 타입을 추론할 수 있습니다.
638
734
 
639
- ### locale
735
+ ### chartConfig / PivotChartConfig (내장 피벗 차트 설정)
640
736
 
641
- | 속성 | 타입 | 기본값 | 설명 |
642
- |------|------|--------|------|
643
- | `locale` | `string` (`'ko' \| 'en' \| 'ja' \| 'zh'` 또는 `'en-US'` 같은 접두 형식) | `'ko'` | UI 언어 |
737
+ **범위: wrapper 내장 피벗 차트(차트 버튼·컨텍스트 메뉴 '차트 열기'로 여는 차트 패널)만.** 그리드 셀·내보내기·`grid.pivot.getSeriesData()` 직접 호출 결과에는 영향이 없습니다. 이름은 `coreConfig`/`contextMenuConfig` 와 같은 `xxxConfig` 규칙입니다. Prop: `chartConfig`.
644
738
 
645
- - **생성 시 1회만 적용 — 런타임 전환 미지원.** public `GridChrome.setLocale` 메서드는 존재하지 않습니다(`setLocale`은 내부 i18n 모듈 함수로 GridChrome 생성자에서만 호출). `setUIOptions`도 locale을 처리하지 않습니다. (래퍼 `WrapperExtras.locale` 의 JSDoc 이 `GridChrome.setLocale` 을 언급하나, 이는 stale 한 주석일 뿐 실제 public API 가 아닙니다.)
646
- - `locale`은 `WrapperExtras`와 코어 `PureSheetConfig` 양쪽에 존재하는 유일한 키이며(둘 다 `string`), 의도적으로 strip되지 않아 코어 설정과 래퍼 옵션(`options.locale`) 양쪽에 전달됩니다(`WRAPPER_EXTRA_KEYS` 에서 `locale` 제외).
739
+ ```ts
740
+ interface PivotChartConfig {
741
+ /** getSeriesData 결과를 차트에 덮어쓰기 직전에 한 번 호출. 반환값을 차트 데이터로 사용 */
742
+ transformData?: (data: PivotSeriesData, ctx: ChartTransformContext) => PivotSeriesData;
743
+ /** X축 라벨 표시 변환 */
744
+ xAxisLabelFormatter?: (label: string) => string;
745
+ /** 레거시 setChartData 대체 — 리프 data 행 × 값 칸마다 호출, add(시리즈명, 값)으로 (범주, X축) 칸에 합산 */
746
+ accumulate?: (cell: PivotChartCell, add: (seriesName: string, value: number | null) => void) => void;
747
+ /** Y축 범주 후보에서 제외할 행 필드 id */
748
+ categoryFieldsExclude?: string[];
749
+ /** X축 범주 후보에서 제외할 열 필드 id */
750
+ xAxisFieldsExclude?: string[];
751
+ /** 값 필드 후보·초기 선택을 이 값 필드 id 로 제한 */
752
+ valueFields?: string[];
753
+ /** 차트를 그릴 외부 요소(또는 CSS 선택자). 미지정/null = 그리드 내부 50/50 분할 */
754
+ container?: HTMLElement | string | null;
755
+ }
756
+ interface PivotChartCell {
757
+ category: string; // getRowHeaderPath(행 필드 값, 현재 범주) — 일반 차트 범주 이름과 같음(중간 단계 범주도 '서울 › 1공장')
758
+ categoryKey: string; // getRowHeaderKey — 경로 값 '|' join('|'·'\' 이스케이프), 범주 합산 기준. 이전 core 면 category
759
+ categoryPath: string[]; // 최상위 행 필드 → 범주 필드 값. 이전 core 면 [category]
760
+ xLabel: string; // 열 경로를 현재 X축 레벨까지 잘라 '\n' join — 일반 차트 xLabels 와 같음
761
+ rowValues: Record<string, CellValue>; // 행 필드 id → 값 (레거시 rowField)
762
+ columnValues: Record<string, CellValue>; // 열 필드 id → 열 헤더 키 문자열
763
+ valueField: string; // 값 필드 id
764
+ value: number | null; // 칸 값(숫자가 아니면 null)
765
+ }
766
+ // PivotSeriesData = { xLabels: string[]; categories: { name: string; key: string; path: string[]; series: { name: string; data: (number | null)[] }[] }[] }
767
+ interface ChartTransformContext {
768
+ categoryField: string; // 차트 툴바 'Y축 범주'(행 필드 식별자)
769
+ columnFieldLevel: number | undefined; // 'X축 범주' 레벨, '전체'면 undefined
770
+ valueFields: string[]; // 선택된 값 필드 식별자
771
+ includeGrandTotal: boolean; // chartIncludeGrandTotal
772
+ rowFields: { id; field; header }[]; // getPivotMeta().rowFields
773
+ columnFields: { id; field; header }[]; // getPivotMeta().columnFields
774
+ allValueFields: { id; field; header; aggregate? }[]; // getPivotMeta().valueFields (선택 무관)
775
+ pivotConfig: PivotConfig | null; // grid.pivot.getConfig() — 내부 참조, 읽기 전용
776
+ }
777
+ ```
647
778
 
648
- ---
779
+ `PivotChartConfig`/`ChartTransformContext` 타입은 `@vmscloud/moz-ui-grid-wrapper` 에서 import 합니다.
649
780
 
650
- ## Legacy exports
781
+ | 키 | 동작 |
782
+ |----|------|
783
+ | 미지정 | 현재 동작 그대로(변환 없음, 축 옵션 무변경) |
784
+ | `transformData` | 차트 갱신(열기·필터/정렬/데이터 로드·피벗 설정 변경·툴바 선택 변경)마다 `getSeriesData` 직후 1회 호출. 인자 `data` 는 **복사본**이라 변조해도 안전. 예외를 던지거나 형태가 잘못되면(`xLabels`/`categories` 가 배열이 아니거나, 각 `name` 이 문자열·`series[].data` 가 배열이 아님) `console.error` 후 **원본 데이터 사용** |
785
+ | `xAxisLabelFormatter` | X축 라벨 **표시**만 변환(`xLabels` 데이터 불변). 상세(풀사이즈) 차트의 축 라벨·axisPointer 라벨·툴팁 제목에 같은 변환 적용. 라벨 회전·초기 줌 계산은 변환 후 문자열 기준. Multiple 모드 미니 차트는 원래 축 라벨을 표시하지 않음. 예외 시 원본 라벨(`console.error` 는 함수당 1회) |
786
+ | `accumulate` | 지정하면 `getSeriesData` 대신 `grid.pivot.getDataMatrix({ subtotals:'none', grandTotals:'none', excludeStructuralColumns:true })` 의 **리프 data 행 × 선택된 값 필드의 값 칸**마다 1회 호출. `add(시리즈명, 값)` 은 (`cell.category`, `cell.xLabel`) 칸에 합산(null 무시, 더해진 값이 없으면 null). `add` 는 `accumulate` 안에서 **동기로만** 호출. 범주·X축 순서 = 피벗 결과 순서, 시리즈 순서 = add 최초 등장 순(모든 범주가 같은 시리즈 목록). `chartIncludeGrandTotal` 이면 범주별 결과를 합산한 `Grand Total` 범주를 마지막에 붙임. 결과는 `transformData` → 렌더로 이어짐. 예외(또는 `columns` 를 주지 않는 이전 core) 시 `console.error` 후 기본 `getSeriesData` 경로. 범주 이름이 일반 차트와 같아 Detail 모드 셀 클릭 → 범주 전환이 그대로 동작 |
787
+ | `categoryFieldsExclude` | 툴바 'Y축 범주' 후보에서 제외할 **행 필드 id**. 초기·폴백 범주 = 제외 후 첫 후보 |
788
+ | `xAxisFieldsExclude` | 툴바 'X축 범주' 후보에서 제외할 **열 필드 id**. 실제로 제외되는 필드가 있으면 '전체' 후보를 숨기고 남은 첫 레벨이 기본(X축 라벨은 기존처럼 그 레벨까지의 경로) |
789
+ | `container` | 차트를 그릴 **외부 요소**(`HTMLElement`) 또는 CSS 선택자. 미지정·`null` 이면 기존처럼 그리드 내부 50/50 분할. 지정하면 차트를 열 때(툴박스 차트 버튼·컨텍스트 메뉴 '차트 열기' 공통) 그리드 root 를 분할하지 않고(그리드 100%, 리사이저·내부 차트 영역 숨김) 차트 패널 `.mzc-pivot-chart.mzc-pivot-chart--external` 을 그 요소의 **마지막 자식으로 붙이고**, 닫거나 그리드를 destroy 하면 떼어냄(요소의 기존 자식은 그대로). 선택자는 **차트를 열 때** `document.querySelector` 로 찾고, 없으면 내부 분할로 대체하며 `console.warn`. 열린 채 바꾸면 즉시 이동(같은 요소·같은 문자열이면 no-op, `null` 이면 내부 분할로 복귀). 외부 요소의 크기(높이)는 소비자가 정함 — 패널은 `height:100%` |
790
+ | `valueFields` | 툴바 '카테고리'(값 필드) 후보·초기 선택을 이 **값 필드 id** 로 제한. 모르는 id 는 무시 |
651
791
 
652
- 아래 항목은 모두 **legacy(이전 React API)** 로, 패키지 루트의 `export * from './legacy'`를 통해 재노출됩니다. 신규 코드는 위의 `MozGrid` 컴포넌트를 사용하세요. 여기서는 그대로(비파괴) 재노출됩니다.
792
+ - 제외 목록 공통: 현재 선택이 런타임에 제외되면 남은 첫 후보로 이동. 후보가 전부 제외되면 제외를 **무시**(차트가 비지 않게) + `console.warn`(같은 내용 1회). 후보가 1개뿐이면 그 Select 는 숨김(기존 규칙). 배열은 **내용** 비교 — 매 렌더 새 배열 리터럴이어도 같으면 no-op.
793
+ - `accumulate` 를 쓰면 상위 범주(예 품목)를 골라도 하위 구분(kind)별 값이 셀 단위로 오므로, 범주를 바꾸지 않고 구분별 시리즈를 만들 수 있습니다. 시리즈 이름이 값 필드 header 와 다르면 `decimalFormat` 연동이 적용되지 않아 툴팁이 기본 표시가 됩니다(아래 규칙과 같음).
794
+ - 성능: 내장 차트에는 렌더 상한이 없습니다(레거시 Wijmo 차트의 `ALLOWED_RENDERING_MAX = 500` 열 그룹 상한은 옮기지 않음). `accumulate` 는 리프 행 수 × 값 칸 수만큼 호출되므로 큰 피벗에서는 가볍게 유지하세요.
653
795
 
654
- > ⚠️ **legacy 표기**: 아래 모든 심볼은 호환을 위해 유지되는 이전 API입니다.
796
+ - 변환 뒤 데이터 기준으로 동작하는 것: Multiple 카테고리 목록, Detail 기본 카테고리 선택, `chartMap`(시리즈 이름 키).
797
+ - 시리즈 이름을 바꾸면 피벗 값 필드 `decimalFormat` 연동(키 = 값 필드 header)이 그 시리즈엔 적용되지 않아 툴팁이 기본 표시(원본 소수 보존 + 천단위)가 됩니다. 소수 자릿수를 유지하려면 값 필드 header 와 같은 시리즈 이름을 쓰세요.
798
+ - 카테고리 이름을 바꾸면 Detail 모드에서 피벗 셀 클릭 → 해당 카테고리 전환이 매칭되지 않아 무시됩니다.
799
+ - 셀 단위 합산(legacy `setChartData({ iterator })`)은 `accumulate` 로 옮깁니다. `transformData` 의 데이터 단위는 현재 선택한 Y축 범주의 결과(상위 범주면 하위 구분별 값은 이미 합산됨)입니다.
800
+ - legacy `MozPivotVue` 의 `formatXAxisLabel`/`setChartData` prop 은 인자 형태가 달라 그대로 옮겨지지 않습니다(`chartConfig` 로 재작성 — 아래 대응표).
655
801
 
656
- ### 컴포넌트 (legacy)
802
+ **외부 차트 영역(`container`)** — 레거시 `ExtendPivotGrid` 의 `teleportChartTo` 처럼 그리드 아래·옆의 별도 영역에 차트를 그립니다. 그리드는 분할되지 않고 전체 높이를 유지합니다. 외부로 나간 패널은 `.mzc-grid-root` 바깥이라 `--mzc-*` 디자인 토큰을 패널 자체(`.mzc-pivot-chart--external`)에 같은 기본값으로 다시 정의합니다(그리드 root 의 토큰을 덮어쓴 경우 이 클래스에도 같이 지정하세요). 차트 크기는 요소 크기를 따르며(echarts autoresize), 이동 직후 한 번 resize 합니다. 런타임 변경은 `container` 값(요소 identity 또는 문자열)이 바뀔 때만 전파됩니다. `useRef` 의 `.current` 는 렌더 중 아직 null 이고 바뀌어도 리렌더가 없으므로, **콜백 ref + state** 또는 선택자 문자열을 쓰세요.
657
803
 
658
- | 심볼 | 종류 | 설명 |
659
- |------|------|------|
660
- | `MozGridReact` | 컴포넌트 | (legacy) 이전 React 그리드 컴포넌트. README의 사용 예제가 이를 사용 |
661
- | `MozGridReactCore` / `MozGridReactCoreHandle` | 컴포넌트 / 타입 | (legacy) 저수준 코어 그리드 컴포넌트 + ref 핸들 |
662
- | `CellTemplate`, `HeaderTemplate`, `CellEditor`, `DetailTemplate`, `GroupTemplate` | 컴포넌트 | (legacy) 선언형 템플릿 등록 child 컴포넌트 |
663
- | `GridToolBox`, `GridOptionModal`, `ColumnPicker`, `GridBulkEditor`, `GridContextMenu` (+`GridContextMenuHandle`) | 컴포넌트 / 타입 | (legacy) 그리드 UI 서브 컴포넌트 |
664
- | `GridSearchInput`, `GridFilter`, `GridSort`, `GridCustomCell`, `GridFooter`, `Dragging`, `draggingEventBus` | 컴포넌트 / 값 | (legacy) 공용 UI 컴포넌트 (`draggingEventBus`는 런타임 값) |
665
- | `MozPivotReact`, `PivotToolBox`, `PivotOptionModal`, `PivotFieldPanel`, `PivotContextMenu` | 컴포넌트 | (legacy) 피벗 컴포넌트 |
666
- | `PivotGrid` | 컴포넌트 | (legacy, `@deprecated`) `MozPivotReact`의 옛 이름 alias |
667
- | `MozPivotChart` (+`MozPivotChartHandle`/`MozPivotChartProps`), `PivotChartDetail`, `PivotChartMini`, `PivotChartMultiple`, `PivotChartToolbar`, `EChart` (+`EChartHandle`/`EChartProps`) | 컴포넌트 / 타입 | (legacy) 피벗 차트 컴포넌트(echarts 기반) |
668
- | `ClientExcelUploadPopup` | 컴포넌트 | (legacy) 클라이언트 Excel 팝업 |
669
- | UI 프리미티브 (Radix 기반): `CheckBox`, `Toggle`, `Button`, `Input`, `NumberInput`, `DateInput`, `EmptyState`, `Popup`, `ContextMenu`, `Select`, `MultiSelect` (+ 각 Props/Handle 타입) | 컴포넌트 / 타입 | (legacy) UI 프리미티브 11종 |
670
- | 아이콘 20종 (`IconAdd`, `IconArrowDownWithTail`, …, `IconToastWarning`) | 컴포넌트 | (legacy) |
671
-
672
- ### 함수 / 유틸 (legacy)
673
-
674
- | 심볼 | 종류 | 설명 |
675
- |------|------|------|
676
- | `toEChartsOption`, `CHART_TYPE_OPTIONS` | 함수 / const | (legacy) 차트 유틸 + 옵션 |
677
- | `pickFile`, `downloadBlob` | 함수 | (legacy) 파일 유틸 |
678
- | `cn`, `generateUUID`, `pxToRem`, `uniqueId`, `shallowEqual`, `formatMessage` | 함수 | (legacy) 공용 유틸 |
679
- | `runClientExport`, `exportCsv`, `generateSessionId` | 함수 | (legacy) 클라이언트 내보내기 헬퍼 |
804
+ ```tsx
805
+ const [chartArea, setChartArea] = useState<HTMLElement | null>(null);
806
+ const chartConfig = useMemo<PivotChartConfig>(() => ({ container: chartArea }), [chartArea]);
807
+
808
+ <>
809
+ <MozGrid coreConfig={coreConfig} usePivot useChart chartConfig={chartConfig} />
810
+ {/* 선택자 문자열도 가능: chartConfig={{ container: '.res-alloc-info-bottom' }} */}
811
+ <div ref={setChartArea} className="res-alloc-info-bottom" style={{ height: 320 }} />
812
+ </>;
813
+ ```
680
814
 
681
- ### i18n (legacy)
815
+ **레거시 → 신규 대응표**
682
816
 
683
- | 심볼 | 종류 | 설명 |
817
+ | 레거시(Wijmo `ExtendPivotGrid`/`MozPivotVue`) | 신규 `chartConfig` | 차이 |
684
818
  |------|------|------|
685
- | `MozConfigProvider`, `MozLocaleContext`, `useMozLocale`, `koKR`, `enUS`, `zhCN`, `jaJP` (+`MozConfigProviderProps`, `MozLocaleMessages`), `useMozContextWrapper`, `MozContextWrapperContext` (+`ContextWrapper`) | 컴포넌트 / Context / 훅 / 사전 / 타입 | (legacy) i18n. `useMozLocale()`는 `MozLocaleMessages` 객체를 직접 반환(Ref 아님, 재렌더 시 자동 갱신). locale은 Context이며 contract가 아님. **`MozConfigProvider contextWrapper`** 로 소비자 Provider 트리를 넘기면 셀·헤더·에디터 템플릿과 slot 의 독립 root 가 그 트리로 감싸져 소비자 context 를 되찾는다(`@vmscloud/moz-ui-components-react` 와 같은 계약, 신규 `MozGrid`·legacy `MozGridReact` 공통). 참조 동일성 유지 필요 |
819
+ | `setChartData({ iterator })` + `iterator(({ accObj, getValue, rowField }) => …)` | `accumulate(cell, add)` | `rowField`(값 배열) → `cell.rowValues`(필드 id → 값), `getValue(값필드 header)` → `cell.valueField`(id) + `cell.value`(칸마다 1회 호출), `accObj[이름] = (accObj[이름]‖0) + v` → `add(이름, v)`. 레거시 총합계 범주는 값 필드별 총합이었으나 신규는 **범주별 accumulate 결과의 합** |
820
+ | `chartRowFieldsException`(헤더 **표시 문자열**) | `categoryFieldsExclude`(행 필드 **id**) | 번역 문자열 대신 필드 id |
821
+ | `chartColumnFieldsException`(헤더 표시 문자열) | `xAxisFieldsExclude`(열 필드 id) | 레거시는 필터 후 인덱스가 밀리는 문제가 있었으나 신규는 원래 레벨 유지 |
822
+ | `chartValueFields`(차트에 그릴 **시리즈 이름** 목록 — setChartData 와 함께 쓰면 accObj 키) | `accumulate` 를 쓰면 **불필요**(add 한 시리즈가 전부 그려짐). 값 필드 후보 제한은 `valueFields`(값 필드 id) | 그릴 시리즈를 더 거르려면 `accumulate` 에서 add 하지 않거나 `transformData` 로 필터 |
823
+ | `formatXAxisLabel` | `xAxisLabelFormatter` | 같음 |
824
+ | `teleportChartTo`(차트를 Teleport 할 선택자) | `container`(요소 또는 선택자) | 레거시처럼 그리드는 분할하지 않음. 선택자를 못 찾으면 내부 분할로 대체(+warn) |
825
+
826
+ ```ts
827
+ // MrpReport 이식 — 행 필드 [item, kind] / 열 필드 [week] / 값 [category_qty].
828
+ // 범주(Y축)=item 그대로, kind 별로 A/B/C 시리즈 합산. 'mrp_category' 등은 범주·X축 후보에서 제외.
829
+ // 모듈 상수(또는 useMemo) — 함수 identity 가 안정적이어야 매 렌더 재계산이 없다.
830
+ const KIND_SERIES: Record<string, string> = {
831
+ 'A-used_qty': '소요량',
832
+ 'B-receipt_qty': '입고 예정량',
833
+ 'C-expected_wip_qty': '예상 재고량',
834
+ };
835
+ const mrpChartConfig: PivotChartConfig = {
836
+ accumulate: (cell, add) => {
837
+ const name = KIND_SERIES[String(cell.rowValues.kind)];
838
+ if (name && cell.valueField === 'category_qty') add(name, cell.value);
839
+ },
840
+ categoryFieldsExclude: ['mrp_category', 'boh_qty', 'total_receipt_qty', 'total_used_qty', 'eoh_qty'],
841
+ xAxisFieldsExclude: ['mrp_category'],
842
+ xAxisLabelFormatter: (label) => (label.startsWith('#') ? label.slice(3) : label),
843
+ };
844
+ ```
686
845
 
687
- ### Notification contract (legacy — React 유일의 host 주입 contract)
846
+ **런타임 변경** — `useEffect([transformData, xAxisLabelFormatter, accumulate, 제외 목록 3종의 내용 키, container])` 로 `chrome.setChartConfig` 에 전파합니다. 매 렌더 새 객체·배열 리터럴이어도 **함수 identity·배열 내용이 같으면 no-op** 입니다. `accumulate`·제외 목록이 바뀌면 차트를 다시 조회합니다. 단 JSX 안 인라인 화살표 함수는 매 렌더 identity 가 바뀌어 차트가 매번 다시 그려지므로 모듈 상수·`useCallback`·`useMemo` 로 고정하세요. `transformData` 교체는 `getSeriesData` 를 다시 부르지 않고 캐시된 원본에 재적용하며, 그리드는 재생성되지 않습니다.
688
847
 
689
- | 심볼 | 종류 | 설명 |
690
- |------|------|------|
691
- | `GridNotificationProvider`, `GridNotificationContext`, `GridNotificationKey`, `useGridNotificationStore`, `provideGridNotification` (+`GridNotificationContract`, `NotificationItem`, `SSENotificationType`, `ExcelNotificationOperationType`) | Provider / Context / Symbol / 훅 / 함수 / 타입 | (legacy) React에서 host-주입 가능한 유일한 contract |
848
+ ```tsx
849
+ import type { PivotChartConfig } from '@vmscloud/moz-ui-grid-wrapper';
850
+
851
+ // MrpReport 형태 — 행 필드 [item_id, kind] 에서 차트 툴바 'Y축 범주'를 leaf(kind)로 두면
852
+ // 카테고리 이름이 "품목 › 구분" 경로로 온다. 구분별 값을 품목 카테고리의 시리즈로 다시 묶는다.
853
+ const KIND_SERIES: Record<string, string> = {
854
+ 'A-used_qty': '소요량',
855
+ 'B-receipt_qty': '입고 예정량',
856
+ 'C-expected_wip_qty': '예상 재고량',
857
+ };
692
858
 
693
- ```ts
694
- interface GridNotificationContract {
695
- show: (item: Omit<NotificationItem, 'id' | 'createdAt'>) => string;
696
- update: (id: string, patch: Partial<NotificationItem>) => void;
697
- remove: (id: string) => void;
698
- list: NotificationItem[];
699
- }
700
- // NotificationItem = { id; type: SSENotificationType; operation?: ExcelNotificationOperationType;
701
- // title; message?; progress?; createdAt }
702
- // SSENotificationType = 'INFO' | 'WARNING' | 'ERROR' | 'SUCCESS'
703
- // ExcelNotificationOperationType = 'IMPORT' | 'EXPORT' | 'EXPORT_CSV'
859
+ // 모듈 상수(또는 useMemo/useCallback) — 함수 identity 가 안정적이어야 매 렌더 차트 재계산이 없다.
860
+ const chartConfig: PivotChartConfig = {
861
+ transformData: (data, ctx) => {
862
+ if (ctx.categoryField !== 'kind') return data; // 다른 범주 선택 시 기본 동작
863
+ const byItem = new Map<string, Map<string, (number | null)[]>>();
864
+ for (const cat of data.categories) {
865
+ const parts = cat.name.split(' › ');
866
+ const seriesName = KIND_SERIES[parts[parts.length - 1]!];
867
+ if (!seriesName) continue;
868
+ const item = parts.slice(0, -1).join(' › ');
869
+ const qty = cat.series.find((s) => s.name === '수량')?.data ?? [];
870
+ const seriesMap = byItem.get(item) ?? new Map();
871
+ const acc = seriesMap.get(seriesName) ?? data.xLabels.map(() => 0);
872
+ qty.forEach((v, i) => { acc[i] = (acc[i] ?? 0) + (v ?? 0); });
873
+ seriesMap.set(seriesName, acc);
874
+ byItem.set(item, seriesMap);
875
+ }
876
+ return {
877
+ xLabels: data.xLabels,
878
+ categories: [...byItem].map(([name, m]) => ({
879
+ name,
880
+ series: [...m].map(([sName, d]) => ({ name: sName, data: d })),
881
+ })),
882
+ };
883
+ },
884
+ // '#01' 같은 정렬용 접두어(3글자) 제거 — 표시만 바뀌고 데이터(xLabels)는 그대로.
885
+ xAxisLabelFormatter: (label) => (label.startsWith('#') ? label.slice(3) : label),
886
+ };
887
+
888
+ <MozGrid coreConfig={coreConfig} useChart chartConfig={chartConfig} />;
704
889
  ```
705
890
 
706
- > ⚠️ 이 contract는 신규 `MozGrid` 컴포넌트가 사용하지 **않습니다**([Vue와의 비대칭](#vue와의-비대칭-주의) 참조). `GridNotificationProvider`(= `GridNotificationContext.Provider`)로 주입하고 `useGridNotificationStore()`(contract 또는 `null` 반환)로 소비합니다. `provideGridNotification(contract)`는 contract를 그대로 반환합니다(Provider 권장).
707
-
708
- ### Contexts (legacy)
709
-
710
- `GridInstanceContext`, `GridContainerContext`, `GridRendererContext`, `GridTemplateRegistryContext`, `GridFilterModuleContext`, `GridSortModuleContext`, `GridCustomCellModuleContext`, `GridBulkEditorModuleContext` — (legacy) 내부이지만 public한 React Context 8종. 템플릿 등록에는 `GridTemplateRegistryContext`를 사용합니다.
711
-
712
- ### 훅 (legacy)
713
-
714
- | 훅 | 시그니처 | 설명 |
715
- |----|----------|------|
716
- | `useGridInstance` | `(): PureSheet \| null` | 현재 PureSheet(초기화 전 `null`). Vue와 달리 인스턴스를 직접 반환 |
717
- | `useGridEvents` | `(handlers: Partial<Record<PureSheetEventType, (payload: unknown) => void>>): void` | 그리드 이벤트 선언형 구독(자동 등록/해제) |
718
- | `useGridSelection` / `useGridSelect` | `() => ...` | 선택 상태/헬퍼(별개 2종) |
719
- | `useGridChanges` | `() => ...` | 변경 추적 |
720
- | `useGridUndoRedo` | `(): UseGridUndoRedoReturn` | Undo/Redo 컨트롤러 |
721
- | `useGridValidation` | `(): UseGridValidationReturn` | 검증 상태 |
722
- | `useGridColumns` | `(): UseGridColumnsReturn` | 컬럼 상태/표시 |
723
- | `useSearchHighlight` | `() => ...` | 검색 하이라이트 부수효과 |
724
- | `useCellTooltip` | `() => ...` | 셀 툴팁 동작 |
725
- | `useGridFilter` | `(): UseGridFilterReturn` | 필터 모듈 |
726
- | `useGridSort` | `(): UseGridSortReturn` | 정렬 모듈 |
727
- | `useGridCustomCell` | `(): UseGridCustomCellReturn` | 커스텀 셀(조건부 서식) 모듈 |
728
- | `useGridBulkEditor` | `(): UseGridBulkEditorReturn` | 일괄 편집 컨트롤러 |
729
- | `useContextMenu` | `(): UseContextMenuReturn` | 컨텍스트 메뉴 컨트롤러 |
730
- | `useClientExcelStore` | `(): UseClientExcelStoreReturn` | 클라이언트 Excel store |
731
- | `useMozLocale` | `(): MozLocaleMessages` | locale 메시지 객체 직접 반환 |
732
- | `useGridNotificationStore` | `(): GridNotificationContract \| null` | host 주입 notification contract 조회 |
733
-
734
- > ⚠️ React 전용으로 제공되지 않는 항목: **`useGridPreset` / userLayout 훅은 없습니다**(Vue 전용).
735
-
736
- ### 타입 재노출 (legacy)
737
-
738
- - 피벗 prop/config 타입: `PivotGridProps`, `PivotFieldItem`, `AvailableField`, `PivotFieldPanelConfig`, `PivotGridSettings`
739
- - legacy 컴포넌트 props 타입: `MozGridReactCoreProps`, `MozGridReactProps`
740
- - 슬롯/레지스트리 타입: `CellSlotProps`, `HeaderSlotProps`, `EditorSlotProps`, `DetailSlotProps`, `GroupSlotProps`, `TemplateRegistry`, `TemplateEntry`
741
- - 차트 타입: `ChartType`, `ChartSeriesConfig`, `EChartsConfig`
742
- - 클라이언트 Excel 타입: `UseClientExcelStoreReturn`, `ClientExcelExportConfig`, `BulkEditorState`, 및 각 훅 반환 타입(`UseGridUndoRedoReturn`/`UseGridValidationReturn`/`UseGridColumnsReturn`/`UseGridFilterReturn`/`UseGridSortReturn`/`UseGridCustomCellReturn`/`UseGridBulkEditorReturn`/`UseContextMenuReturn`)
743
- - 툴바 config 타입: `ComparisonOperator`, `LogicalOperator`, `IFilterDef`, `ISortDef`, `ICustomCellDef`, `ICustomCellRuleSet`, `ICustomCellConfig`, `IContextMenuItem`, `IContextMenuConfig`, `IContextMenuExcelEventArgs`, `AggregateType`, `IAggregateValues`
744
- - 코어 타입 pass-through(`@vmscloud/moz-ui-grid-core` 재노출, type-only): `PureSheetConfig`, `FieldDef`, `PureSheetEventType`, `SortState`, `FilterState`, `FilterOperator`, `Row`, `ColumnDef`, `ColumnState`, `EditorConfig`, `GridState`, `GridActivity`, `ViewState`, `RowSelectionMode`, `CellSelectionMode`, `RowSelectionTrigger`, `RowSelectionConfig`, `CellSelectionConfig`, `DataType`, `AggregateFunc`, `RowState`, `ValidationRule`, `ValidationError`, `ValidationTrigger`, `GroupConfig`, `GroupingConfig`, `TreeConfig`, `MasterDetailConfig`, `HeaderGroupDef`, `FormatRowCallback`, `PartialTranslationMap`, `IPivotAPI`, `PivotState`, `PivotConfig`, `PivotRowField`, `PivotColumnField`, `PivotValueField`, `PivotResult`, `PivotHeaderNode`, `PivotRow`, `ShowValueAs`, `IRowsAPI`, `IColumnsAPI`, `IChangesAPI`, `IUndoManagerAPI`, `IGroupAPI`, `ITreeAPI`, `IMasterDetailAPI`, `IValidationAPI`, `IClipboardAPI`, `IHeadersAPI` — 및 런타임 값 `UndoStack`
745
-
746
- > ⚠️ 서버사이드 Excel은 이 패키지에 의도적으로 포함되지 않습니다 — 클라이언트사이드 Excel만 존재합니다.
891
+ ### locale
892
+
893
+ | 속성 | 타입 | 기본값 | 설명 |
894
+ |------|------|--------|------|
895
+ | `locale` | `string` (`'ko' \| 'en' \| 'ja' \| 'zh'` 또는 `'en-US'` 같은 접두 형식) | `'ko'` | UI 언어(우선순위는 공통 설정 Provider 참조) |
896
+
897
+ - `GridChrome.setLocale`은 공개 메서드가 아닙니다. 대신 `locale` prop/공통 Provider의 변경을 어댑터가 코어 설정에 합쳐 reconcile하고, 상태 보존 재생성으로 적용합니다(`onReady` 재호출).
898
+ - `locale`은 `WrapperExtras`와 코어 `PureSheetConfig` 양쪽에 존재하는 유일한 키이며(둘 다 `string`), 의도적으로 strip되지 않아 코어 설정과 래퍼 옵션(`options.locale`) 양쪽에 전달됩니다(`WRAPPER_EXTRA_KEYS` 에서 `locale` 제외).
899
+
900
+ ---
901
+
902
+ ## Legacy exports
903
+
904
+ 현재 `src/index.ts`는 `export * from './legacy'`를 제공하지 않습니다. 이전 `MozGridReact`/`MozPivotReact`, `MozConfigProvider`, notification contract, `useGrid*` 훅, `ReactRenderer`, 슬롯/코어 타입 등을 React 루트에서 import하는 예제를 사용하지 마세요.
905
+
906
+ - 그리드: `MozGrid` + `MozGridProps`/`MozGridHandle`
907
+ - 공통 locale/timeZone: `MozGridConfigProvider` + `useMozGridConfig`
908
+ - 독립 템플릿/슬롯 root의 소비자 context: `MozContextWrapperContext.Provider`에 `ContextWrapper`를 제공(`useMozContextWrapper`도 공개). wrapper 함수 참조를 안정적으로 유지하세요.
909
+ - 코어 타입은 `@vmscloud/moz-ui-grid-core`, 공용 래퍼 타입은 `@vmscloud/moz-ui-grid-wrapper`에서 가져옵니다. React 루트에서 편의 재노출하는 코어/래퍼 타입은 `PureSheet`와 `GridChrome`입니다.
747
910
 
748
911
  ---
749
912