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.
Files changed (127) hide show
  1. package/3rdparty/walkontable/src/renderer/cells.d.ts +0 -6
  2. package/3rdparty/walkontable/src/renderer/cells.js +2 -7
  3. package/3rdparty/walkontable/src/renderer/cells.mjs +2 -7
  4. package/3rdparty/walkontable/src/renderer/colGroup.js +1 -1
  5. package/3rdparty/walkontable/src/renderer/colGroup.mjs +1 -1
  6. package/3rdparty/walkontable/src/renderer/columnHeaderRows.js +1 -1
  7. package/3rdparty/walkontable/src/renderer/columnHeaderRows.mjs +1 -1
  8. package/3rdparty/walkontable/src/renderer/columnHeaders.d.ts +0 -6
  9. package/3rdparty/walkontable/src/renderer/columnHeaders.js +2 -7
  10. package/3rdparty/walkontable/src/renderer/columnHeaders.mjs +2 -7
  11. package/3rdparty/walkontable/src/renderer/rowHeaders.d.ts +0 -6
  12. package/3rdparty/walkontable/src/renderer/rowHeaders.js +5 -10
  13. package/3rdparty/walkontable/src/renderer/rowHeaders.mjs +5 -10
  14. package/3rdparty/walkontable/src/renderer/rows.js +1 -1
  15. package/3rdparty/walkontable/src/renderer/rows.mjs +1 -1
  16. package/3rdparty/walkontable/src/utils/nodesPool.d.ts +10 -10
  17. package/3rdparty/walkontable/src/utils/nodesPool.js +16 -32
  18. package/3rdparty/walkontable/src/utils/nodesPool.mjs +11 -31
  19. package/3rdparty/walkontable/src/utils/orderView/constants.d.ts +0 -36
  20. package/3rdparty/walkontable/src/utils/orderView/constants.js +0 -24
  21. package/3rdparty/walkontable/src/utils/orderView/constants.mjs +0 -30
  22. package/3rdparty/walkontable/src/utils/orderView/view.d.ts +27 -20
  23. package/3rdparty/walkontable/src/utils/orderView/view.js +79 -16
  24. package/3rdparty/walkontable/src/utils/orderView/view.mjs +86 -17
  25. package/3rdparty/walkontable/src/utils/orderView/viewSize.d.ts +2 -14
  26. package/3rdparty/walkontable/src/utils/orderView/viewSize.js +2 -14
  27. package/3rdparty/walkontable/src/utils/orderView/viewSize.mjs +2 -14
  28. package/CHANGELOG.md +4 -1
  29. package/base.js +2 -2
  30. package/base.mjs +2 -2
  31. package/core/types.d.ts +4 -0
  32. package/core.js +25 -4
  33. package/core.mjs +25 -4
  34. package/dataMap/metaManager/index.d.ts +22 -0
  35. package/dataMap/metaManager/index.js +20 -0
  36. package/dataMap/metaManager/index.mjs +20 -0
  37. package/dataMap/metaManager/metaLayers/cellMeta.d.ts +30 -0
  38. package/dataMap/metaManager/metaLayers/cellMeta.js +110 -0
  39. package/dataMap/metaManager/metaLayers/cellMeta.mjs +111 -1
  40. package/dataMap/metaManager/metaSchema.js +9 -0
  41. package/dataMap/metaManager/metaSchema.mjs +9 -0
  42. package/dataMap/sourceDataValidator.d.ts +25 -2
  43. package/dataMap/sourceDataValidator.js +163 -29
  44. package/dataMap/sourceDataValidator.mjs +178 -30
  45. package/dist/handsontable.full.js +2182 -2392
  46. package/dist/handsontable.full.min.js +95 -95
  47. package/dist/handsontable.js +2182 -2387
  48. package/dist/handsontable.min.js +95 -95
  49. package/dist/themes/classic.js +2 -2
  50. package/dist/themes/classic.min.js +2 -2
  51. package/dist/themes/horizon.js +2 -2
  52. package/dist/themes/horizon.min.js +2 -2
  53. package/dist/themes/main.js +2 -2
  54. package/dist/themes/main.min.js +2 -2
  55. package/dist/themes/static/variables/colors/ant.js +2 -2
  56. package/dist/themes/static/variables/colors/ant.min.js +2 -2
  57. package/dist/themes/static/variables/colors/classic.js +2 -2
  58. package/dist/themes/static/variables/colors/classic.min.js +2 -2
  59. package/dist/themes/static/variables/colors/horizon.js +2 -2
  60. package/dist/themes/static/variables/colors/horizon.min.js +2 -2
  61. package/dist/themes/static/variables/colors/main.js +2 -2
  62. package/dist/themes/static/variables/colors/main.min.js +2 -2
  63. package/dist/themes/static/variables/colors/material.js +2 -2
  64. package/dist/themes/static/variables/colors/material.min.js +2 -2
  65. package/dist/themes/static/variables/colors/shadcn.js +2 -2
  66. package/dist/themes/static/variables/colors/shadcn.min.js +2 -2
  67. package/dist/themes/static/variables/density.js +2 -2
  68. package/dist/themes/static/variables/density.min.js +2 -2
  69. package/dist/themes/static/variables/helpers/iconsMap.js +2 -2
  70. package/dist/themes/static/variables/helpers/iconsMap.min.js +2 -2
  71. package/dist/themes/static/variables/icons/horizon.js +2 -2
  72. package/dist/themes/static/variables/icons/horizon.min.js +2 -2
  73. package/dist/themes/static/variables/icons/main.js +2 -2
  74. package/dist/themes/static/variables/icons/main.min.js +2 -2
  75. package/dist/themes/static/variables/sizing.js +2 -2
  76. package/dist/themes/static/variables/sizing.min.js +2 -2
  77. package/dist/themes/static/variables/tokens/classic.js +2 -2
  78. package/dist/themes/static/variables/tokens/classic.min.js +2 -2
  79. package/dist/themes/static/variables/tokens/horizon.js +2 -2
  80. package/dist/themes/static/variables/tokens/horizon.min.js +2 -2
  81. package/dist/themes/static/variables/tokens/main.js +2 -2
  82. package/dist/themes/static/variables/tokens/main.min.js +2 -2
  83. package/helpers/dom/element.js +13 -6
  84. package/helpers/dom/element.mjs +13 -6
  85. package/helpers/mixed.js +2 -2
  86. package/helpers/mixed.mjs +2 -2
  87. package/package.json +1 -1
  88. package/styles/handsontable.css +2 -2
  89. package/styles/handsontable.min.css +2 -2
  90. package/styles/handsontableStyles.js +1 -1
  91. package/styles/handsontableStyles.mjs +1 -1
  92. package/styles/ht-icons-horizon.min.css +2 -2
  93. package/styles/ht-icons-main.min.css +2 -2
  94. package/styles/ht-theme-classic-no-icons.min.css +2 -2
  95. package/styles/ht-theme-classic.min.css +2 -2
  96. package/styles/ht-theme-horizon-no-icons.min.css +2 -2
  97. package/styles/ht-theme-horizon.min.css +2 -2
  98. package/styles/ht-theme-main-no-icons.min.css +2 -2
  99. package/styles/ht-theme-main.min.css +2 -2
  100. package/utils/licenseNotification.js +1 -1
  101. package/utils/licenseNotification.mjs +1 -1
  102. package/validators/dateValidator/dateValidator.d.ts +3 -0
  103. package/validators/dateValidator/dateValidator.js +4 -0
  104. package/validators/dateValidator/dateValidator.mjs +4 -0
  105. package/validators/timeValidator/timeValidator.d.ts +3 -0
  106. package/validators/timeValidator/timeValidator.js +4 -0
  107. package/validators/timeValidator/timeValidator.mjs +4 -0
  108. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.d.mts +0 -1
  109. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.d.ts +0 -60
  110. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.js +0 -96
  111. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/differBasedRendererAdapter.mjs +0 -91
  112. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.d.mts +0 -1
  113. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.d.ts +0 -45
  114. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.js +0 -97
  115. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/directDomRendererAdapter.mjs +0 -91
  116. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.d.mts +0 -1
  117. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.d.ts +0 -10
  118. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.js +0 -19
  119. package/3rdparty/walkontable/src/utils/orderView/rendererAdapter/index.mjs +0 -14
  120. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.d.mts +0 -1
  121. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.d.ts +0 -44
  122. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.js +0 -138
  123. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/index.mjs +0 -133
  124. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.d.mts +0 -1
  125. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.d.ts +0 -57
  126. package/3rdparty/walkontable/src/utils/orderView/viewDiffer/viewOrder.js +0 -137
  127. 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
- function runSourceDataValidators(hotInstance, source) {
44
- const rowSourceCount = hotInstance.countSourceRows();
45
- const colSourceCount = hotInstance.countSourceCols();
46
- if (rowSourceCount === 0 || colSourceCount === 0) {
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 invalidByMessageType = new Map();
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 = hotInstance.toVisualRow(row);
53
- const visualColumn = hotInstance.toVisualColumn(col);
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
- const { sourceDataWarningMessage, sourceDataValidator, allowInvalid } = cellMeta;
59
- if (!(0, _function.isFunction)(sourceDataValidator)) {
146
+ if (!(0, _function.isFunction)(cellMeta.sourceDataValidator)) {
60
147
  continue;
61
148
  }
62
- const dataSource = hotInstance._getDataSource();
63
- const value = dataSource.getAtCell(row, col, null);
64
- const isValid = sourceDataValidator(value, cellMeta, source);
65
- if (isValid === true) {
66
- continue;
67
- }
68
- if (allowInvalid === false) {
69
- dataSource.setAtCell(row, col, null);
70
- }
71
- if (sourceDataWarningMessage) {
72
- const message = sourceDataWarningMessage;
73
- const list = invalidByMessageType.get(message) ?? [];
74
- list.push({
75
- row,
76
- col,
77
- value,
78
- message
79
- });
80
- invalidByMessageType.set(message, list);
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
- * Runs source-data validators for all cells and emits one aggregated warning per cell type.
29
- */ export function runSourceDataValidators(hotInstance, source) {
30
- const rowSourceCount = hotInstance.countSourceRows();
31
- const colSourceCount = hotInstance.countSourceCols();
32
- if (rowSourceCount === 0 || colSourceCount === 0) {
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 invalidByMessageType = new Map();
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 = hotInstance.toVisualRow(row);
39
- const visualColumn = hotInstance.toVisualColumn(col);
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
- const { sourceDataWarningMessage, sourceDataValidator, allowInvalid } = cellMeta;
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
- if (allowInvalid === false) {
55
- dataSource.setAtCell(row, col, null);
56
- }
57
- if (sourceDataWarningMessage) {
58
- const message = sourceDataWarningMessage;
59
- const list = invalidByMessageType.get(message) ?? [];
60
- list.push({
61
- row,
62
- col,
63
- value,
64
- message
65
- });
66
- invalidByMessageType.set(message, list);
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,