@shbernal/ts-xlsx 1.2.0 → 2.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 (161) hide show
  1. package/README.md +5 -0
  2. package/dist/core/address.d.ts +49 -2
  3. package/dist/core/address.js +31 -3
  4. package/dist/core/autofilter.d.ts +2 -2
  5. package/dist/core/cell.d.ts +17 -16
  6. package/dist/core/cell.js +3 -7
  7. package/dist/core/color-resolution.d.ts +7 -7
  8. package/dist/core/column.d.ts +8 -9
  9. package/dist/core/column.js +2 -4
  10. package/dist/core/comment-thread.d.ts +32 -23
  11. package/dist/core/comment-thread.js +4 -4
  12. package/dist/core/conditional-formatting-overlay.d.ts +1 -1
  13. package/dist/core/conditional-formatting.d.ts +3 -3
  14. package/dist/core/data-validation-overlay.d.ts +1 -1
  15. package/dist/core/data-validation.d.ts +3 -3
  16. package/dist/core/date.d.ts +3 -3
  17. package/dist/core/formula.d.ts +6 -6
  18. package/dist/core/grid-edits.js +3 -3
  19. package/dist/core/image.d.ts +37 -2
  20. package/dist/core/image.js +6 -0
  21. package/dist/core/internal.d.ts +9 -1
  22. package/dist/core/limits.d.ts +4 -4
  23. package/dist/core/merge.d.ts +6 -12
  24. package/dist/core/merge.js +0 -3
  25. package/dist/core/page-setup.d.ts +4 -4
  26. package/dist/core/pivot-table.d.ts +2 -4
  27. package/dist/core/pivot-table.js +5 -5
  28. package/dist/core/preserved.d.ts +3 -3
  29. package/dist/core/protection.d.ts +4 -4
  30. package/dist/core/range.d.ts +16 -14
  31. package/dist/core/range.js +5 -5
  32. package/dist/core/row-input.d.ts +2 -8
  33. package/dist/core/row-input.js +2 -2
  34. package/dist/core/row.d.ts +7 -8
  35. package/dist/core/row.js +2 -4
  36. package/dist/core/style.d.ts +14 -14
  37. package/dist/core/table-style.d.ts +9 -9
  38. package/dist/core/table-style.js +1 -1
  39. package/dist/core/table.d.ts +30 -42
  40. package/dist/core/table.js +16 -10
  41. package/dist/core/theme.d.ts +13 -43
  42. package/dist/core/theme.js +2 -148
  43. package/dist/core/value.d.ts +7 -7
  44. package/dist/core/workbook-protection.d.ts +2 -2
  45. package/dist/core/workbook-styles.d.ts +26 -0
  46. package/dist/core/workbook-styles.js +48 -0
  47. package/dist/core/workbook-theme.d.ts +25 -0
  48. package/dist/core/workbook-theme.js +49 -0
  49. package/dist/core/workbook-vba.d.ts +17 -0
  50. package/dist/core/workbook-vba.js +79 -0
  51. package/dist/core/workbook.d.ts +141 -83
  52. package/dist/core/workbook.js +74 -133
  53. package/dist/core/worksheet-comments.d.ts +9 -0
  54. package/dist/core/worksheet-comments.js +52 -0
  55. package/dist/core/worksheet-model.d.ts +4 -4
  56. package/dist/core/worksheet-model.js +1 -0
  57. package/dist/core/worksheet-pictures.d.ts +29 -0
  58. package/dist/core/worksheet-pictures.js +42 -0
  59. package/dist/core/worksheet.d.ts +94 -77
  60. package/dist/core/worksheet.js +36 -95
  61. package/dist/customui/errors.d.ts +1 -1
  62. package/dist/customui/ribbon.d.ts +3 -3
  63. package/dist/entries/core.d.ts +4 -4
  64. package/dist/entries/core.js +1 -1
  65. package/dist/entries/vba.d.ts +1 -1
  66. package/dist/entries/vba.js +1 -1
  67. package/dist/entries/xlsx.d.ts +1 -0
  68. package/dist/entries/xlsx.js +1 -0
  69. package/dist/errors.d.ts +12 -12
  70. package/dist/io/csv/read.js +1 -2
  71. package/dist/io/csv/write.d.ts +8 -3
  72. package/dist/io/csv/write.js +17 -2
  73. package/dist/io/opc/errors.d.ts +6 -6
  74. package/dist/io/opc/inflate.d.ts +1 -1
  75. package/dist/io/opc/namespaces.d.ts +1 -1
  76. package/dist/io/opc/read-opc.d.ts +19 -3
  77. package/dist/io/opc/read-opc.js +14 -8
  78. package/dist/io/opc/read-options.d.ts +2 -2
  79. package/dist/io/opc/sniff-format.d.ts +2 -2
  80. package/dist/io/opc/zip-mtime.d.ts +5 -0
  81. package/dist/io/opc/zip-mtime.js +1 -0
  82. package/dist/io/style/xf-style.d.ts +38 -6
  83. package/dist/io/style/xf-style.js +22 -0
  84. package/dist/io/xlsb/errors.d.ts +1 -1
  85. package/dist/io/xlsb/formula.d.ts +4 -4
  86. package/dist/io/xlsb/primitives.d.ts +8 -8
  87. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  88. package/dist/io/xlsb/read-styles.js +2 -20
  89. package/dist/io/xlsb/read.d.ts +2 -2
  90. package/dist/io/xlsx/cell-accumulator.d.ts +25 -6
  91. package/dist/io/xlsx/cell-accumulator.js +111 -30
  92. package/dist/io/xlsx/cell-value.d.ts +2 -2
  93. package/dist/io/xlsx/cell-value.js +3 -3
  94. package/dist/io/xlsx/color-xml.js +11 -17
  95. package/dist/io/xlsx/comments.d.ts +5 -5
  96. package/dist/io/xlsx/comments.js +13 -6
  97. package/dist/io/xlsx/conditional-formatting.d.ts +3 -3
  98. package/dist/io/xlsx/conditional-formatting.js +7 -16
  99. package/dist/io/xlsx/data-validation.d.ts +2 -2
  100. package/dist/io/xlsx/data-validation.js +1 -1
  101. package/dist/io/xlsx/edit-vba.js +2 -1
  102. package/dist/io/xlsx/errors.d.ts +3 -3
  103. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  104. package/dist/io/xlsx/hyperlinks.js +5 -5
  105. package/dist/io/xlsx/images.d.ts +1 -1
  106. package/dist/io/xlsx/images.js +8 -8
  107. package/dist/io/xlsx/namespaces.d.ts +2 -2
  108. package/dist/io/xlsx/package-plan.js +5 -2
  109. package/dist/io/xlsx/read-pivot.js +2 -5
  110. package/dist/io/xlsx/read-rows.d.ts +13 -13
  111. package/dist/io/xlsx/read-rows.js +19 -55
  112. package/dist/io/xlsx/read-shared-strings.js +7 -5
  113. package/dist/io/xlsx/read-styles.d.ts +3 -3
  114. package/dist/io/xlsx/read-styles.js +40 -65
  115. package/dist/io/xlsx/read-worksheet.js +67 -115
  116. package/dist/io/xlsx/read.d.ts +3 -5
  117. package/dist/io/xlsx/read.js +94 -71
  118. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  119. package/dist/io/xlsx/rich-runs.js +1 -1
  120. package/dist/io/xlsx/rich-text.d.ts +1 -1
  121. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  122. package/dist/io/xlsx/sheet-properties.js +5 -4
  123. package/dist/io/xlsx/styles.d.ts +8 -9
  124. package/dist/io/xlsx/styles.js +4 -3
  125. package/dist/io/xlsx/tables.d.ts +2 -2
  126. package/dist/io/xlsx/tables.js +8 -13
  127. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  128. package/dist/io/xlsx/theme-xml.js +142 -0
  129. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  130. package/dist/io/xlsx/threaded-comments.js +9 -21
  131. package/dist/io/xlsx/workbook-xml.d.ts +1 -1
  132. package/dist/io/xlsx/workbook-xml.js +6 -2
  133. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  134. package/dist/io/xlsx/worksheet-xml.js +7 -12
  135. package/dist/io/xlsx/write-stream.d.ts +15 -15
  136. package/dist/io/xlsx/write-stream.js +5 -3
  137. package/dist/io/xlsx/write.d.ts +12 -7
  138. package/dist/io/xlsx/write.js +10 -6
  139. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  140. package/dist/vba/bytes.d.ts +12 -0
  141. package/dist/vba/bytes.js +40 -0
  142. package/dist/vba/cfb-writer.d.ts +1 -1
  143. package/dist/vba/cfb.d.ts +1 -1
  144. package/dist/vba/cfb.js +15 -51
  145. package/dist/vba/codepage.d.ts +2 -2
  146. package/dist/vba/errors.d.ts +2 -2
  147. package/dist/vba/index.d.ts +2 -2
  148. package/dist/vba/index.js +2 -2
  149. package/dist/vba/ms-ovba.d.ts +1 -1
  150. package/dist/vba/ms-ovba.js +1 -3
  151. package/dist/vba/project-editor.d.ts +9 -9
  152. package/dist/vba/project-editor.js +2 -11
  153. package/dist/vba/project.d.ts +5 -5
  154. package/dist/vba/project.js +1 -10
  155. package/dist/vba/vba-encoding.d.ts +1 -1
  156. package/dist/xml/errors.d.ts +1 -1
  157. package/dist/xml/xml-read.d.ts +38 -9
  158. package/dist/xml/xml-read.js +23 -0
  159. package/dist/xml/xml.d.ts +31 -9
  160. package/dist/xml/xml.js +23 -3
  161. package/package.json +41 -35
@@ -1,13 +1,14 @@
1
1
  import { isCustomUiRelType, parseCustomUi } from '../customui/index.js';
2
2
  import { AuthoringError } from '../errors.js';
3
- import { addVbaReference, parseVbaProject, removeVbaModule, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, VbaAuthorError, vbaProjectSignatureKind, } from '../vba/index.js';
4
- import { resolveColor } from './color-resolution.js';
3
+ import { addVbaReference, removeVbaModule, } from '../vba/index.js';
5
4
  import { commentThreadGuid } from './comment-thread.js';
6
- import { replaceContents } from './containers.js';
7
- import { normalizeImageExtension } from './image.js';
5
+ import { findRegisteredImage, normalizeImageExtension, } from './image.js';
8
6
  import { INTERNAL } from './internal.js';
9
7
  import { checkTableStyle } from './table-style.js';
10
- import { applyThemeOverrides, DEFAULT_THEME_COLOR_SCHEME, DEFAULT_THEME_FONTS, DEFAULT_THEME_XML, OFFICE_BODY_FACE, parseThemeColorScheme, parseThemeFontScheme, THEME_COLOR_SLOTS, } from './theme.js';
8
+ import { OFFICE_BODY_FACE, THEME_COLOR_SLOTS, } from './theme.js';
9
+ import { WorkbookStyleTables } from './workbook-styles.js';
10
+ import { WorkbookTheme } from './workbook-theme.js';
11
+ import { WorkbookVbaProject } from './workbook-vba.js';
11
12
  import { Worksheet } from './worksheet.js';
12
13
  export const DEFAULT_WORKBOOK_VIEW = {
13
14
  x: -110,
@@ -27,12 +28,8 @@ export class Workbook {
27
28
  #nextSheetId = 1;
28
29
  #media = [];
29
30
  #definedNames = [];
30
- #dxfs = [];
31
- #namedStyles = [];
32
- #indexedColors = [];
33
- #mruColors = [];
34
- #tableStyles = { styles: [] };
35
- #theme;
31
+ #styles = new WorkbookStyleTables();
32
+ #theme = new WorkbookTheme(() => this.#styles.indexedPalette());
36
33
  #preservedReferences = [];
37
34
  #persons = new Map();
38
35
  get worksheets() {
@@ -65,131 +62,58 @@ export class Workbook {
65
62
  }
66
63
  return this.#customUI;
67
64
  }
68
- #vbaParsed = false;
69
- #vbaProject = undefined;
65
+ #vba = new WorkbookVbaProject(this.#preservedReferences);
70
66
  get vbaProject() {
71
- if (!this.#vbaParsed) {
72
- const bytes = this.#vbaProjectEntry()?.bytes;
73
- this.#vbaProject = bytes ? parseVbaProject(bytes) : undefined;
74
- this.#vbaParsed = true;
75
- }
76
- return this.#vbaProject;
67
+ return this.#vba.project;
77
68
  }
78
69
  get vbaProjectBytes() {
79
- return this.#vbaProjectEntry()?.bytes.slice();
70
+ return this.#vba.bytes;
80
71
  }
81
72
  set vbaProjectBytes(bytes) {
82
- if (bytes !== undefined)
83
- parseVbaProject(bytes);
84
- replaceContents(this.#preservedReferences, this.#preservedReferences.filter((r) => !r.relType.endsWith('/vbaProject')));
85
- if (bytes !== undefined) {
86
- this.#preservedReferences.push({
87
- relType: VBA_PROJECT_REL_TYPE,
88
- entryPath: VBA_PROJECT_PART_PATH,
89
- parts: [
90
- {
91
- path: VBA_PROJECT_PART_PATH,
92
- contentType: VBA_PROJECT_CONTENT_TYPE,
93
- bytes: bytes.slice(),
94
- rels: [],
95
- },
96
- ],
97
- });
98
- }
99
- this.#vbaParsed = false;
100
- this.#vbaProject = undefined;
73
+ this.#vba.bytes = bytes;
101
74
  }
102
75
  get vbaProjectSigned() {
103
- return this.#vbaSignatures().length > 0;
76
+ return this.#vba.signatures.length > 0;
104
77
  }
105
78
  get vbaProjectSignatures() {
106
- return this.#vbaSignatures();
107
- }
108
- #vbaSignatures() {
109
- const ref = this.#vbaProjectRef();
110
- const entry = ref?.parts.find((p) => p.path === ref.entryPath);
111
- if (ref === undefined || entry === undefined)
112
- return [];
113
- const partByPath = new Map(ref.parts.map((p) => [p.path, p]));
114
- const signatures = [];
115
- for (const rel of entry.rels) {
116
- const kind = vbaProjectSignatureKind(rel.type);
117
- const part = kind === undefined ? undefined : partByPath.get(rel.targetPath);
118
- if (kind !== undefined && part !== undefined) {
119
- signatures.push({ kind, bytes: part.bytes.slice() });
120
- }
121
- }
122
- return signatures;
79
+ return this.#vba.signatures;
123
80
  }
124
81
  removeVbaModule(name) {
125
- const bytes = this.vbaProjectBytes;
126
- if (bytes === undefined) {
127
- throw new VbaAuthorError('workbook has no VBA project to remove a module from');
128
- }
129
- this.vbaProjectBytes = removeVbaModule(bytes, name);
82
+ this.#vba.removeModule(name);
130
83
  }
131
84
  addVbaReference(ref) {
132
- const bytes = this.vbaProjectBytes;
133
- if (bytes === undefined) {
134
- throw new VbaAuthorError('workbook has no VBA project to add a reference to');
135
- }
136
- this.vbaProjectBytes = addVbaReference(bytes, ref);
137
- }
138
- #vbaProjectRef() {
139
- return this.#preservedReferences.find((r) => r.relType.endsWith('/vbaProject'));
140
- }
141
- #vbaProjectEntry() {
142
- const ref = this.#vbaProjectRef();
143
- return ref?.parts.find((p) => p.path === ref.entryPath);
85
+ this.#vba.addReference(ref);
144
86
  }
145
87
  get differentialStyles() {
146
- return this.#dxfs;
88
+ return this.#styles.differentialStyles;
147
89
  }
148
90
  get indexedColors() {
149
- return this.#indexedColors;
91
+ return this.#styles.indexedColors;
150
92
  }
151
93
  get mruColors() {
152
- return this.#mruColors;
94
+ return this.#styles.mruColors;
153
95
  }
154
96
  get tableStyles() {
155
- return this.#tableStyles;
97
+ return this.#styles.tableStyles;
156
98
  }
157
- #customTableStyles = new Map();
158
99
  addTableStyle(style) {
159
100
  checkTableStyle(style);
160
- this.#customTableStyles.set(style.name, style);
101
+ this.#styles.addCustomTableStyle(style);
161
102
  }
162
103
  get customTableStyles() {
163
- return [...this.#customTableStyles.values()];
104
+ return this.#styles.customTableStyles;
164
105
  }
165
106
  get themePart() {
166
- return this.#theme;
107
+ return this.#theme.part;
167
108
  }
168
- #themeColors;
169
- #authoredTheme = {
170
- colors: {},
171
- fonts: {},
172
- };
173
109
  setTheme(overrides) {
174
- applyThemeOverrides(this.#baseThemeXml(), overrides);
175
- Object.assign(this.#authoredTheme.colors, overrides.colors ?? {});
176
- this.#authoredTheme.fonts = { ...this.#authoredTheme.fonts, ...(overrides.fonts ?? {}) };
177
- this.#themeColors = undefined;
110
+ this.#theme.author(overrides);
178
111
  }
179
112
  get themeColors() {
180
- if (this.#themeColors === undefined) {
181
- const xml = this.#themeXml();
182
- const parsed = xml === undefined ? {} : parseThemeColorScheme(xml);
183
- const base = Object.keys(parsed).length === 0 ? DEFAULT_THEME_COLOR_SCHEME : parsed;
184
- this.#themeColors = { ...base, ...this.#authoredTheme.colors };
185
- }
186
- return this.#themeColors;
113
+ return this.#theme.colors;
187
114
  }
188
115
  get themeFonts() {
189
- const xml = this.#themeXml();
190
- const parsed = xml === undefined ? {} : parseThemeFontScheme(xml);
191
- const base = Object.keys(parsed).length === 0 ? DEFAULT_THEME_FONTS : parsed;
192
- return { ...base, ...this.#authoredTheme.fonts };
116
+ return this.#theme.fonts;
193
117
  }
194
118
  #declaredDefaultFont;
195
119
  #authoredDefaultFont;
@@ -198,17 +122,17 @@ export class Workbook {
198
122
  }
199
123
  setDefaultFont(font) {
200
124
  if (font.size !== undefined && !(Number.isFinite(font.size) && font.size > 0)) {
201
- throw new AuthoringError(`default font size must be a positive number, not ${font.size}`);
125
+ throw new RangeError(`default font size must be a positive number, not ${font.size}`);
202
126
  }
203
127
  if (font.name !== undefined && font.name === '') {
204
- throw new AuthoringError('default font name cannot be empty');
128
+ throw new RangeError('default font name cannot be empty');
205
129
  }
206
130
  this.#authoredDefaultFont = { ...this.#authoredDefaultFont, ...font };
207
131
  }
208
132
  get defaultFont() {
209
133
  const declared = this.#declaredDefaultFont;
210
134
  const authored = this.#authoredDefaultFont;
211
- const authoredFace = this.#authoredTheme.fonts.minor;
135
+ const authoredFace = this.#theme.authoredFonts.minor;
212
136
  if (declared !== undefined && authored === undefined && authoredFace === undefined) {
213
137
  return declared;
214
138
  }
@@ -236,30 +160,14 @@ export class Workbook {
236
160
  }
237
161
  return font;
238
162
  }
239
- authoredThemeXml() {
240
- const { colors, fonts } = this.#authoredTheme;
241
- if (Object.keys(colors).length === 0 && Object.keys(fonts).length === 0)
242
- return undefined;
243
- return applyThemeOverrides(this.#baseThemeXml(), { colors, fonts });
244
- }
245
- #baseThemeXml() {
246
- return this.#themeXml() ?? DEFAULT_THEME_XML;
247
- }
248
- #themeXml() {
249
- const theme = this.#theme;
250
- if (theme === undefined)
251
- return undefined;
252
- const entry = theme.parts.find((part) => part.path === theme.entryPath);
253
- return entry === undefined ? undefined : new TextDecoder().decode(entry.bytes);
163
+ get themeOverrides() {
164
+ return this.#theme.overrides;
254
165
  }
255
166
  resolveColor(color) {
256
- return resolveColor(color, { theme: this.themeColors, indexed: this.#indexedPalette() });
257
- }
258
- #indexedPalette() {
259
- return this.#indexedColors.map((fragment) => /\brgb="([^"]*)"/.exec(fragment)?.[1] ?? '');
167
+ return this.#theme.resolveColor(color);
260
168
  }
261
169
  get namedStyles() {
262
- return this.#namedStyles;
170
+ return this.#styles.namedStyles;
263
171
  }
264
172
  addPerson(person) {
265
173
  const id = commentThreadGuid(person.id, 'a person id');
@@ -284,6 +192,40 @@ export class Workbook {
284
192
  getImage(id) {
285
193
  return this.#media[id];
286
194
  }
195
+ exportImages(sheet) {
196
+ const resolve = (id) => {
197
+ const image = this.#media[id];
198
+ if (image === undefined) {
199
+ throw new AuthoringError(`worksheet "${sheet.name}" shows image id ${id}, which is not registered on this ` +
200
+ "workbook: a sheet's images can only be exported by the workbook that holds them");
201
+ }
202
+ return image;
203
+ };
204
+ const background = sheet.backgroundImageId;
205
+ return {
206
+ anchored: sheet.images.map(({ imageId, anchor }) => ({ image: resolve(imageId), anchor })),
207
+ background: background === undefined ? undefined : resolve(background),
208
+ };
209
+ }
210
+ importImages(sheet, images) {
211
+ for (const id of new Set(sheet.images.map((image) => image.imageId)))
212
+ sheet.removeImage(id);
213
+ sheet.removeBackgroundImage();
214
+ for (const { image, anchor } of images.anchored) {
215
+ sheet.addImageAnchor(this.#registerImage(image), anchor);
216
+ }
217
+ if (images.background !== undefined) {
218
+ sheet.addBackgroundImage(this.#registerImage(images.background));
219
+ }
220
+ }
221
+ #registerImage(image) {
222
+ const candidate = {
223
+ extension: normalizeImageExtension(image.extension, image.data),
224
+ data: image.data,
225
+ };
226
+ return (findRegisteredImage(this.#media, candidate) ??
227
+ this.addImage({ buffer: candidate.data, extension: candidate.extension }));
228
+ }
287
229
  get definedNames() {
288
230
  return this.#definedNames;
289
231
  }
@@ -345,23 +287,22 @@ export class Workbook {
345
287
  this.#preservedRootReferences.push(reference);
346
288
  },
347
289
  restoreDifferentialStyles: (fragments) => {
348
- replaceContents(this.#dxfs, fragments);
290
+ this.#styles.restoreDifferentialStyles(fragments);
349
291
  },
350
292
  restoreIndexedColors: (fragments) => {
351
- replaceContents(this.#indexedColors, fragments);
293
+ this.#styles.restoreIndexedColors(fragments);
352
294
  },
353
295
  restoreMruColors: (fragments) => {
354
- replaceContents(this.#mruColors, fragments);
296
+ this.#styles.restoreMruColors(fragments);
355
297
  },
356
298
  restoreTableStyles: (table) => {
357
- this.#tableStyles = table;
299
+ this.#styles.restoreTableStyles(table);
358
300
  },
359
- restoreThemePart: (theme) => {
360
- this.#theme = theme;
361
- this.#themeColors = undefined;
301
+ restoreThemePart: (theme, declared) => {
302
+ this.#theme.restorePart(theme, declared);
362
303
  },
363
304
  restoreNamedStyles: (styles) => {
364
- replaceContents(this.#namedStyles, styles);
305
+ this.#styles.restoreNamedStyles(styles);
365
306
  },
366
307
  restoreDefaultFont: (font) => {
367
308
  this.#declaredDefaultFont = font;
@@ -0,0 +1,9 @@
1
+ import { type CommentThread } from './comment-thread.ts';
2
+ export declare class WorksheetComments {
3
+ #private;
4
+ constructor(sheetName: () => string);
5
+ get threads(): readonly CommentThread[];
6
+ add(thread: CommentThread): void;
7
+ at(reference: string): CommentThread | undefined;
8
+ restore(threads: readonly CommentThread[]): void;
9
+ }
@@ -0,0 +1,52 @@
1
+ import { decodeCellRef, encodeAddress } from './address.js';
2
+ import { commentThreadGuid, commentThreadOffset } from './comment-thread.js';
3
+ import { replaceContents } from './containers.js';
4
+ export class WorksheetComments {
5
+ #sheetName;
6
+ #threads = [];
7
+ constructor(sheetName) {
8
+ this.#sheetName = sheetName;
9
+ }
10
+ get threads() {
11
+ return this.#threads;
12
+ }
13
+ add(thread) {
14
+ const taken = new Set(this.#threads.flatMap((held) => held.comments.map((comment) => comment.id)));
15
+ const comments = thread.comments.map((comment) => {
16
+ const id = commentThreadGuid(comment.id, 'a comment id');
17
+ if (taken.has(id)) {
18
+ throw new SyntaxError(`a comment id must be unique within a sheet, but "${id}" is already used on "${this.#sheetName()}": ` +
19
+ 'a reply and the legacy fallback comment both find their thread by it');
20
+ }
21
+ taken.add(id);
22
+ return {
23
+ ...comment,
24
+ id,
25
+ ...(comment.personId !== undefined
26
+ ? { personId: commentThreadGuid(comment.personId, "a comment's author id") }
27
+ : {}),
28
+ mentions: comment.mentions.map((mention) => ({
29
+ ...mention,
30
+ personId: commentThreadGuid(mention.personId, "a mention's person id"),
31
+ startIndex: commentThreadOffset(mention.startIndex, "a mention's startIndex"),
32
+ length: commentThreadOffset(mention.length, "a mention's length"),
33
+ ...(mention.mentionId !== undefined
34
+ ? { mentionId: commentThreadGuid(mention.mentionId, 'a mention id') }
35
+ : {}),
36
+ })),
37
+ };
38
+ });
39
+ this.#threads.push({ ...thread, ref: anchorRef(thread.ref), comments });
40
+ }
41
+ at(reference) {
42
+ const anchor = anchorRef(reference);
43
+ return this.#threads.find((thread) => thread.ref === anchor);
44
+ }
45
+ restore(threads) {
46
+ replaceContents(this.#threads, threads);
47
+ }
48
+ }
49
+ function anchorRef(reference) {
50
+ const { col, row } = decodeCellRef(reference);
51
+ return encodeAddress(col, row);
52
+ }
@@ -1,3 +1,4 @@
1
+ import { type AssertNever } from './internal.ts';
1
2
  import type { Worksheet, WorksheetModel } from './worksheet.ts';
2
3
  /** One field of a {@link WorksheetModel}, with both directions of its round-trip declared together. */
3
4
  interface ModelFacet<K extends keyof WorksheetModel = keyof WorksheetModel> {
@@ -7,12 +8,12 @@ interface ModelFacet<K extends keyof WorksheetModel = keyof WorksheetModel> {
7
8
  /**
8
9
  * Apply the field to a sheet whose content has already been reset. Takes the whole model rather
9
10
  * than the field so that a loop over the registry needs no correlation between `key` and the
10
- * field's type {@link facet} does that projection once, where the key is still a single type.
11
+ * field's type. {@link facet} does that projection once, where the key is still a single type.
11
12
  *
12
13
  * The obvious shape, `write(sheet, value: WorksheetModel[K])` stored as-is, cannot be called from
13
14
  * a loop: over a union of `ModelFacet<K>` the parameter is contravariant under
14
15
  * `strictFunctionTypes`, which breaks the correlation. Declaring `write` with method syntax makes
15
- * it compile by making the position bivariant, which buys the call back by switching the check
16
+ * it compile, by making the position bivariant, which buys the call back by switching the check
16
17
  * off. Projecting inside the helper is correlated *and* sound; do not "simplify" it back.
17
18
  */
18
19
  readonly write: (sheet: Worksheet, model: WorksheetModel) => void;
@@ -22,8 +23,7 @@ interface ModelFacet<K extends keyof WorksheetModel = keyof WorksheetModel> {
22
23
  * load-bearing: cells are placed at their exact positions before any merge exists, so a covered
23
24
  * cell's value lands where the model says instead of being routed to a region master mid-load.
24
25
  */
25
- export declare const WORKSHEET_MODEL_FACETS: (ModelFacet<"autoFilter"> | ModelFacet<"cells"> | ModelFacet<"columnBreaks"> | ModelFacet<"columns"> | ModelFacet<"conditionalFormattings"> | ModelFacet<"dataValidations"> | ModelFacet<"headerFooter"> | ModelFacet<"merges"> | ModelFacet<"outline"> | ModelFacet<"pageMargins"> | ModelFacet<"pageSetup"> | ModelFacet<"printOptions"> | ModelFacet<"properties"> | ModelFacet<"protection"> | ModelFacet<"rowBreaks"> | ModelFacet<"rows"> | ModelFacet<"state"> | ModelFacet<"tabColor"> | ModelFacet<"tables">)[];
26
- type AssertNever<T extends never> = T;
26
+ export declare const WORKSHEET_MODEL_FACETS: (ModelFacet<"autoFilter"> | ModelFacet<"cells"> | ModelFacet<"columnBreaks"> | ModelFacet<"columns"> | ModelFacet<"conditionalFormattings"> | ModelFacet<"dataValidations"> | ModelFacet<"headerFooter"> | ModelFacet<"merges"> | ModelFacet<"outline"> | ModelFacet<"pageMargins"> | ModelFacet<"pageSetup"> | ModelFacet<"printOptions"> | ModelFacet<"properties"> | ModelFacet<"protection"> | ModelFacet<"rowBreaks"> | ModelFacet<"rows"> | ModelFacet<"state"> | ModelFacet<"tabColor"> | ModelFacet<"tables"> | ModelFacet<"view">)[];
27
27
  /**
28
28
  * Compile-time proof that {@link WORKSHEET_MODEL_FACETS} covers every {@link WorksheetModel} field.
29
29
  * A field added without a facet resolves this to that field's name, which does not satisfy `never`,
@@ -15,6 +15,7 @@ export const WORKSHEET_MODEL_FACETS = [
15
15
  }),
16
16
  facet('properties', (sheet) => ({ ...sheet.properties }), (sheet, value) => overwrite(sheet.properties, value)),
17
17
  facet('outline', (sheet) => ({ ...sheet.outline }), (sheet, value) => overwrite(sheet.outline, value)),
18
+ facet('view', (sheet) => ({ ...sheet.view }), (sheet, value) => overwrite(sheet.view, value)),
18
19
  facet('pageSetup', (sheet) => ({ ...sheet.pageSetup }), (sheet, value) => overwrite(sheet.pageSetup, value)),
19
20
  facet('printOptions', (sheet) => ({ ...sheet.printOptions }), (sheet, value) => overwrite(sheet.printOptions, value)),
20
21
  facet('pageMargins', (sheet) => ({ ...sheet.pageMargins }), (sheet, value) => overwrite(sheet.pageMargins, value)),
@@ -0,0 +1,29 @@
1
+ import { type AnchoredImage, type AnchorPoint, type ImageAnchor, type ImageEditAs } from './image.ts';
2
+ /** The two shapes {@link WorksheetPictures.add} accepts, in caller-facing pixel units. */
3
+ export type PixelAnchor = {
4
+ readonly tl: AnchorPoint;
5
+ readonly br: AnchorPoint;
6
+ readonly editAs?: ImageEditAs;
7
+ } | {
8
+ readonly tl: AnchorPoint;
9
+ readonly ext: {
10
+ readonly width: number;
11
+ readonly height: number;
12
+ };
13
+ };
14
+ /** How a sheet reports the sizes an anchor point resolves against; `undefined` means "the default". */
15
+ export interface AnchorMetrics {
16
+ columnWidth(col: number): number | undefined;
17
+ rowHeight(row: number): number | undefined;
18
+ }
19
+ export declare class WorksheetPictures {
20
+ #private;
21
+ constructor(metrics: AnchorMetrics);
22
+ /** The anchored images, in the order they were added. */
23
+ get anchors(): AnchoredImage[];
24
+ get backgroundImageId(): number | undefined;
25
+ add(imageId: number, anchor: PixelAnchor): void;
26
+ addAnchor(imageId: number, anchor: ImageAnchor): void;
27
+ remove(imageId: number): void;
28
+ setBackground(imageId: number | undefined): void;
29
+ }
@@ -0,0 +1,42 @@
1
+ import { replaceContents } from './containers.js';
2
+ import { PX_TO_EMU, resolveAnchorPoint, } from './image.js';
3
+ export class WorksheetPictures {
4
+ #metrics;
5
+ #anchors = [];
6
+ #backgroundImageId;
7
+ constructor(metrics) {
8
+ this.#metrics = metrics;
9
+ }
10
+ get anchors() {
11
+ return this.#anchors;
12
+ }
13
+ get backgroundImageId() {
14
+ return this.#backgroundImageId;
15
+ }
16
+ add(imageId, anchor) {
17
+ const columnWidth = (col) => this.#metrics.columnWidth(col);
18
+ const rowHeight = (row) => this.#metrics.rowHeight(row);
19
+ if ('ext' in anchor) {
20
+ const ext = {
21
+ cx: Math.round(anchor.ext.width * PX_TO_EMU),
22
+ cy: Math.round(anchor.ext.height * PX_TO_EMU),
23
+ };
24
+ const from = resolveAnchorPoint(anchor.tl, columnWidth, rowHeight);
25
+ this.#anchors.push({ imageId, anchor: { from, ext } });
26
+ return;
27
+ }
28
+ const from = resolveAnchorPoint(anchor.tl, columnWidth, rowHeight);
29
+ const to = resolveAnchorPoint(anchor.br, columnWidth, rowHeight);
30
+ const twoCell = anchor.editAs !== undefined ? { from, to, editAs: anchor.editAs } : { from, to };
31
+ this.#anchors.push({ imageId, anchor: twoCell });
32
+ }
33
+ addAnchor(imageId, anchor) {
34
+ this.#anchors.push({ imageId, anchor });
35
+ }
36
+ remove(imageId) {
37
+ replaceContents(this.#anchors, this.#anchors.filter((image) => image.imageId !== imageId));
38
+ }
39
+ setBackground(imageId) {
40
+ this.#backgroundImageId = imageId;
41
+ }
42
+ }