canvas-datagrid 0.26.0 → 0.27.1

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,48 @@ 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.1 - 2026-11-07
11
+
12
+ ### Fixed
13
+
14
+ - The mouse wheel over a tree grid or cell grid that has reached the end of
15
+ its scroll range now scrolls the parent grid, and the wheel over a top-level
16
+ grid at the end of its range scrolls the page, the way nested scrolling
17
+ elements chain in HTML. 0.27.0 kept the wheel on the hovered grid even when
18
+ it had nothing left to scroll. The page no longer scrolls along while a tree
19
+ grid or cell grid is scrolling.
20
+ - Documentation: cell grids are created only for columns whose schema `type`
21
+ is `canvas-datagrid`; an array value on its own does not create one. The
22
+ `data` property docs claimed it did, and the `cellGridAttributes` and
23
+ `header.type` docs did not mention the type. The `rendercellgrid` event
24
+ fires on every draw, not once per cell grid. The "Cell Grids" section now
25
+ has an example and there is a "Create a cell grid" tutorial.
26
+
27
+ ## 0.27.0 - 2026-11-07
28
+
29
+ ### Changed
30
+
31
+ - **Breaking:** the default `auto` width and height now size the grid to the
32
+ element it is placed in. When the parent has a size of its own in a
33
+ dimension (a fixed height, a flex or grid track, the width of a block), the
34
+ grid fills it, leaving room for the parent's other content, and follows the
35
+ parent when it is resized. When the parent has no size of its own (a
36
+ content-sized height, a shrink-to-fit width) the grid grows to fit its rows
37
+ and columns as before, but no further than the viewport, and scrolls for the
38
+ rest. Grids that previously relied on `auto` to grow to their content now
39
+ fill their container and show scroll bars when the data is larger; set
40
+ `style.width`/`style.height` explicitly to keep a fixed size.
41
+ - Tree grids and cell grids show scroll bars when their data overflows them,
42
+ whatever the parent grid's `width`/`height` style is.
43
+
44
+ ### Fixed
45
+
46
+ - The mouse wheel over a tree grid scrolled the parent grid as well, and with
47
+ nested tree grids every ancestor scrolled. Only the grid under the pointer
48
+ scrolls now, even when it has nothing left to scroll in that direction. The
49
+ pointer over a tree grid no longer counts as hovering the parent's row, and
50
+ collapsing a tree grid removes its wheel listener from the parent.
51
+
10
52
  ## 0.26.0 - 2026-09-19
11
53
 
12
54
  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,37 +6388,74 @@ __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
+ //BUG Issue 42: https://github.com/TonyGermaneri/canvas-datagrid/issues/42
6400
+ //https://stackoverflow.com/questions/20110224/what-is-the-height-of-a-line-in-a-wheel-event-deltamode-dom-delta-line
6401
+ if (deltaMode === 1) {
6402
+ // line mode = 17 pixels per line
6403
+ deltaY = deltaY * 17;
6404
+ }
6405
+ // A child grid receives the wheel through its parent's `wheel` grid
6406
+ // event (`ev`), not from the DOM. It leaves two marks on that event for
6407
+ // its ancestors: `hovered` says the pointer is over this grid or one of
6408
+ // its descendants, so the ancestor is not the hovered grid itself;
6409
+ // `claim` says this grid consumed the wheel, so no ancestor scrolls and
6410
+ // neither does the page. A hovered grid with nothing left to scroll in
6411
+ // the wheel's direction only marks itself hovered, so the wheel moves on
6412
+ // to the parent grid, and from the top-level grid to the page, the way
6413
+ // nested scrolling elements chain in HTML.
6414
+ function hovered() {
6415
+ if (ev !== e) {
6416
+ ev.hoveredChildGrid = true;
6417
+ }
6418
+ }
6419
+ function claim() {
6420
+ e.preventDefault();
6421
+ if (ev !== e) {
6422
+ ev.claimedByChildGrid = true;
6423
+ ev.preventDefault();
6424
+ }
6425
+ }
6426
+ function canScroll() {
6427
+ var box = self.scrollBox;
6428
+ return box.scrollTop < box.scrollHeight && deltaY > 0 || box.scrollLeft < box.scrollWidth && deltaX > 0 || box.scrollTop > 0 && deltaY < 0 || box.scrollLeft > 0 && deltaX < 0;
6429
+ }
6295
6430
  if (wheeling) {
6296
- ev.preventDefault(e);
6431
+ // still applying the previous wheel, this one belongs here too
6432
+ claim();
6297
6433
  return;
6298
6434
  }
6299
- if (self.dispatchEvent('wheel', {
6435
+ childEvent = {
6300
6436
  NativeEvent: e
6301
- })) {
6437
+ };
6438
+ if (self.dispatchEvent('wheel', childEvent)) {
6439
+ if (childEvent.claimedByChildGrid) {
6440
+ claim();
6441
+ }
6302
6442
  return;
6303
6443
  }
6304
6444
  self.touchHaltAnimation = true;
6445
+ if (!self.hasFocus && !childEvent.hoveredChildGrid) {
6446
+ return;
6447
+ }
6448
+ hovered();
6449
+ if (!canScroll()) {
6450
+ return;
6451
+ }
6452
+ claim();
6305
6453
  l = self.scrollBox.scrollLeft;
6306
6454
  t = self.scrollBox.scrollTop;
6307
- if (self.hasFocus) {
6308
- //BUG Issue 42: https://github.com/TonyGermaneri/canvas-datagrid/issues/42
6309
- //https://stackoverflow.com/questions/20110224/what-is-the-height-of-a-line-in-a-wheel-event-deltamode-dom-delta-line
6310
- if (deltaMode === 1) {
6311
- // line mode = 17 pixels per line
6312
- deltaY = deltaY * 17;
6313
- }
6314
- if (self.scrollBox.scrollTop < self.scrollBox.scrollHeight && deltaY > 0 || self.scrollBox.scrollLeft < self.scrollBox.scrollWidth && deltaX > 0 || self.scrollBox.scrollTop > 0 && deltaY < 0 || self.scrollBox.scrollLeft > 0 && deltaX < 0) {
6315
- ev.preventDefault(e);
6316
- }
6317
- wheeling = setTimeout(function () {
6318
- wheeling = undefined;
6319
- self.scrollBox.scrollTo(deltaX + l, deltaY + t);
6320
- }, 1);
6321
- }
6455
+ wheeling = setTimeout(function () {
6456
+ wheeling = undefined;
6457
+ self.scrollBox.scrollTo(deltaX + l, deltaY + t);
6458
+ }, 1);
6322
6459
  };
6323
6460
  self.pasteData = function (pasteValue, mimeType, startRowIndex, startColIndex, minRowsLength = 0, minColumnsLength = 0) {
6324
6461
  if (mimeType === 'text/html' && !(0,_util__WEBPACK_IMPORTED_MODULE_1__.isSupportedHtml)(pasteValue)) {
@@ -7620,7 +7757,8 @@ __webpack_require__.r(__webpack_exports__);
7620
7757
  self.eventParent.removeEventListener('dblclick', self.dblclick, false);
7621
7758
  self.eventParent.removeEventListener('click', self.click, false);
7622
7759
  self.eventParent.removeEventListener('mousemove', self.mousemove);
7623
- self.eventParent.removeEventListener('wheel', self.scrollWheel, false);
7760
+ // child grids listen for the wheel on the parent grid's event, not the DOM
7761
+ self[self.isChildGrid ? 'parentGrid' : 'eventParent'].removeEventListener('wheel', self.scrollWheel, false);
7624
7762
  self.canvas.removeEventListener('contextmenu', self.contextmenuEvent, false);
7625
7763
  self.canvas.removeEventListener('copy', self.copy);
7626
7764
  self.controlInput.removeEventListener('copy', self.copy);