Repository navigation
feat(xlsx): @gridsheet/xlsx — xlsx ⇄ GridSheet converter - #147
Merged
Merged
Conversation
New headless package converting xlsx <-> GridSheet without exceljs/SheetJS:
a TS-native OOXML reader/writer scoped to what CellType uses, with fflate for
zip. v0 covers values (string/number/boolean), formulas round-tripping as their
"=..." text, multiple sheets, and dates as ISO strings.
- fromXlsx(bytes) -> { [sheet]: { matrices } } (buildInitialCells input shape)
- toXlsx(sheets) -> Uint8Array (accepts a live sheet, cell matrix, or values)
- Jest: a1 / xml units + toXlsx<->fromXlsx round trip + engine-sheet integration
- Verified interop both ways against openpyxl (independent OOXML reader)
- Storybook IO/Xlsx demo (ships sample.xlsx; import/download) + e2e spec
- Wire root scripts, release publish job, and AGENTS.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the sample with an openpyxl-written workbook exercising features v0 does not support (merged cells, fonts/fills/borders, number formats, frozen panes, multiple sheets) to prove graceful degradation: styling/merges are ignored, values + formulas still import (merged range keeps its top-left value; a formatted date reads as its raw serial number). - src/__fixtures__/complex.xlsx + complex.spec.ts (Jest robustness) - Storybook sample.xlsx swapped for the complex one; description + e2e updated - README notes the graceful-degradation behavior Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the styled fixture with a `Summary` sheet whose formulas reference other sheets — `=SUM(Sales!D3:D5)`, `=Sales!D3`, and `=Sales!D6*'Meta Data'!B3` (a sheet name with a space, so quoting is exercised). Renames Meta -> "Meta Data". - fromXlsx preserves cross-sheet formula text verbatim (complex.spec.ts) - engine integration: importing the workbook into one shared registry and giving each sheet a unique id resolves the cross-sheet formulas (Summary B1=17, B2=4.5, B3~=2.601) (sheet-integration.spec.ts) - Storybook demo now renders every imported sheet in a shared book, so cross-sheet references re-evaluate live; e2e asserts Summary!B2 = 17 - add @types/node so the fixture-reading specs typecheck Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cross-sheet formulas are easier to inspect when each sheet shows its formula bar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse xl/styles.xml (fonts, solid fills, cellXfs + alignment) and map each
cell's style record to GridSheet's `style` (backgroundColor, color, fontWeight,
fontStyle, textDecoration) plus justifyContent/alignItems. Colors resolve from
rgb, the legacy indexed palette, and default theme colors with tint.
fromXlsx now returns { matrices, cells } per sheet; cells carries the imported
styles and buildInitialCells merges them with the values. Writing styles back
out is still TODO (toXlsx remains values + formulas).
- styles.ts + StyleTable; read.ts collects per-cell styles
- complex.spec.ts asserts title/header/zebra/alignment styles
- Storybook demo passes the imported cells so colors render; e2e asserts the
dark fill + white bold title paint in the browser
- README documents style import and the export gap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read explicit <cols customWidth> widths and <row customHeight> heights and map them (converted to pixels) onto the column-/row-header cells (ch(col)/rh(row)), so imported sheets keep their sizing. Sheet-wide col spans are skipped to avoid creating thousands of header cells. - read.ts parses cols/rows; complex fixture varies widths/heights - complex.spec.ts asserts the imported px sizes; e2e checks a wide vs narrow column - README updated Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A matrix-aligned, fixed-height GridSheet put the CSS `resize` handle on `.gs-main` while also capping it with `max-height: sheetHeight`. A box can't be dragged past its own max-height, so enlarging was silently blocked (shrinking still worked). Switching to a controlled `height` doesn't help — React overwrites it on every re-render, fighting the drag. Fix: for a user-resizable fixed-height box, leave the height uncontrolled. Seed the initial pixel height in a layout effect (capped at content height so short grids still shrink to content), drop the max-height cap, and let the ResizeObserver sync drags into sheetHeight without clamping a manual resize to the root height. - e2e/sheet-resize.spec.ts guards: no max-height cap, and an enlarged height sticks Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
toXlsx now writes what fromXlsx reads: cell styles (background, text color, bold/italic/underline, horizontal/vertical alignment) into a built styles.xml, and — for a live GridSheet sheet — column widths (<cols>) and row heights (<row ht>). Styles are interned so each distinct font/fill/alignment gets one xf; fills reserve the Excel none/gray125 indices. Completes the style/size round trip. - style_writer.ts: StyleSheetBuilder + CSS-color → ARGB - write.ts: per-cell `s`, <cols>/<row ht>, dynamic styles.xml - sheet-integration.spec.ts: styles + header sizes survive toXlsx → fromXlsx - verified the styled output opens in openpyxl (bold/fill/color/align intact) - README: styles and sizes are now both-directions Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ships under the npm 'next' dist-tag (the release workflow publishes any version containing '-' with --tag next), keeping it off 'latest' while it stabilizes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
New headless package
@gridsheet/xlsxthat converts between.xlsxand GridSheet — a TS-native OOXML reader/writer scoped to whatCellTyperepresents, usingfflatefor zip. It does not wrap exceljs/SheetJS (both effectively unmaintained); exceljs was used only as a reference for the OOXML shapes.API
Supported (both directions)
=...text, including cross-sheet references (=SUM(Sales!D3:D5), quoted names like'Meta Data'!B3).style: background, text color, bold, italic, underline, and horizontal/vertical alignment (justifyContent/alignItems).ch(col)/rh(row)), px↔xlsx units (sizes written when the input is a live sheet).Unsupported features (merged cells, number formats, borders, charts/images) are ignored gracefully on import — a styled, merged, multi-sheet workbook reads fine.
Tests / verification
toXlsx↔fromXlsxround trips (types, formulas, escaping, shared-string dedup, sparse), a hand-built workbook exercising inlineStr / cached formulas, a complex openpyxl-written fixture (styles, merges, sizes, cross-sheet), and engine-integration specs (cross-sheet re-evaluation, style/size round trip).IO/Xlsxdemo (ships a complexsample.xlsx; import a file / download) + Playwright e2e (e2e/xlsx.spec.ts) asserting imported styles render and cross-sheet formulas re-evaluate live.Wiring
Root scripts (
build:xlsx/jest:xlsx/typecheck:xlsxand the:allaggregates), a dependency-orderedupload-xlsxpublish job inrelease.yaml, and AGENTS.md package table / testing policy.Notes
@gridsheet/xlsxmust be registered as an npm OIDC trusted publisher before theupload-xlsxjob can publish it (the job is|| true, so it won't fail the release until then).master).🤖 Generated with Claude Code