@topgrid/grid-export 0.2.0 → 0.4.0

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 +1 @@
1
- {"version":3,"sources":["../../src/internal/getRowsByScope.ts","../../src/exportToExcel.ts","../../src/legacy/downloadExcel.ts"],"names":["XLSX"],"mappings":"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","file":"downloadExcel.cjs","sourcesContent":["import type { Row, Table } from '@tanstack/react-table';\r\nimport type { ExportScope } from '../types';\r\n\r\n/**\r\n * TanStack scope 에 따라 Row 배열 반환 (C-2: 표준 API만 사용)\r\n * G-001 exportToExcel.ts 에서 추출 — Excel + CSV 공유 헬퍼 (D1)\r\n */\r\nexport function getRowsByScope<TData>(\r\n table: Table<TData>,\r\n scope: ExportScope,\r\n): Row<TData>[] {\r\n if (scope === 'all') {\r\n return table.getCoreRowModel().rows;\r\n }\r\n if (scope === 'selected') {\r\n return table.getSelectedRowModel().rows;\r\n }\r\n // 'filtered' (default)\r\n return table.getFilteredRowModel().rows;\r\n}\r\n","import * as XLSX from 'xlsx';\r\nimport type { Table } from '@tanstack/react-table';\r\nimport type { ExcelExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * 헤더 텍스트 추출 헬퍼 — 헤더 값이 string 이면 그대로, 아니면 빈 문자열\r\n */\r\nfunction resolveHeaderText(headerValue: unknown): string {\r\n if (typeof headerValue === 'string') {\r\n return headerValue;\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * TanStack Table 의 헤더 그룹을 순회하여\r\n * - AOA(Array of Arrays) 형태의 헤더 행 배열\r\n * - xlsx merge cells 배열 (다중행 헤더 GroupColumnDef 용)\r\n * 을 반환한다.\r\n *\r\n * AC-003: header.isPlaceholder + header.colSpan 이용 → ws['!merges'] 계산\r\n */\r\nfunction buildHeaderRows<TData>(table: Table<TData>): {\r\n headerRows: unknown[][];\r\n merges: XLSX.Range[];\r\n} {\r\n const headerGroups = table.getHeaderGroups();\r\n const merges: XLSX.Range[] = [];\r\n const headerRows: unknown[][] = [];\r\n\r\n for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {\r\n const group = headerGroups[rowIdx];\r\n const row: unknown[] = [];\r\n\r\n let colIdx = 0;\r\n for (const header of group.headers) {\r\n if (header.isPlaceholder) {\r\n // 상위 그룹 헤더의 placeholder 자식 — 빈 셀\r\n row.push('');\r\n } else {\r\n const text = resolveHeaderText(\r\n typeof header.column.columnDef.header === 'string'\r\n ? header.column.columnDef.header\r\n : header.column.id,\r\n );\r\n row.push(text);\r\n\r\n // colSpan > 1 이면 수평 merge 범위 추가 (GroupColumnDef)\r\n if (header.colSpan > 1) {\r\n merges.push({\r\n s: { r: rowIdx, c: colIdx },\r\n e: { r: rowIdx, c: colIdx + header.colSpan - 1 },\r\n });\r\n }\r\n }\r\n colIdx++;\r\n }\r\n\r\n headerRows.push(row);\r\n }\r\n\r\n // 헤더 그룹이 2행 이상이면 하위 행 리프 헤더를 상위 행에서 수직 merge\r\n // (상위 행의 비-placeholder 단일 리프 헤더는 rowspan으로 처리)\r\n if (headerGroups.length > 1) {\r\n for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {\r\n const group = headerGroups[rowIdx];\r\n let colIdx = 0;\r\n for (const header of group.headers) {\r\n // isPlaceholder false 이고 colSpan == 1 이면 이 컬럼은 리프 → 수직 merge\r\n if (!header.isPlaceholder && header.colSpan === 1) {\r\n if (rowIdx + 1 <= headerGroups.length - 1) {\r\n merges.push({\r\n s: { r: rowIdx, c: colIdx },\r\n e: { r: headerGroups.length - 1, c: colIdx },\r\n });\r\n }\r\n }\r\n colIdx++;\r\n }\r\n }\r\n }\r\n\r\n return { headerRows, merges };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior)\r\n * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)\r\n *\r\n * @remarks\r\n * **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시\r\n * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.\r\n * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).\r\n *\r\n * @example\r\n * // 기본 사용 (filtered 행)\r\n * exportToExcel(table, { fileName: '데이터.xlsx' });\r\n *\r\n * @example\r\n * // 선택 행 + 다중행 헤더\r\n * exportToExcel(table, {\r\n * fileName: '선택데이터.xlsx',\r\n * sheetName: '선택목록',\r\n * scope: 'selected',\r\n * emptyBehavior: 'empty',\r\n * });\r\n */\r\nexport function exportToExcel<TData>(\r\n table: Table<TData>,\r\n options?: ExcelExportOptions,\r\n): void {\r\n const {\r\n fileName = 'export.xlsx',\r\n sheetName = 'Sheet1',\r\n scope = 'filtered',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API만)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-01, EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToExcel: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 추출 (GroupColumnDef 감지 → 다중행 + merge cells, AC-003)\r\n const { headerRows, merges } = buildHeaderRows(table);\r\n\r\n // 4) 데이터 행 추출 (AC-004: 한국어 UTF-8 — xlsx aoa_to_sheet 기본 UTF-8)\r\n const dataRows: unknown[][] = rows.map((row) =>\r\n row.getVisibleCells().map((cell) => {\r\n const value = cell.getValue();\r\n return value !== undefined && value !== null ? value : '';\r\n }),\r\n );\r\n\r\n // 5) AOA sheet 생성 (헤더 + 데이터)\r\n const aoa: unknown[][] = [...headerRows, ...dataRows];\r\n const ws = XLSX.utils.aoa_to_sheet(aoa);\r\n\r\n // 6) merge cells 적용 (다중행 헤더 있을 때, AC-003)\r\n if (merges.length > 0) {\r\n ws['!merges'] = merges;\r\n }\r\n\r\n // 7) workbook + 파일 다운로드 (EC-04: fileName 확장자 자동 추가)\r\n const wb = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\r\n const finalFileName = fileName.endsWith('.xlsx')\r\n ? fileName\r\n : `${fileName}.xlsx`;\r\n XLSX.writeFile(wb, finalFileName);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { DownloadExcelOptions } from '../types';\r\nimport { exportToExcel } from '../exportToExcel';\r\n\r\n/**\r\n * DataTable `buttonInfo.downloadAction` 마이그레이션 호환 alias.\r\n *\r\n * 내부적으로 `exportToExcel(table, options)` 에 위임한다.\r\n * scope 기본값은 'filtered' (현재 필터/정렬 반영 행).\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스\r\n * @param options - export 옵션 (scope 기본값: 'filtered')\r\n *\r\n * @deprecated DataTable buttonInfo.downloadAction 마이그레이션 alias.\r\n * 1 minor 버전 이상 유지 (C-6, C-23).\r\n * 신규 코드는 `exportToExcel()` 직접 사용 권장.\r\n *\r\n * @example\r\n * import { downloadExcel } from '@topgrid/grid-export/legacy';\r\n * // DataTable 기존 패턴 교체\r\n * downloadExcel(table);\r\n */\r\nexport function downloadExcel<TData>(\r\n table: Table<TData>,\r\n options?: DownloadExcelOptions,\r\n): void {\r\n // C-29 exactOptionalPropertyTypes 대응:\r\n // DownloadExcelOptions 의 optional props 를 직접 forwarding 하지 않고\r\n // options 전체를 spread 하여 exportToExcel 에 위임.\r\n // scope 기본값 'filtered' 는 exportToExcel 내부에서 처리됨.\r\n exportToExcel(table, options);\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/internal/getRowsByScope.ts","../../src/internal/buildHeaderRows.ts","../../src/internal/buildGridWorksheet.ts","../../src/exportToExcel.ts","../../src/legacy/downloadExcel.ts"],"names":["XLSX2","XLSX3"],"mappings":"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","file":"downloadExcel.cjs","sourcesContent":["import type { Row, Table } from '@tanstack/react-table';\r\nimport type { ExportScope } from '../types';\r\n\r\n/**\r\n * TanStack scope 에 따라 Row 배열 반환 (C-2: 표준 API만 사용)\r\n * G-001 exportToExcel.ts 에서 추출 — Excel + CSV 공유 헬퍼 (D1)\r\n */\r\nexport function getRowsByScope<TData>(\r\n table: Table<TData>,\r\n scope: ExportScope,\r\n): Row<TData>[] {\r\n if (scope === 'all') {\r\n return table.getCoreRowModel().rows;\r\n }\r\n if (scope === 'selected') {\r\n return table.getSelectedRowModel().rows;\r\n }\r\n // 'filtered' (default)\r\n return table.getFilteredRowModel().rows;\r\n}\r\n","import type { Table } from '@tanstack/react-table';\nimport * as XLSX from 'xlsx';\n\n/**\n * 헤더 텍스트 추출 헬퍼 — 헤더 값이 string 이면 그대로, 아니면 빈 문자열\n */\nfunction resolveHeaderText(headerValue: unknown): string {\n if (typeof headerValue === 'string') {\n return headerValue;\n }\n return '';\n}\n\n/**\n * TanStack Table 의 헤더 그룹을 순회하여\n * - AOA(Array of Arrays) 형태의 헤더 행 배열\n * - xlsx merge cells 배열 (다중행 헤더 GroupColumnDef 용)\n * 을 반환한다.\n *\n * AC-003: header.isPlaceholder + header.colSpan 이용 → ws['!merges'] 계산\n *\n * MOD-GRID-25 G-1: `exportToExcel` 의 private helper 에서 `internal/` 로 이동(동작 동일) —\n * `exportSheetsToExcel`(다중 시트) 와 공유하기 위함. 로직 변경 없음.\n */\nexport function buildHeaderRows<TData>(table: Table<TData>): {\n headerRows: unknown[][];\n merges: XLSX.Range[];\n} {\n const headerGroups = table.getHeaderGroups();\n const merges: XLSX.Range[] = [];\n const headerRows: unknown[][] = [];\n\n for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {\n const group = headerGroups[rowIdx];\n const row: unknown[] = [];\n\n let colIdx = 0;\n for (const header of group.headers) {\n if (header.isPlaceholder) {\n // 상위 그룹 헤더의 placeholder 자식 — 빈 셀\n row.push('');\n } else {\n const text = resolveHeaderText(\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id,\n );\n row.push(text);\n\n // colSpan > 1 이면 수평 merge 범위 추가 (GroupColumnDef)\n if (header.colSpan > 1) {\n merges.push({\n s: { r: rowIdx, c: colIdx },\n e: { r: rowIdx, c: colIdx + header.colSpan - 1 },\n });\n }\n }\n colIdx++;\n }\n\n headerRows.push(row);\n }\n\n // 헤더 그룹이 2행 이상이면 하위 행 리프 헤더를 상위 행에서 수직 merge\n // (상위 행의 비-placeholder 단일 리프 헤더는 rowspan으로 처리)\n if (headerGroups.length > 1) {\n for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {\n const group = headerGroups[rowIdx];\n let colIdx = 0;\n for (const header of group.headers) {\n // isPlaceholder false 이고 colSpan == 1 이면 이 컬럼은 리프 → 수직 merge\n if (!header.isPlaceholder && header.colSpan === 1) {\n if (rowIdx + 1 <= headerGroups.length - 1) {\n merges.push({\n s: { r: rowIdx, c: colIdx },\n e: { r: headerGroups.length - 1, c: colIdx },\n });\n }\n }\n colIdx++;\n }\n }\n }\n\n return { headerRows, merges };\n}\n","import * as XLSX from 'xlsx';\n\n/**\n * 헤더 행 + 데이터 행 + (선택) 네이티브 숫자서식·컬럼 폭 → xlsx `WorkSheet` 빌더.\n *\n * MOD-GRID-25 G-1: `exportToExcel`(단일 시트) 와 `exportSheetsToExcel`(다중 시트) 가 공유.\n * **writeFile 과 분리** — 순수하게 `WorkSheet` 만 반환하므로 node 라운드트립 검증 가능\n * (download 부작용 없음). aoa_to_sheet + merges 동작은 기존 `exportToExcel` 과 동일.\n *\n * 숫자서식: `columnFormats[colId]` 가 있으면 해당 컬럼 **데이터 셀**에 네이티브 Excel\n * number-format(`.z`)을 세팅한다. numeric 셀(`aoa_to_sheet` 가 `t:'n'` 추론)은 type 보존 —\n * `toLocaleString` 문자열 강제와 달리 Excel 안에서 numeric·정렬가능하게 유지된다.\n *\n * 폰트/배경(`.s`) 은 **의도적으로 적용하지 않는다** — community `xlsx@0.18.5` 가 write 시\n * 스트립하므로(round-trip 실측: `.s` → `{patternType:'none'}`) no-op 을 동작처럼 ship 하지\n * 않는다. README 에 한계 명시.\n */\nexport function buildGridWorksheet(params: {\n headerRows: unknown[][];\n merges: XLSX.Range[];\n dataRows: unknown[][];\n /** 데이터 컬럼 순서와 1:1 대응하는 leaf 컬럼 id 배열 (format/width 매핑 키) */\n leafColumnIds: string[];\n /** columnId → Excel number-format 코드 (예 '#,##0.00', 'yyyy-mm-dd') */\n columnFormats?: Record<string, string> | undefined;\n /** columnId → 컬럼 폭 (xlsx wch 단위) */\n columnWidths?: Record<string, number> | undefined;\n}): XLSX.WorkSheet {\n const {\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n } = params;\n\n const aoa: unknown[][] = [...headerRows, ...dataRows];\n const ws = XLSX.utils.aoa_to_sheet(aoa);\n\n if (merges.length > 0) {\n ws['!merges'] = merges;\n }\n\n // 네이티브 숫자서식(.z) — 데이터 셀에만 (헤더 행 offset 만큼 아래)\n if (columnFormats) {\n const headerOffset = headerRows.length;\n for (let c = 0; c < leafColumnIds.length; c++) {\n const fmt = columnFormats[leafColumnIds[c]];\n if (!fmt) continue;\n for (let r = 0; r < dataRows.length; r++) {\n const addr = XLSX.utils.encode_cell({ r: headerOffset + r, c });\n const cell = ws[addr];\n // numeric 셀에만 적용 — Excel number-format 은 numeric 에서만 유효.\n // string 셀에 `.z` 를 달면 화면 변화 없는 silent no-op([[LESS-004]] 와 동형)이라 건너뛴다.\n // JS Date 는 aoa_to_sheet 가 numeric serial(`t:'n'`)로 변환하므로 날짜서식도 여기서 적용됨.\n if (cell && cell.t === 'n') {\n cell.z = fmt;\n }\n }\n }\n }\n\n // 컬럼 폭 → !cols (지정된 컬럼만; 미지정은 기본 폭 유지)\n if (columnWidths) {\n const hasAny = leafColumnIds.some(\n (id) => columnWidths[id] !== undefined,\n );\n if (hasAny) {\n ws['!cols'] = leafColumnIds.map((id) =>\n columnWidths[id] !== undefined ? { wch: columnWidths[id] } : {},\n );\n }\n }\n\n return ws;\n}\n","import * as XLSX from 'xlsx';\nimport type { Table } from '@tanstack/react-table';\nimport type { ExcelExportOptions } from './types';\nimport { getRowsByScope } from './internal/getRowsByScope';\nimport { buildHeaderRows } from './internal/buildHeaderRows';\nimport { buildGridWorksheet } from './internal/buildGridWorksheet';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.\n *\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\n * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior, columnFormats, columnWidths)\n * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)\n *\n * @remarks\n * **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시\n * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.\n * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).\n *\n * **셀 서식(MOD-GRID-25)**: `columnFormats` 는 네이티브 Excel number-format(`.z`)을 적용해\n * 셀이 Excel 안에서 numeric·정렬가능하게 유지됩니다. 폰트/배경색은 community\n * `xlsx@0.18.5` 가 write 시 미지원(round-trip 시 스트립)이라 제공하지 않습니다.\n *\n * @example\n * // 기본 사용 (filtered 행)\n * exportToExcel(table, { fileName: '데이터.xlsx' });\n *\n * @example\n * // 선택 행 + 다중행 헤더\n * exportToExcel(table, {\n * fileName: '선택데이터.xlsx',\n * sheetName: '선택목록',\n * scope: 'selected',\n * emptyBehavior: 'empty',\n * });\n *\n * @example\n * // 네이티브 숫자서식 + 컬럼 폭\n * exportToExcel(table, {\n * columnFormats: { price: '#,##0.00', orderedAt: 'yyyy-mm-dd' },\n * columnWidths: { name: 30 },\n * });\n */\nexport function exportToExcel<TData>(\n table: Table<TData>,\n options?: ExcelExportOptions,\n): void {\n const {\n fileName = 'export.xlsx',\n sheetName = 'Sheet1',\n scope = 'filtered',\n emptyBehavior = 'skip',\n columnFormats,\n columnWidths,\n } = options ?? {};\n\n // 1) 행 결정 (C-2: TanStack 표준 API만)\n const rows = getRowsByScope(table, scope);\n\n // 2) 빈 데이터 처리 (EC-01, EC-03)\n if (rows.length === 0 && emptyBehavior === 'skip') {\n console.warn('[grid-export] exportToExcel: 내보낼 데이터가 없습니다.');\n return;\n }\n\n // 3) 헤더 추출 (GroupColumnDef 감지 → 다중행 + merge cells, AC-003)\n const { headerRows, merges } = buildHeaderRows(table);\n\n // 4) 데이터 행 추출 (AC-004: 한국어 UTF-8 — xlsx aoa_to_sheet 기본 UTF-8)\n const dataRows: unknown[][] = rows.map((row) =>\n row.getVisibleCells().map((cell) => {\n const value = cell.getValue();\n return value !== undefined && value !== null ? value : '';\n }),\n );\n\n // 5) 워크시트 빌드 (AOA + merge + 네이티브 숫자서식/폭, MOD-GRID-25 G-1)\n const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);\n const ws = buildGridWorksheet({\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n });\n\n // 6) workbook + 파일 다운로드 (EC-04: fileName 확장자 자동 추가)\n const wb = XLSX.utils.book_new();\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\n const finalFileName = fileName.endsWith('.xlsx')\n ? fileName\n : `${fileName}.xlsx`;\n XLSX.writeFile(wb, finalFileName);\n}\n","import type { Table } from '@tanstack/react-table';\r\nimport type { DownloadExcelOptions } from '../types';\r\nimport { exportToExcel } from '../exportToExcel';\r\n\r\n/**\r\n * DataTable `buttonInfo.downloadAction` 마이그레이션 호환 alias.\r\n *\r\n * 내부적으로 `exportToExcel(table, options)` 에 위임한다.\r\n * scope 기본값은 'filtered' (현재 필터/정렬 반영 행).\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스\r\n * @param options - export 옵션 (scope 기본값: 'filtered')\r\n *\r\n * @deprecated DataTable buttonInfo.downloadAction 마이그레이션 alias.\r\n * 1 minor 버전 이상 유지 (C-6, C-23).\r\n * 신규 코드는 `exportToExcel()` 직접 사용 권장.\r\n *\r\n * @example\r\n * import { downloadExcel } from '@topgrid/grid-export/legacy';\r\n * // DataTable 기존 패턴 교체\r\n * downloadExcel(table);\r\n */\r\nexport function downloadExcel<TData>(\r\n table: Table<TData>,\r\n options?: DownloadExcelOptions,\r\n): void {\r\n // C-29 exactOptionalPropertyTypes 대응:\r\n // DownloadExcelOptions 의 optional props 를 직접 forwarding 하지 않고\r\n // options 전체를 spread 하여 exportToExcel 에 위임.\r\n // scope 기본값 'filtered' 는 exportToExcel 내부에서 처리됨.\r\n exportToExcel(table, options);\r\n}\r\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Table } from '@tanstack/react-table';
2
- import { D as DownloadExcelOptions } from '../types-BCeqH-13.cjs';
2
+ import { D as DownloadExcelOptions } from '../types-yAFLGyyY.cjs';
3
3
 
4
4
  /**
5
5
  * DataTable `buttonInfo.downloadAction` 마이그레이션 호환 alias.
@@ -1,5 +1,5 @@
1
1
  import { Table } from '@tanstack/react-table';
2
- import { D as DownloadExcelOptions } from '../types-BCeqH-13.js';
2
+ import { D as DownloadExcelOptions } from '../types-yAFLGyyY.js';
3
3
 
4
4
  /**
5
5
  * DataTable `buttonInfo.downloadAction` 마이그레이션 호환 alias.
@@ -1,4 +1,4 @@
1
- import * as XLSX from 'xlsx';
1
+ import * as XLSX3 from 'xlsx';
2
2
 
3
3
  // src/exportToExcel.ts
4
4
 
@@ -12,8 +12,6 @@ function getRowsByScope(table, scope) {
12
12
  }
13
13
  return table.getFilteredRowModel().rows;
14
14
  }
15
-
16
- // src/exportToExcel.ts
17
15
  function resolveHeaderText(headerValue) {
18
16
  if (typeof headerValue === "string") {
19
17
  return headerValue;
@@ -66,12 +64,56 @@ function buildHeaderRows(table) {
66
64
  }
67
65
  return { headerRows, merges };
68
66
  }
67
+ function buildGridWorksheet(params) {
68
+ const {
69
+ headerRows,
70
+ merges,
71
+ dataRows,
72
+ leafColumnIds,
73
+ columnFormats,
74
+ columnWidths
75
+ } = params;
76
+ const aoa = [...headerRows, ...dataRows];
77
+ const ws = XLSX3.utils.aoa_to_sheet(aoa);
78
+ if (merges.length > 0) {
79
+ ws["!merges"] = merges;
80
+ }
81
+ if (columnFormats) {
82
+ const headerOffset = headerRows.length;
83
+ for (let c = 0; c < leafColumnIds.length; c++) {
84
+ const fmt = columnFormats[leafColumnIds[c]];
85
+ if (!fmt) continue;
86
+ for (let r = 0; r < dataRows.length; r++) {
87
+ const addr = XLSX3.utils.encode_cell({ r: headerOffset + r, c });
88
+ const cell = ws[addr];
89
+ if (cell && cell.t === "n") {
90
+ cell.z = fmt;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ if (columnWidths) {
96
+ const hasAny = leafColumnIds.some(
97
+ (id) => columnWidths[id] !== void 0
98
+ );
99
+ if (hasAny) {
100
+ ws["!cols"] = leafColumnIds.map(
101
+ (id) => columnWidths[id] !== void 0 ? { wch: columnWidths[id] } : {}
102
+ );
103
+ }
104
+ }
105
+ return ws;
106
+ }
107
+
108
+ // src/exportToExcel.ts
69
109
  function exportToExcel(table, options) {
70
110
  const {
71
111
  fileName = "export.xlsx",
72
112
  sheetName = "Sheet1",
73
113
  scope = "filtered",
74
- emptyBehavior = "skip"
114
+ emptyBehavior = "skip",
115
+ columnFormats,
116
+ columnWidths
75
117
  } = options ?? {};
76
118
  const rows = getRowsByScope(table, scope);
77
119
  if (rows.length === 0 && emptyBehavior === "skip") {
@@ -85,15 +127,19 @@ function exportToExcel(table, options) {
85
127
  return value !== void 0 && value !== null ? value : "";
86
128
  })
87
129
  );
88
- const aoa = [...headerRows, ...dataRows];
89
- const ws = XLSX.utils.aoa_to_sheet(aoa);
90
- if (merges.length > 0) {
91
- ws["!merges"] = merges;
92
- }
93
- const wb = XLSX.utils.book_new();
94
- XLSX.utils.book_append_sheet(wb, ws, sheetName);
130
+ const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);
131
+ const ws = buildGridWorksheet({
132
+ headerRows,
133
+ merges,
134
+ dataRows,
135
+ leafColumnIds,
136
+ columnFormats,
137
+ columnWidths
138
+ });
139
+ const wb = XLSX3.utils.book_new();
140
+ XLSX3.utils.book_append_sheet(wb, ws, sheetName);
95
141
  const finalFileName = fileName.endsWith(".xlsx") ? fileName : `${fileName}.xlsx`;
96
- XLSX.writeFile(wb, finalFileName);
142
+ XLSX3.writeFile(wb, finalFileName);
97
143
  }
98
144
 
99
145
  // src/legacy/downloadExcel.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/internal/getRowsByScope.ts","../../src/exportToExcel.ts","../../src/legacy/downloadExcel.ts"],"names":[],"mappings":"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","file":"downloadExcel.mjs","sourcesContent":["import type { Row, Table } from '@tanstack/react-table';\r\nimport type { ExportScope } from '../types';\r\n\r\n/**\r\n * TanStack scope 에 따라 Row 배열 반환 (C-2: 표준 API만 사용)\r\n * G-001 exportToExcel.ts 에서 추출 — Excel + CSV 공유 헬퍼 (D1)\r\n */\r\nexport function getRowsByScope<TData>(\r\n table: Table<TData>,\r\n scope: ExportScope,\r\n): Row<TData>[] {\r\n if (scope === 'all') {\r\n return table.getCoreRowModel().rows;\r\n }\r\n if (scope === 'selected') {\r\n return table.getSelectedRowModel().rows;\r\n }\r\n // 'filtered' (default)\r\n return table.getFilteredRowModel().rows;\r\n}\r\n","import * as XLSX from 'xlsx';\r\nimport type { Table } from '@tanstack/react-table';\r\nimport type { ExcelExportOptions } from './types';\r\nimport { getRowsByScope } from './internal/getRowsByScope';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * 헤더 텍스트 추출 헬퍼 — 헤더 값이 string 이면 그대로, 아니면 빈 문자열\r\n */\r\nfunction resolveHeaderText(headerValue: unknown): string {\r\n if (typeof headerValue === 'string') {\r\n return headerValue;\r\n }\r\n return '';\r\n}\r\n\r\n/**\r\n * TanStack Table 의 헤더 그룹을 순회하여\r\n * - AOA(Array of Arrays) 형태의 헤더 행 배열\r\n * - xlsx merge cells 배열 (다중행 헤더 GroupColumnDef 용)\r\n * 을 반환한다.\r\n *\r\n * AC-003: header.isPlaceholder + header.colSpan 이용 → ws['!merges'] 계산\r\n */\r\nfunction buildHeaderRows<TData>(table: Table<TData>): {\r\n headerRows: unknown[][];\r\n merges: XLSX.Range[];\r\n} {\r\n const headerGroups = table.getHeaderGroups();\r\n const merges: XLSX.Range[] = [];\r\n const headerRows: unknown[][] = [];\r\n\r\n for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {\r\n const group = headerGroups[rowIdx];\r\n const row: unknown[] = [];\r\n\r\n let colIdx = 0;\r\n for (const header of group.headers) {\r\n if (header.isPlaceholder) {\r\n // 상위 그룹 헤더의 placeholder 자식 — 빈 셀\r\n row.push('');\r\n } else {\r\n const text = resolveHeaderText(\r\n typeof header.column.columnDef.header === 'string'\r\n ? header.column.columnDef.header\r\n : header.column.id,\r\n );\r\n row.push(text);\r\n\r\n // colSpan > 1 이면 수평 merge 범위 추가 (GroupColumnDef)\r\n if (header.colSpan > 1) {\r\n merges.push({\r\n s: { r: rowIdx, c: colIdx },\r\n e: { r: rowIdx, c: colIdx + header.colSpan - 1 },\r\n });\r\n }\r\n }\r\n colIdx++;\r\n }\r\n\r\n headerRows.push(row);\r\n }\r\n\r\n // 헤더 그룹이 2행 이상이면 하위 행 리프 헤더를 상위 행에서 수직 merge\r\n // (상위 행의 비-placeholder 단일 리프 헤더는 rowspan으로 처리)\r\n if (headerGroups.length > 1) {\r\n for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {\r\n const group = headerGroups[rowIdx];\r\n let colIdx = 0;\r\n for (const header of group.headers) {\r\n // isPlaceholder false 이고 colSpan == 1 이면 이 컬럼은 리프 → 수직 merge\r\n if (!header.isPlaceholder && header.colSpan === 1) {\r\n if (rowIdx + 1 <= headerGroups.length - 1) {\r\n merges.push({\r\n s: { r: rowIdx, c: colIdx },\r\n e: { r: headerGroups.length - 1, c: colIdx },\r\n });\r\n }\r\n }\r\n colIdx++;\r\n }\r\n }\r\n }\r\n\r\n return { headerRows, merges };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\r\n * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior)\r\n * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)\r\n *\r\n * @remarks\r\n * **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시\r\n * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.\r\n * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).\r\n *\r\n * @example\r\n * // 기본 사용 (filtered 행)\r\n * exportToExcel(table, { fileName: '데이터.xlsx' });\r\n *\r\n * @example\r\n * // 선택 행 + 다중행 헤더\r\n * exportToExcel(table, {\r\n * fileName: '선택데이터.xlsx',\r\n * sheetName: '선택목록',\r\n * scope: 'selected',\r\n * emptyBehavior: 'empty',\r\n * });\r\n */\r\nexport function exportToExcel<TData>(\r\n table: Table<TData>,\r\n options?: ExcelExportOptions,\r\n): void {\r\n const {\r\n fileName = 'export.xlsx',\r\n sheetName = 'Sheet1',\r\n scope = 'filtered',\r\n emptyBehavior = 'skip',\r\n } = options ?? {};\r\n\r\n // 1) 행 결정 (C-2: TanStack 표준 API만)\r\n const rows = getRowsByScope(table, scope);\r\n\r\n // 2) 빈 데이터 처리 (EC-01, EC-03)\r\n if (rows.length === 0 && emptyBehavior === 'skip') {\r\n console.warn('[grid-export] exportToExcel: 내보낼 데이터가 없습니다.');\r\n return;\r\n }\r\n\r\n // 3) 헤더 추출 (GroupColumnDef 감지 → 다중행 + merge cells, AC-003)\r\n const { headerRows, merges } = buildHeaderRows(table);\r\n\r\n // 4) 데이터 행 추출 (AC-004: 한국어 UTF-8 — xlsx aoa_to_sheet 기본 UTF-8)\r\n const dataRows: unknown[][] = rows.map((row) =>\r\n row.getVisibleCells().map((cell) => {\r\n const value = cell.getValue();\r\n return value !== undefined && value !== null ? value : '';\r\n }),\r\n );\r\n\r\n // 5) AOA sheet 생성 (헤더 + 데이터)\r\n const aoa: unknown[][] = [...headerRows, ...dataRows];\r\n const ws = XLSX.utils.aoa_to_sheet(aoa);\r\n\r\n // 6) merge cells 적용 (다중행 헤더 있을 때, AC-003)\r\n if (merges.length > 0) {\r\n ws['!merges'] = merges;\r\n }\r\n\r\n // 7) workbook + 파일 다운로드 (EC-04: fileName 확장자 자동 추가)\r\n const wb = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\r\n const finalFileName = fileName.endsWith('.xlsx')\r\n ? fileName\r\n : `${fileName}.xlsx`;\r\n XLSX.writeFile(wb, finalFileName);\r\n}\r\n","import type { Table } from '@tanstack/react-table';\r\nimport type { DownloadExcelOptions } from '../types';\r\nimport { exportToExcel } from '../exportToExcel';\r\n\r\n/**\r\n * DataTable `buttonInfo.downloadAction` 마이그레이션 호환 alias.\r\n *\r\n * 내부적으로 `exportToExcel(table, options)` 에 위임한다.\r\n * scope 기본값은 'filtered' (현재 필터/정렬 반영 행).\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스\r\n * @param options - export 옵션 (scope 기본값: 'filtered')\r\n *\r\n * @deprecated DataTable buttonInfo.downloadAction 마이그레이션 alias.\r\n * 1 minor 버전 이상 유지 (C-6, C-23).\r\n * 신규 코드는 `exportToExcel()` 직접 사용 권장.\r\n *\r\n * @example\r\n * import { downloadExcel } from '@topgrid/grid-export/legacy';\r\n * // DataTable 기존 패턴 교체\r\n * downloadExcel(table);\r\n */\r\nexport function downloadExcel<TData>(\r\n table: Table<TData>,\r\n options?: DownloadExcelOptions,\r\n): void {\r\n // C-29 exactOptionalPropertyTypes 대응:\r\n // DownloadExcelOptions 의 optional props 를 직접 forwarding 하지 않고\r\n // options 전체를 spread 하여 exportToExcel 에 위임.\r\n // scope 기본값 'filtered' 는 exportToExcel 내부에서 처리됨.\r\n exportToExcel(table, options);\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/internal/getRowsByScope.ts","../../src/internal/buildHeaderRows.ts","../../src/internal/buildGridWorksheet.ts","../../src/exportToExcel.ts","../../src/legacy/downloadExcel.ts"],"names":["XLSX2"],"mappings":"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","file":"downloadExcel.mjs","sourcesContent":["import type { Row, Table } from '@tanstack/react-table';\r\nimport type { ExportScope } from '../types';\r\n\r\n/**\r\n * TanStack scope 에 따라 Row 배열 반환 (C-2: 표준 API만 사용)\r\n * G-001 exportToExcel.ts 에서 추출 — Excel + CSV 공유 헬퍼 (D1)\r\n */\r\nexport function getRowsByScope<TData>(\r\n table: Table<TData>,\r\n scope: ExportScope,\r\n): Row<TData>[] {\r\n if (scope === 'all') {\r\n return table.getCoreRowModel().rows;\r\n }\r\n if (scope === 'selected') {\r\n return table.getSelectedRowModel().rows;\r\n }\r\n // 'filtered' (default)\r\n return table.getFilteredRowModel().rows;\r\n}\r\n","import type { Table } from '@tanstack/react-table';\nimport * as XLSX from 'xlsx';\n\n/**\n * 헤더 텍스트 추출 헬퍼 — 헤더 값이 string 이면 그대로, 아니면 빈 문자열\n */\nfunction resolveHeaderText(headerValue: unknown): string {\n if (typeof headerValue === 'string') {\n return headerValue;\n }\n return '';\n}\n\n/**\n * TanStack Table 의 헤더 그룹을 순회하여\n * - AOA(Array of Arrays) 형태의 헤더 행 배열\n * - xlsx merge cells 배열 (다중행 헤더 GroupColumnDef 용)\n * 을 반환한다.\n *\n * AC-003: header.isPlaceholder + header.colSpan 이용 → ws['!merges'] 계산\n *\n * MOD-GRID-25 G-1: `exportToExcel` 의 private helper 에서 `internal/` 로 이동(동작 동일) —\n * `exportSheetsToExcel`(다중 시트) 와 공유하기 위함. 로직 변경 없음.\n */\nexport function buildHeaderRows<TData>(table: Table<TData>): {\n headerRows: unknown[][];\n merges: XLSX.Range[];\n} {\n const headerGroups = table.getHeaderGroups();\n const merges: XLSX.Range[] = [];\n const headerRows: unknown[][] = [];\n\n for (let rowIdx = 0; rowIdx < headerGroups.length; rowIdx++) {\n const group = headerGroups[rowIdx];\n const row: unknown[] = [];\n\n let colIdx = 0;\n for (const header of group.headers) {\n if (header.isPlaceholder) {\n // 상위 그룹 헤더의 placeholder 자식 — 빈 셀\n row.push('');\n } else {\n const text = resolveHeaderText(\n typeof header.column.columnDef.header === 'string'\n ? header.column.columnDef.header\n : header.column.id,\n );\n row.push(text);\n\n // colSpan > 1 이면 수평 merge 범위 추가 (GroupColumnDef)\n if (header.colSpan > 1) {\n merges.push({\n s: { r: rowIdx, c: colIdx },\n e: { r: rowIdx, c: colIdx + header.colSpan - 1 },\n });\n }\n }\n colIdx++;\n }\n\n headerRows.push(row);\n }\n\n // 헤더 그룹이 2행 이상이면 하위 행 리프 헤더를 상위 행에서 수직 merge\n // (상위 행의 비-placeholder 단일 리프 헤더는 rowspan으로 처리)\n if (headerGroups.length > 1) {\n for (let rowIdx = 0; rowIdx < headerGroups.length - 1; rowIdx++) {\n const group = headerGroups[rowIdx];\n let colIdx = 0;\n for (const header of group.headers) {\n // isPlaceholder false 이고 colSpan == 1 이면 이 컬럼은 리프 → 수직 merge\n if (!header.isPlaceholder && header.colSpan === 1) {\n if (rowIdx + 1 <= headerGroups.length - 1) {\n merges.push({\n s: { r: rowIdx, c: colIdx },\n e: { r: headerGroups.length - 1, c: colIdx },\n });\n }\n }\n colIdx++;\n }\n }\n }\n\n return { headerRows, merges };\n}\n","import * as XLSX from 'xlsx';\n\n/**\n * 헤더 행 + 데이터 행 + (선택) 네이티브 숫자서식·컬럼 폭 → xlsx `WorkSheet` 빌더.\n *\n * MOD-GRID-25 G-1: `exportToExcel`(단일 시트) 와 `exportSheetsToExcel`(다중 시트) 가 공유.\n * **writeFile 과 분리** — 순수하게 `WorkSheet` 만 반환하므로 node 라운드트립 검증 가능\n * (download 부작용 없음). aoa_to_sheet + merges 동작은 기존 `exportToExcel` 과 동일.\n *\n * 숫자서식: `columnFormats[colId]` 가 있으면 해당 컬럼 **데이터 셀**에 네이티브 Excel\n * number-format(`.z`)을 세팅한다. numeric 셀(`aoa_to_sheet` 가 `t:'n'` 추론)은 type 보존 —\n * `toLocaleString` 문자열 강제와 달리 Excel 안에서 numeric·정렬가능하게 유지된다.\n *\n * 폰트/배경(`.s`) 은 **의도적으로 적용하지 않는다** — community `xlsx@0.18.5` 가 write 시\n * 스트립하므로(round-trip 실측: `.s` → `{patternType:'none'}`) no-op 을 동작처럼 ship 하지\n * 않는다. README 에 한계 명시.\n */\nexport function buildGridWorksheet(params: {\n headerRows: unknown[][];\n merges: XLSX.Range[];\n dataRows: unknown[][];\n /** 데이터 컬럼 순서와 1:1 대응하는 leaf 컬럼 id 배열 (format/width 매핑 키) */\n leafColumnIds: string[];\n /** columnId → Excel number-format 코드 (예 '#,##0.00', 'yyyy-mm-dd') */\n columnFormats?: Record<string, string> | undefined;\n /** columnId → 컬럼 폭 (xlsx wch 단위) */\n columnWidths?: Record<string, number> | undefined;\n}): XLSX.WorkSheet {\n const {\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n } = params;\n\n const aoa: unknown[][] = [...headerRows, ...dataRows];\n const ws = XLSX.utils.aoa_to_sheet(aoa);\n\n if (merges.length > 0) {\n ws['!merges'] = merges;\n }\n\n // 네이티브 숫자서식(.z) — 데이터 셀에만 (헤더 행 offset 만큼 아래)\n if (columnFormats) {\n const headerOffset = headerRows.length;\n for (let c = 0; c < leafColumnIds.length; c++) {\n const fmt = columnFormats[leafColumnIds[c]];\n if (!fmt) continue;\n for (let r = 0; r < dataRows.length; r++) {\n const addr = XLSX.utils.encode_cell({ r: headerOffset + r, c });\n const cell = ws[addr];\n // numeric 셀에만 적용 — Excel number-format 은 numeric 에서만 유효.\n // string 셀에 `.z` 를 달면 화면 변화 없는 silent no-op([[LESS-004]] 와 동형)이라 건너뛴다.\n // JS Date 는 aoa_to_sheet 가 numeric serial(`t:'n'`)로 변환하므로 날짜서식도 여기서 적용됨.\n if (cell && cell.t === 'n') {\n cell.z = fmt;\n }\n }\n }\n }\n\n // 컬럼 폭 → !cols (지정된 컬럼만; 미지정은 기본 폭 유지)\n if (columnWidths) {\n const hasAny = leafColumnIds.some(\n (id) => columnWidths[id] !== undefined,\n );\n if (hasAny) {\n ws['!cols'] = leafColumnIds.map((id) =>\n columnWidths[id] !== undefined ? { wch: columnWidths[id] } : {},\n );\n }\n }\n\n return ws;\n}\n","import * as XLSX from 'xlsx';\nimport type { Table } from '@tanstack/react-table';\nimport type { ExcelExportOptions } from './types';\nimport { getRowsByScope } from './internal/getRowsByScope';\nimport { buildHeaderRows } from './internal/buildHeaderRows';\nimport { buildGridWorksheet } from './internal/buildGridWorksheet';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * TanStack Table 인스턴스를 기반으로 Excel(.xlsx) 파일을 생성·다운로드한다.\n *\n * @param table - TanStack v8 Table<TData> 인스턴스 (useReactTable 반환값)\n * @param options - Excel export 옵션 (fileName, sheetName, scope, emptyBehavior, columnFormats, columnWidths)\n * @returns void (동기 실행 — xlsx.writeFile 동기 API, D3)\n *\n * @remarks\n * **대용량 데이터 경고**: scope='all' 또는 대용량 필터 결과(>10,000행) 시\n * xlsx.writeFile 이 동기 실행되어 브라우저 메인 스레드를 블로킹할 수 있습니다.\n * 대용량 사용 시 Web Worker 래핑 권장 (EC-05).\n *\n * **셀 서식(MOD-GRID-25)**: `columnFormats` 는 네이티브 Excel number-format(`.z`)을 적용해\n * 셀이 Excel 안에서 numeric·정렬가능하게 유지됩니다. 폰트/배경색은 community\n * `xlsx@0.18.5` 가 write 시 미지원(round-trip 시 스트립)이라 제공하지 않습니다.\n *\n * @example\n * // 기본 사용 (filtered 행)\n * exportToExcel(table, { fileName: '데이터.xlsx' });\n *\n * @example\n * // 선택 행 + 다중행 헤더\n * exportToExcel(table, {\n * fileName: '선택데이터.xlsx',\n * sheetName: '선택목록',\n * scope: 'selected',\n * emptyBehavior: 'empty',\n * });\n *\n * @example\n * // 네이티브 숫자서식 + 컬럼 폭\n * exportToExcel(table, {\n * columnFormats: { price: '#,##0.00', orderedAt: 'yyyy-mm-dd' },\n * columnWidths: { name: 30 },\n * });\n */\nexport function exportToExcel<TData>(\n table: Table<TData>,\n options?: ExcelExportOptions,\n): void {\n const {\n fileName = 'export.xlsx',\n sheetName = 'Sheet1',\n scope = 'filtered',\n emptyBehavior = 'skip',\n columnFormats,\n columnWidths,\n } = options ?? {};\n\n // 1) 행 결정 (C-2: TanStack 표준 API만)\n const rows = getRowsByScope(table, scope);\n\n // 2) 빈 데이터 처리 (EC-01, EC-03)\n if (rows.length === 0 && emptyBehavior === 'skip') {\n console.warn('[grid-export] exportToExcel: 내보낼 데이터가 없습니다.');\n return;\n }\n\n // 3) 헤더 추출 (GroupColumnDef 감지 → 다중행 + merge cells, AC-003)\n const { headerRows, merges } = buildHeaderRows(table);\n\n // 4) 데이터 행 추출 (AC-004: 한국어 UTF-8 — xlsx aoa_to_sheet 기본 UTF-8)\n const dataRows: unknown[][] = rows.map((row) =>\n row.getVisibleCells().map((cell) => {\n const value = cell.getValue();\n return value !== undefined && value !== null ? value : '';\n }),\n );\n\n // 5) 워크시트 빌드 (AOA + merge + 네이티브 숫자서식/폭, MOD-GRID-25 G-1)\n const leafColumnIds = table.getVisibleLeafColumns().map((c) => c.id);\n const ws = buildGridWorksheet({\n headerRows,\n merges,\n dataRows,\n leafColumnIds,\n columnFormats,\n columnWidths,\n });\n\n // 6) workbook + 파일 다운로드 (EC-04: fileName 확장자 자동 추가)\n const wb = XLSX.utils.book_new();\n XLSX.utils.book_append_sheet(wb, ws, sheetName);\n const finalFileName = fileName.endsWith('.xlsx')\n ? fileName\n : `${fileName}.xlsx`;\n XLSX.writeFile(wb, finalFileName);\n}\n","import type { Table } from '@tanstack/react-table';\r\nimport type { DownloadExcelOptions } from '../types';\r\nimport { exportToExcel } from '../exportToExcel';\r\n\r\n/**\r\n * DataTable `buttonInfo.downloadAction` 마이그레이션 호환 alias.\r\n *\r\n * 내부적으로 `exportToExcel(table, options)` 에 위임한다.\r\n * scope 기본값은 'filtered' (현재 필터/정렬 반영 행).\r\n *\r\n * @param table - TanStack v8 Table<TData> 인스턴스\r\n * @param options - export 옵션 (scope 기본값: 'filtered')\r\n *\r\n * @deprecated DataTable buttonInfo.downloadAction 마이그레이션 alias.\r\n * 1 minor 버전 이상 유지 (C-6, C-23).\r\n * 신규 코드는 `exportToExcel()` 직접 사용 권장.\r\n *\r\n * @example\r\n * import { downloadExcel } from '@topgrid/grid-export/legacy';\r\n * // DataTable 기존 패턴 교체\r\n * downloadExcel(table);\r\n */\r\nexport function downloadExcel<TData>(\r\n table: Table<TData>,\r\n options?: DownloadExcelOptions,\r\n): void {\r\n // C-29 exactOptionalPropertyTypes 대응:\r\n // DownloadExcelOptions 의 optional props 를 직접 forwarding 하지 않고\r\n // options 전체를 spread 하여 exportToExcel 에 위임.\r\n // scope 기본값 'filtered' 는 exportToExcel 내부에서 처리됨.\r\n exportToExcel(table, options);\r\n}\r\n"]}
@@ -1,3 +1,5 @@
1
+ import * as _tanstack_react_table from '@tanstack/react-table';
2
+
1
3
  /** Excel export 범위 지정 */
2
4
  type ExportScope = 'all' | 'filtered' | 'selected';
3
5
  /** export 시 데이터 행 0건 동작 — 5개 Options 공유 single source-of-truth (G-005 D2) */
@@ -33,6 +35,58 @@ interface ExcelExportOptions {
33
35
  * @default 'skip'
34
36
  */
35
37
  emptyBehavior?: EmptyBehavior;
38
+ /**
39
+ * 컬럼별 네이티브 Excel number-format 코드 (MOD-GRID-25 G-1)
40
+ *
41
+ * key = 컬럼 id, value = Excel format 코드(예 `'#,##0.00'`, `'yyyy-mm-dd'`, `'0.0%'`).
42
+ * 해당 컬럼 데이터 셀에 `.z` 로 적용되어 셀이 Excel 안에서 numeric·정렬가능하게 유지된다.
43
+ * @default undefined (서식 미적용)
44
+ */
45
+ columnFormats?: Record<string, string>;
46
+ /**
47
+ * 컬럼별 폭 (MOD-GRID-25 G-1) — key = 컬럼 id, value = xlsx `wch` 단위 폭.
48
+ * 지정된 컬럼만 `!cols` 에 반영(미지정은 기본 폭).
49
+ * @default undefined
50
+ */
51
+ columnWidths?: Record<string, number>;
52
+ }
53
+ /**
54
+ * 다중 시트 Excel export 의 시트 1개 정의 (MOD-GRID-25 G-2)
55
+ *
56
+ * @see exportSheetsToExcel
57
+ */
58
+ interface ExcelSheet {
59
+ /** 시트명 (Excel 탭에 표시) */
60
+ name: string;
61
+ /**
62
+ * TanStack v8 Table 인스턴스 — 시트 내용 소스.
63
+ *
64
+ * 다중 시트는 본질적으로 **서로 다른 행 타입의 테이블을 한 배열에 섞으므로**(`Table<Person>` +
65
+ * `Table<Order>`), 단일 `TData` 로 묶을 수 없다. `Table<TData>` 는 `accessorFn` 의 함수
66
+ * 인자 반공변성 때문에 `Table<unknown>` 에 대입 불가(`Table<Person>` ↛ `Table<unknown>`).
67
+ * 이질 배열을 받으려면 `Table<any>` 가 유일한 실용 해법(TS 는 존재 타입 미지원).
68
+ * export 코드는 `getValue()` 결과를 `unknown` 으로만 다뤄 타입 안전을 유지한다.
69
+ */
70
+ table: _tanstack_react_table.Table<any>;
71
+ /**
72
+ * export 대상 행 범위
73
+ * @default 'filtered'
74
+ */
75
+ scope?: ExportScope;
76
+ /** 컬럼별 네이티브 number-format (ExcelExportOptions.columnFormats 와 동일) */
77
+ columnFormats?: Record<string, string>;
78
+ /** 컬럼별 폭 (ExcelExportOptions.columnWidths 와 동일) */
79
+ columnWidths?: Record<string, number>;
80
+ }
81
+ /**
82
+ * `exportSheetsToExcel` 옵션 (MOD-GRID-25 G-2)
83
+ */
84
+ interface MultiSheetOptions {
85
+ /**
86
+ * 다운로드 파일명 (확장자 없으면 .xlsx 자동 추가)
87
+ * @default 'export.xlsx'
88
+ */
89
+ fileName?: string;
36
90
  }
37
91
  /**
38
92
  * DataTable buttonInfo 호환 alias 옵션 (legacy)
@@ -151,6 +205,13 @@ interface ClipboardOptions {
151
205
  * @default 'skip'
152
206
  */
153
207
  emptyBehavior?: EmptyBehavior;
208
+ /**
209
+ * 헤더 행 포함 여부 (MOD-GRID-25 G-3)
210
+ * - `true`: 첫 줄에 헤더 행 포함 (기본 — 기존 동작)
211
+ * - `false`: 데이터 행만 복사 (다른 영역에 붙여넣어 헤더 중복 방지)
212
+ * @default true
213
+ */
214
+ includeHeader?: boolean;
154
215
  }
155
216
  /**
156
217
  * 행 배열 기반 Excel export 의 컬럼 정의
@@ -228,4 +289,4 @@ interface PrintOptions {
228
289
  emptyBehavior?: EmptyBehavior;
229
290
  }
230
291
 
231
- export type { CSVExportOptions as C, DownloadExcelOptions as D, ExcelExportOptions as E, PDFExportOptions as P, ClipboardOptions as a, PrintOptions as b, ExcelColumn as c, ExportRowsOptions as d, EmptyBehavior as e, ExportScope as f };
292
+ export type { CSVExportOptions as C, DownloadExcelOptions as D, ExcelExportOptions as E, MultiSheetOptions as M, PDFExportOptions as P, ClipboardOptions as a, PrintOptions as b, ExcelColumn as c, ExportRowsOptions as d, ExcelSheet as e, EmptyBehavior as f, ExportScope as g };
@@ -1,3 +1,5 @@
1
+ import * as _tanstack_react_table from '@tanstack/react-table';
2
+
1
3
  /** Excel export 범위 지정 */
2
4
  type ExportScope = 'all' | 'filtered' | 'selected';
3
5
  /** export 시 데이터 행 0건 동작 — 5개 Options 공유 single source-of-truth (G-005 D2) */
@@ -33,6 +35,58 @@ interface ExcelExportOptions {
33
35
  * @default 'skip'
34
36
  */
35
37
  emptyBehavior?: EmptyBehavior;
38
+ /**
39
+ * 컬럼별 네이티브 Excel number-format 코드 (MOD-GRID-25 G-1)
40
+ *
41
+ * key = 컬럼 id, value = Excel format 코드(예 `'#,##0.00'`, `'yyyy-mm-dd'`, `'0.0%'`).
42
+ * 해당 컬럼 데이터 셀에 `.z` 로 적용되어 셀이 Excel 안에서 numeric·정렬가능하게 유지된다.
43
+ * @default undefined (서식 미적용)
44
+ */
45
+ columnFormats?: Record<string, string>;
46
+ /**
47
+ * 컬럼별 폭 (MOD-GRID-25 G-1) — key = 컬럼 id, value = xlsx `wch` 단위 폭.
48
+ * 지정된 컬럼만 `!cols` 에 반영(미지정은 기본 폭).
49
+ * @default undefined
50
+ */
51
+ columnWidths?: Record<string, number>;
52
+ }
53
+ /**
54
+ * 다중 시트 Excel export 의 시트 1개 정의 (MOD-GRID-25 G-2)
55
+ *
56
+ * @see exportSheetsToExcel
57
+ */
58
+ interface ExcelSheet {
59
+ /** 시트명 (Excel 탭에 표시) */
60
+ name: string;
61
+ /**
62
+ * TanStack v8 Table 인스턴스 — 시트 내용 소스.
63
+ *
64
+ * 다중 시트는 본질적으로 **서로 다른 행 타입의 테이블을 한 배열에 섞으므로**(`Table<Person>` +
65
+ * `Table<Order>`), 단일 `TData` 로 묶을 수 없다. `Table<TData>` 는 `accessorFn` 의 함수
66
+ * 인자 반공변성 때문에 `Table<unknown>` 에 대입 불가(`Table<Person>` ↛ `Table<unknown>`).
67
+ * 이질 배열을 받으려면 `Table<any>` 가 유일한 실용 해법(TS 는 존재 타입 미지원).
68
+ * export 코드는 `getValue()` 결과를 `unknown` 으로만 다뤄 타입 안전을 유지한다.
69
+ */
70
+ table: _tanstack_react_table.Table<any>;
71
+ /**
72
+ * export 대상 행 범위
73
+ * @default 'filtered'
74
+ */
75
+ scope?: ExportScope;
76
+ /** 컬럼별 네이티브 number-format (ExcelExportOptions.columnFormats 와 동일) */
77
+ columnFormats?: Record<string, string>;
78
+ /** 컬럼별 폭 (ExcelExportOptions.columnWidths 와 동일) */
79
+ columnWidths?: Record<string, number>;
80
+ }
81
+ /**
82
+ * `exportSheetsToExcel` 옵션 (MOD-GRID-25 G-2)
83
+ */
84
+ interface MultiSheetOptions {
85
+ /**
86
+ * 다운로드 파일명 (확장자 없으면 .xlsx 자동 추가)
87
+ * @default 'export.xlsx'
88
+ */
89
+ fileName?: string;
36
90
  }
37
91
  /**
38
92
  * DataTable buttonInfo 호환 alias 옵션 (legacy)
@@ -151,6 +205,13 @@ interface ClipboardOptions {
151
205
  * @default 'skip'
152
206
  */
153
207
  emptyBehavior?: EmptyBehavior;
208
+ /**
209
+ * 헤더 행 포함 여부 (MOD-GRID-25 G-3)
210
+ * - `true`: 첫 줄에 헤더 행 포함 (기본 — 기존 동작)
211
+ * - `false`: 데이터 행만 복사 (다른 영역에 붙여넣어 헤더 중복 방지)
212
+ * @default true
213
+ */
214
+ includeHeader?: boolean;
154
215
  }
155
216
  /**
156
217
  * 행 배열 기반 Excel export 의 컬럼 정의
@@ -228,4 +289,4 @@ interface PrintOptions {
228
289
  emptyBehavior?: EmptyBehavior;
229
290
  }
230
291
 
231
- export type { CSVExportOptions as C, DownloadExcelOptions as D, ExcelExportOptions as E, PDFExportOptions as P, ClipboardOptions as a, PrintOptions as b, ExcelColumn as c, ExportRowsOptions as d, EmptyBehavior as e, ExportScope as f };
292
+ export type { CSVExportOptions as C, DownloadExcelOptions as D, ExcelExportOptions as E, MultiSheetOptions as M, PDFExportOptions as P, ClipboardOptions as a, PrintOptions as b, ExcelColumn as c, ExportRowsOptions as d, ExcelSheet as e, EmptyBehavior as f, ExportScope as g };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topgrid/grid-export",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Excel, PDF, CSV export for grid data",