diff --git a/examples/financial-report.xlsx b/examples/financial-report.xlsx index 2436236..dce1e19 100644 Binary files a/examples/financial-report.xlsx and b/examples/financial-report.xlsx differ diff --git a/examples/kitchen-sink.xlsx b/examples/kitchen-sink.xlsx index ea4f28c..01fc555 100644 Binary files a/examples/kitchen-sink.xlsx and b/examples/kitchen-sink.xlsx differ diff --git a/examples/playground.xlsx b/examples/playground.xlsx index a3ad63f..095069e 100644 Binary files a/examples/playground.xlsx and b/examples/playground.xlsx differ diff --git a/src/index.ts b/src/index.ts index 78999b7..b3a71c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ /* eslint-disable ts/ban-types */ import XLSX, { type WorkSheet, utils } from 'xlsx-js-style' -import type { CellValue, Column, ColumnGroup, ExcelBuildOutput, ExcelBuildParams, ExcelSchema, FormattersMap, GenericObject, NestedPaths, Not, SchemaColumnKeys, SheetConfig, SheetParams, SheetTable, SheetTableBuilder, TOutputType, TransformersMap } from './types' +import type { CellValue, Column, ColumnGroup, ExcelBuildOutput, ExcelBuildParams, ExcelSchema, FormatterPreset, FormattersMap, GenericObject, NestedPaths, Not, SchemaColumnKeys, SheetConfig, SheetParams, SheetTable, SheetTableBuilder, TOutputType, TransformersMap } from './types' import { SheetCacheManager, applyGroupBorders, buildSheetConfig, createCell, getColumnHeaderStyle, getColumnSeparatorIndexes, getWorksheetColumnWidths, tableHasSummary } from './utils' export type * from './types' @@ -26,7 +26,10 @@ export class ExcelSchemaBuilder< return this as unknown as ExcelSchemaBuilder } - withFormatters(formatters: Formatters): ExcelSchemaBuilder { + withFormatters< + Formatters extends FormattersMap, + >(formatters: Formatters, + ): ExcelSchemaBuilder { this.formatters = formatters as FormatMap & Formatters return this as unknown as ExcelSchemaBuilder } @@ -34,9 +37,10 @@ export class ExcelSchemaBuilder< public column< K extends string, FieldValue extends CellKeyPaths | ((data: T) => CellValue), + Preset extends FormatterPreset[keyof FormatMap], >( columnKey: Not, - column: Omit, 'columnKey' | 'type'>, + column: Omit, 'columnKey' | 'type'>, ): ExcelSchemaBuilder { if (this.columns.some(c => c.columnKey === columnKey)) throw new Error(`Column with key '${columnKey}' already exists.`) diff --git a/src/types.ts b/src/types.ts index d7bd803..d0622b1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -57,8 +57,18 @@ export interface TransformersMap { [key: string]: ValueTransformer } +export type FormatterFunction = (params: any) => string + export interface FormattersMap { - [key: string]: string + [key: string]: FormatterFunction | string +} + +export type FormatterPreset = { + [Key in keyof T]: ({ preset: Key } & (T[Key] extends infer P + ? P extends (params: any) => any + ? { params: Parameters

[0] } + : {} + : {})) } export type NonNullableDeep = T extends null | undefined ? never : T @@ -81,13 +91,14 @@ export type Column< ColKey extends string, TransformMap extends TransformersMap, FormatMap extends FormattersMap, + Preset extends FormatterPreset[keyof FormatMap] = never, > = { type: 'column' label?: string columnKey: ColKey key: FieldValue default?: CellValue - format?: string | { preset: keyof FormatMap } | ((rowData: T, rowIndex: number, subRowIndex: number) => string | { preset: keyof FormatMap }) + format?: Preset | string | ((rowData: T, rowIndex: number, subRowIndex: number) => string | Preset) cellStyle?: CellStyle | ((rowData: T, rowIndex: number, subRowIndex: number) => CellStyle) headerStyle?: CellStyle summary?: Array<{ diff --git a/src/utils.ts b/src/utils.ts index ac0e50d..c370019 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import { utils } from 'xlsx-js-style' import type XLSX from 'xlsx-js-style' import type { CellStyle, ExcelDataType, WorkSheet } from 'xlsx-js-style' import { deepmerge } from 'deepmerge-ts' -import type { BaseCellValue, CellValue, Column, FormattersMap, GenericObject, SheetConfig, ValueTransformer } from './types' +import type { BaseCellValue, CellValue, Column, FormatterPreset, FormattersMap, GenericObject, SheetConfig, ValueTransformer } from './types' import { THICK_BORDER_STYLE, THIN_BORDER_STYLE } from './const' export function getPropertyFromPath(obj: GenericObject, path: string) { @@ -335,7 +335,7 @@ export function createCell(params: { data?: GenericObject value?: BaseCellValue style?: CellStyle | ((rowData: any, rowIndex: number, subRowIndex: number) => CellStyle) - format?: string | { preset: string | number | symbol } | ((rowData: any, rowIndex: number, subRowIndex: number) => string | { preset: string | number | symbol }) + format?: string | FormatterPreset | ((rowData: any, rowIndex: number, subRowIndex: number) => string | FormatterPreset) extraStyle?: CellStyle bordered?: boolean rowIndex?: number @@ -352,8 +352,13 @@ export function createCell(params: { const format = typeof rawFormat === 'string' ? rawFormat : rawFormat?.preset - ? params.formatPresets[rawFormat.preset as string] - : '' + ? params.formatPresets[rawFormat.preset as unknown as string] + ? typeof params.formatPresets[rawFormat.preset as unknown as string] === 'function' + ? (params.formatPresets[rawFormat.preset as unknown as string] as Function)(rawFormat.params) + : params.formatPresets[rawFormat.preset as unknown as string] + : '' + : rawFormat + return { v: params.value === null ? '' : params.value, t: getCellDataType(params.value), diff --git a/test/play.test.ts b/test/play.test.ts index b0d0653..3ed0675 100644 --- a/test/play.test.ts +++ b/test/play.test.ts @@ -1,17 +1,18 @@ import fs from 'node:fs' import { describe, it } from 'vitest' import { faker } from '@faker-js/faker' +import type { FormattersMap } from '../src' import { ExcelBuilder, ExcelSchemaBuilder } from '../src' describe('should generate the play excel file', () => { it('exported', () => { interface User { id: string, name: string, birthDate: Date, balance: number } - // Group definition within the schema const schema = ExcelSchemaBuilder.create() .withFormatters({ date: 'd mmm yyyy', - currency: '$#,##0.00', + currency: (params: { currency: string }) => `${params.currency}#,##0.00`, + other: (params: { other: string }) => `${params.other}#,##0.00`, }) .column('id', { key: 'id' }) .column('name', { @@ -20,7 +21,9 @@ describe('should generate the play excel file', () => { headerStyle: { fill: { fgColor: { rgb: '00FF00' } } }, }) .column('birthDate', { key: 'birthDate', format: { preset: 'date' } }) - .column('balance', { key: 'balance', format: { preset: 'currency' } }) + .column('birthDate2', { key: 'birthDate', format: 'd mmm yyyy' }) + .column('balanceUsd', { key: 'balance', format: { preset: 'currency', params: { currency: '$' } } }) + .column('balanceEur', { key: 'balance', format: { preset: 'currency', params: { currency: '€' } } }) .build() const users: User[] = Array.from({ length: 100000 }, (_, i) => ({