@shbernal/ts-xlsx 2.1.0 → 3.0.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.
Files changed (100) hide show
  1. package/dist/core/autofilter.d.ts +1 -1
  2. package/dist/core/autofilter.js +3 -5
  3. package/dist/core/axis-handle.d.ts +8 -0
  4. package/dist/core/axis-handle.js +14 -0
  5. package/dist/core/cell.d.ts +24 -0
  6. package/dist/core/cell.js +9 -1
  7. package/dist/core/column.d.ts +4 -1
  8. package/dist/core/column.js +31 -35
  9. package/dist/core/conditional-formatting.d.ts +5 -5
  10. package/dist/core/conditional-formatting.js +11 -25
  11. package/dist/core/data-validation.d.ts +3 -3
  12. package/dist/core/data-validation.js +7 -15
  13. package/dist/core/image.d.ts +1 -1
  14. package/dist/core/image.js +2 -4
  15. package/dist/core/merge-index.d.ts +31 -0
  16. package/dist/core/merge-index.js +60 -0
  17. package/dist/core/merge.d.ts +0 -10
  18. package/dist/core/merge.js +0 -8
  19. package/dist/core/page-setup.d.ts +72 -2
  20. package/dist/core/page-setup.js +34 -8
  21. package/dist/core/pivot-table.d.ts +1 -1
  22. package/dist/core/pivot-table.js +4 -8
  23. package/dist/core/row.d.ts +4 -1
  24. package/dist/core/row.js +19 -23
  25. package/dist/core/style.d.ts +51 -7
  26. package/dist/core/style.js +29 -34
  27. package/dist/core/table.d.ts +1 -1
  28. package/dist/core/table.js +3 -5
  29. package/dist/core/used-extent.d.ts +44 -0
  30. package/dist/core/used-extent.js +102 -0
  31. package/dist/core/worksheet.d.ts +17 -6
  32. package/dist/core/worksheet.js +48 -42
  33. package/dist/customui/ribbon.js +4 -6
  34. package/dist/io/opc/inflate.js +1 -1
  35. package/dist/io/opc/part-paths.d.ts +1 -0
  36. package/dist/io/opc/part-paths.js +15 -0
  37. package/dist/io/opc/read-opc.d.ts +22 -3
  38. package/dist/io/opc/read-opc.js +11 -39
  39. package/dist/io/opc/rels.d.ts +1 -6
  40. package/dist/io/opc/rels.js +1 -4
  41. package/dist/io/style/xf-style.d.ts +26 -2
  42. package/dist/io/style/xf-style.js +6 -0
  43. package/dist/io/xlsb/read-styles.js +6 -3
  44. package/dist/io/xlsb/read.d.ts +1 -1
  45. package/dist/io/xlsb/read.js +5 -8
  46. package/dist/io/xlsx/cell-accumulator.d.ts +1 -1
  47. package/dist/io/xlsx/cell-accumulator.js +1 -1
  48. package/dist/io/xlsx/cell-value.js +1 -1
  49. package/dist/io/xlsx/color-xml.js +1 -1
  50. package/dist/io/xlsx/comments.js +2 -1
  51. package/dist/io/xlsx/conditional-formatting.d.ts +3 -2
  52. package/dist/io/xlsx/conditional-formatting.js +22 -21
  53. package/dist/io/xlsx/data-validation.d.ts +9 -7
  54. package/dist/io/xlsx/data-validation.js +10 -9
  55. package/dist/io/xlsx/edit-vba.js +4 -11
  56. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  57. package/dist/io/xlsx/hyperlinks.js +20 -17
  58. package/dist/io/xlsx/images.js +15 -14
  59. package/dist/io/xlsx/package-plan.d.ts +9 -1
  60. package/dist/io/xlsx/package-plan.js +29 -16
  61. package/dist/io/xlsx/part-names.d.ts +41 -0
  62. package/dist/io/xlsx/part-names.js +46 -0
  63. package/dist/io/xlsx/read-pivot.js +2 -1
  64. package/dist/io/xlsx/read-rows.js +12 -13
  65. package/dist/io/xlsx/read-shared-strings.js +2 -1
  66. package/dist/io/xlsx/read-styles.d.ts +1 -1
  67. package/dist/io/xlsx/read-styles.js +63 -42
  68. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  69. package/dist/io/xlsx/read-worksheet.js +43 -56
  70. package/dist/io/xlsx/read.d.ts +2 -3
  71. package/dist/io/xlsx/read.js +81 -73
  72. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  73. package/dist/io/xlsx/rich-runs.js +2 -1
  74. package/dist/io/xlsx/sheet-properties.js +21 -28
  75. package/dist/io/xlsx/styles.js +23 -20
  76. package/dist/io/xlsx/tables.js +2 -1
  77. package/dist/io/xlsx/theme-xml.js +1 -1
  78. package/dist/io/xlsx/threaded-comments.js +2 -1
  79. package/dist/io/xlsx/workbook-xml.js +29 -28
  80. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  81. package/dist/io/xlsx/worksheet-xml.js +14 -16
  82. package/dist/io/xlsx/write-stream.d.ts +6 -0
  83. package/dist/io/xlsx/write-stream.js +30 -22
  84. package/dist/io/xlsx/write.d.ts +5 -0
  85. package/dist/io/xlsx/write.js +155 -131
  86. package/dist/token-set.d.ts +15 -0
  87. package/dist/token-set.js +4 -0
  88. package/dist/vba/cfb-format.d.ts +24 -0
  89. package/dist/vba/cfb-format.js +12 -0
  90. package/dist/vba/cfb-writer.js +17 -17
  91. package/dist/vba/cfb.js +1 -7
  92. package/dist/vba/ms-ovba.js +32 -11
  93. package/dist/vba/project.js +13 -13
  94. package/dist/vba/vba-encoding.js +1 -1
  95. package/dist/xml/xml-read.d.ts +59 -74
  96. package/dist/xml/xml-read.js +83 -176
  97. package/dist/xml/xml-scan.d.ts +100 -0
  98. package/dist/xml/xml-scan.js +208 -0
  99. package/dist/xml/xml.d.ts +1 -1
  100. package/package.json +2 -3
@@ -43,7 +43,7 @@ export interface CustomFilterPredicate {
43
43
  }
44
44
  export type CustomFilterOperator = 'equal' | 'notEqual' | 'lessThan' | 'lessThanOrEqual' | 'greaterThan' | 'greaterThanOrEqual';
45
45
  /** Narrow a raw `operator` attribute to a known {@link CustomFilterOperator}. */
46
- export declare function isCustomFilterOperator(value: string): value is CustomFilterOperator;
46
+ export declare const isCustomFilterOperator: (value: string) => value is CustomFilterOperator;
47
47
  /**
48
48
  * Validate and normalise a settable autofilter into its canonical stored form. A bare range string
49
49
  * is the common case (dropdowns, no criteria); an {@link AutoFilter} object carries per-column
@@ -1,17 +1,15 @@
1
1
  import { AuthoringError } from '../errors.js';
2
+ import { tokenSet } from '../token-set.js';
2
3
  import { decodeRange, encodeAddress } from './address.js';
3
4
  import { isDeletedSpan, shiftIndex } from './grid-shift.js';
4
- const CUSTOM_FILTER_OPERATORS = {
5
+ export const isCustomFilterOperator = tokenSet({
5
6
  equal: true,
6
7
  notEqual: true,
7
8
  lessThan: true,
8
9
  lessThanOrEqual: true,
9
10
  greaterThan: true,
10
11
  greaterThanOrEqual: true,
11
- };
12
- export function isCustomFilterOperator(value) {
13
- return Object.hasOwn(CUSTOM_FILTER_OPERATORS, value);
14
- }
12
+ });
15
13
  export function canonicalizeAutoFilter(input) {
16
14
  const ref = typeof input === 'string' ? input : input.ref;
17
15
  const { top, left, bottom, right, dimensions } = decodeRange(ref);
@@ -0,0 +1,8 @@
1
+ export declare abstract class AxisHandle<P extends object> {
2
+ /** This position's format record, or `undefined` when it has none. Never fabricates one. */
3
+ protected abstract propertiesOf(): P | undefined;
4
+ /** This position's format record, created if it does not exist yet. */
5
+ protected abstract ensureProperties(): P;
6
+ protected read<K extends keyof P>(key: K): P[K] | undefined;
7
+ protected write<K extends keyof P>(key: K, value: P[K]): void;
8
+ }
@@ -0,0 +1,14 @@
1
+ export class AxisHandle {
2
+ read(key) {
3
+ return this.propertiesOf()?.[key];
4
+ }
5
+ write(key, value) {
6
+ if (value === undefined) {
7
+ const properties = this.propertiesOf();
8
+ if (properties !== undefined)
9
+ delete properties[key];
10
+ return;
11
+ }
12
+ this.ensureProperties()[key] = value;
13
+ }
14
+ }
@@ -138,6 +138,30 @@ export declare class Cell {
138
138
  get note(): string | undefined;
139
139
  set note(note: string | undefined);
140
140
  }
141
+ /**
142
+ * Whether a cell carries formatting of its own: one of the {@link CellStyle} facets, the quote-prefix
143
+ * flag, or a link to a named style. Driven by {@link CELL_STYLE_FACETS}, so a facet added to the
144
+ * tuple reaches every "is this cell blank" decision without anyone remembering to widen a literal.
145
+ *
146
+ * Row- and column-inherited formatting is not the cell's own and does not count. Neither does a
147
+ * note: it lives in the comments part, not the cell's `<c>` element, so a writer deciding whether an
148
+ * empty cell needs serialising asks exactly this. A caller asking whether the cell is *used* wants
149
+ * {@link cellCarriesContent}, which is this plus the value and the note.
150
+ */
151
+ export declare function cellHasOwnStyle(cell: Cell): boolean;
152
+ /**
153
+ * Whether a cell carries anything at all: a value, formatting of its own, or a note. This is the
154
+ * used-range test, the "carrying anything (data or its own formatting)" that `rowCount` and
155
+ * `columnCount` promise, and it is deliberately wider than "holds a value": someone who pre-formats
156
+ * an empty band has
157
+ * laid claim to those cells, and an append that treats them as free ground writes over the layout
158
+ * they just built; someone who notes an empty cell has put content in it by the note's own
159
+ * definition.
160
+ *
161
+ * A cell merely materialised by `getCell` and then left alone carries nothing, which is what keeps
162
+ * reading a far address from growing the sheet.
163
+ */
164
+ export declare function cellCarriesContent(cell: Cell): boolean;
141
165
  export declare function applyCellStyle(cell: Cell, style: Readonly<CellStyle>): void;
142
166
  export declare function copyCellContent(source: CellModel, target: Cell): void;
143
167
  export declare function cellToModel(cell: Cell): CellModel;
package/dist/core/cell.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { assertColumnInBounds, assertRowInBounds, encodeAddress } from './address.js';
2
2
  import { NAMED_STYLE_ID } from './internal.js';
3
- import { assignStyleFacets, } from './style.js';
3
+ import { assignStyleFacets, CELL_STYLE_FACETS, } from './style.js';
4
4
  import { cellValueToText, coerceCellValue, detectValueType, } from './value.js';
5
5
  export class Cell {
6
6
  row;
@@ -106,6 +106,14 @@ export class Cell {
106
106
  this.#note = note;
107
107
  }
108
108
  }
109
+ export function cellHasOwnStyle(cell) {
110
+ return (CELL_STYLE_FACETS.some((facet) => cell[facet] !== undefined) ||
111
+ cell.quotePrefix === true ||
112
+ cell[NAMED_STYLE_ID] !== undefined);
113
+ }
114
+ export function cellCarriesContent(cell) {
115
+ return cell.value !== null || cell.note !== undefined || cellHasOwnStyle(cell);
116
+ }
109
117
  export function applyCellStyle(cell, style) {
110
118
  assignStyleFacets(cell, style);
111
119
  }
@@ -1,9 +1,10 @@
1
+ import { AxisHandle } from './axis-handle.ts';
1
2
  import type { Cell } from './cell.ts';
2
3
  import { type AssertNever } from './internal.ts';
3
4
  import type { Alignment, Border, Fill, Font, Protection } from './style.ts';
4
5
  import type { CellValue } from './value.ts';
5
6
  import type { ColumnProperties, Worksheet } from './worksheet.ts';
6
- export declare class Column {
7
+ export declare class Column extends AxisHandle<ColumnProperties> {
7
8
  #private;
8
9
  /** 1-based column index. Fixed for this handle's lifetime. */
9
10
  readonly index: number;
@@ -18,6 +19,8 @@ export declare class Column {
18
19
  * is formatted, and they create the record on first write.
19
20
  */
20
21
  get properties(): Readonly<ColumnProperties> | undefined;
22
+ protected propertiesOf(): ColumnProperties | undefined;
23
+ protected ensureProperties(): ColumnProperties;
21
24
  /**
22
25
  * Stable key naming this column so a keyed-object row (see {@link Worksheet.addRow}) can place a
23
26
  * value under it by name rather than position. In-memory only: never serialized to OOXML.
@@ -1,9 +1,11 @@
1
1
  import { assertColumnInBounds, encodeAddress, numberToColumn } from './address.js';
2
+ import { AxisHandle } from './axis-handle.js';
2
3
  import { INTERNAL } from './internal.js';
3
- export class Column {
4
+ export class Column extends AxisHandle {
4
5
  #sheet;
5
6
  index;
6
7
  constructor(sheet, index) {
8
+ super();
7
9
  assertColumnInBounds(index);
8
10
  this.#sheet = sheet;
9
11
  this.index = index;
@@ -12,73 +14,79 @@ export class Column {
12
14
  return numberToColumn(this.index);
13
15
  }
14
16
  get properties() {
17
+ return this.propertiesOf();
18
+ }
19
+ propertiesOf() {
15
20
  return this.#sheet[INTERNAL].columnPropertiesOf(this.index);
16
21
  }
22
+ ensureProperties() {
23
+ return this.#sheet[INTERNAL].ensureColumnProperties(this.index);
24
+ }
17
25
  get key() {
18
- return this.#read('key');
26
+ return this.read('key');
19
27
  }
20
28
  set key(key) {
21
- this.#write('key', key);
29
+ this.write('key', key);
22
30
  }
23
31
  get width() {
24
- return this.#read('width');
32
+ return this.read('width');
25
33
  }
26
34
  set width(width) {
27
- this.#write('width', width);
35
+ this.write('width', width);
28
36
  }
29
37
  get hidden() {
30
- return this.#read('hidden');
38
+ return this.read('hidden');
31
39
  }
32
40
  set hidden(hidden) {
33
- this.#write('hidden', hidden);
41
+ this.write('hidden', hidden);
34
42
  }
35
43
  get outlineLevel() {
36
- return this.#read('outlineLevel');
44
+ return this.read('outlineLevel');
37
45
  }
38
46
  set outlineLevel(outlineLevel) {
39
- this.#write('outlineLevel', outlineLevel);
47
+ this.write('outlineLevel', outlineLevel);
40
48
  }
41
49
  get collapsed() {
42
- return this.#read('collapsed');
50
+ return this.read('collapsed');
43
51
  }
44
52
  set collapsed(collapsed) {
45
- this.#write('collapsed', collapsed);
53
+ this.write('collapsed', collapsed);
46
54
  }
47
55
  get fill() {
48
- return this.#read('fill');
56
+ return this.read('fill');
49
57
  }
50
58
  set fill(fill) {
51
- this.#write('fill', fill);
59
+ this.write('fill', fill);
52
60
  }
53
61
  get numFmt() {
54
- return this.#read('numFmt');
62
+ return this.read('numFmt');
55
63
  }
56
64
  set numFmt(numFmt) {
57
- this.#write('numFmt', numFmt);
65
+ this.write('numFmt', numFmt);
58
66
  }
59
67
  get font() {
60
- return this.#read('font');
68
+ return this.read('font');
61
69
  }
62
70
  set font(font) {
63
- this.#write('font', font);
71
+ this.write('font', font);
64
72
  }
65
73
  get border() {
66
- return this.#read('border');
74
+ return this.read('border');
67
75
  }
68
76
  set border(border) {
69
- this.#write('border', border);
77
+ this.write('border', border);
70
78
  }
71
79
  get alignment() {
72
- return this.#read('alignment');
80
+ return this.read('alignment');
73
81
  }
74
82
  set alignment(alignment) {
75
- this.#write('alignment', alignment);
83
+ this.write('alignment', alignment);
76
84
  }
77
85
  get protection() {
78
- return this.#read('protection');
86
+ return this.read('protection');
79
87
  }
80
88
  set protection(protection) {
81
- this.#write('protection', protection);
89
+ this.write('protection', protection);
82
90
  }
83
91
  getCell(row) {
84
92
  return this.#sheet.getCell(encodeAddress(this.index, row));
@@ -98,16 +106,4 @@ export class Column {
98
106
  this.getCell(index + 1).value = value;
99
107
  });
100
108
  }
101
- #read(key) {
102
- return this.#sheet[INTERNAL].columnPropertiesOf(this.index)?.[key];
103
- }
104
- #write(key, value) {
105
- if (value === undefined) {
106
- const properties = this.#sheet[INTERNAL].columnPropertiesOf(this.index);
107
- if (properties !== undefined)
108
- delete properties[key];
109
- return;
110
- }
111
- this.#sheet[INTERNAL].ensureColumnProperties(this.index)[key] = value;
112
- }
113
109
  }
@@ -2,7 +2,7 @@ import type { Color, DifferentialStyle } from './style.ts';
2
2
  /** How a {@link CfValueObject} reads its `value`: `ST_CfvoType` verbatim. */
3
3
  export type CfValueObjectType = 'num' | 'percent' | 'max' | 'min' | 'percentile' | 'formula';
4
4
  /** Narrow a raw `<cfvo type>` token to a known {@link CfValueObjectType}. */
5
- export declare function isCfValueObjectType(value: string): value is CfValueObjectType;
5
+ export declare const isCfValueObjectType: (value: string) => value is CfValueObjectType;
6
6
  /**
7
7
  * One anchor of a colour-scale, data-bar, or icon-set scale: a "conditional format value object".
8
8
  * `type` names how `value` is read: a literal `num`, a `percent`/`percentile` of the range, a
@@ -22,7 +22,7 @@ export interface CfValueObject {
22
22
  */
23
23
  export type ConditionalFormattingType = 'expression' | 'cellIs' | 'colorScale' | 'dataBar' | 'iconSet' | 'top10' | 'uniqueValues' | 'duplicateValues' | 'containsText' | 'notContainsText' | 'beginsWith' | 'endsWith' | 'containsBlanks' | 'notContainsBlanks' | 'containsErrors' | 'notContainsErrors' | 'timePeriod' | 'aboveAverage';
24
24
  /** Narrow a raw `<cfRule type>` token to a known {@link ConditionalFormattingType}. */
25
- export declare function isConditionalFormattingType(value: string): value is ConditionalFormattingType;
25
+ export declare const isConditionalFormattingType: (value: string) => value is ConditionalFormattingType;
26
26
  /**
27
27
  * How a `cellIs` or text rule compares, as `ST_ConditionalFormattingOperator` enumerates it.
28
28
  *
@@ -32,11 +32,11 @@ export declare function isConditionalFormattingType(value: string): value is Con
32
32
  */
33
33
  export type ConditionalFormattingOperator = 'lessThan' | 'lessThanOrEqual' | 'equal' | 'notEqual' | 'greaterThanOrEqual' | 'greaterThan' | 'between' | 'notBetween' | 'containsText' | 'notContains' | 'beginsWith' | 'endsWith';
34
34
  /** Narrow a raw `<cfRule operator>` token to a known {@link ConditionalFormattingOperator}. */
35
- export declare function isConditionalFormattingOperator(value: string): value is ConditionalFormattingOperator;
35
+ export declare const isConditionalFormattingOperator: (value: string) => value is ConditionalFormattingOperator;
36
36
  /** The window a `timePeriod` rule matches against, relative to the day the sheet is recalculated. */
37
37
  export type CfTimePeriod = 'today' | 'yesterday' | 'tomorrow' | 'last7Days' | 'thisMonth' | 'lastMonth' | 'nextMonth' | 'thisWeek' | 'lastWeek' | 'nextWeek';
38
38
  /** Narrow a raw `<cfRule timePeriod>` token to a known {@link CfTimePeriod}. */
39
- export declare function isCfTimePeriod(value: string): value is CfTimePeriod;
39
+ export declare const isCfTimePeriod: (value: string) => value is CfTimePeriod;
40
40
  /**
41
41
  * The named icon family an `iconSet` rule draws from, as `ST_IconSetType` enumerates it. The leading
42
42
  * digit is the number of icons, which is also how many {@link CfValueObject} anchors the rule needs.
@@ -47,7 +47,7 @@ export declare function isCfTimePeriod(value: string): value is CfTimePeriod;
47
47
  */
48
48
  export type IconSetType = '3Arrows' | '3ArrowsGray' | '3Flags' | '3TrafficLights1' | '3TrafficLights2' | '3Signs' | '3Symbols' | '3Symbols2' | '4Arrows' | '4ArrowsGray' | '4RedToBlack' | '4Rating' | '4TrafficLights' | '5Arrows' | '5ArrowsGray' | '5Rating' | '5Quarters';
49
49
  /** Narrow a raw `<iconSet iconSet>` token to a known {@link IconSetType}. */
50
- export declare function isIconSetType(value: string): value is IconSetType;
50
+ export declare const isIconSetType: (value: string) => value is IconSetType;
51
51
  /**
52
52
  * A single conditional-formatting rule. `type` is the OOXML cfRule type; the remaining fields carry
53
53
  * the operands that type needs and are absent otherwise. A rule the library does not model in depth
@@ -1,15 +1,13 @@
1
- const CF_VALUE_OBJECT_TYPES = {
1
+ import { tokenSet } from '../token-set.js';
2
+ export const isCfValueObjectType = tokenSet({
2
3
  num: true,
3
4
  percent: true,
4
5
  max: true,
5
6
  min: true,
6
7
  percentile: true,
7
8
  formula: true,
8
- };
9
- export function isCfValueObjectType(value) {
10
- return Object.hasOwn(CF_VALUE_OBJECT_TYPES, value);
11
- }
12
- const CONDITIONAL_FORMATTING_TYPES = {
9
+ });
10
+ export const isConditionalFormattingType = tokenSet({
13
11
  expression: true,
14
12
  cellIs: true,
15
13
  colorScale: true,
@@ -28,11 +26,8 @@ const CONDITIONAL_FORMATTING_TYPES = {
28
26
  notContainsErrors: true,
29
27
  timePeriod: true,
30
28
  aboveAverage: true,
31
- };
32
- export function isConditionalFormattingType(value) {
33
- return Object.hasOwn(CONDITIONAL_FORMATTING_TYPES, value);
34
- }
35
- const CONDITIONAL_FORMATTING_OPERATORS = {
29
+ });
30
+ export const isConditionalFormattingOperator = tokenSet({
36
31
  lessThan: true,
37
32
  lessThanOrEqual: true,
38
33
  equal: true,
@@ -45,11 +40,8 @@ const CONDITIONAL_FORMATTING_OPERATORS = {
45
40
  notContains: true,
46
41
  beginsWith: true,
47
42
  endsWith: true,
48
- };
49
- export function isConditionalFormattingOperator(value) {
50
- return Object.hasOwn(CONDITIONAL_FORMATTING_OPERATORS, value);
51
- }
52
- const CF_TIME_PERIODS = {
43
+ });
44
+ export const isCfTimePeriod = tokenSet({
53
45
  today: true,
54
46
  yesterday: true,
55
47
  tomorrow: true,
@@ -60,11 +52,8 @@ const CF_TIME_PERIODS = {
60
52
  thisWeek: true,
61
53
  lastWeek: true,
62
54
  nextWeek: true,
63
- };
64
- export function isCfTimePeriod(value) {
65
- return Object.hasOwn(CF_TIME_PERIODS, value);
66
- }
67
- const ICON_SET_TYPES = {
55
+ });
56
+ export const isIconSetType = tokenSet({
68
57
  '3Arrows': true,
69
58
  '3ArrowsGray': true,
70
59
  '3Flags': true,
@@ -82,10 +71,7 @@ const ICON_SET_TYPES = {
82
71
  '5ArrowsGray': true,
83
72
  '5Rating': true,
84
73
  '5Quarters': true,
85
- };
86
- export function isIconSetType(value) {
87
- return Object.hasOwn(ICON_SET_TYPES, value);
88
- }
74
+ });
89
75
  export function cloneConditionalFormatting(cf) {
90
76
  return { ref: cf.ref, rules: cf.rules.map(cloneRule) };
91
77
  }
@@ -3,16 +3,16 @@
3
3
  * bound a typed value (`whole`/`decimal`/`date`/`time`/`textLength`). */
4
4
  export type DataValidationType = 'none' | 'list' | 'whole' | 'decimal' | 'date' | 'time' | 'textLength' | 'custom';
5
5
  /** Narrow a raw `<dataValidation type>` token to a known {@link DataValidationType}. */
6
- export declare function isDataValidationType(value: string): value is DataValidationType;
6
+ export declare const isDataValidationType: (value: string) => value is DataValidationType;
7
7
  /** How a typed validation compares its operand(s). Absent on a `list`/`custom` rule; defaults to
8
8
  * `between` on a typed rule (the value Excel omits from the XML). */
9
9
  export type DataValidationOperator = 'between' | 'notBetween' | 'equal' | 'notEqual' | 'greaterThan' | 'lessThan' | 'greaterThanOrEqual' | 'lessThanOrEqual';
10
10
  /** Narrow a raw `<dataValidation operator>` token to a known {@link DataValidationOperator}. */
11
- export declare function isDataValidationOperator(value: string): value is DataValidationOperator;
11
+ export declare const isDataValidationOperator: (value: string) => value is DataValidationOperator;
12
12
  /** How Excel reacts to input that fails the rule. */
13
13
  export type DataValidationErrorStyle = 'stop' | 'warning' | 'information';
14
14
  /** Narrow a raw `<dataValidation errorStyle>` token to a known {@link DataValidationErrorStyle}. */
15
- export declare function isDataValidationErrorStyle(value: string): value is DataValidationErrorStyle;
15
+ export declare const isDataValidationErrorStyle: (value: string) => value is DataValidationErrorStyle;
16
16
  /** One validation rule. `formulae` holds the operand(s), `formula1` then optional `formula2`: a
17
17
  * numeric literal is stored as a number, while a cell reference, defined name, or list source keeps
18
18
  * its verbatim string. */
@@ -1,4 +1,5 @@
1
- const DATA_VALIDATION_TYPES = {
1
+ import { tokenSet } from '../token-set.js';
2
+ export const isDataValidationType = tokenSet({
2
3
  none: true,
3
4
  list: true,
4
5
  whole: true,
@@ -7,11 +8,8 @@ const DATA_VALIDATION_TYPES = {
7
8
  time: true,
8
9
  textLength: true,
9
10
  custom: true,
10
- };
11
- export function isDataValidationType(value) {
12
- return Object.hasOwn(DATA_VALIDATION_TYPES, value);
13
- }
14
- const DATA_VALIDATION_OPERATORS = {
11
+ });
12
+ export const isDataValidationOperator = tokenSet({
15
13
  between: true,
16
14
  notBetween: true,
17
15
  equal: true,
@@ -20,18 +18,12 @@ const DATA_VALIDATION_OPERATORS = {
20
18
  lessThan: true,
21
19
  greaterThanOrEqual: true,
22
20
  lessThanOrEqual: true,
23
- };
24
- export function isDataValidationOperator(value) {
25
- return Object.hasOwn(DATA_VALIDATION_OPERATORS, value);
26
- }
27
- const DATA_VALIDATION_ERROR_STYLES = {
21
+ });
22
+ export const isDataValidationErrorStyle = tokenSet({
28
23
  stop: true,
29
24
  warning: true,
30
25
  information: true,
31
- };
32
- export function isDataValidationErrorStyle(value) {
33
- return Object.hasOwn(DATA_VALIDATION_ERROR_STYLES, value);
34
- }
26
+ });
35
27
  export function cloneDataValidation(rule) {
36
28
  return {
37
29
  ...rule,
@@ -18,7 +18,7 @@ export declare const PX_TO_EMU = 9525;
18
18
  * defaults to `oneCell` when the attribute is omitted. */
19
19
  export type ImageEditAs = 'oneCell' | 'twoCell' | 'absolute';
20
20
  /** Narrow a raw `<xdr:twoCellAnchor editAs>` token to a known {@link ImageEditAs}. */
21
- export declare function isImageEditAs(value: string): value is ImageEditAs;
21
+ export declare const isImageEditAs: (value: string) => value is ImageEditAs;
22
22
  /** A fixed image size in EMUs: the extent of a one-cell anchor, which pixel dimensions convert into
23
23
  * via {@link PX_TO_EMU}. */
24
24
  export interface Extent {
@@ -1,8 +1,6 @@
1
+ import { tokenSet } from '../token-set.js';
1
2
  export const PX_TO_EMU = 9525;
2
- const IMAGE_EDIT_AS = { oneCell: true, twoCell: true, absolute: true };
3
- export function isImageEditAs(value) {
4
- return Object.hasOwn(IMAGE_EDIT_AS, value);
5
- }
3
+ export const isImageEditAs = tokenSet({ oneCell: true, twoCell: true, absolute: true });
6
4
  export function isOneCellAnchor(anchor) {
7
5
  return 'ext' in anchor;
8
6
  }
@@ -0,0 +1,31 @@
1
+ import { type GridRect } from './address.ts';
2
+ import type { MergeRect } from './merge.ts';
3
+ /**
4
+ * A row-banded index over a sheet's merged regions. Holds the region list by reference and never
5
+ * mutates it: the sheet stays the owner, reports what it adds, and says when it has rewritten the
6
+ * list wholesale (a splice, an unmerge, a model assignment), which the index answers by rebuilding
7
+ * on the next query rather than by tracking the edit.
8
+ */
9
+ export declare class MergeIndex {
10
+ #private;
11
+ constructor(rects: readonly MergeRect[]);
12
+ /** Note a region added to the list. Ignored while stale: the rebuild reads the list itself. */
13
+ note(rect: MergeRect): void;
14
+ /** Report that the region list has been rewritten from outside this index. */
15
+ invalidate(): void;
16
+ /**
17
+ * The region overlapping `rect`, or `undefined` when the rectangle is free. Which of several
18
+ * overlapping regions is returned is unspecified beyond being one of them; a sheet admits no
19
+ * overlap, so at most one ever applies.
20
+ */
21
+ overlapping(rect: GridRect): MergeRect | undefined;
22
+ /**
23
+ * Resolve a position to the top-left of the merged region covering it, or to itself when no region
24
+ * does. Only fully-bounded regions participate: an unbounded whole-row/column merge carries no
25
+ * rectangle and so resolves nothing.
26
+ */
27
+ masterOf(row: number, col: number): {
28
+ row: number;
29
+ col: number;
30
+ };
31
+ }
@@ -0,0 +1,60 @@
1
+ import { rectsOverlap } from './address.js';
2
+ const BAND_ROWS = 64;
3
+ function bandOf(row) {
4
+ return Math.floor(row / BAND_ROWS);
5
+ }
6
+ export class MergeIndex {
7
+ #rects;
8
+ #bands = new Map();
9
+ #tallest = 0;
10
+ #stale = true;
11
+ constructor(rects) {
12
+ this.#rects = rects;
13
+ }
14
+ note(rect) {
15
+ if (!this.#stale)
16
+ this.#place(rect);
17
+ }
18
+ invalidate() {
19
+ this.#stale = true;
20
+ }
21
+ overlapping(rect) {
22
+ this.#refresh();
23
+ const first = bandOf(Math.max(1, rect.top - this.#tallest + 1));
24
+ const last = bandOf(rect.bottom);
25
+ for (let band = first; band <= last; band++) {
26
+ const bucket = this.#bands.get(band);
27
+ if (bucket === undefined)
28
+ continue;
29
+ for (const candidate of bucket) {
30
+ if (rectsOverlap(candidate, rect))
31
+ return candidate;
32
+ }
33
+ }
34
+ return undefined;
35
+ }
36
+ masterOf(row, col) {
37
+ const covering = this.overlapping({ top: row, left: col, bottom: row, right: col });
38
+ return covering === undefined ? { row, col } : { row: covering.top, col: covering.left };
39
+ }
40
+ #place(rect) {
41
+ const height = rect.bottom - rect.top + 1;
42
+ if (height > this.#tallest)
43
+ this.#tallest = height;
44
+ const band = bandOf(rect.top);
45
+ const bucket = this.#bands.get(band);
46
+ if (bucket === undefined)
47
+ this.#bands.set(band, [rect]);
48
+ else
49
+ bucket.push(rect);
50
+ }
51
+ #refresh() {
52
+ if (!this.#stale)
53
+ return;
54
+ this.#stale = false;
55
+ this.#bands = new Map();
56
+ this.#tallest = 0;
57
+ for (const rect of this.#rects)
58
+ this.#place(rect);
59
+ }
60
+ }
@@ -2,16 +2,6 @@ import { type GridRect } from './address.ts';
2
2
  import type { Cell } from './cell.ts';
3
3
  /** A merged region, as the {@link GridRect} every range-shaped thing in the library is. */
4
4
  export type MergeRect = GridRect;
5
- /**
6
- * Resolve a position to the master (top-left) of the merged region covering it, or to itself when no
7
- * region does. First covering region wins; `Worksheet.mergeCells` rejects overlaps, so at most one
8
- * region ever applies. Only fully-bounded rects participate: an unbounded whole-row/column merge
9
- * carries no rect and so resolves nothing.
10
- */
11
- export declare function masterOf(rects: readonly MergeRect[], row: number, col: number): {
12
- row: number;
13
- col: number;
14
- };
15
5
  /**
16
6
  * Drop any value already sitting in a merge's covered non-anchor cells, keeping only the top-left
17
7
  * anchor, the collapse Excel performs on merge. A leftover covered value would serialise as a
@@ -1,13 +1,5 @@
1
1
  import { encodeCornerRef, tryDecodeRange } from './address.js';
2
2
  import { isDeletedSpan, shiftIndex } from './grid-shift.js';
3
- export function masterOf(rects, row, col) {
4
- for (const rect of rects) {
5
- if (row >= rect.top && row <= rect.bottom && col >= rect.left && col <= rect.right) {
6
- return { row: rect.top, col: rect.left };
7
- }
8
- }
9
- return { row, col };
10
- }
11
3
  export function clearCoveredValues(rows, rect) {
12
4
  for (let row = rect.top; row <= rect.bottom; row++) {
13
5
  const cols = rows.get(row);