canvas-datagrid 0.26.0 → 0.27.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/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ All notable changes to this project will be documented in this file.
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
+ ## 0.27.0 - 2026-11-07
11
+
12
+ ### Changed
13
+
14
+ - **Breaking:** the default `auto` width and height now size the grid to the
15
+ element it is placed in. When the parent has a size of its own in a
16
+ dimension (a fixed height, a flex or grid track, the width of a block), the
17
+ grid fills it, leaving room for the parent's other content, and follows the
18
+ parent when it is resized. When the parent has no size of its own (a
19
+ content-sized height, a shrink-to-fit width) the grid grows to fit its rows
20
+ and columns as before, but no further than the viewport, and scrolls for the
21
+ rest. Grids that previously relied on `auto` to grow to their content now
22
+ fill their container and show scroll bars when the data is larger; set
23
+ `style.width`/`style.height` explicitly to keep a fixed size.
24
+ - Tree grids and cell grids show scroll bars when their data overflows them,
25
+ whatever the parent grid's `width`/`height` style is.
26
+
27
+ ### Fixed
28
+
29
+ - The mouse wheel over a tree grid scrolled the parent grid as well, and with
30
+ nested tree grids every ancestor scrolled. Only the grid under the pointer
31
+ scrolls now, even when it has nothing left to scroll in that direction. The
32
+ pointer over a tree grid no longer counts as hovering the parent's row, and
33
+ collapsing a tree grid removes its wheel listener from the parent.
34
+
10
35
  ## 0.26.0 - 2026-09-19
11
36
 
12
37
  Version note: the project restarted its numbering at 0.3.0 in 2021 while the
@@ -1676,9 +1676,86 @@ __webpack_require__.r(__webpack_exports__);
1676
1676
  };
1677
1677
  };
1678
1678
  /**
1679
- * Watches the host element and its parent for size changes so a grid sized
1680
- * with auto/percentage dimensions follows layout changes (flex, splitters,
1681
- * tab switches) without an explicit `resize()` call (#289, #239).
1679
+ * The element the grid sits in: the parent of the `canvas-datagrid` host
1680
+ * element, or the host of the shadow tree the grid was placed in.
1681
+ * @returns {HTMLElement|undefined}
1682
+ */
1683
+ self.getContainerElement = function () {
1684
+ var host = self.intf && self.intf.nodeType === 1 ? self.intf : self.canvas,
1685
+ container = host && host.parentNode;
1686
+ if (container && container.nodeType === 11 && container.host) {
1687
+ container = container.host;
1688
+ }
1689
+ return container && container.nodeType === 1 ? container : undefined;
1690
+ };
1691
+ /**
1692
+ * Measures the room the container offers the grid in each dimension.
1693
+ * A dimension is `undefined` when the container has no size of its own
1694
+ * there, that is when it grows with the grid (an auto height block, a
1695
+ * shrink-to-fit width); the caller then falls back to the content size.
1696
+ *
1697
+ * The probe makes the canvas one pixel larger than the container's client
1698
+ * size and checks whether the container followed. When it did not, the
1699
+ * overflow that the probe produced tells how much of the container is
1700
+ * taken by padding and siblings. Everything is restored synchronously, so
1701
+ * nothing is painted and no ResizeObserver notification results.
1702
+ * @returns {{width: (number|undefined), height: (number|undefined)}}
1703
+ */
1704
+ self.measureContainerSpace = function () {
1705
+ var space = {},
1706
+ canvas = self.canvas,
1707
+ container = self.getContainerElement(),
1708
+ cache = self.containerSpaceCache,
1709
+ canvasStyle,
1710
+ saved;
1711
+ if (!container || !canvas) {
1712
+ return space;
1713
+ }
1714
+ if (cache && cache.container === container && cache.clientWidth === container.clientWidth && cache.clientHeight === container.clientHeight) {
1715
+ return cache.space;
1716
+ }
1717
+ canvasStyle = canvas.style;
1718
+ saved = {
1719
+ width: canvasStyle.width,
1720
+ height: canvasStyle.height
1721
+ };
1722
+ ['height', 'width'].forEach(function (dim) {
1723
+ var client = dim === 'height' ? 'clientHeight' : 'clientWidth',
1724
+ scroll = dim === 'height' ? 'scrollHeight' : 'scrollWidth',
1725
+ before = container[client],
1726
+ probe = before + 1,
1727
+ after;
1728
+ canvasStyle[dim] = probe + 'px';
1729
+ after = container[client];
1730
+ if (before === 0 || after > before) {
1731
+ // nothing to measure (hidden or inline) or the container grew with
1732
+ // the grid: it has no size of its own in this dimension
1733
+ space[dim] = undefined;
1734
+ } else {
1735
+ // the container kept its size and the grid overflows it by at least
1736
+ // one pixel; the overflow beyond that pixel is padding and siblings
1737
+ space[dim] = Math.max(0, Math.min(after, 2 * after + 1 - container[scroll]));
1738
+ }
1739
+ canvasStyle[dim] = saved[dim];
1740
+ });
1741
+ // resize() may run several times in one frame (drags, style changes);
1742
+ // the probe forces layout, so reuse the result while the container
1743
+ // keeps its size
1744
+ self.containerSpaceCache = {
1745
+ container: container,
1746
+ clientWidth: container.clientWidth,
1747
+ clientHeight: container.clientHeight,
1748
+ space: space
1749
+ };
1750
+ requestAnimationFrame(function () {
1751
+ self.containerSpaceCache = undefined;
1752
+ });
1753
+ return space;
1754
+ };
1755
+ /**
1756
+ * Watches the host element and its container for size changes so a grid
1757
+ * sized with auto/percentage dimensions follows layout changes (flex,
1758
+ * splitters, tab switches) without an explicit `resize()` call (#289, #239).
1682
1759
  * Falls back to the `window` resize listener when ResizeObserver is missing.
1683
1760
  */
1684
1761
  self.initResizeObserver = function () {
@@ -1720,7 +1797,7 @@ __webpack_require__.r(__webpack_exports__);
1720
1797
  self.resizeObserver.disconnect();
1721
1798
  self.observedSizes = new Map();
1722
1799
  var host = self.intf && self.intf.nodeType === 1 ? self.intf : self.canvas;
1723
- [host, host && host.parentNode].forEach(function (target) {
1800
+ [host, self.getContainerElement()].forEach(function (target) {
1724
1801
  if (target && target.nodeType === 1) {
1725
1802
  self.resizeObserver.observe(target);
1726
1803
  }
@@ -3870,15 +3947,31 @@ __webpack_require__.r(__webpack_exports__);
3870
3947
  treeGrid.parentNode.columnGroupsAreaHeight += columnGroupsAreaHeight || 0;
3871
3948
  treeGrid.parentNode.rowGroupsAreaWidth += rowGroupsAreaWidth || 0;
3872
3949
  }
3950
+ // Hit-test entry for the child grid's area. Like every other visible
3951
+ // cell it is in this grid's own coordinates (the child's offsetTop and
3952
+ // offsetLeft are canvas coordinates) and carries the real tree size:
3953
+ // `treeGrid.height`/`width` are undefined on child grids, which made
3954
+ // this entry unmatchable, so a pointer over a tree grid still counted
3955
+ // as hovering the parent and the parent scrolled along with the tree.
3873
3956
  self.visibleCells.unshift({
3874
3957
  rowIndex: rowOrderIndex,
3875
3958
  columnIndex: 0,
3876
- y: treeGrid.parentNode.offsetTop,
3877
- x: treeGrid.parentNode.offsetLeft,
3878
- height: treeGrid.height,
3879
- width: treeGrid.width,
3959
+ y: y + rowSansTreeHeight,
3960
+ x: rowHeaderCellWidth - 1,
3961
+ height: treeHeight,
3962
+ width: treeGrid.parentNode.offsetWidth,
3880
3963
  style: 'tree-grid',
3881
- type: treeGrid.parentNode.nodeType
3964
+ type: treeGrid.parentNode.nodeType,
3965
+ data: rowData,
3966
+ header: {},
3967
+ isGrid: false,
3968
+ isNormal: false,
3969
+ isHeader: false,
3970
+ isCorner: false,
3971
+ isRowHeader: false,
3972
+ isColumnHeader: false,
3973
+ selected: false,
3974
+ hovered: false
3882
3975
  });
3883
3976
  treeGrid.draw();
3884
3977
  } else if (treeGrid) {
@@ -4950,21 +5043,35 @@ __webpack_require__.r(__webpack_exports__);
4950
5043
  rowHeaderCellWidth = self.getRowHeaderCellWidth(),
4951
5044
  topGroupAreaHeight = self.getColumnGroupAreaHeight(),
4952
5045
  leftGroupAreaWidth = self.getRowGroupAreaWidth(),
4953
- ch = self.style.cellHeight;
4954
- // sets actual DOM canvas element
5046
+ ch = self.style.cellHeight,
5047
+ containerSpace = self.isChildGrid ? {} : self.measureContainerSpace();
5048
+ function isAutoSize(value) {
5049
+ return value === undefined || value === 'auto' || value === '';
5050
+ }
4955
5051
  function checkScrollBoxVisibility() {
4956
- var capped = self.autoSizeCapped || {};
4957
- self.scrollBox.horizontalBarVisible = (self.style.width !== 'auto' || capped.width) && dataWidth > self.scrollBox.width && self.style.overflowX !== 'hidden' || self.style.overflowX === 'scroll';
5052
+ // an auto-sized grid shows a scroll bar only when it could not grow to
5053
+ // its content (container, viewport or canvas limit); a child grid is
5054
+ // always bounded by the tree row or cell that holds it
5055
+ var constrained = self.autoSizeConstrained || {};
5056
+ self.scrollBox.horizontalBarVisible = (!isAutoSize(self.style.width) || constrained.width) && dataWidth > self.scrollBox.width && self.style.overflowX !== 'hidden' || self.style.overflowX === 'scroll';
4958
5057
  self.scrollBox.horizontalBoxVisible = dataWidth > self.scrollBox.width;
4959
- self.scrollBox.verticalBarVisible = (self.style.height !== 'auto' || capped.height) && dataHeight > self.scrollBox.height && self.style.overflowY !== 'hidden' || self.style.overflowY === 'scroll';
5058
+ self.scrollBox.verticalBarVisible = (!isAutoSize(self.style.height) || constrained.height) && dataHeight > self.scrollBox.height && self.style.overflowY !== 'hidden' || self.style.overflowY === 'scroll';
4960
5059
  self.scrollBox.verticalBoxVisible = dataHeight > self.scrollBox.height;
4961
5060
  }
4962
5061
  function setScrollBoxSize() {
4963
5062
  self.scrollBox.width = self.width - rowHeaderCellWidth - leftGroupAreaWidth;
4964
5063
  self.scrollBox.height = self.height - columnHeaderCellHeight - topGroupAreaHeight;
4965
5064
  }
5065
+ // sizes the DOM canvas element and reads back the size the grid got
4966
5066
  function setCanvasSize() {
4967
5067
  if (self.isChildGrid) {
5068
+ // a child grid fills the tree row or cell that holds it
5069
+ self.autoSizeConstrained = {
5070
+ width: true,
5071
+ height: true
5072
+ };
5073
+ self.width = self.parentNode.offsetWidth;
5074
+ self.height = self.parentNode.offsetHeight;
4968
5075
  return;
4969
5076
  }
4970
5077
  dims = {
@@ -4972,31 +5079,39 @@ __webpack_require__.r(__webpack_exports__);
4972
5079
  height: columnHeaderCellHeight + topGroupAreaHeight + dataHeight + cellBorder + 1,
4973
5080
  width: dataWidth + rowHeaderCellWidth + cellBorder + leftGroupAreaWidth
4974
5081
  };
4975
- // Browsers refuse to paint canvases beyond roughly 16k px per side, so
4976
- // an auto-sized grid with enough rows went blank past that point
4977
- // (#254). Cap the element and let it scroll for the remainder.
4978
5082
  var maxCanvasSize = self.attributes.maxCanvasSize;
4979
- self.autoSizeCapped = {
5083
+ self.autoSizeConstrained = {
4980
5084
  width: false,
4981
5085
  height: false
4982
5086
  };
4983
5087
  ['width', 'height'].forEach(function (dim) {
4984
- if (dims[dim] > maxCanvasSize) {
4985
- dims[dim] = maxCanvasSize;
4986
- self.autoSizeCapped[dim] = true;
4987
- }
4988
- });
4989
- ['width', 'height'].forEach(function (dim) {
5088
+ var styleIsAuto = isAutoSize(self.style[dim]),
5089
+ inlineIsAuto = isAutoSize(self.appliedInlineStyles[dim]),
5090
+ size;
4990
5091
  //TODO: support inherit
4991
- if (['auto', undefined].indexOf(self.style[dim]) !== -1 && ['auto', undefined].indexOf(self.appliedInlineStyles[dim]) !== -1) {
4992
- self.parentNodeStyle[dim] = dims[dim] + 'px';
4993
- } else if (['auto', undefined].indexOf(self.style[dim]) == -1 && ['auto', undefined].indexOf(self.appliedInlineStyles[dim]) == -1) {
4994
- self.parentNodeStyle[dim] = self.style[dim];
4995
- if (self.isComponent) {
4996
- self.canvas.style[dim] = self.style[dim];
5092
+ if (styleIsAuto && inlineIsAuto) {
5093
+ // No explicit size: fill the container when it has a size of its
5094
+ // own in this dimension, otherwise fit the content but never grow
5095
+ // past the viewport. Browsers also refuse to paint canvases beyond
5096
+ // roughly 16k px per side (#254), so cap the element there too and
5097
+ // let the grid scroll for the remainder.
5098
+ size = dims[dim];
5099
+ if (containerSpace[dim] !== undefined) {
5100
+ size = containerSpace[dim];
5101
+ } else {
5102
+ size = Math.min(size, dim === 'height' ? window.innerHeight : window.innerWidth);
4997
5103
  }
5104
+ size = Math.max(0, Math.min(size, maxCanvasSize));
5105
+ self.autoSizeConstrained[dim] = size < dims[dim];
5106
+ self.parentNodeStyle[dim] = size + 'px';
5107
+ } else if (!styleIsAuto && !inlineIsAuto) {
5108
+ self.parentNodeStyle[dim] = self.style[dim];
4998
5109
  }
4999
5110
  });
5111
+ self.width = self.canvas.offsetWidth;
5112
+ self.height = self.canvas.offsetHeight;
5113
+ self.canvasOffsetLeft = self.args.canvasOffsetLeft || 0;
5114
+ self.canvasOffsetTop = self.args.canvasOffsetTop || 0;
5000
5115
  }
5001
5116
  self.autoLastColumnWidth = undefined;
5002
5117
  dataWidth = self.refreshScrollCacheX();
@@ -5017,15 +5132,6 @@ __webpack_require__.r(__webpack_exports__);
5017
5132
  dataHeight += self.style.cellHeight;
5018
5133
  }
5019
5134
  setCanvasSize();
5020
- if (self.isChildGrid) {
5021
- self.width = self.parentNode.offsetWidth;
5022
- self.height = self.parentNode.offsetHeight;
5023
- } else if (self.height !== self.canvas.offsetHeight || self.width !== self.canvas.offsetWidth) {
5024
- self.height = self.canvas.offsetHeight;
5025
- self.width = self.canvas.offsetWidth;
5026
- self.canvasOffsetLeft = self.args.canvasOffsetLeft || 0;
5027
- self.canvasOffsetTop = self.args.canvasOffsetTop || 0;
5028
- }
5029
5135
  /// calculate scroll bar dimensions
5030
5136
  // non-controversial
5031
5137
  self.scrollBox.top = columnHeaderCellHeight + topGroupAreaHeight + columnHeaderCellBorder;
@@ -5037,18 +5143,12 @@ __webpack_require__.r(__webpack_exports__);
5037
5143
  // if the scroll box is visible, make room for it by expanding the size of the element
5038
5144
  // if the other dimension is set to auto
5039
5145
  if (self.scrollBox.horizontalBarVisible) {
5040
- if (self.style.height === 'auto' && !self.isChildGrid) {
5041
- self.height += sbw;
5042
- }
5043
5146
  dataHeight += sbw;
5044
5147
  setCanvasSize();
5045
5148
  setScrollBoxSize();
5046
5149
  checkScrollBoxVisibility();
5047
5150
  }
5048
5151
  if (self.scrollBox.verticalBarVisible) {
5049
- if (self.style.width === 'auto' && !self.isChildGrid) {
5050
- self.width += sbw;
5051
- }
5052
5152
  dataWidth += sbw;
5053
5153
  setCanvasSize();
5054
5154
  setScrollBoxSize();
@@ -6288,17 +6388,42 @@ __webpack_require__.r(__webpack_exports__);
6288
6388
  var l,
6289
6389
  t,
6290
6390
  ev = e,
6391
+ childEvent,
6291
6392
  deltaX = e.deltaX === undefined ? e.NativeEvent.deltaX : e.deltaX,
6292
6393
  deltaY = e.deltaY === undefined ? e.NativeEvent.deltaY : e.deltaY,
6293
6394
  deltaMode = e.deltaMode === undefined ? e.NativeEvent.deltaMode : e.deltaMode;
6294
6395
  if (e.NativeEvent) e = e.NativeEvent;
6396
+ if (self.disposed) {
6397
+ return;
6398
+ }
6399
+ // A child grid receives the wheel through its parent's `wheel` grid
6400
+ // event (`ev`), not from the DOM. Cancelling and marking that event tells
6401
+ // every ancestor that the grid under the pointer owns this wheel, so only
6402
+ // the hovered grid scrolls, even when it has nothing left to scroll.
6403
+ function claim() {
6404
+ if (ev !== e) {
6405
+ ev.claimedByChildGrid = true;
6406
+ ev.preventDefault();
6407
+ }
6408
+ }
6295
6409
  if (wheeling) {
6296
- ev.preventDefault(e);
6410
+ if (ev === e) {
6411
+ e.preventDefault();
6412
+ } else {
6413
+ claim();
6414
+ }
6297
6415
  return;
6298
6416
  }
6299
- if (self.dispatchEvent('wheel', {
6417
+ if (self.hasFocus) {
6418
+ claim();
6419
+ }
6420
+ childEvent = {
6300
6421
  NativeEvent: e
6301
- })) {
6422
+ };
6423
+ if (self.dispatchEvent('wheel', childEvent)) {
6424
+ if (childEvent.claimedByChildGrid) {
6425
+ claim();
6426
+ }
6302
6427
  return;
6303
6428
  }
6304
6429
  self.touchHaltAnimation = true;
@@ -7620,7 +7745,8 @@ __webpack_require__.r(__webpack_exports__);
7620
7745
  self.eventParent.removeEventListener('dblclick', self.dblclick, false);
7621
7746
  self.eventParent.removeEventListener('click', self.click, false);
7622
7747
  self.eventParent.removeEventListener('mousemove', self.mousemove);
7623
- self.eventParent.removeEventListener('wheel', self.scrollWheel, false);
7748
+ // child grids listen for the wheel on the parent grid's event, not the DOM
7749
+ self[self.isChildGrid ? 'parentGrid' : 'eventParent'].removeEventListener('wheel', self.scrollWheel, false);
7624
7750
  self.canvas.removeEventListener('contextmenu', self.contextmenuEvent, false);
7625
7751
  self.canvas.removeEventListener('copy', self.copy);
7626
7752
  self.controlInput.removeEventListener('copy', self.copy);