@vmscloud/moz-ui-grid-vue 0.2.23 → 0.2.24
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.
- package/dist/chunks/{index-DY0dbAHJ.js → index-CZM3pAmI.js} +1 -1
- package/dist/chunks/{index-BLDAzbGv.cjs → index-niC5bYpl.cjs} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +9 -7
- package/dist/store.cjs +1 -1
- package/dist/store.js +1 -1
- package/dist/style.css +1 -1
- package/dist/types/props.d.ts +1 -1
- package/dist/types/slots.d.ts +5 -0
- package/dist/utils/excel/ExcelImportUtil.cjs +1 -1
- package/dist/utils/excel/ExcelImportUtil.js +1 -1
- package/dist/utils/excel/ExcelQueueManager.cjs +1 -1
- package/dist/utils/excel/ExcelQueueManager.js +1 -1
- package/dist/utils/excel/ExcelTaskRunner.cjs +1 -1
- package/dist/utils/excel/ExcelTaskRunner.js +1 -1
- package/dist/utils/excel/ExcelTimeoutManager.cjs +1 -1
- package/dist/utils/excel/ExcelTimeoutManager.js +1 -1
- package/docs/manual/examples.md +9 -6
- package/docs/manual/reference.md +53 -11
- package/docs/manual/reference_vue.md +4 -4
- package/package.json +1 -1
package/dist/types/props.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export interface MozGridVueProps extends MozGridVueCoreProps {
|
|
|
130
130
|
isCustomState?: boolean;
|
|
131
131
|
headerMsg?: string;
|
|
132
132
|
contentMsg?: string;
|
|
133
|
-
/** 안내 그림 (기본 true). 'auto'
|
|
133
|
+
/** 안내 그림 (기본 true). true·'auto' 는 헤더 아래 안내 영역 높이가 모자라면 그림만 숨기고 문구는 남김(리사이즈 시 재판정), false 는 문구만 */
|
|
134
134
|
useImg?: boolean | 'auto';
|
|
135
135
|
/** 빈 상태 이미지 강제 지정 (생략 시 totalRowCount/filteredRowCount로 자동 결정) */
|
|
136
136
|
selectImg?: 'Custom' | 'Result' | 'Data' | 'Search';
|
package/dist/types/slots.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface DetailSlotProps {
|
|
|
27
27
|
/** 그룹 슬롯 props */
|
|
28
28
|
export interface GroupSlotProps {
|
|
29
29
|
groupKey: string;
|
|
30
|
+
/**
|
|
31
|
+
* 그룹 컬럼의 표시 이름(header → label → 컬럼 id, 헤더를 비운 컬럼이면 `''`).
|
|
32
|
+
* 템플릿은 이름을 자동으로 그리지 않는다 — 필요하면 `{{ columnLabel }}: {{ groupValue }}` 처럼 직접 쓴다.
|
|
33
|
+
*/
|
|
34
|
+
columnLabel: string;
|
|
30
35
|
groupValue: unknown;
|
|
31
36
|
level: number;
|
|
32
37
|
count: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("../../chunks/index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("../../chunks/index-niC5bYpl.cjs"),r=require("pinia"),a=a=>{const{tableID:t,flexGrid:o,primaryKeyColumns:s,usePreclear:l=!1,setUploadMessage:n,additionalColumns:i,targetStorage:u,refreshKey:d,propExclusionMessage:p=""}=a,c=e.useExcelStore(),{excelInfo:m}=r.storeToRefs(c),g=new Set(["cfg_demand"]);m.value.targetTable=t,m.value.refreshKey=d??t,m.value.columnMap=c.createColumnMapForImport(o,s),m.value.useDataPreclear=l,m.value.targetStorage=u;const y=i||{};m.value.additionalColumns=y,m.value.additionalClearConditions=y;g.has(t)?(null==n||n(p),m.value.disableMasterProperty=!0):(null==n||n(""),m.value.disableMasterProperty=!1),c.show(l)};exports.createSmartEditorImporter=e=>{const{tableID:r,flexGrid:t,primaryKeyColumns:o,setUploadMessage:s,additionalColumns:l,targetStorage:n,refreshKey:i,propExclusionMessage:u}=e;return e=>{const d=t();if(!d)return;const p=l?l():void 0;a({tableID:r,flexGrid:d,primaryKeyColumns:o(),usePreclear:e,setUploadMessage:s,additionalColumns:p,targetStorage:n,refreshKey:i,propExclusionMessage:u})}},exports.handleSmartEditorImportExcel=a;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}}),require("pinia"),require("vue"),require("./ExcelGuard.cjs");const e=require("./ExcelTimeoutManager.cjs");require("../../chunks/index-
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}}),require("pinia"),require("vue"),require("./ExcelGuard.cjs");const e=require("./ExcelTimeoutManager.cjs");require("../../chunks/index-niC5bYpl.cjs"),exports.default=e.ExcelQueueManager,exports.queue=e.queue,exports.useExcelQueueStore=e.useExcelQueueStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),require("../../chunks/notification.contract-oFkBaE2U.cjs"),require("../../chunks/index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),require("../../chunks/notification.contract-oFkBaE2U.cjs"),require("../../chunks/index-niC5bYpl.cjs"),require("dayjs"),require("./ExcelGuard.cjs");const e=require("./ExcelTimeoutManager.cjs");require("pinia"),require("vue"),exports.cancelExcelTask=e.cancelExcelTask,exports.startDownloadTask=e.startDownloadTask,exports.useExcelTaskStore=e.useExcelTaskStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=Object.defineProperty,t=(t,n,r)=>((t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r)(t,"symbol"!=typeof n?n+"":n,r);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("pinia"),r=require("vue"),o=require("./ExcelGuard.cjs"),i=require("../../chunks/notification.contract-oFkBaE2U.cjs"),a=require("../../chunks/index-
|
|
1
|
+
"use strict";var e=Object.defineProperty,t=(t,n,r)=>((t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r)(t,"symbol"!=typeof n?n+"":n,r);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("pinia"),r=require("vue"),o=require("./ExcelGuard.cjs"),i=require("../../chunks/notification.contract-oFkBaE2U.cjs"),a=require("../../chunks/index-niC5bYpl.cjs"),s=require("dayjs"),l=e=>e?btoa(encodeURIComponent(e)):"",u=Symbol.for("MozProjectInfo");function c(){const e=r.inject(u);if(!e)throw new Error("[ProjectInfo] store not provided. make sure provideProjectInfo() is called in root component (e.g., App.vue).");return e}const d=n.defineStore("excelTimeout",()=>{const e=c();return{tenantID:r.computed(()=>{var t;return(null==(t=e.tenantInfo)?void 0:t.tenantID)||""}),userID:r.computed(()=>{var t;return(null==(t=e.userInfo)?void 0:t.id)||""}),timeoutMessage:r.computed(()=>a.getGridVueLocale().requestTimedOut)}}),p=class e{constructor(){t(this,"timers",new Map),t(this,"timeoutMs",3e5)}static getInstance(){return e.instance||(e.instance=new e),e.instance}clear(e){const t=this.timers.get(e);t&&clearTimeout(t),this.timers.delete(e)}start(e,t=this.timeoutMs){if(!e)return;this.clear(e);const n=i.useGridNotificationStore(),r=a.useExcelStore(),s=d(),l=window.setTimeout(()=>{var t,i;if(!n)return;const a=n.eventMessages.find(t=>{var n;return(null==(n=t.prop_value)?void 0:n.session_id)===e});a&&a.prop_value&&(a.prop_value.status="error",a.prop_value.error_message=a.prop_value.error_message||s.timeoutMessage),r.excelFileMap.delete(e);const l=o.getUserLockKey(s.tenantID,s.userID);o.unlock(l),null==(t=x.removeDownloadByClientId)||t.call(x,e),null==(i=x.tryStartNext)||i.call(x),o.removeSession(s.tenantID,s.userID),this.clear(e)},t);this.timers.set(e,l)}};t(p,"instance");let v=p;const m=v.getInstance(),_=n.defineStore("excelTask",()=>{const e=c(),t=r.computed(()=>e.currentProjectID),n=r.computed(()=>{var t;return(null==(t=e.tenantInfo)?void 0:t.tenantID)||""}),u=r.computed(()=>{var t;return(null==(t=e.tenantInfo)?void 0:t.tenantName)||""}),d=r.computed(()=>{var t;return(null==(t=e.userInfo)?void 0:t.id)||""}),p=r.computed(()=>{var t;return(null==(t=e.userInfo)?void 0:t.name)||""}),v=r.computed(()=>{var t;return(null==(t=e.currentProject)?void 0:t.projectNM)||""});return{startDownloadTask:async e=>{const r=a.useExcelStore(),c=i.useGridNotificationStore();if(!c){const e=o.getUserLockKey(n.value,d.value);return o.unlock(e),null}const _={...e||{}};_.project_id=_.project_id||t.value,_.api_key&&(_.data_path=`/api/aps/backend/${t.value}/${_.api_key}`,delete _.api_key),_.column_source&&(_.column_map=r.createColumnMapForExport(_.column_source),delete _.column_source),_.field_source&&(_.column_map=r.createFieldMapForExport(_.field_source),delete _.field_source),_.client_id||(_.client_id=a.generateGUID());const f=o.getUserLockKey(n.value,d.value),h=localStorage.getItem(f);if(h)try{const e=JSON.parse(h);if((null==e?void 0:e.sessionId)&&e.sessionId!==_.client_id)return null}catch{o.lock(f,_.client_id)}else o.lock(f,_.client_id);o.setSession(n.value,d.value,_.client_id,_.file_name||""),_.file_format||(_.file_format="excel"),r.excelFileMap.set(_.client_id,_.file_name),m.start(_.client_id);const g={noti_type:"progress",header_key:"text-file_download",noti_datetime:s().format("YYYY-MM-DD HH:mm:ss"),noti_sender_id:"",prop_value:{session_id:_.client_id,file_name:`${_.file_name||""}_${s().format("YYYYMMDD")}`,status:"in_progress",progress_per:0,operation_type:"download",remain_time:-1}};c.eventMessages.find(e=>{var t;return(null==(t=e.prop_value)?void 0:t.session_id)===_.client_id})||c.eventMessages.unshift(g);try{await r.requestGenerateExcel(_,{headers:{"tenant-id":l(n.value),"tenant-name":l(u.value),"project-name":l(v.value),"user-name":l(p.value)}}).then(e=>{const t=e.data.data.file_name,i=_.client_id;r.excelFileMap.set(i,t),o.updateSessionFileName(n.value,d.value,i,t);const a=c.eventMessages.find(e=>{var t;return(null==(t=e.prop_value)?void 0:t.session_id)===i});a&&a.prop_value&&(a.prop_value.file_name=t)}).catch(e=>{r.excelFileMap.delete(_.client_id);const t=c.eventMessages.find(e=>{var t;return(null==(t=e.prop_value)?void 0:t.session_id)===_.client_id});t&&t.prop_value&&(t.prop_value.status="error"),m.clear(_.client_id),o.unlock(f)})}catch(y){r.excelFileMap.delete(_.client_id),m.clear(_.client_id),o.unlock(f)}return{sessionId:_.client_id}},cancelExcelTask:async(e,t,r)=>{var a,s;if("upload"===t)return;const l=i.useGridNotificationStore();l&&l.markSessionCanceled(e),r&&await r(e);const u=o.getUserLockKey(n.value,d.value);o.unlock(u),null==(a=x.removeDownloadByClientId)||a.call(x,e),o.removeSession(n.value,d.value),null==(s=x.tryStartNext)||s.call(x)}}}),f=async e=>_().startDownloadTask(e),h=n.defineStore("excelQueue",()=>{const e=c();return{tenantID:r.computed(()=>{var t;return(null==(t=e.tenantInfo)?void 0:t.tenantID)||""}),userID:r.computed(()=>{var t;return(null==(t=e.userInfo)?void 0:t.id)||""})}}),g=class e{constructor(){t(this,"inMemoryUploadQueue",[]),t(this,"inMemoryDownloadQueue",[]),t(this,"initialized",!1)}static getInstance(){return e.instance||(e.instance=new e),e.instance}initSubscriptions(){this.initialized||(this.initialized=!0,o.initExcelGuard())}loadQueue(){return this.inMemoryDownloadQueue.slice()}saveQueue(e){this.inMemoryDownloadQueue=e}enqueueDownload(e){this.initSubscriptions();const t=this.loadQueue(),n={id:a.generateGUID(),type:"download",payload:{...e},meta:{fileName:null==e?void 0:e.file_name,createdAt:Date.now()}};return t.push(n),this.saveQueue(t),this.tryStartNext(),this.getTotalQueueLength()}getQueueLength(){return this.loadQueue().length}getUploadQueueLength(){return this.inMemoryUploadQueue.length}getTotalQueueLength(){return this.getQueueLength()+this.getUploadQueueLength()}enqueueUpload(e,t){this.initSubscriptions();const n={id:a.generateGUID(),type:"upload",start:e,meta:{fileName:t,createdAt:Date.now()}};return this.inMemoryUploadQueue.push(n),this.tryStartNext(),this.getTotalQueueLength()}removeDownloadByClientId(e){if(!e)return;const t=this.loadQueue(),n=t.findIndex(t=>{var n;return"download"===t.type&&(null==(n=t.payload)?void 0:n.client_id)===e});-1!==n&&(t.splice(n,1),this.saveQueue(t))}async tryStartNext(){this.initSubscriptions();const e=h(),t=o.getUserLockKey(e.tenantID,e.userID);if(o.isLocked(t))return;const n=this.loadQueue(),r=n.length>0?n[0]:null,i=this.inMemoryUploadQueue.length>0?this.inMemoryUploadQueue[0]:null;if(!r&&!i)return;if((()=>{var e,t;if(r&&i){return((null==(e=r.meta)?void 0:e.createdAt)??0)<=((null==(t=i.meta)?void 0:t.createdAt)??0)}return!!r})()&&"download"===(null==r?void 0:r.type)){const e={...r.payload};e.client_id||(e.client_id=a.generateGUID()),o.lock(t,e.client_id),n.shift(),this.saveQueue(n);try{await f(e)}catch(s){o.unlock(t)}return}if(i){const e=this.inMemoryUploadQueue.shift();try{await Promise.resolve(e.start())}catch(s){}return}}};t(g,"instance");let y=g;const x=y.getInstance();exports.ExcelQueueManager=y,exports.cancelExcelTask=async(e,t,n)=>_().cancelExcelTask(e,t,n),exports.default=v,exports.excelTimeoutManager=m,exports.queue=x,exports.startDownloadTask=f,exports.useExcelQueueStore=h,exports.useExcelTaskStore=_,exports.useExcelTimeoutStore=d;
|
|
@@ -5,7 +5,7 @@ import { defineStore } from "pinia";
|
|
|
5
5
|
import { inject, computed } from "vue";
|
|
6
6
|
import { getUserLockKey, unlock, removeSession, lock, setSession, updateSessionFileName, isLocked, initExcelGuard } from "./ExcelGuard.js";
|
|
7
7
|
import { u as useGridNotificationStore } from "../../chunks/notification.contract-BTOch6ed.js";
|
|
8
|
-
import { g as getGridVueLocale, u as useExcelStore, f as generateGUID } from "../../chunks/index-
|
|
8
|
+
import { g as getGridVueLocale, u as useExcelStore, f as generateGUID } from "../../chunks/index-CZM3pAmI.js";
|
|
9
9
|
import dayjs from "dayjs";
|
|
10
10
|
const encodeUnescapeBtoa = (text) => {
|
|
11
11
|
if (!text) return "";
|
package/docs/manual/examples.md
CHANGED
|
@@ -243,7 +243,7 @@ await grid.waitForReady();
|
|
|
243
243
|
| 4 | (빈 칸) | (빈 칸) | 님 |
|
|
244
244
|
| 5, 6 | 홍길동 | A | 홍길동님 |
|
|
245
245
|
|
|
246
|
-
- 묶기: 같은 설정에 `rowGroupConfig: { columns: ['owner'] }` 를 더하면 1·2행은 객체 참조가 달라도 id 가 같아 그룹 하나(`owner:u2`), 3행(동명이인)은 따로(`owner:u3`)입니다. 배너 라벨은 둘 다 `김철수`
|
|
246
|
+
- 묶기: 같은 설정에 `rowGroupConfig: { columns: ['owner'] }` 를 더하면 1·2행은 객체 참조가 달라도 id 가 같아 그룹 하나(`owner:u2`), 3행(동명이인)은 따로(`owner:u3`)입니다. 배너 라벨은 둘 다 `담당자: 김철수` 입니다(`rowGroupConfig.showColumnLabel: false` 면 `김철수`).
|
|
247
247
|
|
|
248
248
|
```typescript
|
|
249
249
|
await grid.rowGroup.collapse('owner:u2'); // 1·2행만 접힌다 — 3행은 그대로 보인다
|
|
@@ -636,6 +636,7 @@ await grid.rowGroup.setConfig({
|
|
|
636
636
|
columns: ['region', 'category'],
|
|
637
637
|
aggregates: { amount: 'sum', quantity: 'avg' },
|
|
638
638
|
defaultCollapsed: false,
|
|
639
|
+
// showColumnLabel: false, // 배너를 `서울 (12개 항목)` 처럼 값만 — 기본(true)은 `지역: 서울 (12개 항목)`
|
|
639
640
|
});
|
|
640
641
|
|
|
641
642
|
// ── 간편 그룹화 ──
|
|
@@ -910,14 +911,16 @@ await grid.sort([{ id: 'region', direction: 'asc', sequence: 1 }]);
|
|
|
910
911
|
// 값(집계) 기준 정렬 — by 로 집계 값 기준 정렬
|
|
911
912
|
await grid.sort([{ id: 'region', direction: 'desc', sequence: 1, by: 'sales' }]);
|
|
912
913
|
|
|
913
|
-
//
|
|
914
|
-
await grid.filter([{ id: 'region', operator: 'eq', filterValue: '서울', sequence: 1 }]);
|
|
915
|
-
// 값(measure) 필터 →
|
|
914
|
+
// 피벗 필터 기본 = post(피벗 결과 기준). 행 필드 필터 → 행을 줄인다. excludeUnmatchedColumns 로 빈 컬럼까지 제거
|
|
915
|
+
await grid.filter([{ id: 'region', operator: 'eq', filterValue: '서울', sequence: 1, excludeUnmatchedColumns: true }]);
|
|
916
|
+
// 값(measure) 필터 → 피벗 값 기준 행 필터. excludeUnmatchedColumns 로 매칭 셀 없는 컬럼 제거
|
|
916
917
|
await grid.filter([
|
|
917
918
|
{ id: 'sales', operator: 'gte', filterValue: 1000, sequence: 1, excludeUnmatchedColumns: true },
|
|
918
919
|
]);
|
|
919
|
-
//
|
|
920
|
-
await grid.filter([{ id: '
|
|
920
|
+
// 열 필드 필터 → 열을 줄인다. excludeUnmatchedRows 로 남은 열에 데이터가 없는 행까지 제거
|
|
921
|
+
await grid.filter([{ id: 'quarter', operator: 'eq', filterValue: 'Q1', sequence: 1, excludeUnmatchedRows: true }]);
|
|
922
|
+
// 명시 mode 로 override (예: 원본 행을 거른 뒤 집계)
|
|
923
|
+
await grid.filter([{ id: 'region', operator: 'eq', filterValue: '서울', sequence: 1, mode: 'pre' }]);
|
|
921
924
|
|
|
922
925
|
// ── 열 노드 네비게이션 (Row 메서드와 대칭) ──
|
|
923
926
|
await grid.pivot.toggleColumn('quarter:Q1');
|
package/docs/manual/reference.md
CHANGED
|
@@ -1331,7 +1331,7 @@ rowSelection(선택된 행들)·cellSelection(선택된 셀 range)은 서로 다
|
|
|
1331
1331
|
|
|
1332
1332
|
**Satellite 키 자동 동기화** — `update` 가 내부적으로 동시에 mutate 하는 짝:
|
|
1333
1333
|
- `pinned` ↔ `frozen` (서로 alias)
|
|
1334
|
-
- `header` ↔ `label` (header 입력 시 label 도 함께 세팅)
|
|
1334
|
+
- `header` ↔ `label` (header 입력 시 label 도 함께 세팅). `label`은 deprecated 입니다 — `header`를 쓰세요
|
|
1335
1335
|
- `dataType` → `type`
|
|
1336
1336
|
|
|
1337
1337
|
**`options.updateSnapshot: true`** — 변경분을 초기 스냅샷에도 미러합니다. 이후 `reset(colId)` 호출 시 이번 변경을 **포함한** 새 baseline 으로 복귀. 사용자 UI 의 임시 오버레이가 아닌 "**새 영구 baseline 으로 승격**" 의도일 때 사용합니다.
|
|
@@ -1361,7 +1361,7 @@ grid.columns.update('amount', { header: '판매금액' }, { updateSnapshot: true
|
|
|
1361
1361
|
**충돌 키 정책** — state(런타임)가 승:
|
|
1362
1362
|
- `width` / `pinned` / `visible` / `autoFit` → 현재값 (state)
|
|
1363
1363
|
- `hidden` → `!visible` 로 파생
|
|
1364
|
-
- `header` → `def.header ?? def.label ?? def.id` 로 항상 비어있지
|
|
1364
|
+
- `header` → `def.header ?? def.label ?? def.id` 로 항상 비어있지 않음. 표시 이름은 이 `header`를 읽으세요(`label`은 deprecated)
|
|
1365
1365
|
|
|
1366
1366
|
#### 저장/복원 (직렬화)
|
|
1367
1367
|
|
|
@@ -1629,7 +1629,7 @@ await grid.changes.commit({ allowInvalid: true });
|
|
|
1629
1629
|
- 결과: `grid.selection.getSelectedGroups()` → `[{ groupId, groupKeys, rowCount, rowIds }]`, `selection:changed`의 `selectedGroups`. 서버 그룹(SSRM)에서는 `rowIds` 가 없습니다. `rowIds`는 그룹의 리프 멤버 전체(접혀 있어도, 중첩 그룹이면 하위 리프 전부)이며 표기는 `getGroupRows`와 같습니다(단일 키 원시값).
|
|
1630
1630
|
- 행 선택(`rowSelection`) 결과는 바뀌지 않습니다 — 그룹 멤버를 행 선택에 넣으려면 소비처가 `rowIds`를 사용합니다.
|
|
1631
1631
|
- 배너에서 고른 그룹은 배너와 그룹행 셀을 행 선택 색으로 칠합니다(`.ps-group-banner-selected`, `.ps-group-row-selected`). 범위가 지나간 그룹은 결과에만 기록되고 화면 표시는 없습니다(사각형은 배너에서 분할).
|
|
1632
|
-
- 배너 커서: selectable이면 배너 본문·그룹행은 데이터 셀과 같은 커서이고 ▾ 버튼만 pointer입니다. 옵션(`selectable`/`defaultCollapsed`/`defaultExpandLevel`)은 그룹 컬럼을 모두 뺐다가(`removeColumn`/`groupBy([])`/`setConfig({ columns: [] })`, `getConfig()`는 `null`) 다시 넣어도 유지되며, 생략 시 이어받고 명시값(`false` 포함)이 이깁니다. `clear()`/`setConfig(null)`은 옵션까지 지웁니다.
|
|
1632
|
+
- 배너 커서: selectable이면 배너 본문·그룹행은 데이터 셀과 같은 커서이고 ▾ 버튼만 pointer입니다. 옵션(`selectable`/`showColumnLabel`/`defaultCollapsed`/`defaultExpandLevel`)은 그룹 컬럼을 모두 뺐다가(`removeColumn`/`groupBy([])`/`setConfig({ columns: [] })`, `getConfig()`는 `null`) 다시 넣어도 유지되며, 생략 시 이어받고 명시값(`false` 포함)이 이깁니다. `clear()`/`setConfig(null)`은 옵션까지 지웁니다.
|
|
1633
1633
|
- 다른 셀/행 클릭·새 범위, `clearSelection`/`clearCellSelection`, `rowGroup.setConfig`(재그룹), `loadData` 시 그룹 선택이 비워집니다.
|
|
1634
1634
|
- Ctrl+A·프로그래매틱 범위 API(`selectCellRangeBy*`)는 그룹을 고르지 않습니다.
|
|
1635
1635
|
- 키보드(셀 범위 모드·행 선택 모드 공통, 펼침/접힘·다단 그룹 모두 같음):
|
|
@@ -1655,6 +1655,31 @@ await grid.loadData(rows);
|
|
|
1655
1655
|
grid.selection.selectGroupsByViewIndices([0]); // 또는 selectGroupsByIds(['region:R1'])
|
|
1656
1656
|
```
|
|
1657
1657
|
|
|
1658
|
+
#### 그룹 배너의 컬럼 이름 (`showColumnLabel`)
|
|
1659
|
+
|
|
1660
|
+
`rowGroupConfig.showColumnLabel`(기본 `true`)이면 기본 그룹 배너에서 그룹 값 앞에 **그룹 컬럼의 헤더 이름**을 붙입니다. `false`면 예전처럼 값만 보입니다.
|
|
1661
|
+
|
|
1662
|
+
| 설정 | 배너 글자 (부서로 묶고 `aggregates: [{ column: 'salary', func: 'sum' }]`) |
|
|
1663
|
+
|------|------|
|
|
1664
|
+
| `showColumnLabel` 생략 / `true` | `▾ 부서: 개발팀 (12개 항목) 연봉 (Sum): 67,254` |
|
|
1665
|
+
| `showColumnLabel: false` | `▾ 개발팀 (12개 항목) 연봉 (Sum): 67,254` |
|
|
1666
|
+
|
|
1667
|
+
- 이름은 그 컬럼의 헤더(`fields[].header`)이고, 헤더가 없으면 컬럼 id 입니다. 예: `{ id: 'dept', header: '부서' }` → `부서: `, `{ id: 'dept' }` → `dept: `. 헤더를 일부러 비운 컬럼(`header: ''`)은 이름 없이 값만 보입니다(`: 개발팀`처럼 콜론만 남지 않음).
|
|
1668
|
+
- 다단계 그룹은 레벨마다 자기 그룹 컬럼 이름을 씁니다. 예: `columns: ['dept', 'status']` → 1단 `부서: 개발팀 (12개 항목)`, 2단 `재직 상태: 재직 (9개 항목)`.
|
|
1669
|
+
- DOM: `<span class="ps-group-label"><span class="ps-group-column-label">부서: </span><strong>개발팀</strong> (12개 항목)</span>`. 이름 부분의 모양만 바꾸려면 `.ps-group-column-label`에 스타일을 줍니다(예: `.ps-group-column-label { color: var(--ps-text-secondary); font-weight: normal; }`).
|
|
1670
|
+
- 내보내기(XLSX·CSV)의 그룹행 글자도 화면과 같습니다: `true` → `부서: 개발팀 (12)`, `false` → `개발팀 (12)`.
|
|
1671
|
+
- GroupTemplate(Vue `<GroupTemplate>`, React `groupTemplate`)으로 배너를 바꾼 경우에는 코어가 이름을 그리지 않습니다 — 템플릿이 배너 내용을 대신 그립니다(내보내기는 위 기본 배너 글자 규칙을 따릅니다). 템플릿에서 이름을 보이려면 슬롯 props `columnLabel`을 직접 씁니다: `<GroupTemplate #default="{ columnLabel, groupValue }"><span v-if="columnLabel" class="ps-group-column-label">{{ columnLabel }}: </span>{{ groupValue }}</GroupTemplate>` (`ps-group-column-label` 클래스를 쓰면 기본 배너와 같은 모양).
|
|
1672
|
+
- 보존 규칙은 `selectable`과 같습니다: 그룹 컬럼을 모두 뺐다가 다시 넣어도 유지되고, 생략하면 이어받으며 명시값(`false` 포함)이 이깁니다. `clear()`/`setConfig(null)`은 옵션까지 지웁니다.
|
|
1673
|
+
|
|
1674
|
+
> **동작 변화** — 기본값이 `true`이므로 이 버전으로 올리면 기존 화면의 배너에도 컬럼 이름이 붙습니다(`개발팀 (12개 항목)` → `부서: 개발팀 (12개 항목)`). 예전 모양을 유지하려면 `showColumnLabel: false`를 주세요.
|
|
1675
|
+
|
|
1676
|
+
```ts
|
|
1677
|
+
// 예전처럼 값만 보이기
|
|
1678
|
+
await grid.rowGroup.setConfig({ columns: ['dept'], showColumnLabel: false });
|
|
1679
|
+
// 이후 groupBy/addColumn 은 false 를 이어받습니다
|
|
1680
|
+
await grid.rowGroup.addColumn('status'); // → 1단 `개발팀 (12개 항목)`, 2단 `재직 (9개 항목)`
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1658
1683
|
---
|
|
1659
1684
|
|
|
1660
1685
|
#### 그룹 집계 표시 자리와 함수형 집계
|
|
@@ -2314,7 +2339,7 @@ await grid.io.saveCsvToFile({ includePinnedRows: true });
|
|
|
2314
2339
|
|
|
2315
2340
|
| 필드 | 타입 | 기본값 | 설명 |
|
|
2316
2341
|
|------|------|--------|------|
|
|
2317
|
-
| `groupRowMode` | `'include' \| 'visible' \| 'dataOnly'` | `'include'` | 플랫 행 그룹화(`grid.rowGroup.setConfig`) 활성 시 export 동작. `'include'`: 접힘 상태 무시하고 모든 그룹 행 + data를 펼친 상태로 export. `'visible'`: 현재 접힘 상태 그대로 (collapsed 자식 제외). `'dataOnly'`: 그룹 행 제외하고 data 행만. 그룹 행·순서·개수·집계는 화면과 같다(행 필터·정렬, 그룹 정렬·그룹 결과 필터 반영). 내보내기는 화면 접힘 상태를 바꾸지 않는다. 그룹 행 집계도 화면 자리 그대로 나간다: 그룹 행에서 자기 칸을 갖는 컬럼(고정 컬럼, center 양 끝의 연속 `unmergeOnGroupRow`)의 컬럼 `aggregate` 값은 그 컬럼 칸에(숫자 셀, 서식·mask 는 본문 칸과 같다), 배너 글자(`rowGroupConfig.aggregates` — 자기 칸 컬럼 항목 포함)는 그룹 라벨 칸에 화면 배너와 같은 글자로 ` · ` 를 붙여 나간다(예: `A (3) · Qty (Sum): 6,000 · Amt (Max): 7
|
|
2342
|
+
| `groupRowMode` | `'include' \| 'visible' \| 'dataOnly'` | `'include'` | 플랫 행 그룹화(`grid.rowGroup.setConfig`) 활성 시 export 동작. `'include'`: 접힘 상태 무시하고 모든 그룹 행 + data를 펼친 상태로 export. `'visible'`: 현재 접힘 상태 그대로 (collapsed 자식 제외). `'dataOnly'`: 그룹 행 제외하고 data 행만. 그룹 행·순서·개수·집계는 화면과 같다(행 필터·정렬, 그룹 정렬·그룹 결과 필터 반영). 내보내기는 화면 접힘 상태를 바꾸지 않는다. 그룹 행 집계도 화면 자리 그대로 나간다: 그룹 행에서 자기 칸을 갖는 컬럼(고정 컬럼, center 양 끝의 연속 `unmergeOnGroupRow`)의 컬럼 `aggregate` 값은 그 컬럼 칸에(숫자 셀, 서식·mask 는 본문 칸과 같다), 배너 글자(`rowGroupConfig.aggregates` — 자기 칸 컬럼 항목 포함)는 그룹 라벨 칸에 화면 배너와 같은 글자로 ` · ` 를 붙여 나간다(예: `Cat: A (3) · Qty (Sum): 6,000 · Amt (Max): 7` — `Cat:`은 그룹 컬럼 헤더 이름이며 `rowGroupConfig.showColumnLabel: false`면 `A (3) · …`, `scope`·숨김 컬럼 규칙도 화면과 같다). 자기 칸이 없는 컬럼의 컬럼 `aggregate`(함수형 포함)는 화면처럼 나가지 않는다. GroupTemplate 으로 배너를 바꾼 화면도 파일은 기본 배너 글자를 쓴다 |
|
|
2318
2343
|
| `groupOutline` | `boolean` | `true` | Excel 네이티브 outline (+/- 버튼) 생성. 그룹 행을 summary row로, 데이터 행에 `outlineLevel`을 부여. `groupRowMode: 'dataOnly'`이면 무시. 중첩 그룹(`columns: ['dept','role']`)은 depth별 outlineLevel 자동 계산 |
|
|
2319
2344
|
|
|
2320
2345
|
##### 트리 그리드 export 옵션
|
|
@@ -2707,6 +2732,7 @@ const b = store.select(s => s.canRedo);
|
|
|
2707
2732
|
| 필드 | 타입 | 설명 |
|
|
2708
2733
|
|------|------|------|
|
|
2709
2734
|
| `activity` | `GridActivity` | 현재 실행 중인 비동기 작업 (중첩 시 가장 바깥쪽만 노출) |
|
|
2735
|
+
| `activities` | `readonly GridActivity[]` | 동시에 진행 중인 비동기 작업 전부(시작 순, 같은 작업은 한 번, 없으면 `[]`). 겹쳐 시작한 작업(예: `refresh` 도중의 `setPivotConfig`)도 보인다 — "이 작업이 하나라도 도는가"는 `s.activities.includes('setPivotConfig')` |
|
|
2710
2736
|
| `isSearching` | `boolean` | 검색 진행 중 여부 (background 작업, `activity`와 독립) |
|
|
2711
2737
|
|
|
2712
2738
|
`GridActivity` 전체 값:
|
|
@@ -3268,10 +3294,11 @@ interface RowGroupConfig {
|
|
|
3268
3294
|
defaultCollapsed?: boolean;
|
|
3269
3295
|
defaultExpandLevel?: number;
|
|
3270
3296
|
selectable?: boolean;
|
|
3297
|
+
showColumnLabel?: boolean; // 기본 true — 배너 값 앞에 그룹 컬럼 헤더 이름(`부서: 개발팀`)
|
|
3271
3298
|
}
|
|
3272
3299
|
```
|
|
3273
3300
|
|
|
3274
|
-
`defaultCollapsed: true`는 `defaultExpandLevel`보다 우선합니다. `defaultExpandLevel`은 0이면 최상위 그룹행만, 1이면 그 아래 한 단계까지 펼치며 생략하면 모두 펼칩니다. `selectable`은 기본 `false`입니다.
|
|
3301
|
+
`defaultCollapsed: true`는 `defaultExpandLevel`보다 우선합니다. `defaultExpandLevel`은 0이면 최상위 그룹행만, 1이면 그 아래 한 단계까지 펼치며 생략하면 모두 펼칩니다. `selectable`은 기본 `false`입니다. `showColumnLabel`은 기본 `true`이며 기본 배너가 `부서: 개발팀 (12개 항목)`처럼 그룹 컬럼 이름을 붙입니다. `false`면 `개발팀 (12개 항목)`처럼 값만 보입니다(→ [그룹 배너의 컬럼 이름](#그룹-배너의-컬럼-이름-showcolumnlabel)).
|
|
3275
3302
|
|
|
3276
3303
|
재로드(`loadData`) 때 기본 접힘 규칙은 **처음 보는 그룹**에 적용하고, 이미 본 그룹은 지금 상태(사용자가 펴거나 접은 것)를 유지합니다(서버 그룹과 같은 규칙). 예: `defaultCollapsed: true`로 빈 데이터에서 시작해 조회하면 도착한 그룹이 모두 접힌 채 나오고, `HR`을 편 뒤 재조회로 `Ops`가 새로 생기면 `HR`은 펼친 채, `Ops`는 접힌 채입니다. 그룹 열이 바뀌거나 `defaultCollapsed`/`defaultExpandLevel`을 바꾸면 모든 그룹에 다시 적용합니다. Vue/React 래퍼의 `data`는 참조로 비교하므로 같은 배열을 다시 넣으면 재로드(`data:loaded`)가 일어나지 않습니다 — 조회마다 새 배열을 넘기세요.
|
|
3277
3304
|
|
|
@@ -3751,7 +3778,9 @@ type RowData = Row;
|
|
|
3751
3778
|
```typescript
|
|
3752
3779
|
interface ColumnDef {
|
|
3753
3780
|
id: string;
|
|
3754
|
-
|
|
3781
|
+
header?: string; // 헤더 표시명 (FieldDef.header가 그대로 들어감)
|
|
3782
|
+
/** @deprecated header 를 쓰세요 — 아직 삭제되지 않음 */
|
|
3783
|
+
label?: string; // FieldDef.header 값이 header 와 같이 들어감
|
|
3755
3784
|
type?: 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object';
|
|
3756
3785
|
width?: number;
|
|
3757
3786
|
minWidth?: number;
|
|
@@ -3772,6 +3801,10 @@ interface ColumnDef {
|
|
|
3772
3801
|
|
|
3773
3802
|
> 사용자 코드에선 보통 `FieldDef`만 작성합니다. `ColumnDef`는 내부 정규화 타입이며, **`grid.columns.*` get류 API 의 반환은 `ColumnInfo`** (ColumnDef + ColumnState 합본) 입니다.
|
|
3774
3803
|
|
|
3804
|
+
> **표시 이름 `header` / `label`** — 플랫 `fields[].header: '부서'`는 `ColumnDef`의 `header`와 `label` 둘 다에 `'부서'`로 들어갑니다. 그리드는 헤더·그룹 배너(`부서: 개발팀`)·내보내기 등에서 **`header` → `label` → `id`** 순으로 표시 이름을 고릅니다. `ColumnDef.label`과 `ColumnInfo.label`(`grid.columns.getById()` 등의 반환)은 deprecated 입니다(아직 삭제되지 않음) — 이름을 읽을 때는 `header`를 쓰세요.
|
|
3805
|
+
> - `FieldDef`(`fields[]`)는 원래 `header`라 바꿀 것이 없습니다.
|
|
3806
|
+
> - `ColumnDef`를 직접 만들면서 `label`과 `header`를 다르게 준 경우만 화면 이름이 `header` 쪽으로 바뀝니다. 예: `{ id: 'dept', label: '부서', header: 'Dept' }` → 헤더·배너 모두 `Dept`. `label`만 준 경우(`{ id: 'dept', label: '부서' }`)는 그대로 `부서`입니다.
|
|
3807
|
+
|
|
3775
3808
|
#### ColumnState
|
|
3776
3809
|
|
|
3777
3810
|
```typescript
|
|
@@ -3801,6 +3834,7 @@ interface ColumnInfo {
|
|
|
3801
3834
|
|
|
3802
3835
|
// ── 데이터 타입 / 라벨 / 헤더 ──
|
|
3803
3836
|
type?: ColumnType;
|
|
3837
|
+
/** @deprecated header 를 쓰세요 (아직 삭제되지 않음) */
|
|
3804
3838
|
label?: string;
|
|
3805
3839
|
/** def.header ?? def.label ?? def.id 로 합성 — 항상 비어있지 않음 */
|
|
3806
3840
|
header?: string;
|
|
@@ -3873,7 +3907,8 @@ interface ColumnInfo {
|
|
|
3873
3907
|
**충돌 키 정책**: state(런타임)가 승.
|
|
3874
3908
|
- `width` / `pinned` / `visible` / `autoFit` → 현재값 (state)
|
|
3875
3909
|
- `hidden` → `!visible` 로 파생
|
|
3876
|
-
- `header` → `def.header ?? def.label ?? def.id` 로 fallback 합성 (항상 truthy)
|
|
3910
|
+
- `header` → `def.header ?? def.label ?? def.id` 로 fallback 합성 (항상 truthy). 그리드가 헤더·그룹 배너·내보내기 등에 쓰는 표시 이름도 같은 순서(header → label → id)입니다
|
|
3911
|
+
- `label` → deprecated. 같은 값이 `header`에도 있으니 `header`를 읽으세요(아직 삭제되지 않음)
|
|
3877
3912
|
- `frozen` → ColumnDef 의 alias 그대로 노출 (정식 사용은 `pinned`)
|
|
3878
3913
|
|
|
3879
3914
|
#### CellPosition
|
|
@@ -3939,14 +3974,20 @@ interface FilterStateBase {
|
|
|
3939
3974
|
mask?: MaskConfig;
|
|
3940
3975
|
/**
|
|
3941
3976
|
* (피벗 전용) 필터 적용 단계 override. 플랫 그리드에서는 무시.
|
|
3942
|
-
* 생략(기본) =
|
|
3943
|
-
*
|
|
3977
|
+
* 생략(기본) = `'post'`: 피벗 결과 기준으로 거른다 — 행 축(행 필드·값) 필터는 행을, 열 필드 필터는 열을
|
|
3978
|
+
* 줄이고 살아남은 행·열로 소계/총합을 재집계한다. `'pre'` = 원본 행을 거른 뒤 집계(소스 축소 강제).
|
|
3944
3979
|
*/
|
|
3945
3980
|
mode?: 'pre' | 'post';
|
|
3946
3981
|
/**
|
|
3947
|
-
* (
|
|
3982
|
+
* (피벗, 행을 줄이는 필터) 남은 행에 셀이 없는 리프 컬럼을 결과에서 제외 (기본 `false`). 짝: `excludeUnmatchedRows`.
|
|
3983
|
+
* 값 필터는 조건을 만족하는 셀, 행 필드 필터는 원본 데이터가 있는 셀이 기준이다.
|
|
3948
3984
|
*/
|
|
3949
3985
|
excludeUnmatchedColumns?: boolean;
|
|
3986
|
+
/**
|
|
3987
|
+
* (피벗, 열 필드 필터) 남은 열 어디에도 원본 데이터가 없는 행(과 아래가 모두 빠진 소계 행)을 결과에서 제외
|
|
3988
|
+
* (기본 `false`). 원본 값 0 은 데이터로 본다. 짝: `excludeUnmatchedColumns`.
|
|
3989
|
+
*/
|
|
3990
|
+
excludeUnmatchedRows?: boolean;
|
|
3950
3991
|
}
|
|
3951
3992
|
|
|
3952
3993
|
/** 값 하나를 비교 — 스칼라 filterValue */
|
|
@@ -5236,6 +5277,7 @@ interface GroupRowContext {
|
|
|
5236
5277
|
viewIndex: number;
|
|
5237
5278
|
groupId: string; // 토글용 ID (= grid.rowGroup.getGroupRows 인자)
|
|
5238
5279
|
column: string;
|
|
5280
|
+
columnLabel: string; // 그룹 컬럼 표시 이름(header → label → id, 헤더를 비우면 ''). showColumnLabel 과 무관하게 항상 들어 있다
|
|
5239
5281
|
value: CellValue;
|
|
5240
5282
|
level: number;
|
|
5241
5283
|
itemCount: number;
|
|
@@ -5316,7 +5358,7 @@ interface FormatHeaderContext {
|
|
|
5316
5358
|
interface HeaderCellInfo {
|
|
5317
5359
|
/** 컬럼 ID — leaf 셀(Flat/rowHeader/center-leaf)에서만 의미. Pivot 그룹 셀은 빈 문자열 */
|
|
5318
5360
|
columnId: string;
|
|
5319
|
-
/** 표시되는 헤더 텍스트 (
|
|
5361
|
+
/** 표시되는 헤더 텍스트 (header ?? label ?? id 합성 — 항상 비어있지 않음) */
|
|
5320
5362
|
header: string;
|
|
5321
5363
|
/** 헤더 셀 DOM (인터랙티브 요소 포함, 정렬 아이콘 등 자식 노드 있음) */
|
|
5322
5364
|
element: HTMLElement;
|
|
@@ -436,14 +436,14 @@ customMenu: [{ id: 'wip-detail', label: '상세 보기', hidden: ({ area, cellVa
|
|
|
436
436
|
| `isCustomState` | `boolean?` | 커스텀 빈 뷰 강제(`custom_empty` 이미지) |
|
|
437
437
|
| `headerMsg` | `string?` | 헤더 텍스트 오버라이드(미지정 시 뷰 타입별 i18n 기본) |
|
|
438
438
|
| `contentMsg` | `string?` | 콘텐츠 텍스트 오버라이드(`unsafeHTML`로 렌더, 미지정 시 i18n 기본) |
|
|
439
|
-
| `useImg` | `boolean \| 'auto'` | 빈 상태 이미지 표시(기본 `true`). `
|
|
439
|
+
| `useImg` | `boolean \| 'auto'` | 빈 상태 이미지 표시(기본 `true`). `true`·`'auto'`는 안내 영역(컬럼 헤더 아래 빈 영역) 높이가 그림(최소 110px)+문구(약 60px)에 모자라면 그림만 숨기고 문구는 남김. 그리드·헤더 높이가 바뀌면 다시 판정. `false`는 문구만. (`'auto'`는 `true`와 같으며 호환용으로 유지) |
|
|
440
440
|
| `selectImg` | `EmptyStateImg?` | 명시적 이미지 선택. 행 수가 0일 때 자동 뷰 타입을 오버라이드 |
|
|
441
441
|
| `noDataHeaderMsg` | `string?` | 전체 소스 0행 전용 헤더. 필터 결과만 0행인 경우에는 미적용 |
|
|
442
442
|
| `noDataContentMsg` | `string?` | 전체 소스 0행 전용 콘텐츠(HTML). 필터 결과만 0행인 경우에는 미적용 |
|
|
443
443
|
|
|
444
444
|
둘 중 하나라도 지정하면 전체 0행은 읽기 전용 여부와 무관하게 `data_empty` 뷰를 사용합니다. `selectImg`/`isCustomState`가 이 자동 선택보다 우선하며, 미지정 시 기존 동작을 유지합니다. `contentMsg`/`noDataContentMsg`는 `unsafeHTML` 렌더 경로이므로 **신뢰할 수 있는 HTML만** 전달하세요. 외부 입력을 그대로 전달하지 마세요.
|
|
445
445
|
|
|
446
|
-
예) 높이 150px 작은 그리드: `emptyState: { enabled: false }` → 0건이면 헤더만 있는 빈 표 / `
|
|
446
|
+
예) 높이 150px 작은 그리드: `emptyState: { enabled: false }` → 0건이면 헤더만 있는 빈 표 / 기본값(`useImg` 미지정) → 문구만 보이고 그림은 숨김(600px로 커지면 그림 다시 표시). 안내는 컬럼 헤더 아래 영역에만 그려져 헤더를 덮지 않음.
|
|
447
447
|
|
|
448
448
|
`EmptyStateImg = 'Custom' | 'Result' | 'Data' | 'Search'`
|
|
449
449
|
|
|
@@ -963,7 +963,7 @@ watch(coreConfig, (cfg) => {
|
|
|
963
963
|
| `HeaderTemplate` | `{ field: string (필수) }` | `HeaderSlotProps = { columnId: string; header: string }` |
|
|
964
964
|
| `CellEditor` | `{ field: string (필수) }` | `EditorSlotProps = { value: unknown; rowData: Record<string,unknown>; columnId: string; onCommit: (newValue: unknown) => void; onCancel: () => void }` |
|
|
965
965
|
| `DetailTemplate` | `{ minHeight?: number=100; maxHeight?: number; singleExpand?: boolean=false; keepDetailRows?: boolean=true }` | `DetailSlotProps = { parentData: Record<string,unknown>; dataIndex: number }` |
|
|
966
|
-
| `GroupTemplate` | (없음) | `GroupSlotProps = { groupKey: string; groupValue: unknown; level: number; count: number; isExpanded: boolean }` |
|
|
966
|
+
| `GroupTemplate` | (없음) | `GroupSlotProps = { groupKey: string; columnLabel: string; groupValue: unknown; level: number; count: number; isExpanded: boolean }` — `columnLabel` = 그룹 컬럼 표시 이름(header → label → id, 헤더를 비우면 `''`) |
|
|
967
967
|
|
|
968
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
969
|
|
|
@@ -980,7 +980,7 @@ const cleanupPopup = () => {
|
|
|
980
980
|
새 draft validation 이벤트/구독은 이 API에 포함되지 않습니다.
|
|
981
981
|
|
|
982
982
|
- `DetailTemplate`: 마운트 시 grid와 default 슬롯이 존재하면 `grid.masterDetail.setConfig`로 props 매핑(`keepDetailRows` → 코어 `keepMasterDetailRows`), 디테일 행마다 `detail:${dataIndex}` 키로 슬롯 렌더. 언마운트 시 `renderer.unmountByPrefix('detail:')`. 아래 예제는 공개 구성 형태이며 최초 mount 순서·grid 교체 시 재등록은 실행 검증 대상입니다. GroupTemplate의 준비 대기/재등록 계약을 DetailTemplate에도 동일하게 보장하지 않습니다.
|
|
983
|
-
- `GroupTemplate`: `_groupTemplate` 이름(래퍼 예약 키)의 `formatRow` 핸들러를 등록한다. 코어는 이 핸들러에만 그룹행 대신 **보이는 그룹 배너 안의 호스트**(`span.ps-group-label.ps-group-template-host`)를 `ctx.element`로 넘기므로, 슬롯이 배너의 라벨·건수·집계 자리를 대체한다(펼침/접힘 토글 버튼은 코어가 유지, 배너 본문 클릭은 그룹 토글 — `rowGroupConfig.selectable: true`면 본문 클릭은 그룹 선택이고 토글은 ▾ 버튼만). 슬롯 요소의 이벤트(툴팁 등)는 슬롯이 그대로 받는다. 배너가 생기지 않는 컬럼 구성(가운데 컬럼이 모두 `unmergeOnGroupRow`)에서는 예전처럼 그룹행 요소에 마운트된다.
|
|
983
|
+
- `GroupTemplate`: `_groupTemplate` 이름(래퍼 예약 키)의 `formatRow` 핸들러를 등록한다. 코어는 이 핸들러에만 그룹행 대신 **보이는 그룹 배너 안의 호스트**(`span.ps-group-label.ps-group-template-host`)를 `ctx.element`로 넘기므로, 슬롯이 배너의 라벨·건수·집계 자리를 대체한다(펼침/접힘 토글 버튼은 코어가 유지, 배너 본문 클릭은 그룹 토글 — `rowGroupConfig.selectable: true`면 본문 클릭은 그룹 선택이고 토글은 ▾ 버튼만). 슬롯 요소의 이벤트(툴팁 등)는 슬롯이 그대로 받는다. 슬롯이 배너 내용을 대신 그리므로 `rowGroupConfig.showColumnLabel`(기본 배너의 `부서: ` 컬럼 이름)은 화면에 영향이 없다 — 이름이 필요하면 슬롯 props `columnLabel`로 직접 그린다(`<span v-if="columnLabel" class="ps-group-column-label">{{ columnLabel }}: </span>`, 클래스는 기본 배너와 같은 모양). 배너가 생기지 않는 컬럼 구성(가운데 컬럼이 모두 `unmergeOnGroupRow`)에서는 예전처럼 그룹행 요소에 마운트된다.
|
|
984
984
|
- 호스트별로 인스턴스 1개(`renderer.mountInstance`)를 유지하고, 접기/펼치기·건수 변화는 props 갱신으로 반영한다. 배너가 스크롤/그룹 해제/데이터 갱신으로 사라지면 코어가 호스트에 보내는 내부 이벤트로 인스턴스를 정리한다. 그리드 준비 전에 마운트돼도 그리드 인스턴스가 준비되면 등록되고, 언마운트 시 `formatRow.removeHandler('_groupTemplate')` 후 남은 인스턴스를 정리한다.
|
|
985
985
|
|
|
986
986
|
```vue
|