@onparallel/write-excel-file-data-validation 0.1.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/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/convertDateToExcelSerial.d.ts +2 -0
- package/dist/convertDateToExcelSerial.d.ts.map +1 -0
- package/dist/convertDateToExcelSerial.js +9 -0
- package/dist/convertDateToExcelSerial.js.map +1 -0
- package/dist/dataValidation.d.ts +4 -0
- package/dist/dataValidation.d.ts.map +1 -0
- package/dist/dataValidation.js +305 -0
- package/dist/dataValidation.js.map +1 -0
- package/dist/getCellCoordinate.d.ts +2 -0
- package/dist/getCellCoordinate.d.ts.map +1 -0
- package/dist/getCellCoordinate.js +13 -0
- package/dist/getCellCoordinate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/src/convertDateToExcelSerial.ts +9 -0
- package/src/dataValidation.ts +409 -0
- package/src/getCellCoordinate.ts +14 -0
- package/src/index.ts +8 -0
- package/src/types.ts +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Santi Albo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @onparallel/write-excel-file-data-validation
|
|
2
|
+
|
|
3
|
+
Data validation custom feature for [`write-excel-file`](https://www.npmjs.com/package/write-excel-file).
|
|
4
|
+
|
|
5
|
+
Adds support for Excel's "data validation" rules — dropdown lists, numeric/date/time ranges, text length limits, and custom formulas — without requiring a fork of the base package.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install write-excel-file @onparallel/write-excel-file-data-validation
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`write-excel-file` is a peer dependency (`^4.0.0`). The package is published publicly under the `@onparallel` scope — no authentication required to install.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Register the feature when calling `writeXlsxFile()`. `write-excel-file/node`'s built-in `SheetOptions` does not know about `dataValidation`, so intersect it with the `DataValidationSheetOptions` type exported by this package:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import writeXlsxFile, { type SheetOptions } from 'write-excel-file/node'
|
|
21
|
+
import dataValidation, {
|
|
22
|
+
type DataValidationSheetOptions
|
|
23
|
+
} from '@onparallel/write-excel-file-data-validation'
|
|
24
|
+
|
|
25
|
+
const sheetOptions: SheetOptions<any> & DataValidationSheetOptions = {
|
|
26
|
+
sheet: 'Sheet1',
|
|
27
|
+
dataValidation: [
|
|
28
|
+
{
|
|
29
|
+
cellRange: {
|
|
30
|
+
from: { row: 2, column: 1 },
|
|
31
|
+
to: { row: 10, column: 1 }
|
|
32
|
+
},
|
|
33
|
+
validation: {
|
|
34
|
+
type: 'list',
|
|
35
|
+
values: ['open', 'closed', 'pending'],
|
|
36
|
+
error: 'Pick one'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await writeXlsxFile(data, sheetOptions, { features: [dataValidation] }).toFile('out.xlsx')
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If you use the intersection in many places, alias it locally: `type MySheetOptions = SheetOptions<any> & DataValidationSheetOptions`.
|
|
46
|
+
|
|
47
|
+
Why not module augmentation? `write-excel-file/node` re-exports its types with `export type { ... }`, which TypeScript does not allow downstream packages to augment.
|
|
48
|
+
|
|
49
|
+
## Supported validation types
|
|
50
|
+
|
|
51
|
+
| `type` | OOXML `type` | Required fields |
|
|
52
|
+
| -------------- | ------------ | --------------------------------------------------------------------------------- |
|
|
53
|
+
| `'list'` | `list` | `values: string[]` **or** `valuesRange: string` (e.g. `'$E$4:$G$4'`) |
|
|
54
|
+
| `'integer'` | `whole` | `operator`, `value` (and `value2` for `'...'` / `'!...'`) |
|
|
55
|
+
| `'decimal'` | `decimal` | same as `integer` |
|
|
56
|
+
| `'date'` | `date` | `operator`, `value` (and `value2` for between operators); values: `Date`/`number` |
|
|
57
|
+
| `'time'` | `time` | same as `date`; `Date` values are reduced to fractional time-of-day |
|
|
58
|
+
| `'textLength'` | `textLength` | same as `integer` |
|
|
59
|
+
| `'custom'` | `custom` | `formula: string` |
|
|
60
|
+
| `'any'` | (no `type`) | Only metadata (used to attach a tooltip/error message without restricting input) |
|
|
61
|
+
|
|
62
|
+
Operators (mirror the symbols used by `conditionalFormatting`):
|
|
63
|
+
|
|
64
|
+
| Symbol | OOXML |
|
|
65
|
+
| ------ | -------------------- |
|
|
66
|
+
| `<` | `lessThan` |
|
|
67
|
+
| `>` | `greaterThan` |
|
|
68
|
+
| `<=` | `lessThanOrEqual` |
|
|
69
|
+
| `>=` | `greaterThanOrEqual` |
|
|
70
|
+
| `=` | `equal` |
|
|
71
|
+
| `!=` | `notEqual` |
|
|
72
|
+
| `...` | `between` |
|
|
73
|
+
| `!...` | `notBetween` |
|
|
74
|
+
|
|
75
|
+
## Common options (any type)
|
|
76
|
+
|
|
77
|
+
| Field | Default | OOXML attribute |
|
|
78
|
+
| ------------------ | -------- | -------------------------------------------------------------------------------- |
|
|
79
|
+
| `error` | — | `error` |
|
|
80
|
+
| `errorTitle` | — | `errorTitle` |
|
|
81
|
+
| `errorStyle` | `'stop'` | `errorStyle` |
|
|
82
|
+
| `input` | — | `prompt` |
|
|
83
|
+
| `inputTitle` | — | `promptTitle` |
|
|
84
|
+
| `allowBlank` | `true` | `allowBlank` |
|
|
85
|
+
| `showErrorMessage` | `true` | `showErrorMessage` |
|
|
86
|
+
| `showInputMessage` | `true` | `showInputMessage` |
|
|
87
|
+
| `showDropdown` | `true` | `showDropDown` (inverted: OOXML `"1"` means hide) — only meaningful for `'list'` |
|
|
88
|
+
|
|
89
|
+
Maximum lengths enforced (Excel limits): titles ≤ 32 characters, messages ≤ 255 characters, inline list formulas ≤ 255 characters.
|
|
90
|
+
|
|
91
|
+
## Limitations
|
|
92
|
+
|
|
93
|
+
- **Sheet-level only.** This feature reads `dataValidation` from `sheetOptions`. Per-cell validation (attaching a `validation` property to a cell object) is **not** supported because `write-excel-file`'s feature API does not expose sheet data to the transform hook. If you need per-cell validation, encode it as a sheet-level rule with a single-cell range:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
{ cellRange: { from: { row: 5, column: 3 }, to: { row: 5, column: 3 } }, validation: { ... } }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- **Inline list commas and quotes.** Excel uses `,` as the list separator inside `<formula1>"…"</formula1>` and `"` as the surrounding delimiter — neither has a working escape. Values containing `,` or `"` are rejected; use `valuesRange` to reference a range of cells instead.
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
The [`examples/`](./examples) folder contains runnable TypeScript scripts — one per validation type — that emit `.xlsx` files next to themselves. Open the generated files in Excel/Numbers/LibreOffice to confirm each rule works.
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
npm install
|
|
107
|
+
npx tsx examples/list-inline.ts # run a single example
|
|
108
|
+
npm run examples # run them all
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
See [`examples/README.md`](./examples/README.md) for the full list.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
npm install
|
|
117
|
+
npm test # vitest
|
|
118
|
+
npm run typecheck # tsc --noEmit
|
|
119
|
+
npm run build # emit dist/ via tsc
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convertDateToExcelSerial.d.ts","sourceRoot":"","sources":["../src/convertDateToExcelSerial.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAEnE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Excel serial date = count of days since 1900-01-01 (with the 1900-leap-year quirk).
|
|
2
|
+
// 70 * 365 + 19 = days between 1900-01-01 (Excel epoch) and 1970-01-01 (Unix epoch),
|
|
3
|
+
// accounting for 19 leap days in that range.
|
|
4
|
+
const DAYS_BEFORE_UNIX_EPOCH = 70 * 365 + 19;
|
|
5
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
6
|
+
export default function convertDateToExcelSerial(date) {
|
|
7
|
+
return date.getTime() / MS_PER_DAY + DAYS_BEFORE_UNIX_EPOCH;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=convertDateToExcelSerial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convertDateToExcelSerial.js","sourceRoot":"","sources":["../src/convertDateToExcelSerial.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,qFAAqF;AACrF,6CAA6C;AAC7C,MAAM,sBAAsB,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,CAAA;AAC5C,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAEtC,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,IAAU;IAC1D,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,UAAU,GAAG,sBAAsB,CAAA;AAC5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataValidation.d.ts","sourceRoot":"","sources":["../src/dataValidation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAmBpD,QAAA,MAAM,cAAc,EAAE,OAAO,CAAC,GAAG,CA8BhC,CAAA;AAED,eAAe,cAAc,CAAA"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { insertElementMarkupAccordingToOrderOfSiblings, getOrderOfSiblings, sanitizeAttributeValue, sanitizeTextContent } from 'write-excel-file/utility';
|
|
2
|
+
import getCellCoordinate from './getCellCoordinate.js';
|
|
3
|
+
import convertDateToExcelSerial from './convertDateToExcelSerial.js';
|
|
4
|
+
const MAX_TITLE_LENGTH = 32;
|
|
5
|
+
const MAX_MESSAGE_LENGTH = 255;
|
|
6
|
+
// `Feature<any>` rather than `Feature<unknown>` so the feature is assignable to
|
|
7
|
+
// `Feature<FileContent>` for any `FileContent` (Buffer/Blob/etc) chosen by the caller.
|
|
8
|
+
// This feature only transforms worksheet XML and never reads or writes file content,
|
|
9
|
+
// so the `FileContent` parameter is irrelevant here.
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
const dataValidation = {
|
|
12
|
+
files: {
|
|
13
|
+
transform: {
|
|
14
|
+
'xl/worksheets/sheet{id}.xml': {
|
|
15
|
+
// `<dataValidations/>` must be placed between `<conditionalFormatting/>` and `<hyperlinks/>`
|
|
16
|
+
// inside `<worksheet/>`. We use `transform()` (not `insert()`) so the inserter can place
|
|
17
|
+
// the markup at the canonical sibling position even when other features have already
|
|
18
|
+
// appended siblings (such as `<drawing/>`).
|
|
19
|
+
transform: (xml, sheetOptions) => {
|
|
20
|
+
const { dataValidation: rules } = sheetOptions;
|
|
21
|
+
if (rules && rules.length > 0) {
|
|
22
|
+
const dataValidationsXml = getDataValidationsXml(rules);
|
|
23
|
+
const order = getOrderOfSiblings('xl/worksheets/sheet{id}.xml', 'worksheet');
|
|
24
|
+
if (!order) {
|
|
25
|
+
throw new Error('write-excel-file did not return an order of siblings for `worksheet`');
|
|
26
|
+
}
|
|
27
|
+
return insertElementMarkupAccordingToOrderOfSiblings(xml, dataValidationsXml, order, 'worksheet');
|
|
28
|
+
}
|
|
29
|
+
return xml;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
export default dataValidation;
|
|
36
|
+
function getDataValidationsXml(rules) {
|
|
37
|
+
let xml = `<dataValidations count="${rules.length}">`;
|
|
38
|
+
for (const rule of rules) {
|
|
39
|
+
xml += getDataValidationXml(rule);
|
|
40
|
+
}
|
|
41
|
+
xml += '</dataValidations>';
|
|
42
|
+
return xml;
|
|
43
|
+
}
|
|
44
|
+
function getDataValidationXml(rule) {
|
|
45
|
+
const { cellRange, validation } = rule;
|
|
46
|
+
if (!cellRange) {
|
|
47
|
+
throw new Error('A data validation rule must specify a `cellRange`');
|
|
48
|
+
}
|
|
49
|
+
if (!validation) {
|
|
50
|
+
throw new Error('A data validation rule must specify a `validation`');
|
|
51
|
+
}
|
|
52
|
+
const { from, to } = cellRange;
|
|
53
|
+
if (!from || !to) {
|
|
54
|
+
throw new Error('A data validation `cellRange` must specify both `from` and `to`');
|
|
55
|
+
}
|
|
56
|
+
if (from.row < 1 || from.column < 1 || to.row < 1 || to.column < 1) {
|
|
57
|
+
throw new Error(`Data validation \`cellRange\` row and column indexes must be >= 1 (1-based), got: ${JSON.stringify(cellRange)}`);
|
|
58
|
+
}
|
|
59
|
+
if (to.row < from.row || to.column < from.column) {
|
|
60
|
+
throw new Error(`Data validation \`cellRange\` \`to\` must be greater than or equal to \`from\`, got: ${JSON.stringify(cellRange)}`);
|
|
61
|
+
}
|
|
62
|
+
const sqref = getCellCoordinate(from.row - 1, from.column - 1) +
|
|
63
|
+
':' +
|
|
64
|
+
getCellCoordinate(to.row - 1, to.column - 1);
|
|
65
|
+
const attributes = [];
|
|
66
|
+
const xlsxType = getXlsxType(validation.type);
|
|
67
|
+
if (xlsxType) {
|
|
68
|
+
attributes.push(`type="${xlsxType}"`);
|
|
69
|
+
}
|
|
70
|
+
if (hasOperator(validation.type)) {
|
|
71
|
+
attributes.push(`operator="${getXlsxOperatorName(validation.operator)}"`);
|
|
72
|
+
}
|
|
73
|
+
const { error, errorTitle, errorStyle, input, inputTitle, allowBlank, showErrorMessage, showInputMessage } = validation;
|
|
74
|
+
// OOXML defaults `allowBlank` to false; we default it to true (matching xlsxwriter).
|
|
75
|
+
attributes.push(`allowBlank="${allowBlank === false ? '0' : '1'}"`);
|
|
76
|
+
// `showDropDown` in OOXML is inverted: setting it to `1` HIDES the dropdown.
|
|
77
|
+
if (validation.type === 'list' && validation.showDropdown === false) {
|
|
78
|
+
attributes.push('showDropDown="1"');
|
|
79
|
+
}
|
|
80
|
+
// OOXML defaults `showErrorMessage`/`showInputMessage` to false; without them Excel does
|
|
81
|
+
// not reject invalid input. Default both to true.
|
|
82
|
+
attributes.push(`showErrorMessage="${showErrorMessage === false ? '0' : '1'}"`);
|
|
83
|
+
attributes.push(`showInputMessage="${showInputMessage === false ? '0' : '1'}"`);
|
|
84
|
+
if (errorStyle !== undefined) {
|
|
85
|
+
validateErrorStyle(errorStyle);
|
|
86
|
+
// OOXML default for `errorStyle` is `stop`. Skip the attribute in that case.
|
|
87
|
+
if (errorStyle !== 'stop') {
|
|
88
|
+
attributes.push(`errorStyle="${errorStyle}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (errorTitle !== undefined) {
|
|
92
|
+
validateTitleLength(errorTitle, 'errorTitle');
|
|
93
|
+
attributes.push(`errorTitle="${sanitizeAttributeValue(errorTitle)}"`);
|
|
94
|
+
}
|
|
95
|
+
if (error !== undefined) {
|
|
96
|
+
validateMessageLength(error, 'error');
|
|
97
|
+
attributes.push(`error="${sanitizeAttributeValue(error)}"`);
|
|
98
|
+
}
|
|
99
|
+
if (inputTitle !== undefined) {
|
|
100
|
+
validateTitleLength(inputTitle, 'inputTitle');
|
|
101
|
+
attributes.push(`promptTitle="${sanitizeAttributeValue(inputTitle)}"`);
|
|
102
|
+
}
|
|
103
|
+
if (input !== undefined) {
|
|
104
|
+
validateMessageLength(input, 'input');
|
|
105
|
+
attributes.push(`prompt="${sanitizeAttributeValue(input)}"`);
|
|
106
|
+
}
|
|
107
|
+
attributes.push(`sqref="${sqref}"`);
|
|
108
|
+
const [formula1, formula2] = getFormulas(validation);
|
|
109
|
+
let xml = `<dataValidation ${attributes.join(' ')}>`;
|
|
110
|
+
if (formula1 !== undefined) {
|
|
111
|
+
xml += `<formula1>${sanitizeTextContent(formula1)}</formula1>`;
|
|
112
|
+
}
|
|
113
|
+
if (formula2 !== undefined) {
|
|
114
|
+
xml += `<formula2>${sanitizeTextContent(formula2)}</formula2>`;
|
|
115
|
+
}
|
|
116
|
+
xml += '</dataValidation>';
|
|
117
|
+
return xml;
|
|
118
|
+
}
|
|
119
|
+
function getXlsxType(type) {
|
|
120
|
+
switch (type) {
|
|
121
|
+
case 'list':
|
|
122
|
+
return 'list';
|
|
123
|
+
case 'integer':
|
|
124
|
+
return 'whole';
|
|
125
|
+
case 'decimal':
|
|
126
|
+
return 'decimal';
|
|
127
|
+
case 'date':
|
|
128
|
+
return 'date';
|
|
129
|
+
case 'time':
|
|
130
|
+
return 'time';
|
|
131
|
+
case 'textLength':
|
|
132
|
+
return 'textLength';
|
|
133
|
+
case 'custom':
|
|
134
|
+
return 'custom';
|
|
135
|
+
case 'any':
|
|
136
|
+
return undefined;
|
|
137
|
+
default:
|
|
138
|
+
throw new Error(`Unknown data validation type: ${type}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function hasOperator(type) {
|
|
142
|
+
switch (type) {
|
|
143
|
+
case 'integer':
|
|
144
|
+
case 'decimal':
|
|
145
|
+
case 'date':
|
|
146
|
+
case 'time':
|
|
147
|
+
case 'textLength':
|
|
148
|
+
return true;
|
|
149
|
+
default:
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function getXlsxOperatorName(operator) {
|
|
154
|
+
switch (operator) {
|
|
155
|
+
case '<':
|
|
156
|
+
return 'lessThan';
|
|
157
|
+
case '>':
|
|
158
|
+
return 'greaterThan';
|
|
159
|
+
case '<=':
|
|
160
|
+
return 'lessThanOrEqual';
|
|
161
|
+
case '>=':
|
|
162
|
+
return 'greaterThanOrEqual';
|
|
163
|
+
case '=':
|
|
164
|
+
return 'equal';
|
|
165
|
+
case '!=':
|
|
166
|
+
return 'notEqual';
|
|
167
|
+
case '...':
|
|
168
|
+
return 'between';
|
|
169
|
+
case '!...':
|
|
170
|
+
return 'notBetween';
|
|
171
|
+
default:
|
|
172
|
+
throw new Error(`Unknown data validation operator: ${operator}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function isBetweenOperator(operator) {
|
|
176
|
+
return operator === '...' || operator === '!...';
|
|
177
|
+
}
|
|
178
|
+
function getFormulas(validation) {
|
|
179
|
+
switch (validation.type) {
|
|
180
|
+
case 'list': {
|
|
181
|
+
if ('values' in validation) {
|
|
182
|
+
return [getListFormulaFromValues(validation.values)];
|
|
183
|
+
}
|
|
184
|
+
if ('valuesRange' in validation) {
|
|
185
|
+
return [validation.valuesRange];
|
|
186
|
+
}
|
|
187
|
+
throw new Error('A `list` data validation must specify either `values` or `valuesRange`');
|
|
188
|
+
}
|
|
189
|
+
case 'custom': {
|
|
190
|
+
if (validation.formula === undefined) {
|
|
191
|
+
throw new Error('A `custom` data validation must specify a `formula`');
|
|
192
|
+
}
|
|
193
|
+
return [validation.formula];
|
|
194
|
+
}
|
|
195
|
+
case 'any':
|
|
196
|
+
return [];
|
|
197
|
+
case 'integer':
|
|
198
|
+
case 'decimal':
|
|
199
|
+
case 'textLength': {
|
|
200
|
+
if (validation.value === undefined) {
|
|
201
|
+
throw new Error(`A \`${validation.type}\` data validation must specify a \`value\``);
|
|
202
|
+
}
|
|
203
|
+
if (isBetweenOperator(validation.operator)) {
|
|
204
|
+
if (validation.value2 === undefined) {
|
|
205
|
+
throw new Error(`A \`${validation.type}\` data validation with operator \`${validation.operator}\` must specify a \`value2\``);
|
|
206
|
+
}
|
|
207
|
+
return [
|
|
208
|
+
formatNumericValue(validation.value, validation.type),
|
|
209
|
+
formatNumericValue(validation.value2, validation.type)
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
return [formatNumericValue(validation.value, validation.type)];
|
|
213
|
+
}
|
|
214
|
+
case 'date':
|
|
215
|
+
case 'time': {
|
|
216
|
+
if (validation.value === undefined) {
|
|
217
|
+
throw new Error(`A \`${validation.type}\` data validation must specify a \`value\``);
|
|
218
|
+
}
|
|
219
|
+
if (isBetweenOperator(validation.operator)) {
|
|
220
|
+
const value2 = validation.value2;
|
|
221
|
+
if (value2 === undefined) {
|
|
222
|
+
throw new Error(`A \`${validation.type}\` data validation with operator \`${validation.operator}\` must specify a \`value2\``);
|
|
223
|
+
}
|
|
224
|
+
return [
|
|
225
|
+
formatDateOrTimeValue(validation.value, validation.type),
|
|
226
|
+
formatDateOrTimeValue(value2, validation.type)
|
|
227
|
+
];
|
|
228
|
+
}
|
|
229
|
+
return [formatDateOrTimeValue(validation.value, validation.type)];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function getListFormulaFromValues(values) {
|
|
234
|
+
if (!Array.isArray(values)) {
|
|
235
|
+
throw new Error(`A \`list\` data validation \`values\` must be an array, got: ${String(values)}`);
|
|
236
|
+
}
|
|
237
|
+
if (values.length === 0) {
|
|
238
|
+
throw new Error('A `list` data validation `values` must contain at least one value');
|
|
239
|
+
}
|
|
240
|
+
for (const value of values) {
|
|
241
|
+
if (typeof value !== 'string') {
|
|
242
|
+
throw new Error(`A \`list\` data validation \`values\` must be strings, got: ${String(value)}`);
|
|
243
|
+
}
|
|
244
|
+
if (value.indexOf(',') !== -1) {
|
|
245
|
+
throw new Error(`A \`list\` data validation \`values\` cannot contain commas (used by Excel as the separator inside the inline list formula). Use \`valuesRange\` to reference a range of cells instead. Got: "${value}"`);
|
|
246
|
+
}
|
|
247
|
+
if (value.indexOf('"') !== -1) {
|
|
248
|
+
throw new Error(`A \`list\` data validation \`values\` cannot contain double-quote characters. Use \`valuesRange\` to reference a range of cells instead. Got: "${value}"`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const serialized = values.join(',');
|
|
252
|
+
if (serialized.length + 2 > MAX_MESSAGE_LENGTH) {
|
|
253
|
+
throw new Error(`A \`list\` data validation \`values\` serialized to "${serialized}" exceeds the maximum length of ${MAX_MESSAGE_LENGTH} characters. Use \`valuesRange\` instead to reference a range of cells.`);
|
|
254
|
+
}
|
|
255
|
+
return `"${serialized}"`;
|
|
256
|
+
}
|
|
257
|
+
function formatNumericValue(value, type) {
|
|
258
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
259
|
+
throw new Error(`A \`${type}\` data validation \`value\` must be a finite number, got: ${String(value)}`);
|
|
260
|
+
}
|
|
261
|
+
return String(value);
|
|
262
|
+
}
|
|
263
|
+
function formatDateOrTimeValue(value, type) {
|
|
264
|
+
let serial;
|
|
265
|
+
if (value instanceof Date) {
|
|
266
|
+
serial = convertDateToExcelSerial(value);
|
|
267
|
+
}
|
|
268
|
+
else if (typeof value === 'number') {
|
|
269
|
+
serial = value;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
throw new Error(`A \`${type}\` data validation \`value\` must be a Date or a number, got: ${String(value)}`);
|
|
273
|
+
}
|
|
274
|
+
if (!Number.isFinite(serial)) {
|
|
275
|
+
throw new Error(`A \`${type}\` data validation \`value\` is not a finite number (possibly an invalid Date), got: ${String(value)}`);
|
|
276
|
+
}
|
|
277
|
+
// For `time`, Excel expects a fractional value in [0, 1) representing the time of day.
|
|
278
|
+
// A `Date` carries both a date and a time, so reduce the serial to its fractional part.
|
|
279
|
+
if (type === 'time') {
|
|
280
|
+
serial = serial - Math.floor(serial);
|
|
281
|
+
}
|
|
282
|
+
return String(serial);
|
|
283
|
+
}
|
|
284
|
+
function validateErrorStyle(errorStyle) {
|
|
285
|
+
if (errorStyle !== 'stop' && errorStyle !== 'warning' && errorStyle !== 'information') {
|
|
286
|
+
throw new Error(`Unknown data validation \`errorStyle\`: ${errorStyle}. Expected \`stop\`, \`warning\` or \`information\``);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function validateTitleLength(title, fieldName) {
|
|
290
|
+
if (typeof title !== 'string') {
|
|
291
|
+
throw new Error(`Data validation \`${fieldName}\` must be a string`);
|
|
292
|
+
}
|
|
293
|
+
if (title.length > MAX_TITLE_LENGTH) {
|
|
294
|
+
throw new Error(`Data validation \`${fieldName}\` is longer than ${MAX_TITLE_LENGTH} characters: "${title}"`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function validateMessageLength(message, fieldName) {
|
|
298
|
+
if (typeof message !== 'string') {
|
|
299
|
+
throw new Error(`Data validation \`${fieldName}\` must be a string`);
|
|
300
|
+
}
|
|
301
|
+
if (message.length > MAX_MESSAGE_LENGTH) {
|
|
302
|
+
throw new Error(`Data validation \`${fieldName}\` is longer than ${MAX_MESSAGE_LENGTH} characters: "${message}"`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=dataValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataValidation.js","sourceRoot":"","sources":["../src/dataValidation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getCellCoordinate.d.ts","sourceRoot":"","sources":["../src/getCellCoordinate.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEvF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const LETTERS_COUNT = 26;
|
|
2
|
+
function getColumnLetter(columnIndex) {
|
|
3
|
+
const prefix = Math.floor(columnIndex / LETTERS_COUNT);
|
|
4
|
+
const letter = String.fromCharCode(97 + (columnIndex % LETTERS_COUNT)).toUpperCase();
|
|
5
|
+
if (prefix === 0) {
|
|
6
|
+
return letter;
|
|
7
|
+
}
|
|
8
|
+
return getColumnLetter(prefix - 1) + letter;
|
|
9
|
+
}
|
|
10
|
+
export default function getCellCoordinate(rowIndex, columnIndex) {
|
|
11
|
+
return `${getColumnLetter(columnIndex)}${rowIndex + 1}`;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=getCellCoordinate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getCellCoordinate.js","sourceRoot":"","sources":["../src/getCellCoordinate.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB,SAAS,eAAe,CAAC,WAAmB;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,aAAa,CAAC,CAAA;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IACpF,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QAClB,OAAO,MAAM,CAAA;IACd,CAAC;IACD,OAAO,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,CAAA;AAC5C,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,QAAgB,EAAE,WAAmB;IAC9E,OAAO,GAAG,eAAe,CAAC,WAAW,CAAC,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAA;AACxD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAC7C,YAAY,EACX,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type DataValidationOperatorOnOneValue = '<' | '<=' | '>' | '>=' | '=' | '!=';
|
|
2
|
+
export type DataValidationOperatorOnTwoValues = '...' | '!...';
|
|
3
|
+
export type DataValidationOperator = DataValidationOperatorOnOneValue | DataValidationOperatorOnTwoValues;
|
|
4
|
+
export type DataValidationErrorStyle = 'stop' | 'warning' | 'information';
|
|
5
|
+
interface DataValidationCommonProperties {
|
|
6
|
+
error?: string;
|
|
7
|
+
errorTitle?: string;
|
|
8
|
+
errorStyle?: DataValidationErrorStyle;
|
|
9
|
+
input?: string;
|
|
10
|
+
inputTitle?: string;
|
|
11
|
+
allowBlank?: boolean;
|
|
12
|
+
showErrorMessage?: boolean;
|
|
13
|
+
showInputMessage?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface DataValidationListWithValues extends DataValidationCommonProperties {
|
|
16
|
+
type: 'list';
|
|
17
|
+
values: string[];
|
|
18
|
+
showDropdown?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface DataValidationListWithRange extends DataValidationCommonProperties {
|
|
21
|
+
type: 'list';
|
|
22
|
+
valuesRange: string;
|
|
23
|
+
showDropdown?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface DataValidationNumericOnOneValue extends DataValidationCommonProperties {
|
|
26
|
+
type: 'integer' | 'decimal' | 'textLength';
|
|
27
|
+
operator: DataValidationOperatorOnOneValue;
|
|
28
|
+
value: number;
|
|
29
|
+
}
|
|
30
|
+
interface DataValidationNumericOnTwoValues extends DataValidationCommonProperties {
|
|
31
|
+
type: 'integer' | 'decimal' | 'textLength';
|
|
32
|
+
operator: DataValidationOperatorOnTwoValues;
|
|
33
|
+
value: number;
|
|
34
|
+
value2: number;
|
|
35
|
+
}
|
|
36
|
+
interface DataValidationDateOnOneValue extends DataValidationCommonProperties {
|
|
37
|
+
type: 'date' | 'time';
|
|
38
|
+
operator: DataValidationOperatorOnOneValue;
|
|
39
|
+
value: Date | number;
|
|
40
|
+
}
|
|
41
|
+
interface DataValidationDateOnTwoValues extends DataValidationCommonProperties {
|
|
42
|
+
type: 'date' | 'time';
|
|
43
|
+
operator: DataValidationOperatorOnTwoValues;
|
|
44
|
+
value: Date | number;
|
|
45
|
+
value2: Date | number;
|
|
46
|
+
}
|
|
47
|
+
interface DataValidationCustom extends DataValidationCommonProperties {
|
|
48
|
+
type: 'custom';
|
|
49
|
+
formula: string;
|
|
50
|
+
}
|
|
51
|
+
interface DataValidationAny extends DataValidationCommonProperties {
|
|
52
|
+
type: 'any';
|
|
53
|
+
}
|
|
54
|
+
export type DataValidation = DataValidationListWithValues | DataValidationListWithRange | DataValidationNumericOnOneValue | DataValidationNumericOnTwoValues | DataValidationDateOnOneValue | DataValidationDateOnTwoValues | DataValidationCustom | DataValidationAny;
|
|
55
|
+
export interface DataValidationRule {
|
|
56
|
+
cellRange: {
|
|
57
|
+
from: {
|
|
58
|
+
row: number;
|
|
59
|
+
column: number;
|
|
60
|
+
};
|
|
61
|
+
to: {
|
|
62
|
+
row: number;
|
|
63
|
+
column: number;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
validation: DataValidation;
|
|
67
|
+
}
|
|
68
|
+
export interface DataValidationSheetOptions {
|
|
69
|
+
dataValidation?: DataValidationRule[];
|
|
70
|
+
}
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gCAAgC,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAA;AAEnF,MAAM,MAAM,iCAAiC,GAAG,KAAK,GAAG,MAAM,CAAA;AAE9D,MAAM,MAAM,sBAAsB,GAC/B,gCAAgC,GAChC,iCAAiC,CAAA;AAEpC,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,CAAA;AAEzE,UAAU,8BAA8B;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,wBAAwB,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,UAAU,4BAA6B,SAAQ,8BAA8B;IAC5E,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,YAAY,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,UAAU,2BAA4B,SAAQ,8BAA8B;IAC3E,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,UAAU,+BAAgC,SAAQ,8BAA8B;IAC/E,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAA;IAC1C,QAAQ,EAAE,gCAAgC,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;CACb;AAED,UAAU,gCAAiC,SAAQ,8BAA8B;IAChF,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAA;IAC1C,QAAQ,EAAE,iCAAiC,CAAA;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACd;AAED,UAAU,4BAA6B,SAAQ,8BAA8B;IAC5E,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,QAAQ,EAAE,gCAAgC,CAAA;IAC1C,KAAK,EAAE,IAAI,GAAG,MAAM,CAAA;CACpB;AAED,UAAU,6BAA8B,SAAQ,8BAA8B;IAC7E,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,QAAQ,EAAE,iCAAiC,CAAA;IAC3C,KAAK,EAAE,IAAI,GAAG,MAAM,CAAA;IACpB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAA;CACrB;AAED,UAAU,oBAAqB,SAAQ,8BAA8B;IACpE,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAkB,SAAQ,8BAA8B;IACjE,IAAI,EAAE,KAAK,CAAA;CACX;AAED,MAAM,MAAM,cAAc,GACvB,4BAA4B,GAC5B,2BAA2B,GAC3B,+BAA+B,GAC/B,gCAAgC,GAChC,4BAA4B,GAC5B,6BAA6B,GAC7B,oBAAoB,GACpB,iBAAiB,CAAA;AAEpB,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE;QACV,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;QACrC,EAAE,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KACnC,CAAA;IACD,UAAU,EAAE,cAAc,CAAA;CAC1B;AAED,MAAM,WAAW,0BAA0B;IAC1C,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAA;CACrC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onparallel/write-excel-file-data-validation",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Data validation custom feature for write-excel-file",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src/**/*.ts",
|
|
18
|
+
"!src/**/*.test.ts",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/onparallel/write-excel-file-data-validation.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/onparallel/write-excel-file-data-validation/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/onparallel/write-excel-file-data-validation#readme",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=22"
|
|
32
|
+
},
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"format": "prettier --write .",
|
|
40
|
+
"format:check": "prettier --check .",
|
|
41
|
+
"examples": "for f in examples/*.ts; do tsx \"$f\"; done",
|
|
42
|
+
"prepublishOnly": "npm run format:check && npm run typecheck && npm test && npm run build"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"xlsx",
|
|
46
|
+
"excel",
|
|
47
|
+
"data-validation",
|
|
48
|
+
"dropdown",
|
|
49
|
+
"write-excel-file"
|
|
50
|
+
],
|
|
51
|
+
"author": "Santi Albo <santi@onparallel.com>",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"write-excel-file": "^4.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^20.0.0",
|
|
58
|
+
"prettier": "^3.3.0",
|
|
59
|
+
"tsx": "^4.7.0",
|
|
60
|
+
"typescript": "^5.4.0",
|
|
61
|
+
"vitest": "^1.6.0",
|
|
62
|
+
"write-excel-file": "^4.0.7"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Excel serial date = count of days since 1900-01-01 (with the 1900-leap-year quirk).
|
|
2
|
+
// 70 * 365 + 19 = days between 1900-01-01 (Excel epoch) and 1970-01-01 (Unix epoch),
|
|
3
|
+
// accounting for 19 leap days in that range.
|
|
4
|
+
const DAYS_BEFORE_UNIX_EPOCH = 70 * 365 + 19
|
|
5
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000
|
|
6
|
+
|
|
7
|
+
export default function convertDateToExcelSerial(date: Date): number {
|
|
8
|
+
return date.getTime() / MS_PER_DAY + DAYS_BEFORE_UNIX_EPOCH
|
|
9
|
+
}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import {
|
|
2
|
+
insertElementMarkupAccordingToOrderOfSiblings,
|
|
3
|
+
getOrderOfSiblings,
|
|
4
|
+
sanitizeAttributeValue,
|
|
5
|
+
sanitizeTextContent
|
|
6
|
+
} from 'write-excel-file/utility'
|
|
7
|
+
import type { Feature } from 'write-excel-file/node'
|
|
8
|
+
|
|
9
|
+
import getCellCoordinate from './getCellCoordinate.js'
|
|
10
|
+
import convertDateToExcelSerial from './convertDateToExcelSerial.js'
|
|
11
|
+
import type {
|
|
12
|
+
DataValidation,
|
|
13
|
+
DataValidationOperator,
|
|
14
|
+
DataValidationRule,
|
|
15
|
+
DataValidationSheetOptions
|
|
16
|
+
} from './types.js'
|
|
17
|
+
|
|
18
|
+
const MAX_TITLE_LENGTH = 32
|
|
19
|
+
const MAX_MESSAGE_LENGTH = 255
|
|
20
|
+
|
|
21
|
+
// `Feature<any>` rather than `Feature<unknown>` so the feature is assignable to
|
|
22
|
+
// `Feature<FileContent>` for any `FileContent` (Buffer/Blob/etc) chosen by the caller.
|
|
23
|
+
// This feature only transforms worksheet XML and never reads or writes file content,
|
|
24
|
+
// so the `FileContent` parameter is irrelevant here.
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
const dataValidation: Feature<any> = {
|
|
27
|
+
files: {
|
|
28
|
+
transform: {
|
|
29
|
+
'xl/worksheets/sheet{id}.xml': {
|
|
30
|
+
// `<dataValidations/>` must be placed between `<conditionalFormatting/>` and `<hyperlinks/>`
|
|
31
|
+
// inside `<worksheet/>`. We use `transform()` (not `insert()`) so the inserter can place
|
|
32
|
+
// the markup at the canonical sibling position even when other features have already
|
|
33
|
+
// appended siblings (such as `<drawing/>`).
|
|
34
|
+
transform: (xml, sheetOptions) => {
|
|
35
|
+
const { dataValidation: rules } = sheetOptions as DataValidationSheetOptions
|
|
36
|
+
if (rules && rules.length > 0) {
|
|
37
|
+
const dataValidationsXml = getDataValidationsXml(rules)
|
|
38
|
+
const order = getOrderOfSiblings('xl/worksheets/sheet{id}.xml', 'worksheet')
|
|
39
|
+
if (!order) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'write-excel-file did not return an order of siblings for `worksheet`'
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return insertElementMarkupAccordingToOrderOfSiblings(
|
|
45
|
+
xml,
|
|
46
|
+
dataValidationsXml,
|
|
47
|
+
order,
|
|
48
|
+
'worksheet'
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
return xml
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default dataValidation
|
|
59
|
+
|
|
60
|
+
function getDataValidationsXml(rules: DataValidationRule[]): string {
|
|
61
|
+
let xml = `<dataValidations count="${rules.length}">`
|
|
62
|
+
for (const rule of rules) {
|
|
63
|
+
xml += getDataValidationXml(rule)
|
|
64
|
+
}
|
|
65
|
+
xml += '</dataValidations>'
|
|
66
|
+
return xml
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getDataValidationXml(rule: DataValidationRule): string {
|
|
70
|
+
const { cellRange, validation } = rule
|
|
71
|
+
|
|
72
|
+
if (!cellRange) {
|
|
73
|
+
throw new Error('A data validation rule must specify a `cellRange`')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!validation) {
|
|
77
|
+
throw new Error('A data validation rule must specify a `validation`')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { from, to } = cellRange
|
|
81
|
+
|
|
82
|
+
if (!from || !to) {
|
|
83
|
+
throw new Error('A data validation `cellRange` must specify both `from` and `to`')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (from.row < 1 || from.column < 1 || to.row < 1 || to.column < 1) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Data validation \`cellRange\` row and column indexes must be >= 1 (1-based), got: ${JSON.stringify(cellRange)}`
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (to.row < from.row || to.column < from.column) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Data validation \`cellRange\` \`to\` must be greater than or equal to \`from\`, got: ${JSON.stringify(cellRange)}`
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const sqref =
|
|
99
|
+
getCellCoordinate(from.row - 1, from.column - 1) +
|
|
100
|
+
':' +
|
|
101
|
+
getCellCoordinate(to.row - 1, to.column - 1)
|
|
102
|
+
|
|
103
|
+
const attributes: string[] = []
|
|
104
|
+
|
|
105
|
+
const xlsxType = getXlsxType(validation.type)
|
|
106
|
+
if (xlsxType) {
|
|
107
|
+
attributes.push(`type="${xlsxType}"`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (hasOperator(validation.type)) {
|
|
111
|
+
attributes.push(
|
|
112
|
+
`operator="${getXlsxOperatorName((validation as { operator: DataValidationOperator }).operator)}"`
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const {
|
|
117
|
+
error,
|
|
118
|
+
errorTitle,
|
|
119
|
+
errorStyle,
|
|
120
|
+
input,
|
|
121
|
+
inputTitle,
|
|
122
|
+
allowBlank,
|
|
123
|
+
showErrorMessage,
|
|
124
|
+
showInputMessage
|
|
125
|
+
} = validation
|
|
126
|
+
|
|
127
|
+
// OOXML defaults `allowBlank` to false; we default it to true (matching xlsxwriter).
|
|
128
|
+
attributes.push(`allowBlank="${allowBlank === false ? '0' : '1'}"`)
|
|
129
|
+
|
|
130
|
+
// `showDropDown` in OOXML is inverted: setting it to `1` HIDES the dropdown.
|
|
131
|
+
if (validation.type === 'list' && validation.showDropdown === false) {
|
|
132
|
+
attributes.push('showDropDown="1"')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// OOXML defaults `showErrorMessage`/`showInputMessage` to false; without them Excel does
|
|
136
|
+
// not reject invalid input. Default both to true.
|
|
137
|
+
attributes.push(`showErrorMessage="${showErrorMessage === false ? '0' : '1'}"`)
|
|
138
|
+
attributes.push(`showInputMessage="${showInputMessage === false ? '0' : '1'}"`)
|
|
139
|
+
|
|
140
|
+
if (errorStyle !== undefined) {
|
|
141
|
+
validateErrorStyle(errorStyle)
|
|
142
|
+
// OOXML default for `errorStyle` is `stop`. Skip the attribute in that case.
|
|
143
|
+
if (errorStyle !== 'stop') {
|
|
144
|
+
attributes.push(`errorStyle="${errorStyle}"`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (errorTitle !== undefined) {
|
|
149
|
+
validateTitleLength(errorTitle, 'errorTitle')
|
|
150
|
+
attributes.push(`errorTitle="${sanitizeAttributeValue(errorTitle)}"`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (error !== undefined) {
|
|
154
|
+
validateMessageLength(error, 'error')
|
|
155
|
+
attributes.push(`error="${sanitizeAttributeValue(error)}"`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (inputTitle !== undefined) {
|
|
159
|
+
validateTitleLength(inputTitle, 'inputTitle')
|
|
160
|
+
attributes.push(`promptTitle="${sanitizeAttributeValue(inputTitle)}"`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (input !== undefined) {
|
|
164
|
+
validateMessageLength(input, 'input')
|
|
165
|
+
attributes.push(`prompt="${sanitizeAttributeValue(input)}"`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
attributes.push(`sqref="${sqref}"`)
|
|
169
|
+
|
|
170
|
+
const [formula1, formula2] = getFormulas(validation)
|
|
171
|
+
|
|
172
|
+
let xml = `<dataValidation ${attributes.join(' ')}>`
|
|
173
|
+
if (formula1 !== undefined) {
|
|
174
|
+
xml += `<formula1>${sanitizeTextContent(formula1)}</formula1>`
|
|
175
|
+
}
|
|
176
|
+
if (formula2 !== undefined) {
|
|
177
|
+
xml += `<formula2>${sanitizeTextContent(formula2)}</formula2>`
|
|
178
|
+
}
|
|
179
|
+
xml += '</dataValidation>'
|
|
180
|
+
|
|
181
|
+
return xml
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getXlsxType(type: DataValidation['type']): string | undefined {
|
|
185
|
+
switch (type) {
|
|
186
|
+
case 'list':
|
|
187
|
+
return 'list'
|
|
188
|
+
case 'integer':
|
|
189
|
+
return 'whole'
|
|
190
|
+
case 'decimal':
|
|
191
|
+
return 'decimal'
|
|
192
|
+
case 'date':
|
|
193
|
+
return 'date'
|
|
194
|
+
case 'time':
|
|
195
|
+
return 'time'
|
|
196
|
+
case 'textLength':
|
|
197
|
+
return 'textLength'
|
|
198
|
+
case 'custom':
|
|
199
|
+
return 'custom'
|
|
200
|
+
case 'any':
|
|
201
|
+
return undefined
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`Unknown data validation type: ${type as string}`)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function hasOperator(type: DataValidation['type']): boolean {
|
|
208
|
+
switch (type) {
|
|
209
|
+
case 'integer':
|
|
210
|
+
case 'decimal':
|
|
211
|
+
case 'date':
|
|
212
|
+
case 'time':
|
|
213
|
+
case 'textLength':
|
|
214
|
+
return true
|
|
215
|
+
default:
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getXlsxOperatorName(operator: DataValidationOperator): string {
|
|
221
|
+
switch (operator) {
|
|
222
|
+
case '<':
|
|
223
|
+
return 'lessThan'
|
|
224
|
+
case '>':
|
|
225
|
+
return 'greaterThan'
|
|
226
|
+
case '<=':
|
|
227
|
+
return 'lessThanOrEqual'
|
|
228
|
+
case '>=':
|
|
229
|
+
return 'greaterThanOrEqual'
|
|
230
|
+
case '=':
|
|
231
|
+
return 'equal'
|
|
232
|
+
case '!=':
|
|
233
|
+
return 'notEqual'
|
|
234
|
+
case '...':
|
|
235
|
+
return 'between'
|
|
236
|
+
case '!...':
|
|
237
|
+
return 'notBetween'
|
|
238
|
+
default:
|
|
239
|
+
throw new Error(`Unknown data validation operator: ${operator as string}`)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isBetweenOperator(operator: DataValidationOperator): boolean {
|
|
244
|
+
return operator === '...' || operator === '!...'
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
type Formulas = [string | undefined, string | undefined] | [string] | []
|
|
248
|
+
|
|
249
|
+
function getFormulas(validation: DataValidation): Formulas {
|
|
250
|
+
switch (validation.type) {
|
|
251
|
+
case 'list': {
|
|
252
|
+
if ('values' in validation) {
|
|
253
|
+
return [getListFormulaFromValues(validation.values)]
|
|
254
|
+
}
|
|
255
|
+
if ('valuesRange' in validation) {
|
|
256
|
+
return [validation.valuesRange]
|
|
257
|
+
}
|
|
258
|
+
throw new Error('A `list` data validation must specify either `values` or `valuesRange`')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'custom': {
|
|
262
|
+
if (validation.formula === undefined) {
|
|
263
|
+
throw new Error('A `custom` data validation must specify a `formula`')
|
|
264
|
+
}
|
|
265
|
+
return [validation.formula]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'any':
|
|
269
|
+
return []
|
|
270
|
+
|
|
271
|
+
case 'integer':
|
|
272
|
+
case 'decimal':
|
|
273
|
+
case 'textLength': {
|
|
274
|
+
if (validation.value === undefined) {
|
|
275
|
+
throw new Error(`A \`${validation.type}\` data validation must specify a \`value\``)
|
|
276
|
+
}
|
|
277
|
+
if (isBetweenOperator(validation.operator)) {
|
|
278
|
+
if ((validation as { value2?: number }).value2 === undefined) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`A \`${validation.type}\` data validation with operator \`${validation.operator}\` must specify a \`value2\``
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
return [
|
|
284
|
+
formatNumericValue(validation.value, validation.type),
|
|
285
|
+
formatNumericValue((validation as { value2: number }).value2, validation.type)
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
return [formatNumericValue(validation.value, validation.type)]
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'date':
|
|
292
|
+
case 'time': {
|
|
293
|
+
if (validation.value === undefined) {
|
|
294
|
+
throw new Error(`A \`${validation.type}\` data validation must specify a \`value\``)
|
|
295
|
+
}
|
|
296
|
+
if (isBetweenOperator(validation.operator)) {
|
|
297
|
+
const value2 = (validation as { value2?: Date | number }).value2
|
|
298
|
+
if (value2 === undefined) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`A \`${validation.type}\` data validation with operator \`${validation.operator}\` must specify a \`value2\``
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
formatDateOrTimeValue(validation.value, validation.type),
|
|
305
|
+
formatDateOrTimeValue(value2, validation.type)
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
return [formatDateOrTimeValue(validation.value, validation.type)]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getListFormulaFromValues(values: string[]): string {
|
|
314
|
+
if (!Array.isArray(values)) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`A \`list\` data validation \`values\` must be an array, got: ${String(values)}`
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
if (values.length === 0) {
|
|
320
|
+
throw new Error('A `list` data validation `values` must contain at least one value')
|
|
321
|
+
}
|
|
322
|
+
for (const value of values) {
|
|
323
|
+
if (typeof value !== 'string') {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`A \`list\` data validation \`values\` must be strings, got: ${String(value)}`
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
if (value.indexOf(',') !== -1) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`A \`list\` data validation \`values\` cannot contain commas (used by Excel as the separator inside the inline list formula). Use \`valuesRange\` to reference a range of cells instead. Got: "${value}"`
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
if (value.indexOf('"') !== -1) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`A \`list\` data validation \`values\` cannot contain double-quote characters. Use \`valuesRange\` to reference a range of cells instead. Got: "${value}"`
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const serialized = values.join(',')
|
|
340
|
+
if (serialized.length + 2 > MAX_MESSAGE_LENGTH) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`A \`list\` data validation \`values\` serialized to "${serialized}" exceeds the maximum length of ${MAX_MESSAGE_LENGTH} characters. Use \`valuesRange\` instead to reference a range of cells.`
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
return `"${serialized}"`
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function formatNumericValue(value: number, type: string): string {
|
|
349
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
`A \`${type}\` data validation \`value\` must be a finite number, got: ${String(value)}`
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
return String(value)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function formatDateOrTimeValue(value: Date | number, type: 'date' | 'time'): string {
|
|
358
|
+
let serial: number
|
|
359
|
+
if (value instanceof Date) {
|
|
360
|
+
serial = convertDateToExcelSerial(value)
|
|
361
|
+
} else if (typeof value === 'number') {
|
|
362
|
+
serial = value
|
|
363
|
+
} else {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`A \`${type}\` data validation \`value\` must be a Date or a number, got: ${String(value)}`
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
if (!Number.isFinite(serial)) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`A \`${type}\` data validation \`value\` is not a finite number (possibly an invalid Date), got: ${String(value)}`
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
// For `time`, Excel expects a fractional value in [0, 1) representing the time of day.
|
|
374
|
+
// A `Date` carries both a date and a time, so reduce the serial to its fractional part.
|
|
375
|
+
if (type === 'time') {
|
|
376
|
+
serial = serial - Math.floor(serial)
|
|
377
|
+
}
|
|
378
|
+
return String(serial)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function validateErrorStyle(errorStyle: string): void {
|
|
382
|
+
if (errorStyle !== 'stop' && errorStyle !== 'warning' && errorStyle !== 'information') {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Unknown data validation \`errorStyle\`: ${errorStyle}. Expected \`stop\`, \`warning\` or \`information\``
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function validateTitleLength(title: string, fieldName: string): void {
|
|
390
|
+
if (typeof title !== 'string') {
|
|
391
|
+
throw new Error(`Data validation \`${fieldName}\` must be a string`)
|
|
392
|
+
}
|
|
393
|
+
if (title.length > MAX_TITLE_LENGTH) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Data validation \`${fieldName}\` is longer than ${MAX_TITLE_LENGTH} characters: "${title}"`
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function validateMessageLength(message: string, fieldName: string): void {
|
|
401
|
+
if (typeof message !== 'string') {
|
|
402
|
+
throw new Error(`Data validation \`${fieldName}\` must be a string`)
|
|
403
|
+
}
|
|
404
|
+
if (message.length > MAX_MESSAGE_LENGTH) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`Data validation \`${fieldName}\` is longer than ${MAX_MESSAGE_LENGTH} characters: "${message}"`
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const LETTERS_COUNT = 26
|
|
2
|
+
|
|
3
|
+
function getColumnLetter(columnIndex: number): string {
|
|
4
|
+
const prefix = Math.floor(columnIndex / LETTERS_COUNT)
|
|
5
|
+
const letter = String.fromCharCode(97 + (columnIndex % LETTERS_COUNT)).toUpperCase()
|
|
6
|
+
if (prefix === 0) {
|
|
7
|
+
return letter
|
|
8
|
+
}
|
|
9
|
+
return getColumnLetter(prefix - 1) + letter
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function getCellCoordinate(rowIndex: number, columnIndex: number): string {
|
|
13
|
+
return `${getColumnLetter(columnIndex)}${rowIndex + 1}`
|
|
14
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export type DataValidationOperatorOnOneValue = '<' | '<=' | '>' | '>=' | '=' | '!='
|
|
2
|
+
|
|
3
|
+
export type DataValidationOperatorOnTwoValues = '...' | '!...'
|
|
4
|
+
|
|
5
|
+
export type DataValidationOperator =
|
|
6
|
+
| DataValidationOperatorOnOneValue
|
|
7
|
+
| DataValidationOperatorOnTwoValues
|
|
8
|
+
|
|
9
|
+
export type DataValidationErrorStyle = 'stop' | 'warning' | 'information'
|
|
10
|
+
|
|
11
|
+
interface DataValidationCommonProperties {
|
|
12
|
+
error?: string
|
|
13
|
+
errorTitle?: string
|
|
14
|
+
errorStyle?: DataValidationErrorStyle
|
|
15
|
+
input?: string
|
|
16
|
+
inputTitle?: string
|
|
17
|
+
allowBlank?: boolean
|
|
18
|
+
showErrorMessage?: boolean
|
|
19
|
+
showInputMessage?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface DataValidationListWithValues extends DataValidationCommonProperties {
|
|
23
|
+
type: 'list'
|
|
24
|
+
values: string[]
|
|
25
|
+
showDropdown?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface DataValidationListWithRange extends DataValidationCommonProperties {
|
|
29
|
+
type: 'list'
|
|
30
|
+
valuesRange: string
|
|
31
|
+
showDropdown?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface DataValidationNumericOnOneValue extends DataValidationCommonProperties {
|
|
35
|
+
type: 'integer' | 'decimal' | 'textLength'
|
|
36
|
+
operator: DataValidationOperatorOnOneValue
|
|
37
|
+
value: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface DataValidationNumericOnTwoValues extends DataValidationCommonProperties {
|
|
41
|
+
type: 'integer' | 'decimal' | 'textLength'
|
|
42
|
+
operator: DataValidationOperatorOnTwoValues
|
|
43
|
+
value: number
|
|
44
|
+
value2: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface DataValidationDateOnOneValue extends DataValidationCommonProperties {
|
|
48
|
+
type: 'date' | 'time'
|
|
49
|
+
operator: DataValidationOperatorOnOneValue
|
|
50
|
+
value: Date | number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface DataValidationDateOnTwoValues extends DataValidationCommonProperties {
|
|
54
|
+
type: 'date' | 'time'
|
|
55
|
+
operator: DataValidationOperatorOnTwoValues
|
|
56
|
+
value: Date | number
|
|
57
|
+
value2: Date | number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface DataValidationCustom extends DataValidationCommonProperties {
|
|
61
|
+
type: 'custom'
|
|
62
|
+
formula: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface DataValidationAny extends DataValidationCommonProperties {
|
|
66
|
+
type: 'any'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type DataValidation =
|
|
70
|
+
| DataValidationListWithValues
|
|
71
|
+
| DataValidationListWithRange
|
|
72
|
+
| DataValidationNumericOnOneValue
|
|
73
|
+
| DataValidationNumericOnTwoValues
|
|
74
|
+
| DataValidationDateOnOneValue
|
|
75
|
+
| DataValidationDateOnTwoValues
|
|
76
|
+
| DataValidationCustom
|
|
77
|
+
| DataValidationAny
|
|
78
|
+
|
|
79
|
+
export interface DataValidationRule {
|
|
80
|
+
cellRange: {
|
|
81
|
+
from: { row: number; column: number }
|
|
82
|
+
to: { row: number; column: number }
|
|
83
|
+
}
|
|
84
|
+
validation: DataValidation
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DataValidationSheetOptions {
|
|
88
|
+
dataValidation?: DataValidationRule[]
|
|
89
|
+
}
|