@vmscloud/moz-ui-grid-vue 0.2.17 → 0.2.18

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 Vue 래퍼 레퍼런스 (Props · Emits · Slots · Instance API)
2
2
 
3
3
  > 패키지: `@vmscloud/moz-ui-grid-vue`
4
- > 검증 기준: `0481bd5` (main 리베이스 후 소스)
4
+ > 정적 소스 대조 기준: `c945b9f5db6a930d07bc32f46ea2ebc90f455e46` (Vue manifest 0.2.17). 예제 실행·빌드·생성 선언·npm 게시물 일치 검증은 포함하지 않습니다.
5
5
 
6
6
  > 📋 **이 문서는 프레임워크 래퍼 표면만 다룹니다.** 코어 그리드 설정(`PureSheetConfig` 필드), 코어 타입, 이벤트 페이로드 형태는 코어 레퍼런스에 있습니다 → [reference.md](reference.md)
7
7
  > 📋 사용 예제 코드는 [examples.md](examples.md) 참조
@@ -54,6 +54,7 @@ const gridConfig = reactive<MozGridConfig>({ locale: 'ko', timeZone: 'Asia/Seoul
54
54
  - [인스턴스 API (ref / defineExpose)](#인스턴스-api-ref--defineexpose)
55
55
  - [GridChrome 표면 전체](#gridchrome-표면-전체)
56
56
  - [공개 컨트롤러 및 필드](#공개-컨트롤러-및-필드)
57
+ - [공유 wrapper controller 호출 계약](#공유-wrapper-controller-호출-계약)
57
58
  - [공개 메서드](#공개-메서드)
58
59
  - [coreConfig 역류 (update:coreConfig)](#coreconfig-역류-updatecoreconfig)
59
60
  - [템플릿 컴포넌트](#템플릿-컴포넌트)
@@ -63,6 +64,7 @@ const gridConfig = reactive<MozGridConfig>({ locale: 'ko', timeZone: 'Asia/Seoul
63
64
  - [User Layout Contract](#user-layout-contract)
64
65
  - [compat 래퍼 (MozGridVue / MozPivotVue)](#compat-래퍼-mozgridvue--mozpivotvue)
65
66
  - [추가 익스포트 (엑셀·locale·타입·Injection 키)](#추가-익스포트-엑셀locale타입injection-키)
67
+ - [객체 값 열·점 표기 필드](#객체-값-열점-표기-필드)
66
68
  - [관련 문서](#관련-문서)
67
69
 
68
70
  ---
@@ -113,7 +115,7 @@ npmjs에 공개 패키지로 게시됩니다. 레지스트리 매핑이나 토
113
115
  | `file-saver` | `>=2.0.0` | 선택 (Excel 기능 사용 시) |
114
116
  | `dayjs` | `>=1.11.0` | 선택 (알림 타임스탬프 등) |
115
117
 
116
- > 런타임 의존성(peer 아님): `@vmscloud/moz-ui-grid-core`, `@vmscloud/moz-ui-grid-wrapper`, `crypto-js`, `echarts`, `es-toolkit`. 별도 설치 불필요(패키지 deps).
118
+ > 런타임 의존성(peer 아님): `@vmscloud/moz-ui-grid-core`, `@vmscloud/moz-ui-grid-wrapper`, `bytebuffer`, `crypto-js`, `echarts`, `es-toolkit`, `lit-html`. 별도 설치 불필요(패키지 deps).
117
119
 
118
120
  ### 스타일 import
119
121
 
@@ -131,6 +133,7 @@ wrapper의 `style.css`는 코어 + 셸 통합 스타일이며, 위 Vue 통합
131
133
  |----------|------|
132
134
  | `.` | 메인 엔트리 (`MozGrid`, 컨트랙트, 템플릿, compat 등) |
133
135
  | `./style.css` | 코어 + 셸 + Vue 어댑터 통합 스타일 |
136
+ | `./package.json` | 패키지 manifest |
134
137
  | `./store` | Excel 스토어 (`useExcelStore` / `useExcelDownloadStore` / `useExcelUploadStore`) |
135
138
  | `./utils/excel/*` | Excel 유틸 5종 (`ExcelGuard` · `ExcelQueueManager` · `ExcelTaskRunner` · `ExcelTimeoutManager` · `ExcelImportUtil`) |
136
139
 
@@ -138,7 +141,7 @@ wrapper의 `style.css`는 코어 + 셸 통합 스타일이며, 위 Vue 통합
138
141
 
139
142
  ### 타입 재노출
140
143
 
141
- Vue 패키지는 소비처가 wrapper/core 를 직접 의존하지 않아도 핸들러를 타입 지정할 수 있도록 아래 타입을 재노출합니다 (ITSM-2026-002154).
144
+ Vue 패키지는 소비처가 wrapper/core 를 직접 의존하지 않아도 핸들러를 타입 지정할 수 있도록 아래 주요 타입을 재노출합니다 (ITSM-2026-002154). 전체 export의 정본은 [Vue 엔트리](../../moz-ui-grid-vue/src/index.ts)입니다.
142
145
 
143
146
  | 타입 | 원 출처 | 용도 |
144
147
  |------|---------|------|
@@ -191,7 +194,6 @@ function onReady(grid: PureSheet, chrome: GridChrome) {
191
194
  v-model:coreConfig="coreConfig"
192
195
  height="600px"
193
196
  :use-tool-box="true"
194
- :use-summary-footer="true"
195
197
  locale="ko"
196
198
  @ready="onReady"
197
199
  @cell:change="(p) => console.log('cell changed', p)"
@@ -237,14 +239,12 @@ function onReady(grid: PureSheet, chrome: GridChrome) {
237
239
  `MozGridCoreProps`는 코어의 `PureSheetConfig`(= `FlatModeConfig | PivotModeConfig` 판별 유니온)를 단일 객체로 평탄화한 타입입니다.
238
240
 
239
241
  ```typescript
240
- type MozGridCoreProps =
241
- & Partial<Omit<FlatModeConfig, 'mode'>>
242
- & Partial<Omit<PivotModeConfig, 'mode'>>
243
- & { mode?: 'flat' | 'pivot' };
242
+ import type { MozGridCoreProps } from '@vmscloud/moz-ui-grid-vue';
244
243
  ```
245
244
 
245
+ - 공개 타입을 import하세요. flat/pivot의 키를 optional로 모으되 공통 키의 값은 **키별 union**이며, 두 설정의 교집합이 아닙니다. `data?: Record<string, unknown>[]`와 `mode?: 'flat' | 'pivot'`를 별도로 정의합니다. 정본은 wrapper의 `config/types.ts`입니다.
246
246
  - 평탄화 이유: Vue `defineProps<union>`은 외부 prop 타입을 `Record<string, any>`로 떨어뜨리므로, flat+pivot 모든 키를 정밀 타입을 유지한 채 optional로 노출하기 위함입니다(유니온의 엄격성 — `fields` 필수, 모드 배타 키 — 은 완화되지만 어댑터엔 무해).
247
- - **필드별 의미(`data`/`fields`/`pivot`/`group`/`theme`/`mode` 등)는 코어 레퍼런스 참조** → [reference.md Config](reference.md#config-설정). 본 문서는 "`MozGridCoreProps` === 코어 설정"이라는 사실만 명시합니다.
247
+ - **필드별 의미(`data`/`fields`/`pivot`/`rowGroupConfig`/`theme`/`mode` 등)는 코어 레퍼런스 참조** → [reference.md Config](reference.md#config-설정). 어댑터 타입은 위의 optional·키별 union·data 완화를 포함하므로 코어 판별 union과 동일한 타입은 아닙니다.
248
248
  - 내부 그리드 변경(피벗 패널, 옵션 모달, 그룹 변경)은 `update:coreConfig`로 부모에 역류합니다. → [coreConfig 역류](#coreconfig-역류-updatecoreconfig)
249
249
 
250
250
  > `coreConfig`의 `fields`는 코어에 전달되기 전에 `cell`/`header`/`editor` 슬롯 템플릿을 `FieldDef` 콜백으로 변환하는 `buildFieldDefs`를 거치며, raw 참조 동일성으로 메모이즈됩니다(큰 배열의 깊은 순회 회피).
@@ -253,6 +253,7 @@ type MozGridCoreProps =
253
253
 
254
254
  - `sourceRevision?: unknown`은 wrapper prop입니다. 외부 데이터셋 교체 때만 바꾸며 `coreConfig` 안에 넣지 않습니다. `ready`의 두 번째 인수 `chrome.getReadyContext()`에서 `{ reason, dataRestored, sourceRevision }`를 확인하세요. [세션 보존·Worker·끝점 API 상세](runtime-contracts.md).
255
255
  - 일반 런타임 설정은 공통 coordinator가 추적된 작업 종료를 기다려 동일 인스턴스에 적용하며 `ready`를 다시 발생시키지 않습니다. 실제 같은 소스 인스턴스 교체에서는 비동기 capture/restore로 baseline·dirty·지원 undo/redo를 이전하며 Worker 세션과 pivot 상태도 포함합니다. 미지원 custom history나 호환되지 않는 keyFields 변경은 거부합니다. capture 실패 시 기존 인스턴스를 유지하고 오류를 보고하며, 실제 load/validation 실패를 무한 재시도하지 않습니다. 세부 경계는 [실행 계약](runtime-contracts.md)을 따릅니다.
256
+ - Worker 모드(`coreConfig.loader.worker`)의 `loader.params`·`addRow` 값·데이터 행은 `toRaw()`로 벗기거나 평범한 객체로 넘기세요. `reactive`/`ref` Proxy를 그대로 넘겨도 동작은 하지만, 그리드가 호출마다 깊은 복사를 하고 `[PureSheet]` 경고를 냅니다.
256
257
 
257
258
  - **행 식별 설정은 필수**입니다. 업무 키가 있으면 `coreConfig.keyFields`를 명시하고, 키 없이 시작하려면 `coreConfig.enableReferCache: true`를 설정하세요(기본 `false`). 두 설정 모두 생략하거나 `keyFields: []`를 전달하면 초기화할 수 없습니다. 원본 `id` 필드를 자동으로 키로 선택하지 않습니다. 자동 ID 수명과 행 객체 참조 계약은 [코어 Config](reference.md#config-설정)를 참조하세요.
258
259
  - 내장 숫자/날짜시간 편집기는 별도 래퍼 prop이 아니라 `coreConfig.fields[].editor`로 전달합니다. 아래 설정을 기존 `coreConfig`에 병합하세요.
@@ -264,8 +265,8 @@ const editingConfig: MozGridCoreProps = {
264
265
  editable: true,
265
266
  timeZone: 'Asia/Seoul',
266
267
  fields: [
267
- { id: 'quantity', dataType: 'number', editor: { type: 'number', min: 0, max: 100, step: 0.5 } },
268
- { id: 'scheduledAt', dataType: 'date', editor: { type: 'datetime' },
268
+ { id: 'quantity', header: '수량', dataType: 'number', editor: { type: 'number', min: 0, max: 100, step: 0.5 } },
269
+ { id: 'scheduledAt', header: '예정 일시', dataType: 'date', editor: { type: 'datetime' },
269
270
  mask: { type: 'date', pattern: 'YYYY-MM-DD HH:mm:ss' } },
270
271
  ],
271
272
  };
@@ -279,7 +280,7 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
279
280
 
280
281
  `WrapperExtras`는 `WrapperUIOptions`를 확장(UI 플래그 + 래퍼 전용 키)합니다. 어댑터가 코어 설정을 만들기 전에 이 키들을 분리(strip)합니다. **코어 `PureSheetConfig`와 겹치는 유일한 키는 `locale`**(둘 다 `string`, 무해하며 의도적으로 분리 대상에서 제외 → 코어·래퍼 양쪽에 전달).
281
282
 
282
- > **기본값 출처 주의** — Vue는 미전달 boolean prop을 `false`로 변환합니다. `withDefaults`가 툴박스·푸터·설정 권한·피벗 바 등 기본-on 플래그와 `usePreset: true`를 복원하며, `useSummaryFooter`는 명시적으로 `false`입니다. React의 undefined → `resolveUIOptions` 경로와 구분하세요.
283
+ > **기본값 출처 주의** — Vue는 미전달 boolean prop을 `false`로 변환합니다. `withDefaults`가 툴박스·푸터·설정 권한·피벗 바 등 기본-on 플래그와 `usePreset: true`를 복원합니다. React의 undefined → `resolveUIOptions` 경로와 구분하세요.
283
284
  >
284
285
  > ⚠️ `usePivotBar` 가 `withDefaults` 에 있는 이유: **Vue 는 미전달 boolean prop 을 `false` 로 강제**하므로 여기서 복원하지 않으면 prop 을 안 준 소비처에서 바가 통째로 사라집니다(= 피벗 정렬·필터 진입점 상실).
285
286
 
@@ -289,8 +290,11 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
289
290
  | `height` | `string \| number` | `'100%'` | 호스트 컨테이너 높이(CSS). number=px, string=그대로. **어댑터 로컬 전용**(코어/chrome에 전달 X — `hostStyle` computed에만 사용) |
290
291
  | `useToolBox` | `boolean` | `true` | 툴박스 표시 |
291
292
  | `useToolBoxSetting` | `boolean` | `true` | 툴박스 설정(기어) 버튼 표시 |
292
- | `useExtendFooter` | `boolean` | `true` | 확장 푸터 표시 |
293
- | `useSummaryFooter` | `boolean` | `false` | 플랫 컬럼 하단 집계 행. 켰을 때 `fields[].aggregate`가 있는 컬럼만 현재 필터 결과로 계산한다. OFF는 footer 전용 계산도 하지 않는다. 컬럼 옵션 → 집계에서 표시·함수 편집 가능 |
293
+ | `useExtendFooter` | `boolean` | `true` | 확장 푸터 표시. 선택 영역 집계(평균/개수/최소/최대/합계)는 **선택 칸 2개 이상**일 때만 보인다(칸 1개면 숨김, 행 수 표시는 유지) |
294
+ | — 피벗 푸터 '총 개수'·빈 상태 | | | 피벗은 **원본 행 수**(소계·총합계·접기 무관)로 센다: 필터 없으면 `총 개수: 5개`, 필터 중이면 `필터 후 원본 / 전체 원본`(예 `2 / 5`, 플랫과 같은 형식). 필터로 원본 0건이면 총합계 행 없이 '검색 결과 없음' 안내(core `grid.pivot.getSourceRowCount()`) |
295
+
296
+ > 하단 합계 행은 `coreConfig.summaryRow: 'pinnedBottom'` 으로 켭니다(`fields[].aggregate` 가 있는 컬럼만 현재 필터 결과로 계산). 보기 설정 → 집계에서 표시·함수를 바꿀 수 있습니다. (`useSummaryFooter` prop 은 없어졌습니다.)
297
+
294
298
  | `useSort` | `boolean` | `true` | 정렬 기능 활성화 |
295
299
  | `useRowGroupSettings` | `boolean` | `true` | 그룹 설정 변경 허용. `false`이면 보기 설정의 그룹 패널 토글을 비활성화하고 정보 아이콘에 안내 툴팁 표시. 컨텍스트 메뉴 진입점은 숨기고 그리드 설정의 그룹핑 탭은 읽기 전용. 기존 그룹·그룹별 집계 보존. 런타임 변경 지원 |
296
300
  | `useFieldSettings` | `boolean` | `true` | 플랫 왼쪽 컬럼 목록의 표시·순서 변경 허용. false이면 피벗 필드 탭 본문 전체(검색·선택·드롭존·집계 편집)와 옵션 바 드래그를 차단. 옵션 바에는 별도 안내 표시 없이 정렬·필터·닫기는 유지. 고정·너비는 오른쪽 서식 영역에 속하므로 `useFormatSettings`로 제어. 런타임 변경 지원 |
@@ -333,10 +337,24 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
333
337
  >
334
338
  > 그래서 기본값이 `true` 이며, `false` 는 "피벗을 읽기 전용 표로만 쓴다"는 선언에 가깝습니다.
335
339
 
336
- > **라이브 토글** — UI 플래그(`useToolBox`/`useToolBoxSetting`/`useExtendFooter`/`useSummaryFooter`/`useSort`/`useCustomCell`/`useContextMenu`/`useBuiltinTooltip`/`toolbarConfig`/`usePivot`/`usePivotBar` 및 `useRowGroupSettings`/`useFieldSettings`/`useFormatSettings`/`useLayoutSettings`/`useAggregateSettings`)는 변경 시 `chrome.setUIOptions`로 런타임 갱신됩니다(`useBuiltinTooltip`은 내장 툴팁 렌더러를 런타임에 생성/파괴). `loading`·`sourceFields`·**`contextMenuConfig`**·**`chartConfig`** 는 각자 전용 watch 를 가집니다(`sourceFields`/`contextMenuConfig`/`chartConfig` 는 래퍼 전용 키라 reconcile 을 거치지 않고 `chrome.setSourceFields`/`chrome.setContextMenuConfig`/`chrome.setChartConfig` 로 직접 전파). `useChart`/`useMultipleChart`/`chartIncludeGrandTotal`/`chartMap`은 `GridChromeOptions`로 전달만 됩니다(라이브 watch X). `height`는 순수 어댑터 로컬(`hostStyle`).
340
+ > **라이브 토글** — UI 플래그(`useToolBox`/`useToolBoxSetting`/`useExtendFooter`/`useSort`/`useCustomCell`/`useContextMenu`/`useBuiltinTooltip`/`toolbarConfig`/`usePivot`/`usePivotBar` 및 `useRowGroupSettings`/`useFieldSettings`/`useFormatSettings`/`useLayoutSettings`/`useAggregateSettings`)는 변경 시 `chrome.setUIOptions`로 런타임 갱신됩니다(`useBuiltinTooltip`은 내장 툴팁 렌더러를 런타임에 생성/파괴). `loading`·`sourceFields`·**`contextMenuConfig`**·**`chartConfig`** 는 각자 전용 watch 를 가집니다(`sourceFields`/`contextMenuConfig`/`chartConfig` 는 래퍼 전용 키라 reconcile 을 거치지 않고 `chrome.setSourceFields`/`chrome.setContextMenuConfig`/`chrome.setChartConfig` 로 직접 전파). `useChart`/`useMultipleChart`/`chartIncludeGrandTotal`/`chartMap`은 `GridChromeOptions`로 전달만 됩니다(라이브 watch X). `height`는 순수 어댑터 로컬(`hostStyle`).
337
341
  >
338
342
  > ⚠️ `contextMenuConfig` 는 이전에 `buildChromeOptions` 가 **생성 시 1회만** 읽어, 소비자가 `customMenu` 를 비동기로 채우면(예: 새 탭 열기 대상의 메뉴 이름 lookup 응답 후) 그대로 유실됐습니다. 이제 변경 시 크롬에 전파됩니다.
339
343
 
344
+ ### Prop 변경 반영 경계
345
+
346
+ | 입력 | 추적/반영 방식 | 사용 경계 |
347
+ |---|---|---|
348
+ | `coreConfig`, resolved `locale`/`timeZone`, `sourceRevision` | 공통 settings coordinator | `data`/`fields`는 참조 교체, 나머지 reactive 설정은 deep 추적. 일반 runtime 적용은 동일 인스턴스이며 `ready` 재발화 없음. 실제 교체/완료는 [준비 완료 계약](#준비-완료재생성서식-콜백) 참조 |
349
+ | UI 플래그, `toolbarConfig.filter.disabled/hidden` | 읽은 값 watch → `setUIOptions` | toolbarConfig의 reactive 중첩 값 변경도 추적 |
350
+ | `loading` | immediate watch → `setUIOptions` | 호스트 로딩 상태 |
351
+ | `sourceFields` | 배열 identity watch → `setSourceFields` | in-place push/항목 변경만으로 전파되지 않으므로 새 배열 전달 |
352
+ | `contextMenuConfig`, `chartConfig` | deep watch → 전용 setter | reactive 중첩 변경 지원; core reconcile 우회 |
353
+ | `height` | 로컬 computed CSS | core/chrome 설정 아님 |
354
+ | `emptyState`, `useChart`, `useMultipleChart`, `chartIncludeGrandTotal`, `chartMap` | chrome 생성 시 읽음 | 라이브 prop watch 없음. 변경을 적용하려면 명시적인 재마운트 경계 사용 |
355
+ | Chrome named slot의 존재/매핑 | chrome 생성 시 수집 | 이미 등록된 슬롯의 내용 갱신과 새 슬롯 이름을 조건부로 추가하는 것은 다름. 슬롯 구성은 안정적으로 유지 |
356
+ | `usePreset` | composable 생성 시 읽음 | 마운트 후 활성화/해제 전환 없음 |
357
+
340
358
  ### WrapperUIOptions — UI 표시 플래그
341
359
 
342
360
  `WrapperExtras`가 확장하는 UI 플래그 베이스 인터페이스입니다. `DEFAULT_UI_OPTIONS`(`Required<WrapperUIOptions>`)가 기본값 SSOT이고, `resolveUIOptions`가 `options.X ?? DEFAULT_UI_OPTIONS.X`로 병합합니다(프레임워크 무관 기본값).
@@ -346,12 +364,12 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
346
364
  | `useToolBox` | `true` | `useContextMenu` | `true` |
347
365
  | `useToolBoxSetting` | `true` | `usePivot` | `false` |
348
366
  | `useExtendFooter` | `true` | `usePivotBar` | `true` (피벗 전용) |
349
- | `useSummaryFooter` | `false` | `useChart` | `false` (피벗 전용) |
367
+ | | | `useChart` | `false` (피벗 전용) |
350
368
  | `useSort` | `true` | `loading` | `false` |
351
369
  | `useCustomCell` | `true` | `useBuiltinTooltip` | `true` |
352
370
  | `useRowGroupSettings` | `true` | `useFieldSettings` | `true` |
353
371
  | `useFormatSettings` | `true` | `useLayoutSettings` | `true` |
354
- | `useAggregateSettings` | `true` | — | — |
372
+ | `useAggregateSettings` | `true` | `toolbarConfig` | `{ filter: { disabled: false, hidden: false } }` |
355
373
 
356
374
  > `useMultipleChart`/`chartIncludeGrandTotal`/`chartMap`/`chartConfig`/`height`/`locale`/`sourceFields`/`contextMenuConfig`/`emptyState`는 `WrapperUIOptions`가 아니라 `WrapperExtras`에만 존재합니다.
357
375
 
@@ -362,6 +380,7 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
362
380
  | 속성 | 타입 | 설명 |
363
381
  |------|------|------|
364
382
  | `useViewSelectColumn` | `boolean?` | 컬럼 표시 선택 항목 토글 |
383
+ | `useMoveColumnFirst` | `boolean?` | 일반 메뉴 `첫 번째 열로 보내기` 항목(기본 `false`). 고정 바로 뒤. 예) 열 `[a,b,c]`에서 b → `[b,a,c]`. 고정 열·합성 열(행 헤더 등)·다단 헤더(컬럼 그룹) 그리드에서는 비활성. 피벗 메뉴에는 없음 |
365
384
  | `useFixedColumn` | `boolean?` | 컬럼 고정 항목 토글 |
366
385
  | `useContextMenu` | `boolean?` | 컨텍스트 메뉴 마스터 토글(주: `WrapperUIOptions.useContextMenu`와 별개) |
367
386
  | `useSort` | `boolean?` | 정렬 항목 토글 |
@@ -388,7 +407,24 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
388
407
  | `onClientExportComplete` | `(result: SaveExcelToFileResult) => void` | 클라이언트 내보내기 완료 — FSA 스트리밍/폴백 다운로드 결과 |
389
408
  | `customMenu` | `IContextMenuItem[]?` | 커스텀 메뉴 항목 |
390
409
 
391
- `IContextMenuItem = { id: string; label: string; icon?: string; disabled?: boolean; separator?: boolean; children?: IContextMenuItem[]; handler?: () => void; order?: number (기본 50); aggregate?: { header: string; aggLabel: string; value: string }; compact?: boolean }`
410
+ `IContextMenuItem = { id: string; label: string; icon?: string; disabled?: boolean | ((ctx: ContextMenuContext) => boolean); hidden?: boolean | ((ctx: ContextMenuContext) => boolean); separator?: boolean; children?: IContextMenuItem[]; handler?: (ctx: ContextMenuContext) => void; order?: number (기본 50); aggregate?: { header: string; aggLabel: string; value: string }; compact?: boolean }`
411
+
412
+ `ContextMenuContext = { area?: 'cell' | 'header'; rowIndex: number; rowId?: string | number; columnId: string; groupId?: string; field: FieldDef | null; cellValue: unknown; rowData: Record<string, unknown> | null }` — 우클릭한 위치. 열 머리글 = `area:'header'`(rowIndex -1, cellValue·rowData null), 데이터 셀·피벗 행 머리글 = `'cell'`. 피벗 값 셀은 `field` = 값 필드, `cellValue` = 집계값(빈 칸 null). `@vmscloud/moz-ui-grid-vue`에서 타입 export. ※ 메뉴 ctx 의 area 이고, `@contextmenu` 이벤트 원본 payload 의 `area`(`'columnHeader'` 등)는 그대로다.
413
+
414
+ - `hidden`(ITSM-2026-004445 버그 10): `customMenu`·`registerItems` 항목(`children` 포함)을 우클릭할 때마다 판단해 뺀다. 미지정 = 항상 보임. 자식이 모두 숨으면 부모도 빠지고, 빈 구분선은 남지 않는다. 함수가 예외를 던지면 보임 + `console.warn('[moz-ui-grid] …')`.
415
+ - `disabled` 함수형: 우클릭할 때마다 계산(예외 시 활성). 기존 `boolean` 그대로 동작.
416
+ - `handler(ctx)`: 우클릭한 위치를 받는다. 인자 없는 기존 핸들러도 그대로 동작.
417
+
418
+ 예) 재공 분포 현황 피벗 — 값 셀이 아니거나 값이 0 이면 '상세 보기' 숨김(Wijmo `setContextMenuProps(ht)` 대체):
419
+ ```ts
420
+ customMenu: [{ id: 'wip-detail', label: '상세 보기', hidden: ({ area, cellValue }) => area !== 'cell' || cellValue === 0, handler: onOpenPopup }]
421
+ ```
422
+ | 우클릭 | 메뉴 |
423
+ |---|---|
424
+ | 값 5 셀 | 상세 보기, ─, 필터…, 복사… |
425
+ | 값 0 셀 | 필터…, 복사… (상세 보기·그 아래 구분선 없음) |
426
+ | 열 머리글 (`area:'header'`) | 상세 보기 없음 |
427
+ (피벗 행 머리글도 `area:'cell'` 이라 숨기려면 `field?.id !== '<값 필드 id>'` 조건을 더한다.)
392
428
 
393
429
  ### emptyState — EmptyStateConfig
394
430
 
@@ -396,11 +432,18 @@ XLSX 컨텍스트 메뉴는 `contextMenuConfig.clientExportOptions`로 옵션을
396
432
 
397
433
  | 속성 | 타입 | 설명 |
398
434
  |------|------|------|
435
+ | `enabled` | `boolean?` | 빈 상태 안내 사용(기본 `true`). `false`면 전체 0행·필터 결과 0행 모두 안내를 그리지 않아 헤더만 있는 빈 표가 됨(작은 그리드에서 안내가 표를 덮을 때) |
399
436
  | `isCustomState` | `boolean?` | 커스텀 빈 뷰 강제(`custom_empty` 이미지) |
400
437
  | `headerMsg` | `string?` | 헤더 텍스트 오버라이드(미지정 시 뷰 타입별 i18n 기본) |
401
438
  | `contentMsg` | `string?` | 콘텐츠 텍스트 오버라이드(`unsafeHTML`로 렌더, 미지정 시 i18n 기본) |
402
- | `useImg` | `boolean?` | 빈 상태 이미지 표시(내부 기본 `true`) |
439
+ | `useImg` | `boolean \| 'auto'` | 빈 상태 이미지 표시(기본 `true`). `false`는 문구만. `'auto'`(옵트인)는 안내 영역 높이가 그림(최소 110px)+문구(약 60px)에 모자라면 그림만 숨기고 문구는 남김. 그리드 높이가 바뀌면 다시 판정 |
403
440
  | `selectImg` | `EmptyStateImg?` | 명시적 이미지 선택. 행 수가 0일 때 자동 뷰 타입을 오버라이드 |
441
+ | `noDataHeaderMsg` | `string?` | 전체 소스 0행 전용 헤더. 필터 결과만 0행인 경우에는 미적용 |
442
+ | `noDataContentMsg` | `string?` | 전체 소스 0행 전용 콘텐츠(HTML). 필터 결과만 0행인 경우에는 미적용 |
443
+
444
+ 둘 중 하나라도 지정하면 전체 0행은 읽기 전용 여부와 무관하게 `data_empty` 뷰를 사용합니다. `selectImg`/`isCustomState`가 이 자동 선택보다 우선하며, 미지정 시 기존 동작을 유지합니다. `contentMsg`/`noDataContentMsg`는 `unsafeHTML` 렌더 경로이므로 **신뢰할 수 있는 HTML만** 전달하세요. 외부 입력을 그대로 전달하지 마세요.
445
+
446
+ 예) 높이 150px 작은 그리드: `emptyState: { enabled: false }` → 0건이면 헤더만 있는 빈 표 / `emptyState: { useImg: 'auto' }` → 문구만 보이고 그림은 숨김(600px로 커지면 그림 다시 표시).
404
447
 
405
448
  `EmptyStateImg = 'Custom' | 'Result' | 'Data' | 'Search'`
406
449
 
@@ -425,13 +468,14 @@ type PivotSourceField = PivotRowField & PivotColumnField & PivotValueField;
425
468
  | `type` | `ChartType?` | 시리즈 차트 타입. `ChartType = 'bar' \| 'line' \| 'lineSymbols' \| 'area'` |
426
469
  | `color` | `string?` | 시리즈 색상(미지정 시 팔레트 기본) |
427
470
 
428
- > `ChartSeriesConfig`/`ChartType`은 래퍼 index에서 재-export되지 않으며 `chartMap`의 값 타입으로만 도달합니다.
471
+ > `ChartSeriesConfig`는 `import type { ChartSeriesConfig } from '@vmscloud/moz-ui-grid-wrapper'`로 가져올 수 있습니다. `ChartType`은 wrapper root에서 별도 재-export되지 않으며 `ChartSeriesConfig['type']`으로 참조할 수 있습니다. Vue root의 직접 재-export와는 구분하세요. wrapper에서 직접 import하는 앱은 wrapper를 직접 의존성으로 선언하세요.
429
472
 
430
473
  ### chartConfig — 내장 피벗 차트 설정
431
474
 
432
475
  **범위: wrapper 내장 피벗 차트(차트 버튼·컨텍스트 메뉴 '차트 열기'로 여는 차트 패널)만.** 그리드 셀·내보내기·`grid.pivot.getSeriesData()` 직접 호출 결과에는 영향이 없습니다. 이름은 `coreConfig`/`contextMenuConfig` 와 같은 `xxxConfig` 규칙입니다. Prop: `:chart-config`.
433
476
 
434
477
  ```ts
478
+ // 의미를 설명하는 타입 요약입니다. 복제 선언 대신 공개 타입을 import하세요.
435
479
  interface PivotChartConfig {
436
480
  /** getSeriesData 결과를 차트에 덮어쓰기 직전에 한 번 호출. 반환값을 차트 데이터로 사용 */
437
481
  transformData?: (data: PivotSeriesData, ctx: ChartTransformContext) => PivotSeriesData;
@@ -458,16 +502,16 @@ interface PivotChartCell {
458
502
  valueField: string; // 값 필드 id
459
503
  value: number | null; // 칸 값(숫자가 아니면 null)
460
504
  }
461
- // PivotSeriesData = { xLabels: string[]; categories: { name: string; key: string; path: string[]; series: { name: string; data: (number | null)[] }[] }[] }
505
+ // PivotSeriesData = { xLabels: string[]; categories: { name: string; key?: string; path?: string[]; series: { name: string; data: (number | null)[] }[] }[] }
462
506
  interface ChartTransformContext {
463
507
  categoryField: string; // 차트 툴바 'Y축 범주'(행 필드 식별자)
464
508
  columnFieldLevel: number | undefined; // 'X축 범주' 레벨, '전체'면 undefined
465
509
  valueFields: string[]; // 선택된 값 필드 식별자
466
510
  includeGrandTotal: boolean; // chartIncludeGrandTotal
467
- rowFields: { id; field; header }[]; // getPivotMeta().rowFields
468
- columnFields: { id; field; header }[]; // getPivotMeta().columnFields
469
- allValueFields: { id; field; header; aggregate? }[]; // getPivotMeta().valueFields (선택 무관)
470
- pivotConfig: PivotConfig | null; // grid.pivot.getConfig() — 내부 참조, 읽기 전용
511
+ rowFields: ReadonlyArray<Readonly<FieldMeta>>; // getPivotMeta().rowFields
512
+ columnFields: ReadonlyArray<Readonly<FieldMeta>>; // getPivotMeta().columnFields
513
+ allValueFields: ReadonlyArray<Readonly<FieldMeta>>; // getPivotMeta().valueFields (선택 무관)
514
+ pivotConfig: Readonly<NonNullable<ReturnType<PureSheet['pivot']['getConfig']>>> | null; // 내부 참조, 읽기 전용
471
515
  }
472
516
  ```
473
517
 
@@ -598,8 +642,8 @@ const xAxisLabelFormatter = (label: string) => (label.startsWith('#') ? label.sl
598
642
  | `locale` | `string` (코드: `'ko'` \| `'en'` \| `'ja'` \| `'zh'` 또는 `'en-US'` 같은 접두형) | `'ko'` | UI 언어(우선순위는 공통 설정 Provider 참조) |
599
643
 
600
644
  - 허용 코드는 `LocaleCode = 'ko' \| 'en' \| 'ja' \| 'zh'`이며 필드 타입은 `string`이라 접두형(`'en-US'`)도 내부 `normalize()`가 정규화합니다. 미설정 시 `'ko'`.
601
- - 공개 `GridChrome.setLocale`은 없습니다. `locale` prop/Provider 변경은 어댑터가 추적하여 상태 보존 재생성으로 적용합니다(`ready` 재발화). Excel 팝업·토스트 locale도 watch로 갱신됩니다.
602
- - `locale`은 `WrapperExtras`와 코어 `PureSheetConfig` 양쪽에 있는 유일한 키입니다(둘 다 `string`). 의도적으로 `WRAPPER_EXTRA_KEYS`에서 제외되어 코어 설정과 `options.locale` 양쪽에 흐릅니다. Vue에서도 `coreConfig` 변경 감지에 folding되어 locale 변경은 reinit을 유발합니다.
645
+ - 공개 `GridChrome.setLocale`은 없습니다. `locale` prop/Provider 변경은 coordinator가 core runtime 경로에 적용하며 동일 인스턴스에서는 `ready`를 재발화하지 않습니다. Excel 팝업·토스트 locale은 별도 watch로 갱신됩니다. 이것이 wrapper의 모든 기존 UI 문자열을 즉시 다시 번역한다는 보장은 아닙니다.
646
+ - `locale`은 `WrapperExtras`와 코어 `PureSheetConfig` 양쪽에 있는 유일한 키입니다(둘 다 `string`). 의도적으로 `WRAPPER_EXTRA_KEYS`에서 제외되어 코어 설정과 `options.locale` 양쪽에 흐릅니다. Vue에서도 `coreConfig` 변경 감지에 folding되며 일반 runtime 변경으로 처리합니다.
603
647
 
604
648
  > `WRAPPER_EXTRA_KEYS`는 compat 등에서 평탄 props를 분리할 때 사용하는 공용 키 목록입니다. `locale`은 코어·래퍼 양쪽에 필요하므로 제외됩니다. 새 `MozGrid`에는 코어 옵션을 `coreConfig`, 래퍼 옵션을 개별 prop으로 전달하세요.
605
649
 
@@ -691,7 +735,7 @@ const xAxisLabelFormatter = (label: string) => (label.startsWith('#') ? label.sl
691
735
 
692
736
  | 속성 | 타입 | 설명 |
693
737
  |------|------|------|
694
- | `disabled` | `boolean` | 그리드 비활성(표시 가능 행 0개) |
738
+ | `disabled` | `boolean` | `(total > 0 ? total : sourceRowCount) === 0`. 표시 행이 0이어도 원본 행이 있으면 false일 수 있으므로 필터 결과 없음과 동일하지 않음 |
695
739
  | `hasItem` | `boolean` | 적용된 항목(필터 등) 존재(`filter.count > 0`) |
696
740
 
697
741
  `toolItemsProps`는 `{ disabled: true, hasItem: false }`로 시드되며, ready/reinit 후 `chrome.getToolItemsProps()`로 채워지고 `onToolItemsPropsChange` 콜백으로 반응형 갱신됩니다.
@@ -740,6 +784,8 @@ const xAxisLabelFormatter = (label: string) => (label.startsWith('#') ? label.sl
740
784
 
741
785
  `MozGrid`의 `ref`로 노출되는 값은 **큐레이트된 메서드 목록이 아니라 현재 `GridChrome` 인스턴스 전체를 투명하게 노출하는 Proxy**입니다. `defineExpose`는 `GridChrome & { chrome: GridChrome | null }` 타입으로 선언됩니다. core `PureSheet`는 `ref.value.grid`로 노출되므로, displayed 좌표계 API도 `ref.value?.grid.getDisplayedData()` / `.getDisplayedRowIds()`(배열), `ref.value?.grid.rows.displayedCount()` / `.getByDisplayedIndex(i)` / `.getDisplayedIndexById(id)`(traversal 인덱싱)로 바로 호출합니다. 자세한 계약은 core 레퍼런스(reference.md)를 참조하세요.
742
786
 
787
+ > 위 내용은 소스의 runtime expose 계약입니다. 생성된 `InstanceType<typeof MozGrid>` 선언 및 strictTemplates named slot 추론까지 검증했다는 뜻은 아닙니다. 사용 중인 정확한 배포 artifact로 별도 확인해야 하며, 여기서는 특정 npm 버전의 선언 결함을 확정하지 않습니다. `@ready` 인자는 Vue root에서 재-export하는 `PureSheet`/`GridChrome` 타입으로 지정할 수 있습니다.
788
+
743
789
  ```typescript
744
790
  const g = gridRef.value; // GridChrome 표면 전체
745
791
  g.grid // 코어 PureSheet (→ reference.md)
@@ -752,7 +798,7 @@ g.getCoreConfig();
752
798
  g.chrome // 원시 GridChrome | null (legacy 호환)
753
799
  ```
754
800
 
755
- - Proxy는 접근 시마다 살아있는 `chrome`을 읽고, 함수면 `chrome`에 바인딩하여 반환합니다(메서드의 `this` 유지). `chrome`은 mount/reinit마다 재할당되므로 Proxy는 항상 최신 인스턴스를 반영합니다(stale 참조 없음).
801
+ - Proxy는 접근 시마다 살아있는 `chrome`을 읽고, 함수면 `chrome`에 바인딩하여 반환합니다(메서드의 `this` 유지). `gridRef.value.method()`처럼 매번 ref의 멤버에 접근하면 최신 인스턴스를 반영합니다. 단 `const fn = gridRef.value.method`로 캐시한 bound 함수나 `.grid`/`.chrome`/controller 객체는 취득 당시 인스턴스를 가리키므로 교체 후 stale일 수 있습니다. 직접 구독은 각 `@ready`에서 다시 등록하세요.
756
802
  - 특수 키 `chrome`은 원시 `GridChrome | null`(마운트 전/언마운트 후 `null`)을 반환합니다(하위 호환).
757
803
  - `mountChrome` 완료 전(또는 `onBeforeUnmount`로 `chrome=null` 이후)에는 `chrome` 외 모든 멤버가 `undefined`입니다.
758
804
 
@@ -781,7 +827,41 @@ g.chrome // 원시 GridChrome | null (legacy 호환)
781
827
  | `columnFormat` | `ColumnFormatController` (readonly) | 컬럼 단위 셀/헤더 스타일(조건부 서식 하위 base 레이어) |
782
828
  | `contextMenu` | `ContextMenuController` (readonly) | 컨텍스트 메뉴 |
783
829
  | `chart` | `ChartController \| null` (readonly) | **피벗 모드에서만 non-null**(flat 모드 `null` → 반드시 null 체크) |
784
- | `hosts` | `Readonly<Omit<ChromeShell, 'root'>>` (getter) | 내부 DOM 호스트 요소(읽기 전용): `gridPane`, `toolboxHost`, `groupPanelHost`, `notificationHost`, `body`, `gridContainer`, `emptyStateHost`, `loadingHost`, `footerHost`, `splitResizer`, `chartHost` |
830
+ | `hosts` | `Readonly<Omit<ChromeShell, 'root'>>` (getter) | 내부 DOM 호스트 요소(읽기 전용): `gridPane`, `toolboxHost`, `pivotBarHost`, `groupPanelHost`, `notificationHost`, `body`, `gridContainer`, `emptyStateHost`, `loadingHost`, `footerHost`, `splitResizer`, `chartHost` |
831
+
832
+ ### 공유 wrapper controller 호출 계약
833
+
834
+ 이 절은 Vue 전용 API가 아니라 `.filter`/`.excel`/`.contextMenu`/`.customCell`/`.columnFormat`의 **공유 wrapper 호출 계약**입니다. `.grid` 아래 core API와 구분하세요. 전체 인자 타입은 공개 `GridChrome`의 멤버 타입에서 가져오고 구현 정본은 [wrapper controllers](../../moz-ui-grid-wrapper/src/controllers/index.ts)를 참조하세요. UI 내부 panel/helper를 모두 소비자 지원 API로 나열한 목록은 아닙니다.
835
+
836
+ | 호출 | 반환·상태·실패 계약 |
837
+ |---|---|
838
+ | `filter.applyFilter(filters?: IFilterDef[], ownedKeys?: string[], groupFilter?: Extract<FilterGroup, { scope: 'group' }> \| null)` | `Promise<void>`. 원래 core Promise를 반환하므로 PS906 등 core 거절을 await 호출자에게 전달. 성공/실패 양쪽에서 core 상태로 resync하여 거절된 필터를 적용 상태로 남기지 않음. 미연결/동일 적용 no-op은 resolved Promise |
839
+ | `filter.setFilter`, `addFilter`, `clearFilter`, `applyAdvancedFilters` | void helper. `await clearFilter()`가 필터 적용 완료를 기다리지는 않음. 완료/거절이 필요한 경우 `applyFilter` 사용 |
840
+ | `excel.confirmImport()` | `Promise<void>`. 선택 파일을 가져오며 오류는 controller error/알림 hook으로 보고. grid/파일이 없으면 no-op |
841
+ | `excel.exportExcel()` / `exportOriginalExcel()` | 각각 `Promise<void>`. 오류는 controller error/hooks로 보고하며 저장 취소 AbortError도 cancel hook 후 정상 return. resolve만으로 파일 저장 성공을 판정하지 말고 완료·오류·취소 hook을 구분. core `grid.io`의 reject 계약과 다름 |
842
+ | `contextMenu.registerItems(items: IContextMenuItem[])` / `unregisterItems(itemIds: string[])` | 각각 void. 같은 id는 교체 / 지정 id 제거 |
843
+ | `contextMenu.onCommand(callback)` | `callback: (commandId: string, ctx: ContextMenuContext \| null) => void`, 반환 void. Unsubscribe를 반환하는 이벤트 API가 아님 |
844
+ | `customCell.setCustomCell(config: ICustomCellConfig \| null \| undefined)` / `getCustomCell()` | setter void, null/undefined는 빈 규칙으로 reset. getter는 ICustomCellConfig의 ruleSets 배열을 복사(깊은 복사 보장 아님) |
845
+ | `columnFormat.setConfig(config: IColumnFormatConfig \| null \| undefined)` / `getConfig()` | setter void, null/undefined는 빈 서식으로 reset하고 적용. getter는 IColumnFormatConfig의 깊은 복사본 |
846
+
847
+ ```ts
848
+ import type { GridChrome } from '@vmscloud/moz-ui-grid-vue';
849
+
850
+ async function applyFilters(
851
+ chrome: GridChrome,
852
+ filters: Parameters<GridChrome['filter']['applyFilter']>[0],
853
+ ) {
854
+ try {
855
+ await chrome.filter.applyFilter(filters);
856
+ } catch (error) {
857
+ // PS906 등의 적용 거절: controller는 core의 실제 필터 상태로 resync
858
+ console.error('필터 적용 거절', error);
859
+ throw error;
860
+ }
861
+ }
862
+ ```
863
+
864
+ 필터 적용 거절은 새 draft validation 이벤트나 데이터 validation 오류 맵 항목이 아닙니다. 서식 setter의 void 반환도 렌더 완료 신호가 아닙니다. 추적된 후속 작업이 필요하면 `await chrome.grid.waitForSettled()`를 사용하되 임의 외부 fetch/hook까지 기다린다고 해석하지 마세요. core 비동기 세션 캡처 `captureDataSessionAsync()` 및 지원 범위는 [실행 계약](runtime-contracts.md)에 위임합니다.
785
865
 
786
866
  ### 공개 메서드
787
867
 
@@ -791,14 +871,20 @@ g.chrome // 원시 GridChrome | null (legacy 호환)
791
871
  |--------|----------|------|
792
872
  | `ready` | `async ready(): Promise<void>` | 코어 `waitForReady` 대기 → `isDataLoaded=true` → 카운트/푸터/빈 상태 갱신. 생성 후 1회 호출 |
793
873
  | `on` | `on(event, handler): Unsubscribe` | 코어 이벤트 구독 + chrome 구독 목록에 추적(destroy 시 자동 정리). `Unsubscribe` 반환 |
794
- | `reconcileCoreConfig` | `reconcileCoreConfig(next: PureSheetConfig, sourceRevision?: unknown): { reinitRequired: boolean }` | 변경된 코어 설정 키만 타겟 setter로 적용. 런타임 setter 없는 키(rowHeight/headerHeight/keyFields/selection/editable/mode/locale/...) 변경 시 `{ reinitRequired: true }` 반환 → 어댑터가 상태 보존 reinit. disposed 시 early-return |
874
+ | `reconcileCoreConfig` | `reconcileCoreConfig(next: PureSheetConfig, sourceRevision?: unknown): { reinitRequired: boolean; completion?: Promise<void> }` | 어댑터 조정용. rowHeight/headerHeight/selection/editable/mode/locale 등은 runtime 적용하며 선택적 completion으로 완료/실패 전달(no-ready). keyFields·engine/loader 등 생성/소유권 경계 또는 sourceRevision 변경은 reinitRequired로 별도 교체 요청. disposed/no-op에는 completion이 없을 수 있음 |
875
+ | `isSourceReplacement` | `isSourceReplacement(next: PureSheetConfig, sourceRevision?: unknown): boolean` | 어댑터 조정용. data/loader identity 또는 sourceRevision 변경 여부 판정; 자체로 교체하지 않음 |
876
+ | `waitForReinitSettled` | `waitForReinitSettled(signal?: AbortSignal, sourceReplaced?: boolean): Promise<void>` | 어댑터 조정용. 복원 중 작업과 같은 소스의 추적 작업을 기다림. 소스 교체는 별도 대기 경계, abort/destroy 시 AbortError. 모든 외부 비동기 작업 완료 보장 아님 |
795
877
  | `getReadyContext` | `getReadyContext(): Readonly<GridChromeReadyContext>` | `{ reason: 'initial' \| 'configuration' \| 'source-replacement', dataRestored: boolean, sourceRevision?: unknown }`. 복원 성공 여부와 데이터 교체 경계 확인 |
796
878
  | `captureReinitState` | `captureReinitState(next?: PureSheetConfig, sourceRevision?: unknown): GridChromeReinitState` | destroy 전에 core/layout·dataSession·wrapper 상태 캡처. 같은 소스 capture 실패는 throw하여 기존 인스턴스를 보존. 소스 교체는 이전 세션 capture 생략 |
879
+ | `captureReinitStateAsync` | `captureReinitStateAsync(next?: PureSheetConfig, sourceRevision?: unknown): Promise<GridChromeReinitState>` | 실제 교체에 사용하는 비동기 캡처. Worker 세션 export까지 await한 **뒤에만** 기존 인스턴스 destroy. 캡처 실패 시 기존 인스턴스 유지; 폐기 후에는 AbortError |
797
880
  | `restoreReinitState` | `async restoreReinitState(state): Promise<void>` | 새 인스턴스에서 `applyConfig(core)` 후 `columnFormat`(base) → `customCell`(override) 재적용. 실패는 reject하며 성공 ready를 내보내지 않음. dataSession을 포함한 GridChromeReinitState를 새 GridChrome 생성자의 네 번째 인수로도 전달 |
798
881
  | `getCoreConfig` | `getCoreConfig(base?: PureSheetConfig): PureSheetConfig` | INPUT 형태의 현재 코어 설정 빌드(`update:coreConfig` 역류용). 라이브 mode/group/pivot 키만 오버레이, `fields`/`data` 참조 보존, sorts/filters 뷰 상태 미포함 |
799
- | `onConfigChange` | `onConfigChange(cb: () => void): Unsubscribe` | 내부 코어 변경(pivotConfig + groupConfig.columns) 구독 → `update:coreConfig` 역류. UI 옵션/차트/커스텀 셀은 미포함. 반환 unsub도 destroy 추적 |
882
+ | `onConfigChange` | `onConfigChange(cb: () => void): Unsubscribe` | 내부 코어 변경(pivotConfig + pivotMode + rowGroupConfig.columns) 구독 → `update:coreConfig` 역류. UI 옵션/차트/커스텀 셀은 미포함. 반환 unsub도 destroy 추적 |
800
883
  | `syncAppliedConfig` | `syncAppliedConfig(next: PureSheetConfig): void` | reconcile 기준선(`appliedConfig`) 강제 동기화 → 직후 prop-back reconcile을 no-op으로(중복 피벗 재계산 회피) |
801
884
  | `setSourceFields` | `setSourceFields(sources?: PivotSourceField[]): void` | 피벗 후보 카탈로그 런타임 갱신(래퍼 전용 키라 reconcile 우회, 어댑터가 직접 호출) |
885
+ | `setChartConfig` | `setChartConfig(config?: PivotChartConfig \| null): void` | 내장 chart controller에 전달. chart가 없으면 no-op; 렌더 완료 Promise가 아님 |
886
+ | `getPresetViewState` | `getPresetViewState(): WrapperViewOptions & { pivotBarVisible: boolean }` | 프리셋용 wrapper 보기 상태 |
887
+ | `setPresetViewState` | `setPresetViewState(state: Partial<WrapperViewOptions & { pivotBarVisible: boolean }> \| null \| undefined): void` | 주어진 boolean 키만 적용. null/undefined는 무시하며 reset은 baseline을 전달. 없는 툴바/피벗 바는 기존 setter의 no-op 규칙 적용 |
802
888
  | `setContextMenuConfig` | `setContextMenuConfig(config?: IContextMenuConfig): void` | **컨텍스트 메뉴 설정 런타임 갱신** (ITSM-2026-002154). `sourceFields` 와 같은 래퍼 전용 키라 reconcile 을 타지 않음 — `buildChromeOptions` 는 생성 시 1회만 읽으므로, 소비자가 `customMenu`/`exportFileName` 을 비동기로 확정하는 경우 이 메서드로 전파해야 유실되지 않음 |
803
889
  | `setUIOptions` | `setUIOptions(partial: Partial<WrapperUIOptions>): void` | UI 플래그 런타임 갱신 — `this.ui` 재해석 + 툴박스/푸터/컨텍스트 메뉴/로딩 오버레이 부분 재구성(PureSheet 미변경, 컨트롤러 상태/슬롯 보존). disposed 시 early-return |
804
890
  | `getToolItemsProps` | `getToolItemsProps(): WrapperToolItemsProps` | `tool-items` 슬롯 props `{ disabled, hasItem }` |
@@ -814,11 +900,11 @@ g.chrome // 원시 GridChrome | null (legacy 호환)
814
900
 
815
901
  > `hosts`는 메서드가 아니라 `get hosts()` 게터(`Readonly<Omit<ChromeShell, 'root'>>` 반환)라 위 메서드 표에 포함되지 않습니다.
816
902
 
817
- > 어댑터의 양방향 바인딩 4중주: `reconcileCoreConfig`(props → core), `getCoreConfig`(core → `update:coreConfig`), `onConfigChange`(내부 변경 구독), `syncAppliedConfig`(에코 억제). reinit 쌍: `captureReinitState`/`restoreReinitState`(`reconcileCoreConfig`가 `{ reinitRequired: true }` 반환 시 구동).
903
+ > 어댑터 조정용 메서드: `reconcileCoreConfig`(props → core), `getCoreConfig`(core → `update:coreConfig`), `onConfigChange`(내부 변경 구독), `syncAppliedConfig`(에코 억제), `isSourceReplacement`/`waitForReinitSettled` 및 capture/restore. Vue가 이 순서를 소유하므로 일반 소비처가 prop watch와 중복 호출하지 마세요. 실제 교체는 `await captureReinitStateAsync(...)` → 기존 destroy → 캡처 상태를 새 생성자에 전달 → ready → `await restoreReinitState(...)` 순서입니다. 동기 capture를 Worker 완료 대기의 대체로 사용하지 마세요. 지원 세션·실패·소스 교체 경계는 [실행 계약](runtime-contracts.md)을 따릅니다.
818
904
 
819
905
  **관련 보조 타입**:
820
906
 
821
- - `GridChromeReinitState = { core: ReturnType<PureSheet['getConfig']>; customCell: ReturnType<CustomCellController['getCustomCell']>; columnFormat: ReturnType<ColumnFormatController['getConfig']> }` — 상태 보존 reinit 스냅샷(코어 `getConfig`에 없는 래퍼 전용 조건부 서식·컬럼 서식 상태 포함). `@vmscloud/moz-ui-grid-wrapper`에서 export.
907
+ - `GridChromeReinitState`는 `@vmscloud/moz-ui-grid-wrapper`에서 타입 import하세요. 필수 `core`/`customCell`/`columnFormat` 외에 `dataSession?: unknown`, `sourceRevision?: unknown`, `sourceReplaced?: boolean`, `immediateApply?: boolean`, `reinitKeys?: string[]`를 포함합니다. `dataSession`은 불투명한 인스턴스 이전 상태이며 JSON preset/서버 저장 형식이 아닙니다. 내부 표현을 파싱하거나 조립하지 마세요.
822
908
  - `WrapperViewOptions = { toolbarVisible: boolean; footerVisible: boolean; groupPanelVisible: boolean }` — 뷰 설정 상태(옵션 모달에서 토글). `getViewOptions()`로 읽고, `GridChromeOptions.onViewOptionsChange`로 알림.
823
909
 
824
910
  ---
@@ -835,8 +921,8 @@ g.chrome // 원시 GridChrome | null (legacy 호환)
835
921
 
836
922
  들어오는 prop 변경 흐름:
837
923
 
838
- - `coreConfig` 변경 감지 — `data`/`fields`는 ref 동일성으로 추적(큰 배열 깊은 순회 X), 나머지는 deep-compare, `locale`은 fold-in(생성 시 키라 변경 시 reinit).
839
- - 변경 시 `chrome.reconcileCoreConfig(buildCoreConfig())` 실행. `reinitRequired`(런타임 setter 없는 변경)이면 상태 보존 `reinitChrome()` 발화 → `ready`가 다시 emit됩니다.
924
+ - `coreConfig` 변경 감지 — `data`/`fields`는 ref 동일성으로 추적(큰 배열 깊은 순회 X), 나머지는 deep-compare, resolved `locale`/`timeZone` 및 `sourceRevision`을 함께 추적합니다.
925
+ - 변경은 coordinator를 통해 `chrome.reconcileCoreConfig(config, sourceRevision)`에 전달하고 선택적 `completion`을 기다립니다. 일반 runtime 변경은 동일 인스턴스(no-ready)입니다. 실제 `reinitRequired`이면 비동기 상태 capture 성공 후 기존 인스턴스를 destroy하고 새 인스턴스의 ready/restore 완료 후에만 `ready`를 emit합니다. 실패·취소 경계는 [실행 계약](runtime-contracts.md)을 따릅니다.
840
926
 
841
927
  ```vue
842
928
  <script setup lang="ts">
@@ -867,6 +953,8 @@ watch(coreConfig, (cfg) => {
867
953
 
868
954
  (A) **Registry 기반** (`CellTemplate`/`HeaderTemplate`/`CellEditor`): `GRID_TEMPLATE_REGISTRY_KEY`를 inject하여 마운트 시 `registry.register({ type, fieldId: props.field, slot })`, 언마운트 시 `unregister`. `field: string` prop 필수.
869
955
 
956
+ `field`는 원본 데이터 키가 아니라 **`FieldDef.id`**입니다. 마운트 중 field prop을 바꾸거나 같은 fields 배열을 유지한 채 v-if로 템플릿을 추가/삭제해도 이미 빌드된 콜백이 자동 갱신되는 계약은 아닙니다. 템플릿 구성/field는 안정적으로 유지하세요. 구성 변경 시 올바른 등록이 끝난 뒤 새 fields identity로 다시 빌드하는 경계 또는 컴포넌트 재마운트가 필요합니다. 임의 동적 조합의 자동 재등록 지원을 뜻하지 않습니다.
957
+
870
958
  (B) **Grid-API 기반** (`DetailTemplate`/`GroupTemplate`): `GRID_INSTANCE_KEY` + `GRID_RENDERER_KEY`를 inject하여 코어를 직접 구동.
871
959
 
872
960
  | 컴포넌트 | props | default 슬롯 scoped props |
@@ -877,7 +965,21 @@ watch(coreConfig, (cfg) => {
877
965
  | `DetailTemplate` | `{ minHeight?: number=100; maxHeight?: number; singleExpand?: boolean=false; keepDetailRows?: boolean=true }` | `DetailSlotProps = { parentData: Record<string,unknown>; dataIndex: number }` |
878
966
  | `GroupTemplate` | (없음) | `GroupSlotProps = { groupKey: string; groupValue: unknown; level: number; count: number; isExpanded: boolean }` |
879
967
 
880
- - `DetailTemplate`: 마운트 시 `grid.masterDetail.setConfig`로 props 매핑(`keepDetailRows` → 코어 `keepMasterDetailRows`), 디테일 행마다 `detail:${dataIndex}` 키로 슬롯 렌더. 언마운트 시 `renderer.unmountByPrefix('detail:')`.
968
+ **Editor popup 경계:** `CellEditor`의 `EditorSlotProps`에는 `registerOwnedSurface`가 전달되지 않습니다. body/Teleport popup이 자동으로 editor 소유 영역에 등록되거나 자동 제거된다고 가정하지 마세요. 소유권 등록이 필요한 경우 [코어 custom editor 계약](reference.md#셀-에디터--editoroption--customeditorparams)에 따라 `CustomEditorParams.registerOwnedSurface?: (surface: HTMLElement) => () => void`를 실제 전달받는 core custom renderer 경로를 사용합니다. 아래는 그 경로의 수명주기 일부이며 CellEditor 슬롯 예제가 아닙니다.
969
+
970
+ ```ts
971
+ // params는 core CustomEditorParams, popup은 앱이 생성한 HTMLElement
972
+ const unregister = params.registerOwnedSurface?.(popup);
973
+ // editor renderer의 destroy에서 호출할 정리 함수
974
+ const cleanupPopup = () => {
975
+ unregister?.();
976
+ popup.remove(); // 등록 해제는 DOM 제거를 대신하지 않음
977
+ };
978
+ ```
979
+
980
+ 새 draft validation 이벤트/구독은 이 API에 포함되지 않습니다.
981
+
982
+ - `DetailTemplate`: 마운트 시 grid와 default 슬롯이 존재하면 `grid.masterDetail.setConfig`로 props 매핑(`keepDetailRows` → 코어 `keepMasterDetailRows`), 디테일 행마다 `detail:${dataIndex}` 키로 슬롯 렌더. 언마운트 시 `renderer.unmountByPrefix('detail:')`. 아래 예제는 공개 구성 형태이며 최초 mount 순서·grid 교체 시 재등록은 실행 검증 대상입니다. GroupTemplate의 준비 대기/재등록 계약을 DetailTemplate에도 동일하게 보장하지 않습니다.
881
983
  - `GroupTemplate`: `_groupTemplate` 이름(래퍼 예약 키)의 `formatRow` 핸들러를 등록한다. 코어는 이 핸들러에만 그룹행 대신 **보이는 그룹 배너 안의 호스트**(`span.ps-group-label.ps-group-template-host`)를 `ctx.element`로 넘기므로, 슬롯이 배너의 라벨·건수·집계 자리를 대체한다(펼침/접힘 토글 버튼은 코어가 유지, 배너 본문 클릭은 그룹 토글 — `rowGroupConfig.selectable: true`면 본문 클릭은 그룹 선택이고 토글은 ▾ 버튼만). 슬롯 요소의 이벤트(툴팁 등)는 슬롯이 그대로 받는다. 배너가 생기지 않는 컬럼 구성(가운데 컬럼이 모두 `unmergeOnGroupRow`)에서는 예전처럼 그룹행 요소에 마운트된다.
882
984
  - 호스트별로 인스턴스 1개(`renderer.mountInstance`)를 유지하고, 접기/펼치기·건수 변화는 props 갱신으로 반영한다. 배너가 스크롤/그룹 해제/데이터 갱신으로 사라지면 코어가 호스트에 보내는 내부 이벤트로 인스턴스를 정리한다. 그리드 준비 전에 마운트돼도 그리드 인스턴스가 준비되면 등록되고, 언마운트 시 `formatRow.removeHandler('_groupTemplate')` 후 남은 인스턴스를 정리한다.
883
985
 
@@ -909,7 +1011,7 @@ watch(coreConfig, (cfg) => {
909
1011
 
910
1012
  > `useGridPreset`은 **패키지 export 표면이 아닙니다**(`src/index.ts`/`legacy`에서 재-export 안 됨). `MozGrid`가 내부에서만 소비합니다. 소비처는 `MozGrid` + `name` prop + 호스트의 `'user-layout-module'` 제공으로 그 동작을 암묵적으로 얻습니다.
911
1013
 
912
- 사용자 레이아웃 프리셋을 저장/복원합니다. 등록은 `options.gridInstance` watch 안에서 **그리드 인스턴스 존재 + `options.name.value` 비어있지 않음** 조건의 첫 `data:loaded`에서, `layoutInstance.setDefaultToCurrent()` 후 `nextTick`에서 수행됩니다(Wijmo ExtendFlexGrid 패턴 — 동적 컬럼까지 default 스냅샷에 포착). 호스트가 모듈을 제공하지 않으면 register는 no-op.
1014
+ 사용자 레이아웃 프리셋을 저장/복원합니다. 등록은 `options.gridInstance` watch 안에서 **그리드 인스턴스 존재 + `options.name.value` 비어있지 않음** 조건의 첫 `data:loaded`에서, `layoutInstance.setDefaultToCurrent()` 후 `nextTick`에서 수행됩니다(Wijmo ExtendFlexGrid 패턴 — 동적 컬럼까지 default 스냅샷에 포착). `MozGrid`는 `readyGrid`를 넘기므로 기본 보기 캡처는 첫 `data:loaded`와 `ready` emit(소비자 `@ready` 처리) 둘 다 지난 뒤입니다 — inline data여도 `@ready`의 `setToolbarVisible(false)` 같은 초기 설정이 기본 보기에 들어가며, 기본 보기 적용 시 툴바가 다시 열리지 않습니다. 호스트가 모듈을 제공하지 않으면 register는 no-op.
913
1015
 
914
1016
  **exportDef 저장 항목**
915
1017
 
@@ -917,7 +1019,7 @@ watch(coreConfig, (cfg) => {
917
1019
  |---|---|---|---|
918
1020
  | `version` | `stampPresetVersion` — 최소 `'0.5'` | wrapper | 빠진 항목 정책 판정 |
919
1021
  | `current` (모드·컬럼 너비/고정/숨김/순서/집계·정렬·필터·행 그룹·트리/디테일·피벗 축·테마·`summaryRow` 등) | `grid.getConfig()` | core | `grid.applyConfig(config, { missing })` |
920
- | `current` 의 UI 옵션(`rowHeight`·`headerHeight`·`editable`·`tooltipMode`·`alternatingRows`·`showHeader`·`showStatusBar`·선택 모드 등) | `grid.getConfig()` | core(앱 선언값) | **core applyConfig 가 복원하지 않음**(F02, core 범위). `showStatusBar` 는 wrapper 가 항상 false 강제(자체 Footer) |
1022
+ | `current` 의 UI 옵션(`rowHeight`·`headerHeight`·`editable`·`tooltipMode`·`alternatingRows`·`showHeader`·`showStatusBar`·선택 모드 등) | `grid.getConfig()` | core | core `applyConfig`의 runtime UI 복원 대상. 모든 생성 옵션의 복원을 뜻하지 않음. `showStatusBar`는 core 지원과 별개로 wrapper 생성 설정에서 자체 Footer를 위해 false로 override하며 prop reconcile 대상에서도 제외 |
921
1023
  | `snapshot` | `grid.getConfig()` | core | 컬럼 reset baseline |
922
1024
  | `customCellConfig` | `getCustomCellState() ?? null` | wrapper | `setCustomCellState` |
923
1025
  | `columnFormatConfig` | `getColumnFormatState() ?? null` | wrapper | `setColumnFormatState` |
@@ -936,7 +1038,7 @@ watch(coreConfig, (cfg) => {
936
1038
 
937
1039
  `missing` 은 core `applyConfig(config, { missing })` 계약으로 전달합니다(옵션 객체는 `toCoreApplyOptions` 한 곳에서 생성). 이 옵션을 모르는 이전 core 는 무시하므로 core 상태의 snapshot 초기화는 core 지원 후에 동작합니다.
938
1040
 
939
- 연속 호출은 **마지막 요청 우선**입니다: 요청은 직렬로 실행되고(core 적용이 겹치지 않음), 차례가 왔을 때 더 새 요청이 있으면 건너뜁니다. 실패(변환 오류·core 오류)는 reject 하지 않고 `console.error('[Grid preset] import failed', error)` 로 알립니다 — 변환 오류는 적용 전에 거절되어 상태가 바뀌지 않습니다. `UserLayoutInstance` 의 `setApplied(true)` 타이밍은 바뀌지 않습니다(완료 전 표시).
1041
+ 연속 호출은 **마지막 요청 우선**이며 importer는 직렬 실행됩니다. core 적용 → columnFormat → customCell → 보기 상태를 각각 await하고, 마지막 `grid.waitForSettled()`로 추적 작업 완료를 기다립니다. importer 완료 성공 뒤에만 `UserLayoutInstance.setApplied(true)`를 호출합니다. 실패는 로그와 `failed` 결과로 전달합니다. 공개 호출자는 [User Layout Contract](#user-layout-contract)의 `await instance.apply(preset)` 결과를 확인하세요. Promise resolve 자체가 성공은 아닙니다. `superseded`/`disposed` 결과가 반환되어도 이미 시작된 importer의 즉시 물리 취소를 보장하지 않으며, 전체 적용의 rollback을 보장하지도 않습니다.
940
1042
 
941
1043
  `UseGridPresetOptions`: `usePreset?: boolean`(기본 `true`, 호출 시 `false`면 인스턴스 생성·watch·등록·복원 없이 즉시 반환), `name: Ref<string>`(필수), `type: 'mozFlatGrid' | 'mozPivotGrid'`(필수), `gridInstance: Ref<PureSheet | null>`(필수), `getCustomCellState`/`setCustomCellState`/`getColumnFormatState`/`setColumnFormatState`/`getViewOptionsState`/`setViewOptionsState`(선택 — setter 의 `null` = 빈 상태), `getSourceFields`/`onImportDiagnostics`(내부 호환성 옵션). `MozGrid` 의 `getSourceFields` 는 피벗 카탈로그의 `field` 를 선언 `fields` 의 실제 `id` 로 되찾아 넘깁니다(id≠field 그리드).
942
1044
 
@@ -956,7 +1058,7 @@ Wijmo `columnLayout`/`viewDefinition` 저장본은 import callback에서 검증
956
1058
  | `SSENotificationType` | `'info' \| 'notice' \| 'engine' \| 'progress' \| 'inbound' \| 'delete' \| 'demandplan'` | 알림 카테고리 |
957
1059
  | `ExcelNotificationOperationType` | `'upload' \| 'download'` | progress 알림 내 Excel 방향 |
958
1060
 
959
- > Excel 라이프사이클 훅(`ExcelLifecycleHooks`)은 어댑터가 `GridChromeOptions.excelHooks`로 GridChrome에 전달하며, 위 스토어가 없으면 모든 훅이 조용히 no-op입니다. 훅 종류 7개: `onImportComplete`, `onImportError`, `onExportStart`, `onExportProgress`, `onExportComplete`, `onExportError`, `onExportCancel`. 메시지 포맷팅(타임스탬프·header_key·`NotificationItem` 형태)은 앱별이라 Vue 어댑터가 소유합니다.
1061
+ > Excel 라이프사이클 훅(`ExcelLifecycleHooks`)은 어댑터가 `GridChromeOptions.excelHooks`로 GridChrome에 전달하며, 위 스토어가 없으면 다음 **알림 훅 7개만** 조용히 no-op입니다: `onImportComplete`, `onImportError`, `onExportStart`, `onExportProgress`, `onExportComplete`, `onExportError`, `onExportCancel`. 별도 `onClientImportOpen` 훅은 알림 스토어 유무와 무관하게 `clientExcelStore.show`로 가져오기 UI를 위임합니다. 메시지 포맷팅(타임스탬프·header_key·`NotificationItem` 형태)은 앱별이라 Vue 어댑터가 소유합니다.
960
1062
  >
961
1063
  > **파일명 소유권과 알림 타이밍**: 내보내기 파일명은 `ExcelController`가 단독 계산합니다(`${exportFileName}_${YYYYMMDD}.xlsx`). 이 값은 `suggestedName`으로 core에 넘어가고, core는 **저장 파일명이 확정된 시점**(FSA 경로 = 사용자가 저장 다이얼로그에서 저장을 누른 직후의 `handle.name`, 폴백 다운로드 경로 = 다이얼로그가 없으므로 즉시 `suggestedName`)에 `SaveExcelToFileOptions.onFileNameResolved(fileName)`를 호출합니다. `ExcelController`는 이를 `onExportStart(fileName)`으로 어댑터에 전달하므로, 진행률 알림은 **뜨는 순간부터** 실제 저장될 파일명을 표시합니다(사용자가 다이얼로그에서 이름을 바꿔도 다운로드 중 표시가 어긋나지 않음). 저장을 **취소하면 `onExportStart`가 발화하지 않아** 알림이 떴다 사라지는 깜빡임도 없습니다. `onExportComplete(result)`의 `result.fileName`은 동일한 확정명으로, 어댑터가 완료 시 한 번 더 동기화합니다.
962
1064
 
@@ -976,7 +1078,38 @@ provideGridNotification(myNotificationStore); // GridNotificationContract 구현
976
1078
  | `UserLayoutModuleContract` | `{ register(instance: UserLayoutInstance): void; currentPreset: any }` | 그리드가 호스트에 요구하는 컨트랙트 |
977
1079
  | `UserLayoutInstance` | class | export/import 콜백 + default 스냅샷 보유. moz-shared view-manager 인스턴스의 복사본(직접 수정 금지) |
978
1080
 
979
- `UserLayoutInstance` 생성자: `{ id: string; type: string; importDef: (value) => void; exportDef: () => any; strict?: boolean; dependencies?: Ref<any>; manualLifeCycle?: boolean }`. 생성 시(`this._default = exportDef()`)와 `setDefaultToCurrent()`에서 default 스냅샷을 캡처하고, `get id`/`get type`/`get/set current`/`get/set default`/`get/set pending` 접근자를 노출합니다(`set pending(false)`가 버퍼된 import를 flush). `manualLifeCycle`이 아니면 Vue `onBeforeUnmount`에서 자동 dispose하므로 반드시 setup 안에서 생성해야 합니다.
1081
+ `UserLayoutInstance` 생성자: `{ id: string; type: string; importDef: (value: any) => void | UserLayoutApplyResult | Promise<void | UserLayoutApplyResult>; exportDef: () => any; strict?: boolean; dependencies?: Ref<any>; manualLifeCycle?: boolean }`. 생성 시(`this._default = exportDef()`)와 `setDefaultToCurrent()`에서 default 스냅샷을 캡처하고, `get id`/`get type`/`get/set current`/`get/set default`/`get/set pending` 접근자를 노출합니다(`pending = false`가 버퍼된 요청 실행을 재개). `manualLifeCycle`이 아니면 Vue `onBeforeUnmount`에서 자동 dispose하므로 setup 안에서 생성해야 합니다.
1082
+
1083
+ | 공개 API/타입 | 계약 |
1084
+ |---|---|
1085
+ | `apply(value): Promise<UserLayoutApplyResult>` | 결과를 기다리는 명시적 호출. 각 요청은 requestId로 구분 |
1086
+ | `UserLayoutApplyStatus` | `'applied' \| 'superseded' \| 'disposed' \| 'failed'` |
1087
+ | `UserLayoutApplyResult` | `{ status: UserLayoutApplyStatus; requestId: number; error?: unknown }`. 두 결과 타입 모두 Vue root export |
1088
+ | `applying` / `disposed` | 읽기 전용 실행·대기 요청 존재 여부 / 폐기 상태 |
1089
+ | `lastApplyResult` / `requestId` | 최신 요청의 결과(없으면 undefined) / 요청 순번 |
1090
+ | `current = value` | 내부적으로 `void apply(value)`인 fire-and-forget setter. 완료가 필요한 코드는 apply 사용 |
1091
+ | `dispose()` | 멱등 폐기. 대기/실행 요청은 disposed로 settle; 이미 실행 중인 importer 자체를 즉시 중단하지 않음 |
1092
+
1093
+ ```ts
1094
+ import type { UserLayoutInstance } from '@vmscloud/moz-ui-grid-vue';
1095
+
1096
+ async function applyLayout(instance: UserLayoutInstance, preset: unknown) {
1097
+ const result = await instance.apply(preset);
1098
+ switch (result.status) {
1099
+ case 'applied':
1100
+ console.log('적용 완료', result.requestId);
1101
+ break;
1102
+ case 'failed':
1103
+ console.error('적용 실패', result.requestId, result.error);
1104
+ break;
1105
+ case 'superseded':
1106
+ case 'disposed':
1107
+ // 최신 요청/소유권이 아니므로 성공 알림을 표시하지 않음
1108
+ break;
1109
+ }
1110
+ return result;
1111
+ }
1112
+ ```
980
1113
 
981
1114
  ---
982
1115
 
@@ -988,33 +1121,109 @@ provideGridNotification(myNotificationStore); // GridNotificationContract 구현
988
1121
 
989
1122
  | compat 컴포넌트 | props 타입 | emits | slots | defineExpose |
990
1123
  |-----------------|-----------|-------|-------|--------------|
991
- | `MozGridVue` (compat) | `MozGridVueProps` (레거시 withDefaults) | 28개 레거시 emit 1:1 포워딩 | `tool-left`, `tool-items`, `notification`, `footer`, `customState`(→`empty-state`), `default` | `{ grid, core, filterModule, sortModule, customCellModule, bulkEditorModule, refresh, loadData, sort, filter }` |
1124
+ | `MozGridVue` (compat) | `MozGridVueProps` (레거시 withDefaults) | 28개 레거시 emit 호환 (`ready`는 grid 한 인자) | `tool-left`, `tool-items`, `notification`, `footer`, `customState`(→`empty-state`), `default` | `{ grid, core, filterModule, sortModule, customCellModule, bulkEditorModule, refresh, loadData, sort, filter }` |
992
1125
  | `MozPivotVue` (compat) | `PivotGridProps` (피벗 withDefaults) | `ready`/`data:loaded`/`filter:changed`/`pivot:configured`/`pivot:toggled`/`update:rowFields`/`update:columnFields`/`update:valueFields` | `tool-left`, `tool-items`, `footer`, `customState`(→`empty-state`), `default` (notification 슬롯 없음) | `{ grid, core, pivotApi, filterModule, sortModule, customCellModule, showChart, refresh, setConfig, getResult, expandAll, collapseAll, expandToLevel, toggleChart }` |
993
1126
 
994
- - `MozPivotVue`는 `<MozGrid>`에 `mode='pivot'`을 강제(coreConfig computed 내부). 직접 forward되는 이벤트는 `['ready','data:loaded','filter:changed','pivot:toggled']`이며(`pivot:toggled`는 forward 목록에 있으나 코어 미발화), `update:rowFields`/`columnFields`/`valueFields`와 `pivot:configured`는 별도 `@update:coreConfig` 핸들러(`onCoreConfigUpdate`)에서 재-emit됩니다. coreConfig computed는 `availableFields`/`useFilter`를 삭제하고 `mode='pivot'` 설정, wrapperConfig는 `sourceFields > availableFields(legacy) > fields` 우선순위로 `sourceFields`를 채웁니다.
1127
+ - 두 compat의 `ready`는 전용 핸들러가 `(grid)` 한 인자만 emit합니다. 신규 `MozGrid`의 `(grid, chrome)`와 다릅니다.
1128
+ - `MozPivotVue`는 `<MozGrid>`에 `mode='pivot'`을 강제(coreConfig computed 내부). 다른 forward 이벤트는 `['data:loaded','filter:changed','pivot:toggled']`이며(`pivot:toggled`는 forward 목록에 있으나 코어 미발화), `update:rowFields`/`columnFields`/`valueFields`와 `pivot:configured`는 별도 `@update:coreConfig` 핸들러(`onCoreConfigUpdate`)에서 재-emit됩니다. coreConfig computed는 `availableFields`/`useFilter`를 삭제하고 `mode='pivot'` 설정, wrapperConfig는 `sourceFields > availableFields(legacy) > fields` 우선순위로 `sourceFields`를 채웁니다.
995
1129
 
996
1130
  ---
997
1131
 
998
1132
  ## 추가 익스포트 (엑셀·locale·타입·Injection 키)
999
1133
 
1000
- 0.2.0 에서 옛 `legacy` 배럴(pre-`MozGrid` Vue 컴포넌트 21종, `useGrid*` composable 17종, 아이콘 20종)을 **제거**했습니다. 같은 기능은 `MozGrid` 가 GridChrome(`@vmscloud/moz-ui-grid-wrapper`)으로 제공합니다. 아래는 패키지 엔트리(`src/index.ts`)가 `MozGrid`·템플릿·compat·계약 외에 추가로 내보내는 표면입니다. 패키지 엔트리는 wrapper 통합 CSS(코어 + 셸)를 side-effect로 import합니다.
1134
+ 0.2.0 에서 옛 `legacy` 배럴(pre-`MozGrid` Vue 컴포넌트 21종, `useGrid*` composable 17종, 아이콘 20종)을 **제거**했습니다. 같은 기능은 `MozGrid` 가 GridChrome(`@vmscloud/moz-ui-grid-wrapper`)으로 제공합니다. 아래는 **주요 export 요약**이며 전체 목록은 [Vue 엔트리](../../moz-ui-grid-vue/src/index.ts)를 참조하세요. 패키지 엔트리는 wrapper 통합 CSS(코어 + 셸)를 side-effect로 import합니다.
1001
1135
 
1002
1136
  | 분류 | 익스포트 |
1003
1137
  |------|----------|
1004
1138
  | 엑셀 (서버사이드 스토어) | `useExcelStore`, `useExcelDownloadStore`, `useExcelUploadStore` (서브패스 `@vmscloud/moz-ui-grid-vue/store` 와 동일), `ExcelDataType`, `FileParameter`, `StreamingParameter`, 타입 `IExcelInfo`, `IExcelAPI`, `AdditionalColumn`, `SystemType` |
1005
1139
  | 엑셀 (클라이언트사이드 grid.io) | `ClientExcelUploadPopup`(Vue, `MozGrid` 가 내부에서 사용), `ClientExcelUploadController`(wrapper Popup 기반 컨트롤러), `useClientExcelStore`, `runClientExport`, `exportCsv`, `generateSessionId`, `pickFile`, `downloadBlob`, 타입 `ClientExcelExportConfig`, `ClientExcelImportMode`, `ClientExcelConflictStrategy`, `ClientExcelImportOptions` |
1006
- | 서브패스 | `./store`, `./utils/excel/ExcelGuard`, `./utils/excel/ExcelQueueManager`, `./utils/excel/ExcelTaskRunner`, `./utils/excel/ExcelTimeoutManager`, `./utils/excel/ExcelImportUtil`, `./style.css` |
1140
+ | 서브패스 | `./store`, `./utils/excel/ExcelGuard`, `./utils/excel/ExcelQueueManager`, `./utils/excel/ExcelTaskRunner`, `./utils/excel/ExcelTimeoutManager`, `./utils/excel/ExcelImportUtil`, `./style.css`, `./package.json` |
1007
1141
  | 토스트 | `showMessage(message, result)`, `openToast(message, type, options)`, 타입 `ToastType`, `ToastOptions` — body 에 `.mzc-toast` 로 표시, 같은 메시지는 300ms debounce |
1008
1142
  | locale | `setGridVueLocale(code)`, `getGridVueLocale()`, `getGridVueLocaleCode()`, `normalizeGridVueLocale()`, 사전 `koKR`/`enUS`/`jaJP`/`zhCN`, 타입 `GridVueLocaleCode`, `GridVueLocaleMessages` — 엑셀 팝업·토스트 문자열. `MozGrid` 의 `locale` prop 이 자동으로 설정하며 기본은 ko |
1009
1143
  | Bridge | `VueRenderer`, `UndoStack`, `getRowHeaderCell`, `getCornerCell`, `ROW_HEADER_COLUMN_ID` |
1010
- | 타입 | `MozGridVueCoreProps`, `MozGridVueProps`, `MozGridVueEmits`, `CellSlotProps`, `HeaderSlotProps`, `EditorSlotProps`, `DetailSlotProps`, `GroupSlotProps`, `TemplateRegistry`, `TemplateEntry`, `ComparisonOperator`, `LogicalOperator`, `IFilterDef`, `ISortDef`, `ICustomCellDef`, `ICustomCellRuleSet`, `ICustomCellConfig`, `IContextMenuItem`, `IContextMenuConfig`, `IContextMenuExcelEventArgs`, `AggregateType`, `IAggregateValues`, `IColumnCellFormat`, `IColumnHeaderFormat`, `IColumnFormatConfig`, `PivotGridProps`, `PivotFieldItem`, `AvailableField`, `PivotFieldPanelConfig`, `PivotGridSettings`, `UseGridFilterReturn`, `UseGridSortReturn`, `UseGridCustomCellReturn`(모듈 계약 — 옛 useGrid* 반환 타입을 독립 인터페이스로 유지), `GridModuleContract` |
1144
+ | 타입 | `MozGridVueCoreProps`, `MozGridVueProps`, `MozGridVueEmits`, `CellSlotProps`, `HeaderSlotProps`, `EditorSlotProps`, `DetailSlotProps`, `GroupSlotProps`, `TemplateRegistry`, `TemplateEntry`, `ComparisonOperator`, `LogicalOperator`, `IFilterDef`, `ISortDef`, `ICustomCellDef`, `ICustomCellRuleSet`, `ICustomCellConfig`, `IContextMenuItem`, `IContextMenuConfig`, `ContextMenuContext`, `IContextMenuExcelEventArgs`, `AggregateType`, `IAggregateValues`, `IColumnCellFormat`, `IColumnHeaderFormat`, `IColumnFormatConfig`, `PivotGridProps`, `PivotFieldItem`, `AvailableField`, `PivotFieldPanelConfig`, `PivotGridSettings`, `UseGridFilterReturn`, `UseGridSortReturn`, `UseGridCustomCellReturn`(모듈 계약 — 옛 useGrid* 반환 타입을 독립 인터페이스로 유지), `GridModuleContract` |
1011
1145
  | Injection 키 (7/8) | `GRID_INSTANCE_KEY`, `GRID_RENDERER_KEY`, `GRID_TEMPLATE_REGISTRY_KEY`, `GRID_FILTER_MODULE_KEY`, `GRID_SORT_MODULE_KEY`, `GRID_CUSTOM_CELL_MODULE_KEY`, `GRID_BULK_EDITOR_MODULE_KEY` (`GRID_CONTAINER_KEY` 제외). 필터·정렬·조건부 스타일 키의 값 타입은 각각 `UseGridFilterReturn`/`UseGridSortReturn`/`UseGridCustomCellReturn`, 일괄 편집 키는 `GridModuleContract`(호스트 임의 모듈) |
1012
- | 코어 타입 재-export | `PureSheetConfig`, `FieldDef`, `SortState`, `FilterState`, `Row`, `ColumnDef`, `RowGroupConfig`, `TreeConfig`, `MasterDetailConfig`, `PivotConfig`, `PivotRowField`, `PivotColumnField`, `PivotValueField`, `PivotResult`, `IPivotAPI`, `IRowsAPI` 외 다수(편의 패스스루), `FormatHeaderRowCallback`/`FormatHeaderRowContext`(구명 alias) → 의미는 [reference.md](reference.md) |
1146
+ | 코어 타입 재-export | `AggregateFn`, `AggregateContext`, `PureSheetConfig`, `FieldDef`, `SortState`, `FilterState`, `Row`, `ColumnDef`, `RowGroupConfig`, `TreeConfig`, `MasterDetailConfig`, `PivotConfig`, `PivotRowField`, `PivotColumnField`, `PivotValueField`, `PivotResult`, `IPivotAPI`, `IRowsAPI` 외 다수(편의 패스스루), `FormatHeaderRowCallback`/`FormatHeaderRowContext`(구명 alias) → 의미는 [reference.md](reference.md) |
1013
1147
 
1014
1148
  > Injection 키 8종은 모두 `Symbol('moz-grid-…')`(번들 로컬, `Symbol.for` 아님)로 생성됩니다. `MozGrid`는 그중 4개(`GRID_INSTANCE_KEY`/`GRID_RENDERER_KEY`/`GRID_TEMPLATE_REGISTRY_KEY`/`GRID_CONTAINER_KEY`)를 provide합니다.
1015
1149
 
1016
1150
  ---
1017
1151
 
1152
+ ## 객체 값 열·점 표기 필드
1153
+
1154
+ `coreConfig.fields` 의 `dataType: 'object'`·`objectKey`·`sortValue`·`equals`·`mask.parser`, 점 표기 `field: 'owner.team'`, 수식 `[owner.name]` 은 코어 기능입니다. 래퍼는 필드 정의를 복사해 코어에 넘기므로 설정 방법·동작은 코어 문서와 같습니다 → [reference.md — 점 표기 필드 경로](reference.md#점-표기-필드-경로-field-ownerteam) · [객체 값 열](reference.md#객체-값-열-datatype-object) · [계산 필드](reference.md#계산-필드) · [SSRM 서버 계약](reference.md#객체-값-열점-표기-필드의-서버-계약), 예제는 [examples.md](examples.md#객체-값-열점-표기-필드-사용-예제).
1155
+
1156
+ 템플릿에서 주의할 점:
1157
+
1158
+ - `CellTemplate` 의 `value` 는 코어 `cellRenderer` 의 `value` 그대로입니다. 객체 값 열이면 **객체**, 점 표기 열이면 경로 값(`row.owner.team`)입니다. `{{ value }}` 로 바로 찍으면 `[object Object]` 가 되니 속성을 꺼내 쓰세요.
1159
+ - `CellEditor` 를 둔 열은 코어에서 `custom` 에디터가 되므로, `mask.parser` 가 없는 객체 값 열도 편집할 수 있습니다. 슬롯의 `value` 는 원본 객체이고, `onCommit` 에 새 객체를 넘깁니다. 변경 여부는 열의 `equals`(없으면 내용 비교)로 판단합니다.
1160
+ - `field` prop 은 데이터 경로가 아니라 `FieldDef.id` 입니다(`field="ownerTeam"`, `field="owner.team"` 아님).
1161
+
1162
+ ```vue
1163
+ <script setup lang="ts">
1164
+ const users = [{ id: 'u1', name: '홍길동' }, { id: 'u2', name: '김철수' }];
1165
+ const pickUser = (e: Event) => users.find(u => u.id === (e.target as HTMLSelectElement).value) ?? null;
1166
+ const coreConfig = ref({
1167
+ fields: [
1168
+ { id: 'owner', header: '담당자', dataType: 'object', objectKey: 'id',
1169
+ mask: { type: 'function', formatter: (v: any) => v?.name ?? '' } },
1170
+ { id: 'ownerTeam', header: '팀', field: 'owner.team' },
1171
+ ],
1172
+ data: rows,
1173
+ });
1174
+ </script>
1175
+
1176
+ <template>
1177
+ <MozGrid v-model:coreConfig="coreConfig">
1178
+ <!-- value = { id, name, … } 또는 null -->
1179
+ <CellTemplate field="owner" #default="{ value }">
1180
+ <span v-if="value">{{ value.name }} ({{ value.id }})</span>
1181
+ </CellTemplate>
1182
+
1183
+ <CellEditor field="owner" #default="{ value, onCommit, onCancel }">
1184
+ <select :value="value?.id" @change="onCommit(pickUser($event))" @keydown.esc="onCancel">
1185
+ <option v-for="u in users" :key="u.id" :value="u.id">{{ u.name }}</option>
1186
+ </select>
1187
+ </CellEditor>
1188
+ </MozGrid>
1189
+ </template>
1190
+ ```
1191
+
1192
+ ### JSON 편집기 (`jsonEditor`)
1193
+
1194
+ 객체 값 열을 JSON 으로 고치는 에디터입니다(CodeMirror 6). `@vmscloud/moz-ui-grid-vue` 에서 가져옵니다.
1195
+
1196
+ **자동 적용**: `MozGrid` 는 `dataType: 'object'` 이고 `editor`·`editorResolver`·`mask.parser`·`displayKey` 가 모두 없으며 편집 가능한(`readonly` 아님, `editable: false` 아님) 열에 `editor: jsonEditor()` 를 붙여 코어에 넘깁니다. 넘긴 `fields` 는 바꾸지 않습니다. 열에 `editor` 를 직접 주면(`CellEditor` 슬롯 포함) 그 에디터가 우선합니다. `grid.updateFields()` 를 직접 부르면 자동 적용되지 않으니 `editor: jsonEditor()` 를 직접 넣으세요.
1197
+
1198
+ ```ts
1199
+ import { jsonEditor } from '@vmscloud/moz-ui-grid-vue';
1200
+
1201
+ { id: 'meta', header: '속성', dataType: 'object', editor: jsonEditor({ indent: 2, width: 480, height: 260 }) }
1202
+ ```
1203
+
1204
+ | 옵션 | 기본값 | 뜻 |
1205
+ |---|---|---|
1206
+ | `indent` | `2` | 들여쓰기 칸 수 |
1207
+ | `width` | `480` | 팝업 너비(px) |
1208
+ | `height` | `260` | 편집 영역 높이(px) |
1209
+
1210
+ - 셀 아래(공간이 없으면 위)에 팝업으로 열립니다. 시작 글자는 줄을 나눈 JSON 이고, 셀에서 바로 글자를 쳐서 열면 그 글자가 원래 JSON 끝에 붙습니다.
1211
+ - 줄 번호·접기·괄호 짝/자동 닫기·자동 들여쓰기·Tab 들여쓰기·JSON 색·Ctrl+F 찾기/바꾸기·문법 오류 표시를 지원합니다. 상태줄에 `✓ 올바른 객체`(바뀌었으면 `· 변경됨`) 또는 `✕ 오류` 가 나옵니다. [정리]는 줄을 나누고, [한 줄로]는 한 줄로 만듭니다.
1212
+ - 확정: [적용] 또는 Ctrl+Enter(유효할 때만). 빈 글자는 `null`, JSON 객체는 그 객체입니다. 배열·숫자·문자열 같은 객체가 아닌 JSON 은 넣을 수 없습니다.
1213
+ - 취소: [취소] 또는 Esc. 찾기 창이 열려 있으면 Esc 는 찾기 창만 닫습니다.
1214
+ - 창 밖을 누르거나 다른 셀로 옮겨 그리드가 편집을 끝내면, 유효한 글자는 기록하고 유효하지 않은 글자는 버립니다(원래 값 유지).
1215
+ - 그리드가 `theme: 'dark'` 면 팝업도 어둡게 그립니다. CodeMirror 는 팝업을 처음 열 때 따로 불러옵니다.
1216
+
1217
+ **래퍼 화면 기능** (헤더·우클릭 메뉴)
1218
+
1219
+ | 기능 | 객체 값 열에서 |
1220
+ |---|---|
1221
+ | 헤더 값 필터 패널 | 항목 라벨은 `mask.formatter`, 항목 키·체크 상태·저장 필터 복원은 `objectKey` — 동명이인은 따로 체크됩니다. 검색으로 좁힌 상태에서 목록 밖이 된 항목도 라벨과 원본 객체를 유지합니다. |
1222
+ | 우클릭 "선택 값으로 필터" | 그 셀 객체로 값 목록 `in` 필터를 겁니다 — 같은 `objectKey` 행이 모두 남고, 헤더 필터 패널을 다시 열면 그 값만 체크돼 있습니다. 같은 열의 기존 값 목록 필터는 대체합니다. |
1223
+ | 일괄 편집 | `select`/`dropdown` 옵션(객체 배열 가능, 라벨 = `mask.formatter`, 현재 값 비교 = `objectKey`)으로 고르거나, 옵션 없이 `mask.parser` 가 있으면 글자로 입력합니다. 둘 다 없으면 우클릭 일괄 변경이 꺼집니다. |
1224
+
1225
+ ---
1226
+
1018
1227
  ## 관련 문서
1019
1228
 
1020
1229
  - **코어 레퍼런스** → [reference.md](reference.md) — `PureSheetConfig`(`coreConfig`) 필드, 코어 타입, 이벤트 페이로드 형태, `PureSheet` API 전체