@shbernal/ts-xlsx 2.0.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.
- package/README.md +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +26 -4
- package/dist/core/address.js +21 -9
- package/dist/core/autofilter.d.ts +12 -1
- package/dist/core/autofilter.js +35 -12
- package/dist/core/axis-handle.d.ts +8 -0
- package/dist/core/axis-handle.js +14 -0
- package/dist/core/cell.d.ts +24 -0
- package/dist/core/cell.js +9 -1
- package/dist/core/color-resolution.d.ts +1 -1
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/column.d.ts +4 -1
- package/dist/core/column.js +31 -35
- package/dist/core/conditional-formatting-overlay.d.ts +5 -0
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +45 -5
- package/dist/core/conditional-formatting.js +74 -0
- package/dist/core/data-validation-overlay.d.ts +11 -0
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +9 -2
- package/dist/core/data-validation.js +26 -0
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +26 -8
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +2 -0
- package/dist/core/image.js +2 -0
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +12 -11
- package/dist/core/merge.js +36 -10
- package/dist/core/page-setup.d.ts +86 -2
- package/dist/core/page-setup.js +34 -1
- package/dist/core/pivot-table.d.ts +2 -0
- package/dist/core/pivot-table.js +23 -18
- package/dist/core/protection.d.ts +3 -0
- package/dist/core/protection.js +20 -14
- package/dist/core/row-input.d.ts +7 -0
- package/dist/core/row-input.js +10 -8
- package/dist/core/row.d.ts +4 -1
- package/dist/core/row.js +19 -23
- package/dist/core/style.d.ts +78 -7
- package/dist/core/style.js +90 -80
- package/dist/core/table.d.ts +26 -12
- package/dist/core/table.js +67 -33
- package/dist/core/theme.d.ts +3 -1
- package/dist/core/theme.js +4 -0
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- package/dist/core/workbook.d.ts +2 -2
- package/dist/core/worksheet-comments.d.ts +8 -0
- package/dist/core/worksheet-comments.js +18 -0
- package/dist/core/worksheet.d.ts +31 -7
- package/dist/core/worksheet.js +72 -86
- package/dist/customui/ribbon.js +23 -24
- package/dist/entries/core.d.ts +3 -3
- package/dist/entries/csv.d.ts +1 -1
- package/dist/entries/node-unavailable.d.ts +15 -0
- package/dist/entries/node-unavailable.js +20 -0
- package/dist/entries/node.d.ts +1 -0
- package/dist/entries/node.js +1 -0
- package/dist/entries/xlsx.d.ts +0 -1
- package/dist/entries/xlsx.js +0 -1
- package/dist/io/csv/read.js +1 -1
- package/dist/io/csv/write.d.ts +12 -2
- package/dist/io/csv/write.js +17 -2
- package/dist/io/opc/inflate.js +3 -13
- package/dist/io/opc/part-paths.d.ts +1 -0
- package/dist/io/opc/part-paths.js +15 -0
- package/dist/io/opc/read-opc.d.ts +22 -3
- package/dist/io/opc/read-opc.js +11 -39
- package/dist/io/opc/rels.d.ts +12 -6
- package/dist/io/opc/rels.js +3 -5
- package/dist/io/style/xf-style.d.ts +33 -4
- package/dist/io/style/xf-style.js +12 -1
- package/dist/io/xlsb/read-styles.js +6 -3
- package/dist/io/xlsb/read.d.ts +1 -1
- package/dist/io/xlsb/read.js +5 -8
- package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
- package/dist/io/xlsx/cell-accumulator.js +20 -58
- package/dist/io/xlsx/cell-value.d.ts +1 -1
- package/dist/io/xlsx/cell-value.js +7 -4
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +8 -7
- package/dist/io/xlsx/comments.js +6 -3
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
- package/dist/io/xlsx/conditional-formatting.js +81 -65
- package/dist/io/xlsx/data-validation.d.ts +10 -8
- package/dist/io/xlsx/data-validation.js +44 -41
- package/dist/io/xlsx/edit-vba.js +4 -11
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +26 -31
- package/dist/io/xlsx/images.js +33 -35
- package/dist/io/xlsx/package-plan.d.ts +9 -1
- package/dist/io/xlsx/package-plan.js +29 -16
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/read-pivot.js +4 -9
- package/dist/io/xlsx/read-rows.js +12 -13
- package/dist/io/xlsx/read-shared-strings.js +9 -47
- package/dist/io/xlsx/read-styles.d.ts +1 -1
- package/dist/io/xlsx/read-styles.js +65 -43
- package/dist/io/xlsx/read-worksheet.d.ts +10 -1
- package/dist/io/xlsx/read-worksheet.js +48 -63
- package/dist/io/xlsx/read.d.ts +3 -4
- package/dist/io/xlsx/read.js +124 -146
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +95 -30
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +23 -26
- package/dist/io/xlsx/styles.d.ts +3 -15
- package/dist/io/xlsx/styles.js +83 -105
- package/dist/io/xlsx/tables.js +14 -13
- package/dist/io/xlsx/theme-xml.js +8 -5
- package/dist/io/xlsx/threaded-comments.js +20 -19
- package/dist/io/xlsx/workbook-xml.js +43 -36
- package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
- package/dist/io/xlsx/worksheet-xml.js +47 -52
- package/dist/io/xlsx/write-stream.d.ts +14 -1
- package/dist/io/xlsx/write-stream.js +40 -33
- package/dist/io/xlsx/write.d.ts +5 -0
- package/dist/io/xlsx/write.js +155 -131
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/token-set.d.ts +15 -0
- package/dist/token-set.js +4 -0
- package/dist/vba/bytes.d.ts +1 -2
- package/dist/vba/bytes.js +1 -12
- package/dist/vba/cfb-format.d.ts +24 -0
- package/dist/vba/cfb-format.js +12 -0
- package/dist/vba/cfb-writer.js +17 -17
- package/dist/vba/cfb.js +1 -7
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/ms-ovba.js +32 -11
- package/dist/vba/project-editor.js +5 -34
- package/dist/vba/project.js +15 -31
- package/dist/vba/vba-encoding.js +1 -1
- package/dist/xml/xml-read.d.ts +117 -72
- package/dist/xml/xml-read.js +123 -169
- package/dist/xml/xml-scan.d.ts +100 -0
- package/dist/xml/xml-scan.js +208 -0
- package/dist/xml/xml.d.ts +44 -2
- package/dist/xml/xml.js +17 -5
- package/package.json +22 -6
|
@@ -1,26 +1,66 @@
|
|
|
1
1
|
import type { Color, DifferentialStyle } from './style.ts';
|
|
2
|
+
/** How a {@link CfValueObject} reads its `value`: `ST_CfvoType` verbatim. */
|
|
3
|
+
export type CfValueObjectType = 'num' | 'percent' | 'max' | 'min' | 'percentile' | 'formula';
|
|
4
|
+
/** Narrow a raw `<cfvo type>` token to a known {@link CfValueObjectType}. */
|
|
5
|
+
export declare const isCfValueObjectType: (value: string) => value is CfValueObjectType;
|
|
2
6
|
/**
|
|
3
7
|
* One anchor of a colour-scale, data-bar, or icon-set scale: a "conditional format value object".
|
|
4
8
|
* `type` names how `value` is read: a literal `num`, a `percent`/`percentile` of the range, a
|
|
5
9
|
* `formula`, or the range's own `min`/`max` (which carry no value).
|
|
6
10
|
*/
|
|
7
11
|
export interface CfValueObject {
|
|
8
|
-
type:
|
|
12
|
+
type: CfValueObjectType;
|
|
9
13
|
value?: number | string;
|
|
10
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* What a rule tests, as `<cfRule type>` carries it: `ST_CfType` verbatim.
|
|
17
|
+
*
|
|
18
|
+
* Closed, and stated in full rather than left as `string`, even though the library models only some
|
|
19
|
+
* of these in depth. Depth of modelling and legality are different questions: a `timePeriod` rule
|
|
20
|
+
* whose operands the library never inspects still round-trips, while a token outside this list is one
|
|
21
|
+
* Excel refuses to open, so it is refused on the way in and on the way out alike.
|
|
22
|
+
*/
|
|
23
|
+
export type ConditionalFormattingType = 'expression' | 'cellIs' | 'colorScale' | 'dataBar' | 'iconSet' | 'top10' | 'uniqueValues' | 'duplicateValues' | 'containsText' | 'notContainsText' | 'beginsWith' | 'endsWith' | 'containsBlanks' | 'notContainsBlanks' | 'containsErrors' | 'notContainsErrors' | 'timePeriod' | 'aboveAverage';
|
|
24
|
+
/** Narrow a raw `<cfRule type>` token to a known {@link ConditionalFormattingType}. */
|
|
25
|
+
export declare const isConditionalFormattingType: (value: string) => value is ConditionalFormattingType;
|
|
26
|
+
/**
|
|
27
|
+
* How a `cellIs` or text rule compares, as `ST_ConditionalFormattingOperator` enumerates it.
|
|
28
|
+
*
|
|
29
|
+
* Overlaps {@link import('./data-validation.ts').DataValidationOperator} in four members and diverges
|
|
30
|
+
* in the rest: this one has the text comparisons a validation has no use for, and spells "does not
|
|
31
|
+
* contain" as `notContains` where nothing else in the format does.
|
|
32
|
+
*/
|
|
33
|
+
export type ConditionalFormattingOperator = 'lessThan' | 'lessThanOrEqual' | 'equal' | 'notEqual' | 'greaterThanOrEqual' | 'greaterThan' | 'between' | 'notBetween' | 'containsText' | 'notContains' | 'beginsWith' | 'endsWith';
|
|
34
|
+
/** Narrow a raw `<cfRule operator>` token to a known {@link ConditionalFormattingOperator}. */
|
|
35
|
+
export declare const isConditionalFormattingOperator: (value: string) => value is ConditionalFormattingOperator;
|
|
36
|
+
/** The window a `timePeriod` rule matches against, relative to the day the sheet is recalculated. */
|
|
37
|
+
export type CfTimePeriod = 'today' | 'yesterday' | 'tomorrow' | 'last7Days' | 'thisMonth' | 'lastMonth' | 'nextMonth' | 'thisWeek' | 'lastWeek' | 'nextWeek';
|
|
38
|
+
/** Narrow a raw `<cfRule timePeriod>` token to a known {@link CfTimePeriod}. */
|
|
39
|
+
export declare const isCfTimePeriod: (value: string) => value is CfTimePeriod;
|
|
40
|
+
/**
|
|
41
|
+
* The named icon family an `iconSet` rule draws from, as `ST_IconSetType` enumerates it. The leading
|
|
42
|
+
* digit is the number of icons, which is also how many {@link CfValueObject} anchors the rule needs.
|
|
43
|
+
*
|
|
44
|
+
* The 2009 extension adds three more families (`3Stars`, `3Triangles`, `5Boxes`) under its own
|
|
45
|
+
* namespace. They are absent here because the classic `<iconSet>` element this list types cannot
|
|
46
|
+
* carry them; a file using one states it in the extension, which the library round-trips verbatim.
|
|
47
|
+
*/
|
|
48
|
+
export type IconSetType = '3Arrows' | '3ArrowsGray' | '3Flags' | '3TrafficLights1' | '3TrafficLights2' | '3Signs' | '3Symbols' | '3Symbols2' | '4Arrows' | '4ArrowsGray' | '4RedToBlack' | '4Rating' | '4TrafficLights' | '5Arrows' | '5ArrowsGray' | '5Rating' | '5Quarters';
|
|
49
|
+
/** Narrow a raw `<iconSet iconSet>` token to a known {@link IconSetType}. */
|
|
50
|
+
export declare const isIconSetType: (value: string) => value is IconSetType;
|
|
11
51
|
/**
|
|
12
52
|
* A single conditional-formatting rule. `type` is the OOXML cfRule type; the remaining fields carry
|
|
13
53
|
* the operands that type needs and are absent otherwise. A rule the library does not model in depth
|
|
14
54
|
* still preserves `type`, `priority`, `operator`, `formulae`, and `dxfId` across a round-trip.
|
|
15
55
|
*/
|
|
16
56
|
export interface ConditionalFormattingRule {
|
|
17
|
-
type:
|
|
57
|
+
type: ConditionalFormattingType;
|
|
18
58
|
/** Evaluation precedence; lower wins. Excel requires one, so the writer supplies it when absent. */
|
|
19
59
|
priority?: number;
|
|
20
60
|
/** Halt evaluation of lower-priority rules on any cell this rule matches. */
|
|
21
61
|
stopIfTrue?: boolean;
|
|
22
62
|
/** cellIs / text comparison operator (`greaterThan`, `between`, `beginsWith`, …). */
|
|
23
|
-
operator?:
|
|
63
|
+
operator?: ConditionalFormattingOperator;
|
|
24
64
|
/** Formula operands: cellIs bounds, an expression predicate, a containsText target formula, … */
|
|
25
65
|
formulae?: (string | number)[];
|
|
26
66
|
/** The literal a containsText / beginsWith / endsWith rule searches for. */
|
|
@@ -42,7 +82,7 @@ export interface ConditionalFormattingRule {
|
|
|
42
82
|
/** A dataBar's axis colour (the zero line between positive and negative bars). An x14 property. */
|
|
43
83
|
axisColor?: Color;
|
|
44
84
|
/** An iconSet's named icon family (e.g. `3TrafficLights1`). */
|
|
45
|
-
iconSet?:
|
|
85
|
+
iconSet?: IconSetType;
|
|
46
86
|
/** top10 rank cutoff. */
|
|
47
87
|
rank?: number;
|
|
48
88
|
/** top10: the rank is a percentage rather than a count. */
|
|
@@ -56,7 +96,7 @@ export interface ConditionalFormattingRule {
|
|
|
56
96
|
/** aboveAverage: match beyond this many standard deviations. */
|
|
57
97
|
stdDev?: number;
|
|
58
98
|
/** timePeriod window (`today`, `lastWeek`, …). */
|
|
59
|
-
timePeriod?:
|
|
99
|
+
timePeriod?: CfTimePeriod;
|
|
60
100
|
}
|
|
61
101
|
/** A set of rules bound to the range(s) they cover. `ref` is an OOXML `sqref`: one or more
|
|
62
102
|
* space-separated areas (`"A1:C1 A3:C3 A5:C5"`), the shape Excel writes when one rule is applied to
|
|
@@ -1,3 +1,77 @@
|
|
|
1
|
+
import { tokenSet } from '../token-set.js';
|
|
2
|
+
export const isCfValueObjectType = tokenSet({
|
|
3
|
+
num: true,
|
|
4
|
+
percent: true,
|
|
5
|
+
max: true,
|
|
6
|
+
min: true,
|
|
7
|
+
percentile: true,
|
|
8
|
+
formula: true,
|
|
9
|
+
});
|
|
10
|
+
export const isConditionalFormattingType = tokenSet({
|
|
11
|
+
expression: true,
|
|
12
|
+
cellIs: true,
|
|
13
|
+
colorScale: true,
|
|
14
|
+
dataBar: true,
|
|
15
|
+
iconSet: true,
|
|
16
|
+
top10: true,
|
|
17
|
+
uniqueValues: true,
|
|
18
|
+
duplicateValues: true,
|
|
19
|
+
containsText: true,
|
|
20
|
+
notContainsText: true,
|
|
21
|
+
beginsWith: true,
|
|
22
|
+
endsWith: true,
|
|
23
|
+
containsBlanks: true,
|
|
24
|
+
notContainsBlanks: true,
|
|
25
|
+
containsErrors: true,
|
|
26
|
+
notContainsErrors: true,
|
|
27
|
+
timePeriod: true,
|
|
28
|
+
aboveAverage: true,
|
|
29
|
+
});
|
|
30
|
+
export const isConditionalFormattingOperator = tokenSet({
|
|
31
|
+
lessThan: true,
|
|
32
|
+
lessThanOrEqual: true,
|
|
33
|
+
equal: true,
|
|
34
|
+
notEqual: true,
|
|
35
|
+
greaterThanOrEqual: true,
|
|
36
|
+
greaterThan: true,
|
|
37
|
+
between: true,
|
|
38
|
+
notBetween: true,
|
|
39
|
+
containsText: true,
|
|
40
|
+
notContains: true,
|
|
41
|
+
beginsWith: true,
|
|
42
|
+
endsWith: true,
|
|
43
|
+
});
|
|
44
|
+
export const isCfTimePeriod = tokenSet({
|
|
45
|
+
today: true,
|
|
46
|
+
yesterday: true,
|
|
47
|
+
tomorrow: true,
|
|
48
|
+
last7Days: true,
|
|
49
|
+
thisMonth: true,
|
|
50
|
+
lastMonth: true,
|
|
51
|
+
nextMonth: true,
|
|
52
|
+
thisWeek: true,
|
|
53
|
+
lastWeek: true,
|
|
54
|
+
nextWeek: true,
|
|
55
|
+
});
|
|
56
|
+
export const isIconSetType = tokenSet({
|
|
57
|
+
'3Arrows': true,
|
|
58
|
+
'3ArrowsGray': true,
|
|
59
|
+
'3Flags': true,
|
|
60
|
+
'3TrafficLights1': true,
|
|
61
|
+
'3TrafficLights2': true,
|
|
62
|
+
'3Signs': true,
|
|
63
|
+
'3Symbols': true,
|
|
64
|
+
'3Symbols2': true,
|
|
65
|
+
'4Arrows': true,
|
|
66
|
+
'4ArrowsGray': true,
|
|
67
|
+
'4RedToBlack': true,
|
|
68
|
+
'4Rating': true,
|
|
69
|
+
'4TrafficLights': true,
|
|
70
|
+
'5Arrows': true,
|
|
71
|
+
'5ArrowsGray': true,
|
|
72
|
+
'5Rating': true,
|
|
73
|
+
'5Quarters': true,
|
|
74
|
+
});
|
|
1
75
|
export function cloneConditionalFormatting(cf) {
|
|
2
76
|
return { ref: cf.ref, rules: cf.rules.map(cloneRule) };
|
|
3
77
|
}
|
|
@@ -11,6 +11,11 @@ export declare class DataValidationOverlay {
|
|
|
11
11
|
* (`<x14:dataValidation>`), the carrier Excel uses for a list source on another sheet and other
|
|
12
12
|
* shapes the standard element cannot express. The reader sets it for a rule found in that form so a
|
|
13
13
|
* round-trip writes it back there instead of silently corrupting the cross-sheet reference.
|
|
14
|
+
*
|
|
15
|
+
* @throws {AuthoringError} when `sqref` names no area at all. A rule attached to nothing covers no
|
|
16
|
+
* cell and is written back as the same unreadable text, so it is a mistake worth surfacing at the
|
|
17
|
+
* call. The reader does not reach this: it drops such an entry at its own boundary, where a foreign
|
|
18
|
+
* file's malformed attribute is supposed to be dropped.
|
|
14
19
|
*/
|
|
15
20
|
add(sqref: string, rule: DataValidation, options?: {
|
|
16
21
|
extended?: boolean;
|
|
@@ -23,6 +28,12 @@ export declare class DataValidationOverlay {
|
|
|
23
28
|
* validations.
|
|
24
29
|
*/
|
|
25
30
|
at(col: number, row: number): DataValidation | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Re-anchor every rule through a row or column splice, so a dropdown stays on the cells it was
|
|
33
|
+
* attached to rather than on whatever moved into their place. A rule whose every target area fell
|
|
34
|
+
* inside a deleted span is dropped with them.
|
|
35
|
+
*/
|
|
36
|
+
shift(axis: 'row' | 'col', start: number, count: number, delta: number): void;
|
|
26
37
|
/** Drop every validation, leaving the overlay empty. */
|
|
27
38
|
clear(): void;
|
|
28
39
|
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
import { AuthoringError } from '../errors.js';
|
|
2
|
+
import { replaceContents } from './containers.js';
|
|
1
3
|
import { cloneDataValidation, } from './data-validation.js';
|
|
2
|
-
import { decodeSqrefRects } from './merge.js';
|
|
4
|
+
import { decodeSqrefRects, shiftSqref } from './merge.js';
|
|
3
5
|
export class DataValidationOverlay {
|
|
4
6
|
#entries = [];
|
|
5
7
|
#rects = [];
|
|
6
8
|
add(sqref, rule, options = {}) {
|
|
9
|
+
const rects = decodeSqrefRects(sqref);
|
|
10
|
+
if (rects.length === 0) {
|
|
11
|
+
throw new AuthoringError(`data validation range "${sqref}" names no cells`);
|
|
12
|
+
}
|
|
7
13
|
const stored = cloneDataValidation(rule);
|
|
8
14
|
const entry = { sqref, rule: stored };
|
|
9
15
|
if (options.extended)
|
|
10
16
|
entry.extended = true;
|
|
11
17
|
this.#entries.push(entry);
|
|
12
|
-
this.#rects.push({ rects
|
|
18
|
+
this.#rects.push({ rects, rule: stored });
|
|
13
19
|
}
|
|
14
20
|
get entries() {
|
|
15
21
|
return this.#entries;
|
|
@@ -24,6 +30,19 @@ export class DataValidationOverlay {
|
|
|
24
30
|
}
|
|
25
31
|
return undefined;
|
|
26
32
|
}
|
|
33
|
+
shift(axis, start, count, delta) {
|
|
34
|
+
const entries = [];
|
|
35
|
+
const rects = [];
|
|
36
|
+
for (const entry of this.#entries) {
|
|
37
|
+
const sqref = shiftSqref(entry.sqref, axis, start, count, delta);
|
|
38
|
+
if (sqref === undefined)
|
|
39
|
+
continue;
|
|
40
|
+
entries.push({ ...entry, sqref });
|
|
41
|
+
rects.push({ rects: decodeSqrefRects(sqref), rule: entry.rule });
|
|
42
|
+
}
|
|
43
|
+
replaceContents(this.#entries, entries);
|
|
44
|
+
replaceContents(this.#rects, rects);
|
|
45
|
+
}
|
|
27
46
|
clear() {
|
|
28
47
|
this.#entries.length = 0;
|
|
29
48
|
this.#rects.length = 0;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/** The kind of constraint a validation enforces. `list` is a dropdown; `custom` is an arbitrary
|
|
2
|
-
* boolean formula;
|
|
3
|
-
|
|
2
|
+
* boolean formula; `none` constrains nothing and exists only to carry the rule's messages; the rest
|
|
3
|
+
* bound a typed value (`whole`/`decimal`/`date`/`time`/`textLength`). */
|
|
4
|
+
export type DataValidationType = 'none' | 'list' | 'whole' | 'decimal' | 'date' | 'time' | 'textLength' | 'custom';
|
|
5
|
+
/** Narrow a raw `<dataValidation type>` token to a known {@link DataValidationType}. */
|
|
6
|
+
export declare const isDataValidationType: (value: string) => value is DataValidationType;
|
|
4
7
|
/** How a typed validation compares its operand(s). Absent on a `list`/`custom` rule; defaults to
|
|
5
8
|
* `between` on a typed rule (the value Excel omits from the XML). */
|
|
6
9
|
export type DataValidationOperator = 'between' | 'notBetween' | 'equal' | 'notEqual' | 'greaterThan' | 'lessThan' | 'greaterThanOrEqual' | 'lessThanOrEqual';
|
|
10
|
+
/** Narrow a raw `<dataValidation operator>` token to a known {@link DataValidationOperator}. */
|
|
11
|
+
export declare const isDataValidationOperator: (value: string) => value is DataValidationOperator;
|
|
7
12
|
/** How Excel reacts to input that fails the rule. */
|
|
8
13
|
export type DataValidationErrorStyle = 'stop' | 'warning' | 'information';
|
|
14
|
+
/** Narrow a raw `<dataValidation errorStyle>` token to a known {@link DataValidationErrorStyle}. */
|
|
15
|
+
export declare const isDataValidationErrorStyle: (value: string) => value is DataValidationErrorStyle;
|
|
9
16
|
/** One validation rule. `formulae` holds the operand(s), `formula1` then optional `formula2`: a
|
|
10
17
|
* numeric literal is stored as a number, while a cell reference, defined name, or list source keeps
|
|
11
18
|
* its verbatim string. */
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
import { tokenSet } from '../token-set.js';
|
|
2
|
+
export const isDataValidationType = tokenSet({
|
|
3
|
+
none: true,
|
|
4
|
+
list: true,
|
|
5
|
+
whole: true,
|
|
6
|
+
decimal: true,
|
|
7
|
+
date: true,
|
|
8
|
+
time: true,
|
|
9
|
+
textLength: true,
|
|
10
|
+
custom: true,
|
|
11
|
+
});
|
|
12
|
+
export const isDataValidationOperator = tokenSet({
|
|
13
|
+
between: true,
|
|
14
|
+
notBetween: true,
|
|
15
|
+
equal: true,
|
|
16
|
+
notEqual: true,
|
|
17
|
+
greaterThan: true,
|
|
18
|
+
lessThan: true,
|
|
19
|
+
greaterThanOrEqual: true,
|
|
20
|
+
lessThanOrEqual: true,
|
|
21
|
+
});
|
|
22
|
+
export const isDataValidationErrorStyle = tokenSet({
|
|
23
|
+
stop: true,
|
|
24
|
+
warning: true,
|
|
25
|
+
information: true,
|
|
26
|
+
});
|
|
1
27
|
export function cloneDataValidation(rule) {
|
|
2
28
|
return {
|
|
3
29
|
...rule,
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import { type AutoFilter } from './autofilter.ts';
|
|
1
2
|
import { Cell } from './cell.ts';
|
|
3
|
+
import type { ConditionalFormattingOverlay } from './conditional-formatting-overlay.ts';
|
|
4
|
+
import type { DataValidationOverlay } from './data-validation-overlay.ts';
|
|
2
5
|
import { type AnchoredImage } from './image.ts';
|
|
3
6
|
import type { MergeRect } from './merge.ts';
|
|
4
7
|
import type { Table } from './table.ts';
|
|
5
8
|
import { type CellValue } from './value.ts';
|
|
9
|
+
import type { WorksheetComments } from './worksheet-comments.ts';
|
|
6
10
|
import type { ColumnProperties, RowProperties } from './worksheet.ts';
|
|
11
|
+
/**
|
|
12
|
+
* The sheet's autofilter, reached as a slot rather than held by reference like the containers beside
|
|
13
|
+
* it: it is a single replaceable value, and a splice that deletes every filtered line clears it.
|
|
14
|
+
*/
|
|
15
|
+
export interface AutoFilterSlot {
|
|
16
|
+
get(): AutoFilter | undefined;
|
|
17
|
+
set(next: AutoFilter | undefined): void;
|
|
18
|
+
}
|
|
7
19
|
interface GridStorage {
|
|
8
20
|
readonly rows: Map<number, Map<number, Cell>>;
|
|
9
21
|
readonly rowProperties: Map<number, RowProperties>;
|
|
@@ -12,6 +24,10 @@ interface GridStorage {
|
|
|
12
24
|
readonly mergeRects: MergeRect[];
|
|
13
25
|
readonly tables: Table[];
|
|
14
26
|
readonly images: AnchoredImage[];
|
|
27
|
+
readonly dataValidations: DataValidationOverlay;
|
|
28
|
+
readonly conditionalFormattings: ConditionalFormattingOverlay;
|
|
29
|
+
readonly comments: WorksheetComments;
|
|
30
|
+
readonly autoFilter: AutoFilterSlot;
|
|
15
31
|
}
|
|
16
32
|
export declare class GridEdits {
|
|
17
33
|
#private;
|
package/dist/core/grid-edits.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { decodeRange, encodeAddress, tryDecodeCellRef } from './address.js';
|
|
2
|
+
import { shiftAutoFilter } from './autofilter.js';
|
|
2
3
|
import { Cell, copyCellContent } from './cell.js';
|
|
3
4
|
import { replaceContents } from './containers.js';
|
|
5
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
4
6
|
import { isOneCellAnchor } from './image.js';
|
|
5
7
|
import { isSharedFormulaValue } from './value.js';
|
|
6
|
-
function shiftIndex(v, start, count, delta) {
|
|
7
|
-
return v < start ? v : v >= start + count ? v + delta : start;
|
|
8
|
-
}
|
|
9
8
|
export class GridEdits {
|
|
10
9
|
#rows;
|
|
11
10
|
#rowProperties;
|
|
@@ -14,6 +13,10 @@ export class GridEdits {
|
|
|
14
13
|
#mergeRects;
|
|
15
14
|
#tables;
|
|
16
15
|
#images;
|
|
16
|
+
#dataValidations;
|
|
17
|
+
#conditionalFormattings;
|
|
18
|
+
#comments;
|
|
19
|
+
#autoFilter;
|
|
17
20
|
constructor(storage) {
|
|
18
21
|
this.#rows = storage.rows;
|
|
19
22
|
this.#rowProperties = storage.rowProperties;
|
|
@@ -22,6 +25,10 @@ export class GridEdits {
|
|
|
22
25
|
this.#mergeRects = storage.mergeRects;
|
|
23
26
|
this.#tables = storage.tables;
|
|
24
27
|
this.#images = storage.images;
|
|
28
|
+
this.#dataValidations = storage.dataValidations;
|
|
29
|
+
this.#conditionalFormattings = storage.conditionalFormattings;
|
|
30
|
+
this.#comments = storage.comments;
|
|
31
|
+
this.#autoFilter = storage.autoFilter;
|
|
25
32
|
}
|
|
26
33
|
spliceRows(start, count, inserted) {
|
|
27
34
|
const delta = inserted.length - count;
|
|
@@ -43,6 +50,7 @@ export class GridEdits {
|
|
|
43
50
|
this.#shiftTables('row', start, count, delta);
|
|
44
51
|
this.#shiftImages('row', start, count, delta);
|
|
45
52
|
this.#reanchorSharedFormulas('row', start, count, delta);
|
|
53
|
+
this.#shiftRangeBoundOverlays('row', start, count, delta);
|
|
46
54
|
}
|
|
47
55
|
spliceColumns(start, count, inserts) {
|
|
48
56
|
const delta = inserts.length - count;
|
|
@@ -74,6 +82,16 @@ export class GridEdits {
|
|
|
74
82
|
this.#shiftTables('col', start, count, delta);
|
|
75
83
|
this.#shiftImages('col', start, count, delta);
|
|
76
84
|
this.#reanchorSharedFormulas('col', start, count, delta);
|
|
85
|
+
this.#shiftRangeBoundOverlays('col', start, count, delta);
|
|
86
|
+
}
|
|
87
|
+
#shiftRangeBoundOverlays(axis, start, count, delta) {
|
|
88
|
+
this.#dataValidations.shift(axis, start, count, delta);
|
|
89
|
+
this.#conditionalFormattings.shift(axis, start, count, delta);
|
|
90
|
+
this.#comments.shift(axis, start, count, delta);
|
|
91
|
+
const filter = this.#autoFilter.get();
|
|
92
|
+
if (filter !== undefined) {
|
|
93
|
+
this.#autoFilter.set(shiftAutoFilter(filter, axis, start, count, delta));
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
#relocateRow(cols, destRow) {
|
|
79
97
|
const moved = new Map();
|
|
@@ -99,8 +117,8 @@ export class GridEdits {
|
|
|
99
117
|
if (master === undefined)
|
|
100
118
|
continue;
|
|
101
119
|
const anchored = axis === 'row'
|
|
102
|
-
? encodeAddress(master.col, shiftIndex(master.row, start, count, delta))
|
|
103
|
-
: encodeAddress(shiftIndex(master.col, start, count, delta), master.row);
|
|
120
|
+
? encodeAddress(master.col, shiftIndex(master.row, start, count, delta, 'row'))
|
|
121
|
+
: encodeAddress(shiftIndex(master.col, start, count, delta, 'col'), master.row);
|
|
104
122
|
if (anchored === value.sharedFormula)
|
|
105
123
|
continue;
|
|
106
124
|
const reanchored = { ...value, sharedFormula: anchored };
|
|
@@ -121,7 +139,7 @@ export class GridEdits {
|
|
|
121
139
|
map.set(index, value);
|
|
122
140
|
}
|
|
123
141
|
#shiftMerges(axis, start, count, delta) {
|
|
124
|
-
const shift = (v) => shiftIndex(v, start, count, delta);
|
|
142
|
+
const shift = (v) => shiftIndex(v, start, count, delta, axis);
|
|
125
143
|
const merges = [];
|
|
126
144
|
const rects = [];
|
|
127
145
|
for (const range of this.#merges) {
|
|
@@ -131,7 +149,7 @@ export class GridEdits {
|
|
|
131
149
|
continue;
|
|
132
150
|
}
|
|
133
151
|
const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
|
|
134
|
-
if (lo
|
|
152
|
+
if (isDeletedSpan(lo, hi, start, count))
|
|
135
153
|
continue;
|
|
136
154
|
const rect = axis === 'row'
|
|
137
155
|
? { top: shift(top), left, bottom: shift(bottom), right }
|
|
@@ -151,7 +169,7 @@ export class GridEdits {
|
|
|
151
169
|
#shiftImages(axis, start, count, delta) {
|
|
152
170
|
const shiftPoint = (point) => {
|
|
153
171
|
const zeroBased = axis === 'row' ? point.row : point.col;
|
|
154
|
-
const shifted = shiftIndex(zeroBased + 1, start, count, delta) - 1;
|
|
172
|
+
const shifted = shiftIndex(zeroBased + 1, start, count, delta, axis) - 1;
|
|
155
173
|
if (shifted === zeroBased)
|
|
156
174
|
return point;
|
|
157
175
|
return axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a 1-based coordinate on `axis` lands after `count` lines are deleted at `start` and the
|
|
3
|
+
* replacement lines shift what follows by `delta`: before the edit it stays put, at or after the
|
|
4
|
+
* edited span it shifts, and inside a deleted span it clamps to the cut line. The clamp is the best
|
|
5
|
+
* effort for a geometry straddling the cut; a caller that must instead *drop* what a delete swallowed
|
|
6
|
+
* whole tests {@link isDeletedSpan} first.
|
|
7
|
+
*
|
|
8
|
+
* A shift never leaves the grid: the result is clamped to the axis's last line, so a region already
|
|
9
|
+
* touching the bottom (or the right edge) keeps its edge there instead of naming a line the format
|
|
10
|
+
* has no room for. That shrinks such a region by what it could not move, which is the lesser of the
|
|
11
|
+
* two evils and is what Excel does to the same region on the same edit.
|
|
12
|
+
*/
|
|
13
|
+
export declare function shiftIndex(v: number, start: number, count: number, delta: number, axis: 'row' | 'col'): number;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the inclusive span `lo..hi` lies entirely within the `count` lines deleted at `start`: the
|
|
16
|
+
* test that separates "this moved" from "this is gone". A single coordinate is the degenerate span
|
|
17
|
+
* `lo === hi`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isDeletedSpan(lo: number, hi: number, start: number, count: number): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MAX_COLUMN, MAX_ROW } from './address.js';
|
|
2
|
+
export function shiftIndex(v, start, count, delta, axis) {
|
|
3
|
+
const moved = v < start ? v : v >= start + count ? v + delta : start;
|
|
4
|
+
return Math.min(moved, axis === 'row' ? MAX_ROW : MAX_COLUMN);
|
|
5
|
+
}
|
|
6
|
+
export function isDeletedSpan(lo, hi, start, count) {
|
|
7
|
+
return lo >= start && hi < start + count;
|
|
8
|
+
}
|
package/dist/core/image.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const PX_TO_EMU = 9525;
|
|
|
17
17
|
* them; `oneCell` moves but keeps its size; `absolute` is pinned to the page and does neither. Excel
|
|
18
18
|
* defaults to `oneCell` when the attribute is omitted. */
|
|
19
19
|
export type ImageEditAs = 'oneCell' | 'twoCell' | 'absolute';
|
|
20
|
+
/** Narrow a raw `<xdr:twoCellAnchor editAs>` token to a known {@link ImageEditAs}. */
|
|
21
|
+
export declare const isImageEditAs: (value: string) => value is ImageEditAs;
|
|
20
22
|
/** A fixed image size in EMUs: the extent of a one-cell anchor, which pixel dimensions convert into
|
|
21
23
|
* via {@link PX_TO_EMU}. */
|
|
22
24
|
export interface Extent {
|
package/dist/core/image.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/core/merge.d.ts
CHANGED
|
@@ -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
|
|
@@ -21,5 +11,16 @@ export declare function masterOf(rects: readonly MergeRect[], row: number, col:
|
|
|
21
11
|
export declare function clearCoveredValues(rows: Map<number, Map<number, Cell>>, rect: MergeRect): void;
|
|
22
12
|
/** Decode an OOXML `sqref` (one or more space-separated ranges) into containment rectangles. A whole
|
|
23
13
|
* column or row leaves one axis unbounded, so its missing edges open to `Infinity` rather than
|
|
24
|
-
* clamping: a cell anywhere down the column still resolves inside it.
|
|
14
|
+
* clamping: a cell anywhere down the column still resolves inside it. An area that names no region
|
|
15
|
+
* that can exist contributes no rectangle, the reader's rule for every other foreign attribute. */
|
|
25
16
|
export declare function decodeSqrefRects(sqref: string): MergeRect[];
|
|
17
|
+
/**
|
|
18
|
+
* Re-anchor an OOXML `sqref` through a splice of `count` lines at `start` on `axis`, the inverse of
|
|
19
|
+
* {@link decodeSqrefRects}. Returns `undefined` when the splice deleted every area the `sqref` named:
|
|
20
|
+
* an empty `sqref` is not writable, so the entry holding it goes too.
|
|
21
|
+
*
|
|
22
|
+
* Each space-separated area shifts on its own, and one the splice does not move is returned as the
|
|
23
|
+
* *original text*. That matters for a file the library did not author: `B:B` and `B1:B1048576` decode
|
|
24
|
+
* identically, so a re-encode would rewrite a foreign spelling and cost the byte-clean round trip.
|
|
25
|
+
*/
|
|
26
|
+
export declare function shiftSqref(sqref: string, axis: 'row' | 'col', start: number, count: number, delta: number): string | undefined;
|
package/dist/core/merge.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
for (const rect of rects) {
|
|
4
|
-
if (row >= rect.top && row <= rect.bottom && col >= rect.left && col <= rect.right) {
|
|
5
|
-
return { row: rect.top, col: rect.left };
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
return { row, col };
|
|
9
|
-
}
|
|
1
|
+
import { encodeCornerRef, tryDecodeRange } from './address.js';
|
|
2
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
10
3
|
export function clearCoveredValues(rows, rect) {
|
|
11
4
|
for (let row = rect.top; row <= rect.bottom; row++) {
|
|
12
5
|
const cols = rows.get(row);
|
|
@@ -26,7 +19,10 @@ export function decodeSqrefRects(sqref) {
|
|
|
26
19
|
for (const part of sqref.split(/\s+/)) {
|
|
27
20
|
if (part === '')
|
|
28
21
|
continue;
|
|
29
|
-
const
|
|
22
|
+
const decoded = tryDecodeRange(part);
|
|
23
|
+
if (decoded === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
const { top, left, bottom, right } = decoded;
|
|
30
26
|
rects.push({
|
|
31
27
|
top: top ?? 1,
|
|
32
28
|
left: left ?? 1,
|
|
@@ -36,3 +32,33 @@ export function decodeSqrefRects(sqref) {
|
|
|
36
32
|
}
|
|
37
33
|
return rects;
|
|
38
34
|
}
|
|
35
|
+
export function shiftSqref(sqref, axis, start, count, delta) {
|
|
36
|
+
const areas = [];
|
|
37
|
+
for (const area of sqref.split(/\s+/)) {
|
|
38
|
+
if (area === '')
|
|
39
|
+
continue;
|
|
40
|
+
const shifted = shiftSqrefArea(area, axis, start, count, delta);
|
|
41
|
+
if (shifted !== undefined)
|
|
42
|
+
areas.push(shifted);
|
|
43
|
+
}
|
|
44
|
+
return areas.length > 0 ? areas.join(' ') : undefined;
|
|
45
|
+
}
|
|
46
|
+
function shiftSqrefArea(area, axis, start, count, delta) {
|
|
47
|
+
const decoded = tryDecodeRange(area);
|
|
48
|
+
if (decoded === undefined)
|
|
49
|
+
return area;
|
|
50
|
+
const { top, left, bottom, right } = decoded;
|
|
51
|
+
const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
|
|
52
|
+
if (lo === undefined || hi === undefined)
|
|
53
|
+
return area;
|
|
54
|
+
if (isDeletedSpan(lo, hi, start, count))
|
|
55
|
+
return undefined;
|
|
56
|
+
const movedLo = shiftIndex(lo, start, count, delta, axis);
|
|
57
|
+
const movedHi = shiftIndex(hi, start, count, delta, axis);
|
|
58
|
+
if (movedLo === lo && movedHi === hi)
|
|
59
|
+
return area;
|
|
60
|
+
const [tl, br] = axis === 'row'
|
|
61
|
+
? [encodeCornerRef(left, movedLo), encodeCornerRef(right, movedHi)]
|
|
62
|
+
: [encodeCornerRef(movedLo, top), encodeCornerRef(movedHi, bottom)];
|
|
63
|
+
return area.includes(':') ? `${tl}:${br}` : tl;
|
|
64
|
+
}
|