@beyondwork/docx-react-component 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/package.json +35 -21
- package/src/api/public-types.ts +103 -1
- package/src/core/commands/formatting-commands.ts +742 -0
- package/src/core/commands/image-commands.ts +84 -2
- package/src/core/commands/structural-helpers.ts +309 -0
- package/src/core/commands/table-structure-commands.ts +721 -0
- package/src/core/commands/text-commands.ts +166 -1
- package/src/core/state/editor-state.ts +318 -9
- package/src/formats/xlsx/io/parse-sheet.ts +177 -7
- package/src/formats/xlsx/io/parse-styles.ts +2 -0
- package/src/formats/xlsx/io/xlsx-session.ts +18 -12
- package/src/formats/xlsx/model/sheet.ts +81 -1
- package/src/formats/xlsx/model/workbook.ts +10 -6
- package/src/io/docx-session.ts +392 -22
- package/src/io/export/export-session.ts +55 -0
- package/src/io/export/serialize-footnotes.ts +5 -20
- package/src/io/export/serialize-headers-footers.ts +5 -31
- package/src/io/export/serialize-main-document.ts +78 -5
- package/src/io/normalize/normalize-text.ts +90 -1
- package/src/io/ooxml/parse-footnotes.ts +68 -5
- package/src/io/ooxml/parse-headers-footers.ts +67 -9
- package/src/io/ooxml/parse-main-document.ts +169 -6
- package/src/io/opc/package-reader.ts +3 -3
- package/src/io/source-package-provenance.ts +241 -0
- package/src/model/canonical-document.ts +450 -2
- package/src/model/cds-1.0.0.ts +5 -2
- package/src/model/snapshot.ts +190 -19
- package/src/preservation/package-preservation.ts +0 -7
- package/src/runtime/document-runtime.ts +7 -1
- package/src/runtime/read-only-diagnostics-runtime.ts +1 -1
- package/src/runtime/surface-projection.ts +200 -17
- package/src/runtime/table-commands.ts +79 -0
- package/src/runtime/table-schema.ts +9 -0
- package/src/ui/WordReviewEditor.tsx +708 -16
- package/src/ui-tailwind/editor-surface/pm-schema.ts +121 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +73 -7
- package/src/ui-tailwind/editor-surface/search-plugin.ts +76 -16
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +162 -14
- package/src/validation/compatibility-engine.ts +208 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeXmlEntities
|
|
1
|
+
import { decodeXmlEntities } from "./parse-shared-strings.ts";
|
|
2
2
|
import { parseXmlAttributes } from "./parse-styles.ts";
|
|
3
3
|
|
|
4
4
|
// Re-export for external use
|
|
@@ -12,11 +12,23 @@ export { decodeXmlEntities };
|
|
|
12
12
|
* b (boolean), e (error), str (formula string result), and blank.
|
|
13
13
|
*/
|
|
14
14
|
export type XlsxParsedCellValue =
|
|
15
|
+
| { type: "blank" }
|
|
16
|
+
| { type: "text"; value: string; fromSharedString: boolean }
|
|
17
|
+
| { type: "number"; value: number }
|
|
18
|
+
| { type: "boolean"; value: boolean }
|
|
19
|
+
| {
|
|
20
|
+
type: "formula";
|
|
21
|
+
formula: string;
|
|
22
|
+
referenceTokens: string[];
|
|
23
|
+
cachedValue: XlsxParsedFormulaCachedValue | null;
|
|
24
|
+
}
|
|
25
|
+
| { type: "error"; errorCode: string };
|
|
26
|
+
|
|
27
|
+
export type XlsxParsedFormulaCachedValue =
|
|
15
28
|
| { type: "blank" }
|
|
16
29
|
| { type: "text"; value: string }
|
|
17
30
|
| { type: "number"; value: number }
|
|
18
31
|
| { type: "boolean"; value: boolean }
|
|
19
|
-
| { type: "formula"; formula: string; cachedValue: string | null }
|
|
20
32
|
| { type: "error"; errorCode: string };
|
|
21
33
|
|
|
22
34
|
export interface XlsxParsedCell {
|
|
@@ -37,9 +49,17 @@ export interface XlsxParsedMerge {
|
|
|
37
49
|
endCol: number;
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
export interface XlsxParsedDimension {
|
|
53
|
+
startRow: number;
|
|
54
|
+
startCol: number;
|
|
55
|
+
endRow: number;
|
|
56
|
+
endCol: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
export interface SheetParseResult {
|
|
41
60
|
cells: XlsxParsedCell[];
|
|
42
61
|
merges: XlsxParsedMerge[];
|
|
62
|
+
dimension: XlsxParsedDimension | null;
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
/**
|
|
@@ -54,6 +74,7 @@ export function parseSheetXml(
|
|
|
54
74
|
): SheetParseResult {
|
|
55
75
|
const cells: XlsxParsedCell[] = [];
|
|
56
76
|
const merges: XlsxParsedMerge[] = [];
|
|
77
|
+
const dimension = parseSheetDimension(xml);
|
|
57
78
|
|
|
58
79
|
const sheetDataMatch = /<sheetData>([\s\S]*?)<\/sheetData>/i.exec(xml);
|
|
59
80
|
if (sheetDataMatch) {
|
|
@@ -65,7 +86,7 @@ export function parseSheetXml(
|
|
|
65
86
|
parseMergeCells(mergeCellsMatch[1] ?? "", merges);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
return { cells, merges };
|
|
89
|
+
return { cells, merges, dimension };
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
function parseSheetData(
|
|
@@ -145,10 +166,12 @@ function resolveCellValue(
|
|
|
145
166
|
): XlsxParsedCellValue {
|
|
146
167
|
// Formula cells may have any result type; store formula + cached value.
|
|
147
168
|
if (formulaText !== null) {
|
|
169
|
+
const decodedFormula = decodeXmlEntities(formulaText);
|
|
148
170
|
return {
|
|
149
171
|
type: "formula",
|
|
150
|
-
formula:
|
|
151
|
-
|
|
172
|
+
formula: decodedFormula,
|
|
173
|
+
referenceTokens: extractFormulaReferenceTokens(decodedFormula),
|
|
174
|
+
cachedValue: resolveFormulaCachedValue(typeCode, rawValue, inlineText, sharedStrings),
|
|
152
175
|
};
|
|
153
176
|
}
|
|
154
177
|
|
|
@@ -160,11 +183,11 @@ function resolveCellValue(
|
|
|
160
183
|
!Number.isNaN(index) && index >= 0 && index < sharedStrings.length
|
|
161
184
|
? (sharedStrings[index] ?? "")
|
|
162
185
|
: "";
|
|
163
|
-
return { type: "text", value: text };
|
|
186
|
+
return { type: "text", value: text, fromSharedString: true };
|
|
164
187
|
}
|
|
165
188
|
|
|
166
189
|
case "inlineStr": {
|
|
167
|
-
return { type: "text", value: inlineText ?? "" };
|
|
190
|
+
return { type: "text", value: inlineText ?? "", fromSharedString: false };
|
|
168
191
|
}
|
|
169
192
|
|
|
170
193
|
case "str": {
|
|
@@ -173,6 +196,7 @@ function resolveCellValue(
|
|
|
173
196
|
return {
|
|
174
197
|
type: "text",
|
|
175
198
|
value: rawValue !== null ? decodeXmlEntities(rawValue) : "",
|
|
199
|
+
fromSharedString: false,
|
|
176
200
|
};
|
|
177
201
|
}
|
|
178
202
|
|
|
@@ -202,6 +226,61 @@ function resolveCellValue(
|
|
|
202
226
|
}
|
|
203
227
|
}
|
|
204
228
|
|
|
229
|
+
function resolveFormulaCachedValue(
|
|
230
|
+
typeCode: string,
|
|
231
|
+
rawValue: string | null,
|
|
232
|
+
inlineText: string | null,
|
|
233
|
+
sharedStrings: readonly string[],
|
|
234
|
+
): XlsxParsedFormulaCachedValue | null {
|
|
235
|
+
switch (typeCode) {
|
|
236
|
+
case "s": {
|
|
237
|
+
const index = rawValue !== null ? parseInt(rawValue, 10) : NaN;
|
|
238
|
+
return {
|
|
239
|
+
type: "text",
|
|
240
|
+
value:
|
|
241
|
+
!Number.isNaN(index) && index >= 0 && index < sharedStrings.length
|
|
242
|
+
? (sharedStrings[index] ?? "")
|
|
243
|
+
: "",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case "inlineStr":
|
|
248
|
+
return { type: "text", value: inlineText ?? "" };
|
|
249
|
+
|
|
250
|
+
case "str":
|
|
251
|
+
return rawValue === null
|
|
252
|
+
? { type: "blank" }
|
|
253
|
+
: { type: "text", value: decodeXmlEntities(rawValue) };
|
|
254
|
+
|
|
255
|
+
case "b":
|
|
256
|
+
return rawValue === null
|
|
257
|
+
? { type: "blank" }
|
|
258
|
+
: { type: "boolean", value: rawValue === "1" };
|
|
259
|
+
|
|
260
|
+
case "e":
|
|
261
|
+
return rawValue === null
|
|
262
|
+
? { type: "blank" }
|
|
263
|
+
: { type: "error", errorCode: decodeXmlEntities(rawValue) };
|
|
264
|
+
|
|
265
|
+
default: {
|
|
266
|
+
if (rawValue === null || rawValue === "") {
|
|
267
|
+
return { type: "blank" };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const numericValue = Number(rawValue);
|
|
271
|
+
if (!Number.isNaN(numericValue)) {
|
|
272
|
+
return { type: "number", value: numericValue };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (rawValue === "TRUE" || rawValue === "FALSE") {
|
|
276
|
+
return { type: "boolean", value: rawValue === "TRUE" };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return { type: "text", value: decodeXmlEntities(rawValue) };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
205
284
|
function extractTextContent(xml: string, tagName: string): string | null {
|
|
206
285
|
const pattern = new RegExp(`<${tagName}(?:\\s[^>]*)?>([^<]*)</${tagName}>`, "i");
|
|
207
286
|
const match = pattern.exec(xml);
|
|
@@ -260,6 +339,97 @@ function parseMergeRef(ref: string): XlsxParsedMerge | null {
|
|
|
260
339
|
};
|
|
261
340
|
}
|
|
262
341
|
|
|
342
|
+
function parseSheetDimension(xml: string): XlsxParsedDimension | null {
|
|
343
|
+
const dimensionMatch = /<dimension\b([^>]*?)(?:\/>|>)/i.exec(xml);
|
|
344
|
+
if (!dimensionMatch) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const attrs = parseXmlAttributes(dimensionMatch[1] ?? "");
|
|
349
|
+
const ref = attrs["ref"];
|
|
350
|
+
if (!ref) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return parseDimensionRef(ref);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const SHEET_PREFIX_PATTERN =
|
|
358
|
+
"(?:'(?:[^']|'')+'|[A-Za-z_][A-Za-z0-9_.]*)!";
|
|
359
|
+
const CELL_REFERENCE_PATTERN = "\\$?[A-Z]{1,3}\\$?[1-9][0-9]*";
|
|
360
|
+
const RANGE_REFERENCE_PATTERN =
|
|
361
|
+
`${CELL_REFERENCE_PATTERN}(?::${CELL_REFERENCE_PATTERN})?`;
|
|
362
|
+
const FORMULA_REFERENCE_PATTERN = new RegExp(
|
|
363
|
+
String.raw`(?<![A-Za-z0-9_.$])(?:${SHEET_PREFIX_PATTERN})?${RANGE_REFERENCE_PATTERN}(?![A-Za-z0-9_])`,
|
|
364
|
+
"g",
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
function extractFormulaReferenceTokens(formula: string): string[] {
|
|
368
|
+
const maskedFormula = maskQuotedFormulaStrings(formula);
|
|
369
|
+
const tokens: string[] = [];
|
|
370
|
+
const seen = new Set<string>();
|
|
371
|
+
|
|
372
|
+
for (const match of maskedFormula.matchAll(FORMULA_REFERENCE_PATTERN)) {
|
|
373
|
+
const index = match.index ?? -1;
|
|
374
|
+
if (index < 0) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const token = formula.slice(index, index + match[0].length);
|
|
378
|
+
if (seen.has(token)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
seen.add(token);
|
|
382
|
+
tokens.push(token);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return tokens;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function maskQuotedFormulaStrings(formula: string): string {
|
|
389
|
+
let output = "";
|
|
390
|
+
let inString = false;
|
|
391
|
+
|
|
392
|
+
for (let index = 0; index < formula.length; index += 1) {
|
|
393
|
+
const char = formula[index];
|
|
394
|
+
const next = formula[index + 1];
|
|
395
|
+
|
|
396
|
+
if (char === "\"") {
|
|
397
|
+
output += char;
|
|
398
|
+
if (inString && next === "\"") {
|
|
399
|
+
output += " ";
|
|
400
|
+
index += 1;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
inString = !inString;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
output += inString ? " " : char;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return output;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function parseDimensionRef(ref: string): XlsxParsedDimension | null {
|
|
414
|
+
const [startRef, endRef = startRef] = ref.split(":");
|
|
415
|
+
if (!startRef || !endRef) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const start = parseCellRef(startRef);
|
|
420
|
+
const end = parseCellRef(endRef);
|
|
421
|
+
if (start === null || end === null) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
startRow: start.row,
|
|
427
|
+
startCol: start.col,
|
|
428
|
+
endRow: end.row,
|
|
429
|
+
endCol: end.col,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
263
433
|
function parseCellRef(ref: string): { row: number; col: number } | null {
|
|
264
434
|
const match = /^([A-Z]+)([0-9]+)$/i.exec(ref.trim());
|
|
265
435
|
if (!match) {
|
|
@@ -9,6 +9,7 @@ export interface XlsxStyleEntry {
|
|
|
9
9
|
fontId: number;
|
|
10
10
|
fillId: number;
|
|
11
11
|
borderId: number;
|
|
12
|
+
rawAttributes: Record<string, string>;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function parseStylesXml(xml: string): XlsxStyleEntry[] {
|
|
@@ -29,6 +30,7 @@ export function parseStylesXml(xml: string): XlsxStyleEntry[] {
|
|
|
29
30
|
fontId: parseIntAttr(attrs["fontId"], 0),
|
|
30
31
|
fillId: parseIntAttr(attrs["fillId"], 0),
|
|
31
32
|
borderId: parseIntAttr(attrs["borderId"], 0),
|
|
33
|
+
rawAttributes: attrs,
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -33,7 +33,7 @@ import { parseWorkbookXml } from "./parse-workbook.ts";
|
|
|
33
33
|
import { parseSharedStringsXml } from "./parse-shared-strings.ts";
|
|
34
34
|
import { parseStylesXml, parseXmlAttributes } from "./parse-styles.ts";
|
|
35
35
|
import { parseSheetXml } from "./parse-sheet.ts";
|
|
36
|
-
import type { XlsxParsedCellValue } from "./parse-sheet.ts";
|
|
36
|
+
import type { XlsxParsedCellValue, XlsxParsedFormulaCachedValue } from "./parse-sheet.ts";
|
|
37
37
|
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
// Relationship type constants (SpreadsheetML / OPC)
|
|
@@ -177,7 +177,7 @@ function buildStyleRegistry(stylesXml: string): WorkbookStyleRegistry {
|
|
|
177
177
|
fillId: entry.fillId,
|
|
178
178
|
borderId: entry.borderId,
|
|
179
179
|
numFmtId: entry.numFmtId,
|
|
180
|
-
rawAttributes:
|
|
180
|
+
rawAttributes: entry.rawAttributes,
|
|
181
181
|
}),
|
|
182
182
|
);
|
|
183
183
|
|
|
@@ -239,7 +239,7 @@ function convertParsedCellValue(
|
|
|
239
239
|
return styleRef ? makeBlankCell(styleRef) : null;
|
|
240
240
|
|
|
241
241
|
case "text":
|
|
242
|
-
return makeTextCell(parsed.value,
|
|
242
|
+
return makeTextCell(parsed.value, parsed.fromSharedString, styleRef);
|
|
243
243
|
|
|
244
244
|
case "number":
|
|
245
245
|
return makeNumberCell(parsed.value, styleRef);
|
|
@@ -255,6 +255,7 @@ function convertParsedCellValue(
|
|
|
255
255
|
parsed.formula,
|
|
256
256
|
convertCachedFormulaValue(parsed.cachedValue),
|
|
257
257
|
styleRef,
|
|
258
|
+
parsed.referenceTokens,
|
|
258
259
|
);
|
|
259
260
|
}
|
|
260
261
|
}
|
|
@@ -282,19 +283,24 @@ function normalizeErrorCode(raw: string): CellErrorCode {
|
|
|
282
283
|
* parse layer does not currently surface for formula cells.
|
|
283
284
|
*/
|
|
284
285
|
function convertCachedFormulaValue(
|
|
285
|
-
|
|
286
|
+
cachedValue: XlsxParsedFormulaCachedValue | null,
|
|
286
287
|
): CachedFormulaValue | undefined {
|
|
287
|
-
if (
|
|
288
|
+
if (cachedValue === null) {
|
|
288
289
|
return undefined;
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
|
|
292
|
+
switch (cachedValue.type) {
|
|
293
|
+
case "blank":
|
|
294
|
+
return { type: "blank" };
|
|
295
|
+
case "number":
|
|
296
|
+
return { type: "number", value: cachedValue.value };
|
|
297
|
+
case "text":
|
|
298
|
+
return { type: "text", value: cachedValue.value };
|
|
299
|
+
case "boolean":
|
|
300
|
+
return { type: "boolean", value: cachedValue.value };
|
|
301
|
+
case "error":
|
|
302
|
+
return { type: "error", errorCode: normalizeErrorCode(cachedValue.errorCode) };
|
|
296
303
|
}
|
|
297
|
-
return { type: "text", value: raw };
|
|
298
304
|
}
|
|
299
305
|
|
|
300
306
|
// ---------------------------------------------------------------------------
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* explicit style are stored. Row and column metadata is also sparse.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { CellAddress, CellKey, CellValue, ColIndex, RowIndex } from "./cell.ts";
|
|
8
|
+
import type { CellAddress, CellKey, CellValue, ColIndex, RowIndex, StyleRef } from "./cell.ts";
|
|
9
9
|
import { cellKey } from "./cell.ts";
|
|
10
10
|
|
|
11
11
|
export type { ColIndex, RowIndex } from "./cell.ts";
|
|
@@ -23,6 +23,8 @@ export interface RowProperties {
|
|
|
23
23
|
hidden?: boolean;
|
|
24
24
|
/** Whether the row height was set via customHeight attribute. */
|
|
25
25
|
customHeight?: boolean;
|
|
26
|
+
/** Style index reference applied at the row level, if present. */
|
|
27
|
+
styleRef?: StyleRef;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface ColProperties {
|
|
@@ -34,6 +36,8 @@ export interface ColProperties {
|
|
|
34
36
|
hidden?: boolean;
|
|
35
37
|
/** Whether the width was explicitly customized. */
|
|
36
38
|
customWidth?: boolean;
|
|
39
|
+
/** Style index reference applied at the column level, if present. */
|
|
40
|
+
styleRef?: StyleRef;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
// ---------------------------------------------------------------------------
|
|
@@ -164,6 +168,60 @@ export function getCell(
|
|
|
164
168
|
return sheet.cells.get(cellKey(row, col));
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Set sparse row metadata.
|
|
173
|
+
* Passing `undefined`, or only default fields, removes the row entry.
|
|
174
|
+
*/
|
|
175
|
+
export function setRowProperties(
|
|
176
|
+
sheet: CanonicalSheet,
|
|
177
|
+
rowIndex: RowIndex,
|
|
178
|
+
props: Omit<RowProperties, "rowIndex"> | undefined,
|
|
179
|
+
): void {
|
|
180
|
+
if (!props || isDefaultRowProperties(props)) {
|
|
181
|
+
sheet.rowProps.delete(rowIndex);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
sheet.rowProps.set(rowIndex, {
|
|
186
|
+
rowIndex,
|
|
187
|
+
...props,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getRowProperties(
|
|
192
|
+
sheet: CanonicalSheet,
|
|
193
|
+
rowIndex: RowIndex,
|
|
194
|
+
): RowProperties | undefined {
|
|
195
|
+
return sheet.rowProps.get(rowIndex);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Set sparse column metadata.
|
|
200
|
+
* Passing `undefined`, or only default fields, removes the column entry.
|
|
201
|
+
*/
|
|
202
|
+
export function setColProperties(
|
|
203
|
+
sheet: CanonicalSheet,
|
|
204
|
+
colIndex: ColIndex,
|
|
205
|
+
props: Omit<ColProperties, "colIndex"> | undefined,
|
|
206
|
+
): void {
|
|
207
|
+
if (!props || isDefaultColProperties(props)) {
|
|
208
|
+
sheet.colProps.delete(colIndex);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
sheet.colProps.set(colIndex, {
|
|
213
|
+
colIndex,
|
|
214
|
+
...props,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function getColProperties(
|
|
219
|
+
sheet: CanonicalSheet,
|
|
220
|
+
colIndex: ColIndex,
|
|
221
|
+
): ColProperties | undefined {
|
|
222
|
+
return sheet.colProps.get(colIndex);
|
|
223
|
+
}
|
|
224
|
+
|
|
167
225
|
/**
|
|
168
226
|
* Return the effective address bounds for all occupied cells.
|
|
169
227
|
* Returns `null` when the sheet has no cells.
|
|
@@ -244,3 +302,25 @@ export function findMergesContaining(
|
|
|
244
302
|
col <= m.endCol,
|
|
245
303
|
);
|
|
246
304
|
}
|
|
305
|
+
|
|
306
|
+
function isDefaultRowProperties(
|
|
307
|
+
props: Omit<RowProperties, "rowIndex">,
|
|
308
|
+
): boolean {
|
|
309
|
+
return (
|
|
310
|
+
props.heightPt === undefined &&
|
|
311
|
+
props.hidden === undefined &&
|
|
312
|
+
props.customHeight === undefined &&
|
|
313
|
+
props.styleRef === undefined
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function isDefaultColProperties(
|
|
318
|
+
props: Omit<ColProperties, "colIndex">,
|
|
319
|
+
): boolean {
|
|
320
|
+
return (
|
|
321
|
+
props.widthChars === undefined &&
|
|
322
|
+
props.hidden === undefined &&
|
|
323
|
+
props.customWidth === undefined &&
|
|
324
|
+
props.styleRef === undefined
|
|
325
|
+
);
|
|
326
|
+
}
|
|
@@ -158,15 +158,15 @@ export function addSheet(
|
|
|
158
158
|
}
|
|
159
159
|
const orderIndex =
|
|
160
160
|
atIndex !== undefined
|
|
161
|
-
?
|
|
161
|
+
? clampSheetIndex(atIndex, workbook.sheetOrder.length)
|
|
162
162
|
: workbook.sheetOrder.length;
|
|
163
163
|
const sheet = createSheet(sheetId, name, orderIndex);
|
|
164
164
|
workbook.sheets.set(sheetId, sheet);
|
|
165
165
|
|
|
166
|
-
if (atIndex !== undefined &&
|
|
167
|
-
workbook.sheetOrder.splice(
|
|
166
|
+
if (atIndex !== undefined && orderIndex < workbook.sheetOrder.length) {
|
|
167
|
+
workbook.sheetOrder.splice(orderIndex, 0, sheetId);
|
|
168
168
|
// Reindex sheets after the insertion point
|
|
169
|
-
for (let i =
|
|
169
|
+
for (let i = orderIndex + 1; i < workbook.sheetOrder.length; i++) {
|
|
170
170
|
const id = workbook.sheetOrder[i];
|
|
171
171
|
const s = workbook.sheets.get(id);
|
|
172
172
|
if (s) s.orderIndex = i;
|
|
@@ -258,10 +258,10 @@ export function moveSheet(
|
|
|
258
258
|
): void {
|
|
259
259
|
const currentIndex = workbook.sheetOrder.indexOf(sheetId);
|
|
260
260
|
if (currentIndex === -1) throw new Error(`Sheet not found: ${sheetId}`);
|
|
261
|
-
|
|
261
|
+
const clampedTarget = clampSheetIndex(toIndex, workbook.sheetOrder.length - 1);
|
|
262
|
+
if (currentIndex === clampedTarget) return;
|
|
262
263
|
|
|
263
264
|
workbook.sheetOrder.splice(currentIndex, 1);
|
|
264
|
-
const clampedTarget = Math.min(toIndex, workbook.sheetOrder.length);
|
|
265
265
|
workbook.sheetOrder.splice(clampedTarget, 0, sheetId);
|
|
266
266
|
|
|
267
267
|
// Reindex all sheets
|
|
@@ -272,6 +272,10 @@ export function moveSheet(
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
function clampSheetIndex(index: number, maxIndex: number): number {
|
|
276
|
+
return Math.max(0, Math.min(index, maxIndex));
|
|
277
|
+
}
|
|
278
|
+
|
|
275
279
|
// ---------------------------------------------------------------------------
|
|
276
280
|
// Shared string table helpers
|
|
277
281
|
// ---------------------------------------------------------------------------
|