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
@@ -1,10 +1,15 @@
1
1
  import { ViewSizeSet } from './viewSizeSet';
2
- import { ViewDiffer } from './viewDiffer';
3
2
  /**
4
3
  * Executive model for each table renderer. It's responsible for injecting DOM nodes in a
5
4
  * specified order and adjusting the number of elements in the root node.
6
5
  *
7
- * Only this class have rights to juggling DOM elements within the root node (see render method).
6
+ * Rendering writes directly to the DOM: the root node keeps exactly `viewSize` children, and on
7
+ * each render cycle the existing children are reused in place (their content is overwritten by the
8
+ * cell renderer). There is no node pool keyed by source coordinate and no diffing strategy — those
9
+ * were removed because keeping a fixed, viewport-sized set of nodes is simpler and bounds memory to
10
+ * the viewport regardless of dataset size.
11
+ *
12
+ * Only this class has rights to juggle DOM elements within the root node (see render method).
8
13
  *
9
14
  * @class {OrderView}
10
15
  */
@@ -20,7 +25,7 @@ export declare class OrderView {
20
25
  *
21
26
  * @type {function(number): HTMLElement}
22
27
  */
23
- nodesPool: (index?: number) => HTMLElement;
28
+ nodesPool: () => HTMLElement;
24
29
  /**
25
30
  * Holder for sizing and positioning of the view.
26
31
  *
@@ -34,32 +39,26 @@ export declare class OrderView {
34
39
  */
35
40
  collectedNodes: HTMLElement[];
36
41
  /**
37
- * The differ which calculates the differences between current and next view. It generates
38
- * commands that are processed by the OrderView (see `applyCommand` method).
39
- *
40
- * @type {ViewDiffer}
41
- */
42
- viewDiffer: ViewDiffer;
43
- /**
44
- * Node type which the order view will manage while rendering the DOM elements.
42
+ * Node type which the order view will manage while rendering the DOM elements (uppercased to
43
+ * match `Element.tagName`).
45
44
  *
46
45
  * @type {string}
47
46
  */
48
47
  childNodeType: string;
49
48
  /**
50
- * The renderer adapter that handles browser-specific rendering logic.
49
+ * The visual index of the currently processed child within a render cycle.
51
50
  *
52
- * @type {StandardRendererAdapter|FirefoxRendererAdapter}
51
+ * @type {number}
53
52
  */
54
- rendererAdapter: import("./rendererAdapter/differBasedRendererAdapter").DifferBasedRendererAdapter | import("./rendererAdapter/directDomRendererAdapter").DirectDomRendererAdapter;
53
+ visualIndex: number;
55
54
  /**
56
55
  * Creates a new OrderView instance.
57
56
  *
58
57
  * @param {HTMLElement} rootNode The root node to manage.
59
- * @param {function(number?): HTMLElement} nodesPool Factory for creating new DOM elements.
58
+ * @param {function(): HTMLElement} nodesPool Factory for creating new DOM elements.
60
59
  * @param {string} childNodeType The type of child nodes to manage.
61
60
  */
62
- constructor(rootNode: HTMLElement, nodesPool: (index?: number) => HTMLElement, childNodeType: string);
61
+ constructor(rootNode: HTMLElement, nodesPool: () => HTMLElement, childNodeType: string);
63
62
  /**
64
63
  * Sets the size for rendered elements. It can be a size for rows, cells or size for row
65
64
  * headers etc. It depends for what table renderer this instance was created.
@@ -98,18 +97,26 @@ export declare class OrderView {
98
97
  */
99
98
  getCurrentNode(): HTMLElement | null;
100
99
  /**
101
- * Setups and prepares all necessary properties and start the rendering process.
100
+ * Returns the count of children in the root node that belong to this view (for shared root nodes
101
+ * only the children matching this view's node type and working space are counted).
102
+ *
103
+ * @returns {number}
104
+ */
105
+ getRenderedChildCount(): number;
106
+ /**
107
+ * Setups and prepares all necessary properties and starts the rendering process. It adjusts the
108
+ * number of children in the root node to the next view size, creating or removing nodes as needed.
102
109
  * This method has to be called only once (at the start) for the render cycle.
103
110
  */
104
111
  start(): void;
105
112
  /**
106
- * Renders the DOM element based on visual index (which is calculated internally).
113
+ * Renders the DOM element for the current visual index by reusing the child already in place.
114
+ * If a child of the wrong node type occupies the slot, it is replaced with a freshly created one.
107
115
  * This method has to be called as many times as the size count is met (to cover all previously rendered DOM elements).
108
116
  */
109
117
  render(): void;
110
118
  /**
111
- * Ends the render process.
112
- * This method has to be called only once (at the end) for the render cycle.
119
+ * Ends the render process. No cleanup is required — all work is done in `start()` and `render()`.
113
120
  */
114
121
  end(): void;
115
122
  }
@@ -9,8 +9,7 @@ Object.defineProperty(exports, "OrderView", {
9
9
  }
10
10
  });
11
11
  const _viewSizeSet = require("./viewSizeSet");
12
- const _viewDiffer = require("./viewDiffer");
13
- const _rendererAdapter = require("./rendererAdapter");
12
+ const _constants = require("./constants");
14
13
  class OrderView {
15
14
  /**
16
15
  * Sets the size for rendered elements. It can be a size for rows, cells or size for row
@@ -58,28 +57,94 @@ class OrderView {
58
57
  return length > 0 ? this.collectedNodes[length - 1] : null;
59
58
  }
60
59
  /**
61
- * Setups and prepares all necessary properties and start the rendering process.
60
+ * Returns the count of children in the root node that belong to this view (for shared root nodes
61
+ * only the children matching this view's node type and working space are counted).
62
+ *
63
+ * @returns {number}
64
+ */ getRenderedChildCount() {
65
+ const { rootNode, sizeSet } = this;
66
+ let childElementCount = 0;
67
+ if (this.isSharedViewSet()) {
68
+ let element = rootNode.firstElementChild;
69
+ while(element){
70
+ if (element.tagName === this.childNodeType) {
71
+ childElementCount += 1;
72
+ } else if (sizeSet.isPlaceOn(_constants.WORKING_SPACE_TOP)) {
73
+ break;
74
+ }
75
+ element = element.nextElementSibling;
76
+ }
77
+ } else {
78
+ childElementCount = rootNode.childElementCount;
79
+ }
80
+ return childElementCount;
81
+ }
82
+ /**
83
+ * Setups and prepares all necessary properties and starts the rendering process. It adjusts the
84
+ * number of children in the root node to the next view size, creating or removing nodes as needed.
62
85
  * This method has to be called only once (at the start) for the render cycle.
63
86
  */ start() {
64
- this.rendererAdapter.start();
87
+ this.collectedNodes.length = 0;
88
+ this.visualIndex = 0;
89
+ const { rootNode, sizeSet } = this;
90
+ const isShared = this.isSharedViewSet();
91
+ const { nextSize } = sizeSet.getViewSize();
92
+ let childElementCount = this.getRenderedChildCount();
93
+ while(childElementCount < nextSize){
94
+ const newNode = this.nodesPool();
95
+ if (!isShared || sizeSet.isPlaceOn(_constants.WORKING_SPACE_BOTTOM)) {
96
+ rootNode.appendChild(newNode);
97
+ } else {
98
+ rootNode.insertBefore(newNode, rootNode.firstChild);
99
+ }
100
+ childElementCount += 1;
101
+ }
102
+ const isSharedPlacedOnTop = isShared && sizeSet.isPlaceOn(_constants.WORKING_SPACE_TOP);
103
+ while(childElementCount > nextSize){
104
+ const childToRemove = isSharedPlacedOnTop ? rootNode.firstChild : rootNode.lastChild;
105
+ if (childToRemove) {
106
+ rootNode.removeChild(childToRemove);
107
+ }
108
+ childElementCount -= 1;
109
+ }
65
110
  }
66
111
  /**
67
- * Renders the DOM element based on visual index (which is calculated internally).
112
+ * Renders the DOM element for the current visual index by reusing the child already in place.
113
+ * If a child of the wrong node type occupies the slot, it is replaced with a freshly created one.
68
114
  * This method has to be called as many times as the size count is met (to cover all previously rendered DOM elements).
69
115
  */ render() {
70
- this.rendererAdapter.render();
116
+ const { rootNode, sizeSet } = this;
117
+ let visualIndex = this.visualIndex;
118
+ if (this.isSharedViewSet() && sizeSet.isPlaceOn(_constants.WORKING_SPACE_BOTTOM) && sizeSet.sharedSize) {
119
+ visualIndex += sizeSet.sharedSize.nextSize;
120
+ }
121
+ // Index by element children only (`children`, not `childNodes`) to stay consistent with the
122
+ // element-based counting in `start()` and `getRenderedChildCount()`; a stray text/comment node
123
+ // in the root would otherwise shift the slot.
124
+ let node = rootNode.children[visualIndex];
125
+ if (!node) {
126
+ // Defensive guard: `start()` always provisions enough children for this render cycle, so the
127
+ // slot should exist. Recreate it rather than dereferencing `undefined` if that ever breaks.
128
+ node = this.nodesPool();
129
+ rootNode.appendChild(node);
130
+ } else if (node.tagName !== this.childNodeType) {
131
+ const newNode = this.nodesPool();
132
+ node.replaceWith(newNode);
133
+ node = newNode;
134
+ }
135
+ this.collectedNodes.push(node);
136
+ this.visualIndex += 1;
71
137
  }
72
138
  /**
73
- * Ends the render process.
74
- * This method has to be called only once (at the end) for the render cycle.
139
+ * Ends the render process. No cleanup is required — all work is done in `start()` and `render()`.
75
140
  */ end() {
76
- this.rendererAdapter.end();
141
+ // intentionally empty
77
142
  }
78
143
  /**
79
144
  * Creates a new OrderView instance.
80
145
  *
81
146
  * @param {HTMLElement} rootNode The root node to manage.
82
- * @param {function(number?): HTMLElement} nodesPool Factory for creating new DOM elements.
147
+ * @param {function(): HTMLElement} nodesPool Factory for creating new DOM elements.
83
148
  * @param {string} childNodeType The type of child nodes to manage.
84
149
  */ constructor(rootNode, nodesPool, childNodeType){
85
150
  /**
@@ -93,14 +158,12 @@ class OrderView {
93
158
  * @type {HTMLElement[]}
94
159
  */ this.collectedNodes = [];
95
160
  /**
96
- * The differ which calculates the differences between current and next view. It generates
97
- * commands that are processed by the OrderView (see `applyCommand` method).
161
+ * The visual index of the currently processed child within a render cycle.
98
162
  *
99
- * @type {ViewDiffer}
100
- */ this.viewDiffer = new _viewDiffer.ViewDiffer(this.sizeSet);
163
+ * @type {number}
164
+ */ this.visualIndex = 0;
101
165
  this.rootNode = rootNode;
102
166
  this.nodesPool = nodesPool;
103
- this.childNodeType = childNodeType;
104
- this.rendererAdapter = (0, _rendererAdapter.createRendererAdapter)(this);
167
+ this.childNodeType = typeof childNodeType === 'string' ? childNodeType.toUpperCase() : childNodeType;
105
168
  }
106
169
  }
@@ -1,11 +1,16 @@
1
1
  import { ViewSizeSet } from './viewSizeSet.mjs';
2
- import { ViewDiffer } from './viewDiffer/index.mjs';
3
- import { createRendererAdapter } from './rendererAdapter/index.mjs';
2
+ import { WORKING_SPACE_TOP, WORKING_SPACE_BOTTOM } from './constants.mjs';
4
3
  /**
5
4
  * Executive model for each table renderer. It's responsible for injecting DOM nodes in a
6
5
  * specified order and adjusting the number of elements in the root node.
7
6
  *
8
- * Only this class have rights to juggling DOM elements within the root node (see render method).
7
+ * Rendering writes directly to the DOM: the root node keeps exactly `viewSize` children, and on
8
+ * each render cycle the existing children are reused in place (their content is overwritten by the
9
+ * cell renderer). There is no node pool keyed by source coordinate and no diffing strategy — those
10
+ * were removed because keeping a fixed, viewport-sized set of nodes is simpler and bounds memory to
11
+ * the viewport regardless of dataset size.
12
+ *
13
+ * Only this class has rights to juggle DOM elements within the root node (see render method).
9
14
  *
10
15
  * @class {OrderView}
11
16
  */ export class OrderView {
@@ -55,28 +60,94 @@ import { createRendererAdapter } from './rendererAdapter/index.mjs';
55
60
  return length > 0 ? this.collectedNodes[length - 1] : null;
56
61
  }
57
62
  /**
58
- * Setups and prepares all necessary properties and start the rendering process.
63
+ * Returns the count of children in the root node that belong to this view (for shared root nodes
64
+ * only the children matching this view's node type and working space are counted).
65
+ *
66
+ * @returns {number}
67
+ */ getRenderedChildCount() {
68
+ const { rootNode, sizeSet } = this;
69
+ let childElementCount = 0;
70
+ if (this.isSharedViewSet()) {
71
+ let element = rootNode.firstElementChild;
72
+ while(element){
73
+ if (element.tagName === this.childNodeType) {
74
+ childElementCount += 1;
75
+ } else if (sizeSet.isPlaceOn(WORKING_SPACE_TOP)) {
76
+ break;
77
+ }
78
+ element = element.nextElementSibling;
79
+ }
80
+ } else {
81
+ childElementCount = rootNode.childElementCount;
82
+ }
83
+ return childElementCount;
84
+ }
85
+ /**
86
+ * Setups and prepares all necessary properties and starts the rendering process. It adjusts the
87
+ * number of children in the root node to the next view size, creating or removing nodes as needed.
59
88
  * This method has to be called only once (at the start) for the render cycle.
60
89
  */ start() {
61
- this.rendererAdapter.start();
90
+ this.collectedNodes.length = 0;
91
+ this.visualIndex = 0;
92
+ const { rootNode, sizeSet } = this;
93
+ const isShared = this.isSharedViewSet();
94
+ const { nextSize } = sizeSet.getViewSize();
95
+ let childElementCount = this.getRenderedChildCount();
96
+ while(childElementCount < nextSize){
97
+ const newNode = this.nodesPool();
98
+ if (!isShared || sizeSet.isPlaceOn(WORKING_SPACE_BOTTOM)) {
99
+ rootNode.appendChild(newNode);
100
+ } else {
101
+ rootNode.insertBefore(newNode, rootNode.firstChild);
102
+ }
103
+ childElementCount += 1;
104
+ }
105
+ const isSharedPlacedOnTop = isShared && sizeSet.isPlaceOn(WORKING_SPACE_TOP);
106
+ while(childElementCount > nextSize){
107
+ const childToRemove = isSharedPlacedOnTop ? rootNode.firstChild : rootNode.lastChild;
108
+ if (childToRemove) {
109
+ rootNode.removeChild(childToRemove);
110
+ }
111
+ childElementCount -= 1;
112
+ }
62
113
  }
63
114
  /**
64
- * Renders the DOM element based on visual index (which is calculated internally).
115
+ * Renders the DOM element for the current visual index by reusing the child already in place.
116
+ * If a child of the wrong node type occupies the slot, it is replaced with a freshly created one.
65
117
  * This method has to be called as many times as the size count is met (to cover all previously rendered DOM elements).
66
118
  */ render() {
67
- this.rendererAdapter.render();
119
+ const { rootNode, sizeSet } = this;
120
+ let visualIndex = this.visualIndex;
121
+ if (this.isSharedViewSet() && sizeSet.isPlaceOn(WORKING_SPACE_BOTTOM) && sizeSet.sharedSize) {
122
+ visualIndex += sizeSet.sharedSize.nextSize;
123
+ }
124
+ // Index by element children only (`children`, not `childNodes`) to stay consistent with the
125
+ // element-based counting in `start()` and `getRenderedChildCount()`; a stray text/comment node
126
+ // in the root would otherwise shift the slot.
127
+ let node = rootNode.children[visualIndex];
128
+ if (!node) {
129
+ // Defensive guard: `start()` always provisions enough children for this render cycle, so the
130
+ // slot should exist. Recreate it rather than dereferencing `undefined` if that ever breaks.
131
+ node = this.nodesPool();
132
+ rootNode.appendChild(node);
133
+ } else if (node.tagName !== this.childNodeType) {
134
+ const newNode = this.nodesPool();
135
+ node.replaceWith(newNode);
136
+ node = newNode;
137
+ }
138
+ this.collectedNodes.push(node);
139
+ this.visualIndex += 1;
68
140
  }
69
141
  /**
70
- * Ends the render process.
71
- * This method has to be called only once (at the end) for the render cycle.
142
+ * Ends the render process. No cleanup is required — all work is done in `start()` and `render()`.
72
143
  */ end() {
73
- this.rendererAdapter.end();
144
+ // intentionally empty
74
145
  }
75
146
  /**
76
147
  * Creates a new OrderView instance.
77
148
  *
78
149
  * @param {HTMLElement} rootNode The root node to manage.
79
- * @param {function(number?): HTMLElement} nodesPool Factory for creating new DOM elements.
150
+ * @param {function(): HTMLElement} nodesPool Factory for creating new DOM elements.
80
151
  * @param {string} childNodeType The type of child nodes to manage.
81
152
  */ constructor(rootNode, nodesPool, childNodeType){
82
153
  /**
@@ -90,14 +161,12 @@ import { createRendererAdapter } from './rendererAdapter/index.mjs';
90
161
  * @type {HTMLElement[]}
91
162
  */ this.collectedNodes = [];
92
163
  /**
93
- * The differ which calculates the differences between current and next view. It generates
94
- * commands that are processed by the OrderView (see `applyCommand` method).
164
+ * The visual index of the currently processed child within a render cycle.
95
165
  *
96
- * @type {ViewDiffer}
97
- */ this.viewDiffer = new ViewDiffer(this.sizeSet);
166
+ * @type {number}
167
+ */ this.visualIndex = 0;
98
168
  this.rootNode = rootNode;
99
169
  this.nodesPool = nodesPool;
100
- this.childNodeType = childNodeType;
101
- this.rendererAdapter = createRendererAdapter(this);
170
+ this.childNodeType = typeof childNodeType === 'string' ? childNodeType.toUpperCase() : childNodeType;
102
171
  }
103
172
  }
@@ -5,25 +5,13 @@
5
5
  */
6
6
  export declare class ViewSize {
7
7
  /**
8
- * Current size of the rendered DOM elements.
9
- *
10
- * @type {number}
11
- */
12
- currentSize: number;
13
- /**
14
- * Next size of the rendered DOM elements which should be fulfilled.
8
+ * Size of the rendered DOM elements which should be fulfilled.
15
9
  *
16
10
  * @type {number}
17
11
  */
18
12
  nextSize: number;
19
13
  /**
20
- * Current offset.
21
- *
22
- * @type {number}
23
- */
24
- currentOffset: number;
25
- /**
26
- * Next offset.
14
+ * Offset of the first rendered element according to the scroll position.
27
15
  *
28
16
  * @type {number}
29
17
  */
@@ -18,7 +18,6 @@ class ViewSize {
18
18
  *
19
19
  * @param {number} size The size.
20
20
  */ setSize(size) {
21
- this.currentSize = this.nextSize;
22
21
  this.nextSize = size;
23
22
  }
24
23
  /**
@@ -26,27 +25,16 @@ class ViewSize {
26
25
  *
27
26
  * @param {number} offset The offset.
28
27
  */ setOffset(offset) {
29
- this.currentOffset = this.nextOffset;
30
28
  this.nextOffset = offset;
31
29
  }
32
30
  constructor(){
33
31
  /**
34
- * Current size of the rendered DOM elements.
35
- *
36
- * @type {number}
37
- */ this.currentSize = 0;
38
- /**
39
- * Next size of the rendered DOM elements which should be fulfilled.
32
+ * Size of the rendered DOM elements which should be fulfilled.
40
33
  *
41
34
  * @type {number}
42
35
  */ this.nextSize = 0;
43
36
  /**
44
- * Current offset.
45
- *
46
- * @type {number}
47
- */ this.currentOffset = 0;
48
- /**
49
- * Next offset.
37
+ * Offset of the first rendered element according to the scroll position.
50
38
  *
51
39
  * @type {number}
52
40
  */ this.nextOffset = 0;
@@ -8,7 +8,6 @@
8
8
  *
9
9
  * @param {number} size The size.
10
10
  */ setSize(size) {
11
- this.currentSize = this.nextSize;
12
11
  this.nextSize = size;
13
12
  }
14
13
  /**
@@ -16,27 +15,16 @@
16
15
  *
17
16
  * @param {number} offset The offset.
18
17
  */ setOffset(offset) {
19
- this.currentOffset = this.nextOffset;
20
18
  this.nextOffset = offset;
21
19
  }
22
20
  constructor(){
23
21
  /**
24
- * Current size of the rendered DOM elements.
25
- *
26
- * @type {number}
27
- */ this.currentSize = 0;
28
- /**
29
- * Next size of the rendered DOM elements which should be fulfilled.
22
+ * Size of the rendered DOM elements which should be fulfilled.
30
23
  *
31
24
  * @type {number}
32
25
  */ this.nextSize = 0;
33
26
  /**
34
- * Current offset.
35
- *
36
- * @type {number}
37
- */ this.currentOffset = 0;
38
- /**
39
- * Next offset.
27
+ * Offset of the first rendered element according to the scroll position.
40
28
  *
41
29
  * @type {number}
42
30
  */ this.nextOffset = 0;
package/CHANGELOG.md CHANGED
@@ -9,7 +9,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
10
  <!-- UNVERSIONED -->
11
11
 
12
- ## [18.0.0-rc3] - 2026-06-23
12
+ ## [18.0.0] - 2026-06-30
13
13
 
14
14
  ### Added
15
15
  - **Breaking change**: Added wrapper layout slots and a `layout` option to control the order of UI elements rendered around the grid (pagination, dialog). The license notification always renders last in the bottom area and is not orderable. Built-in UI now mounts into dedicated wrapper containers, which changes the root DOM structure. [#12094](https://github.com/handsontable/handsontable/issues/12094)
@@ -32,6 +32,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
32
32
  - Changed the Notification plugin to render its toasts in the grid's overlay layer. [#12777](https://github.com/handsontable/handsontable/issues/12777)
33
33
  - Angular: Modernized the Angular wrapper to align with Angular 17–19, simplify setup, reduce dependencies, and clean up tooling. [#12451](https://github.com/handsontable/handsontable/issues/12451)
34
34
  - Angular: Added support for installing Angular 16 through 22, without the --force flag [#12752](https://github.com/handsontable/handsontable/issues/12752)
35
+ - Reduced memory usage and improved initialization time for large datasets by no longer materializing cell metadata for every cell during source data validation. [#12847](https://github.com/handsontable/handsontable/pull/12847)
35
36
 
36
37
  ### Removed
37
38
  - **Breaking change**: Removed the numbro, moment.js, DOMPurify, and @handsontable/pikaday dependencies. [#12689](https://github.com/handsontable/handsontable/issues/12689)
@@ -120,6 +121,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
120
121
  - React: Fixed React component renderers being unmounted and visibly cleared on every grid render after a cell edit. [#12494](https://github.com/handsontable/handsontable/issues/12494)
121
122
  - React: Fixed an issue where `HotColumn` children removed from a `HotTable` left phantom columns behind in the React wrapper. [#12596](https://github.com/handsontable/handsontable/issues/12596)
122
123
  - Angular: Fixed subscription leak, missing EmbeddedViewRef cleanup, repeated registerRenderer calls, wrong constructor.name, incorrect prop type, missing null guard in ngOnDestroy, premature editor destruction, redundant double-destroy of already-swept renderer refs, and stale editor reuse when a different column takes an index. [#12657](https://github.com/handsontable/handsontable/issues/12657)
124
+ - Fixed cell meta set with `setCellMeta` (for example, `readOnly`) being reset by `updateSettings`. [#12811](https://github.com/handsontable/handsontable/issues/12811)
125
+ - Reduced memory usage during scrolling and fixed potential out-of-memory errors on very large datasets. [#12844](https://github.com/handsontable/handsontable/pull/12844)
123
126
 
124
127
  ## [17.1.0] - 2026-05-19
125
128
 
package/base.js CHANGED
@@ -72,8 +72,8 @@ Handsontable.hooks = _hooks.Hooks.getSingleton();
72
72
  Handsontable.CellCoords = _src.CellCoords;
73
73
  Handsontable.CellRange = _src.CellRange;
74
74
  Handsontable.packageName = 'handsontable';
75
- Handsontable.buildDate = "24/06/2026 08:31:19";
76
- Handsontable.version = "18.0.0-rc4";
75
+ Handsontable.buildDate = "30/06/2026 08:45:33";
76
+ Handsontable.version = "18.0.0";
77
77
  Handsontable.languages = {
78
78
  dictionaryKeys: _registry.dictionaryKeys,
79
79
  getLanguageDictionary: _registry.getLanguageDictionary,
package/base.mjs CHANGED
@@ -43,8 +43,8 @@ Handsontable.hooks = Hooks.getSingleton();
43
43
  Handsontable.CellCoords = CellCoords;
44
44
  Handsontable.CellRange = CellRange;
45
45
  Handsontable.packageName = 'handsontable';
46
- Handsontable.buildDate = "24/06/2026 08:31:19";
47
- Handsontable.version = "18.0.0-rc4";
46
+ Handsontable.buildDate = "30/06/2026 08:45:33";
47
+ Handsontable.version = "18.0.0";
48
48
  Handsontable.languages = {
49
49
  dictionaryKeys,
50
50
  getLanguageDictionary,
package/core/types.d.ts CHANGED
@@ -16,6 +16,8 @@ import type { FocusGridManager as FocusManagerInstance } from '../focusManager/g
16
16
  import type { FocusScopeManager as FocusScopeManagerInstance } from '../focusManager/scopeManager';
17
17
  import type { LayoutManager } from './layout';
18
18
  import type { default as EditorManagerInstance } from '../editorManager';
19
+ import type { default as DataSourceInstance } from '../dataMap/dataSource';
20
+ import type { default as MetaManagerInstance } from '../dataMap/metaManager';
19
21
  import type { BaseEditor as BaseEditorInstance } from '../editors/baseEditor/baseEditor';
20
22
  import type { StylesHandler } from '../utils/stylesHandler';
21
23
  import type { ThemeManager } from '../themes/engine/manager';
@@ -194,6 +196,8 @@ export interface HotInstance {
194
196
  getFocusScopeManager(): FocusScopeManagerInstance;
195
197
  getLayoutManager(): LayoutManager;
196
198
  _getEditorManager(): EditorManagerInstance;
199
+ _getDataSource(): DataSourceInstance;
200
+ _getMetaManager(): MetaManagerInstance;
197
201
  rootElement: HTMLElement;
198
202
  rootDocument: Document;
199
203
  rootWindow: Window & typeof globalThis;
package/core.js CHANGED
@@ -2428,6 +2428,10 @@ function Core(rootContainer, userSettings, rootInstanceSymbol = false) {
2428
2428
  * Since 12.0.0 passing `data` inside `settings` objects no longer results in resetting states corresponding to rows and columns
2429
2429
  * (for example, row/column sequence, column width, row height, frozen columns etc.).
2430
2430
  *
2431
+ * Cell meta set imperatively through [[setCellMeta]] (for example, by the user or the context menu) is preserved across
2432
+ * `updateSettings`, even when `settings` includes `cell`, `cells`, or `columns`. On a direct conflict, a value re-stated
2433
+ * through the declarative `cell` option takes precedence over the preserved imperative value.
2434
+ *
2431
2435
  * When [[Hooks#hasExternalDataSource]] is true, Handsontable clears and rebinds the placeholder dataset only during
2432
2436
  * initialization or when `settings` includes `data` or `dataProvider`. Other keys alone (for example `height`) do not clear loaded rows.
2433
2437
  * If only `columns` changes, the column map is rebuilt without clearing rows.
@@ -2566,9 +2570,17 @@ function Core(rootContainer, userSettings, rootInstanceSymbol = false) {
2566
2570
  }
2567
2571
  const clen = instance.countCols();
2568
2572
  const columnSetting = tableMeta.columns;
2569
- // Clear cell meta cache
2573
+ // Clear cell meta cache. Cell meta set imperatively through `setCellMeta` (for example, by the
2574
+ // user or the context menu) is snapshotted beforehand and replayed afterward, so that it
2575
+ // survives the clear instead of being discarded (see GitHub issue #4446).
2570
2576
  if (settings.cell !== undefined || settings.cells !== undefined || settings.columns !== undefined) {
2577
+ const userDefinedCellMetas = metaManager.getUserDefinedCellMetas();
2571
2578
  metaManager.clearCache();
2579
+ // Replay before the column and `cell` option re-application, so that a value re-stated through
2580
+ // the declarative `cell` option still wins on a direct conflict (preserving legacy behavior).
2581
+ userDefinedCellMetas.forEach(({ physicalRow, physicalColumn, key, value })=>{
2582
+ metaManager.setCellMeta(physicalRow, physicalColumn, key, value);
2583
+ });
2572
2584
  }
2573
2585
  if (clen > 0) {
2574
2586
  for(i = 0, j = 0; i < clen; i++){
@@ -2583,9 +2595,18 @@ function Core(rootContainer, userSettings, rootInstanceSymbol = false) {
2583
2595
  }
2584
2596
  }
2585
2597
  if ((0, _mixed.isDefined)(settings.cell)) {
2586
- settings.cell.forEach((cell)=>{
2587
- instance.setCellMetaObject(cell.row, cell.col, cell);
2588
- });
2598
+ // The `cell` option is declarative - it is re-applied from settings on every `updateSettings`
2599
+ // call. Recording is disabled so these writes are not tracked as user-defined (and a key
2600
+ // re-stated here de-marks any imperative value), keeping `updateSettings({ cell: [] })` able
2601
+ // to remove previously declared entries.
2602
+ metaManager.disableUserDefinedMetaRecording();
2603
+ try {
2604
+ settings.cell.forEach((cell)=>{
2605
+ instance.setCellMetaObject(cell.row, cell.col, cell);
2606
+ });
2607
+ } finally{
2608
+ metaManager.enableUserDefinedMetaRecording();
2609
+ }
2589
2610
  }
2590
2611
  instance.runHooks('afterCellMetaReset');
2591
2612
  // for the first run, we need to run the source data warnings after cell meta initialization
package/core.mjs CHANGED
@@ -2413,6 +2413,10 @@ let activeGuid = null;
2413
2413
  * Since 12.0.0 passing `data` inside `settings` objects no longer results in resetting states corresponding to rows and columns
2414
2414
  * (for example, row/column sequence, column width, row height, frozen columns etc.).
2415
2415
  *
2416
+ * Cell meta set imperatively through [[setCellMeta]] (for example, by the user or the context menu) is preserved across
2417
+ * `updateSettings`, even when `settings` includes `cell`, `cells`, or `columns`. On a direct conflict, a value re-stated
2418
+ * through the declarative `cell` option takes precedence over the preserved imperative value.
2419
+ *
2416
2420
  * When [[Hooks#hasExternalDataSource]] is true, Handsontable clears and rebinds the placeholder dataset only during
2417
2421
  * initialization or when `settings` includes `data` or `dataProvider`. Other keys alone (for example `height`) do not clear loaded rows.
2418
2422
  * If only `columns` changes, the column map is rebuilt without clearing rows.
@@ -2551,9 +2555,17 @@ let activeGuid = null;
2551
2555
  }
2552
2556
  const clen = instance.countCols();
2553
2557
  const columnSetting = tableMeta.columns;
2554
- // Clear cell meta cache
2558
+ // Clear cell meta cache. Cell meta set imperatively through `setCellMeta` (for example, by the
2559
+ // user or the context menu) is snapshotted beforehand and replayed afterward, so that it
2560
+ // survives the clear instead of being discarded (see GitHub issue #4446).
2555
2561
  if (settings.cell !== undefined || settings.cells !== undefined || settings.columns !== undefined) {
2562
+ const userDefinedCellMetas = metaManager.getUserDefinedCellMetas();
2556
2563
  metaManager.clearCache();
2564
+ // Replay before the column and `cell` option re-application, so that a value re-stated through
2565
+ // the declarative `cell` option still wins on a direct conflict (preserving legacy behavior).
2566
+ userDefinedCellMetas.forEach(({ physicalRow, physicalColumn, key, value })=>{
2567
+ metaManager.setCellMeta(physicalRow, physicalColumn, key, value);
2568
+ });
2557
2569
  }
2558
2570
  if (clen > 0) {
2559
2571
  for(i = 0, j = 0; i < clen; i++){
@@ -2568,9 +2580,18 @@ let activeGuid = null;
2568
2580
  }
2569
2581
  }
2570
2582
  if (isDefined(settings.cell)) {
2571
- settings.cell.forEach((cell)=>{
2572
- instance.setCellMetaObject(cell.row, cell.col, cell);
2573
- });
2583
+ // The `cell` option is declarative - it is re-applied from settings on every `updateSettings`
2584
+ // call. Recording is disabled so these writes are not tracked as user-defined (and a key
2585
+ // re-stated here de-marks any imperative value), keeping `updateSettings({ cell: [] })` able
2586
+ // to remove previously declared entries.
2587
+ metaManager.disableUserDefinedMetaRecording();
2588
+ try {
2589
+ settings.cell.forEach((cell)=>{
2590
+ instance.setCellMetaObject(cell.row, cell.col, cell);
2591
+ });
2592
+ } finally{
2593
+ metaManager.enableUserDefinedMetaRecording();
2594
+ }
2574
2595
  }
2575
2596
  instance.runHooks('afterCellMetaReset');
2576
2597
  // for the first run, we need to run the source data warnings after cell meta initialization