documonster 0.1.2 → 0.2.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.
@@ -13,13 +13,19 @@ export interface DataValidationsData {
13
13
  }
14
14
  /** Create a data-validation registry, optionally seeded from a parsed model. */
15
15
  export declare function createDataValidations(model?: ValidationModel): DataValidationsData;
16
- /** Register a validation at an exact address. */
17
- export declare function dataValidationAdd(dv: DataValidationsData, address: string, validation: DataValidation): DataValidation;
16
+ /**
17
+ * Register a validation for a cell or A1 range.
18
+ *
19
+ * Whole-column (`"A:A"`) and whole-row (`"1:3"`) references are expanded to
20
+ * Excel's sheet limits. Ranges are stored as a single `range:` model entry so
21
+ * they serialise to one data-validation element instead of one per cell.
22
+ */
23
+ export declare function dataValidationAdd(dv: DataValidationsData, ref: string, validation: DataValidation): DataValidation;
18
24
  /**
19
25
  * Resolve the validation that applies to `address`: first an exact-address
20
26
  * match, then any `range:`-prefixed key whose decoded range contains it.
21
27
  */
22
28
  export declare function dataValidationFind(dv: DataValidationsData, address: string): DataValidation | undefined;
23
- /** Clear the validation registered at `address`. */
24
- export declare function dataValidationRemove(dv: DataValidationsData, address: string): void;
29
+ /** Clear the validation registered for a cell or A1 range. */
30
+ export declare function dataValidationRemove(dv: DataValidationsData, ref: string): void;
25
31
  export {};
@@ -1,11 +1,101 @@
1
+ import { InvalidAddressError } from "../errors.js";
1
2
  import { colCache } from "../utils/col-cache.js";
2
3
  /** Create a data-validation registry, optionally seeded from a parsed model. */
3
4
  export function createDataValidations(model) {
4
5
  return { model: model || {} };
5
6
  }
6
- /** Register a validation at an exact address. */
7
- export function dataValidationAdd(dv, address, validation) {
8
- return (dv.model[address] = validation);
7
+ /**
8
+ * Register a validation for a cell or A1 range.
9
+ *
10
+ * Whole-column (`"A:A"`) and whole-row (`"1:3"`) references are expanded to
11
+ * Excel's sheet limits. Ranges are stored as a single `range:` model entry so
12
+ * they serialise to one data-validation element instead of one per cell.
13
+ */
14
+ export function dataValidationAdd(dv, ref, validation) {
15
+ return (dv.model[validationModelKey(ref)] = validation);
16
+ }
17
+ /** Excel's hard sheet limits, used to expand whole-column / whole-row refs. */
18
+ const EXCEL_MAX_ROW = 1048576;
19
+ const EXCEL_MAX_COL_LETTER = "XFD";
20
+ /** Whole-column reference, e.g. "A:A" or "A:C" (optionally `$`-anchored). */
21
+ const WHOLE_COLUMN_RE = /^\$?([A-Z]{1,3})\$?:\$?([A-Z]{1,3})$/;
22
+ /** Whole-row reference, e.g. "1:1" or "2:5" (optionally `$`-anchored). */
23
+ const WHOLE_ROW_RE = /^\$?(\d+)\$?:\$?(\d+)$/;
24
+ /** A single concrete cell reference, e.g. "A1", "XFD1048576" (no `$`). */
25
+ const CELL_RE = /^([A-Z]{1,3})(\d+)$/;
26
+ /**
27
+ * Assert that `cell` is a valid, in-bounds A1 cell reference (`A1`..`XFD1048576`).
28
+ * `colCache.decodeEx` is too lenient (it happily returns garbage for "foo",
29
+ * "A0", etc.), so we validate strictly here to avoid persisting a malformed
30
+ * `sqref` that would corrupt the workbook.
31
+ */
32
+ function assertValidCell(cell, ref) {
33
+ const m = cell.match(CELL_RE);
34
+ if (!m) {
35
+ throw new InvalidAddressError(ref, "not a valid cell or range reference");
36
+ }
37
+ colCache.l2n(m[1]); // throws ColumnOutOfBoundsError if the column is > XFD
38
+ const row = Number(m[2]);
39
+ if (row < 1 || row > EXCEL_MAX_ROW) {
40
+ throw new InvalidAddressError(ref, `row ${row} is outside 1..${EXCEL_MAX_ROW}`);
41
+ }
42
+ }
43
+ function decodeStrictCell(cell, ref) {
44
+ assertValidCell(cell, ref);
45
+ const m = cell.match(CELL_RE);
46
+ return { col: colCache.l2n(m[1]), row: Number(m[2]) };
47
+ }
48
+ function normaliseEndpoints(start, end, ref) {
49
+ const a = decodeStrictCell(start, ref);
50
+ const b = decodeStrictCell(end, ref);
51
+ const top = Math.min(a.row, b.row);
52
+ const left = Math.min(a.col, b.col);
53
+ const bottom = Math.max(a.row, b.row);
54
+ const right = Math.max(a.col, b.col);
55
+ return `${colCache.encodeAddress(top, left)}:${colCache.encodeAddress(bottom, right)}`;
56
+ }
57
+ /**
58
+ * Normalise a user-supplied A1 range reference into a concrete `top:bottom`
59
+ * range string suitable for a `range:` model key (and for the xlsx `sqref`).
60
+ *
61
+ * Handles the whole-column (`"A:A"`, `"A:C"`) and whole-row (`"1:3"`) shorthand
62
+ * by expanding them to Excel's sheet limits, mirroring how Excel itself stores
63
+ * a validation applied to an entire column (`A1:A1048576`). Any leading
64
+ * `Sheet!` qualifier is stripped, since data-validation `sqref` values are
65
+ * sheet-local. Absolute `$` markers are dropped. Throws
66
+ * {@link InvalidAddressError} for anything that is not a valid cell or range.
67
+ */
68
+ function normaliseRangeRef(ref) {
69
+ if (typeof ref !== "string" || ref.length === 0) {
70
+ throw new InvalidAddressError(String(ref), "range reference must be a non-empty string");
71
+ }
72
+ // Strip a leading sheet qualifier: `Sheet1!A1:A10` / `'My Sheet'!A:A`.
73
+ const bang = ref.lastIndexOf("!");
74
+ const local = bang === -1 ? ref : ref.slice(bang + 1);
75
+ const wholeCol = local.match(WHOLE_COLUMN_RE);
76
+ if (wholeCol) {
77
+ return normaliseEndpoints(`${wholeCol[1]}1`, `${wholeCol[2]}${EXCEL_MAX_ROW}`, ref);
78
+ }
79
+ const wholeRow = local.match(WHOLE_ROW_RE);
80
+ if (wholeRow) {
81
+ return normaliseEndpoints(`A${wholeRow[1]}`, `${EXCEL_MAX_COL_LETTER}${wholeRow[2]}`, ref);
82
+ }
83
+ // Concrete cell or cell:cell range — drop absolute markers so keys match the
84
+ // `sqref` form Excel writes, then validate every endpoint strictly.
85
+ const bare = local.replace(/\$/g, "");
86
+ const endpoints = bare.split(":");
87
+ if (endpoints.length > 2) {
88
+ throw new InvalidAddressError(ref, "not a valid cell or range reference");
89
+ }
90
+ if (endpoints.length === 2) {
91
+ return normaliseEndpoints(endpoints[0], endpoints[1], ref);
92
+ }
93
+ assertValidCell(bare, ref);
94
+ return bare;
95
+ }
96
+ function validationModelKey(ref) {
97
+ const normalised = normaliseRangeRef(ref);
98
+ return normalised.includes(":") ? `range:${normalised}` : normalised;
9
99
  }
10
100
  /**
11
101
  * Resolve the validation that applies to `address`: first an exact-address
@@ -42,7 +132,7 @@ export function dataValidationFind(dv, address) {
42
132
  }
43
133
  return undefined;
44
134
  }
45
- /** Clear the validation registered at `address`. */
46
- export function dataValidationRemove(dv, address) {
47
- dv.model[address] = undefined;
135
+ /** Clear the validation registered for a cell or A1 range. */
136
+ export function dataValidationRemove(dv, ref) {
137
+ dv.model[validationModelKey(ref)] = undefined;
48
138
  }
@@ -4,14 +4,104 @@ exports.createDataValidations = createDataValidations;
4
4
  exports.dataValidationAdd = dataValidationAdd;
5
5
  exports.dataValidationFind = dataValidationFind;
6
6
  exports.dataValidationRemove = dataValidationRemove;
7
+ const errors_1 = require("../errors.js");
7
8
  const col_cache_1 = require("../utils/col-cache.js");
8
9
  /** Create a data-validation registry, optionally seeded from a parsed model. */
9
10
  function createDataValidations(model) {
10
11
  return { model: model || {} };
11
12
  }
12
- /** Register a validation at an exact address. */
13
- function dataValidationAdd(dv, address, validation) {
14
- return (dv.model[address] = validation);
13
+ /**
14
+ * Register a validation for a cell or A1 range.
15
+ *
16
+ * Whole-column (`"A:A"`) and whole-row (`"1:3"`) references are expanded to
17
+ * Excel's sheet limits. Ranges are stored as a single `range:` model entry so
18
+ * they serialise to one data-validation element instead of one per cell.
19
+ */
20
+ function dataValidationAdd(dv, ref, validation) {
21
+ return (dv.model[validationModelKey(ref)] = validation);
22
+ }
23
+ /** Excel's hard sheet limits, used to expand whole-column / whole-row refs. */
24
+ const EXCEL_MAX_ROW = 1048576;
25
+ const EXCEL_MAX_COL_LETTER = "XFD";
26
+ /** Whole-column reference, e.g. "A:A" or "A:C" (optionally `$`-anchored). */
27
+ const WHOLE_COLUMN_RE = /^\$?([A-Z]{1,3})\$?:\$?([A-Z]{1,3})$/;
28
+ /** Whole-row reference, e.g. "1:1" or "2:5" (optionally `$`-anchored). */
29
+ const WHOLE_ROW_RE = /^\$?(\d+)\$?:\$?(\d+)$/;
30
+ /** A single concrete cell reference, e.g. "A1", "XFD1048576" (no `$`). */
31
+ const CELL_RE = /^([A-Z]{1,3})(\d+)$/;
32
+ /**
33
+ * Assert that `cell` is a valid, in-bounds A1 cell reference (`A1`..`XFD1048576`).
34
+ * `colCache.decodeEx` is too lenient (it happily returns garbage for "foo",
35
+ * "A0", etc.), so we validate strictly here to avoid persisting a malformed
36
+ * `sqref` that would corrupt the workbook.
37
+ */
38
+ function assertValidCell(cell, ref) {
39
+ const m = cell.match(CELL_RE);
40
+ if (!m) {
41
+ throw new errors_1.InvalidAddressError(ref, "not a valid cell or range reference");
42
+ }
43
+ col_cache_1.colCache.l2n(m[1]); // throws ColumnOutOfBoundsError if the column is > XFD
44
+ const row = Number(m[2]);
45
+ if (row < 1 || row > EXCEL_MAX_ROW) {
46
+ throw new errors_1.InvalidAddressError(ref, `row ${row} is outside 1..${EXCEL_MAX_ROW}`);
47
+ }
48
+ }
49
+ function decodeStrictCell(cell, ref) {
50
+ assertValidCell(cell, ref);
51
+ const m = cell.match(CELL_RE);
52
+ return { col: col_cache_1.colCache.l2n(m[1]), row: Number(m[2]) };
53
+ }
54
+ function normaliseEndpoints(start, end, ref) {
55
+ const a = decodeStrictCell(start, ref);
56
+ const b = decodeStrictCell(end, ref);
57
+ const top = Math.min(a.row, b.row);
58
+ const left = Math.min(a.col, b.col);
59
+ const bottom = Math.max(a.row, b.row);
60
+ const right = Math.max(a.col, b.col);
61
+ return `${col_cache_1.colCache.encodeAddress(top, left)}:${col_cache_1.colCache.encodeAddress(bottom, right)}`;
62
+ }
63
+ /**
64
+ * Normalise a user-supplied A1 range reference into a concrete `top:bottom`
65
+ * range string suitable for a `range:` model key (and for the xlsx `sqref`).
66
+ *
67
+ * Handles the whole-column (`"A:A"`, `"A:C"`) and whole-row (`"1:3"`) shorthand
68
+ * by expanding them to Excel's sheet limits, mirroring how Excel itself stores
69
+ * a validation applied to an entire column (`A1:A1048576`). Any leading
70
+ * `Sheet!` qualifier is stripped, since data-validation `sqref` values are
71
+ * sheet-local. Absolute `$` markers are dropped. Throws
72
+ * {@link InvalidAddressError} for anything that is not a valid cell or range.
73
+ */
74
+ function normaliseRangeRef(ref) {
75
+ if (typeof ref !== "string" || ref.length === 0) {
76
+ throw new errors_1.InvalidAddressError(String(ref), "range reference must be a non-empty string");
77
+ }
78
+ // Strip a leading sheet qualifier: `Sheet1!A1:A10` / `'My Sheet'!A:A`.
79
+ const bang = ref.lastIndexOf("!");
80
+ const local = bang === -1 ? ref : ref.slice(bang + 1);
81
+ const wholeCol = local.match(WHOLE_COLUMN_RE);
82
+ if (wholeCol) {
83
+ return normaliseEndpoints(`${wholeCol[1]}1`, `${wholeCol[2]}${EXCEL_MAX_ROW}`, ref);
84
+ }
85
+ const wholeRow = local.match(WHOLE_ROW_RE);
86
+ if (wholeRow) {
87
+ return normaliseEndpoints(`A${wholeRow[1]}`, `${EXCEL_MAX_COL_LETTER}${wholeRow[2]}`, ref);
88
+ }
89
+ // Concrete cell or cell:cell range — drop absolute markers so keys match the
90
+ // `sqref` form Excel writes, then validate every endpoint strictly.
91
+ const bare = local.replace(/\$/g, "");
92
+ const endpoints = bare.split(":");
93
+ if (endpoints.length > 2) {
94
+ throw new errors_1.InvalidAddressError(ref, "not a valid cell or range reference");
95
+ }
96
+ if (endpoints.length === 2) {
97
+ return normaliseEndpoints(endpoints[0], endpoints[1], ref);
98
+ }
99
+ assertValidCell(bare, ref);
100
+ return bare;
101
+ }
102
+ function validationModelKey(ref) {
103
+ const normalised = normaliseRangeRef(ref);
104
+ return normalised.includes(":") ? `range:${normalised}` : normalised;
15
105
  }
16
106
  /**
17
107
  * Resolve the validation that applies to `address`: first an exact-address
@@ -48,7 +138,7 @@ function dataValidationFind(dv, address) {
48
138
  }
49
139
  return undefined;
50
140
  }
51
- /** Clear the validation registered at `address`. */
52
- function dataValidationRemove(dv, address) {
53
- dv.model[address] = undefined;
141
+ /** Clear the validation registered for a cell or A1 range. */
142
+ function dataValidationRemove(dv, ref) {
143
+ dv.model[validationModelKey(ref)] = undefined;
54
144
  }
@@ -1,11 +1,101 @@
1
+ import { InvalidAddressError } from "../errors.js";
1
2
  import { colCache } from "../utils/col-cache.js";
2
3
  /** Create a data-validation registry, optionally seeded from a parsed model. */
3
4
  export function createDataValidations(model) {
4
5
  return { model: model || {} };
5
6
  }
6
- /** Register a validation at an exact address. */
7
- export function dataValidationAdd(dv, address, validation) {
8
- return (dv.model[address] = validation);
7
+ /**
8
+ * Register a validation for a cell or A1 range.
9
+ *
10
+ * Whole-column (`"A:A"`) and whole-row (`"1:3"`) references are expanded to
11
+ * Excel's sheet limits. Ranges are stored as a single `range:` model entry so
12
+ * they serialise to one data-validation element instead of one per cell.
13
+ */
14
+ export function dataValidationAdd(dv, ref, validation) {
15
+ return (dv.model[validationModelKey(ref)] = validation);
16
+ }
17
+ /** Excel's hard sheet limits, used to expand whole-column / whole-row refs. */
18
+ const EXCEL_MAX_ROW = 1048576;
19
+ const EXCEL_MAX_COL_LETTER = "XFD";
20
+ /** Whole-column reference, e.g. "A:A" or "A:C" (optionally `$`-anchored). */
21
+ const WHOLE_COLUMN_RE = /^\$?([A-Z]{1,3})\$?:\$?([A-Z]{1,3})$/;
22
+ /** Whole-row reference, e.g. "1:1" or "2:5" (optionally `$`-anchored). */
23
+ const WHOLE_ROW_RE = /^\$?(\d+)\$?:\$?(\d+)$/;
24
+ /** A single concrete cell reference, e.g. "A1", "XFD1048576" (no `$`). */
25
+ const CELL_RE = /^([A-Z]{1,3})(\d+)$/;
26
+ /**
27
+ * Assert that `cell` is a valid, in-bounds A1 cell reference (`A1`..`XFD1048576`).
28
+ * `colCache.decodeEx` is too lenient (it happily returns garbage for "foo",
29
+ * "A0", etc.), so we validate strictly here to avoid persisting a malformed
30
+ * `sqref` that would corrupt the workbook.
31
+ */
32
+ function assertValidCell(cell, ref) {
33
+ const m = cell.match(CELL_RE);
34
+ if (!m) {
35
+ throw new InvalidAddressError(ref, "not a valid cell or range reference");
36
+ }
37
+ colCache.l2n(m[1]); // throws ColumnOutOfBoundsError if the column is > XFD
38
+ const row = Number(m[2]);
39
+ if (row < 1 || row > EXCEL_MAX_ROW) {
40
+ throw new InvalidAddressError(ref, `row ${row} is outside 1..${EXCEL_MAX_ROW}`);
41
+ }
42
+ }
43
+ function decodeStrictCell(cell, ref) {
44
+ assertValidCell(cell, ref);
45
+ const m = cell.match(CELL_RE);
46
+ return { col: colCache.l2n(m[1]), row: Number(m[2]) };
47
+ }
48
+ function normaliseEndpoints(start, end, ref) {
49
+ const a = decodeStrictCell(start, ref);
50
+ const b = decodeStrictCell(end, ref);
51
+ const top = Math.min(a.row, b.row);
52
+ const left = Math.min(a.col, b.col);
53
+ const bottom = Math.max(a.row, b.row);
54
+ const right = Math.max(a.col, b.col);
55
+ return `${colCache.encodeAddress(top, left)}:${colCache.encodeAddress(bottom, right)}`;
56
+ }
57
+ /**
58
+ * Normalise a user-supplied A1 range reference into a concrete `top:bottom`
59
+ * range string suitable for a `range:` model key (and for the xlsx `sqref`).
60
+ *
61
+ * Handles the whole-column (`"A:A"`, `"A:C"`) and whole-row (`"1:3"`) shorthand
62
+ * by expanding them to Excel's sheet limits, mirroring how Excel itself stores
63
+ * a validation applied to an entire column (`A1:A1048576`). Any leading
64
+ * `Sheet!` qualifier is stripped, since data-validation `sqref` values are
65
+ * sheet-local. Absolute `$` markers are dropped. Throws
66
+ * {@link InvalidAddressError} for anything that is not a valid cell or range.
67
+ */
68
+ function normaliseRangeRef(ref) {
69
+ if (typeof ref !== "string" || ref.length === 0) {
70
+ throw new InvalidAddressError(String(ref), "range reference must be a non-empty string");
71
+ }
72
+ // Strip a leading sheet qualifier: `Sheet1!A1:A10` / `'My Sheet'!A:A`.
73
+ const bang = ref.lastIndexOf("!");
74
+ const local = bang === -1 ? ref : ref.slice(bang + 1);
75
+ const wholeCol = local.match(WHOLE_COLUMN_RE);
76
+ if (wholeCol) {
77
+ return normaliseEndpoints(`${wholeCol[1]}1`, `${wholeCol[2]}${EXCEL_MAX_ROW}`, ref);
78
+ }
79
+ const wholeRow = local.match(WHOLE_ROW_RE);
80
+ if (wholeRow) {
81
+ return normaliseEndpoints(`A${wholeRow[1]}`, `${EXCEL_MAX_COL_LETTER}${wholeRow[2]}`, ref);
82
+ }
83
+ // Concrete cell or cell:cell range — drop absolute markers so keys match the
84
+ // `sqref` form Excel writes, then validate every endpoint strictly.
85
+ const bare = local.replace(/\$/g, "");
86
+ const endpoints = bare.split(":");
87
+ if (endpoints.length > 2) {
88
+ throw new InvalidAddressError(ref, "not a valid cell or range reference");
89
+ }
90
+ if (endpoints.length === 2) {
91
+ return normaliseEndpoints(endpoints[0], endpoints[1], ref);
92
+ }
93
+ assertValidCell(bare, ref);
94
+ return bare;
95
+ }
96
+ function validationModelKey(ref) {
97
+ const normalised = normaliseRangeRef(ref);
98
+ return normalised.includes(":") ? `range:${normalised}` : normalised;
9
99
  }
10
100
  /**
11
101
  * Resolve the validation that applies to `address`: first an exact-address
@@ -42,7 +132,7 @@ export function dataValidationFind(dv, address) {
42
132
  }
43
133
  return undefined;
44
134
  }
45
- /** Clear the validation registered at `address`. */
46
- export function dataValidationRemove(dv, address) {
47
- dv.model[address] = undefined;
135
+ /** Clear the validation registered for a cell or A1 range. */
136
+ export function dataValidationRemove(dv, ref) {
137
+ dv.model[validationModelKey(ref)] = undefined;
48
138
  }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * documonster v0.1.2
2
+ * documonster v0.2.0
3
3
  * Zero-dependency TypeScript toolkit for Excel, Word, PDF, CSV, Markdown, XML & ZIP — one API across Node.js, Bun & browsers.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the Apache-2.0 License
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * documonster v0.1.2
2
+ * documonster v0.2.0
3
3
  * Zero-dependency TypeScript toolkit for Excel, Word, PDF, CSV, Markdown, XML & ZIP — one API across Node.js, Bun & browsers.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the Apache-2.0 License
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * documonster v0.1.2
2
+ * documonster v0.2.0
3
3
  * Zero-dependency TypeScript toolkit for Excel, Word, PDF, CSV, Markdown, XML & ZIP — one API across Node.js, Bun & browsers.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the Apache-2.0 License
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * documonster v0.1.2
2
+ * documonster v0.2.0
3
3
  * Zero-dependency TypeScript toolkit for Excel, Word, PDF, CSV, Markdown, XML & ZIP — one API across Node.js, Bun & browsers.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the Apache-2.0 License
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * documonster v0.1.2
2
+ * documonster v0.2.0
3
3
  * Zero-dependency TypeScript toolkit for Excel, Word, PDF, CSV, Markdown, XML & ZIP — one API across Node.js, Bun & browsers.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the Apache-2.0 License
@@ -501,14 +501,90 @@ this.Documonster = this.Documonster || {};
501
501
  }));
502
502
  //#endregion
503
503
  //#region src/modules/excel/core/data-validations.ts
504
+ init_errors$2();
504
505
  init_col_cache();
505
506
  /** Create a data-validation registry, optionally seeded from a parsed model. */
506
507
  function createDataValidations(model) {
507
508
  return { model: model || {} };
508
509
  }
509
- /** Register a validation at an exact address. */
510
- function dataValidationAdd(dv, address, validation) {
511
- return dv.model[address] = validation;
510
+ /**
511
+ * Register a validation for a cell or A1 range.
512
+ *
513
+ * Whole-column (`"A:A"`) and whole-row (`"1:3"`) references are expanded to
514
+ * Excel's sheet limits. Ranges are stored as a single `range:` model entry so
515
+ * they serialise to one data-validation element instead of one per cell.
516
+ */
517
+ function dataValidationAdd(dv, ref, validation) {
518
+ return dv.model[validationModelKey(ref)] = validation;
519
+ }
520
+ /** Excel's hard sheet limits, used to expand whole-column / whole-row refs. */
521
+ const EXCEL_MAX_ROW$2 = 1048576;
522
+ const EXCEL_MAX_COL_LETTER = "XFD";
523
+ /** Whole-column reference, e.g. "A:A" or "A:C" (optionally `$`-anchored). */
524
+ const WHOLE_COLUMN_RE = /^\$?([A-Z]{1,3})\$?:\$?([A-Z]{1,3})$/;
525
+ /** Whole-row reference, e.g. "1:1" or "2:5" (optionally `$`-anchored). */
526
+ const WHOLE_ROW_RE = /^\$?(\d+)\$?:\$?(\d+)$/;
527
+ /** A single concrete cell reference, e.g. "A1", "XFD1048576" (no `$`). */
528
+ const CELL_RE = /^([A-Z]{1,3})(\d+)$/;
529
+ /**
530
+ * Assert that `cell` is a valid, in-bounds A1 cell reference (`A1`..`XFD1048576`).
531
+ * `colCache.decodeEx` is too lenient (it happily returns garbage for "foo",
532
+ * "A0", etc.), so we validate strictly here to avoid persisting a malformed
533
+ * `sqref` that would corrupt the workbook.
534
+ */
535
+ function assertValidCell(cell, ref) {
536
+ const m = cell.match(CELL_RE);
537
+ if (!m) throw new InvalidAddressError(ref, "not a valid cell or range reference");
538
+ colCache.l2n(m[1]);
539
+ const row = Number(m[2]);
540
+ if (row < 1 || row > EXCEL_MAX_ROW$2) throw new InvalidAddressError(ref, `row ${row} is outside 1..${EXCEL_MAX_ROW$2}`);
541
+ }
542
+ function decodeStrictCell(cell, ref) {
543
+ assertValidCell(cell, ref);
544
+ const m = cell.match(CELL_RE);
545
+ return {
546
+ col: colCache.l2n(m[1]),
547
+ row: Number(m[2])
548
+ };
549
+ }
550
+ function normaliseEndpoints(start, end, ref) {
551
+ const a = decodeStrictCell(start, ref);
552
+ const b = decodeStrictCell(end, ref);
553
+ const top = Math.min(a.row, b.row);
554
+ const left = Math.min(a.col, b.col);
555
+ const bottom = Math.max(a.row, b.row);
556
+ const right = Math.max(a.col, b.col);
557
+ return `${colCache.encodeAddress(top, left)}:${colCache.encodeAddress(bottom, right)}`;
558
+ }
559
+ /**
560
+ * Normalise a user-supplied A1 range reference into a concrete `top:bottom`
561
+ * range string suitable for a `range:` model key (and for the xlsx `sqref`).
562
+ *
563
+ * Handles the whole-column (`"A:A"`, `"A:C"`) and whole-row (`"1:3"`) shorthand
564
+ * by expanding them to Excel's sheet limits, mirroring how Excel itself stores
565
+ * a validation applied to an entire column (`A1:A1048576`). Any leading
566
+ * `Sheet!` qualifier is stripped, since data-validation `sqref` values are
567
+ * sheet-local. Absolute `$` markers are dropped. Throws
568
+ * {@link InvalidAddressError} for anything that is not a valid cell or range.
569
+ */
570
+ function normaliseRangeRef(ref) {
571
+ if (typeof ref !== "string" || ref.length === 0) throw new InvalidAddressError(String(ref), "range reference must be a non-empty string");
572
+ const bang = ref.lastIndexOf("!");
573
+ const local = bang === -1 ? ref : ref.slice(bang + 1);
574
+ const wholeCol = local.match(WHOLE_COLUMN_RE);
575
+ if (wholeCol) return normaliseEndpoints(`${wholeCol[1]}1`, `${wholeCol[2]}${EXCEL_MAX_ROW$2}`, ref);
576
+ const wholeRow = local.match(WHOLE_ROW_RE);
577
+ if (wholeRow) return normaliseEndpoints(`A${wholeRow[1]}`, `${EXCEL_MAX_COL_LETTER}${wholeRow[2]}`, ref);
578
+ const bare = local.replace(/\$/g, "");
579
+ const endpoints = bare.split(":");
580
+ if (endpoints.length > 2) throw new InvalidAddressError(ref, "not a valid cell or range reference");
581
+ if (endpoints.length === 2) return normaliseEndpoints(endpoints[0], endpoints[1], ref);
582
+ assertValidCell(bare, ref);
583
+ return bare;
584
+ }
585
+ function validationModelKey(ref) {
586
+ const normalised = normaliseRangeRef(ref);
587
+ return normalised.includes(":") ? `range:${normalised}` : normalised;
512
588
  }
513
589
  /**
514
590
  * Resolve the validation that applies to `address`: first an exact-address
@@ -530,9 +606,9 @@ this.Documonster = this.Documonster || {};
530
606
  if (decoded.row >= tlAddr.row && decoded.row <= brAddr.row && decoded.col >= tlAddr.col && decoded.col <= brAddr.col) return dv.model[key];
531
607
  }
532
608
  }
533
- /** Clear the validation registered at `address`. */
534
- function dataValidationRemove(dv, address) {
535
- dv.model[address] = void 0;
609
+ /** Clear the validation registered for a cell or A1 range. */
610
+ function dataValidationRemove(dv, ref) {
611
+ dv.model[validationModelKey(ref)] = void 0;
536
612
  }
537
613
  //#endregion
538
614
  //#region src/modules/excel/core/range.ts
@@ -45606,7 +45682,6 @@ self.onmessage = async function(event) {
45606
45682
  var WRITE_AFTER_END_ERROR, AsyncStreamCodec, BufferedCodec;
45607
45683
  var init_streaming_compress_browser = __esmMin((() => {
45608
45684
  init_compress_base();
45609
- init_compress_browser();
45610
45685
  init_deflate_fallback();
45611
45686
  init_index_browser();
45612
45687
  init_defaults();