@one-grid-core/angular 0.4.0 → 0.5.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.
@@ -1516,6 +1516,11 @@ class OneGridApi {
1516
1516
  }
1517
1517
  const validRowIds = new Set();
1518
1518
  this.buildRowNodes(rowData, null, 0, validRowIds);
1519
+ // Prune BEFORE sorting and filtering: both read `_originalRowNodes`, and
1520
+ // with the prune last, a data swap while a sort was active resurrected
1521
+ // the removed rows into `_rowNodes` for one whole render — the old and
1522
+ // the new dataset on screen together until the next rebuild.
1523
+ this.pruneRemovedNodes(validRowIds);
1519
1524
  // Re-apply the active sort. `buildRowNodes` rebuilds the row list in the
1520
1525
  // order the data arrived, so without this a data update silently returned
1521
1526
  // the rows to that order while `sortModel` — and therefore the header's
@@ -1524,12 +1529,11 @@ class OneGridApi {
1524
1529
  if (this._sortModel.length > 0)
1525
1530
  this.applySorting();
1526
1531
  this.applyFilters();
1527
- this.pruneRemovedNodes(validRowIds);
1528
1532
  }
1529
1533
  /**
1530
1534
  * Recursive Build Row Nodes (Optimized)
1531
1535
  */
1532
- buildRowNodes(rowData, parent = null, level = 0, validRowIds = new Set(), expandedIds = new Set(this.expansionService.getExpandedIds()), collapsedIds = new Set(this.expansionService.getCollapsedIds()), selectedIds = new Set(this.selectionService.getSelectedIds())) {
1536
+ buildRowNodes(rowData, parent = null, level = 0, validRowIds = new Set(), expandedIds = new Set(this.expansionService.getExpandedIds()), collapsedIds = new Set(this.expansionService.getCollapsedIds()), selectedIds = new Set(this.selectionService.getSelectedIds()), visitedData = new Set()) {
1533
1537
  if (parent === null) {
1534
1538
  this._rowNodes = [];
1535
1539
  this._currentIndex = 0;
@@ -1539,6 +1543,16 @@ class OneGridApi {
1539
1543
  const nodes = [];
1540
1544
  for (const data of rowData) {
1541
1545
  const rowId = this.rowService.generateRowId(data, this._currentIndex);
1546
+ // A cycle in the children graph — a row reachable from its own subtree,
1547
+ // the classic ORM back-reference — used to recurse until the stack blew,
1548
+ // freezing the app; and with generated row ids it did so while minting
1549
+ // nodes the whole way down. The same guard also drops a duplicate row id
1550
+ // (or the same row object appearing twice): first occurrence wins.
1551
+ if (visitedData.has(data) || validRowIds.has(rowId)) {
1552
+ console.warn(`[one-grid] cyclic or duplicate row (id "${rowId}") — skipped, first occurrence wins. Tree data must be acyclic and row ids unique.`);
1553
+ continue;
1554
+ }
1555
+ visitedData.add(data);
1542
1556
  const children = data[this.treeDataChildrenField] || null;
1543
1557
  let node = this._originalRowNodes.get(rowId);
1544
1558
  validRowIds.add(rowId);
@@ -1575,7 +1589,7 @@ class OneGridApi {
1575
1589
  this._currentIndex++;
1576
1590
  this._rowNodes.push(node);
1577
1591
  nodes.push(node);
1578
- node.children = this.treeData && childrenExist ? this.buildRowNodes(children || [], node, level + 1, validRowIds, expandedIds, collapsedIds, selectedIds) : [];
1592
+ node.children = this.treeData && childrenExist ? this.buildRowNodes(children || [], node, level + 1, validRowIds, expandedIds, collapsedIds, selectedIds, visitedData) : [];
1579
1593
  }
1580
1594
  return nodes;
1581
1595
  }
@@ -1774,7 +1788,7 @@ class OneGridApi {
1774
1788
  this._rowNodes = [...this._originalRowNodes.values()];
1775
1789
  return;
1776
1790
  }
1777
- this._rowNodes = [...this._originalRowNodes.values()].sort((a, b) => {
1791
+ const comparator = (a, b) => {
1778
1792
  for (const sortItem of this._sortModel) {
1779
1793
  const { colId, sort } = sortItem;
1780
1794
  const valA = getFieldValue(a.data, colId);
@@ -1785,7 +1799,31 @@ class OneGridApi {
1785
1799
  }
1786
1800
  }
1787
1801
  return 0;
1788
- });
1802
+ };
1803
+ if (!this.treeData) {
1804
+ this._rowNodes = [...this._originalRowNodes.values()].sort(comparator);
1805
+ return;
1806
+ }
1807
+ // A tree sorts siblings within their parent, never across it — a flat
1808
+ // sort of the whole list interleaved parents with other parents' children
1809
+ // and scrambled the hierarchy. Sorting the sibling arrays and re-walking
1810
+ // depth-first keeps every parent immediately above its own children,
1811
+ // which `refreshVisibleNodes` also depends on for its visibility cache.
1812
+ // Group rows carry their aggregates in `data`, so sorting a grouped grid
1813
+ // on an aggregated column orders the groups by their aggregate.
1814
+ const flattened = [];
1815
+ const walk = (siblings) => {
1816
+ const sorted = [...siblings].sort(comparator);
1817
+ for (const node of sorted) {
1818
+ flattened.push(node);
1819
+ if (node.children?.length) {
1820
+ node.children = walk(node.children);
1821
+ }
1822
+ }
1823
+ return sorted;
1824
+ };
1825
+ walk([...this._originalRowNodes.values()].filter(node => !node.parent));
1826
+ this._rowNodes = flattened;
1789
1827
  }
1790
1828
  compareValues(valA, valB, sortDirection) {
1791
1829
  // `undefined` has to be treated as empty alongside `null`: getFieldValue