From 0316a9b61d37198c8e45dce84291bbba032de11e Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Thu, 11 Jun 2026 19:48:24 +0000 Subject: [PATCH 01/11] Model workbook-defined (custom) table styles Add Workbook.tableStyles: custom table/pivot style definitions keyed by style name, following the namedStyles precedent. Each definition carries pivot/table applicability flags and a list of per-region elements with differential Style formatting (the inline-style approach of PivotFormat.style), so no JSF-level dxf table is needed. New types: - TableStyleDefinition: one named custom style - TableStyleElement: formatting of one table region (+ stripe size) - TableStyleElementType: the 28 region kinds (ST_TableStyleType) --- src/types/tables/TableStyleDefinition.ts | 38 +++++++++++++ src/types/tables/TableStyleElement.ts | 28 ++++++++++ src/types/tables/TableStyleElementType.ts | 67 +++++++++++++++++++++++ src/types/tables/index.ts | 3 + src/types/workbooks/Workbook.ts | 10 +++- 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/types/tables/TableStyleDefinition.ts create mode 100644 src/types/tables/TableStyleElement.ts create mode 100644 src/types/tables/TableStyleElementType.ts diff --git a/src/types/tables/TableStyleDefinition.ts b/src/types/tables/TableStyleDefinition.ts new file mode 100644 index 0000000..96cbb0d --- /dev/null +++ b/src/types/tables/TableStyleDefinition.ts @@ -0,0 +1,38 @@ +import type { TableStyleElement } from './TableStyleElement.ts'; + +/** + * A workbook-defined (custom) style for tables and/or pivot tables. + * + * Definitions live in {@link Workbook.tableStyles} and are referenced by name from + * {@link TableStyle.name} and {@link PivotTableStyle.name}, the same way built-in style + * names are referenced. In OOXML these correspond to `` elements in the + * styles part, with each element's formatting held in a `` record. + * + * A definition only describes the regions it lists; regions without an element get no + * formatting from the style. Regions overlap (e.g. a header-row cell is also part of the + * whole table), in which case the more specific region's formatting wins, property by + * property. + * + * @group Workbooks + */ +export type TableStyleDefinition = { + /** + * The name of the style, as referenced from tables and pivot tables. Must match the + * definition's key in {@link Workbook.tableStyles}. + */ + name: string; + /** + * Whether this style can be applied to pivot tables. + * + * @default true + */ + pivot?: boolean; + /** + * Whether this style can be applied to (non-pivot) tables. + * + * @default true + */ + table?: boolean; + /** The formatting of each table region that this style defines. */ + elements?: TableStyleElement[]; +}; diff --git a/src/types/tables/TableStyleElement.ts b/src/types/tables/TableStyleElement.ts new file mode 100644 index 0000000..7212fac --- /dev/null +++ b/src/types/tables/TableStyleElement.ts @@ -0,0 +1,28 @@ +import type { integer } from '../integer.ts'; +import type { Style } from '../styles/index.ts'; +import type { TableStyleElementType } from './TableStyleElementType.ts'; + +/** + * The formatting of one region of a table or pivot table, within a + * {@link TableStyleDefinition}. + * + * @group Workbooks + */ +export type TableStyleElement = { + /** The table region this element's formatting applies to. */ + type: TableStyleElementType; + /** + * The number of consecutive rows or columns in each stripe band. Only meaningful for the + * four stripe element types. + * + * @default 1 + */ + size?: integer; + /** + * The formatting to overlay on the region's cells. Only the properties present apply; + * everything else on a cell is left as-is (differential-format semantics). An element + * without a style has no visual effect (a stripe element may still carry a meaningful + * {@link size}). + */ + style?: Style; +}; diff --git a/src/types/tables/TableStyleElementType.ts b/src/types/tables/TableStyleElementType.ts new file mode 100644 index 0000000..08c3936 --- /dev/null +++ b/src/types/tables/TableStyleElementType.ts @@ -0,0 +1,67 @@ +/** + * The region of a table or pivot table that a {@link TableStyleElement} applies to. + * + * The values correspond to OOXML's `ST_TableStyleType`. Some regions only occur in tables + * (e.g. `totalRow`), others only in pivot tables (e.g. `pageFieldLabels`, the subheading and + * subtotal regions); a style definition usable for both kinds of table may include elements + * for both. + * + * @group Workbooks + */ +export type TableStyleElementType = + /** The entire table. */ + 'wholeTable' | + /** The header row. */ + 'headerRow' | + /** The totals row. */ + 'totalRow' | + /** The first column. */ + 'firstColumn' | + /** The last column. */ + 'lastColumn' | + /** Odd row stripes (the first, third, ... band of rows). */ + 'firstRowStripe' | + /** Even row stripes (the second, fourth, ... band of rows). */ + 'secondRowStripe' | + /** Odd column stripes (the first, third, ... band of columns). */ + 'firstColumnStripe' | + /** Even column stripes (the second, fourth, ... band of columns). */ + 'secondColumnStripe' | + /** The first cell of the header row. */ + 'firstHeaderCell' | + /** The last cell of the header row. */ + 'lastHeaderCell' | + /** The first cell of the totals row. */ + 'firstTotalCell' | + /** The last cell of the totals row. */ + 'lastTotalCell' | + /** A pivot table's first-level subtotal columns. */ + 'firstSubtotalColumn' | + /** A pivot table's second-level subtotal columns. */ + 'secondSubtotalColumn' | + /** A pivot table's third-level subtotal columns. */ + 'thirdSubtotalColumn' | + /** A pivot table's first-level subtotal rows. */ + 'firstSubtotalRow' | + /** A pivot table's second-level subtotal rows. */ + 'secondSubtotalRow' | + /** A pivot table's third-level subtotal rows. */ + 'thirdSubtotalRow' | + /** A pivot table's blank rows. */ + 'blankRow' | + /** A pivot table's first-level column subheadings. */ + 'firstColumnSubheading' | + /** A pivot table's second-level column subheadings. */ + 'secondColumnSubheading' | + /** A pivot table's third-level column subheadings. */ + 'thirdColumnSubheading' | + /** A pivot table's first-level row subheadings. */ + 'firstRowSubheading' | + /** A pivot table's second-level row subheadings. */ + 'secondRowSubheading' | + /** A pivot table's third-level row subheadings. */ + 'thirdRowSubheading' | + /** A pivot table's page field label cells (the filter-field name cells). */ + 'pageFieldLabels' | + /** A pivot table's page field value cells (the filter-field selection cells). */ + 'pageFieldValues'; diff --git a/src/types/tables/index.ts b/src/types/tables/index.ts index f09df54..d32c703 100644 --- a/src/types/tables/index.ts +++ b/src/types/tables/index.ts @@ -2,4 +2,7 @@ export type * from './Table.ts'; export type * from './TableColumn.ts'; export type * from './TableColumnDataType.ts'; export type * from './TableStyle.ts'; +export type * from './TableStyleDefinition.ts'; +export type * from './TableStyleElement.ts'; +export type * from './TableStyleElementType.ts'; export type * from './TableStyleName.ts'; diff --git a/src/types/workbooks/Workbook.ts b/src/types/workbooks/Workbook.ts index df81daa..250d512 100644 --- a/src/types/workbooks/Workbook.ts +++ b/src/types/workbooks/Workbook.ts @@ -3,7 +3,7 @@ import type { DefinedName } from '../DefinedName.ts'; import type { External } from '../External.ts'; import type { NamedStyle } from '../styles/index.ts'; import type { Style } from '../styles/index.ts'; -import type { Table } from '../tables/index.ts'; +import type { Table, TableStyleDefinition } from '../tables/index.ts'; import type { Theme } from '../themes/index.ts'; import type { PivotTable } from '../pivotTables/index.ts'; import type { Worksheet } from '../worksheets/index.ts'; @@ -33,6 +33,14 @@ export type Workbook = { styles?: Style[]; /** Named cell style definitions (e.g. "Normal", "Heading 1"), keyed by style name. */ namedStyles?: Record; + /** + * Workbook-defined (custom) table and pivot table style definitions, keyed by style name. + * + * Tables and pivot tables reference these definitions by name, through + * {@link TableStyle.name} and {@link PivotTableStyle.name}, the same way they reference + * the built-in style names. + */ + tableStyles?: Record; /** External cells referenced by the workbook. An external cell is a cell in another workbook. */ externals?: External[]; /** From 252f345236aed6ec2c185d948c232c65e05d56aa Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Thu, 11 Jun 2026 19:48:24 +0000 Subject: [PATCH 02/11] Accept custom style names in TableStyle/PivotTableStyle Widen the name fields to PivotTableStyleName | (string & {}) (and the table equivalent) so workbook-defined style names are representable. The (string & {}) intersection keeps editor autocomplete for the built-in names while admitting any string. The closed unions remain exported unchanged, still meaning "a built-in style name". Names that are neither built-in nor defined in Workbook.tableStyles are documented to render unstyled, matching an omitted name. --- src/types/pivotTables/PivotTableStyle.ts | 9 ++++++--- src/types/tables/TableStyle.ts | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/types/pivotTables/PivotTableStyle.ts b/src/types/pivotTables/PivotTableStyle.ts index 5593618..77449b6 100644 --- a/src/types/pivotTables/PivotTableStyle.ts +++ b/src/types/pivotTables/PivotTableStyle.ts @@ -7,14 +7,17 @@ import type { PivotTableStyleName } from './PivotTableStyleName.ts'; */ export type PivotTableStyle = { /** - * The name of the pivot table style (e.g. `"PivotStyleMedium9"`). + * The name of the pivot table style: either one of the built-in style names + * (e.g. `"PivotStyleMedium9"`, see {@link PivotTableStyleName}) or the name of a + * workbook-defined style (see {@link Workbook.tableStyles}). * * If the value is null or omitted the table should not be rendered with any special styling (note - * that this only applies if the style object itself is present). + * that this only applies if the style object itself is present). A name that is neither a + * built-in style name nor defined in the workbook should be treated the same way. * * @default null */ - name?: PivotTableStyleName | null; + name?: PivotTableStyleName | (string & {}) | null; /** * Whether row header formatting should be applied. * diff --git a/src/types/tables/TableStyle.ts b/src/types/tables/TableStyle.ts index 60f3b8a..af1806e 100644 --- a/src/types/tables/TableStyle.ts +++ b/src/types/tables/TableStyle.ts @@ -8,13 +8,16 @@ import type { TableStyleName } from './TableStyleName.ts'; */ export type TableStyle = { /** - * The name of the table style to use with this table. + * The name of the table style to use with this table: either one of the built-in style names + * ({@link TableStyleName}) or the name of a workbook-defined style (see + * {@link Workbook.tableStyles}). * * If the value is null or omitted the table should not be rendered with any special styling (note - * that this only applies if the style object itself is present). + * that this only applies if the style object itself is present). A name that is neither a + * built-in style name nor defined in the workbook should be treated the same way. * @default null */ - name?: TableStyleName | null; + name?: TableStyleName | (string & {}) | null; /** * Whether row stripe formatting should be applied. * From 29921e8d1296e36e4f4f824ddc15f181881fe036 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 10:40:17 +0000 Subject: [PATCH 03/11] Extract LooseAutocomplete helper for widened style name unions Replace the inline `Name | (string & {})` idiom on the table and pivot table style `name` properties with a named, documented utility type: LooseAutocomplete = T | (string & {}) It accepts any string while preserving editor autocomplete for the known literals in T; the `& {}` on the string arm prevents TypeScript from collapsing the union to plain `string` (microsoft/TypeScript#29729). Applied to: - TableStyle.name?: LooseAutocomplete | null - PivotTableStyle.name?: LooseAutocomplete | null Behaviourally identical to the inline form; this is a clarity rename. --- src/types/LooseAutocomplete.ts | 15 +++++++++++++++ src/types/index.ts | 1 + src/types/pivotTables/PivotTableStyle.ts | 3 ++- src/types/tables/TableStyle.ts | 3 ++- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/types/LooseAutocomplete.ts diff --git a/src/types/LooseAutocomplete.ts b/src/types/LooseAutocomplete.ts new file mode 100644 index 0000000..d41930f --- /dev/null +++ b/src/types/LooseAutocomplete.ts @@ -0,0 +1,15 @@ +/** + * A string union that also accepts any other string, while still offering the literals + * in `T` as editor autocomplete suggestions. + * + * Useful where a property has a set of well-known string values but may also hold + * arbitrary custom values (e.g. a style name that is usually a built-in name but may be + * a workbook-defined one). + * + * The `& {}` on the `string` arm is what makes this work: without it TypeScript collapses + * `T | string` to plain `string` and the literal suggestions are lost. See + * {@link https://github.com/microsoft/TypeScript/issues/29729 | microsoft/TypeScript#29729}. + * + * @group Workbooks + */ +export type LooseAutocomplete = T | (string & {}); diff --git a/src/types/index.ts b/src/types/index.ts index 50c1be0..9c86564 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,6 +11,7 @@ export type * from './ExternalDefinedName.ts'; export type * from './ExternalWorksheet.ts'; export type * from './GridSize.ts'; export type * from './integer.ts'; +export type * from './LooseAutocomplete.ts'; export type * from './Note.ts'; export type * from './PixelValue.ts'; export type * from './colors/index.ts'; diff --git a/src/types/pivotTables/PivotTableStyle.ts b/src/types/pivotTables/PivotTableStyle.ts index 77449b6..c705455 100644 --- a/src/types/pivotTables/PivotTableStyle.ts +++ b/src/types/pivotTables/PivotTableStyle.ts @@ -1,3 +1,4 @@ +import type { LooseAutocomplete } from '../LooseAutocomplete.ts'; import type { PivotTableStyleName } from './PivotTableStyleName.ts'; /** @@ -17,7 +18,7 @@ export type PivotTableStyle = { * * @default null */ - name?: PivotTableStyleName | (string & {}) | null; + name?: LooseAutocomplete | null; /** * Whether row header formatting should be applied. * diff --git a/src/types/tables/TableStyle.ts b/src/types/tables/TableStyle.ts index af1806e..405b2ec 100644 --- a/src/types/tables/TableStyle.ts +++ b/src/types/tables/TableStyle.ts @@ -1,3 +1,4 @@ +import type { LooseAutocomplete } from '../LooseAutocomplete.ts'; import type { TableStyleName } from './TableStyleName.ts'; /** @@ -17,7 +18,7 @@ export type TableStyle = { * built-in style name nor defined in the workbook should be treated the same way. * @default null */ - name?: TableStyleName | (string & {}) | null; + name?: LooseAutocomplete | null; /** * Whether row stripe formatting should be applied. * From a266fe5e8af88d3e679bf37a2e15bcba29e08cf6 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 15:30:04 +0000 Subject: [PATCH 04/11] Revert LooseAutocomplete; use plain `| string` for custom style names Per review: drop the LooseAutocomplete helper (a code-editor-autocomplete convenience to be discussed in its own PR, if at all) and widen the style name fields with plain `string`: - TableStyle.name?: TableStyleName | string | null - PivotTableStyle.name?: PivotTableStyleName | string | null Delete src/types/LooseAutocomplete.ts, its index export, and the now unused imports. --- src/types/LooseAutocomplete.ts | 15 --------------- src/types/index.ts | 1 - src/types/pivotTables/PivotTableStyle.ts | 3 +-- src/types/tables/TableStyle.ts | 3 +-- 4 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 src/types/LooseAutocomplete.ts diff --git a/src/types/LooseAutocomplete.ts b/src/types/LooseAutocomplete.ts deleted file mode 100644 index d41930f..0000000 --- a/src/types/LooseAutocomplete.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * A string union that also accepts any other string, while still offering the literals - * in `T` as editor autocomplete suggestions. - * - * Useful where a property has a set of well-known string values but may also hold - * arbitrary custom values (e.g. a style name that is usually a built-in name but may be - * a workbook-defined one). - * - * The `& {}` on the `string` arm is what makes this work: without it TypeScript collapses - * `T | string` to plain `string` and the literal suggestions are lost. See - * {@link https://github.com/microsoft/TypeScript/issues/29729 | microsoft/TypeScript#29729}. - * - * @group Workbooks - */ -export type LooseAutocomplete = T | (string & {}); diff --git a/src/types/index.ts b/src/types/index.ts index 9c86564..50c1be0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,7 +11,6 @@ export type * from './ExternalDefinedName.ts'; export type * from './ExternalWorksheet.ts'; export type * from './GridSize.ts'; export type * from './integer.ts'; -export type * from './LooseAutocomplete.ts'; export type * from './Note.ts'; export type * from './PixelValue.ts'; export type * from './colors/index.ts'; diff --git a/src/types/pivotTables/PivotTableStyle.ts b/src/types/pivotTables/PivotTableStyle.ts index c705455..5c115fd 100644 --- a/src/types/pivotTables/PivotTableStyle.ts +++ b/src/types/pivotTables/PivotTableStyle.ts @@ -1,4 +1,3 @@ -import type { LooseAutocomplete } from '../LooseAutocomplete.ts'; import type { PivotTableStyleName } from './PivotTableStyleName.ts'; /** @@ -18,7 +17,7 @@ export type PivotTableStyle = { * * @default null */ - name?: LooseAutocomplete | null; + name?: PivotTableStyleName | string | null; /** * Whether row header formatting should be applied. * diff --git a/src/types/tables/TableStyle.ts b/src/types/tables/TableStyle.ts index 405b2ec..e2d1270 100644 --- a/src/types/tables/TableStyle.ts +++ b/src/types/tables/TableStyle.ts @@ -1,4 +1,3 @@ -import type { LooseAutocomplete } from '../LooseAutocomplete.ts'; import type { TableStyleName } from './TableStyleName.ts'; /** @@ -18,7 +17,7 @@ export type TableStyle = { * built-in style name nor defined in the workbook should be treated the same way. * @default null */ - name?: LooseAutocomplete | null; + name?: TableStyleName | string | null; /** * Whether row stripe formatting should be applied. * From 4bf2b535af0c1832c208c99b5fcd7cd7aac93b7f Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 15:30:17 +0000 Subject: [PATCH 05/11] Model table-style applicability as an enum, not two booleans Per review: replace the `pivot?`/`table?` boolean pair (which allows the nonsensical "applies to neither" combination) with a single enum: table?: 'table' | 'pivot' | 'all' // @default "all" ... and drop the TableStyleDefinition docstring's OOXML sentence. --- src/types/tables/TableStyleDefinition.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/types/tables/TableStyleDefinition.ts b/src/types/tables/TableStyleDefinition.ts index 96cbb0d..d991f20 100644 --- a/src/types/tables/TableStyleDefinition.ts +++ b/src/types/tables/TableStyleDefinition.ts @@ -5,8 +5,7 @@ import type { TableStyleElement } from './TableStyleElement.ts'; * * Definitions live in {@link Workbook.tableStyles} and are referenced by name from * {@link TableStyle.name} and {@link PivotTableStyle.name}, the same way built-in style - * names are referenced. In OOXML these correspond to `` elements in the - * styles part, with each element's formatting held in a `` record. + * names are referenced. * * A definition only describes the regions it lists; regions without an element get no * formatting from the style. Regions overlap (e.g. a header-row cell is also part of the @@ -22,17 +21,11 @@ export type TableStyleDefinition = { */ name: string; /** - * Whether this style can be applied to pivot tables. + * Whether this style may be applied to pivot tables, regular tables, or both. * - * @default true + * @default "all" */ - pivot?: boolean; - /** - * Whether this style can be applied to (non-pivot) tables. - * - * @default true - */ - table?: boolean; + table?: 'table' | 'pivot' | 'all'; /** The formatting of each table region that this style defines. */ elements?: TableStyleElement[]; }; From 57ef8aa5237503d44a79c8dad185ab8e9977ac1a Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 15:30:17 +0000 Subject: [PATCH 06/11] Document TableStyleElementType regions as a JSDoc table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: per-member comments in a union type don't render in the generated docs, so move them into a markdown table in the type's JSDoc (matching the CellValueType / TextVerticalType pattern). Add a Kind column indicating whether each region applies to tables, pivot tables, or both. Also apply the review's reworded "Some regions only occur in tables…" intro. --- src/types/tables/TableStyleElementType.ts | 68 ++++++++++++----------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/types/tables/TableStyleElementType.ts b/src/types/tables/TableStyleElementType.ts index 08c3936..5269a45 100644 --- a/src/types/tables/TableStyleElementType.ts +++ b/src/types/tables/TableStyleElementType.ts @@ -1,67 +1,71 @@ /** * The region of a table or pivot table that a {@link TableStyleElement} applies to. * - * The values correspond to OOXML's `ST_TableStyleType`. Some regions only occur in tables - * (e.g. `totalRow`), others only in pivot tables (e.g. `pageFieldLabels`, the subheading and - * subtotal regions); a style definition usable for both kinds of table may include elements - * for both. + * Some regions only occur in tables (e.g. `totalRow`), others only in pivot tables (e.g. + * `pageFieldLabels`, the subheading and subtotal regions); a style definition usable for + * both kinds of table may include elements for both. + * + * The `Kind` column below indicates which kind of table each region applies to. + * + * | Value | Kind | Region + * |--------------------------|-------|----------------------------------------------------------- + * | `wholeTable` | both | The entire table. + * | `headerRow` | both | The header row. + * | `totalRow` | table | The totals row. + * | `firstColumn` | both | The first column. + * | `lastColumn` | both | The last column. + * | `firstRowStripe` | both | Odd row stripes (the first, third, … band of rows). + * | `secondRowStripe` | both | Even row stripes (the second, fourth, … band of rows). + * | `firstColumnStripe` | both | Odd column stripes (the first, third, … band of columns). + * | `secondColumnStripe` | both | Even column stripes (the second, fourth, … band of columns). + * | `firstHeaderCell` | table | The first cell of the header row. + * | `lastHeaderCell` | table | The last cell of the header row. + * | `firstTotalCell` | table | The first cell of the totals row. + * | `lastTotalCell` | table | The last cell of the totals row. + * | `firstSubtotalColumn` | pivot | First-level subtotal columns. + * | `secondSubtotalColumn` | pivot | Second-level subtotal columns. + * | `thirdSubtotalColumn` | pivot | Third-level subtotal columns. + * | `firstSubtotalRow` | pivot | First-level subtotal rows. + * | `secondSubtotalRow` | pivot | Second-level subtotal rows. + * | `thirdSubtotalRow` | pivot | Third-level subtotal rows. + * | `blankRow` | pivot | Blank rows. + * | `firstColumnSubheading` | pivot | First-level column subheadings. + * | `secondColumnSubheading` | pivot | Second-level column subheadings. + * | `thirdColumnSubheading` | pivot | Third-level column subheadings. + * | `firstRowSubheading` | pivot | First-level row subheadings. + * | `secondRowSubheading` | pivot | Second-level row subheadings. + * | `thirdRowSubheading` | pivot | Third-level row subheadings. + * | `pageFieldLabels` | pivot | Page field label cells (the filter-field name cells). + * | `pageFieldValues` | pivot | Page field value cells (the filter-field selection cells). * * @group Workbooks */ export type TableStyleElementType = - /** The entire table. */ 'wholeTable' | - /** The header row. */ 'headerRow' | - /** The totals row. */ 'totalRow' | - /** The first column. */ 'firstColumn' | - /** The last column. */ 'lastColumn' | - /** Odd row stripes (the first, third, ... band of rows). */ 'firstRowStripe' | - /** Even row stripes (the second, fourth, ... band of rows). */ 'secondRowStripe' | - /** Odd column stripes (the first, third, ... band of columns). */ 'firstColumnStripe' | - /** Even column stripes (the second, fourth, ... band of columns). */ 'secondColumnStripe' | - /** The first cell of the header row. */ 'firstHeaderCell' | - /** The last cell of the header row. */ 'lastHeaderCell' | - /** The first cell of the totals row. */ 'firstTotalCell' | - /** The last cell of the totals row. */ 'lastTotalCell' | - /** A pivot table's first-level subtotal columns. */ 'firstSubtotalColumn' | - /** A pivot table's second-level subtotal columns. */ 'secondSubtotalColumn' | - /** A pivot table's third-level subtotal columns. */ 'thirdSubtotalColumn' | - /** A pivot table's first-level subtotal rows. */ 'firstSubtotalRow' | - /** A pivot table's second-level subtotal rows. */ 'secondSubtotalRow' | - /** A pivot table's third-level subtotal rows. */ 'thirdSubtotalRow' | - /** A pivot table's blank rows. */ 'blankRow' | - /** A pivot table's first-level column subheadings. */ 'firstColumnSubheading' | - /** A pivot table's second-level column subheadings. */ 'secondColumnSubheading' | - /** A pivot table's third-level column subheadings. */ 'thirdColumnSubheading' | - /** A pivot table's first-level row subheadings. */ 'firstRowSubheading' | - /** A pivot table's second-level row subheadings. */ 'secondRowSubheading' | - /** A pivot table's third-level row subheadings. */ 'thirdRowSubheading' | - /** A pivot table's page field label cells (the filter-field name cells). */ 'pageFieldLabels' | - /** A pivot table's page field value cells (the filter-field selection cells). */ 'pageFieldValues'; From ec190b9b08124828b7061f0d139bd60de59d2de2 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 15:51:37 +0000 Subject: [PATCH 07/11] Add shared differential-style list; index PivotFormat by dxfId Introduce a workbook-level list of differential styles (dxf records) that formatting-overlay features reference by index, instead of each feature inlining a full Style. This is the differential-overlay analog of Workbook.styles: where Cell.s indexes a complete cell style, an overlay's dxfId indexes a partial override. The list element type reuses the existing Style type, which is already all-optional; what differs is interpretation (absent property means "leave unchanged", not "use the default") and the list it lives in, not the structure. A separate DiffStyle type would duplicate ~25 identical properties for no structural gain, mirroring OOXML where reuses the same children as . - Workbook.diffStyles?: Style[] (name provisional) - PivotFormat: replace inline `style?: Style` with `dxfId?: integer` indexing into Workbook.diffStyles. BREAKING: this changes the shipped 2.5.0 PivotFormat.style (inline Style) to PivotFormat.dxfId (index). Done intentionally to avoid baking the inline-Style "legacy" deeper before more features depend on it. --- src/types/pivotTables/PivotFormat.ts | 14 ++++++++------ src/types/workbooks/Workbook.ts | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/types/pivotTables/PivotFormat.ts b/src/types/pivotTables/PivotFormat.ts index 182e511..b741d51 100644 --- a/src/types/pivotTables/PivotFormat.ts +++ b/src/types/pivotTables/PivotFormat.ts @@ -1,4 +1,4 @@ -import type { Style } from '../styles/index.ts'; +import type { integer } from '../integer.ts'; import type { PivotArea } from './PivotArea.ts'; /** @@ -8,7 +8,7 @@ import type { PivotArea } from './PivotArea.ts'; * format targets with an action indicating whether formatting is applied or blanked. * * Formats are the durable representation of user formatting on pivot output: a consumer - * re-resolves each format's area against the current layout and overlays its {@link style} on + * re-resolves each format's area against the current layout and overlays its differential style on * every repaint, so the formatting follows the targeted region (an item's data cells, the grand * total row, ...) when the pivot reshapes, rather than sticking to cell coordinates. * @@ -24,9 +24,11 @@ export type PivotFormat = { /** The pivot table region this format applies to. */ pivotArea: PivotArea; /** - * The formatting to overlay on the area's cells. Only the properties present apply; everything - * else on a cell is left as-is (the differential-format semantics of OOXML's `dxf`). - * Absent for `action: 'blank'`. + * Index of the differential style to overlay on the area's cells, into + * {@link Workbook.diffStyles}. Only the properties present in that style apply; everything else + * on a cell is left as-is. Absent for `action: 'blank'`. + * + * @see {@link Workbook.diffStyles} */ - style?: Style; + dxfId?: integer; }; diff --git a/src/types/workbooks/Workbook.ts b/src/types/workbooks/Workbook.ts index df81daa..f9cc949 100644 --- a/src/types/workbooks/Workbook.ts +++ b/src/types/workbooks/Workbook.ts @@ -31,6 +31,24 @@ export type Workbook = { calculationProperties?: CalcProps; /** Styles for cells in the workbook. */ styles?: Style[]; + /** + * Differential styles: partial style overrides referenced by index from features that overlay + * formatting on top of a cell's own style rather than replacing it (pivot table formats, custom + * table styles, and future dynamic/conditional styles). + * + * This is the differential-overlay analog of {@link Workbook.styles}: where {@link Cell.s} + * indexes a complete cell style in `styles`, an overlay's `dxfId` indexes a partial override + * here. Only the properties present in an entry apply; everything else on the target cell is + * left as-is. + * + * Each entry reuses the {@link Style} type (already all-optional), but is interpreted + * differentially: an absent property means "leave unchanged", not "use the default". + * + * The property name is provisional and may change before this is finalized. + * + * @see {@link PivotFormat.dxfId} + */ + diffStyles?: Style[]; /** Named cell style definitions (e.g. "Normal", "Heading 1"), keyed by style name. */ namedStyles?: Record; /** External cells referenced by the workbook. An external cell is a cell in another workbook. */ From 708ad3486943952353a6ed1c61f61c17b47dc202 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 15:53:16 +0000 Subject: [PATCH 08/11] Reference dxf by index from TableStyleElement Replace TableStyleElement's inline `style?: Style` with `dxfId?: integer` indexing into Workbook.diffStyles, so custom table styles share the same differential-style list as pivot formats (and future dynamic styles) instead of inlining their own copies. --- src/types/tables/TableStyleElement.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/types/tables/TableStyleElement.ts b/src/types/tables/TableStyleElement.ts index 7212fac..c82e8f9 100644 --- a/src/types/tables/TableStyleElement.ts +++ b/src/types/tables/TableStyleElement.ts @@ -1,5 +1,4 @@ import type { integer } from '../integer.ts'; -import type { Style } from '../styles/index.ts'; import type { TableStyleElementType } from './TableStyleElementType.ts'; /** @@ -19,10 +18,12 @@ export type TableStyleElement = { */ size?: integer; /** - * The formatting to overlay on the region's cells. Only the properties present apply; - * everything else on a cell is left as-is (differential-format semantics). An element - * without a style has no visual effect (a stripe element may still carry a meaningful - * {@link size}). + * Index of the differential style to overlay on the region's cells, into + * {@link Workbook.diffStyles}. Only the properties present in that style apply; everything else + * on a cell is left as-is. An element without a `dxfId` has no visual effect (a stripe element + * may still carry a meaningful {@link size}). + * + * @see {@link Workbook.diffStyles} */ - style?: Style; + dxfId?: integer; }; From 8d9e7f3c3d69c6480d2d6f8b62f152ab58567a41 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 16:21:23 +0000 Subject: [PATCH 09/11] Rename index field to diffStyleId; finalize diffStyles naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the differential-style index field from `dxfId` to `diffStyleId` (PivotFormat.diffStyleId) and update the Workbook.diffStyles docstring and @see references to match. The old name echoed a file-format attribute name; `diffStyleId` keeps the model naming consistent with `diffStyles` and free of format-specific terms. Also drop the "name is provisional" note from the diffStyles docstring — the name is now final. --- src/types/pivotTables/PivotFormat.ts | 2 +- src/types/workbooks/Workbook.ts | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/types/pivotTables/PivotFormat.ts b/src/types/pivotTables/PivotFormat.ts index b741d51..68b6a00 100644 --- a/src/types/pivotTables/PivotFormat.ts +++ b/src/types/pivotTables/PivotFormat.ts @@ -30,5 +30,5 @@ export type PivotFormat = { * * @see {@link Workbook.diffStyles} */ - dxfId?: integer; + diffStyleId?: integer; }; diff --git a/src/types/workbooks/Workbook.ts b/src/types/workbooks/Workbook.ts index f9cc949..33e9870 100644 --- a/src/types/workbooks/Workbook.ts +++ b/src/types/workbooks/Workbook.ts @@ -37,16 +37,14 @@ export type Workbook = { * table styles, and future dynamic/conditional styles). * * This is the differential-overlay analog of {@link Workbook.styles}: where {@link Cell.s} - * indexes a complete cell style in `styles`, an overlay's `dxfId` indexes a partial override - * here. Only the properties present in an entry apply; everything else on the target cell is - * left as-is. + * indexes a complete cell style in `styles`, an overlay's `diffStyleId` indexes a partial + * override here. Only the properties present in an entry apply; everything else on the target + * cell is left as-is. * * Each entry reuses the {@link Style} type (already all-optional), but is interpreted * differentially: an absent property means "leave unchanged", not "use the default". * - * The property name is provisional and may change before this is finalized. - * - * @see {@link PivotFormat.dxfId} + * @see {@link PivotFormat.diffStyleId} */ diffStyles?: Style[]; /** Named cell style definitions (e.g. "Normal", "Heading 1"), keyed by style name. */ From 7c37301acc88326ae6dc8012f2b5b23d36a83aff Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 16:23:08 +0000 Subject: [PATCH 10/11] Update PivotTable.formats docstring for the shared diffStyles model The formats docstring still said each format carries its differential style inline and that there is no separate style table, and linked the removed PivotFormat.style. Both are now false: formats reference a shared differential style by index. Point at PivotFormat.diffStyleId and Workbook.diffStyles instead. --- src/types/pivotTables/PivotTable.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/pivotTables/PivotTable.ts b/src/types/pivotTables/PivotTable.ts index c2805da..9e4fa14 100644 --- a/src/types/pivotTables/PivotTable.ts +++ b/src/types/pivotTables/PivotTable.ts @@ -92,9 +92,9 @@ export type PivotTable = { /** Presentation style for the pivot table. */ style?: PivotTableStyle; /** - * Custom formatting applied to specific regions of the pivot table. Each format carries its - * differential style inline (see {@link PivotFormat.style}); there is no separate dxf table - * in JSF. + * Custom formatting applied to specific regions of the pivot table. Each format references a + * shared differential style by index (see {@link PivotFormat.diffStyleId} and + * {@link Workbook.diffStyles}). */ formats?: PivotFormat[]; // conditionalFormats is omitted for now: the conditional formatting model is not finalized. From ef2cc9122befdbd059888963b2d8a4fac0969cb4 Mon Sep 17 00:00:00 2001 From: Gunnlaugur Thor Briem Date: Wed, 24 Jun 2026 16:24:04 +0000 Subject: [PATCH 11/11] Rename TableStyleElement.dxfId to diffStyleId Match the renamed index field on the shared differential-style model: TableStyleElement now references Workbook.diffStyles via diffStyleId (updating its docstring and @see) instead of dxfId. --- src/types/tables/TableStyleElement.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/tables/TableStyleElement.ts b/src/types/tables/TableStyleElement.ts index c82e8f9..503c57f 100644 --- a/src/types/tables/TableStyleElement.ts +++ b/src/types/tables/TableStyleElement.ts @@ -20,10 +20,10 @@ export type TableStyleElement = { /** * Index of the differential style to overlay on the region's cells, into * {@link Workbook.diffStyles}. Only the properties present in that style apply; everything else - * on a cell is left as-is. An element without a `dxfId` has no visual effect (a stripe element - * may still carry a meaningful {@link size}). + * on a cell is left as-is. An element without a `diffStyleId` has no visual effect (a stripe + * element may still carry a meaningful {@link size}). * * @see {@link Workbook.diffStyles} */ - dxfId?: integer; + diffStyleId?: integer; };