handsontable 18.0.0-rc4 → 18.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/3rdparty/walkontable/src/renderer/cells.d.ts +0 -6
- package/3rdparty/walkontable/src/renderer/cells.js +2 -7
- package/3rdparty/walkontable/src/renderer/cells.mjs +2 -7
- package/3rdparty/walkontable/src/renderer/colGroup.js +1 -1
- package/3rdparty/walkontable/src/renderer/colGroup.mjs +1 -1
- package/3rdparty/walkontable/src/renderer/columnHeaderRows.js +1 -1
- package/3rdparty/walkontable/src/renderer/columnHeaderRows.mjs +1 -1
- package/3rdparty/walkontable/src/renderer/columnHeaders.d.ts +0 -6
- package/3rdparty/walkontable/src/renderer/columnHeaders.js +2 -7
- package/3rdparty/walkontable/src/renderer/columnHeaders.mjs +2 -7
- package/3rdparty/walkontable/src/renderer/rowHeaders.d.ts +0 -6
- package/3rdparty/walkontable/src/renderer/rowHeaders.js +5 -10
- package/3rdparty/walkontable/src/renderer/rowHeaders.mjs +5 -10
- package/3rdparty/walkontable/src/renderer/rows.js +1 -1
- package/3rdparty/walkontable/src/renderer/rows.mjs +1 -1
- package/3rdparty/walkontable/src/utils/nodesPool.d.ts +10 -10
- package/3rdparty/walkontable/src/utils/nodesPool.js +16 -32
- package/3rdparty/walkontable/src/utils/nodesPool.mjs +11 -31
- package/3rdparty/walkontable/src/utils/orderView/constants.d.ts +0 -36
- package/3rdparty/walkontable/src/utils/orderView/constants.js +0 -24
- package/3rdparty/walkontable/src/utils/orderView/constants.mjs +0 -30
- package/3rdparty/walkontable/src/utils/orderView/view.d.ts +27 -20
- package/3rdparty/walkontable/src/utils/orderView/view.js +79 -16
- package/3rdparty/walkontable/src/utils/orderView/view.mjs +86 -17
- package/3rdparty/walkontable/src/utils/orderView/viewSize.d.ts +2 -14
- package/3rdparty/walkontable/src/utils/orderView/viewSize.js +2 -14
- package/3rdparty/walkontable/src/utils/orderView/viewSize.mjs +2 -14
- package/CHANGELOG.md +4 -1
- package/base.js +2 -2
- package/base.mjs +2 -2
- package/core/types.d.ts +4 -0
- package/core.js +25 -4
- package/core.mjs +25 -4
- package/dataMap/metaManager/index.d.ts +22 -0
- package/dataMap/metaManager/index.js +20 -0
- package/dataMap/metaManager/index.mjs +20 -0
- package/dataMap/metaManager/metaLayers/cellMeta.d.ts +30 -0
- package/dataMap/metaManager/metaLayers/cellMeta.js +110 -0
- package/dataMap/metaManager/metaLayers/cellMeta.mjs +111 -1
- package/dataMap/metaManager/metaSchema.js +9 -0
- package/dataMap/metaManager/metaSchema.mjs +9 -0
- package/dataMap/sourceDataValidator.d.ts +25 -2
- package/dataMap/sourceDataValidator.js +163 -29
- package/dataMap/sourceDataValidator.mjs +178 -30
- package/dist/handsontable.full.js +2182 -2392
- package/dist/handsontable.full.min.js +95 -95
- package/dist/handsontable.js +2182 -2387
- package/dist/handsontable.min.js +95 -95
- package/dist/themes/classic.js +2 -2
- package/dist/themes/classic.min.js +2 -2
- package/dist/themes/horizon.js +2 -2
- package/dist/themes/horizon.min.js +2 -2
- package/dist/themes/main.js +2 -2
- package/dist/themes/main.min.js +2 -2
- package/dist/themes/static/variables/colors/ant.js +2 -2
- package/dist/themes/static/variables/colors/ant.min.js +2 -2
- package/dist/themes/static/variables/colors/classic.js +2 -2
- package/dist/themes/static/variables/colors/classic.min.js +2 -2
- package/dist/themes/static/variables/colors/horizon.js +2 -2
- package/dist/themes/static/variables/colors/horizon.min.js +2 -2
- package/dist/themes/static/variables/colors/main.js +2 -2
- package/dist/themes/static/variables/colors/main.min.js +2 -2
- package/dist/themes/static/variables/colors/material.js +2 -2
- package/dist/themes/static/variables/colors/material.min.js +2 -2
- package/dist/themes/static/variables/colors/shadcn.js +2 -2
- package/dist/themes/static/variables/colors/shadcn.min.js +2 -2
- package/dist/themes/static/variables/density.js +2 -2
- package/dist/themes/static/variables/density.min.js +2 -2
- package/dist/themes/static/variables/helpers/iconsMap.js +2 -2
- package/dist/themes/static/variables/helpers/iconsMap.min.js +2 -2
- package/dist/themes/static/variables/icons/horizon.js +2 -2
- package/dist/themes/static/variables/icons/horizon.min.js +2 -2
- package/dist/themes/static/variables/icons/main.js +2 -2
- package/dist/themes/static/variables/icons/main.min.js +2 -2
- package/dist/themes/static/variables/sizing.js +2 -2
- package/dist/themes/static/variables/sizing.min.js +2 -2
- package/dist/themes/static/variables/tokens/classic.js +2 -2
- package/dist/themes/static/variables/tokens/classic.min.js +2 -2
- package/dist/themes/static/variables/tokens/horizon.js +2 -2
- package/dist/themes/static/variables/tokens/horizon.min.js +2 -2
- package/dist/themes/static/variables/tokens/main.js +2 -2
- package/dist/themes/static/variables/tokens/main.min.js +2 -2
- package/helpers/dom/element.js +13 -6
- package/helpers/dom/element.mjs +13 -6
- package/helpers/mixed.js +2 -2
- package/helpers/mixed.mjs +2 -2
- package/package.json +1 -1
- package/styles/handsontable.css +2 -2
- package/styles/handsontable.min.css +2 -2
- package/styles/handsontableStyles.js +1 -1
- package/styles/handsontableStyles.mjs +1 -1
- package/styles/ht-icons-horizon.min.css +2 -2
- package/styles/ht-icons-main.min.css +2 -2
- package/styles/ht-theme-classic-no-icons.min.css +2 -2
- package/styles/ht-theme-classic.min.css +2 -2
- package/styles/ht-theme-horizon-no-icons.min.css +2 -2
- package/styles/ht-theme-horizon.min.css +2 -2
- package/styles/ht-theme-main-no-icons.min.css +2 -2
- package/styles/ht-theme-main.min.css +2 -2
- package/utils/licenseNotification.js +1 -1
- package/utils/licenseNotification.mjs +1 -1
- package/validators/dateValidator/dateValidator.d.ts +3 -0
- package/validators/dateValidator/dateValidator.js +4 -0
- package/validators/dateValidator/dateValidator.mjs +4 -0
- package/validators/timeValidator/timeValidator.d.ts +3 -0
- package/validators/timeValidator/timeValidator.js +4 -0
- package/validators/timeValidator/timeValidator.mjs +4 -0
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.d.mts +0 -1
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.d.ts +0 -60
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.js +0 -96
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.mjs +0 -91
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.d.mts +0 -1
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.d.ts +0 -45
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.js +0 -97
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.mjs +0 -91
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.d.mts +0 -1
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.d.ts +0 -10
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.js +0 -19
- package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.mjs +0 -14
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.d.mts +0 -1
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.d.ts +0 -44
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.js +0 -138
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.mjs +0 -133
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.d.mts +0 -1
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.d.ts +0 -57
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.js +0 -137
- package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.mjs +0 -127
|
@@ -40,45 +40,175 @@ function runSourceDataValidator(value, cellMeta, source) {
|
|
|
40
40
|
}
|
|
41
41
|
return allowInvalid === true;
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Validates a single source cell against its (already resolved) cell meta, blanking the value when it
|
|
45
|
+
* is invalid and `allowInvalid` is `false`, and collecting an aggregated-warning entry.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} cellMeta The resolved cell meta (its `sourceDataValidator` must be a function).
|
|
48
|
+
* @param {unknown} value The source value to validate.
|
|
49
|
+
* @param {number} physicalRow The physical row index.
|
|
50
|
+
* @param {number} physicalColumn The physical column index.
|
|
51
|
+
* @param {object} dataSource The data source used to blank invalid values.
|
|
52
|
+
* @param {Map} invalidByMessageType The accumulator of invalid entries keyed by warning message.
|
|
53
|
+
* @param {string} [source] The source identifier of the operation.
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/ function validateSourceCell(cellMeta, value, physicalRow, physicalColumn, dataSource, invalidByMessageType, source) {
|
|
56
|
+
const validator = cellMeta.sourceDataValidator;
|
|
57
|
+
if (!(0, _function.isFunction)(validator)) {
|
|
47
58
|
return;
|
|
48
59
|
}
|
|
49
|
-
const
|
|
60
|
+
const isValid = validator(value, cellMeta, source);
|
|
61
|
+
if (isValid === true) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (cellMeta.allowInvalid === false) {
|
|
65
|
+
dataSource.setAtCell(physicalRow, physicalColumn, null);
|
|
66
|
+
}
|
|
67
|
+
const message = cellMeta.sourceDataWarningMessage;
|
|
68
|
+
if (message) {
|
|
69
|
+
const list = invalidByMessageType.get(message) ?? [];
|
|
70
|
+
list.push({
|
|
71
|
+
row: physicalRow,
|
|
72
|
+
col: physicalColumn,
|
|
73
|
+
value,
|
|
74
|
+
message
|
|
75
|
+
});
|
|
76
|
+
invalidByMessageType.set(message, list);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Inspects column-level meta (one sample cell per column, O(cols)) to decide how source-data
|
|
81
|
+
* validation should run. Returns the columns whose validator is row-independent (safe to validate by
|
|
82
|
+
* reusing one column meta), or signals a full per-cell scan when any column carries a validator that
|
|
83
|
+
* may depend on per-row meta.
|
|
84
|
+
*
|
|
85
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
86
|
+
* @param {number} colSourceCount The number of physical source columns.
|
|
87
|
+
* @returns {object} `{ fullScan, columns }` — when `fullScan` is `true`, `columns` is empty.
|
|
88
|
+
*/ function collectColumnValidators(hotInstance, colSourceCount) {
|
|
89
|
+
const sampleVisualRow = hotInstance.rowIndexMapper.getVisualFromPhysicalIndex(0);
|
|
90
|
+
if (sampleVisualRow === null) {
|
|
91
|
+
return {
|
|
92
|
+
fullScan: true,
|
|
93
|
+
columns: []
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const columns = [];
|
|
97
|
+
for(let physicalColumn = 0; physicalColumn < colSourceCount; physicalColumn += 1){
|
|
98
|
+
const visualColumn = hotInstance.columnIndexMapper.getVisualFromPhysicalIndex(physicalColumn);
|
|
99
|
+
if (visualColumn === null) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const cellMeta = hotInstance.getCellMeta(sampleVisualRow, visualColumn);
|
|
103
|
+
const validator = cellMeta.sourceDataValidator;
|
|
104
|
+
if (!(0, _function.isFunction)(validator)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// A validator that may read per-row meta cannot be batched with a single column meta — fall back
|
|
108
|
+
// to the per-cell scan for the whole dataset to preserve exact behavior.
|
|
109
|
+
if (validator.rowIndependent !== true) {
|
|
110
|
+
return {
|
|
111
|
+
fullScan: true,
|
|
112
|
+
columns: []
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
columns.push({
|
|
116
|
+
physicalColumn,
|
|
117
|
+
cellMeta
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
fullScan: false,
|
|
122
|
+
columns
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Validates every source cell by resolving meta per cell (O(rows*cols) meta). Used when per-cell meta
|
|
127
|
+
* can vary by row (a `cells` function, a `cell` array, or a row-dependent custom validator).
|
|
128
|
+
*
|
|
129
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
130
|
+
* @param {DataSource} dataSource The data source.
|
|
131
|
+
* @param {number} rowSourceCount The number of physical source rows.
|
|
132
|
+
* @param {number} colSourceCount The number of physical source columns.
|
|
133
|
+
* @param {Map} invalidByMessageType The accumulator of invalid entries keyed by warning message.
|
|
134
|
+
* @param {string} [source] The source identifier of the operation.
|
|
135
|
+
* @returns {void}
|
|
136
|
+
*/ function validatePerCell(hotInstance, dataSource, rowSourceCount, colSourceCount, invalidByMessageType, source) {
|
|
137
|
+
const { rowIndexMapper, columnIndexMapper } = hotInstance;
|
|
50
138
|
for(let row = 0; row < rowSourceCount; row += 1){
|
|
51
139
|
for(let col = 0; col < colSourceCount; col += 1){
|
|
52
|
-
const visualRow =
|
|
53
|
-
const visualColumn =
|
|
140
|
+
const visualRow = rowIndexMapper.getVisualFromPhysicalIndex(row);
|
|
141
|
+
const visualColumn = columnIndexMapper.getVisualFromPhysicalIndex(col);
|
|
54
142
|
if (visualRow === null || visualColumn === null) {
|
|
55
143
|
continue;
|
|
56
144
|
}
|
|
57
145
|
const cellMeta = hotInstance.getCellMeta(visualRow, visualColumn);
|
|
58
|
-
|
|
59
|
-
if (!(0, _function.isFunction)(sourceDataValidator)) {
|
|
146
|
+
if (!(0, _function.isFunction)(cellMeta.sourceDataValidator)) {
|
|
60
147
|
continue;
|
|
61
148
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
149
|
+
const value = dataSource.getAtCell(row, col);
|
|
150
|
+
validateSourceCell(cellMeta, value, row, col, dataSource, invalidByMessageType, source);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Validates source cells while reusing one column-level meta object per column instead of
|
|
156
|
+
* materializing a meta object per cell (O(cols) meta instead of O(rows*cols)).
|
|
157
|
+
*
|
|
158
|
+
* Iterates row by row (column inner) so each physical row is translated to its visual index once and
|
|
159
|
+
* the aggregated-warning order stays row-major (matching the per-cell scan).
|
|
160
|
+
*
|
|
161
|
+
* @param {Array} columns The columns whose (row-independent) validator should run.
|
|
162
|
+
* @param {DataSource} dataSource The data source.
|
|
163
|
+
* @param {number} rowSourceCount The number of physical source rows.
|
|
164
|
+
* @param {Function} toVisualRow Translates a physical row index to its visual index (or `null`).
|
|
165
|
+
* @param {Map} invalidByMessageType The accumulator of invalid entries keyed by warning message.
|
|
166
|
+
* @param {string} [source] The source identifier of the operation.
|
|
167
|
+
* @returns {void}
|
|
168
|
+
*/ function validateBatched(columns, dataSource, rowSourceCount, toVisualRow, invalidByMessageType, source) {
|
|
169
|
+
for(let row = 0; row < rowSourceCount; row += 1){
|
|
170
|
+
// Skip rows that have no visual index (trimmed rows) to match the per-cell scan, which never
|
|
171
|
+
// validates or blanks source values for rows that map to `null`.
|
|
172
|
+
if (toVisualRow(row) === null) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
for(let i = 0; i < columns.length; i += 1){
|
|
176
|
+
const { physicalColumn, cellMeta } = columns[i];
|
|
177
|
+
const value = dataSource.getAtCell(row, physicalColumn);
|
|
178
|
+
validateSourceCell(cellMeta, value, row, physicalColumn, dataSource, invalidByMessageType, source);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Detects whether per-cell meta can vary by row, which makes the row-0 sample unsafe to reuse for the
|
|
184
|
+
* whole column. Meta varies through a `cells` function, an explicit `cell` array, the
|
|
185
|
+
* `beforeGetCellMeta`/`afterGetCellMeta` hooks (a row-dependent `type`/validator/`allowInvalid` can be
|
|
186
|
+
* applied there), or any imperatively-set cell meta (`setCellMeta`) that survives `updateData`.
|
|
187
|
+
*
|
|
188
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
189
|
+
* @param {GridSettings} settings The resolved grid settings.
|
|
190
|
+
* @returns {boolean} `true` when meta must be resolved per cell.
|
|
191
|
+
*/ function hasRowVaryingMeta(hotInstance, settings) {
|
|
192
|
+
return (0, _function.isFunction)(settings.cells) || Array.isArray(settings.cell) && settings.cell.length > 0 || hotInstance.hasHook('beforeGetCellMeta') || hotInstance.hasHook('afterGetCellMeta') || hotInstance._getMetaManager().getUserDefinedCellMetas().length > 0;
|
|
193
|
+
}
|
|
194
|
+
function runSourceDataValidators(hotInstance, source) {
|
|
195
|
+
const rowSourceCount = hotInstance.countSourceRows();
|
|
196
|
+
const colSourceCount = hotInstance.countSourceCols();
|
|
197
|
+
if (rowSourceCount === 0 || colSourceCount === 0) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const settings = hotInstance.getSettings();
|
|
201
|
+
const dataSource = hotInstance._getDataSource();
|
|
202
|
+
const invalidByMessageType = new Map();
|
|
203
|
+
if (hasRowVaryingMeta(hotInstance, settings)) {
|
|
204
|
+
validatePerCell(hotInstance, dataSource, rowSourceCount, colSourceCount, invalidByMessageType, source);
|
|
205
|
+
} else {
|
|
206
|
+
const { fullScan, columns } = collectColumnValidators(hotInstance, colSourceCount);
|
|
207
|
+
if (fullScan) {
|
|
208
|
+
validatePerCell(hotInstance, dataSource, rowSourceCount, colSourceCount, invalidByMessageType, source);
|
|
209
|
+
} else {
|
|
210
|
+
const toVisualRow = (row)=>hotInstance.rowIndexMapper.getVisualFromPhysicalIndex(row);
|
|
211
|
+
validateBatched(columns, dataSource, rowSourceCount, toVisualRow, invalidByMessageType, source);
|
|
82
212
|
}
|
|
83
213
|
}
|
|
84
214
|
invalidByMessageType.forEach((items, message)=>{
|
|
@@ -86,7 +216,11 @@ function runSourceDataValidators(hotInstance, source) {
|
|
|
86
216
|
});
|
|
87
217
|
}
|
|
88
218
|
/**
|
|
219
|
+
* Emits one aggregated console warning for a group of invalid source-data cells.
|
|
89
220
|
*
|
|
221
|
+
* @param {string} message The warning message template.
|
|
222
|
+
* @param {Array} items The invalid cell entries to report.
|
|
223
|
+
* @returns {void}
|
|
90
224
|
*/ function logSourceDataWarning(message, items) {
|
|
91
225
|
(0, _console.logAggregatedItems)({
|
|
92
226
|
logFunction: _console.warn,
|
|
@@ -3,6 +3,11 @@ import { isFunction } from '../helpers/function.mjs';
|
|
|
3
3
|
import { stringify } from '../helpers/mixed.mjs';
|
|
4
4
|
/**
|
|
5
5
|
* Runs source-data validator for a single cell.
|
|
6
|
+
*
|
|
7
|
+
* @param {unknown} value The value to validate.
|
|
8
|
+
* @param {object} cellMeta The cell meta object.
|
|
9
|
+
* @param {string} [source] The source identifier of the operation.
|
|
10
|
+
* @returns {boolean} `true` when the value is valid (or no validator is configured).
|
|
6
11
|
*/ export function runSourceDataValidator(value, cellMeta, source) {
|
|
7
12
|
const validator = cellMeta.sourceDataValidator;
|
|
8
13
|
if (!isFunction(validator)) {
|
|
@@ -25,46 +30,185 @@ import { stringify } from '../helpers/mixed.mjs';
|
|
|
25
30
|
return allowInvalid === true;
|
|
26
31
|
}
|
|
27
32
|
/**
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
* Validates a single source cell against its (already resolved) cell meta, blanking the value when it
|
|
34
|
+
* is invalid and `allowInvalid` is `false`, and collecting an aggregated-warning entry.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} cellMeta The resolved cell meta (its `sourceDataValidator` must be a function).
|
|
37
|
+
* @param {unknown} value The source value to validate.
|
|
38
|
+
* @param {number} physicalRow The physical row index.
|
|
39
|
+
* @param {number} physicalColumn The physical column index.
|
|
40
|
+
* @param {object} dataSource The data source used to blank invalid values.
|
|
41
|
+
* @param {Map} invalidByMessageType The accumulator of invalid entries keyed by warning message.
|
|
42
|
+
* @param {string} [source] The source identifier of the operation.
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*/ function validateSourceCell(cellMeta, value, physicalRow, physicalColumn, dataSource, invalidByMessageType, source) {
|
|
45
|
+
const validator = cellMeta.sourceDataValidator;
|
|
46
|
+
if (!isFunction(validator)) {
|
|
33
47
|
return;
|
|
34
48
|
}
|
|
35
|
-
const
|
|
49
|
+
const isValid = validator(value, cellMeta, source);
|
|
50
|
+
if (isValid === true) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (cellMeta.allowInvalid === false) {
|
|
54
|
+
dataSource.setAtCell(physicalRow, physicalColumn, null);
|
|
55
|
+
}
|
|
56
|
+
const message = cellMeta.sourceDataWarningMessage;
|
|
57
|
+
if (message) {
|
|
58
|
+
const list = invalidByMessageType.get(message) ?? [];
|
|
59
|
+
list.push({
|
|
60
|
+
row: physicalRow,
|
|
61
|
+
col: physicalColumn,
|
|
62
|
+
value,
|
|
63
|
+
message
|
|
64
|
+
});
|
|
65
|
+
invalidByMessageType.set(message, list);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Inspects column-level meta (one sample cell per column, O(cols)) to decide how source-data
|
|
70
|
+
* validation should run. Returns the columns whose validator is row-independent (safe to validate by
|
|
71
|
+
* reusing one column meta), or signals a full per-cell scan when any column carries a validator that
|
|
72
|
+
* may depend on per-row meta.
|
|
73
|
+
*
|
|
74
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
75
|
+
* @param {number} colSourceCount The number of physical source columns.
|
|
76
|
+
* @returns {object} `{ fullScan, columns }` — when `fullScan` is `true`, `columns` is empty.
|
|
77
|
+
*/ function collectColumnValidators(hotInstance, colSourceCount) {
|
|
78
|
+
const sampleVisualRow = hotInstance.rowIndexMapper.getVisualFromPhysicalIndex(0);
|
|
79
|
+
if (sampleVisualRow === null) {
|
|
80
|
+
return {
|
|
81
|
+
fullScan: true,
|
|
82
|
+
columns: []
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const columns = [];
|
|
86
|
+
for(let physicalColumn = 0; physicalColumn < colSourceCount; physicalColumn += 1){
|
|
87
|
+
const visualColumn = hotInstance.columnIndexMapper.getVisualFromPhysicalIndex(physicalColumn);
|
|
88
|
+
if (visualColumn === null) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const cellMeta = hotInstance.getCellMeta(sampleVisualRow, visualColumn);
|
|
92
|
+
const validator = cellMeta.sourceDataValidator;
|
|
93
|
+
if (!isFunction(validator)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// A validator that may read per-row meta cannot be batched with a single column meta — fall back
|
|
97
|
+
// to the per-cell scan for the whole dataset to preserve exact behavior.
|
|
98
|
+
if (validator.rowIndependent !== true) {
|
|
99
|
+
return {
|
|
100
|
+
fullScan: true,
|
|
101
|
+
columns: []
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
columns.push({
|
|
105
|
+
physicalColumn,
|
|
106
|
+
cellMeta
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
fullScan: false,
|
|
111
|
+
columns
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validates every source cell by resolving meta per cell (O(rows*cols) meta). Used when per-cell meta
|
|
116
|
+
* can vary by row (a `cells` function, a `cell` array, or a row-dependent custom validator).
|
|
117
|
+
*
|
|
118
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
119
|
+
* @param {DataSource} dataSource The data source.
|
|
120
|
+
* @param {number} rowSourceCount The number of physical source rows.
|
|
121
|
+
* @param {number} colSourceCount The number of physical source columns.
|
|
122
|
+
* @param {Map} invalidByMessageType The accumulator of invalid entries keyed by warning message.
|
|
123
|
+
* @param {string} [source] The source identifier of the operation.
|
|
124
|
+
* @returns {void}
|
|
125
|
+
*/ function validatePerCell(hotInstance, dataSource, rowSourceCount, colSourceCount, invalidByMessageType, source) {
|
|
126
|
+
const { rowIndexMapper, columnIndexMapper } = hotInstance;
|
|
36
127
|
for(let row = 0; row < rowSourceCount; row += 1){
|
|
37
128
|
for(let col = 0; col < colSourceCount; col += 1){
|
|
38
|
-
const visualRow =
|
|
39
|
-
const visualColumn =
|
|
129
|
+
const visualRow = rowIndexMapper.getVisualFromPhysicalIndex(row);
|
|
130
|
+
const visualColumn = columnIndexMapper.getVisualFromPhysicalIndex(col);
|
|
40
131
|
if (visualRow === null || visualColumn === null) {
|
|
41
132
|
continue;
|
|
42
133
|
}
|
|
43
134
|
const cellMeta = hotInstance.getCellMeta(visualRow, visualColumn);
|
|
44
|
-
|
|
45
|
-
if (!isFunction(sourceDataValidator)) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const dataSource = hotInstance._getDataSource();
|
|
49
|
-
const value = dataSource.getAtCell(row, col, null);
|
|
50
|
-
const isValid = sourceDataValidator(value, cellMeta, source);
|
|
51
|
-
if (isValid === true) {
|
|
135
|
+
if (!isFunction(cellMeta.sourceDataValidator)) {
|
|
52
136
|
continue;
|
|
53
137
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
138
|
+
const value = dataSource.getAtCell(row, col);
|
|
139
|
+
validateSourceCell(cellMeta, value, row, col, dataSource, invalidByMessageType, source);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Validates source cells while reusing one column-level meta object per column instead of
|
|
145
|
+
* materializing a meta object per cell (O(cols) meta instead of O(rows*cols)).
|
|
146
|
+
*
|
|
147
|
+
* Iterates row by row (column inner) so each physical row is translated to its visual index once and
|
|
148
|
+
* the aggregated-warning order stays row-major (matching the per-cell scan).
|
|
149
|
+
*
|
|
150
|
+
* @param {Array} columns The columns whose (row-independent) validator should run.
|
|
151
|
+
* @param {DataSource} dataSource The data source.
|
|
152
|
+
* @param {number} rowSourceCount The number of physical source rows.
|
|
153
|
+
* @param {Function} toVisualRow Translates a physical row index to its visual index (or `null`).
|
|
154
|
+
* @param {Map} invalidByMessageType The accumulator of invalid entries keyed by warning message.
|
|
155
|
+
* @param {string} [source] The source identifier of the operation.
|
|
156
|
+
* @returns {void}
|
|
157
|
+
*/ function validateBatched(columns, dataSource, rowSourceCount, toVisualRow, invalidByMessageType, source) {
|
|
158
|
+
for(let row = 0; row < rowSourceCount; row += 1){
|
|
159
|
+
// Skip rows that have no visual index (trimmed rows) to match the per-cell scan, which never
|
|
160
|
+
// validates or blanks source values for rows that map to `null`.
|
|
161
|
+
if (toVisualRow(row) === null) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
for(let i = 0; i < columns.length; i += 1){
|
|
165
|
+
const { physicalColumn, cellMeta } = columns[i];
|
|
166
|
+
const value = dataSource.getAtCell(row, physicalColumn);
|
|
167
|
+
validateSourceCell(cellMeta, value, row, physicalColumn, dataSource, invalidByMessageType, source);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Detects whether per-cell meta can vary by row, which makes the row-0 sample unsafe to reuse for the
|
|
173
|
+
* whole column. Meta varies through a `cells` function, an explicit `cell` array, the
|
|
174
|
+
* `beforeGetCellMeta`/`afterGetCellMeta` hooks (a row-dependent `type`/validator/`allowInvalid` can be
|
|
175
|
+
* applied there), or any imperatively-set cell meta (`setCellMeta`) that survives `updateData`.
|
|
176
|
+
*
|
|
177
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
178
|
+
* @param {GridSettings} settings The resolved grid settings.
|
|
179
|
+
* @returns {boolean} `true` when meta must be resolved per cell.
|
|
180
|
+
*/ function hasRowVaryingMeta(hotInstance, settings) {
|
|
181
|
+
return isFunction(settings.cells) || Array.isArray(settings.cell) && settings.cell.length > 0 || hotInstance.hasHook('beforeGetCellMeta') || hotInstance.hasHook('afterGetCellMeta') || hotInstance._getMetaManager().getUserDefinedCellMetas().length > 0;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Runs source-data validators for all cells and emits one aggregated warning per cell type.
|
|
185
|
+
*
|
|
186
|
+
* For the common case (no `cells` function, no `cell` array, no `before`/`afterGetCellMeta` hooks, no
|
|
187
|
+
* imperatively-set cell meta, and only row-independent built-in source validators such as `date`/`time`),
|
|
188
|
+
* validation reuses one column-level meta per column — avoiding the O(rows*cols) cell-meta
|
|
189
|
+
* materialization that otherwise retains a meta object for every cell at load.
|
|
190
|
+
*
|
|
191
|
+
* @param {HotInstance} hotInstance The Handsontable instance.
|
|
192
|
+
* @param {string} [source] The source identifier of the operation.
|
|
193
|
+
* @returns {void}
|
|
194
|
+
*/ export function runSourceDataValidators(hotInstance, source) {
|
|
195
|
+
const rowSourceCount = hotInstance.countSourceRows();
|
|
196
|
+
const colSourceCount = hotInstance.countSourceCols();
|
|
197
|
+
if (rowSourceCount === 0 || colSourceCount === 0) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const settings = hotInstance.getSettings();
|
|
201
|
+
const dataSource = hotInstance._getDataSource();
|
|
202
|
+
const invalidByMessageType = new Map();
|
|
203
|
+
if (hasRowVaryingMeta(hotInstance, settings)) {
|
|
204
|
+
validatePerCell(hotInstance, dataSource, rowSourceCount, colSourceCount, invalidByMessageType, source);
|
|
205
|
+
} else {
|
|
206
|
+
const { fullScan, columns } = collectColumnValidators(hotInstance, colSourceCount);
|
|
207
|
+
if (fullScan) {
|
|
208
|
+
validatePerCell(hotInstance, dataSource, rowSourceCount, colSourceCount, invalidByMessageType, source);
|
|
209
|
+
} else {
|
|
210
|
+
const toVisualRow = (row)=>hotInstance.rowIndexMapper.getVisualFromPhysicalIndex(row);
|
|
211
|
+
validateBatched(columns, dataSource, rowSourceCount, toVisualRow, invalidByMessageType, source);
|
|
68
212
|
}
|
|
69
213
|
}
|
|
70
214
|
invalidByMessageType.forEach((items, message)=>{
|
|
@@ -72,7 +216,11 @@ import { stringify } from '../helpers/mixed.mjs';
|
|
|
72
216
|
});
|
|
73
217
|
}
|
|
74
218
|
/**
|
|
219
|
+
* Emits one aggregated console warning for a group of invalid source-data cells.
|
|
75
220
|
*
|
|
221
|
+
* @param {string} message The warning message template.
|
|
222
|
+
* @param {Array} items The invalid cell entries to report.
|
|
223
|
+
* @returns {void}
|
|
76
224
|
*/ function logSourceDataWarning(message, items) {
|
|
77
225
|
logAggregatedItems({
|
|
78
226
|
logFunction: warn,
|