@niicojs/excel 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -9,7 +9,12 @@ A TypeScript library for Excel/OpenXML manipulation with maximum format preserva
9
9
  - Full formula support (read/write/preserve)
10
10
  - Cell styles (fonts, fills, borders, alignment)
11
11
  - Merged cells
12
+ - Column widths and row heights
13
+ - Freeze panes
14
+ - Pivot tables with fluent API
12
15
  - Sheet operations (add, delete, rename, copy)
16
+ - Create sheets from arrays of objects (`addSheetFromData`)
17
+ - Convert sheets to JSON arrays (`toJson`)
13
18
  - Create new workbooks from scratch
14
19
  - TypeScript-first with full type definitions
15
20
 
@@ -28,6 +33,7 @@ import { Workbook } from '@niicojs/excel';
28
33
 
29
34
  // Create a new workbook
30
35
  const wb = Workbook.create();
36
+ wb.addSheet('Sheet1');
31
37
  const sheet = wb.sheet('Sheet1');
32
38
 
33
39
  // Write data
@@ -136,6 +142,47 @@ sheet.unmergeCells('A1:C1');
136
142
  console.log(sheet.mergedCells); // ['A1:C3', 'D5:E10', ...]
137
143
  ```
138
144
 
145
+ ## Column Widths and Row Heights
146
+
147
+ ```typescript
148
+ const sheet = wb.sheet(0);
149
+
150
+ // Set column width (by letter or 0-based index)
151
+ sheet.setColumnWidth('A', 20);
152
+ sheet.setColumnWidth(1, 15); // Column B
153
+
154
+ // Get column width
155
+ const width = sheet.getColumnWidth('A'); // 20 or undefined
156
+
157
+ // Set row height (0-based index)
158
+ sheet.setRowHeight(0, 30); // First row
159
+
160
+ // Get row height
161
+ const height = sheet.getRowHeight(0); // 30 or undefined
162
+ ```
163
+
164
+ ## Freeze Panes
165
+
166
+ ```typescript
167
+ const sheet = wb.sheet(0);
168
+
169
+ // Freeze first row (header)
170
+ sheet.freezePane(1, 0);
171
+
172
+ // Freeze first column
173
+ sheet.freezePane(0, 1);
174
+
175
+ // Freeze first row and first column
176
+ sheet.freezePane(1, 1);
177
+
178
+ // Unfreeze
179
+ sheet.freezePane(0, 0);
180
+
181
+ // Get current freeze pane configuration
182
+ const frozen = sheet.getFrozenPane();
183
+ // { row: 1, col: 0 } or null
184
+ ```
185
+
139
186
  ## Sheet Operations
140
187
 
141
188
  ```typescript
@@ -146,7 +193,7 @@ wb.addSheet('Data');
146
193
  wb.addSheet('Summary', 0); // Insert at index 0
147
194
 
148
195
  // Get sheet names
149
- console.log(wb.sheetNames); // ['Summary', 'Sheet1', 'Data']
196
+ console.log(wb.sheetNames); // ['Summary', 'Data']
150
197
 
151
198
  // Access sheets
152
199
  const sheet = wb.sheet('Data'); // By name
@@ -162,14 +209,191 @@ wb.copySheet('RawData', 'RawData_Backup');
162
209
  wb.deleteSheet('Summary');
163
210
  ```
164
211
 
212
+ ## Pivot Tables
213
+
214
+ Create pivot tables with a fluent API:
215
+
216
+ ```typescript
217
+ const wb = await Workbook.fromFile('sales-data.xlsx');
218
+
219
+ // Create a pivot table from source data
220
+ const pivot = wb.createPivotTable({
221
+ name: 'SalesPivot',
222
+ source: 'Data!A1:E100', // Source range with headers
223
+ target: 'Summary!A3', // Where to place the pivot table
224
+ refreshOnLoad: true, // Refresh when file opens (default: true)
225
+ });
226
+
227
+ // Configure fields using fluent API
228
+ pivot
229
+ .addRowField('Region') // Group by region
230
+ .addRowField('Product') // Then by product
231
+ .addColumnField('Year') // Columns by year
232
+ .addValueField('Sales', 'sum', 'Total Sales') // Sum of sales
233
+ .addValueField('Quantity', 'count', 'Order Count') // Count of orders
234
+ .addFilterField('Category'); // Page filter
235
+
236
+ // Sort and filter fields
237
+ pivot
238
+ .sortField('Region', 'asc') // Sort ascending
239
+ .filterField('Product', { include: ['Widget', 'Gadget'] }); // Include only these
240
+
241
+ await wb.toFile('report.xlsx');
242
+ ```
243
+
244
+ ### Pivot Table API
245
+
246
+ ```typescript
247
+ // Add fields to different areas
248
+ pivot.addRowField(fieldName: string): PivotTable
249
+ pivot.addColumnField(fieldName: string): PivotTable
250
+ pivot.addValueField(fieldName: string, aggregation?: AggregationType, displayName?: string): PivotTable
251
+ pivot.addFilterField(fieldName: string): PivotTable
252
+
253
+ // Aggregation types: 'sum' | 'count' | 'average' | 'min' | 'max'
254
+
255
+ // Sort a row or column field
256
+ pivot.sortField(fieldName: string, order: 'asc' | 'desc'): PivotTable
257
+
258
+ // Filter field values
259
+ pivot.filterField(fieldName: string, filter: { include?: string[] } | { exclude?: string[] }): PivotTable
260
+ ```
261
+
262
+ ## Creating Sheets from Data
263
+
264
+ Create sheets directly from arrays of objects with `addSheetFromData`:
265
+
266
+ ```typescript
267
+ const wb = Workbook.create();
268
+
269
+ // Simple usage - object keys become column headers
270
+ const employees = [
271
+ { name: 'Alice', age: 30, city: 'Paris' },
272
+ { name: 'Bob', age: 25, city: 'London' },
273
+ ];
274
+
275
+ wb.addSheetFromData({
276
+ name: 'Employees',
277
+ data: employees,
278
+ });
279
+
280
+ // Custom column configuration
281
+ wb.addSheetFromData({
282
+ name: 'Custom',
283
+ data: employees,
284
+ columns: [
285
+ { key: 'name', header: 'Full Name' },
286
+ { key: 'age', header: 'Age (years)' },
287
+ { key: 'city', header: 'Location', style: { bold: true } },
288
+ ],
289
+ });
290
+
291
+ // With formulas and styles using RichCellValue
292
+ const orderLines = [
293
+ { product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
294
+ { product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
295
+ ];
296
+
297
+ wb.addSheetFromData({
298
+ name: 'Orders',
299
+ data: orderLines,
300
+ });
301
+
302
+ // Other options
303
+ wb.addSheetFromData({
304
+ name: 'Options',
305
+ data: employees,
306
+ headerStyle: false, // Don't bold headers
307
+ startCell: 'B3', // Start at B3 instead of A1
308
+ });
309
+ ```
310
+
311
+ ## Converting Sheets to JSON
312
+
313
+ Convert sheet data back to arrays of objects with `toJson`:
314
+
315
+ ```typescript
316
+ const sheet = wb.sheet('Data');
317
+
318
+ // Using first row as headers
319
+ const data = sheet.toJson();
320
+ // [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
321
+
322
+ // Using custom field names (first row is data, not headers)
323
+ const data2 = sheet.toJson({
324
+ fields: ['name', 'age', 'city'],
325
+ });
326
+
327
+ // With TypeScript generics
328
+ interface Person {
329
+ name: string | null;
330
+ age: number | null;
331
+ }
332
+ const people = sheet.toJson<Person>();
333
+
334
+ // Starting from a specific position
335
+ const data3 = sheet.toJson({
336
+ startRow: 2, // Skip first 2 rows (0-based)
337
+ startCol: 1, // Start from column B
338
+ });
339
+
340
+ // Limiting the range
341
+ const data4 = sheet.toJson({
342
+ endRow: 10, // Stop at row 11 (0-based, inclusive)
343
+ endCol: 3, // Only read columns A-D
344
+ });
345
+
346
+ // Continue past empty rows
347
+ const data5 = sheet.toJson({
348
+ stopOnEmptyRow: false, // Default is true
349
+ });
350
+
351
+ // Control how dates are serialized
352
+ const data6 = sheet.toJson({
353
+ dateHandling: 'isoString', // 'jsDate' | 'excelSerial' | 'isoString'
354
+ });
355
+ ```
356
+
357
+ ### Roundtrip Example
358
+
359
+ ```typescript
360
+ // Create from objects
361
+ const originalData = [
362
+ { name: 'Alice', age: 30 },
363
+ { name: 'Bob', age: 25 },
364
+ ];
365
+
366
+ const sheet = wb.addSheetFromData({
367
+ name: 'People',
368
+ data: originalData,
369
+ });
370
+
371
+ // Read back as objects
372
+ const readData = sheet.toJson();
373
+ // readData equals originalData
374
+ ```
375
+
165
376
  ## Saving
166
377
 
167
378
  ```typescript
168
- // Save to file
169
- await wb.toFile('output.xlsx');
379
+ // Load from file
380
+ const wb = await Workbook.fromFile('template.xlsx');
170
381
 
171
- // Save to buffer (Uint8Array)
172
- const buffer = await wb.toBuffer();
382
+ // Or load from buffer
383
+ const buffer = await fetch('https://example.com/file.xlsx').then((r) => r.arrayBuffer());
384
+ const wb2 = await Workbook.fromBuffer(new Uint8Array(buffer));
385
+
386
+ // Read data
387
+ const sheet = wb.sheet('Sheet1');
388
+ console.log(sheet.cell('A1').value); // The cell value
389
+ console.log(sheet.cell('A1').formula); // The formula (if any)
390
+ console.log(sheet.cell('A1').type); // 'string' | 'number' | 'boolean' | 'date' | 'error' | 'empty'
391
+
392
+ // Check if a cell exists without creating it
393
+ const existingCell = sheet.getCellIfExists('A1');
394
+ if (existingCell) {
395
+ console.log(existingCell.value);
396
+ }
173
397
  ```
174
398
 
175
399
  ## Type Definitions
@@ -187,8 +411,152 @@ type CellType = 'number' | 'string' | 'boolean' | 'date' | 'error' | 'empty';
187
411
 
188
412
  // Border types
189
413
  type BorderType = 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
414
+
415
+ // Configuration for addSheetFromData
416
+ interface SheetFromDataConfig<T> {
417
+ name: string;
418
+ data: T[];
419
+ columns?: ColumnConfig<T>[];
420
+ headerStyle?: boolean; // Default: true
421
+ startCell?: string; // Default: 'A1'
422
+ }
423
+
424
+ interface ColumnConfig<T> {
425
+ key: keyof T;
426
+ header?: string;
427
+ style?: CellStyle;
428
+ }
429
+
430
+ // Rich cell value for formulas/styles in data
431
+ interface RichCellValue {
432
+ value?: CellValue;
433
+ formula?: string;
434
+ style?: CellStyle;
435
+ }
436
+
437
+ // Configuration for toJson
438
+ interface SheetToJsonConfig {
439
+ fields?: string[];
440
+ startRow?: number;
441
+ startCol?: number;
442
+ endRow?: number;
443
+ endCol?: number;
444
+ stopOnEmptyRow?: boolean; // Default: true
445
+ dateHandling?: 'jsDate' | 'excelSerial' | 'isoString'; // Default: 'jsDate'
446
+ }
447
+
448
+ // Pivot table configuration
449
+ interface PivotTableConfig {
450
+ name: string;
451
+ source: string; // e.g., "Sheet1!A1:D100"
452
+ target: string; // e.g., "Sheet2!A3"
453
+ refreshOnLoad?: boolean; // Default: true
454
+ }
455
+
456
+ type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
457
+ type PivotSortOrder = 'asc' | 'desc';
458
+ interface PivotFieldFilter {
459
+ include?: string[];
460
+ exclude?: string[];
461
+ }
462
+ ```
463
+
464
+ ## Address Utilities
465
+
466
+ Helper functions for working with Excel cell addresses:
467
+
468
+ ```typescript
469
+ import {
470
+ colToLetter,
471
+ letterToCol,
472
+ parseAddress,
473
+ toAddress,
474
+ parseRange,
475
+ toRange,
476
+ normalizeRange,
477
+ isInRange,
478
+ } from '@niicojs/excel';
479
+
480
+ // Convert column index to letter (0-based)
481
+ colToLetter(0); // 'A'
482
+ colToLetter(25); // 'Z'
483
+ colToLetter(26); // 'AA'
484
+
485
+ // Convert column letter to index (0-based)
486
+ letterToCol('A'); // 0
487
+ letterToCol('Z'); // 25
488
+ letterToCol('AA'); // 26
489
+
490
+ // Parse cell address to row/col
491
+ parseAddress('B3'); // { row: 2, col: 1 }
492
+ parseAddress('$A$1'); // { row: 0, col: 0 } (absolute refs supported)
493
+
494
+ // Convert row/col to address
495
+ toAddress(0, 0); // 'A1'
496
+ toAddress(9, 2); // 'C10'
497
+
498
+ // Parse range string
499
+ parseRange('A1:C10'); // { start: { row: 0, col: 0 }, end: { row: 9, col: 2 } }
500
+
501
+ // Convert range object to string
502
+ toRange({ start: { row: 0, col: 0 }, end: { row: 9, col: 2 } }); // 'A1:C10'
503
+
504
+ // Normalize range (ensure start is top-left, end is bottom-right)
505
+ normalizeRange({ start: { row: 5, col: 3 }, end: { row: 0, col: 0 } });
506
+ // { start: { row: 0, col: 0 }, end: { row: 5, col: 3 } }
507
+
508
+ // Check if address is within a range
509
+ isInRange({ row: 2, col: 1 }, { start: { row: 0, col: 0 }, end: { row: 5, col: 5 } }); // true
510
+ isInRange({ row: 10, col: 0 }, { start: { row: 0, col: 0 }, end: { row: 5, col: 5 } }); // false
190
511
  ```
191
512
 
513
+ ## Style Schema
514
+
515
+ Supported style properties (CellStyle):
516
+
517
+ ```typescript
518
+ interface CellStyle {
519
+ bold?: boolean;
520
+ italic?: boolean;
521
+ underline?: boolean | 'single' | 'double';
522
+ strike?: boolean;
523
+ fontSize?: number;
524
+ fontName?: string;
525
+ fontColor?: string; // Hex (RGB/RRGGBB/AARRGGBB)
526
+ fill?: string; // Hex (RGB/RRGGBB/AARRGGBB)
527
+ border?: {
528
+ top?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
529
+ bottom?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
530
+ left?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
531
+ right?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
532
+ };
533
+ alignment?: {
534
+ horizontal?: 'left' | 'center' | 'right' | 'justify';
535
+ vertical?: 'top' | 'middle' | 'bottom';
536
+ wrapText?: boolean;
537
+ textRotation?: number;
538
+ };
539
+ numberFormat?: string; // Excel format code
540
+ }
541
+ ```
542
+
543
+ ## Performance and Large Files
544
+
545
+ Tips for large sheets and high-volume writes:
546
+
547
+ - Prefer `addSheetFromData` for bulk writes when possible.
548
+ - Use `range.getValues({ createMissing: false })` to avoid creating empty cells during reads.
549
+ - Keep shared strings small: prefer numbers/booleans where applicable.
550
+ - Avoid frequent `toBuffer()` calls in loops; batch writes and serialize once.
551
+
552
+ ## Limitations
553
+
554
+ The library focuses on preserving existing structure and editing common parts of workbooks.
555
+
556
+ - Chart editing is not supported (charts are preserved only).
557
+ - Conditional formatting and data validation are preserved but not editable yet.
558
+ - Some advanced Excel features (sparklines, slicers, macros) are preserved only.
559
+
192
560
  ## Format Preservation
193
561
 
194
562
  When modifying existing Excel files, this library preserves: