@thkl/agrid 0.1.10 → 0.1.11

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, signal, computed, viewChild, effect, ChangeDetectionStrategy, Component, inject, ElementRef, DestroyRef, afterNextRender, linkedSignal, isWritableSignal, forwardRef, Directive, HostListener } from '@angular/core';
2
+ import { input, computed, output, signal, viewChild, effect, ChangeDetectionStrategy, Component, inject, ElementRef, DestroyRef, afterNextRender, linkedSignal, isWritableSignal, forwardRef, Directive, HostListener } from '@angular/core';
3
3
  import * as i1 from '@angular/cdk/scrolling';
4
4
  import { VIRTUAL_SCROLL_STRATEGY, ScrollingModule } from '@angular/cdk/scrolling';
5
5
  import { NgTemplateOutlet } from '@angular/common';
@@ -61,6 +61,20 @@ function coerceDateInputValue(value, originalValue) {
61
61
  }
62
62
  return value;
63
63
  }
64
+ /**
65
+ * Converts a numeric editor draft to its storage value.
66
+ * Both dot and comma are accepted as decimal separators; invalid drafts are preserved so
67
+ * column validation can report them instead of silently changing the user's input.
68
+ */
69
+ function coerceNumberInputValue(value) {
70
+ if (typeof value !== 'string')
71
+ return value;
72
+ const trimmed = value.trim();
73
+ if (trimmed === '')
74
+ return null;
75
+ const numeric = Number(trimmed.includes('.') ? trimmed : trimmed.replace(',', '.'));
76
+ return Number.isNaN(numeric) ? value : numeric;
77
+ }
64
78
  /** Resolve the display string for a raw cell value via ValueOption label, formatter, or coercion. */
65
79
  function getDisplayForField(col, raw, locale) {
66
80
  if (!col)
@@ -254,7 +268,17 @@ function applySortToIndices(rows, indices, sortEntries, colMap, locale) {
254
268
  /**
255
269
  * Compute aggregate values for the given rows across every column that has a static
256
270
  * (`ColDef.aggregate`) or control-configured aggregate. Returns a `field → value` map containing
257
- * only aggregated columns. Shared by the grid footer and per-group subtotals.
271
+ * only aggregated columns. Shared by the grid footer, per-group subtotals, and tree-node rollups.
272
+ *
273
+ * Numeric aggregates coerce values through `Number`; nonnumeric values are skipped. `count`
274
+ * counts non-null/non-empty values, while custom functions receive all raw values in index order.
275
+ * A control aggregate takes precedence over the static column definition.
276
+ *
277
+ * @param rows Complete datasource rows addressed by `indices`.
278
+ * @param indices Source indices included in this aggregate, in projection order.
279
+ * @param cols Columns inspected for static or runtime aggregate configuration.
280
+ * @param controlAggregates Runtime aggregate overrides keyed by field.
281
+ * @returns A field-to-result map containing only configured aggregate columns.
258
282
  */
259
283
  function computeAggregates(rows, indices, cols, controlAggregates) {
260
284
  const result = {};
@@ -354,8 +378,13 @@ function buildGroupedItems(rows, indices, groupField, colMap, sortEntries, expan
354
378
  * @param expandedIds Ids whose children should be rendered.
355
379
  * @param forceExpandedIds Ids forced open regardless of `expandedIds` — used to reveal the
356
380
  * ancestor path of filter matches. Optional.
381
+ * @param aggregateCols Columns whose configured aggregate should be attached to expandable nodes.
382
+ * Pass an empty array to disable tree rollups.
383
+ * @param controlAggregates Runtime aggregate overrides. These take precedence over each column's
384
+ * static aggregate function.
385
+ * @returns Visible tree rows. Expandable rows include `aggregates` when aggregation is enabled.
357
386
  */
358
- function buildTreeItems(rows, indices, accessors, expandedIds, forceExpandedIds) {
387
+ function buildTreeItems(rows, indices, accessors, expandedIds, forceExpandedIds, aggregateCols = [], controlAggregates = {}) {
359
388
  const { getId, getParentId } = accessors;
360
389
  const includedIds = new Set();
361
390
  for (const i of indices)
@@ -377,6 +406,29 @@ function buildTreeItems(rows, indices, accessors, expandedIds, forceExpandedIds)
377
406
  }
378
407
  const items = [];
379
408
  const visited = new Set();
409
+ const hasAggregates = aggregateCols.some(col => controlAggregates[col.field] ?? col.aggregate);
410
+ // Cache each subtree's leaf indices. Besides preventing repeated traversal for nested visible
411
+ // parents, leaf-only aggregation avoids double-counting parent rows that store their own subtotal.
412
+ // This walk uses the complete filtered hierarchy and is intentionally independent of expansion.
413
+ const leafMemo = new Map();
414
+ const collectLeaves = (index, visiting = new Set()) => {
415
+ const id = getId(rows[index]);
416
+ const cached = leafMemo.get(id);
417
+ if (cached)
418
+ return cached;
419
+ // A cyclic edge cannot produce a valid leaf. Excluding that edge keeps aggregation finite and
420
+ // matches the renderer's existing policy of omitting duplicate/cyclic visits.
421
+ if (visiting.has(id))
422
+ return [];
423
+ visiting.add(id);
424
+ const children = childrenByParent.get(id);
425
+ const leaves = children?.length
426
+ ? children.flatMap(childIndex => collectLeaves(childIndex, visiting))
427
+ : [index];
428
+ visiting.delete(id);
429
+ leafMemo.set(id, leaves);
430
+ return leaves;
431
+ };
380
432
  const visit = (index, level) => {
381
433
  const row = rows[index];
382
434
  const id = getId(row);
@@ -386,7 +438,10 @@ function buildTreeItems(rows, indices, accessors, expandedIds, forceExpandedIds)
386
438
  const children = childrenByParent.get(id);
387
439
  const expandable = !!children?.length;
388
440
  const expanded = expandable && (expandedIds.has(id) || !!forceExpandedIds?.has(id));
389
- items.push({ row, originalIndex: index, level, expandable, expanded });
441
+ const aggregates = expandable && hasAggregates
442
+ ? computeAggregates(rows, collectLeaves(index), aggregateCols, controlAggregates)
443
+ : undefined;
444
+ items.push({ row, originalIndex: index, level, expandable, expanded, aggregates });
390
445
  if (expanded && children) {
391
446
  for (const childIndex of children)
392
447
  visit(childIndex, level + 1);
@@ -396,10 +451,20 @@ function buildTreeItems(rows, indices, accessors, expandedIds, forceExpandedIds)
396
451
  visit(rootIndex, 0);
397
452
  return items;
398
453
  }
399
- /** Stable expansion id for a generated path prefix. */
454
+ /**
455
+ * Return the stable expansion key for a generated path prefix.
456
+ * String normalization makes numeric and string path segments share the same branch identity.
457
+ */
400
458
  function pathTreeNodeId(path) {
401
459
  return `__agrid_path__${JSON.stringify(path.map(String))}`;
402
460
  }
461
+ /**
462
+ * Derive a deterministic UUID-shaped identifier for a generated path branch.
463
+ *
464
+ * This is an identity helper, not a cryptographic hash. Four independently mixed 32-bit lanes
465
+ * provide stable output, after which the version and variant nibbles are normalized to a
466
+ * v5-shaped UUID layout. It does not implement RFC 4122 namespace/SHA-1 UUID generation.
467
+ */
403
468
  function pathTreeNodeUuid(path) {
404
469
  const source = JSON.stringify(path.map(String));
405
470
  let a = 0x9e3779b9;
@@ -421,8 +486,24 @@ function pathTreeNodeUuid(path) {
421
486
  hex[16] = ((parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
422
487
  return `${hex.slice(0, 8).join('')}-${hex.slice(8, 12).join('')}-${hex.slice(12, 16).join('')}-${hex.slice(16, 20).join('')}-${hex.slice(20, 32).join('')}`;
423
488
  }
424
- /** Builds display-only branch nodes and datasource-backed leaves from path segments. */
425
- function buildPathTreeItems(rows, indices, config, expandedIds, forceExpanded = false) {
489
+ /**
490
+ * Build a depth-first projection from datasource paths.
491
+ *
492
+ * Every non-final path segment becomes a display-only {@link PathTreeNodeItem}; the final segment
493
+ * remains a datasource-backed {@link TreeRowItem}. Shared prefixes are coalesced by their stable
494
+ * path id. When aggregate columns are supplied, each generated branch receives values computed
495
+ * over every datasource row sharing that prefix, regardless of expansion state.
496
+ *
497
+ * @param rows Complete datasource rows.
498
+ * @param indices Filtered and sorted datasource indices included in the tree.
499
+ * @param config Path extraction, labeling, and identity configuration.
500
+ * @param expandedIds Generated branch ids whose children should be emitted.
501
+ * @param forceExpanded Whether filtering should reveal every generated branch in the projection.
502
+ * @param aggregateCols Columns whose configured aggregates should be attached to branch nodes.
503
+ * @param controlAggregates Runtime aggregate overrides keyed by field.
504
+ * @returns Visible generated branches and datasource leaves in depth-first order.
505
+ */
506
+ function buildPathTreeItems(rows, indices, config, expandedIds, forceExpanded = false, aggregateCols = [], controlAggregates = {}) {
426
507
  const roots = new Map();
427
508
  for (const originalIndex of indices) {
428
509
  const rawPath = config.getPath(rows[originalIndex]).filter(segment => String(segment).length > 0);
@@ -452,9 +533,11 @@ function buildPathTreeItems(rows, indices, config, expandedIds, forceExpanded =
452
533
  level,
453
534
  children: new Map(),
454
535
  leaves: [],
536
+ descendantIndices: [],
455
537
  };
456
538
  branches.set(id, branch);
457
539
  }
540
+ branch.descendantIndices.push(originalIndex);
458
541
  if (level === path.length - 2) {
459
542
  branch.leaves.push({
460
543
  originalIndex,
@@ -474,13 +557,16 @@ function buildPathTreeItems(rows, indices, config, expandedIds, forceExpanded =
474
557
  level: -1,
475
558
  children: new Map(),
476
559
  leaves: [],
560
+ descendantIndices: [],
477
561
  };
478
562
  roots.set(id, branch);
479
563
  }
480
564
  branch.leaves.push({ originalIndex, label: formatSegment(0, true) });
565
+ branch.descendantIndices.push(originalIndex);
481
566
  }
482
567
  }
483
568
  const items = [];
569
+ const hasAggregates = aggregateCols.some(col => controlAggregates[col.field] ?? col.aggregate);
484
570
  const visit = (branch) => {
485
571
  const isHiddenRoot = branch.level < 0;
486
572
  const expanded = forceExpanded || expandedIds.has(branch.id);
@@ -492,6 +578,9 @@ function buildPathTreeItems(rows, indices, config, expandedIds, forceExpanded =
492
578
  level: branch.level,
493
579
  expandable: true,
494
580
  expanded,
581
+ aggregates: hasAggregates
582
+ ? computeAggregates(rows, branch.descendantIndices, aggregateCols, controlAggregates)
583
+ : undefined,
495
584
  });
496
585
  }
497
586
  if (!isHiddenRoot && !expanded)
@@ -556,6 +645,16 @@ class AgridCellComponent {
556
645
  displayValueOverride = input(null, ...(ngDevMode ? [{ debugName: "displayValueOverride" }] : /* istanbul ignore next */ []));
557
646
  /** Full row data — passed to `cellRenderer` when set. */
558
647
  row = input({}, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
648
+ /** Runtime formatting resolved from the column definition and current cell context. */
649
+ resolvedCellFormat = computed(() => {
650
+ const col = this.col();
651
+ return col.cellFormat?.({
652
+ row: this.row(),
653
+ value: this.value(),
654
+ column: col,
655
+ originalIndex: this.rowIndex(),
656
+ }) ?? {};
657
+ }, ...(ngDevMode ? [{ debugName: "resolvedCellFormat" }] : /* istanbul ignore next */ []));
559
658
  /** Locale used for built-in date formatting. */
560
659
  locale = input(undefined, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
561
660
  /** Whether this cell has the active selection outline. */
@@ -762,7 +861,7 @@ class AgridCellComponent {
762
861
  this.draftChange.emit(rawValue);
763
862
  }
764
863
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
765
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridCellComponent, isStandalone: true, selector: "agrid-cell", inputs: { col: { classPropertyName: "col", publicName: "col", isSignal: true, isRequired: true, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, displayValueOverride: { classPropertyName: "displayValueOverride", publicName: "displayValueOverride", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, showInfoIcon: { classPropertyName: "showInfoIcon", publicName: "showInfoIcon", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, treeCell: { classPropertyName: "treeCell", publicName: "treeCell", isSignal: true, isRequired: false, transformFunction: null }, treeLevel: { classPropertyName: "treeLevel", publicName: "treeLevel", isSignal: true, isRequired: false, transformFunction: null }, treeExpandable: { classPropertyName: "treeExpandable", publicName: "treeExpandable", isSignal: true, isRequired: false, transformFunction: null }, treeExpanded: { classPropertyName: "treeExpanded", publicName: "treeExpanded", isSignal: true, isRequired: false, transformFunction: null }, seedChar: { classPropertyName: "seedChar", publicName: "seedChar", isSignal: true, isRequired: false, transformFunction: null }, selectTextOnEdit: { classPropertyName: "selectTextOnEdit", publicName: "selectTextOnEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { treeToggle: "treeToggle", activate: "activate", startEdit: "startEdit", booleanToggle: "booleanToggle", infoClick: "infoClick", draftChange: "draftChange" }, host: { attributes: { "role": "gridcell", "tabindex": "-1" }, listeners: { "click": "activate.emit($event)", "dblclick": "startEdit.emit()" }, properties: { "class.selected": "selected()", "class.editing": "editing()", "class.ag-cell--tree": "treeCell()", "class.ag-cell--with-info": "showInfoIcon() && !editing()", "attr.aria-readonly": "!editable() ? \"true\" : null", "attr.title": "displayValue()" } }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["editInput"], descendants: true, isSignal: true }, { propertyName: "selectEl", first: true, predicate: ["editSelect"], descendants: true, isSignal: true }], ngImport: i0, template: `
864
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridCellComponent, isStandalone: true, selector: "agrid-cell", inputs: { col: { classPropertyName: "col", publicName: "col", isSignal: true, isRequired: true, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, displayValueOverride: { classPropertyName: "displayValueOverride", publicName: "displayValueOverride", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, showInfoIcon: { classPropertyName: "showInfoIcon", publicName: "showInfoIcon", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, treeCell: { classPropertyName: "treeCell", publicName: "treeCell", isSignal: true, isRequired: false, transformFunction: null }, treeLevel: { classPropertyName: "treeLevel", publicName: "treeLevel", isSignal: true, isRequired: false, transformFunction: null }, treeExpandable: { classPropertyName: "treeExpandable", publicName: "treeExpandable", isSignal: true, isRequired: false, transformFunction: null }, treeExpanded: { classPropertyName: "treeExpanded", publicName: "treeExpanded", isSignal: true, isRequired: false, transformFunction: null }, seedChar: { classPropertyName: "seedChar", publicName: "seedChar", isSignal: true, isRequired: false, transformFunction: null }, selectTextOnEdit: { classPropertyName: "selectTextOnEdit", publicName: "selectTextOnEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { treeToggle: "treeToggle", activate: "activate", startEdit: "startEdit", booleanToggle: "booleanToggle", infoClick: "infoClick", draftChange: "draftChange" }, host: { attributes: { "role": "gridcell", "tabindex": "-1" }, listeners: { "click": "activate.emit($event)", "dblclick": "startEdit.emit()" }, properties: { "class.selected": "selected()", "class.editing": "editing()", "class.ag-cell--tree": "treeCell()", "class.ag-cell--with-info": "showInfoIcon() && !editing()", "attr.aria-readonly": "!editable() ? \"true\" : null", "attr.title": "displayValue()", "style.background-color": "resolvedCellFormat().backgroundColor ?? null", "style.border-color": "resolvedCellFormat().borderColor ?? null", "style.color": "resolvedCellFormat().color ?? null", "style.font": "resolvedCellFormat().font ?? null", "style.font-family": "resolvedCellFormat().fontFamily ?? null", "style.font-size": "resolvedCellFormat().fontSize ?? null", "style.font-style": "resolvedCellFormat().fontStyle ?? null", "style.font-weight": "resolvedCellFormat().fontWeight ?? null", "style.text-decoration": "resolvedCellFormat().textDecoration ?? null", "style.text-align": "resolvedCellFormat().textAlign ?? null" } }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["editInput"], descendants: true, isSignal: true }, { propertyName: "selectEl", first: true, predicate: ["editSelect"], descendants: true, isSignal: true }], ngImport: i0, template: `
766
865
  @if (booleanCell()) {
767
866
  <input
768
867
  type="checkbox"
@@ -789,7 +888,8 @@ class AgridCellComponent {
789
888
  #editInput
790
889
  class="ag-cell-input"
791
890
  [class.ag-cell-input--invalid]="!!error()"
792
- [type]="col().type === 'date' ? 'date' : col().type === 'number' ? 'number' : 'text'"
891
+ [type]="col().type === 'date' ? 'date' : 'text'"
892
+ [attr.inputmode]="col().type === 'number' ? 'decimal' : null"
793
893
  [value]="editorValue()"
794
894
  (input)="onInput($event)"
795
895
  />
@@ -842,6 +942,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
842
942
  '[class.ag-cell--with-info]': 'showInfoIcon() && !editing()',
843
943
  '[attr.aria-readonly]': '!editable() ? "true" : null',
844
944
  '[attr.title]': 'displayValue()',
945
+ '[style.background-color]': 'resolvedCellFormat().backgroundColor ?? null',
946
+ '[style.border-color]': 'resolvedCellFormat().borderColor ?? null',
947
+ '[style.color]': 'resolvedCellFormat().color ?? null',
948
+ '[style.font]': 'resolvedCellFormat().font ?? null',
949
+ '[style.font-family]': 'resolvedCellFormat().fontFamily ?? null',
950
+ '[style.font-size]': 'resolvedCellFormat().fontSize ?? null',
951
+ '[style.font-style]': 'resolvedCellFormat().fontStyle ?? null',
952
+ '[style.font-weight]': 'resolvedCellFormat().fontWeight ?? null',
953
+ '[style.text-decoration]': 'resolvedCellFormat().textDecoration ?? null',
954
+ '[style.text-align]': 'resolvedCellFormat().textAlign ?? null',
845
955
  '(click)': 'activate.emit($event)',
846
956
  '(dblclick)': 'startEdit.emit()',
847
957
  tabindex: '-1',
@@ -872,7 +982,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
872
982
  #editInput
873
983
  class="ag-cell-input"
874
984
  [class.ag-cell-input--invalid]="!!error()"
875
- [type]="col().type === 'date' ? 'date' : col().type === 'number' ? 'number' : 'text'"
985
+ [type]="col().type === 'date' ? 'date' : 'text'"
986
+ [attr.inputmode]="col().type === 'number' ? 'decimal' : null"
876
987
  [value]="editorValue()"
877
988
  (input)="onInput($event)"
878
989
  />
@@ -1372,6 +1483,7 @@ const AGRID_LOCALE_TEXT = {
1372
1483
  aggregate: 'Aggregate',
1373
1484
  aggregateAvg: 'Average',
1374
1485
  aggregateCount: 'Count',
1486
+ aggregateCustom: 'Custom function',
1375
1487
  aggregateMax: 'Maximum',
1376
1488
  aggregateMin: 'Minimum',
1377
1489
  aggregateSum: 'Sum',
@@ -1425,6 +1537,11 @@ const AGRID_LOCALE_TEXT = {
1425
1537
  next: 'Next',
1426
1538
  noRows: 'No rows to display',
1427
1539
  pagination: 'Pagination',
1540
+ pivot: 'Pivot',
1541
+ pivotDescription: 'Configure the fields used to build the pivot result.',
1542
+ pivotRows: 'Rows',
1543
+ pivotColumns: 'Columns',
1544
+ pivotValues: 'Values',
1428
1545
  pinColumn: 'Pin left',
1429
1546
  pinColumnRight: 'Pin right',
1430
1547
  unpinColumnRight: 'Unpin right',
@@ -1450,6 +1567,7 @@ const AGRID_LOCALE_TEXT = {
1450
1567
  aggregate: 'Aggregat',
1451
1568
  aggregateAvg: 'Durchschnitt',
1452
1569
  aggregateCount: 'Anzahl',
1570
+ aggregateCustom: 'Benutzerdefinierte Funktion',
1453
1571
  aggregateMax: 'Maximum',
1454
1572
  aggregateMin: 'Minimum',
1455
1573
  aggregateSum: 'Summe',
@@ -1503,6 +1621,11 @@ const AGRID_LOCALE_TEXT = {
1503
1621
  next: 'Weiter',
1504
1622
  noRows: 'Keine Zeilen vorhanden',
1505
1623
  pagination: 'Seitennavigation',
1624
+ pivot: 'Pivot',
1625
+ pivotDescription: 'Felder für das Pivot-Ergebnis konfigurieren.',
1626
+ pivotRows: 'Zeilen',
1627
+ pivotColumns: 'Spalten',
1628
+ pivotValues: 'Werte',
1506
1629
  pinColumn: 'Links fixieren',
1507
1630
  pinColumnRight: 'Rechts fixieren',
1508
1631
  unpinColumnRight: 'Rechts lösen',
@@ -1769,6 +1892,10 @@ class AgridColumnMenuController {
1769
1892
  const menu = this.menu();
1770
1893
  if (!menu)
1771
1894
  return new Set();
1895
+ const col = this.getColDef(menu.field);
1896
+ if (this.opts.serverSideFiltering() && col?.values?.length) {
1897
+ return new Set(this.items().map(item => item.rawStr));
1898
+ }
1772
1899
  const rows = this.opts.dataSource().rows();
1773
1900
  const control = this.opts.control();
1774
1901
  let indices = rows.map((_, index) => index);
@@ -2517,6 +2644,149 @@ class AgridColumnStateService {
2517
2644
  }
2518
2645
  }
2519
2646
 
2647
+ /**
2648
+ * Signal-based data container shared between the grid and the host component.
2649
+ *
2650
+ * Both the grid and the host component hold a reference to the same `AgridDataSource`
2651
+ * instance. Mutations made by either side (e.g. the grid calling `patchRow` when a cell
2652
+ * is committed, or the host calling `setData` to load fresh data) are immediately reflected
2653
+ * in the grid because {@link rows} is a readonly Angular signal.
2654
+ *
2655
+ * @example
2656
+ * ```ts
2657
+ * readonly ds = new AgridDataSource(generateRows(1000));
2658
+ *
2659
+ * // Later — update one row from outside the grid:
2660
+ * this.ds.patchRow(5, { salary: 99000 });
2661
+ * ```
2662
+ */
2663
+ class AgridDataSource {
2664
+ _linkedRows = signal(null, ...(ngDevMode ? [{ debugName: "_linkedRows" }] : /* istanbul ignore next */ []));
2665
+ _rows = linkedSignal(() => this._linkedRows()?.() ?? [], ...(ngDevMode ? [{ debugName: "_rows" }] : /* istanbul ignore next */ []));
2666
+ _writableLinkedRows = null;
2667
+ _rowAdded = signal(null, ...(ngDevMode ? [{ debugName: "_rowAdded" }] : /* istanbul ignore next */ []));
2668
+ _changeSequence = 0;
2669
+ /**
2670
+ * @param initialData Rows to seed the data source with.
2671
+ * The array is shallow-copied so external mutations do not affect the source.
2672
+ */
2673
+ constructor(initialData = []) {
2674
+ this._rows.set([...initialData]);
2675
+ }
2676
+ /**
2677
+ * Readonly signal of the current row array.
2678
+ * Read it inside Angular templates or `computed()` to react to changes automatically.
2679
+ */
2680
+ rows = this._rows.asReadonly();
2681
+ /** Latest row insertion, used by attached grids to reveal the inserted row. */
2682
+ rowAdded = this._rowAdded.asReadonly();
2683
+ /**
2684
+ * Link an external row signal to this data source.
2685
+ *
2686
+ * Whenever `source` changes, its array becomes the current datasource value without an
2687
+ * intermediate effect or array copy. If `source` is writable, datasource mutations are written
2688
+ * back to it automatically. Mutations remain local when linking a readonly signal.
2689
+ */
2690
+ linkSignal(source) {
2691
+ this._writableLinkedRows = isWritableSignal(source)
2692
+ ? source
2693
+ : null;
2694
+ this._linkedRows.set(source);
2695
+ }
2696
+ /**
2697
+ * Replace the entire row array.
2698
+ * Triggers a full grid re-render via the signal.
2699
+ */
2700
+ setData(rows) {
2701
+ this.setRows([...rows]);
2702
+ }
2703
+ /**
2704
+ * Overwrite the row at `index` with a new row object.
2705
+ * Use {@link patchRow} when you only want to change specific fields.
2706
+ */
2707
+ updateRow(index, row) {
2708
+ this.updateRows(rows => {
2709
+ const next = [...rows];
2710
+ next[index] = row;
2711
+ return next;
2712
+ });
2713
+ }
2714
+ /**
2715
+ * Merge `patch` into the existing row at `index`, leaving other fields untouched.
2716
+ * The grid calls this internally when a cell edit is committed.
2717
+ */
2718
+ patchRow(index, patch) {
2719
+ this.updateRows(rows => {
2720
+ const next = [...rows];
2721
+ next[index] = { ...next[index], ...patch };
2722
+ return next;
2723
+ });
2724
+ }
2725
+ /**
2726
+ * Insert a row into the data source and return the index at which it was inserted.
2727
+ *
2728
+ * @param row The row object to insert.
2729
+ * @param atIndex Optional insertion index. Defaults to the end of the array.
2730
+ * @returns The index the row was inserted at.
2731
+ */
2732
+ addRow(row, atIndex) {
2733
+ let insertedAt;
2734
+ this.updateRows(rows => {
2735
+ if (atIndex === undefined) {
2736
+ insertedAt = rows.length;
2737
+ return [...rows, row];
2738
+ }
2739
+ insertedAt = atIndex;
2740
+ const next = [...rows];
2741
+ next.splice(atIndex, 0, row);
2742
+ return next;
2743
+ });
2744
+ this._rowAdded.set({ index: insertedAt, sequence: ++this._changeSequence });
2745
+ return insertedAt;
2746
+ }
2747
+ /**
2748
+ * Remove the row at `index`.
2749
+ * The grid adjusts `selectedCell` and `editingCell` internally when a deletion occurs
2750
+ * via the control column context menu.
2751
+ */
2752
+ removeRow(index) {
2753
+ this.updateRows(rows => rows.filter((_, i) => i !== index));
2754
+ }
2755
+ /**
2756
+ * Move the row at `from` to position `to` (insert-before semantics).
2757
+ * Designed to be called directly from a `(rowReorder)` handler:
2758
+ * ```ts
2759
+ * onReorder(e: RowReorderEvent) { this.ds.moveRow(e.oldIndex, e.newIndex); }
2760
+ * ```
2761
+ */
2762
+ moveRow(from, to) {
2763
+ if (from === to)
2764
+ return;
2765
+ this.updateRows(rows => {
2766
+ const arr = [...rows];
2767
+ const [item] = arr.splice(from, 1);
2768
+ arr.splice(to > from ? to - 1 : to, 0, item);
2769
+ return arr;
2770
+ });
2771
+ }
2772
+ /** Return the current row at `index` (non-reactive snapshot). */
2773
+ getRow(index) {
2774
+ return this._rows()[index];
2775
+ }
2776
+ /** Current number of rows (non-reactive snapshot). */
2777
+ get length() {
2778
+ return this._rows().length;
2779
+ }
2780
+ updateRows(update) {
2781
+ this.setRows(update(this._rows()));
2782
+ }
2783
+ /** Replace the backing row array without copying. Intended for specialized datasource models. */
2784
+ setRows(rows) {
2785
+ this._writableLinkedRows?.set(rows);
2786
+ this._rows.set(rows);
2787
+ }
2788
+ }
2789
+
2520
2790
  /**
2521
2791
  * Handles both row-reorder drag and drag-select. The two share `_getHoveredRow`
2522
2792
  * and the same pointer-event lifecycle pattern, so they live together.
@@ -2764,7 +3034,9 @@ class AgridEditController {
2764
3034
  return true;
2765
3035
  }
2766
3036
  const oldValue = row[col.field];
2767
- const newValue = this.currentDraft();
3037
+ const newValue = col.type === 'number'
3038
+ ? coerceNumberInputValue(this.currentDraft())
3039
+ : this.currentDraft();
2768
3040
  if (oldValue !== newValue) {
2769
3041
  const message = col.validate?.(newValue, row) ?? null;
2770
3042
  if (message) {
@@ -3221,6 +3493,22 @@ class AgridNavigationController {
3221
3493
  event.preventDefault();
3222
3494
  this.moveSelection(0, 1, event.shiftKey);
3223
3495
  break;
3496
+ case 'PageUp':
3497
+ event.preventDefault();
3498
+ this.moveSelectionByPage(-1, event.shiftKey);
3499
+ break;
3500
+ case 'PageDown':
3501
+ event.preventDefault();
3502
+ this.moveSelectionByPage(1, event.shiftKey);
3503
+ break;
3504
+ case 'Home':
3505
+ event.preventDefault();
3506
+ this.moveSelectionToBoundary('start', event.ctrlKey || event.metaKey, event.shiftKey);
3507
+ break;
3508
+ case 'End':
3509
+ event.preventDefault();
3510
+ this.moveSelectionToBoundary('end', event.ctrlKey || event.metaKey, event.shiftKey);
3511
+ break;
3224
3512
  case 'Tab':
3225
3513
  event.preventDefault();
3226
3514
  this.moveSelection(0, event.shiftKey ? -1 : 1);
@@ -3411,6 +3699,59 @@ class AgridNavigationController {
3411
3699
  this.scrollToKeepVisible(newDi, newCi);
3412
3700
  this.opts.focusGrid();
3413
3701
  }
3702
+ /** Move by the number of complete data rows currently visible in the viewport. */
3703
+ moveSelectionByPage(direction, extendRange) {
3704
+ const items = this.opts.filteredItems();
3705
+ const dataRows = items
3706
+ .map((item, displayIndex) => ({ item, displayIndex }))
3707
+ .filter((entry) => isDataRowItem(entry.item));
3708
+ if (dataRows.length === 0)
3709
+ return;
3710
+ const selected = this.opts.selectedCell();
3711
+ const current = selected
3712
+ ? dataRows.findIndex(entry => entry.item.originalIndex === selected.rowIndex)
3713
+ : -1;
3714
+ const rowsPerPage = Math.max(1, Math.floor(this.opts.viewport().getViewportSize() / this.opts.rowHeight()));
3715
+ const target = current < 0
3716
+ ? direction > 0 ? 0 : dataRows.length - 1
3717
+ : Math.max(0, Math.min(dataRows.length - 1, current + direction * rowsPerPage));
3718
+ this.selectProjectedRow(dataRows[target].item.originalIndex, dataRows[target].displayIndex, selected?.colIndex ?? 0, extendRange);
3719
+ }
3720
+ /**
3721
+ * Home/End move horizontally within the selected row; Ctrl/Cmd moves to the corresponding
3722
+ * corner of the projected grid, matching common spreadsheet and ARIA-grid conventions.
3723
+ */
3724
+ moveSelectionToBoundary(edge, wholeGrid, extendRange) {
3725
+ const items = this.opts.filteredItems();
3726
+ const cols = this.opts.visibleColDefs();
3727
+ if (items.length === 0 || cols.length === 0)
3728
+ return;
3729
+ const selected = this.opts.selectedCell();
3730
+ let displayIndex = selected ? this.selectedDisplayIndex() : -1;
3731
+ if (wholeGrid || displayIndex < 0 || !isDataRowItem(items[displayIndex])) {
3732
+ const step = edge === 'start' ? 1 : -1;
3733
+ displayIndex = edge === 'start' ? 0 : items.length - 1;
3734
+ while (displayIndex >= 0 && displayIndex < items.length && !isDataRowItem(items[displayIndex])) {
3735
+ displayIndex += step;
3736
+ }
3737
+ }
3738
+ const item = items[displayIndex];
3739
+ if (!isDataRowItem(item))
3740
+ return;
3741
+ this.selectProjectedRow(item.originalIndex, displayIndex, edge === 'start' ? 0 : cols.length - 1, extendRange);
3742
+ }
3743
+ /** Apply a projected keyboard destination and keep both axes visible. */
3744
+ selectProjectedRow(originalIndex, displayIndex, colIndex, extendRange) {
3745
+ if (extendRange && this.opts.selectedCell()) {
3746
+ this.opts.extendRangeTo(originalIndex, colIndex);
3747
+ }
3748
+ else {
3749
+ this.opts.selectedRange.set(null);
3750
+ this.opts.selectedCell.set({ rowIndex: originalIndex, colIndex });
3751
+ }
3752
+ this.scrollToKeepVisible(displayIndex, colIndex);
3753
+ this.opts.focusGrid();
3754
+ }
3414
3755
  selectedDisplayIndex() {
3415
3756
  const sel = this.opts.selectedCell();
3416
3757
  if (!sel)
@@ -3501,6 +3842,18 @@ class AgridPresentationService {
3501
3842
  }
3502
3843
  }
3503
3844
 
3845
+ class AgridMenuBarIcon {
3846
+ class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : /* istanbul ignore next */ []));
3847
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridMenuBarIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
3848
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: AgridMenuBarIcon, isStandalone: true, selector: "agrid-menu-icon", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "class() || \"\"" } }, ngImport: i0, template: `<span aria-hidden="true"><ng-content></ng-content></span>`, isInline: true, styles: [":host{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;min-width:14px;line-height:1}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3849
+ }
3850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridMenuBarIcon, decorators: [{
3851
+ type: Component,
3852
+ args: [{ selector: 'agrid-menu-icon', changeDetection: ChangeDetectionStrategy.OnPush, imports: [], host: {
3853
+ '[class]': 'class() || ""'
3854
+ }, template: `<span aria-hidden="true"><ng-content></ng-content></span>`, styles: [":host{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;min-width:14px;line-height:1}\n"] }]
3855
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3856
+
3504
3857
  /** Internal renderer for provider-configured grid commands and dropdown items. */
3505
3858
  class AgridMenuBarComponent {
3506
3859
  items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
@@ -3579,13 +3932,14 @@ class AgridMenuBarComponent {
3579
3932
  items[next].focus();
3580
3933
  }
3581
3934
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridMenuBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3582
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridMenuBarComponent, isStandalone: true, selector: "agrid-menu-bar", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, openItemId: { classPropertyName: "openItemId", publicName: "openItemId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", openItemIdChange: "openItemIdChange" }, ngImport: i0, template: "<div class=\"ag-menu-bar\" role=\"toolbar\" [attr.aria-label]=\"label()\">\n @for (item of items(); track item.id) {\n @let children = visibleChildren(item);\n <div class=\"ag-menu-bar-group\" [attr.data-menu-id]=\"item.id\">\n <button type=\"button\" class=\"ag-menu-bar-button\"\n [class.ag-menu-bar-button--active]=\"isActive(item)\"\n [class.ag-menu-bar-button--split]=\"children.length > 0\"\n [disabled]=\"isDisabled(item)\"\n [attr.aria-pressed]=\"item.active !== undefined ? isActive(item) : null\"\n (click)=\"runAction($event, item)\">\n @if (item.icon) {\n <span class=\"ag-menu-bar-icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span class=\"ag-menu-bar-label\">{{ item.label }}</span>\n </button>\n @if (children.length > 0) {\n <button type=\"button\" class=\"ag-menu-bar-trigger\"\n [class.ag-menu-bar-trigger--open]=\"openItemId() === item.id\"\n [attr.aria-label]=\"item.label + ': ' + label()\"\n [attr.aria-expanded]=\"openItemId() === item.id\" aria-haspopup=\"menu\"\n [disabled]=\"isDisabled(item)\"\n (click)=\"toggleMenu($event, item)\"\n (keydown)=\"onTriggerKeydown($event, item)\">&#9662;</button>\n @if (openItemId() === item.id) {\n <div class=\"ag-menu-bar-dropdown\" role=\"menu\" [attr.aria-label]=\"item.label\"\n (click)=\"$event.stopPropagation()\" (keydown)=\"onMenuKeydown($event)\">\n @for (child of children; track child.id) {\n <button type=\"button\" class=\"ag-menu-bar-dropdown-item\" role=\"menuitem\"\n [class.ag-menu-bar-dropdown-item--active]=\"isActive(child)\"\n [disabled]=\"isDisabled(child)\"\n [attr.aria-current]=\"isActive(child) ? 'true' : null\"\n (click)=\"runAction($event, child)\">\n <span class=\"ag-menu-bar-dropdown-check\" aria-hidden=\"true\">{{ isActive(child) ? '&#10003;' : '' }}</span>\n @if (child.icon) {\n <span class=\"ag-menu-bar-icon\" aria-hidden=\"true\">{{ child.icon }}</span>\n }\n <span class=\"ag-menu-bar-label\">{{ child.label }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;min-width:0}.ag-menu-bar{display:flex;align-items:center;flex-wrap:wrap;gap:4px;min-width:0}.ag-menu-bar-group{position:relative;display:inline-flex;align-items:stretch}.ag-menu-bar-button,.ag-menu-bar-trigger{display:inline-flex;align-items:center;justify-content:center;height:30px;border:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);color:var(--agrid-color-text);cursor:pointer;font:inherit;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-button{gap:6px;min-width:0;padding:0 10px;border-radius:4px;font-weight:500;white-space:nowrap}.ag-menu-bar-button--split{border-radius:4px 0 0 4px}.ag-menu-bar-trigger{width:28px;padding:0;margin-left:-1px;border-radius:0 4px 4px 0;color:var(--agrid-color-text-muted);font-size:10px}.ag-menu-bar-button--active,.ag-menu-bar-trigger--open{position:relative;z-index:1;border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-button:focus-visible,.ag-menu-bar-trigger:focus-visible,.ag-menu-bar-dropdown-item:focus-visible{position:relative;z-index:2;outline:2px solid var(--agrid-color-accent);outline-offset:1px}.ag-menu-bar-button:active:not(:disabled),.ag-menu-bar-trigger:active:not(:disabled),.ag-menu-bar-dropdown-item:active:not(:disabled){transform:scale(.97)}.ag-menu-bar-button:disabled,.ag-menu-bar-trigger:disabled,.ag-menu-bar-dropdown-item:disabled{opacity:.48;cursor:not-allowed}.ag-menu-bar-icon{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;min-width:14px;line-height:1}.ag-menu-bar-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-menu-bar-dropdown{position:absolute;z-index:12;top:calc(100% + 4px);left:0;display:flex;flex-direction:column;min-width:184px;max-width:min(280px,calc(100vw - 24px));padding:4px;border:1px solid var(--agrid-color-border);border-radius:6px;background:var(--agrid-color-bg);box-shadow:0 10px 28px var(--agrid-color-shadow);transform-origin:top left}.ag-menu-bar-group:last-child .ag-menu-bar-dropdown{right:0;left:auto;transform-origin:top right}.ag-menu-bar-dropdown-item{display:flex;align-items:center;gap:7px;width:100%;min-height:30px;padding:4px 8px;border:0;border-radius:4px;background:transparent;color:var(--agrid-color-text);cursor:pointer;font:inherit;text-align:left;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-dropdown-item--active{background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-dropdown-check{flex:0 0 12px;color:var(--agrid-color-accent);font-size:11px;font-weight:700;text-align:center}@media(hover:hover)and (pointer:fine){.ag-menu-bar-button:hover:not(:disabled),.ag-menu-bar-trigger:hover:not(:disabled),.ag-menu-bar-dropdown-item:hover:not(:disabled){background:var(--agrid-color-bg-muted)}.ag-menu-bar-button--active:hover:not(:disabled),.ag-menu-bar-trigger--open:hover:not(:disabled),.ag-menu-bar-dropdown-item--active:hover:not(:disabled){background:var(--agrid-color-accent-subtle)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3935
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridMenuBarComponent, isStandalone: true, selector: "agrid-menu-bar", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, openItemId: { classPropertyName: "openItemId", publicName: "openItemId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", openItemIdChange: "openItemIdChange" }, ngImport: i0, template: "<div class=\"ag-menu-bar\" role=\"toolbar\" [attr.aria-label]=\"label()\">\n @for (item of items(); track item.id) {\n @let children = visibleChildren(item);\n <div class=\"ag-menu-bar-group\" [attr.data-menu-id]=\"item.id\">\n <button type=\"button\" class=\"ag-menu-bar-button\"\n [class.ag-menu-bar-button--active]=\"isActive(item)\"\n [class.ag-menu-bar-button--split]=\"children.length > 0\"\n [disabled]=\"isDisabled(item)\"\n [attr.aria-pressed]=\"item.active !== undefined ? isActive(item) : null\"\n (click)=\"runAction($event, item)\">\n @if (item.icon) {\n <agrid-menu-icon [class]=\"item.class\">{{item.icon}}</agrid-menu-icon>\n }\n <span class=\"ag-menu-bar-label\">{{ item.label }}</span>\n </button>\n @if (children.length > 0) {\n <button type=\"button\" class=\"ag-menu-bar-trigger\"\n [class.ag-menu-bar-trigger--open]=\"openItemId() === item.id\"\n [attr.aria-label]=\"item.label + ': ' + label()\"\n [attr.aria-expanded]=\"openItemId() === item.id\" aria-haspopup=\"menu\"\n [disabled]=\"isDisabled(item)\"\n (click)=\"toggleMenu($event, item)\"\n (keydown)=\"onTriggerKeydown($event, item)\">&#9662;</button>\n @if (openItemId() === item.id) {\n <div class=\"ag-menu-bar-dropdown\" role=\"menu\" [attr.aria-label]=\"item.label\"\n (click)=\"$event.stopPropagation()\" (keydown)=\"onMenuKeydown($event)\">\n @for (child of children; track child.id) {\n <button type=\"button\" class=\"ag-menu-bar-dropdown-item\" role=\"menuitem\"\n [class.ag-menu-bar-dropdown-item--active]=\"isActive(child)\"\n [disabled]=\"isDisabled(child)\"\n [attr.aria-current]=\"isActive(child) ? 'true' : null\"\n (click)=\"runAction($event, child)\">\n <span class=\"ag-menu-bar-dropdown-check\" aria-hidden=\"true\">{{ isActive(child) ? '&#10003;' : '' }}</span>\n @if (child.icon) {\n <agrid-menu-icon [class]=\"item.class\">{{child.icon}}</agrid-menu-icon>\n }\n <span class=\"ag-menu-bar-label\">{{ child.label }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;min-width:0}.ag-menu-bar{display:flex;align-items:center;flex-wrap:wrap;gap:4px;min-width:0}.ag-menu-bar-group{position:relative;display:inline-flex;align-items:stretch}.ag-menu-bar-button,.ag-menu-bar-trigger{display:inline-flex;align-items:center;justify-content:center;height:30px;border:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);color:var(--agrid-color-text);cursor:pointer;font:inherit;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-button{gap:6px;min-width:0;padding:0 10px;border-radius:4px;font-weight:500;white-space:nowrap}.ag-menu-bar-button--split{border-radius:4px 0 0 4px}.ag-menu-bar-trigger{width:28px;padding:0;margin-left:-1px;border-radius:0 4px 4px 0;color:var(--agrid-color-text-muted);font-size:10px}.ag-menu-bar-button--active,.ag-menu-bar-trigger--open{position:relative;z-index:1;border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-button:focus-visible,.ag-menu-bar-trigger:focus-visible,.ag-menu-bar-dropdown-item:focus-visible{position:relative;z-index:2;outline:2px solid var(--agrid-color-accent);outline-offset:1px}.ag-menu-bar-button:active:not(:disabled),.ag-menu-bar-trigger:active:not(:disabled),.ag-menu-bar-dropdown-item:active:not(:disabled){transform:scale(.97)}.ag-menu-bar-button:disabled,.ag-menu-bar-trigger:disabled,.ag-menu-bar-dropdown-item:disabled{opacity:.48;cursor:not-allowed}.ag-menu-bar-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-menu-bar-dropdown{position:absolute;z-index:12;top:calc(100% + 4px);left:0;display:flex;flex-direction:column;min-width:184px;max-width:min(280px,calc(100vw - 24px));padding:4px;border:1px solid var(--agrid-color-border);border-radius:6px;background:var(--agrid-color-bg);box-shadow:0 10px 28px var(--agrid-color-shadow);transform-origin:top left}.ag-menu-bar-group:last-child .ag-menu-bar-dropdown{right:0;left:auto;transform-origin:top right}.ag-menu-bar-dropdown-item{display:flex;align-items:center;gap:7px;width:100%;min-height:30px;padding:4px 8px;border:0;border-radius:4px;background:transparent;color:var(--agrid-color-text);cursor:pointer;font:inherit;text-align:left;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-dropdown-item--active{background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-dropdown-check{flex:0 0 12px;color:var(--agrid-color-accent);font-size:11px;font-weight:700;text-align:center}@media(hover:hover)and (pointer:fine){.ag-menu-bar-button:hover:not(:disabled),.ag-menu-bar-trigger:hover:not(:disabled),.ag-menu-bar-dropdown-item:hover:not(:disabled){background:var(--agrid-color-bg-muted)}.ag-menu-bar-button--active:hover:not(:disabled),.ag-menu-bar-trigger--open:hover:not(:disabled),.ag-menu-bar-dropdown-item--active:hover:not(:disabled){background:var(--agrid-color-accent-subtle)}}\n"], dependencies: [{ kind: "component", type: AgridMenuBarIcon, selector: "agrid-menu-icon", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3583
3936
  }
3584
3937
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridMenuBarComponent, decorators: [{
3585
3938
  type: Component,
3586
- args: [{ selector: 'agrid-menu-bar', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ag-menu-bar\" role=\"toolbar\" [attr.aria-label]=\"label()\">\n @for (item of items(); track item.id) {\n @let children = visibleChildren(item);\n <div class=\"ag-menu-bar-group\" [attr.data-menu-id]=\"item.id\">\n <button type=\"button\" class=\"ag-menu-bar-button\"\n [class.ag-menu-bar-button--active]=\"isActive(item)\"\n [class.ag-menu-bar-button--split]=\"children.length > 0\"\n [disabled]=\"isDisabled(item)\"\n [attr.aria-pressed]=\"item.active !== undefined ? isActive(item) : null\"\n (click)=\"runAction($event, item)\">\n @if (item.icon) {\n <span class=\"ag-menu-bar-icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span class=\"ag-menu-bar-label\">{{ item.label }}</span>\n </button>\n @if (children.length > 0) {\n <button type=\"button\" class=\"ag-menu-bar-trigger\"\n [class.ag-menu-bar-trigger--open]=\"openItemId() === item.id\"\n [attr.aria-label]=\"item.label + ': ' + label()\"\n [attr.aria-expanded]=\"openItemId() === item.id\" aria-haspopup=\"menu\"\n [disabled]=\"isDisabled(item)\"\n (click)=\"toggleMenu($event, item)\"\n (keydown)=\"onTriggerKeydown($event, item)\">&#9662;</button>\n @if (openItemId() === item.id) {\n <div class=\"ag-menu-bar-dropdown\" role=\"menu\" [attr.aria-label]=\"item.label\"\n (click)=\"$event.stopPropagation()\" (keydown)=\"onMenuKeydown($event)\">\n @for (child of children; track child.id) {\n <button type=\"button\" class=\"ag-menu-bar-dropdown-item\" role=\"menuitem\"\n [class.ag-menu-bar-dropdown-item--active]=\"isActive(child)\"\n [disabled]=\"isDisabled(child)\"\n [attr.aria-current]=\"isActive(child) ? 'true' : null\"\n (click)=\"runAction($event, child)\">\n <span class=\"ag-menu-bar-dropdown-check\" aria-hidden=\"true\">{{ isActive(child) ? '&#10003;' : '' }}</span>\n @if (child.icon) {\n <span class=\"ag-menu-bar-icon\" aria-hidden=\"true\">{{ child.icon }}</span>\n }\n <span class=\"ag-menu-bar-label\">{{ child.label }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;min-width:0}.ag-menu-bar{display:flex;align-items:center;flex-wrap:wrap;gap:4px;min-width:0}.ag-menu-bar-group{position:relative;display:inline-flex;align-items:stretch}.ag-menu-bar-button,.ag-menu-bar-trigger{display:inline-flex;align-items:center;justify-content:center;height:30px;border:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);color:var(--agrid-color-text);cursor:pointer;font:inherit;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-button{gap:6px;min-width:0;padding:0 10px;border-radius:4px;font-weight:500;white-space:nowrap}.ag-menu-bar-button--split{border-radius:4px 0 0 4px}.ag-menu-bar-trigger{width:28px;padding:0;margin-left:-1px;border-radius:0 4px 4px 0;color:var(--agrid-color-text-muted);font-size:10px}.ag-menu-bar-button--active,.ag-menu-bar-trigger--open{position:relative;z-index:1;border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-button:focus-visible,.ag-menu-bar-trigger:focus-visible,.ag-menu-bar-dropdown-item:focus-visible{position:relative;z-index:2;outline:2px solid var(--agrid-color-accent);outline-offset:1px}.ag-menu-bar-button:active:not(:disabled),.ag-menu-bar-trigger:active:not(:disabled),.ag-menu-bar-dropdown-item:active:not(:disabled){transform:scale(.97)}.ag-menu-bar-button:disabled,.ag-menu-bar-trigger:disabled,.ag-menu-bar-dropdown-item:disabled{opacity:.48;cursor:not-allowed}.ag-menu-bar-icon{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;min-width:14px;line-height:1}.ag-menu-bar-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-menu-bar-dropdown{position:absolute;z-index:12;top:calc(100% + 4px);left:0;display:flex;flex-direction:column;min-width:184px;max-width:min(280px,calc(100vw - 24px));padding:4px;border:1px solid var(--agrid-color-border);border-radius:6px;background:var(--agrid-color-bg);box-shadow:0 10px 28px var(--agrid-color-shadow);transform-origin:top left}.ag-menu-bar-group:last-child .ag-menu-bar-dropdown{right:0;left:auto;transform-origin:top right}.ag-menu-bar-dropdown-item{display:flex;align-items:center;gap:7px;width:100%;min-height:30px;padding:4px 8px;border:0;border-radius:4px;background:transparent;color:var(--agrid-color-text);cursor:pointer;font:inherit;text-align:left;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-dropdown-item--active{background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-dropdown-check{flex:0 0 12px;color:var(--agrid-color-accent);font-size:11px;font-weight:700;text-align:center}@media(hover:hover)and (pointer:fine){.ag-menu-bar-button:hover:not(:disabled),.ag-menu-bar-trigger:hover:not(:disabled),.ag-menu-bar-dropdown-item:hover:not(:disabled){background:var(--agrid-color-bg-muted)}.ag-menu-bar-button--active:hover:not(:disabled),.ag-menu-bar-trigger--open:hover:not(:disabled),.ag-menu-bar-dropdown-item--active:hover:not(:disabled){background:var(--agrid-color-accent-subtle)}}\n"] }]
3939
+ args: [{ selector: 'agrid-menu-bar', imports: [AgridMenuBarIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ag-menu-bar\" role=\"toolbar\" [attr.aria-label]=\"label()\">\n @for (item of items(); track item.id) {\n @let children = visibleChildren(item);\n <div class=\"ag-menu-bar-group\" [attr.data-menu-id]=\"item.id\">\n <button type=\"button\" class=\"ag-menu-bar-button\"\n [class.ag-menu-bar-button--active]=\"isActive(item)\"\n [class.ag-menu-bar-button--split]=\"children.length > 0\"\n [disabled]=\"isDisabled(item)\"\n [attr.aria-pressed]=\"item.active !== undefined ? isActive(item) : null\"\n (click)=\"runAction($event, item)\">\n @if (item.icon) {\n <agrid-menu-icon [class]=\"item.class\">{{item.icon}}</agrid-menu-icon>\n }\n <span class=\"ag-menu-bar-label\">{{ item.label }}</span>\n </button>\n @if (children.length > 0) {\n <button type=\"button\" class=\"ag-menu-bar-trigger\"\n [class.ag-menu-bar-trigger--open]=\"openItemId() === item.id\"\n [attr.aria-label]=\"item.label + ': ' + label()\"\n [attr.aria-expanded]=\"openItemId() === item.id\" aria-haspopup=\"menu\"\n [disabled]=\"isDisabled(item)\"\n (click)=\"toggleMenu($event, item)\"\n (keydown)=\"onTriggerKeydown($event, item)\">&#9662;</button>\n @if (openItemId() === item.id) {\n <div class=\"ag-menu-bar-dropdown\" role=\"menu\" [attr.aria-label]=\"item.label\"\n (click)=\"$event.stopPropagation()\" (keydown)=\"onMenuKeydown($event)\">\n @for (child of children; track child.id) {\n <button type=\"button\" class=\"ag-menu-bar-dropdown-item\" role=\"menuitem\"\n [class.ag-menu-bar-dropdown-item--active]=\"isActive(child)\"\n [disabled]=\"isDisabled(child)\"\n [attr.aria-current]=\"isActive(child) ? 'true' : null\"\n (click)=\"runAction($event, child)\">\n <span class=\"ag-menu-bar-dropdown-check\" aria-hidden=\"true\">{{ isActive(child) ? '&#10003;' : '' }}</span>\n @if (child.icon) {\n <agrid-menu-icon [class]=\"item.class\">{{child.icon}}</agrid-menu-icon>\n }\n <span class=\"ag-menu-bar-label\">{{ child.label }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;min-width:0}.ag-menu-bar{display:flex;align-items:center;flex-wrap:wrap;gap:4px;min-width:0}.ag-menu-bar-group{position:relative;display:inline-flex;align-items:stretch}.ag-menu-bar-button,.ag-menu-bar-trigger{display:inline-flex;align-items:center;justify-content:center;height:30px;border:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);color:var(--agrid-color-text);cursor:pointer;font:inherit;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-button{gap:6px;min-width:0;padding:0 10px;border-radius:4px;font-weight:500;white-space:nowrap}.ag-menu-bar-button--split{border-radius:4px 0 0 4px}.ag-menu-bar-trigger{width:28px;padding:0;margin-left:-1px;border-radius:0 4px 4px 0;color:var(--agrid-color-text-muted);font-size:10px}.ag-menu-bar-button--active,.ag-menu-bar-trigger--open{position:relative;z-index:1;border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-button:focus-visible,.ag-menu-bar-trigger:focus-visible,.ag-menu-bar-dropdown-item:focus-visible{position:relative;z-index:2;outline:2px solid var(--agrid-color-accent);outline-offset:1px}.ag-menu-bar-button:active:not(:disabled),.ag-menu-bar-trigger:active:not(:disabled),.ag-menu-bar-dropdown-item:active:not(:disabled){transform:scale(.97)}.ag-menu-bar-button:disabled,.ag-menu-bar-trigger:disabled,.ag-menu-bar-dropdown-item:disabled{opacity:.48;cursor:not-allowed}.ag-menu-bar-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-menu-bar-dropdown{position:absolute;z-index:12;top:calc(100% + 4px);left:0;display:flex;flex-direction:column;min-width:184px;max-width:min(280px,calc(100vw - 24px));padding:4px;border:1px solid var(--agrid-color-border);border-radius:6px;background:var(--agrid-color-bg);box-shadow:0 10px 28px var(--agrid-color-shadow);transform-origin:top left}.ag-menu-bar-group:last-child .ag-menu-bar-dropdown{right:0;left:auto;transform-origin:top right}.ag-menu-bar-dropdown-item{display:flex;align-items:center;gap:7px;width:100%;min-height:30px;padding:4px 8px;border:0;border-radius:4px;background:transparent;color:var(--agrid-color-text);cursor:pointer;font:inherit;text-align:left;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-menu-bar-dropdown-item--active{background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-menu-bar-dropdown-check{flex:0 0 12px;color:var(--agrid-color-accent);font-size:11px;font-weight:700;text-align:center}@media(hover:hover)and (pointer:fine){.ag-menu-bar-button:hover:not(:disabled),.ag-menu-bar-trigger:hover:not(:disabled),.ag-menu-bar-dropdown-item:hover:not(:disabled){background:var(--agrid-color-bg-muted)}.ag-menu-bar-button--active:hover:not(:disabled),.ag-menu-bar-trigger--open:hover:not(:disabled),.ag-menu-bar-dropdown-item--active:hover:not(:disabled){background:var(--agrid-color-accent-subtle)}}\n"] }]
3587
3940
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], openItemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "openItemId", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], openItemIdChange: [{ type: i0.Output, args: ["openItemIdChange"] }] } });
3588
3941
 
3942
+ const CONTROL_RUNTIME_STATE = new WeakMap();
3589
3943
  /**
3590
3944
  * Signal-based container for mutable grid UI state such as column widths and active filters.
3591
3945
  *
@@ -3618,36 +3972,37 @@ class AgridControl {
3618
3972
  _totalRows = signal(0, ...(ngDevMode ? [{ debugName: "_totalRows" }] : /* istanbul ignore next */ []));
3619
3973
  _aggregates = signal({}, ...(ngDevMode ? [{ debugName: "_aggregates" }] : /* istanbul ignore next */ []));
3620
3974
  _sortOrder = signal([], ...(ngDevMode ? [{ debugName: "_sortOrder" }] : /* istanbul ignore next */ []));
3975
+ _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : /* istanbul ignore next */ []));
3976
+ _readonly = signal(false, ...(ngDevMode ? [{ debugName: "_readonly" }] : /* istanbul ignore next */ []));
3977
+ _autoAddRows = signal(false, ...(ngDevMode ? [{ debugName: "_autoAddRows" }] : /* istanbul ignore next */ []));
3621
3978
  /** @param state Optional initial state, e.g. deserialized from storage. */
3622
3979
  constructor(state) {
3623
- if (state?.columnWidths)
3624
- this._columnWidths.set({ ...state.columnWidths });
3625
- if (state?.filters)
3626
- this._filters.set({ ...state.filters });
3627
- if (state?.quickFilter)
3628
- this._quickFilter.set(state.quickFilter);
3629
- if (state?.allowRowReorder)
3630
- this._allowRowReorder.set(state.allowRowReorder);
3631
- if (state?.groupByField !== undefined)
3632
- this._groupByField.set(state.groupByField ?? null);
3633
- if (state?.hiddenColumns?.length)
3634
- this._hiddenColumns.set(new Set(state.hiddenColumns));
3635
- if (state?.columnOrder?.length)
3636
- this._columnOrder.set([...state.columnOrder]);
3637
- if (state?.pinnedColumns?.length)
3638
- this._pinnedColumns.set(new Set(state.pinnedColumns));
3639
- if (state?.pinnedRightColumns?.length)
3640
- this._pinnedRightColumns.set(new Set(state.pinnedRightColumns));
3641
- if (state?.pageSize)
3642
- this._pageSize.set(state.pageSize);
3643
- if (state?.currentPage)
3644
- this._currentPage.set(state.currentPage);
3645
- if (state?.totalRows)
3646
- this._totalRows.set(state.totalRows);
3647
- if (state?.aggregates)
3648
- this._aggregates.set({ ...state.aggregates });
3649
- if (state?.sortOrder?.length)
3650
- this._sortOrder.set([...state.sortOrder]);
3980
+ CONTROL_RUNTIME_STATE.set(this, {
3981
+ loading: this._loading,
3982
+ readonly: this._readonly,
3983
+ autoAddRows: this._autoAddRows,
3984
+ });
3985
+ if (state)
3986
+ this.loadState(state);
3987
+ }
3988
+ // ── Runtime grid state ────────────────────────────────────────────────────
3989
+ /** Whether the grid displays its loading overlay. This transient state is not serialized. */
3990
+ loading = this._loading.asReadonly();
3991
+ /** Show or hide the grid loading overlay. */
3992
+ setLoading(value) {
3993
+ this._loading.set(value);
3994
+ }
3995
+ /** Whether all grid editing and mutation UI is disabled. This transient state is not serialized. */
3996
+ readonly = this._readonly.asReadonly();
3997
+ /** Enable or disable readonly mode at runtime. */
3998
+ setReadonly(value) {
3999
+ this._readonly.set(value);
4000
+ }
4001
+ /** Whether navigation beyond the final row automatically inserts a row. Not serialized. */
4002
+ autoAddRows = this._autoAddRows.asReadonly();
4003
+ /** Enable or disable automatic row insertion at runtime. */
4004
+ setAutoAddRows(value) {
4005
+ this._autoAddRows.set(value);
3651
4006
  }
3652
4007
  /**
3653
4008
  * When `true`, the control column shows a drag handle and rows can be
@@ -4032,6 +4387,27 @@ class AgridControl {
4032
4387
  || (!!f.operator && f.operand != null && f.operand !== ''));
4033
4388
  }
4034
4389
  // ── Serialization ──────────────────────────────────────────────────────────
4390
+ /**
4391
+ * Replace the live serializable state from a plain object.
4392
+ * Missing properties reset to their defaults, making a loaded snapshot deterministic.
4393
+ * Transient loading, readonly, auto-add, selection, and edit-history state are preserved.
4394
+ */
4395
+ loadState(state) {
4396
+ this._columnWidths.set({ ...(state.columnWidths ?? {}) });
4397
+ this._filters.set({ ...(state.filters ?? {}) });
4398
+ this._quickFilter.set(state.quickFilter ?? '');
4399
+ this._allowRowReorder.set(state.allowRowReorder ?? false);
4400
+ this._groupByField.set(state.groupByField ?? null);
4401
+ this._hiddenColumns.set(new Set(state.hiddenColumns ?? []));
4402
+ this._columnOrder.set([...(state.columnOrder ?? [])]);
4403
+ this._pinnedColumns.set(new Set(state.pinnedColumns ?? []));
4404
+ this._pinnedRightColumns.set(new Set(state.pinnedRightColumns ?? []));
4405
+ this._pageSize.set(state.pageSize ?? 0);
4406
+ this._currentPage.set(state.currentPage ?? 1);
4407
+ this._totalRows.set(state.totalRows ?? 0);
4408
+ this._aggregates.set({ ...(state.aggregates ?? {}) });
4409
+ this._sortOrder.set([...(state.sortOrder ?? [])]);
4410
+ }
4035
4411
  /** Serialize current state to a plain object suitable for JSON storage. */
4036
4412
  toJSON() {
4037
4413
  return {
@@ -4056,147 +4432,9 @@ class AgridControl {
4056
4432
  return new AgridControl(state);
4057
4433
  }
4058
4434
  }
4059
-
4060
- /**
4061
- * Signal-based data container shared between the grid and the host component.
4062
- *
4063
- * Both the grid and the host component hold a reference to the same `AgridDataSource`
4064
- * instance. Mutations made by either side (e.g. the grid calling `patchRow` when a cell
4065
- * is committed, or the host calling `setData` to load fresh data) are immediately reflected
4066
- * in the grid because {@link rows} is a readonly Angular signal.
4067
- *
4068
- * @example
4069
- * ```ts
4070
- * readonly ds = new AgridDataSource(generateRows(1000));
4071
- *
4072
- * // Later — update one row from outside the grid:
4073
- * this.ds.patchRow(5, { salary: 99000 });
4074
- * ```
4075
- */
4076
- class AgridDataSource {
4077
- _linkedRows = signal(null, ...(ngDevMode ? [{ debugName: "_linkedRows" }] : /* istanbul ignore next */ []));
4078
- _rows = linkedSignal(() => this._linkedRows()?.() ?? [], ...(ngDevMode ? [{ debugName: "_rows" }] : /* istanbul ignore next */ []));
4079
- _writableLinkedRows = null;
4080
- _rowAdded = signal(null, ...(ngDevMode ? [{ debugName: "_rowAdded" }] : /* istanbul ignore next */ []));
4081
- _changeSequence = 0;
4082
- /**
4083
- * @param initialData Rows to seed the data source with.
4084
- * The array is shallow-copied so external mutations do not affect the source.
4085
- */
4086
- constructor(initialData = []) {
4087
- this._rows.set([...initialData]);
4088
- }
4089
- /**
4090
- * Readonly signal of the current row array.
4091
- * Read it inside Angular templates or `computed()` to react to changes automatically.
4092
- */
4093
- rows = this._rows.asReadonly();
4094
- /** Latest row insertion, used by attached grids to reveal the inserted row. */
4095
- rowAdded = this._rowAdded.asReadonly();
4096
- /**
4097
- * Link an external row signal to this data source.
4098
- *
4099
- * Whenever `source` changes, its array becomes the current datasource value without an
4100
- * intermediate effect or array copy. If `source` is writable, datasource mutations are written
4101
- * back to it automatically. Mutations remain local when linking a readonly signal.
4102
- */
4103
- linkSignal(source) {
4104
- this._writableLinkedRows = isWritableSignal(source)
4105
- ? source
4106
- : null;
4107
- this._linkedRows.set(source);
4108
- }
4109
- /**
4110
- * Replace the entire row array.
4111
- * Triggers a full grid re-render via the signal.
4112
- */
4113
- setData(rows) {
4114
- this.setRows([...rows]);
4115
- }
4116
- /**
4117
- * Overwrite the row at `index` with a new row object.
4118
- * Use {@link patchRow} when you only want to change specific fields.
4119
- */
4120
- updateRow(index, row) {
4121
- this.updateRows(rows => {
4122
- const next = [...rows];
4123
- next[index] = row;
4124
- return next;
4125
- });
4126
- }
4127
- /**
4128
- * Merge `patch` into the existing row at `index`, leaving other fields untouched.
4129
- * The grid calls this internally when a cell edit is committed.
4130
- */
4131
- patchRow(index, patch) {
4132
- this.updateRows(rows => {
4133
- const next = [...rows];
4134
- next[index] = { ...next[index], ...patch };
4135
- return next;
4136
- });
4137
- }
4138
- /**
4139
- * Insert a row into the data source and return the index at which it was inserted.
4140
- *
4141
- * @param row The row object to insert.
4142
- * @param atIndex Optional insertion index. Defaults to the end of the array.
4143
- * @returns The index the row was inserted at.
4144
- */
4145
- addRow(row, atIndex) {
4146
- let insertedAt;
4147
- this.updateRows(rows => {
4148
- if (atIndex === undefined) {
4149
- insertedAt = rows.length;
4150
- return [...rows, row];
4151
- }
4152
- insertedAt = atIndex;
4153
- const next = [...rows];
4154
- next.splice(atIndex, 0, row);
4155
- return next;
4156
- });
4157
- this._rowAdded.set({ index: insertedAt, sequence: ++this._changeSequence });
4158
- return insertedAt;
4159
- }
4160
- /**
4161
- * Remove the row at `index`.
4162
- * The grid adjusts `selectedCell` and `editingCell` internally when a deletion occurs
4163
- * via the control column context menu.
4164
- */
4165
- removeRow(index) {
4166
- this.updateRows(rows => rows.filter((_, i) => i !== index));
4167
- }
4168
- /**
4169
- * Move the row at `from` to position `to` (insert-before semantics).
4170
- * Designed to be called directly from a `(rowReorder)` handler:
4171
- * ```ts
4172
- * onReorder(e: RowReorderEvent) { this.ds.moveRow(e.oldIndex, e.newIndex); }
4173
- * ```
4174
- */
4175
- moveRow(from, to) {
4176
- if (from === to)
4177
- return;
4178
- this.updateRows(rows => {
4179
- const arr = [...rows];
4180
- const [item] = arr.splice(from, 1);
4181
- arr.splice(to > from ? to - 1 : to, 0, item);
4182
- return arr;
4183
- });
4184
- }
4185
- /** Return the current row at `index` (non-reactive snapshot). */
4186
- getRow(index) {
4187
- return this._rows()[index];
4188
- }
4189
- /** Current number of rows (non-reactive snapshot). */
4190
- get length() {
4191
- return this._rows().length;
4192
- }
4193
- updateRows(update) {
4194
- this.setRows(update(this._rows()));
4195
- }
4196
- setRows(rows) {
4197
- this._writableLinkedRows?.set(rows);
4198
- this._rows.set(rows);
4199
- }
4435
+ /** @internal Bridge for deprecated writable signals on AgridProvider. */
4436
+ function ɵgetAgridControlRuntimeState(control) {
4437
+ return CONTROL_RUNTIME_STATE.get(control);
4200
4438
  }
4201
4439
 
4202
4440
  /**
@@ -4208,10 +4446,23 @@ class AgridDataSource {
4208
4446
  class AgridProvider {
4209
4447
  /** Mutable row data consumed by the grid. */
4210
4448
  datasource;
4449
+ /** Lazy server-side model, or `null` for the regular client-side datasource. */
4450
+ serverSideRowModel;
4211
4451
  /** Runtime UI state and imperative grid controls. */
4212
4452
  control;
4213
4453
  /** Reactive column definitions in display order. */
4214
4454
  columns;
4455
+ _pivotConfig = signal(null, ...(ngDevMode ? [{ debugName: "_pivotConfig" }] : /* istanbul ignore next */ []));
4456
+ /**
4457
+ * Client-side pivot configuration, or `null` for the normal row view.
4458
+ * Assigning a new configuration reactively rebuilds the derived pivot rows and columns.
4459
+ */
4460
+ get pivotConfig() {
4461
+ return this._pivotConfig();
4462
+ }
4463
+ set pivotConfig(config) {
4464
+ this._pivotConfig.set(config);
4465
+ }
4215
4466
  /** Header-group labels referenced by column definitions. */
4216
4467
  headerGroups;
4217
4468
  /** Tree configuration, or `null` when the grid is not in tree mode. */
@@ -4261,7 +4512,7 @@ class AgridProvider {
4261
4512
  menuBarItems;
4262
4513
  /** Enabled sorting mode. */
4263
4514
  sortOption;
4264
- /** Toggle auto-add-rows without recreating the provider. @default signal(false) */
4515
+ /** @deprecated Use `control.autoAddRows` and `control.setAutoAddRows()` instead. */
4265
4516
  autoAddRows;
4266
4517
  /** Enabled row-selection mode. */
4267
4518
  rowSelection;
@@ -4293,28 +4544,39 @@ class AgridProvider {
4293
4544
  detailRenderer;
4294
4545
  /** Fixed height in pixels of an expanded detail panel row. */
4295
4546
  detailRowHeight;
4296
- /** Toggle the loading overlay without recreating the provider. @default signal(false) */
4547
+ /** @deprecated Use `control.loading` and `control.setLoading()` instead. */
4297
4548
  loading;
4298
- /** Toggle readonly mode without recreating the provider. @default signal(false) */
4549
+ /** @deprecated Use `control.readonly` and `control.setReadonly()` instead. */
4299
4550
  readonlyGrid;
4300
4551
  /** Creates a provider from the supplied data, state, columns, and display options. */
4301
4552
  constructor(config = {}) {
4553
+ if (config.serverSideRowModel && (config.treeConfig || config.pivotConfig)) {
4554
+ throw new Error('AgridServerSideRowModel does not support treeConfig or pivotConfig.');
4555
+ }
4556
+ if (config.treeConfig && config.pivotConfig) {
4557
+ throw new Error('treeConfig and pivotConfig are mutually exclusive.');
4558
+ }
4302
4559
  this.options = { locale: config.locale ?? 'auto' };
4303
- this.datasource = config.datasource ?? new AgridDataSource([]);
4560
+ this.serverSideRowModel = config.serverSideRowModel ?? null;
4561
+ this.datasource = this.serverSideRowModel ?? config.datasource ?? new AgridDataSource([]);
4304
4562
  this.control = config.control ?? new AgridControl({ allowRowReorder: true });
4563
+ const runtimeState = ɵgetAgridControlRuntimeState(this.control);
4305
4564
  this.columns = signal(config.columns ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
4565
+ this.pivotConfig = config.pivotConfig ?? null;
4306
4566
  this.headerGroups = config.headerGroups ?? [];
4307
4567
  this.treeConfig = config.treeConfig ?? null;
4308
4568
  this.rowHeight = config.rowHeight ?? 32;
4309
4569
  this.minHeight = config.minHeight;
4310
4570
  this.maxHeight = config.maxHeight;
4311
4571
  this.allowAddRows = config.allowAddRows ?? false;
4312
- this.autoAddRows = signal(config.autoAddRows ?? false, ...(ngDevMode ? [{ debugName: "autoAddRows" }] : /* istanbul ignore next */ []));
4572
+ if (config.autoAddRows !== undefined)
4573
+ this.control.setAutoAddRows(config.autoAddRows);
4574
+ this.autoAddRows = runtimeState.autoAddRows;
4313
4575
  this.showControlColumn = config.showControlColumn ?? false;
4314
4576
  this.enableRowMarking = config.enableRowMarking ?? false;
4315
4577
  this.showSidebar = config.showSidebar ?? false;
4316
4578
  this.autoOpenDetail = config.autoOpenDetail ?? false;
4317
- this.serverSideFiltering = config.serverSideFiltering ?? false;
4579
+ this.serverSideFiltering = this.serverSideRowModel ? true : config.serverSideFiltering ?? false;
4318
4580
  this.filterDebounceMs = Math.max(0, config.filterDebounceMs ?? 300);
4319
4581
  this.enableQuickFilter = config.enableQuickFilter ?? false;
4320
4582
  this.menuBarItems = config.menuBarItems ?? [];
@@ -4328,8 +4590,12 @@ class AgridProvider {
4328
4590
  this.showChangedCellIndicator = config.showChangedCellIndicator ?? false;
4329
4591
  this.confirmRowDelete = config.confirmRowDelete ?? false;
4330
4592
  this.emptyText = config.emptyText;
4331
- this.loading = signal(config.loading ?? false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
4332
- this.readonlyGrid = signal(config.readonly ?? false, ...(ngDevMode ? [{ debugName: "readonlyGrid" }] : /* istanbul ignore next */ []));
4593
+ if (config.loading !== undefined)
4594
+ this.control.setLoading(config.loading);
4595
+ if (config.readonly !== undefined)
4596
+ this.control.setReadonly(config.readonly);
4597
+ this.loading = runtimeState.loading;
4598
+ this.readonlyGrid = runtimeState.readonly;
4333
4599
  this.useSidebarEditor = config.useSidebarEditor ?? false;
4334
4600
  this.getRowClass = config.getRowClass;
4335
4601
  this.pinRow = config.pinRow;
@@ -4341,6 +4607,338 @@ class AgridProvider {
4341
4607
  getGridData() {
4342
4608
  return this.datasource.rows();
4343
4609
  }
4610
+ /**
4611
+ * Return a detached, JSON-safe snapshot of persistent grid and pivot settings.
4612
+ * Custom pivot aggregate functions cannot be serialized and produce an explicit error.
4613
+ */
4614
+ saveSettings() {
4615
+ const pivot = this.pivotConfig;
4616
+ const aggregate = pivot?.aggregate;
4617
+ if (typeof aggregate === 'function') {
4618
+ throw new Error('Custom pivot aggregate functions cannot be saved as JSON settings.');
4619
+ }
4620
+ return {
4621
+ version: 1,
4622
+ control: this.control.toJSON(),
4623
+ pivotConfig: pivot
4624
+ ? {
4625
+ rowField: pivot.rowField,
4626
+ columnField: pivot.columnField,
4627
+ valueField: pivot.valueField,
4628
+ aggregate: aggregate ?? 'sum',
4629
+ }
4630
+ : null,
4631
+ };
4632
+ }
4633
+ /**
4634
+ * Apply a previously saved snapshot to this live provider.
4635
+ * Existing component instances update immediately because both control and pivot state are
4636
+ * signal-backed. Unknown versions and pivot fields fail early with actionable errors.
4637
+ */
4638
+ loadSettings(settings) {
4639
+ if (settings.version !== 1) {
4640
+ throw new Error(`Unsupported aGrid settings version: ${settings.version}`);
4641
+ }
4642
+ if (settings.pivotConfig) {
4643
+ if (this.serverSideRowModel || this.treeConfig) {
4644
+ throw new Error('Saved pivot settings cannot be loaded with treeConfig or a server-side row model.');
4645
+ }
4646
+ const fields = new Set(this.columns().map(column => column.field));
4647
+ const pivotFields = [
4648
+ settings.pivotConfig.rowField,
4649
+ settings.pivotConfig.columnField,
4650
+ settings.pivotConfig.valueField,
4651
+ ];
4652
+ if (pivotFields.some(field => !fields.has(field))) {
4653
+ throw new Error('Saved pivot settings reference a column that is not configured.');
4654
+ }
4655
+ }
4656
+ this.control.loadState(settings.control);
4657
+ this.pivotConfig = settings.pivotConfig;
4658
+ }
4659
+ }
4660
+
4661
+ /** Apply one supported aggregate to the raw values in a pivot bucket. */
4662
+ function aggregatePivotValues(values, aggregate) {
4663
+ if (typeof aggregate === 'function')
4664
+ return aggregate(values);
4665
+ if (aggregate === 'count') {
4666
+ return values.reduce((count, value) => count + (value != null && value !== '' ? 1 : 0), 0);
4667
+ }
4668
+ const numeric = values
4669
+ .map(value => Number(value))
4670
+ .filter(value => !Number.isNaN(value));
4671
+ if (aggregate === 'sum')
4672
+ return numeric.reduce((sum, value) => sum + value, 0);
4673
+ if (numeric.length === 0)
4674
+ return null;
4675
+ if (aggregate === 'avg') {
4676
+ return numeric.reduce((sum, value) => sum + value, 0) / numeric.length;
4677
+ }
4678
+ let result = numeric[0];
4679
+ for (let index = 1; index < numeric.length; index++) {
4680
+ result = aggregate === 'min'
4681
+ ? Math.min(result, numeric[index])
4682
+ : Math.max(result, numeric[index]);
4683
+ }
4684
+ return result;
4685
+ }
4686
+ /**
4687
+ * Convert flat datasource rows into a read-only cross-tabulation.
4688
+ *
4689
+ * Distinct row and column values retain their raw identity while their configured column
4690
+ * formatters supply labels. Generated field names are private implementation keys, avoiding
4691
+ * collisions with source fields. Empty intersections are represented by `null`.
4692
+ */
4693
+ function buildPivotResult(rows, sourceColumns, config, locale) {
4694
+ // ColDef is deliberately row-type invariant because callback parameters contain T. Pivoting
4695
+ // reads only display metadata, so normalize definitions to the renderer's untyped view here.
4696
+ const normalizedColumns = sourceColumns;
4697
+ const columnMap = new Map(normalizedColumns.map(column => [column.field, column]));
4698
+ const rowColumn = columnMap.get(config.rowField);
4699
+ const pivotColumn = columnMap.get(config.columnField);
4700
+ const valueColumn = columnMap.get(config.valueField);
4701
+ if (!rowColumn || !pivotColumn || !valueColumn) {
4702
+ throw new Error('Pivot rowField, columnField, and valueField must reference configured columns.');
4703
+ }
4704
+ const buckets = new Map();
4705
+ const rowLabels = new Map();
4706
+ const columnLabels = new Map();
4707
+ for (const row of rows) {
4708
+ const record = row;
4709
+ const rowValue = record[config.rowField];
4710
+ const columnValue = record[config.columnField];
4711
+ rowLabels.set(rowValue, getDisplayForField(rowColumn, rowValue, locale));
4712
+ columnLabels.set(columnValue, getDisplayForField(pivotColumn, columnValue, locale));
4713
+ let columns = buckets.get(rowValue);
4714
+ if (!columns) {
4715
+ columns = new Map();
4716
+ buckets.set(rowValue, columns);
4717
+ }
4718
+ const values = columns.get(columnValue);
4719
+ if (values)
4720
+ values.push(record[config.valueField]);
4721
+ else
4722
+ columns.set(columnValue, [record[config.valueField]]);
4723
+ }
4724
+ const byLabel = (labels) => ([a], [b]) => (labels.get(a) ?? '').localeCompare(labels.get(b) ?? '', locale, { sensitivity: 'base' });
4725
+ const pivotValues = [...columnLabels.entries()].sort(byLabel(columnLabels)).map(([value]) => value);
4726
+ const rowValues = [...rowLabels.entries()].sort(byLabel(rowLabels)).map(([value]) => value);
4727
+ const aggregate = config.aggregate ?? 'sum';
4728
+ const columns = [
4729
+ { ...rowColumn, editable: false, aggregate: undefined },
4730
+ ...pivotValues.map((value, index) => ({
4731
+ field: `__agrid_pivot_${index}`,
4732
+ header: columnLabels.get(value) ?? String(value ?? ''),
4733
+ type: valueColumn.type,
4734
+ width: valueColumn.width,
4735
+ // A count describes bucket cardinality rather than the source value's unit (for example
4736
+ // dollars), so carrying a currency/percentage formatter onto it would be misleading.
4737
+ formatter: aggregate === 'count'
4738
+ ? undefined
4739
+ : valueColumn.formatter,
4740
+ editable: false,
4741
+ })),
4742
+ ];
4743
+ const pivotRows = rowValues.map(rowValue => {
4744
+ const result = { [config.rowField]: rowValue };
4745
+ pivotValues.forEach((columnValue, index) => {
4746
+ const values = buckets.get(rowValue)?.get(columnValue);
4747
+ result[`__agrid_pivot_${index}`] = values
4748
+ ? aggregatePivotValues(values, aggregate)
4749
+ : null;
4750
+ });
4751
+ return result;
4752
+ });
4753
+ return { rows: pivotRows, columns };
4754
+ }
4755
+
4756
+ const SERVER_ROW_PLACEHOLDER = Symbol('agrid-server-row-placeholder');
4757
+ const PLACEHOLDER = { [SERVER_ROW_PLACEHOLDER]: true };
4758
+ /**
4759
+ * Sparse datasource that lazy-loads row blocks while retaining global datasource indices.
4760
+ * Attach it to an `AgridProvider` through `serverSideRowModel`.
4761
+ */
4762
+ class AgridServerSideRowModel extends AgridDataSource {
4763
+ blockSize;
4764
+ maxBlocksInCache;
4765
+ remote;
4766
+ slots = [];
4767
+ loadedBlocks = new Map();
4768
+ loadingBlocks = new Set();
4769
+ query = { filters: {}, sort: [], quickFilter: '' };
4770
+ queryKey = '';
4771
+ generation = 0;
4772
+ accessSequence = 0;
4773
+ knownRowCount = false;
4774
+ _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : /* istanbul ignore next */ []));
4775
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
4776
+ _rowCount = signal(0, ...(ngDevMode ? [{ debugName: "_rowCount" }] : /* istanbul ignore next */ []));
4777
+ /** Whether at least one block request is in flight. */
4778
+ loading = this._loading.asReadonly();
4779
+ /** Most recent load error, cleared by the next successful request or refresh. */
4780
+ error = this._error.asReadonly();
4781
+ /** Current logical row count represented by the virtual scrollbar. */
4782
+ rowCount = this._rowCount.asReadonly();
4783
+ constructor(config) {
4784
+ super();
4785
+ this.remote = config.datasource;
4786
+ this.blockSize = Math.max(1, Math.floor(config.blockSize ?? 100));
4787
+ this.maxBlocksInCache = Math.max(1, Math.floor(config.maxBlocksInCache ?? 10));
4788
+ const initial = config.initialRowCount;
4789
+ this.knownRowCount = initial !== undefined;
4790
+ this.replaceSlots(this.createPlaceholders(Math.max(0, initial ?? this.blockSize)));
4791
+ }
4792
+ /** Update server query state. A changed query invalidates cached blocks and starts at row zero. */
4793
+ setQuery(control, sortFields) {
4794
+ const filters = cloneFilters(control?.filters() ?? {});
4795
+ const sort = sortFields.flatMap(field => {
4796
+ const direction = filters[field]?.sort;
4797
+ return direction ? [{ field, direction }] : [];
4798
+ });
4799
+ const quickFilter = control?.quickFilter() ?? '';
4800
+ const key = JSON.stringify({ filters, sort, quickFilter });
4801
+ if (key === this.queryKey)
4802
+ return false;
4803
+ this.queryKey = key;
4804
+ this.query = { filters, sort, quickFilter };
4805
+ this.reset();
4806
+ return true;
4807
+ }
4808
+ /** Invalidate all cached blocks while preserving a known total row count. */
4809
+ refresh() {
4810
+ this.reset();
4811
+ }
4812
+ /** Ensure every block intersecting the requested half-open range is loaded. */
4813
+ ensureRange(startRow, endRow) {
4814
+ const count = this.slots.length;
4815
+ if (!count)
4816
+ return;
4817
+ const start = Math.max(0, Math.min(Math.floor(startRow), count - 1));
4818
+ const end = Math.max(start + 1, Math.min(Math.ceil(endRow), count));
4819
+ const firstBlock = Math.floor(start / this.blockSize);
4820
+ const lastBlock = Math.floor((end - 1) / this.blockSize);
4821
+ for (let block = firstBlock; block <= lastBlock; block++)
4822
+ void this.loadBlock(block);
4823
+ }
4824
+ /** Whether a logical datasource row has not been loaded yet. */
4825
+ isPlaceholder(index) {
4826
+ return isServerRowPlaceholder(this.slots[index]);
4827
+ }
4828
+ reset() {
4829
+ this.generation++;
4830
+ this.loadedBlocks.clear();
4831
+ this.loadingBlocks.clear();
4832
+ this._loading.set(false);
4833
+ this._error.set(null);
4834
+ const length = this.knownRowCount ? this._rowCount() : this.blockSize;
4835
+ this.replaceSlots(this.createPlaceholders(length));
4836
+ }
4837
+ async loadBlock(block) {
4838
+ if (this.loadedBlocks.has(block) || this.loadingBlocks.has(block)) {
4839
+ if (this.loadedBlocks.has(block))
4840
+ this.loadedBlocks.set(block, ++this.accessSequence);
4841
+ return;
4842
+ }
4843
+ const startRow = block * this.blockSize;
4844
+ if (startRow >= this.slots.length)
4845
+ return;
4846
+ const endRow = Math.min(startRow + this.blockSize, this.slots.length);
4847
+ const generation = this.generation;
4848
+ this.loadingBlocks.add(block);
4849
+ this._loading.set(true);
4850
+ try {
4851
+ const result = await this.remote.getRows({
4852
+ startRow,
4853
+ endRow,
4854
+ filters: this.query.filters,
4855
+ sort: this.query.sort,
4856
+ quickFilter: this.query.quickFilter,
4857
+ });
4858
+ if (generation !== this.generation)
4859
+ return;
4860
+ this.applyBlock(block, startRow, result);
4861
+ this._error.set(null);
4862
+ }
4863
+ catch (error) {
4864
+ if (generation === this.generation)
4865
+ this._error.set(error);
4866
+ }
4867
+ finally {
4868
+ if (generation === this.generation) {
4869
+ this.loadingBlocks.delete(block);
4870
+ this._loading.set(this.loadingBlocks.size > 0);
4871
+ }
4872
+ }
4873
+ }
4874
+ applyBlock(block, startRow, result) {
4875
+ if (result.rowCount !== undefined) {
4876
+ this.knownRowCount = true;
4877
+ this.resize(Math.max(0, Math.floor(result.rowCount)));
4878
+ }
4879
+ else if (result.rows.length < this.blockSize) {
4880
+ this.knownRowCount = true;
4881
+ this.resize(startRow + result.rows.length);
4882
+ }
4883
+ else if (!this.knownRowCount && startRow + result.rows.length >= this.slots.length) {
4884
+ this.resize(this.slots.length + this.blockSize);
4885
+ }
4886
+ const next = [...this.slots];
4887
+ for (let index = 0; index < result.rows.length; index++) {
4888
+ const target = startRow + index;
4889
+ if (target >= next.length)
4890
+ break;
4891
+ next[target] = result.rows[index];
4892
+ }
4893
+ this.loadedBlocks.set(block, ++this.accessSequence);
4894
+ this.evictBlocks(block, next);
4895
+ this.replaceSlots(next);
4896
+ }
4897
+ evictBlocks(activeBlock, slots) {
4898
+ while (this.loadedBlocks.size > this.maxBlocksInCache) {
4899
+ const candidate = [...this.loadedBlocks]
4900
+ .filter(([block]) => block !== activeBlock)
4901
+ .sort((left, right) => left[1] - right[1])[0];
4902
+ if (!candidate)
4903
+ return;
4904
+ const [block] = candidate;
4905
+ this.loadedBlocks.delete(block);
4906
+ const start = block * this.blockSize;
4907
+ const end = Math.min(start + this.blockSize, slots.length);
4908
+ for (let index = start; index < end; index++)
4909
+ slots[index] = placeholder();
4910
+ }
4911
+ }
4912
+ resize(length) {
4913
+ if (length === this.slots.length)
4914
+ return;
4915
+ if (length < this.slots.length)
4916
+ this.slots = this.slots.slice(0, length);
4917
+ else
4918
+ this.slots = [...this.slots, ...this.createPlaceholders(length - this.slots.length)];
4919
+ this._rowCount.set(length);
4920
+ }
4921
+ replaceSlots(slots) {
4922
+ this.slots = slots;
4923
+ this._rowCount.set(slots.length);
4924
+ this.setRows(slots);
4925
+ }
4926
+ createPlaceholders(length) {
4927
+ return Array.from({ length }, () => placeholder());
4928
+ }
4929
+ }
4930
+ /** @internal */
4931
+ function isServerRowPlaceholder(value) {
4932
+ return !!value && typeof value === 'object' && SERVER_ROW_PLACEHOLDER in value;
4933
+ }
4934
+ function placeholder() {
4935
+ return PLACEHOLDER;
4936
+ }
4937
+ function cloneFilters(filters) {
4938
+ return Object.fromEntries(Object.entries(filters).map(([field, filter]) => [field, {
4939
+ ...filter,
4940
+ selectedValues: filter.selectedValues ? [...filter.selectedValues] : null,
4941
+ }]));
4344
4942
  }
4345
4943
 
4346
4944
  /**
@@ -4366,7 +4964,7 @@ class AgridProjectionModel {
4366
4964
  if (quick) {
4367
4965
  indices = applyQuickFilter(rows, indices, quick, this.opts.visibleColDefs(), this.opts.locale());
4368
4966
  }
4369
- if (control.groupByField())
4967
+ if (control.groupByField() && !this.opts.pivotMode?.())
4370
4968
  return indices;
4371
4969
  const sortEntries = this.sortEntries(filters);
4372
4970
  return sortEntries.length
@@ -4376,7 +4974,9 @@ class AgridProjectionModel {
4376
4974
  /** Total filtered row count, unaffected by client-side pagination. */
4377
4975
  filteredRowCount = computed(() => this.filteredSortedIndices().length, ...(ngDevMode ? [{ debugName: "filteredRowCount" }] : /* istanbul ignore next */ []));
4378
4976
  /** Whether row pinning is active: a `pinRow` callback is supplied and not in tree mode. */
4379
- pinningActive = computed(() => !!this.opts.pinRow?.() && !this.opts.treeConfig(), ...(ngDevMode ? [{ debugName: "pinningActive" }] : /* istanbul ignore next */ []));
4977
+ pinningActive = computed(() => !(this.opts.dataSource() instanceof AgridServerSideRowModel)
4978
+ && !!this.opts.pinRow?.()
4979
+ && !this.opts.treeConfig(), ...(ngDevMode ? [{ debugName: "pinningActive" }] : /* istanbul ignore next */ []));
4380
4980
  /**
4381
4981
  * Partitions the filtered+sorted indices into top-pinned, bottom-pinned, and a body set.
4382
4982
  * Pinned rows keep their real source index, so editing/selection over them is unchanged.
@@ -4435,13 +5035,21 @@ class AgridProjectionModel {
4435
5035
  }, ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
4436
5036
  /** Whether client or server pagination controls should be rendered. */
4437
5037
  showPagination = computed(() => {
5038
+ if (this.opts.dataSource() instanceof AgridServerSideRowModel)
5039
+ return false;
4438
5040
  const control = this.opts.control();
4439
5041
  if (this.opts.treeConfig())
4440
5042
  return false;
4441
- return (control?.pageSize() ?? 0) > 0 && !control?.groupByField();
5043
+ return (control?.pageSize() ?? 0) > 0
5044
+ && (!control?.groupByField() || !!this.opts.pivotMode?.());
4442
5045
  }, ...(ngDevMode ? [{ debugName: "showPagination" }] : /* istanbul ignore next */ []));
4443
5046
  /** Whether at least one visible column has an aggregate footer. */
4444
5047
  showFooter = computed(() => {
5048
+ if (this.opts.dataSource() instanceof AgridServerSideRowModel)
5049
+ return false;
5050
+ // Aggregating already-aggregated pivot cells is not generally valid (for example avg of avg).
5051
+ if (this.opts.pivotMode?.())
5052
+ return false;
4445
5053
  const aggregates = this.opts.control()?.aggregates() ?? {};
4446
5054
  return this.opts.visibleColDefs().some(col => col.aggregate || aggregates[col.field]);
4447
5055
  }, ...(ngDevMode ? [{ debugName: "showFooter" }] : /* istanbul ignore next */ []));
@@ -4450,20 +5058,38 @@ class AgridProjectionModel {
4450
5058
  /** Filtered, sorted, paginated, and optionally grouped virtual-scroll items. */
4451
5059
  filteredItems = computed(() => {
4452
5060
  const rows = this.opts.dataSource().rows();
5061
+ const serverModel = this.opts.dataSource() instanceof AgridServerSideRowModel
5062
+ ? this.opts.dataSource()
5063
+ : null;
4453
5064
  const control = this.opts.control();
4454
5065
  let indices = this.filteredSortedIndices();
5066
+ // Server-side blocks already arrive filtered and sorted. Preserve their global indices and
5067
+ // represent unloaded sparse slots as non-interactive virtual rows.
5068
+ if (serverModel) {
5069
+ return indices.map(originalIndex => serverModel.isPlaceholder(originalIndex)
5070
+ ? { loading: true, originalIndex }
5071
+ : { row: rows[originalIndex], originalIndex });
5072
+ }
4455
5073
  // Tree mode takes precedence over grouping/pagination: the already filtered-and-sorted
4456
5074
  // indices are flattened into a hierarchy honoring the expanded-id set.
4457
5075
  const treeConfig = this.opts.treeConfig();
4458
5076
  if (treeConfig) {
4459
5077
  const expandedIds = this.opts.expandedTreeIds();
5078
+ // Passing no columns keeps the tree builders allocation-free when rollups are disabled.
5079
+ // Runtime control aggregates override static ColDef.aggregate values inside the builders.
5080
+ const treeAggregateCols = treeConfig.aggregateTreeNodes
5081
+ ? this.opts.visibleColDefs()
5082
+ : [];
5083
+ const treeControlAggregates = treeConfig.aggregateTreeNodes
5084
+ ? control?.aggregates() ?? {}
5085
+ : {};
4460
5086
  // When a text/value filter is active, keep the ancestors of every match visible (and
4461
5087
  // force their path open) so deep matches don't vanish under filtered-out parents.
4462
5088
  const filters = control?.filters() ?? {};
4463
5089
  const filterActive = !this.opts.serverSideFiltering()
4464
5090
  && Object.values(filters).some(f => f.text || f.selectedValues !== null);
4465
5091
  if (isPathTreeConfig(treeConfig)) {
4466
- const items = this.appendTreeDetailItems(buildPathTreeItems(rows, indices, treeConfig, expandedIds, filterActive && treeConfig.keepAncestorsOnFilter !== false), rows, treeConfig);
5092
+ const items = this.appendTreeDetailItems(buildPathTreeItems(rows, indices, treeConfig, expandedIds, filterActive && treeConfig.keepAncestorsOnFilter !== false, treeAggregateCols, treeControlAggregates), rows, treeConfig);
4467
5093
  this.appendAddRow(items);
4468
5094
  return items;
4469
5095
  }
@@ -4485,11 +5111,11 @@ class AgridProjectionModel {
4485
5111
  }
4486
5112
  }
4487
5113
  const ordered = this.allSortedIndices().filter(index => visible.has(index));
4488
- const items = this.appendTreeDetailItems(buildTreeItems(rows, ordered, treeConfig, expandedIds, forced), rows, treeConfig);
5114
+ const items = this.appendTreeDetailItems(buildTreeItems(rows, ordered, treeConfig, expandedIds, forced, treeAggregateCols, treeControlAggregates), rows, treeConfig);
4489
5115
  this.appendAddRow(items);
4490
5116
  return items;
4491
5117
  }
4492
- const items = this.appendTreeDetailItems(buildTreeItems(rows, indices, treeConfig, expandedIds), rows, treeConfig);
5118
+ const items = this.appendTreeDetailItems(buildTreeItems(rows, indices, treeConfig, expandedIds, undefined, treeAggregateCols, treeControlAggregates), rows, treeConfig);
4493
5119
  this.appendAddRow(items);
4494
5120
  return items;
4495
5121
  }
@@ -4506,7 +5132,7 @@ class AgridProjectionModel {
4506
5132
  const page = Math.max(1, Math.min(control.currentPage(), this.totalPages()));
4507
5133
  indices = indices.slice((page - 1) * pageSize, page * pageSize);
4508
5134
  }
4509
- if (groupField) {
5135
+ if (groupField && !this.opts.pivotMode?.()) {
4510
5136
  const expansion = this.opts.expandedGroups();
4511
5137
  const expandedLabels = expansion.field === groupField
4512
5138
  ? expansion.labels
@@ -4563,6 +5189,7 @@ class AgridProjectionModel {
4563
5189
  if (item
4564
5190
  && typeof item === 'object'
4565
5191
  && 'originalIndex' in item
5192
+ && 'row' in item
4566
5193
  && (isPathTreeConfig(treeConfig)
4567
5194
  || !parentIds.has(treeConfig.getId(item.row)))
4568
5195
  && expandedDetail.has(item.originalIndex)) {
@@ -5092,7 +5719,7 @@ class AgridSidebarController {
5092
5719
  return;
5093
5720
  let newValue = stringValue;
5094
5721
  if (col.type === 'number') {
5095
- newValue = stringValue === '' ? null : Number(stringValue);
5722
+ newValue = coerceNumberInputValue(stringValue);
5096
5723
  }
5097
5724
  else if (col.type === 'date') {
5098
5725
  newValue = coerceDateInputValue(stringValue, this.opts.dataSource().getRow(index)[field]);
@@ -5137,6 +5764,10 @@ class AgridSidebarComponent {
5137
5764
  open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
5138
5765
  activeTab = input('columns', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
5139
5766
  columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
5767
+ /** Original datasource columns available as pivot dimensions and values. */
5768
+ pivotColumns = input([], ...(ngDevMode ? [{ debugName: "pivotColumns" }] : /* istanbul ignore next */ []));
5769
+ /** Active pivot configuration; `null` hides the pivot tab. */
5770
+ pivotConfig = input(null, ...(ngDevMode ? [{ debugName: "pivotConfig" }] : /* istanbul ignore next */ []));
5140
5771
  headerGroups = input([], ...(ngDevMode ? [{ debugName: "headerGroups" }] : /* istanbul ignore next */ []));
5141
5772
  row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
5142
5773
  rowIndex = input(null, ...(ngDevMode ? [{ debugName: "rowIndex" }] : /* istanbul ignore next */ []));
@@ -5154,6 +5785,43 @@ class AgridSidebarComponent {
5154
5785
  toggleColumnGroup = output();
5155
5786
  detailEdit = output();
5156
5787
  save = output();
5788
+ /** Emits a complete replacement configuration after one pivot control changes. */
5789
+ pivotChange = output();
5790
+ /** Built-in aggregate selected by the sidebar, or `custom` for host functions. */
5791
+ pivotAggregate = computed(() => {
5792
+ const aggregate = this.pivotConfig()?.aggregate ?? 'sum';
5793
+ return typeof aggregate === 'function' ? 'custom' : aggregate;
5794
+ }, ...(ngDevMode ? [{ debugName: "pivotAggregate" }] : /* istanbul ignore next */ []));
5795
+ /** Localized title for the currently active sidebar tab. */
5796
+ activeTabLabel() {
5797
+ const locale = this.localeText();
5798
+ if (this.activeTab() === 'pivot')
5799
+ return locale.pivot;
5800
+ return this.activeTab() === 'columns' ? locale.columns : locale.detail;
5801
+ }
5802
+ /** Replace one pivot dimension/value field while preserving the other settings. */
5803
+ onPivotFieldChange(field, event) {
5804
+ const config = this.pivotConfig();
5805
+ if (!config)
5806
+ return;
5807
+ this.pivotChange.emit({
5808
+ ...config,
5809
+ [field]: event.target.value,
5810
+ });
5811
+ }
5812
+ /** Replace the pivot aggregate with one of the serializable built-in functions. */
5813
+ onPivotAggregateChange(event) {
5814
+ const config = this.pivotConfig();
5815
+ if (!config)
5816
+ return;
5817
+ const aggregate = event.target.value;
5818
+ if (aggregate === 'custom')
5819
+ return;
5820
+ this.pivotChange.emit({
5821
+ ...config,
5822
+ aggregate: aggregate,
5823
+ });
5824
+ }
5157
5825
  columnEntries = computed(() => {
5158
5826
  const groupLabels = new Map(this.headerGroups().map(group => [group.id, group.label]));
5159
5827
  const entries = [];
@@ -5245,12 +5913,12 @@ class AgridSidebarComponent {
5245
5913
  });
5246
5914
  }, ...(ngDevMode ? [{ debugName: "detailFields" }] : /* istanbul ignore next */ []));
5247
5915
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5248
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridSidebarComponent, isStandalone: true, selector: "agrid-sidebar", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, headerGroups: { classPropertyName: "headerGroups", publicName: "headerGroups", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: false, transformFunction: null }, hiddenColumns: { classPropertyName: "hiddenColumns", publicName: "hiddenColumns", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, localeText: { classPropertyName: "localeText", publicName: "localeText", isSignal: true, isRequired: false, transformFunction: null }, readonlyGrid: { classPropertyName: "readonlyGrid", publicName: "readonlyGrid", isSignal: true, isRequired: false, transformFunction: null }, useSidebarEditor: { classPropertyName: "useSidebarEditor", publicName: "useSidebarEditor", isSignal: true, isRequired: false, transformFunction: null }, isCellEditable: { classPropertyName: "isCellEditable", publicName: "isCellEditable", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", tabChange: "tabChange", toggleColumn: "toggleColumn", toggleColumnGroup: "toggleColumnGroup", detailEdit: "detailEdit", save: "save" }, ngImport: i0, template: "@if (open()) {\n <div class=\"ag-sidebar\" role=\"region\"\n [attr.aria-label]=\"activeTab() === 'columns' ? localeText().columns : localeText().detail\">\n <div class=\"ag-sidebar-header\">\n <span>{{ activeTab() === 'columns' ? localeText().columns : localeText().detail }}</span>\n <button class=\"ag-sidebar-close\" (click)=\"close.emit()\" [title]=\"localeText().close\"\n [attr.aria-label]=\"localeText().close\">\u2715</button>\n </div>\n\n @if (activeTab() === 'columns') {\n @for (entry of columnEntries(); track entry.kind === 'group' ? 'group-' + entry.id : 'column-' + entry.col.field) {\n @if (entry.kind === 'group') {\n <div class=\"ag-sidebar-group\">\n <label class=\"ag-sidebar-item ag-sidebar-group-label\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupVisible(entry.columns)\"\n [indeterminate]=\"isGroupPartiallyVisible(entry.columns)\"\n (change)=\"onGroupToggle(entry.columns, $event)\"\n />\n {{ entry.label }}\n </label>\n <div class=\"ag-sidebar-group-children\">\n @for (col of entry.columns; track col.field) {\n <label class=\"ag-sidebar-item ag-sidebar-group-child\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(col.field)\"\n (change)=\"toggleColumn.emit(col.field)\"\n />\n {{ col.header }}\n </label>\n }\n </div>\n </div>\n } @else {\n <label class=\"ag-sidebar-item\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(entry.col.field)\"\n (change)=\"toggleColumn.emit(entry.col.field)\"\n />\n {{ entry.col.header }}\n </label>\n }\n }\n } @else {\n @if (detailFields(); as fields) {\n @for (field of fields; track field.col.field) {\n <div class=\"ag-detail-field\">\n <span class=\"ag-detail-label\">\n {{ field.label }}\n @if (field.hidden) {\n <span class=\"ag-detail-hidden\">{{ localeText().hiddenColumn }}</span>\n }\n </span>\n @if (field.editable && field.col.values?.length) {\n <select\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n (change)=\"detailEdit.emit({\n field: field.col.field,\n col: field.col,\n value: $any($event.target).value\n })\"\n >\n @for (opt of field.col.values; track $index) {\n @if ($any(opt).value !== undefined) {\n <option [value]=\"$any(opt).value\" [selected]=\"$any(opt).value === field.rawValue\">\n {{ $any(opt).label }}\n </option>\n } @else {\n <option [value]=\"opt\" [selected]=\"opt === field.rawValue\">{{ opt }}</option>\n }\n }\n </select>\n } @else if (field.editable) {\n <input\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n [type]=\"field.col.type === 'number' ? 'number' : field.col.type === 'date' ? 'date' : 'text'\"\n [value]=\"field.inputValue\"\n (input)=\"onDetailMaskInput(field, $event)\"\n (change)=\"onDetailChange(field, $event)\"\n />\n } @else {\n <span class=\"ag-detail-value\">{{ field.value }}</span>\n }\n @if (errors()[field.col.field]; as msg) {\n <span class=\"ag-detail-error\" role=\"alert\">{{ msg }}</span>\n }\n </div>\n }\n @if (useSidebarEditor()) {\n <div class=\"ag-detail-field\">\n <button class=\"ag-button\" (click)=\"save.emit(fields)\">{{ localeText().save }}</button>\n </div>\n }\n } @else {\n <div class=\"ag-detail-empty\">{{ localeText().noRows }}</div>\n }\n }\n </div>\n}\n\n<div class=\"ag-sidebar-strip\">\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'columns'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'columns'\"\n (click)=\"tabChange.emit('columns')\"\n >{{ localeText().columns }}</button>\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'detail'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'detail'\"\n (click)=\"tabChange.emit('detail')\"\n >{{ localeText().detail }}</button>\n</div>\n", styles: [":host{display:flex;flex-shrink:0;min-height:0}.ag-sidebar{width:200px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);display:flex;flex-direction:column;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-sidebar::-webkit-scrollbar{width:8px;height:8px}.ag-sidebar::-webkit-scrollbar-track{background:transparent}.ag-sidebar::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-sidebar::-webkit-scrollbar-thumb:hover{background:#00000052}.ag-sidebar-strip{width:24px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);display:flex;flex-direction:column}.ag-sidebar-strip-btn{background:none;border:none;border-bottom:1px solid var(--agrid-color-border);cursor:pointer;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none;writing-mode:vertical-rl;transform:rotate(180deg);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;padding:10px 5px;text-align:center}.ag-sidebar-strip-btn:hover{background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-sidebar-strip-btn.active{color:var(--agrid-color-text);background:var(--agrid-color-bg);border-left:2px solid var(--agrid-color-accent, #4f8ef7)}.ag-sidebar-header{display:flex;align-items:center;justify-content:space-between;padding:0 8px 0 12px;height:32px;flex-shrink:0;border-bottom:1px solid var(--agrid-color-border);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg-muted)}.ag-sidebar-close{background:none;border:none;cursor:pointer;color:var(--agrid-color-text-muted);font-size:11px;padding:2px 4px;border-radius:3px;line-height:1}.ag-sidebar-close:hover,.ag-sidebar-item:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-sidebar-item{display:flex;align-items:center;gap:8px;padding:5px 12px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}.ag-sidebar-item input[type=checkbox]{cursor:pointer;margin:0;flex-shrink:0}.ag-sidebar-group-label{font-weight:600}.ag-sidebar-group-children{position:relative}.ag-sidebar-group-children:before{content:\"\";position:absolute;top:0;bottom:8px;left:19px;border-left:1px solid var(--agrid-color-border)}.ag-sidebar-group-child{position:relative;padding-left:30px}.ag-sidebar-group-child:before{content:\"\";position:absolute;left:19px;width:7px;border-top:1px solid var(--agrid-color-border)}.ag-detail-field{display:flex;flex-direction:column;gap:2px;padding:7px 12px;border-bottom:1px solid var(--agrid-color-border);font-size:13px}.ag-detail-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted)}.ag-detail-hidden{font-weight:400;text-transform:none;letter-spacing:0;opacity:.6}.ag-detail-value{color:var(--agrid-color-text);word-break:break-word}.ag-detail-empty{padding:16px 12px;font-size:13px;color:var(--agrid-color-text-muted);text-align:center}.ag-detail-input{width:100%;box-sizing:border-box;font-size:13px;font-family:inherit;color:var(--agrid-color-text);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:3px;padding:3px 6px;outline:none}.ag-detail-input:focus{border-color:var(--agrid-color-accent, #4f8ef7);box-shadow:0 0 0 2px color-mix(in srgb,var(--agrid-color-accent, #4f8ef7) 20%,transparent)}.ag-button{display:flex;align-items:center;justify-content:center;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text);cursor:pointer;font-size:13px;padding:0;line-height:1}.ag-detail-input--invalid{border-color:var(--agrid-color-danger);outline:1px solid var(--agrid-color-danger)}.ag-detail-error{display:block;margin-top:3px;font-size:12px;line-height:1.4;color:var(--agrid-color-danger)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5916
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridSidebarComponent, isStandalone: true, selector: "agrid-sidebar", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, pivotColumns: { classPropertyName: "pivotColumns", publicName: "pivotColumns", isSignal: true, isRequired: false, transformFunction: null }, pivotConfig: { classPropertyName: "pivotConfig", publicName: "pivotConfig", isSignal: true, isRequired: false, transformFunction: null }, headerGroups: { classPropertyName: "headerGroups", publicName: "headerGroups", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: false, transformFunction: null }, hiddenColumns: { classPropertyName: "hiddenColumns", publicName: "hiddenColumns", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, localeText: { classPropertyName: "localeText", publicName: "localeText", isSignal: true, isRequired: false, transformFunction: null }, readonlyGrid: { classPropertyName: "readonlyGrid", publicName: "readonlyGrid", isSignal: true, isRequired: false, transformFunction: null }, useSidebarEditor: { classPropertyName: "useSidebarEditor", publicName: "useSidebarEditor", isSignal: true, isRequired: false, transformFunction: null }, isCellEditable: { classPropertyName: "isCellEditable", publicName: "isCellEditable", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", tabChange: "tabChange", toggleColumn: "toggleColumn", toggleColumnGroup: "toggleColumnGroup", detailEdit: "detailEdit", save: "save", pivotChange: "pivotChange" }, ngImport: i0, template: "@if (open()) {\n <div class=\"ag-sidebar\" role=\"region\"\n [attr.aria-label]=\"activeTabLabel()\">\n <div class=\"ag-sidebar-header\">\n <span>{{ activeTabLabel() }}</span>\n <button class=\"ag-sidebar-close\" (click)=\"close.emit()\" [title]=\"localeText().close\"\n [attr.aria-label]=\"localeText().close\">\u2715</button>\n </div>\n\n @if (activeTab() === 'columns') {\n @for (entry of columnEntries(); track entry.kind === 'group' ? 'group-' + entry.id : 'column-' + entry.col.field) {\n @if (entry.kind === 'group') {\n <div class=\"ag-sidebar-group\">\n <label class=\"ag-sidebar-item ag-sidebar-group-label\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupVisible(entry.columns)\"\n [indeterminate]=\"isGroupPartiallyVisible(entry.columns)\"\n (change)=\"onGroupToggle(entry.columns, $event)\"\n />\n {{ entry.label }}\n </label>\n <div class=\"ag-sidebar-group-children\">\n @for (col of entry.columns; track col.field) {\n <label class=\"ag-sidebar-item ag-sidebar-group-child\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(col.field)\"\n (change)=\"toggleColumn.emit(col.field)\"\n />\n {{ col.header }}\n </label>\n }\n </div>\n </div>\n } @else {\n <label class=\"ag-sidebar-item\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(entry.col.field)\"\n (change)=\"toggleColumn.emit(entry.col.field)\"\n />\n {{ entry.col.header }}\n </label>\n }\n }\n } @else if (activeTab() === 'pivot' && pivotConfig(); as pivot) {\n <div class=\"ag-pivot-config\">\n <p class=\"ag-pivot-description\">{{ localeText().pivotDescription }}</p>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().pivotRows }}</span>\n <select [value]=\"pivot.rowField\"\n (change)=\"onPivotFieldChange('rowField', $event)\">\n @for (col of pivotColumns(); track col.field) {\n <option [value]=\"col.field\" [selected]=\"col.field === pivot.rowField\">{{ col.header }}</option>\n }\n </select>\n </label>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().pivotColumns }}</span>\n <select [value]=\"pivot.columnField\"\n (change)=\"onPivotFieldChange('columnField', $event)\">\n @for (col of pivotColumns(); track col.field) {\n <option [value]=\"col.field\" [selected]=\"col.field === pivot.columnField\">{{ col.header }}</option>\n }\n </select>\n </label>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().pivotValues }}</span>\n <select [value]=\"pivot.valueField\"\n (change)=\"onPivotFieldChange('valueField', $event)\">\n @for (col of pivotColumns(); track col.field) {\n <option [value]=\"col.field\" [selected]=\"col.field === pivot.valueField\">{{ col.header }}</option>\n }\n </select>\n </label>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().aggregate }}</span>\n <select [value]=\"pivotAggregate()\" (change)=\"onPivotAggregateChange($event)\">\n <option value=\"sum\" [selected]=\"pivotAggregate() === 'sum'\">{{ localeText().aggregateSum }}</option>\n <option value=\"avg\" [selected]=\"pivotAggregate() === 'avg'\">{{ localeText().aggregateAvg }}</option>\n <option value=\"min\" [selected]=\"pivotAggregate() === 'min'\">{{ localeText().aggregateMin }}</option>\n <option value=\"max\" [selected]=\"pivotAggregate() === 'max'\">{{ localeText().aggregateMax }}</option>\n <option value=\"count\" [selected]=\"pivotAggregate() === 'count'\">{{ localeText().aggregateCount }}</option>\n @if (pivotAggregate() === 'custom') {\n <option value=\"custom\" disabled selected>{{ localeText().aggregateCustom }}</option>\n }\n </select>\n </label>\n\n <div class=\"ag-pivot-columns-heading\">{{ localeText().columns }}</div>\n <div class=\"ag-pivot-column-list\">\n @for (entry of columnEntries(); track entry.kind === 'group' ? 'pivot-group-' + entry.id : 'pivot-column-' + entry.col.field) {\n @if (entry.kind === 'group') {\n <div class=\"ag-sidebar-group\">\n <label class=\"ag-sidebar-item ag-sidebar-group-label\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\"\n [checked]=\"isGroupVisible(entry.columns)\"\n [indeterminate]=\"isGroupPartiallyVisible(entry.columns)\"\n (change)=\"onGroupToggle(entry.columns, $event)\" />\n {{ entry.label }}\n </label>\n <div class=\"ag-sidebar-group-children\">\n @for (col of entry.columns; track col.field) {\n <label class=\"ag-sidebar-item ag-sidebar-group-child\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(col.field)\"\n (change)=\"toggleColumn.emit(col.field)\" />\n {{ col.header }}\n </label>\n }\n </div>\n </div>\n } @else {\n <label class=\"ag-sidebar-item\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(entry.col.field)\"\n (change)=\"toggleColumn.emit(entry.col.field)\" />\n {{ entry.col.header }}\n </label>\n }\n }\n </div>\n </div>\n } @else {\n @if (detailFields(); as fields) {\n @for (field of fields; track field.col.field) {\n <div class=\"ag-detail-field\">\n <span class=\"ag-detail-label\">\n {{ field.label }}\n @if (field.hidden) {\n <span class=\"ag-detail-hidden\">{{ localeText().hiddenColumn }}</span>\n }\n </span>\n @if (field.editable && field.col.values?.length) {\n <select\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n (change)=\"detailEdit.emit({\n field: field.col.field,\n col: field.col,\n value: $any($event.target).value\n })\"\n >\n @for (opt of field.col.values; track $index) {\n @if ($any(opt).value !== undefined) {\n <option [value]=\"$any(opt).value\" [selected]=\"$any(opt).value === field.rawValue\">\n {{ $any(opt).label }}\n </option>\n } @else {\n <option [value]=\"opt\" [selected]=\"opt === field.rawValue\">{{ opt }}</option>\n }\n }\n </select>\n } @else if (field.editable) {\n <input\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n [type]=\"field.col.type === 'date' ? 'date' : 'text'\"\n [attr.inputmode]=\"field.col.type === 'number' ? 'decimal' : null\"\n [value]=\"field.inputValue\"\n (input)=\"onDetailMaskInput(field, $event)\"\n (change)=\"onDetailChange(field, $event)\"\n />\n } @else {\n <span class=\"ag-detail-value\">{{ field.value }}</span>\n }\n @if (errors()[field.col.field]; as msg) {\n <span class=\"ag-detail-error\" role=\"alert\">{{ msg }}</span>\n }\n </div>\n }\n @if (useSidebarEditor()) {\n <div class=\"ag-detail-field\">\n <button class=\"ag-button\" (click)=\"save.emit(fields)\">{{ localeText().save }}</button>\n </div>\n }\n } @else {\n <div class=\"ag-detail-empty\">{{ localeText().noRows }}</div>\n }\n }\n </div>\n}\n\n<div class=\"ag-sidebar-strip\">\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'columns'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'columns'\"\n (click)=\"tabChange.emit('columns')\"\n >{{ localeText().columns }}</button>\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'detail'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'detail'\"\n (click)=\"tabChange.emit('detail')\"\n >{{ localeText().detail }}</button>\n @if (pivotConfig()) {\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'pivot'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'pivot'\"\n (click)=\"tabChange.emit('pivot')\"\n >{{ localeText().pivot }}</button>\n }\n</div>\n", styles: [":host{display:flex;flex-shrink:0;min-height:0}.ag-sidebar{width:200px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);display:flex;flex-direction:column;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-sidebar::-webkit-scrollbar{width:8px;height:8px}.ag-sidebar::-webkit-scrollbar-track{background:transparent}.ag-sidebar::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-sidebar::-webkit-scrollbar-thumb:hover{background:#00000052}.ag-sidebar-strip{width:24px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);display:flex;flex-direction:column}.ag-sidebar-strip-btn{background:none;border:none;border-bottom:1px solid var(--agrid-color-border);cursor:pointer;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none;writing-mode:vertical-rl;transform:rotate(180deg);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;padding:10px 5px;text-align:center}.ag-sidebar-strip-btn:hover{background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-sidebar-strip-btn.active{color:var(--agrid-color-text);background:var(--agrid-color-bg);border-left:2px solid var(--agrid-color-accent, #4f8ef7)}.ag-sidebar-header{display:flex;align-items:center;justify-content:space-between;padding:0 8px 0 12px;height:32px;flex-shrink:0;border-bottom:1px solid var(--agrid-color-border);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg-muted)}.ag-sidebar-close{background:none;border:none;cursor:pointer;color:var(--agrid-color-text-muted);font-size:11px;padding:2px 4px;border-radius:3px;line-height:1}.ag-sidebar-close:hover,.ag-sidebar-item:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-sidebar-item{display:flex;align-items:center;gap:8px;padding:5px 12px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}.ag-sidebar-item input[type=checkbox]{cursor:pointer;margin:0;flex-shrink:0}.ag-sidebar-group-label{font-weight:600}.ag-sidebar-group-children{position:relative}.ag-sidebar-group-children:before{content:\"\";position:absolute;top:0;bottom:8px;left:19px;border-left:1px solid var(--agrid-color-border)}.ag-sidebar-group-child{position:relative;padding-left:30px}.ag-sidebar-group-child:before{content:\"\";position:absolute;left:19px;width:7px;border-top:1px solid var(--agrid-color-border)}.ag-detail-field{display:flex;flex-direction:column;gap:2px;padding:7px 12px;border-bottom:1px solid var(--agrid-color-border);font-size:13px}.ag-detail-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted)}.ag-detail-hidden{font-weight:400;text-transform:none;letter-spacing:0;opacity:.6}.ag-detail-value{color:var(--agrid-color-text);word-break:break-word}.ag-detail-empty{padding:16px 12px;font-size:13px;color:var(--agrid-color-text-muted);text-align:center}.ag-detail-input{width:100%;box-sizing:border-box;font-size:13px;font-family:inherit;color:var(--agrid-color-text);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:3px;padding:3px 6px;outline:none}.ag-detail-input:focus{border-color:var(--agrid-color-accent, #4f8ef7);box-shadow:0 0 0 2px color-mix(in srgb,var(--agrid-color-accent, #4f8ef7) 20%,transparent)}.ag-button{display:flex;align-items:center;justify-content:center;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text);cursor:pointer;font-size:13px;padding:0;line-height:1}.ag-detail-input--invalid{border-color:var(--agrid-color-danger);outline:1px solid var(--agrid-color-danger)}.ag-detail-error{display:block;margin-top:3px;font-size:12px;line-height:1.4;color:var(--agrid-color-danger)}.ag-pivot-config{display:grid;gap:12px;padding:12px}.ag-pivot-description{margin:0 0 2px;color:var(--agrid-color-text-muted);font-size:12px;line-height:1.45}.ag-pivot-field{display:grid;gap:5px}.ag-pivot-field>span{color:var(--agrid-color-text-muted);font-size:10px;font-weight:650;letter-spacing:.4px;text-transform:uppercase}.ag-pivot-field select{width:100%;height:30px;box-sizing:border-box;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text);padding:0 7px;font:inherit;font-size:12px}.ag-pivot-field select:focus-visible{outline:2px solid var(--agrid-color-accent);outline-offset:2px}.ag-pivot-columns-heading{margin:2px -12px 0;padding:10px 12px 6px;border-top:1px solid var(--agrid-color-border);color:var(--agrid-color-text-muted);font-size:10px;font-weight:650;letter-spacing:.4px;text-transform:uppercase}.ag-pivot-column-list{margin:-6px -12px -12px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5249
5917
  }
5250
5918
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridSidebarComponent, decorators: [{
5251
5919
  type: Component,
5252
- args: [{ selector: 'agrid-sidebar', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open()) {\n <div class=\"ag-sidebar\" role=\"region\"\n [attr.aria-label]=\"activeTab() === 'columns' ? localeText().columns : localeText().detail\">\n <div class=\"ag-sidebar-header\">\n <span>{{ activeTab() === 'columns' ? localeText().columns : localeText().detail }}</span>\n <button class=\"ag-sidebar-close\" (click)=\"close.emit()\" [title]=\"localeText().close\"\n [attr.aria-label]=\"localeText().close\">\u2715</button>\n </div>\n\n @if (activeTab() === 'columns') {\n @for (entry of columnEntries(); track entry.kind === 'group' ? 'group-' + entry.id : 'column-' + entry.col.field) {\n @if (entry.kind === 'group') {\n <div class=\"ag-sidebar-group\">\n <label class=\"ag-sidebar-item ag-sidebar-group-label\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupVisible(entry.columns)\"\n [indeterminate]=\"isGroupPartiallyVisible(entry.columns)\"\n (change)=\"onGroupToggle(entry.columns, $event)\"\n />\n {{ entry.label }}\n </label>\n <div class=\"ag-sidebar-group-children\">\n @for (col of entry.columns; track col.field) {\n <label class=\"ag-sidebar-item ag-sidebar-group-child\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(col.field)\"\n (change)=\"toggleColumn.emit(col.field)\"\n />\n {{ col.header }}\n </label>\n }\n </div>\n </div>\n } @else {\n <label class=\"ag-sidebar-item\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(entry.col.field)\"\n (change)=\"toggleColumn.emit(entry.col.field)\"\n />\n {{ entry.col.header }}\n </label>\n }\n }\n } @else {\n @if (detailFields(); as fields) {\n @for (field of fields; track field.col.field) {\n <div class=\"ag-detail-field\">\n <span class=\"ag-detail-label\">\n {{ field.label }}\n @if (field.hidden) {\n <span class=\"ag-detail-hidden\">{{ localeText().hiddenColumn }}</span>\n }\n </span>\n @if (field.editable && field.col.values?.length) {\n <select\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n (change)=\"detailEdit.emit({\n field: field.col.field,\n col: field.col,\n value: $any($event.target).value\n })\"\n >\n @for (opt of field.col.values; track $index) {\n @if ($any(opt).value !== undefined) {\n <option [value]=\"$any(opt).value\" [selected]=\"$any(opt).value === field.rawValue\">\n {{ $any(opt).label }}\n </option>\n } @else {\n <option [value]=\"opt\" [selected]=\"opt === field.rawValue\">{{ opt }}</option>\n }\n }\n </select>\n } @else if (field.editable) {\n <input\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n [type]=\"field.col.type === 'number' ? 'number' : field.col.type === 'date' ? 'date' : 'text'\"\n [value]=\"field.inputValue\"\n (input)=\"onDetailMaskInput(field, $event)\"\n (change)=\"onDetailChange(field, $event)\"\n />\n } @else {\n <span class=\"ag-detail-value\">{{ field.value }}</span>\n }\n @if (errors()[field.col.field]; as msg) {\n <span class=\"ag-detail-error\" role=\"alert\">{{ msg }}</span>\n }\n </div>\n }\n @if (useSidebarEditor()) {\n <div class=\"ag-detail-field\">\n <button class=\"ag-button\" (click)=\"save.emit(fields)\">{{ localeText().save }}</button>\n </div>\n }\n } @else {\n <div class=\"ag-detail-empty\">{{ localeText().noRows }}</div>\n }\n }\n </div>\n}\n\n<div class=\"ag-sidebar-strip\">\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'columns'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'columns'\"\n (click)=\"tabChange.emit('columns')\"\n >{{ localeText().columns }}</button>\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'detail'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'detail'\"\n (click)=\"tabChange.emit('detail')\"\n >{{ localeText().detail }}</button>\n</div>\n", styles: [":host{display:flex;flex-shrink:0;min-height:0}.ag-sidebar{width:200px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);display:flex;flex-direction:column;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-sidebar::-webkit-scrollbar{width:8px;height:8px}.ag-sidebar::-webkit-scrollbar-track{background:transparent}.ag-sidebar::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-sidebar::-webkit-scrollbar-thumb:hover{background:#00000052}.ag-sidebar-strip{width:24px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);display:flex;flex-direction:column}.ag-sidebar-strip-btn{background:none;border:none;border-bottom:1px solid var(--agrid-color-border);cursor:pointer;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none;writing-mode:vertical-rl;transform:rotate(180deg);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;padding:10px 5px;text-align:center}.ag-sidebar-strip-btn:hover{background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-sidebar-strip-btn.active{color:var(--agrid-color-text);background:var(--agrid-color-bg);border-left:2px solid var(--agrid-color-accent, #4f8ef7)}.ag-sidebar-header{display:flex;align-items:center;justify-content:space-between;padding:0 8px 0 12px;height:32px;flex-shrink:0;border-bottom:1px solid var(--agrid-color-border);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg-muted)}.ag-sidebar-close{background:none;border:none;cursor:pointer;color:var(--agrid-color-text-muted);font-size:11px;padding:2px 4px;border-radius:3px;line-height:1}.ag-sidebar-close:hover,.ag-sidebar-item:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-sidebar-item{display:flex;align-items:center;gap:8px;padding:5px 12px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}.ag-sidebar-item input[type=checkbox]{cursor:pointer;margin:0;flex-shrink:0}.ag-sidebar-group-label{font-weight:600}.ag-sidebar-group-children{position:relative}.ag-sidebar-group-children:before{content:\"\";position:absolute;top:0;bottom:8px;left:19px;border-left:1px solid var(--agrid-color-border)}.ag-sidebar-group-child{position:relative;padding-left:30px}.ag-sidebar-group-child:before{content:\"\";position:absolute;left:19px;width:7px;border-top:1px solid var(--agrid-color-border)}.ag-detail-field{display:flex;flex-direction:column;gap:2px;padding:7px 12px;border-bottom:1px solid var(--agrid-color-border);font-size:13px}.ag-detail-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted)}.ag-detail-hidden{font-weight:400;text-transform:none;letter-spacing:0;opacity:.6}.ag-detail-value{color:var(--agrid-color-text);word-break:break-word}.ag-detail-empty{padding:16px 12px;font-size:13px;color:var(--agrid-color-text-muted);text-align:center}.ag-detail-input{width:100%;box-sizing:border-box;font-size:13px;font-family:inherit;color:var(--agrid-color-text);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:3px;padding:3px 6px;outline:none}.ag-detail-input:focus{border-color:var(--agrid-color-accent, #4f8ef7);box-shadow:0 0 0 2px color-mix(in srgb,var(--agrid-color-accent, #4f8ef7) 20%,transparent)}.ag-button{display:flex;align-items:center;justify-content:center;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text);cursor:pointer;font-size:13px;padding:0;line-height:1}.ag-detail-input--invalid{border-color:var(--agrid-color-danger);outline:1px solid var(--agrid-color-danger)}.ag-detail-error{display:block;margin-top:3px;font-size:12px;line-height:1.4;color:var(--agrid-color-danger)}\n"] }]
5253
- }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], headerGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerGroups", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], rowIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIndex", required: false }] }], hiddenColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenColumns", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], localeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeText", required: false }] }], readonlyGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonlyGrid", required: false }] }], useSidebarEditor: [{ type: i0.Input, args: [{ isSignal: true, alias: "useSidebarEditor", required: false }] }], isCellEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCellEditable", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], toggleColumn: [{ type: i0.Output, args: ["toggleColumn"] }], toggleColumnGroup: [{ type: i0.Output, args: ["toggleColumnGroup"] }], detailEdit: [{ type: i0.Output, args: ["detailEdit"] }], save: [{ type: i0.Output, args: ["save"] }] } });
5920
+ args: [{ selector: 'agrid-sidebar', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open()) {\n <div class=\"ag-sidebar\" role=\"region\"\n [attr.aria-label]=\"activeTabLabel()\">\n <div class=\"ag-sidebar-header\">\n <span>{{ activeTabLabel() }}</span>\n <button class=\"ag-sidebar-close\" (click)=\"close.emit()\" [title]=\"localeText().close\"\n [attr.aria-label]=\"localeText().close\">\u2715</button>\n </div>\n\n @if (activeTab() === 'columns') {\n @for (entry of columnEntries(); track entry.kind === 'group' ? 'group-' + entry.id : 'column-' + entry.col.field) {\n @if (entry.kind === 'group') {\n <div class=\"ag-sidebar-group\">\n <label class=\"ag-sidebar-item ag-sidebar-group-label\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupVisible(entry.columns)\"\n [indeterminate]=\"isGroupPartiallyVisible(entry.columns)\"\n (change)=\"onGroupToggle(entry.columns, $event)\"\n />\n {{ entry.label }}\n </label>\n <div class=\"ag-sidebar-group-children\">\n @for (col of entry.columns; track col.field) {\n <label class=\"ag-sidebar-item ag-sidebar-group-child\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(col.field)\"\n (change)=\"toggleColumn.emit(col.field)\"\n />\n {{ col.header }}\n </label>\n }\n </div>\n </div>\n } @else {\n <label class=\"ag-sidebar-item\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(entry.col.field)\"\n (change)=\"toggleColumn.emit(entry.col.field)\"\n />\n {{ entry.col.header }}\n </label>\n }\n }\n } @else if (activeTab() === 'pivot' && pivotConfig(); as pivot) {\n <div class=\"ag-pivot-config\">\n <p class=\"ag-pivot-description\">{{ localeText().pivotDescription }}</p>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().pivotRows }}</span>\n <select [value]=\"pivot.rowField\"\n (change)=\"onPivotFieldChange('rowField', $event)\">\n @for (col of pivotColumns(); track col.field) {\n <option [value]=\"col.field\" [selected]=\"col.field === pivot.rowField\">{{ col.header }}</option>\n }\n </select>\n </label>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().pivotColumns }}</span>\n <select [value]=\"pivot.columnField\"\n (change)=\"onPivotFieldChange('columnField', $event)\">\n @for (col of pivotColumns(); track col.field) {\n <option [value]=\"col.field\" [selected]=\"col.field === pivot.columnField\">{{ col.header }}</option>\n }\n </select>\n </label>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().pivotValues }}</span>\n <select [value]=\"pivot.valueField\"\n (change)=\"onPivotFieldChange('valueField', $event)\">\n @for (col of pivotColumns(); track col.field) {\n <option [value]=\"col.field\" [selected]=\"col.field === pivot.valueField\">{{ col.header }}</option>\n }\n </select>\n </label>\n\n <label class=\"ag-pivot-field\">\n <span>{{ localeText().aggregate }}</span>\n <select [value]=\"pivotAggregate()\" (change)=\"onPivotAggregateChange($event)\">\n <option value=\"sum\" [selected]=\"pivotAggregate() === 'sum'\">{{ localeText().aggregateSum }}</option>\n <option value=\"avg\" [selected]=\"pivotAggregate() === 'avg'\">{{ localeText().aggregateAvg }}</option>\n <option value=\"min\" [selected]=\"pivotAggregate() === 'min'\">{{ localeText().aggregateMin }}</option>\n <option value=\"max\" [selected]=\"pivotAggregate() === 'max'\">{{ localeText().aggregateMax }}</option>\n <option value=\"count\" [selected]=\"pivotAggregate() === 'count'\">{{ localeText().aggregateCount }}</option>\n @if (pivotAggregate() === 'custom') {\n <option value=\"custom\" disabled selected>{{ localeText().aggregateCustom }}</option>\n }\n </select>\n </label>\n\n <div class=\"ag-pivot-columns-heading\">{{ localeText().columns }}</div>\n <div class=\"ag-pivot-column-list\">\n @for (entry of columnEntries(); track entry.kind === 'group' ? 'pivot-group-' + entry.id : 'pivot-column-' + entry.col.field) {\n @if (entry.kind === 'group') {\n <div class=\"ag-sidebar-group\">\n <label class=\"ag-sidebar-item ag-sidebar-group-label\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\"\n [checked]=\"isGroupVisible(entry.columns)\"\n [indeterminate]=\"isGroupPartiallyVisible(entry.columns)\"\n (change)=\"onGroupToggle(entry.columns, $event)\" />\n {{ entry.label }}\n </label>\n <div class=\"ag-sidebar-group-children\">\n @for (col of entry.columns; track col.field) {\n <label class=\"ag-sidebar-item ag-sidebar-group-child\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(col.field)\"\n (change)=\"toggleColumn.emit(col.field)\" />\n {{ col.header }}\n </label>\n }\n </div>\n </div>\n } @else {\n <label class=\"ag-sidebar-item\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\"\n [checked]=\"!hiddenColumns().has(entry.col.field)\"\n (change)=\"toggleColumn.emit(entry.col.field)\" />\n {{ entry.col.header }}\n </label>\n }\n }\n </div>\n </div>\n } @else {\n @if (detailFields(); as fields) {\n @for (field of fields; track field.col.field) {\n <div class=\"ag-detail-field\">\n <span class=\"ag-detail-label\">\n {{ field.label }}\n @if (field.hidden) {\n <span class=\"ag-detail-hidden\">{{ localeText().hiddenColumn }}</span>\n }\n </span>\n @if (field.editable && field.col.values?.length) {\n <select\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n (change)=\"detailEdit.emit({\n field: field.col.field,\n col: field.col,\n value: $any($event.target).value\n })\"\n >\n @for (opt of field.col.values; track $index) {\n @if ($any(opt).value !== undefined) {\n <option [value]=\"$any(opt).value\" [selected]=\"$any(opt).value === field.rawValue\">\n {{ $any(opt).label }}\n </option>\n } @else {\n <option [value]=\"opt\" [selected]=\"opt === field.rawValue\">{{ opt }}</option>\n }\n }\n </select>\n } @else if (field.editable) {\n <input\n class=\"ag-detail-input\"\n [class.ag-detail-input--invalid]=\"!!errors()[field.col.field]\"\n [type]=\"field.col.type === 'date' ? 'date' : 'text'\"\n [attr.inputmode]=\"field.col.type === 'number' ? 'decimal' : null\"\n [value]=\"field.inputValue\"\n (input)=\"onDetailMaskInput(field, $event)\"\n (change)=\"onDetailChange(field, $event)\"\n />\n } @else {\n <span class=\"ag-detail-value\">{{ field.value }}</span>\n }\n @if (errors()[field.col.field]; as msg) {\n <span class=\"ag-detail-error\" role=\"alert\">{{ msg }}</span>\n }\n </div>\n }\n @if (useSidebarEditor()) {\n <div class=\"ag-detail-field\">\n <button class=\"ag-button\" (click)=\"save.emit(fields)\">{{ localeText().save }}</button>\n </div>\n }\n } @else {\n <div class=\"ag-detail-empty\">{{ localeText().noRows }}</div>\n }\n }\n </div>\n}\n\n<div class=\"ag-sidebar-strip\">\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'columns'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'columns'\"\n (click)=\"tabChange.emit('columns')\"\n >{{ localeText().columns }}</button>\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'detail'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'detail'\"\n (click)=\"tabChange.emit('detail')\"\n >{{ localeText().detail }}</button>\n @if (pivotConfig()) {\n <button\n class=\"ag-sidebar-strip-btn\"\n [class.active]=\"open() && activeTab() === 'pivot'\"\n [attr.aria-pressed]=\"open() && activeTab() === 'pivot'\"\n (click)=\"tabChange.emit('pivot')\"\n >{{ localeText().pivot }}</button>\n }\n</div>\n", styles: [":host{display:flex;flex-shrink:0;min-height:0}.ag-sidebar{width:200px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);display:flex;flex-direction:column;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-sidebar::-webkit-scrollbar{width:8px;height:8px}.ag-sidebar::-webkit-scrollbar-track{background:transparent}.ag-sidebar::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-sidebar::-webkit-scrollbar-thumb:hover{background:#00000052}.ag-sidebar-strip{width:24px;flex-shrink:0;border-left:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);display:flex;flex-direction:column}.ag-sidebar-strip-btn{background:none;border:none;border-bottom:1px solid var(--agrid-color-border);cursor:pointer;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none;writing-mode:vertical-rl;transform:rotate(180deg);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;padding:10px 5px;text-align:center}.ag-sidebar-strip-btn:hover{background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-sidebar-strip-btn.active{color:var(--agrid-color-text);background:var(--agrid-color-bg);border-left:2px solid var(--agrid-color-accent, #4f8ef7)}.ag-sidebar-header{display:flex;align-items:center;justify-content:space-between;padding:0 8px 0 12px;height:32px;flex-shrink:0;border-bottom:1px solid var(--agrid-color-border);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg-muted)}.ag-sidebar-close{background:none;border:none;cursor:pointer;color:var(--agrid-color-text-muted);font-size:11px;padding:2px 4px;border-radius:3px;line-height:1}.ag-sidebar-close:hover,.ag-sidebar-item:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-sidebar-item{display:flex;align-items:center;gap:8px;padding:5px 12px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}.ag-sidebar-item input[type=checkbox]{cursor:pointer;margin:0;flex-shrink:0}.ag-sidebar-group-label{font-weight:600}.ag-sidebar-group-children{position:relative}.ag-sidebar-group-children:before{content:\"\";position:absolute;top:0;bottom:8px;left:19px;border-left:1px solid var(--agrid-color-border)}.ag-sidebar-group-child{position:relative;padding-left:30px}.ag-sidebar-group-child:before{content:\"\";position:absolute;left:19px;width:7px;border-top:1px solid var(--agrid-color-border)}.ag-detail-field{display:flex;flex-direction:column;gap:2px;padding:7px 12px;border-bottom:1px solid var(--agrid-color-border);font-size:13px}.ag-detail-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted)}.ag-detail-hidden{font-weight:400;text-transform:none;letter-spacing:0;opacity:.6}.ag-detail-value{color:var(--agrid-color-text);word-break:break-word}.ag-detail-empty{padding:16px 12px;font-size:13px;color:var(--agrid-color-text-muted);text-align:center}.ag-detail-input{width:100%;box-sizing:border-box;font-size:13px;font-family:inherit;color:var(--agrid-color-text);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:3px;padding:3px 6px;outline:none}.ag-detail-input:focus{border-color:var(--agrid-color-accent, #4f8ef7);box-shadow:0 0 0 2px color-mix(in srgb,var(--agrid-color-accent, #4f8ef7) 20%,transparent)}.ag-button{display:flex;align-items:center;justify-content:center;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text);cursor:pointer;font-size:13px;padding:0;line-height:1}.ag-detail-input--invalid{border-color:var(--agrid-color-danger);outline:1px solid var(--agrid-color-danger)}.ag-detail-error{display:block;margin-top:3px;font-size:12px;line-height:1.4;color:var(--agrid-color-danger)}.ag-pivot-config{display:grid;gap:12px;padding:12px}.ag-pivot-description{margin:0 0 2px;color:var(--agrid-color-text-muted);font-size:12px;line-height:1.45}.ag-pivot-field{display:grid;gap:5px}.ag-pivot-field>span{color:var(--agrid-color-text-muted);font-size:10px;font-weight:650;letter-spacing:.4px;text-transform:uppercase}.ag-pivot-field select{width:100%;height:30px;box-sizing:border-box;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text);padding:0 7px;font:inherit;font-size:12px}.ag-pivot-field select:focus-visible{outline:2px solid var(--agrid-color-accent);outline-offset:2px}.ag-pivot-columns-heading{margin:2px -12px 0;padding:10px 12px 6px;border-top:1px solid var(--agrid-color-border);color:var(--agrid-color-text-muted);font-size:10px;font-weight:650;letter-spacing:.4px;text-transform:uppercase}.ag-pivot-column-list{margin:-6px -12px -12px}\n"] }]
5921
+ }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], pivotColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "pivotColumns", required: false }] }], pivotConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pivotConfig", required: false }] }], headerGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerGroups", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], rowIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIndex", required: false }] }], hiddenColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenColumns", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], localeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeText", required: false }] }], readonlyGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonlyGrid", required: false }] }], useSidebarEditor: [{ type: i0.Input, args: [{ isSignal: true, alias: "useSidebarEditor", required: false }] }], isCellEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCellEditable", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], toggleColumn: [{ type: i0.Output, args: ["toggleColumn"] }], toggleColumnGroup: [{ type: i0.Output, args: ["toggleColumnGroup"] }], detailEdit: [{ type: i0.Output, args: ["detailEdit"] }], save: [{ type: i0.Output, args: ["save"] }], pivotChange: [{ type: i0.Output, args: ["pivotChange"] }] } });
5254
5922
 
5255
5923
  /** Pixels rendered above and below the viewport so scrolling does not reveal blank rows. */
5256
5924
  const BUFFER_PX = 200;
@@ -5406,6 +6074,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
5406
6074
  * | Key | Action |
5407
6075
  * |-----|--------|
5408
6076
  * | Arrow keys | Move selection |
6077
+ * | Page Up / Page Down | Move by one visible viewport page |
6078
+ * | Home / End | Move to the first / last cell in the current row |
6079
+ * | Ctrl/Cmd+Home / Ctrl/Cmd+End | Move to the first / last cell in the grid |
5409
6080
  * | Tab / Shift+Tab | Move right / left (wraps rows) |
5410
6081
  * | Enter | Enter edit mode |
5411
6082
  * | Ctrl/Cmd+Enter | Toggle an expandable tree node |
@@ -5422,8 +6093,8 @@ class AgridComponent {
5422
6093
  rowHeight = computed(() => this.provider().rowHeight, ...(ngDevMode ? [{ debugName: "rowHeight" }] : /* istanbul ignore next */ []));
5423
6094
  minHeight = computed(() => this.provider().minHeight, ...(ngDevMode ? [{ debugName: "minHeight" }] : /* istanbul ignore next */ []));
5424
6095
  maxHeight = computed(() => this.provider().maxHeight, ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
5425
- allowAddRows = computed(() => this.provider().allowAddRows, ...(ngDevMode ? [{ debugName: "allowAddRows" }] : /* istanbul ignore next */ []));
5426
- autoAddRows = computed(() => this.provider().autoAddRows(), ...(ngDevMode ? [{ debugName: "autoAddRows" }] : /* istanbul ignore next */ []));
6096
+ allowAddRows = computed(() => this.provider().allowAddRows && !this.provider().pivotConfig, ...(ngDevMode ? [{ debugName: "allowAddRows" }] : /* istanbul ignore next */ []));
6097
+ autoAddRows = computed(() => !this.provider().pivotConfig && (this.control()?.autoAddRows() ?? false), ...(ngDevMode ? [{ debugName: "autoAddRows" }] : /* istanbul ignore next */ []));
5427
6098
  enableRowMarking = computed(() => this.provider().enableRowMarking, ...(ngDevMode ? [{ debugName: "enableRowMarking" }] : /* istanbul ignore next */ []));
5428
6099
  showControlColumn = computed(() => this.provider().showControlColumn || this.enableRowMarking() || this.masterDetail(), ...(ngDevMode ? [{ debugName: "showControlColumn" }] : /* istanbul ignore next */ []));
5429
6100
  controlColumnWidth = computed(() => this.enableRowMarking() ? 48 : 24, ...(ngDevMode ? [{ debugName: "controlColumnWidth" }] : /* istanbul ignore next */ []));
@@ -5442,17 +6113,41 @@ class AgridComponent {
5442
6113
  cellMenuItems = computed(() => this.provider().cellMenuItems, ...(ngDevMode ? [{ debugName: "cellMenuItems" }] : /* istanbul ignore next */ []));
5443
6114
  headerGroups = computed(() => this.provider().headerGroups, ...(ngDevMode ? [{ debugName: "headerGroups" }] : /* istanbul ignore next */ []));
5444
6115
  treeConfig = computed(() => this.provider().treeConfig, ...(ngDevMode ? [{ debugName: "treeConfig" }] : /* istanbul ignore next */ []));
6116
+ /** Whether the provider is rendering a derived client-side pivot table. */
6117
+ pivotMode = computed(() => !!this.provider().pivotConfig, ...(ngDevMode ? [{ debugName: "pivotMode" }] : /* istanbul ignore next */ []));
5445
6118
  zebraStripes = computed(() => this.provider().zebraStripes, ...(ngDevMode ? [{ debugName: "zebraStripes" }] : /* istanbul ignore next */ []));
5446
6119
  showChangedCellIndicator = computed(() => this.provider().showChangedCellIndicator, ...(ngDevMode ? [{ debugName: "showChangedCellIndicator" }] : /* istanbul ignore next */ []));
5447
6120
  confirmRowDelete = computed(() => this.provider().confirmRowDelete, ...(ngDevMode ? [{ debugName: "confirmRowDelete" }] : /* istanbul ignore next */ []));
5448
- readonlyGrid = computed(() => this.provider().readonlyGrid(), ...(ngDevMode ? [{ debugName: "readonlyGrid" }] : /* istanbul ignore next */ []));
5449
- loading = computed(() => this.provider().loading(), ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
6121
+ readonlyGrid = computed(() => !!this.provider().pivotConfig || (this.control()?.readonly() ?? false), ...(ngDevMode ? [{ debugName: "readonlyGrid" }] : /* istanbul ignore next */ []));
6122
+ loading = computed(() => this.control()?.loading() ?? false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
5450
6123
  emptyText = computed(() => this.provider().emptyText, ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
5451
6124
  useSidebarEditor = computed(() => this.provider().useSidebarEditor, ...(ngDevMode ? [{ debugName: "useSidebarEditor" }] : /* istanbul ignore next */ []));
5452
6125
  /** Host callback for per-row CSS classes, or `undefined`. */
5453
6126
  rowClassFn = computed(() => this.provider().getRowClass, ...(ngDevMode ? [{ debugName: "rowClassFn" }] : /* istanbul ignore next */ []));
5454
6127
  /** Host callback designating pinned rows, or `undefined`. */
5455
6128
  pinRowFn = computed(() => this.provider().pinRow, ...(ngDevMode ? [{ debugName: "pinRowFn" }] : /* istanbul ignore next */ []));
6129
+ pivotRowColumnField = computed(() => {
6130
+ return this.provider().pivotConfig?.rowField;
6131
+ }, ...(ngDevMode ? [{ debugName: "pivotRowColumnField" }] : /* istanbul ignore next */ []));
6132
+ pivotHeaderLabel = computed(() => {
6133
+ const aggr = this.provider().pivotConfig?.aggregate;
6134
+ const vcfield = this.provider().pivotConfig?.valueField;
6135
+ const valueColumn = this.provider().columns().find(c => c.field === vcfield);
6136
+ switch (aggr) {
6137
+ case "sum":
6138
+ return `${this.localeText().aggregateSum} ${valueColumn?.header ?? ''}`;
6139
+ case "avg":
6140
+ return `${this.localeText().aggregateAvg} ${valueColumn?.header ?? ''}`;
6141
+ case "count":
6142
+ return `${this.localeText().aggregateCount} ${valueColumn?.header ?? ''}`;
6143
+ case "max":
6144
+ return `${this.localeText().aggregateMax} ${valueColumn?.header ?? ''}`;
6145
+ case "min":
6146
+ return `${this.localeText().aggregateMin} ${valueColumn?.header ?? ''}`;
6147
+ default:
6148
+ return undefined;
6149
+ }
6150
+ }, ...(ngDevMode ? [{ debugName: "pivotHeaderLabel" }] : /* istanbul ignore next */ []));
5456
6151
  /**
5457
6152
  * Effective pin resolver fed to the projection: a runtime UI override wins (including an explicit
5458
6153
  * `null` unpin), otherwise the provider `pinRow` predicate decides. Returns `undefined` when
@@ -5472,13 +6167,28 @@ class AgridComponent {
5472
6167
  }, ...(ngDevMode ? [{ debugName: "effectivePinRow" }] : /* istanbul ignore next */ []));
5473
6168
  /** Whether master/detail is enabled and applicable (flat rows or tree leaves; not grouped). */
5474
6169
  masterDetail = computed(() => this.provider().masterDetail && !!this.provider().detailRenderer
5475
- && !this.control()?.groupByField(), ...(ngDevMode ? [{ debugName: "masterDetail" }] : /* istanbul ignore next */ []));
6170
+ && !this.control()?.groupByField() && !this.provider().pivotConfig, ...(ngDevMode ? [{ debugName: "masterDetail" }] : /* istanbul ignore next */ []));
5476
6171
  /** Fixed detail-panel height in pixels. */
5477
6172
  detailRowHeight = computed(() => this.provider().detailRowHeight, ...(ngDevMode ? [{ debugName: "detailRowHeight" }] : /* istanbul ignore next */ []));
5478
- /** Column definitions from the active provider. */
5479
- colDefs = computed(() => this.provider().columns(), ...(ngDevMode ? [{ debugName: "colDefs" }] : /* istanbul ignore next */ []));
5480
- /** Signal-based data container from the active provider. */
5481
- dataSource = computed(() => this.provider().datasource, ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
6173
+ /** Read-only pivot rows and generated columns, or `null` in the normal datasource view. */
6174
+ pivotResult = computed(() => {
6175
+ const provider = this.provider();
6176
+ if (!provider.pivotConfig)
6177
+ return null;
6178
+ return buildPivotResult(provider.datasource.rows(), provider.columns(), provider.pivotConfig, resolveLocale(provider.options.locale));
6179
+ }, ...(ngDevMode ? [{ debugName: "pivotResult" }] : /* istanbul ignore next */ []));
6180
+ /** Reactive row projection linked into a stable datasource instance for pivot mode. */
6181
+ pivotRows = computed(() => this.pivotResult()?.rows ?? [], ...(ngDevMode ? [{ debugName: "pivotRows" }] : /* istanbul ignore next */ []));
6182
+ pivotDataSource = new AgridDataSource();
6183
+ /** Column definitions from the active provider or generated by the active pivot. */
6184
+ colDefs = computed(() => this.pivotResult()?.columns
6185
+ ?? this.provider().columns(), ...(ngDevMode ? [{ debugName: "colDefs" }] : /* istanbul ignore next */ []));
6186
+ /** Signal-based source rows or a derived, read-only pivot datasource. */
6187
+ dataSource = computed(() => {
6188
+ return this.pivotResult() ? this.pivotDataSource : this.provider().datasource;
6189
+ }, ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
6190
+ /** Active lazy server-side row model, when configured on the provider. */
6191
+ serverSideRowModel = computed(() => this.provider().serverSideRowModel, ...(ngDevMode ? [{ debugName: "serverSideRowModel" }] : /* istanbul ignore next */ []));
5482
6192
  treeParentIds = computed(() => {
5483
6193
  const config = this.treeConfig();
5484
6194
  if (!config || isPathTreeConfig(config))
@@ -5520,6 +6230,8 @@ class AgridComponent {
5520
6230
  rowClick = output();
5521
6231
  /** Emitted when the user single-clicks a generated path-tree branch node. */
5522
6232
  treeNodeClick = output();
6233
+ /** Emitted after sidebar changes produce a new persistable grid settings snapshot. */
6234
+ settingsChange = output();
5523
6235
  /** Emitted when the user double-clicks a generated path-tree branch node. */
5524
6236
  treeNodeDoubleClicked = output();
5525
6237
  /**
@@ -5589,6 +6301,29 @@ class AgridComponent {
5589
6301
  onSidebarStripClick(tab) {
5590
6302
  this.sidebarController.selectTab(tab);
5591
6303
  }
6304
+ /** @internal Replace the active pivot configuration from the sidebar controls. */
6305
+ onSidebarPivotChange(config) {
6306
+ this.provider().pivotConfig = config;
6307
+ this.emitSettingsChange();
6308
+ }
6309
+ /** Return a detached, JSON-safe snapshot suitable for persistence by the host application. */
6310
+ saveSettings() {
6311
+ return this.provider().saveSettings();
6312
+ }
6313
+ /** Apply a saved settings snapshot to this live grid. */
6314
+ loadSettings(settings) {
6315
+ this.provider().loadSettings(settings);
6316
+ }
6317
+ /** Emit when the active state is JSON-safe; custom function aggregates remain host-owned. */
6318
+ emitSettingsChange() {
6319
+ try {
6320
+ this.settingsChange.emit(this.saveSettings());
6321
+ }
6322
+ catch (error) {
6323
+ if (!(error instanceof Error) || !error.message.includes('cannot be saved'))
6324
+ throw error;
6325
+ }
6326
+ }
5592
6327
  /** @internal */
5593
6328
  onSidebarDetailEdit(event) {
5594
6329
  this.sidebarController.edit(event);
@@ -5677,7 +6412,9 @@ class AgridComponent {
5677
6412
  });
5678
6413
  treeController = new AgridTreeController();
5679
6414
  // ── Derived state ─────────────────────────────────────────────────────────────
5680
- allowRowReorder = computed(() => (this.control()?.allowRowReorder() ?? false) && !this.control()?.groupByField(), ...(ngDevMode ? [{ debugName: "allowRowReorder" }] : /* istanbul ignore next */ []));
6415
+ allowRowReorder = computed(() => (this.control()?.allowRowReorder() ?? false)
6416
+ && !this.control()?.groupByField()
6417
+ && !this.provider().pivotConfig, ...(ngDevMode ? [{ debugName: "allowRowReorder" }] : /* istanbul ignore next */ []));
5681
6418
  /** `true` when there is a committed edit that can be undone (Ctrl+Z). */
5682
6419
  canUndo = computed(() => this.control()?.canUndo() ?? false, ...(ngDevMode ? [{ debugName: "canUndo" }] : /* istanbul ignore next */ []));
5683
6420
  /** `true` when there is a previously undone edit that can be re-applied (Ctrl+Y / Ctrl+Shift+Z). */
@@ -5728,6 +6465,7 @@ class AgridComponent {
5728
6465
  autoAddRows: this.autoAddRows,
5729
6466
  expandedGroups: this.groupController.expandedGroups,
5730
6467
  treeConfig: this.treeConfig,
6468
+ pivotMode: this.pivotMode,
5731
6469
  expandedTreeIds: this.treeController.expandedIds,
5732
6470
  pinRow: this.effectivePinRow,
5733
6471
  masterDetail: this.masterDetail,
@@ -5846,6 +6584,7 @@ class AgridComponent {
5846
6584
  destroyRef = inject(DestroyRef);
5847
6585
  _hostEl = inject((ElementRef));
5848
6586
  browser = new AgridBrowserAdapter();
6587
+ viewReady = false;
5849
6588
  rangeController = new AgridRangeController({
5850
6589
  control: this.control,
5851
6590
  dataSource: this.dataSource,
@@ -6009,6 +6748,8 @@ class AgridComponent {
6009
6748
  sidebarTab = this.sidebarController.tab;
6010
6749
  sidebarRow = this.sidebarController.row;
6011
6750
  sidebarHiddenColumns = this.sidebarController.hiddenColumns;
6751
+ /** Original provider columns used as pivot field choices. */
6752
+ sidebarPivotColumns = computed(() => this.provider().columns(), ...(ngDevMode ? [{ debugName: "sidebarPivotColumns" }] : /* istanbul ignore next */ []));
6012
6753
  clipboardHandler = new AgridClipboardHandler({
6013
6754
  control: this.control,
6014
6755
  dataSource: this.dataSource,
@@ -6177,6 +6918,9 @@ class AgridComponent {
6177
6918
  };
6178
6919
  }
6179
6920
  constructor() {
6921
+ // Keep one datasource identity so selection/controllers are not reset whenever source data
6922
+ // causes the computed pivot rows to be regenerated.
6923
+ this.pivotDataSource.linkSignal(this.pivotRows);
6180
6924
  effect(() => this.sidebarController.syncAutoOpen());
6181
6925
  effect(() => {
6182
6926
  const datasource = this.dataSource();
@@ -6190,10 +6934,31 @@ class AgridComponent {
6190
6934
  this.flushDirtyInlineRows(activeRowIndex);
6191
6935
  });
6192
6936
  afterNextRender(() => {
6937
+ this.viewReady = true;
6193
6938
  const wrapper = this.wrapperEl().nativeElement;
6939
+ const renderedRangeSubscription = this.viewport().renderedRangeStream.subscribe(() => this.ensureServerRowsVisible());
6194
6940
  const onKeyDown = (event) => this.onKeyDown(event);
6195
6941
  wrapper.addEventListener('keydown', onKeyDown, { capture: true });
6196
- this.destroyRef.onDestroy(() => wrapper.removeEventListener('keydown', onKeyDown, { capture: true }));
6942
+ this.destroyRef.onDestroy(() => {
6943
+ renderedRangeSubscription.unsubscribe();
6944
+ wrapper.removeEventListener('keydown', onKeyDown, { capture: true });
6945
+ });
6946
+ this.ensureServerRowsVisible();
6947
+ });
6948
+ // Query changes invalidate lazy blocks. The model owns request cancellation by generation,
6949
+ // so late responses from an old filter/sort state cannot overwrite current rows.
6950
+ effect(() => {
6951
+ const model = this.serverSideRowModel();
6952
+ const ctrl = this.control();
6953
+ if (!model)
6954
+ return;
6955
+ ctrl?.filters();
6956
+ ctrl?.sortOrder();
6957
+ ctrl?.quickFilter();
6958
+ const changed = model.setQuery(ctrl, this.projection.effectiveSortOrder());
6959
+ if (changed && this.viewReady)
6960
+ this.viewport().scrollToIndex(0);
6961
+ queueMicrotask(() => this.ensureServerRowsVisible());
6197
6962
  });
6198
6963
  const onDocumentKeyDown = (event) => {
6199
6964
  if (event.key !== 'Escape' || event.defaultPrevented)
@@ -6281,6 +7046,10 @@ class AgridComponent {
6281
7046
  isDataRowItem(item) {
6282
7047
  return isDataRowItem(item);
6283
7048
  }
7049
+ /** @internal Whether this virtual row is waiting for a server-side block. */
7050
+ isLoadingRow(item) {
7051
+ return !!item && typeof item === 'object' && 'loading' in item;
7052
+ }
6284
7053
  /** @internal */
6285
7054
  isGroupHeaderItem(item) {
6286
7055
  return isGroupHeaderItem(item);
@@ -6391,9 +7160,30 @@ class AgridComponent {
6391
7160
  treeRowExpanded(item) {
6392
7161
  return (isTreeRowItem(item) || isPathTreeNodeItem(item)) && item.expanded;
6393
7162
  }
6394
- /** @internal Display-only final path segment for a datasource-backed path-tree leaf. */
7163
+ /**
7164
+ * @internal Resolve the non-persistent value shown by a tree cell.
7165
+ *
7166
+ * A formatted path-leaf label wins in the tree column. In every other aggregate column, an
7167
+ * expandable parent shows its descendant rollup instead of its stored value. Returning `null`
7168
+ * delegates to the normal cell formatter. The source row is never mutated.
7169
+ */
6395
7170
  treeCellDisplayOverride(item, col) {
6396
- return isTreeRowItem(item) && this.isTreeCell(col) ? item.treeLabel ?? null : null;
7171
+ if (!isTreeRowItem(item))
7172
+ return null;
7173
+ if (this.isTreeCell(col) && item.treeLabel != null)
7174
+ return item.treeLabel;
7175
+ if (item.aggregates && col.field in item.aggregates) {
7176
+ return this.getFooterDisplay(col, item.aggregates[col.field]);
7177
+ }
7178
+ return null;
7179
+ }
7180
+ /**
7181
+ * @internal True when a datasource-backed tree parent cell displays a computed rollup.
7182
+ * The template uses this to prevent editing a display-only value into the source parent row.
7183
+ * Generated path branches are not cell components and render their aggregates separately.
7184
+ */
7185
+ isTreeAggregateCell(item, col) {
7186
+ return isTreeRowItem(item) && !!item.aggregates && col.field in item.aggregates;
6397
7187
  }
6398
7188
  /** @internal Whether the configured info action is visible for this cell. */
6399
7189
  showCellInfoIcon(col, row) {
@@ -6494,6 +7284,8 @@ class AgridComponent {
6494
7284
  return '__ghost__';
6495
7285
  if (item === null)
6496
7286
  return -1;
7287
+ if (this.isLoadingRow(item))
7288
+ return `__loading__${item.originalIndex}`;
6497
7289
  if (isGroupHeaderItem(item))
6498
7290
  return `__group__${item.groupLabel}`;
6499
7291
  if (isDetailRowItem(item))
@@ -7112,10 +7904,12 @@ class AgridComponent {
7112
7904
  /** @internal */
7113
7905
  onSidebarToggleColumn(field) {
7114
7906
  this.columnMenuController.toggleColumnVisibility(field);
7907
+ this.emitSettingsChange();
7115
7908
  }
7116
7909
  /** @internal Sets every column in a sidebar header group to the requested visibility. */
7117
7910
  onSidebarToggleColumnGroup(fields, visible) {
7118
7911
  this.columnMenuController.setColumnsVisibility(fields, visible);
7912
+ this.emitSettingsChange();
7119
7913
  }
7120
7914
  /** @internal Mirrors vertical scrolling from the main viewport into both pinned panes. */
7121
7915
  onBodyScroll() {
@@ -7123,6 +7917,16 @@ class AgridComponent {
7123
7917
  this.pinnedViewport()?.scrollToOffset(offset);
7124
7918
  this.rightPinnedViewport()?.scrollToOffset(offset);
7125
7919
  }
7920
+ ensureServerRowsVisible() {
7921
+ const model = this.serverSideRowModel();
7922
+ if (!model || !this.viewReady)
7923
+ return;
7924
+ const viewport = this.viewport();
7925
+ const range = viewport.getRenderedRange();
7926
+ const start = Math.max(0, range.start - model.blockSize);
7927
+ const end = Math.max(range.end, range.start + model.blockSize) + model.blockSize;
7928
+ model.ensureRange(start, end);
7929
+ }
7126
7930
  /** @internal Keeps the row-delete prompt visible while columns scroll horizontally. */
7127
7931
  onHorizontalScroll() {
7128
7932
  this.updateDeleteConfirmationPosition();
@@ -7228,7 +8032,7 @@ class AgridComponent {
7228
8032
  return this.columnSizing.getWidthToken(col);
7229
8033
  }
7230
8034
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7231
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridComponent, isStandalone: true, selector: "agrid", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellEdit: "cellEdit", recordEdit: "recordEdit", rowRemoved: "rowRemoved", prepareAddRecord: "prepareAddRecord", rowReorder: "rowReorder", rowSelect: "rowSelect", rowDoubleClicked: "rowDoubleClicked", rowClick: "rowClick", treeNodeClick: "treeNodeClick", treeNodeDoubleClicked: "treeNodeDoubleClicked", rowChanged: "rowChanged", pageChange: "pageChange", filterChange: "filterChange", sortChange: "sortChange", quickFilterChange: "quickFilterChange", validationFailed: "validationFailed", cellInfo: "cellInfo", menuBarAction: "menuBarAction" }, host: { properties: { "class.ag-zebra": "zebraStripes()", "style.min-height": "minHeight()", "style.max-height": "maxHeight()" } }, viewQueries: [{ propertyName: "viewport", first: true, predicate: ["scrollViewport"], descendants: true, isSignal: true }, { propertyName: "pinnedViewport", first: true, predicate: ["pinnedViewport"], descendants: true, isSignal: true }, { propertyName: "rightPinnedViewport", first: true, predicate: ["rightPinnedViewport"], descendants: true, isSignal: true }, { propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }, { propertyName: "horizontalScrollerEl", first: true, predicate: ["horizontalScroller"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #wrapper class=\"ag-wrapper\" tabindex=\"0\" role=\"grid\" [attr.aria-label]=\"localeText().grid\"\n [attr.aria-rowcount]=\"ariaRowCount()\" [attr.aria-colcount]=\"ariaColCount()\"\n [attr.aria-multiselectable]=\"rowSelection() === 'multi' ? 'true' : null\"\n [attr.aria-readonly]=\"readonlyGrid() ? 'true' : null\" [attr.aria-busy]=\"loading() ? 'true' : null\"\n (copy)=\"onCopy($event)\" (paste)=\"onPaste($event)\" (focusin)=\"onGridFocusIn($event)\"\n (click)=\"closeContextMenu(); closeCellContextMenu(); closeFilterMenu(); closeGroupActionsMenu(); closeMenuBarMenu()\">\n @if (visibleMenuBarItems().length > 0 || enableQuickFilter()) {\n <div class=\"ag-toolbar\">\n @if (visibleMenuBarItems().length > 0) {\n <agrid-menu-bar [items]=\"visibleMenuBarItems()\" [context]=\"menuBarContext()\"\n [label]=\"localeText().actions\" [openItemId]=\"openMenuBarItemId()\"\n (openItemIdChange)=\"onMenuBarOpenItemChange($event)\" (action)=\"menuBarAction.emit($event)\" />\n }\n @if (enableQuickFilter()) {\n <input class=\"ag-quick-filter\" type=\"search\" [value]=\"quickFilterValue()\" (input)=\"onQuickFilterInput($event)\"\n [placeholder]=\"localeText().quickFilterPlaceholder\" [attr.aria-label]=\"localeText().quickFilterPlaceholder\" />\n }\n </div>\n }\n <div class=\"ag-main-area\">\n <div class=\"ag-grid-split\" [class.ag-has-right-pane]=\"hasRightPinnedPane()\">\n @if (hasPinnedPane()) {\n <div class=\"ag-pinned-pane\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups ag-header-groups--pinned\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-group-cell ag-header-group-cell--empty\"></div>\n }\n @for (run of pinnedHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header ag-header--pinned\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-cell ag-control-header\" role=\"columnheader\" aria-colindex=\"1\">\n <div class=\"ag-header-cell-top\"></div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\"></div>\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-last]=\"isLastPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}</span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\"\n (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #leftRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.data-control]=\"true\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isPinnedPaneRowSelected(item)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || item === 'ghost' ? pinnedGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell\" role=\"rowheader\" aria-colindex=\"1\"\n [class.ag-control-cell--reorder]=\"allowRowReorder()\"\n (contextmenu)=\"onControlContextMenu($event, item.originalIndex)\" (click)=\"$event.stopPropagation()\"\n (pointerdown)=\"onControlPointerDown($event, item.originalIndex)\">\n @if (canToggleDetail(item)) {\n <button class=\"ag-detail-toggle\" type=\"button\"\n [class.ag-detail-toggle--expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-label]=\"localeText().toggleDetail\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"onDetailToggle(item.originalIndex); $event.stopPropagation()\">\u25B6</button>\n }\n @if (enableRowMarking()) {\n <input class=\"ag-row-marker\" type=\"checkbox\" [checked]=\"isRowMarked(item.originalIndex)\"\n [attr.aria-label]=\"localeText().markRow\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\" (change)=\"toggleRowMarked(item.originalIndex)\" />\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\"\n [row]=\"item.row\" [locale]=\"locale()\" [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [attr.data-col-field]=\"col.field\" [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex)\" [error]=\"cellValidationError(item.originalIndex, ci)\"\n [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\"\n [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\" [class.ag-cell--pinned]=\"true\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell ag-control-cell--reorder ag-ghost-handle\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #pinnedViewport class=\"ag-body ag-pinned-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.width.px]=\"pinnedPaneWidth()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\">\n @if (showControlColumn()) {\n <div class=\"ag-footer-cell ag-footer-cell--control\" role=\"gridcell\" aria-colindex=\"1\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"ag-scroll-pane\">\n <div #horizontalScroller class=\"ag-horizontal-scroll\" (scroll)=\"onHorizontalScroll()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (run of scrollableHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-header-cell\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.data-col-field]=\"col.field\" (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}</span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">\u229F</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\"\n (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #bodyRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || item === 'ghost' ? scrollableGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @for (col of scrollableColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\"\n [row]=\"item.row\" [locale]=\"locale()\" [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [attr.data-col-field]=\"col.field\" [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex)\" [error]=\"cellValidationError(item.originalIndex, ci)\"\n [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\"\n [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\"\n (startEdit)=\"onStartEdit(item.originalIndex, ci)\" (draftChange)=\"onDraftChange($event)\"\n (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\" (treeToggle)=\"onTreeToggle(item)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (item === null) {\n <div class=\"ag-add-row-label\" (click)=\"onActivateAddRow()\">\n <span class=\"ag-add-row-icon\">+</span> {{ localeText().addRow }}\n </div>\n } @else if (item === 'ghost') {\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-ghost-cell\" [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else if (isDetailRowItem(item)) {\n <div class=\"ag-detail-panel\" [style.height.px]=\"detailRowHeight()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" [innerHTML]=\"detailHtml(item)\"></div>\n } @else if (isPathTreeNodeItem(item)) {\n <div class=\"ag-path-tree-content\" [style.min-width.px]=\"scrollableTotalWidth()\"\n [style.padding-left.px]=\"treeRowLevel(item) * 16 + 8\"\n (click)=\"onTreeNodeClick(item); $event.stopPropagation()\"\n (dblclick)=\"onTreeNodeDoubleClick(item); $event.stopPropagation()\">\n <button type=\"button\" class=\"ag-tree-twisty\"\n [class.ag-tree-twisty--expanded]=\"treeRowExpanded(item)\"\n [attr.aria-expanded]=\"treeRowExpanded(item)\"\n (click)=\"onTreeToggle(item); $event.stopPropagation()\">\u25B6</button>\n <span>{{ pathTreeLabel(item) }}</span>\n </div>\n } @else {\n <div class=\"ag-group-header-content\" [style.min-width.px]=\"scrollableTotalWidth()\">\n <div class=\"ag-group-header-main\"\n (click)=\"onGroupHeaderClick(item.groupLabel); $event.stopPropagation()\">\n <span class=\"ag-group-icon\" [class.ag-group-icon--expanded]=\"!item.collapsed\">\u25B6</span>\n <span class=\"ag-group-label\">{{ item.groupLabel }}</span>\n @if (groupActions().length > 0) {\n <button class=\"ag-group-actions-btn\" [title]=\"localeText().actions\"\n (click)=\"openGroupActionsMenu($event, item.groupLabel)\">\u22EE</button>\n }\n <span class=\"ag-group-count\">{{ item.count }}</span>\n @if (getGroupDescription(item.groupLabel); as desc) {\n <span class=\"ag-group-description\">{{ desc }}</span>\n }\n @if (item.aggregates; as aggs) {\n <span class=\"ag-group-aggregates\">\n @for (col of visibleColDefs(); track col.field) {\n @if (hasAggregate(col) && aggs[col.field] !== undefined) {\n <span class=\"ag-group-aggregate\">\n <span class=\"ag-group-aggregate-col\">{{ col.header }}</span>\n <span class=\"ag-group-aggregate-op\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, aggs[col.field]) }}\n </span>\n }\n }\n </span>\n }\n </div>\n </div>\n }\n @if (isDataRowItem(item) && isRowPendingDelete(item.originalIndex)) {\n <div class=\"ag-delete-confirmation\" role=\"alertdialog\" [attr.aria-label]=\"localeText().confirmDeleteRow\"\n [style.left.px]=\"deleteConfirmationLeft()\" [style.width.px]=\"deleteConfirmationWidth() || null\"\n (pointerdown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\n <span class=\"ag-delete-confirmation-text\">{{ localeText().confirmDeleteRow }}</span>\n <button class=\"ag-delete-confirmation-btn ag-delete-confirmation-btn--yes\" type=\"button\"\n (click)=\"confirmPendingRowDelete()\">{{ localeText().confirmYes }}</button>\n <button class=\"ag-delete-confirmation-btn\" type=\"button\" data-delete-confirm-no\n (click)=\"cancelRowDelete()\">{{ localeText().confirmNo }}</button>\n </div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #scrollViewport class=\"ag-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" (scroll)=\"onBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (hasRightPinnedPane()) {\n <div class=\"ag-pinned-pane ag-pinned-pane--right\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (run of rightHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}</span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">'\u229F '</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\"\n (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #rightRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-ghost-row]=\"item === 'ghost'\" [class.ag-group-header-row]=\"isGroupHeaderItem(item)\"\n [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || item === 'ghost' ? rightGridTemplateColumns() : null\">\n @if (isDataRowItem(item)) {\n @for (col of rightPinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\"\n [row]=\"item.row\" [locale]=\"locale()\" [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [attr.data-col-field]=\"col.field\" [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex)\" [error]=\"cellValidationError(item.originalIndex, ci)\"\n [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\"\n [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\" [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--pinned]=\"true\" [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">{{ getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #rightPinnedViewport class=\"ag-body ag-pinned-body ag-right-pinned-body\"\n [agridVariableRowSize]=\"itemSizes()\" [style.width.px]=\"rightPinnedPaneWidth()\"\n (scroll)=\"onRightPinnedBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (loading()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ localeText().loading }}</div>\n } @else if (isEmpty()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ emptyTextLabel() }}</div>\n }\n\n @if (showSidebar()) {\n <agrid-sidebar [open]=\"sidebarOpen()\" [activeTab]=\"sidebarTab()\" [columns]=\"colDefs()\"\n [headerGroups]=\"headerGroups()\" [row]=\"sidebarRow()\" [rowIndex]=\"selectedRowIndex()\"\n [hiddenColumns]=\"sidebarHiddenColumns()\" [locale]=\"locale()\"\n [localeText]=\"localeText()\" [readonlyGrid]=\"readonlyGrid()\" [useSidebarEditor]=\"useSidebarEditor()\"\n [isCellEditable]=\"isCellEditableForRow\"\n [errors]=\"sidebarValidationErrors()\" (close)=\"toggleSidebar()\" (tabChange)=\"onSidebarStripClick($event)\"\n (toggleColumn)=\"onSidebarToggleColumn($event)\"\n (toggleColumnGroup)=\"onSidebarToggleColumnGroup($event.fields, $event.visible)\"\n (detailEdit)=\"onSidebarDetailEdit($event)\" (save)=\"onSidebarDetailSave($event)\" />\n }\n </div><!-- /.ag-main-area -->\n\n @if (showPagination()) {\n <nav class=\"ag-pagination\" [attr.aria-label]=\"localeText().pagination\">\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().firstPage\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToFirstPage()\">\u00AB</button>\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().previous\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToPrevPage()\">\u2039</button>\n <span class=\"ag-page-info\" aria-live=\"polite\">{{ control()!.currentPage() }} / {{ totalPages() }}</span>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().next\" (click)=\"goToNextPage()\">\u203A</button>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().lastPage\" (click)=\"goToLastPage()\">\u00BB</button>\n <span class=\"ag-page-count\">{{ localeText().rows(filteredRowCount()) }}</span>\n </nav>\n }\n\n @if (findOpen()) {\n <agrid-find-panel [query]=\"findQuery()\" [matchCount]=\"findMatches().length\" [activeIndex]=\"findActiveIndex()\"\n [localeText]=\"localeText()\" (queryChange)=\"onFindInput($event)\" (previous)=\"goToFindMatch(-1)\"\n (next)=\"goToFindMatch(1)\" (close)=\"closeFind()\" />\n }\n\n <!-- Row context menu -->\n @if (contextMenu(); as menu) {\n @if(hasContextMenuEntries()) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @if (!treeConfig()) {\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n }\n @if (!readonlyGrid()) {\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">\n {{ localeText().deleteRow }}\n </button>\n }\n </div>\n }\n }\n\n <!-- Cell context menu -->\n @if (cellContextMenuState(); as menu) {\n @let col = getColDef(menu.field)!;\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyCellToClipboard(menu.rowIndex, col)\">{{\n localeText().copyCellValue\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyRowToClipboard(menu.rowIndex)\">{{ localeText().copyRow\n }}</button>\n @if (!treeConfig()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n }\n @if (allowAddRows() && !readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex)\">{{ localeText().insertRowAbove\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex + 1)\">{{\n localeText().insertRowBelow }}</button>\n }\n @if (!readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">{{\n localeText().deleteRow }}</button>\n }\n @if (cellMenuItems().length) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @for (item of cellMenuItems(); track $index) {\n @if (item === null) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" [class.ag-context-item--danger]=\"item.danger\"\n [disabled]=\"item.disabled\" (click)=\"runCellMenuItem(item, menu)\">{{\n item.label }}</button>\n }\n }\n }\n </div>\n }\n\n <!-- Group actions menu -->\n @if (groupActionsMenu(); as menu) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @for (action of groupActions(); track action.label) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onGroupAction(action, menu.label)\">\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n <!-- Filter dropdown -->\n @if (filterMenu(); as menu) {\n <agrid-column-menu [x]=\"menu.x\" [y]=\"menu.y\" [header]=\"getColDef(menu.field)?.header ?? menu.field\"\n [sortDir]=\"getSort(menu.field)\" [sortable]=\"sortOption() !== 'none'\" [showColumnActions]=\"!!control()\"\n [pinned]=\"getColumnPinState(menu.field)\" [groupable]=\"!!getColDef(menu.field)?.groupable\"\n [grouped]=\"isGroupedByField(menu.field)\" [filterable]=\"!!getColDef(menu.field)?.filterable\"\n [showValueFilter]=\"!serverSideFiltering()\" [filterType]=\"getMenuFilterType(menu.field)\"\n [operator]=\"getMenuOperator(menu.field)\" [operand]=\"getMenuOperand(menu.field)\"\n [operand2]=\"getMenuOperand2(menu.field)\" (operatorChange)=\"onMenuOperatorChange(menu.field, $event)\"\n (operandChange)=\"onMenuOperandChange(menu.field, $event)\"\n (operand2Change)=\"onMenuOperand2Change(menu.field, $event)\" [search]=\"filterMenuSearch()\"\n [allSelected]=\"isMenuAllSelected(menu.field)\"\n [valueItems]=\"serverSideFiltering() || !getColDef(menu.field)?.filterable ? [] : columnMenuValueItems()\"\n [localeText]=\"localeText()\" [sortPriority]=\"getSortPriority(menu.field)\" [hasMultiSort]=\"hasMultiSort()\"\n (sort)=\"onMenuSort(menu.field, $event)\" (resetSort)=\"onMenuResetSort(menu.field, $event)\"\n (autosize)=\"onMenuAutosizeColumn(menu.field)\" (togglePin)=\"onMenuTogglePin(menu.field)\"\n (togglePinRight)=\"onMenuTogglePinRight(menu.field)\" (hide)=\"onMenuHideColumn(menu.field)\"\n (toggleGroup)=\"onMenuToggleGroupBy(menu.field)\" (clearFilter)=\"onMenuClearFilter(menu.field)\"\n (clearAll)=\"onMenuClearAll()\" (searchChange)=\"onFilterMenuSearch($event)\" (toggleAll)=\"onMenuToggleAll(menu.field)\"\n (toggleValue)=\"onMenuToggleValue(menu.field, $event)\" [aggregate]=\"getEffectiveAggregate(getColDef(menu.field)!)\"\n (setAggregate)=\"onMenuSetAggregate(menu.field, $event)\" />\n }\n\n @if (columnDragPreview(); as preview) {\n <div class=\"ag-column-drag-preview\" [style.left.px]=\"preview.x\" [style.top.px]=\"preview.y\"\n [style.width.px]=\"preview.width\" [style.height.px]=\"preview.height\">\n <span>{{ preview.label }}</span>\n </div>\n }\n</div>\n", styles: ["@layer agrid-defaults{:host{--agrid-color-text: #24292f;--agrid-color-text-muted: #57606a;--agrid-color-accent: #1a73e8;--agrid-color-accent-subtle: #e8f0fe;--agrid-color-accent-fg: #1558b0;--agrid-color-accent-border: #c8d8f8;--agrid-color-danger: #d1242f;--agrid-color-danger-subtle: #fff1f0;--agrid-color-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-subtle: #fafbfc;--agrid-color-bg-muted: #f6f8fa;--agrid-color-shadow: rgba(140, 149, 159, .2);--agrid-color-bg-stripe: #f0f2f5;--agrid-color-cell-changed: #f59e0b;--agrid-color-row-marked: #fff8c5}}:host{display:flex;flex-direction:column;min-height:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:var(--agrid-color-text)}.ag-wrapper{position:relative;display:flex;flex-direction:column;flex:1;min-height:0;border:1px solid var(--agrid-color-border);overflow:hidden;outline:none;border-radius:4px}.ag-state-overlay{position:absolute;inset:34px 0 0;display:flex;align-items:center;justify-content:center;color:var(--agrid-color-text-muted);background:color-mix(in srgb,var(--agrid-color-bg) 78%,transparent);pointer-events:none;z-index:3}.ag-header{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-bottom:2px solid var(--agrid-color-border)}.ag-header-groups{display:grid;flex-shrink:0;height:28px;background:color-mix(in srgb,var(--agrid-color-bg-muted) 72%,var(--agrid-color-bg));border-bottom:1px solid var(--agrid-color-border)}.ag-header-group-cell{display:flex;align-items:center;justify-content:center;min-width:0;padding:0 8px;overflow:hidden;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;color:var(--agrid-color-text);font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap;cursor:grab;-webkit-user-select:none;user-select:none;transition:opacity .1s ease}.ag-header-group-cell:last-child{border-right:none}.ag-header-group-cell--empty,.ag-header-group-cell--locked{cursor:default}.ag-header-group-cell--dragging{opacity:.12}.ag-header-cell{position:relative;display:flex;align-items:center;font-weight:600;border-right:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;-webkit-user-select:none;user-select:none;box-sizing:border-box}.ag-header-cell:last-child{border-right:none}.ag-header-cell-top{display:flex;align-items:center;flex:1;min-width:0;padding:0 6px;height:32px;overflow:hidden;white-space:nowrap}.ag-header-cell-label{overflow:hidden;text-overflow:ellipsis;flex:1}.ag-header--with-filters .ag-header-cell{flex-direction:column;align-items:stretch;height:auto;white-space:normal}.ag-header--with-filters .ag-header-cell-top{flex:0 0 32px}.ag-header-cell-filter{height:28px;display:flex;align-items:center;padding:0 2px;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-header-cell{cursor:grab;transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-row>agrid-cell,.ag-ghost-cell,.ag-footer-cell{transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-header-cell--dragging{opacity:.12;cursor:grabbing}.ag-column-reorder-item--dragging{opacity:.12}.ag-header-cell--drop-before,.ag-header-cell--drop-after{z-index:1}.ag-column-drag-preview{position:fixed;z-index:1000;display:flex;align-items:flex-start;padding:8px 10px;border:1px solid var(--agrid-color-accent);border-radius:5px;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-bg) 94%,var(--agrid-color-accent));box-shadow:0 10px 28px var(--agrid-color-shadow);font-weight:600;line-height:16px;box-sizing:border-box;pointer-events:none;opacity:.96}.ag-sort-badge{font-size:11px;color:var(--agrid-color-accent);flex-shrink:0;line-height:1}.ag-sort-priority{font-size:9px;vertical-align:super;opacity:.75}.ag-resize-handle{position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;z-index:1}.ag-resize-handle:hover{background:var(--agrid-color-accent);opacity:.5}.ag-filter-input{flex:1;min-width:0;height:20px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 4px;background:var(--agrid-color-bg)}.ag-filter-condition-btn{flex:0 0 24px;height:20px;padding:0;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text-muted);cursor:pointer}.ag-filter-condition-btn:hover,.ag-filter-condition-btn:focus-visible{border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg)}.ag-filter-condition-btn--active{border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-filter-input:focus{border-color:var(--agrid-color-accent)}.ag-header-cell--filtered .ag-header-cell-label:after{content:\" \\25be\";font-size:9px;color:var(--agrid-color-accent)}.ag-main-area{display:flex;flex:1;min-height:0;overflow:hidden}.ag-grid-split{display:flex;flex:1;min-width:0;min-height:0;overflow:hidden}.ag-pinned-pane{flex-shrink:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-scroll-pane{flex:1;min-width:0;min-height:0;display:flex}.ag-horizontal-scroll{flex:1;min-width:0;min-height:0;overflow-x:auto;overflow-y:hidden;display:flex;flex-direction:column}.ag-body{flex:1;overflow-x:clip;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-horizontal-scroll{scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-body::-webkit-scrollbar,.ag-horizontal-scroll::-webkit-scrollbar{width:8px;height:8px}.ag-body::-webkit-scrollbar-track,.ag-horizontal-scroll::-webkit-scrollbar-track{background:transparent}.ag-body::-webkit-scrollbar-thumb,.ag-horizontal-scroll::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-body::-webkit-scrollbar-thumb:hover,.ag-horizontal-scroll::-webkit-scrollbar-thumb:hover{background:#00000052;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-pinned-body{overflow:hidden}.ag-right-pinned-body{overflow-y:auto}.ag-has-right-pane .ag-body:not(.ag-pinned-body){scrollbar-width:none}.ag-has-right-pane .ag-body:not(.ag-pinned-body)::-webkit-scrollbar{display:none}.ag-row{display:grid;position:relative}.ag-row--pending-delete>:not(.ag-delete-confirmation){opacity:.2;pointer-events:none;transition:opacity .14s ease}.ag-delete-confirmation{position:absolute;top:0;bottom:0;z-index:7;display:flex;align-items:center;justify-content:center;gap:8px;min-width:min(100%,320px);padding:2px 12px;box-sizing:border-box;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-danger-subtle) 88%,transparent);border-block:1px solid color-mix(in srgb,var(--agrid-color-danger) 35%,transparent)}.ag-delete-confirmation-text{font-weight:600;white-space:nowrap}.ag-delete-confirmation-btn{min-width:42px;padding:3px 9px;border:1px solid var(--agrid-color-border);border-radius:4px;color:var(--agrid-color-text);background:var(--agrid-color-bg);font:inherit;cursor:pointer}.ag-delete-confirmation-btn:hover,.ag-delete-confirmation-btn:focus-visible{border-color:var(--agrid-color-accent);outline:none}.ag-delete-confirmation-btn--yes{color:#fff;border-color:var(--agrid-color-danger);background:var(--agrid-color-danger)}.ag-delete-confirmation-btn--yes:hover,.ag-delete-confirmation-btn--yes:focus-visible{border-color:color-mix(in srgb,var(--agrid-color-danger) 75%,#000);background:color-mix(in srgb,var(--agrid-color-danger) 85%,#000)}:host(.ag-zebra) .ag-row--odd agrid-cell:not(.editing){background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-control-cell{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd:hover .ag-control-cell{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned-first{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned-first{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned-first{background:var(--agrid-color-accent-subtle)}.ag-row:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}.ag-row--marked agrid-cell:not(.editing),.ag-row--marked .ag-control-cell{background:var(--agrid-color-row-marked)}.ag-row--marked{box-shadow:inset 3px 0 #bf8700}.ag-row--selected.ag-row--marked{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--range-selected:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent-border)}.ag-row agrid-cell.ag-cell--find-match:not(.editing){background:#fff7cc}.ag-row agrid-cell.ag-cell--find-active:not(.editing){background:#ffe58a;box-shadow:inset 0 0 0 2px #b7791f}.ag-row agrid-cell.ag-cell--range-selected.selected:not(.editing){box-shadow:inset 0 0 0 2px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-preview:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing){position:relative}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing):after{content:\"\";position:absolute;right:1px;bottom:1px;width:6px;height:6px;background:var(--agrid-color-accent);border:1px solid var(--agrid-color-bg);box-sizing:border-box;cursor:crosshair;z-index:4}.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}.ag-row--selected{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-control-header{z-index:3;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted)}.ag-control-header .ag-header-cell-filter{background:var(--agrid-color-bg-subtle)}.ag-control-cell{z-index:2;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle);cursor:context-menu;display:flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;flex-direction:row-reverse}.ag-control-cell:after{content:\"\\22ee\";font-size:11px;color:var(--agrid-color-border);line-height:1}.ag-control-cell:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder{cursor:grab}.ag-control-cell--reorder:after{content:\"\\283f\";font-size:13px;color:var(--agrid-color-text)}.ag-control-cell--reorder:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder:active{cursor:grabbing}.ag-row-marker{position:relative;z-index:1;width:14px;height:14px;margin:0;accent-color:var(--agrid-color-accent);cursor:pointer}.ag-ghost-row{background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1.5px var(--agrid-color-accent);pointer-events:none;animation:ag-ghost-in .1s ease}@keyframes ag-ghost-in{0%{opacity:0}to{opacity:1}}.ag-ghost-handle:after{color:var(--agrid-color-accent)!important}.ag-ghost-cell{display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-right:1px solid var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-size:13px;box-sizing:border-box;-webkit-user-select:none;user-select:none}.ag-ghost-cell:last-child{border-right:none}.ag-pinned-row-spacer,.ag-pinned-group-spacer{height:100%;border-bottom:1px solid var(--agrid-color-border);box-sizing:border-box}.ag-pinned-group-spacer{background:var(--agrid-color-bg-muted)}.ag-context-menu{position:fixed;z-index:1000;background:var(--agrid-color-bg-subtle);border:1px solid var(--agrid-color-border);border-radius:6px;box-shadow:0 8px 24px var(--agrid-color-shadow);min-width:160px;padding:4px 0;font-size:13px}.ag-context-item{display:block;width:100%;padding:6px 16px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit}.ag-context-item:hover{background:var(--agrid-color-bg-muted)}.ag-context-separator{height:1px;background:var(--agrid-color-border);margin:3px 0}.ag-context-item--danger{color:var(--agrid-color-danger)}.ag-context-item--danger:hover{background:var(--agrid-color-danger-subtle)}.ag-group-header-row{display:flex;align-items:stretch;background:var(--agrid-color-bg-muted);border-bottom:1px solid var(--agrid-color-border);overflow:hidden}.ag-path-tree-content{display:flex;align-items:center;gap:4px;height:100%;box-sizing:border-box;color:var(--agrid-color-text);font-weight:600;border-bottom:1px solid var(--agrid-color-border)}.ag-path-tree-content .ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;color:inherit;background:transparent;cursor:pointer;font-size:10px;transition:transform .12s ease}.ag-path-tree-content .ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-group-header-content{display:flex;align-items:stretch;height:100%;width:100%;-webkit-user-select:none;user-select:none}.ag-group-header-main{display:flex;align-items:center;gap:6px;padding:0 10px;flex:1;min-width:0;cursor:pointer}.ag-group-header-main:hover{background:var(--agrid-color-bg-subtle)}.ag-group-description{font-size:11px;color:var(--agrid-color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-group-actions-btn{display:flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:none;border-radius:3px;cursor:pointer;color:var(--agrid-color-text-muted);font-size:14px;padding:0}.ag-group-actions-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-group-icon{font-size:9px;color:var(--agrid-color-text-muted);line-height:1;display:inline-block;transition:transform .15s ease}.ag-group-icon--expanded{transform:rotate(90deg)}.ag-group-label{font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-group-count{font-size:11px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:10px;padding:0 7px;line-height:16px}.ag-group-aggregates{display:inline-flex;align-items:center;gap:10px;margin-left:4px;flex-wrap:wrap}.ag-group-aggregate{font-size:11px;color:var(--agrid-color-text);white-space:nowrap}.ag-group-aggregate-col{color:var(--agrid-color-text-muted);margin-right:4px}.ag-group-aggregate-op{color:var(--agrid-color-text-muted);margin-right:3px}.ag-footer{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-top:2px solid var(--agrid-color-border)}.ag-footer-cell{display:flex;align-items:center;gap:4px;padding:0 6px;height:30px;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;overflow:hidden;white-space:nowrap;font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-footer-cell:last-child{border-right:none}.ag-footer-cell--control{background:var(--agrid-color-bg-subtle);border-right:1px solid var(--agrid-color-border)}.ag-footer-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-footer-label{font-size:10px;font-weight:400;color:var(--agrid-color-text-muted);flex-shrink:0}.ag-pagination{display:flex;align-items:center;gap:4px;padding:0 10px;height:34px;flex-shrink:0;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);font-size:12px;color:var(--agrid-color-text-muted)}.ag-page-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font-size:13px;cursor:pointer;padding:0;line-height:1}.ag-page-btn:hover:not(:disabled){background:var(--agrid-color-bg-muted);border-color:var(--agrid-color-text-muted)}.ag-page-btn:disabled{opacity:.35;cursor:default}.ag-page-info{padding:0 6px;font-weight:500;color:var(--agrid-color-text);min-width:48px;text-align:center}.ag-page-count{margin-left:auto;color:var(--agrid-color-text-muted)}.ag-add-row{border-bottom:1px dashed var(--agrid-color-border);color:var(--agrid-color-text-muted);cursor:pointer}.ag-add-row:hover .ag-add-row-label{background:var(--agrid-color-bg-muted)}.ag-add-row--selected .ag-add-row-label{outline:2px solid var(--agrid-color-accent);outline-offset:-2px}.ag-add-row-label{display:flex;align-items:center;gap:4px;height:100%;padding:0 6px;font-size:12px;-webkit-user-select:none;user-select:none}.ag-add-row-icon{font-size:16px;line-height:1;color:var(--agrid-color-text-muted)}.ag-pinned-pane--right{border-left:1px solid var(--agrid-color-border);border-right:none;box-shadow:-3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-header-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned-first{box-shadow:none}.ag-cell--pinned{background:var(--agrid-color-bg)}.ag-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-cell--pinned-first{box-shadow:none}.ag-row:hover .ag-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-row--selected .ag-cell--pinned,.ag-row .ag-cell--pinned.ag-cell--range-selected,.ag-row .ag-cell--pinned.ag-cell--fill-preview{background:var(--agrid-color-accent-subtle)}.ag-row .ag-cell--pinned.ag-cell--find-match{background:#fff7cc}.ag-row .ag-cell--pinned.ag-cell--find-active{background:#ffe58a}.ag-header-menu-btn{flex-shrink:0;width:16px;height:16px;padding:0;margin-right:2px;background:none;border:1px solid transparent;border-radius:3px;cursor:pointer;font-size:10px;color:var(--agrid-color-text-muted);display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity .12s;line-height:1}.ag-header-menu-btn--active{color:var(--agrid-color-accent);border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle)}.ag-header-menu-btn:hover{background:var(--agrid-color-bg);border-color:var(--agrid-color-border)}.ag-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;padding:6px 8px;border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle)}.ag-quick-filter{margin-left:auto;width:100%;max-width:260px;height:30px;padding:0 10px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-quick-filter:focus{border-color:var(--agrid-color-accent-border)}.ag-detail-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;cursor:pointer;font-size:9px;line-height:1;color:var(--agrid-color-text-muted);transition:transform .12s ease,color .12s ease}.ag-detail-toggle:hover{color:var(--agrid-color-text)}.ag-detail-toggle--expanded{transform:rotate(90deg)}.ag-detail-row{display:block;position:relative;box-sizing:border-box}.ag-detail-panel{box-sizing:border-box;height:100%;overflow:auto;padding:8px 12px;background:var(--agrid-color-bg-subtle);border-bottom:1px solid var(--agrid-color-border)}.ag-pinned-rows{position:relative;z-index:3;flex:none;background:var(--agrid-color-bg)}.ag-pinned-rows--top{box-shadow:0 2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows--bottom{box-shadow:0 -2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows .ag-row{background:var(--agrid-color-bg-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: AgridVariableRowSizeDirective, selector: "cdk-virtual-scroll-viewport[agridVariableRowSize]", inputs: ["agridVariableRowSize"] }, { kind: "component", type: AgridCellComponent, selector: "agrid-cell", inputs: ["col", "rowIndex", "colIndex", "value", "displayValueOverride", "row", "locale", "selected", "editing", "editable", "showInfoIcon", "error", "treeCell", "treeLevel", "treeExpandable", "treeExpanded", "seedChar", "selectTextOnEdit"], outputs: ["treeToggle", "activate", "startEdit", "booleanToggle", "infoClick", "draftChange"] }, { kind: "component", type: AgridMenuBarComponent, selector: "agrid-menu-bar", inputs: ["items", "context", "label", "openItemId"], outputs: ["action", "openItemIdChange"] }, { kind: "component", type: AgridColumnMenuComponent, selector: "agrid-column-menu", inputs: ["localeText", "x", "y", "header", "sortDir", "sortable", "showColumnActions", "pinned", "groupable", "grouped", "filterable", "showValueFilter", "filterType", "operator", "operand", "operand2", "search", "allSelected", "valueItems", "sortPriority", "hasMultiSort", "aggregate"], outputs: ["operatorChange", "operandChange", "operand2Change", "sort", "resetSort", "autosize", "togglePin", "togglePinRight", "hide", "toggleGroup", "clearFilter", "clearAll", "searchChange", "toggleAll", "toggleValue", "setAggregate"] }, { kind: "component", type: AgridFindPanelComponent, selector: "agrid-find-panel", inputs: ["localeText", "query", "matchCount", "activeIndex"], outputs: ["queryChange", "next", "previous", "close"] }, { kind: "component", type: AgridSidebarComponent, selector: "agrid-sidebar", inputs: ["open", "activeTab", "columns", "headerGroups", "row", "rowIndex", "hiddenColumns", "locale", "localeText", "readonlyGrid", "useSidebarEditor", "isCellEditable", "errors"], outputs: ["close", "tabChange", "toggleColumn", "toggleColumnGroup", "detailEdit", "save"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8035
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AgridComponent, isStandalone: true, selector: "agrid", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellEdit: "cellEdit", recordEdit: "recordEdit", rowRemoved: "rowRemoved", prepareAddRecord: "prepareAddRecord", rowReorder: "rowReorder", rowSelect: "rowSelect", rowDoubleClicked: "rowDoubleClicked", rowClick: "rowClick", treeNodeClick: "treeNodeClick", settingsChange: "settingsChange", treeNodeDoubleClicked: "treeNodeDoubleClicked", rowChanged: "rowChanged", pageChange: "pageChange", filterChange: "filterChange", sortChange: "sortChange", quickFilterChange: "quickFilterChange", validationFailed: "validationFailed", cellInfo: "cellInfo", menuBarAction: "menuBarAction" }, host: { properties: { "class.ag-zebra": "zebraStripes()", "style.min-height": "minHeight()", "style.max-height": "maxHeight()" } }, viewQueries: [{ propertyName: "viewport", first: true, predicate: ["scrollViewport"], descendants: true, isSignal: true }, { propertyName: "pinnedViewport", first: true, predicate: ["pinnedViewport"], descendants: true, isSignal: true }, { propertyName: "rightPinnedViewport", first: true, predicate: ["rightPinnedViewport"], descendants: true, isSignal: true }, { propertyName: "wrapperEl", first: true, predicate: ["wrapper"], descendants: true, isSignal: true }, { propertyName: "horizontalScrollerEl", first: true, predicate: ["horizontalScroller"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #wrapper class=\"ag-wrapper\" tabindex=\"0\" role=\"grid\" [attr.aria-label]=\"localeText().grid\"\n [attr.aria-rowcount]=\"ariaRowCount()\" [attr.aria-colcount]=\"ariaColCount()\"\n [attr.aria-multiselectable]=\"rowSelection() === 'multi' ? 'true' : null\"\n [attr.aria-readonly]=\"readonlyGrid() ? 'true' : null\" [attr.aria-busy]=\"loading() ? 'true' : null\"\n (copy)=\"onCopy($event)\" (paste)=\"onPaste($event)\" (focusin)=\"onGridFocusIn($event)\"\n (click)=\"closeContextMenu(); closeCellContextMenu(); closeFilterMenu(); closeGroupActionsMenu(); closeMenuBarMenu()\">\n @if (visibleMenuBarItems().length > 0 || enableQuickFilter()) {\n <div class=\"ag-toolbar\">\n @if (visibleMenuBarItems().length > 0) {\n <agrid-menu-bar [items]=\"visibleMenuBarItems()\" [context]=\"menuBarContext()\" [label]=\"localeText().actions\"\n [openItemId]=\"openMenuBarItemId()\" (openItemIdChange)=\"onMenuBarOpenItemChange($event)\"\n (action)=\"menuBarAction.emit($event)\" />\n }\n @if (enableQuickFilter()) {\n <input class=\"ag-quick-filter\" type=\"search\" [value]=\"quickFilterValue()\" (input)=\"onQuickFilterInput($event)\"\n [placeholder]=\"localeText().quickFilterPlaceholder\" [attr.aria-label]=\"localeText().quickFilterPlaceholder\" />\n }\n </div>\n }\n <div class=\"ag-main-area\">\n <div class=\"ag-grid-split\" [class.ag-has-right-pane]=\"hasRightPinnedPane()\">\n @if (hasPinnedPane()) {\n <div class=\"ag-pinned-pane\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups ag-header-groups--pinned\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-group-cell ag-header-group-cell--empty\"></div>\n }\n @for (run of pinnedHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header ag-header--pinned\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-cell ag-control-header\" role=\"columnheader\" aria-colindex=\"1\">\n <div class=\"ag-header-cell-top\"></div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\"></div>\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-last]=\"isLastPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}\n @if (pivotHeaderLabel() && pivotRowColumnField()!==col.field) {\n ({{pivotHeaderLabel()}})\n }\n\n </span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #leftRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\" [attr.data-control]=\"true\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isPinnedPaneRowSelected(item)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || isLoadingRow(item) || item === 'ghost' ? pinnedGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell\" role=\"rowheader\" aria-colindex=\"1\"\n [class.ag-control-cell--reorder]=\"allowRowReorder()\"\n (contextmenu)=\"onControlContextMenu($event, item.originalIndex)\" (click)=\"$event.stopPropagation()\"\n (pointerdown)=\"onControlPointerDown($event, item.originalIndex)\">\n @if (canToggleDetail(item)) {\n <button class=\"ag-detail-toggle\" type=\"button\"\n [class.ag-detail-toggle--expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-label]=\"localeText().toggleDetail\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"onDetailToggle(item.originalIndex); $event.stopPropagation()\">\u25B6</button>\n }\n @if (enableRowMarking()) {\n <input class=\"ag-row-marker\" type=\"checkbox\" [checked]=\"isRowMarked(item.originalIndex)\"\n [attr.aria-label]=\"localeText().markRow\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\" (change)=\"toggleRowMarked(item.originalIndex)\" />\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\" [row]=\"item.row\" [locale]=\"locale()\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\" [attr.data-col-field]=\"col.field\"\n [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex) && !isTreeAggregateCell(item, col)\"\n [error]=\"cellValidationError(item.originalIndex, ci)\" [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\" [class.ag-cell--pinned]=\"true\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\" (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (isLoadingRow(item)) {\n <div class=\"ag-loading-row\" aria-hidden=\"true\"><span></span></div>\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell ag-control-cell--reorder ag-ghost-handle\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #pinnedViewport class=\"ag-body ag-pinned-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.width.px]=\"pinnedPaneWidth()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\">\n @if (showControlColumn()) {\n <div class=\"ag-footer-cell ag-footer-cell--control\" role=\"gridcell\" aria-colindex=\"1\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"ag-scroll-pane\">\n <div #horizontalScroller class=\"ag-horizontal-scroll\" (scroll)=\"onHorizontalScroll()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (run of scrollableHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-header-cell\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.data-col-field]=\"col.field\" (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}\n @if (pivotHeaderLabel() && pivotRowColumnField()!==col.field) {\n ({{pivotHeaderLabel()}})\n }\n\n </span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">\u229F</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #bodyRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || isLoadingRow(item) || item === 'ghost' ? scrollableGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @for (col of scrollableColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\" [row]=\"item.row\" [locale]=\"locale()\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\" [attr.data-col-field]=\"col.field\"\n [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex) && !isTreeAggregateCell(item, col)\"\n [error]=\"cellValidationError(item.originalIndex, ci)\" [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\"\n (startEdit)=\"onStartEdit(item.originalIndex, ci)\" (draftChange)=\"onDraftChange($event)\"\n (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\" (treeToggle)=\"onTreeToggle(item)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (isLoadingRow(item)) {\n <div class=\"ag-loading-row\" aria-hidden=\"true\"><span></span></div>\n } @else if (item === null) {\n <div class=\"ag-add-row-label\" (click)=\"onActivateAddRow()\">\n <span class=\"ag-add-row-icon\">+</span> {{ localeText().addRow }}\n </div>\n } @else if (item === 'ghost') {\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-ghost-cell\" [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else if (isDetailRowItem(item)) {\n <div class=\"ag-detail-panel\" [style.height.px]=\"detailRowHeight()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" [innerHTML]=\"detailHtml(item)\"></div>\n } @else if (isPathTreeNodeItem(item)) {\n <div class=\"ag-path-tree-content\" [style.min-width.px]=\"scrollableTotalWidth()\"\n [style.padding-left.px]=\"treeRowLevel(item) * 16 + 8\"\n (click)=\"onTreeNodeClick(item); $event.stopPropagation()\"\n (dblclick)=\"onTreeNodeDoubleClick(item); $event.stopPropagation()\">\n <button type=\"button\" class=\"ag-tree-twisty\" [class.ag-tree-twisty--expanded]=\"treeRowExpanded(item)\"\n [attr.aria-expanded]=\"treeRowExpanded(item)\"\n (click)=\"onTreeToggle(item); $event.stopPropagation()\">\u25B6</button>\n <span>{{ pathTreeLabel(item) }}</span>\n @if (item.aggregates; as aggregates) {\n <span class=\"ag-group-aggregates\">\n @for (col of visibleColDefs(); track col.field) {\n @if (col.field !== treeConfig()!.treeField && aggregates[col.field] !== undefined) {\n <span class=\"ag-group-aggregate\">\n <span class=\"ag-group-aggregate-col\">{{ col.header }}</span>\n <span class=\"ag-group-aggregate-op\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, aggregates[col.field]) }}\n </span>\n }\n }\n </span>\n }\n </div>\n } @else {\n <div class=\"ag-group-header-content\" [style.min-width.px]=\"scrollableTotalWidth()\">\n <div class=\"ag-group-header-main\"\n (click)=\"onGroupHeaderClick(item.groupLabel); $event.stopPropagation()\">\n <span class=\"ag-group-icon\" [class.ag-group-icon--expanded]=\"!item.collapsed\">\u25B6</span>\n <span class=\"ag-group-label\">{{ item.groupLabel }}</span>\n @if (groupActions().length > 0) {\n <button class=\"ag-group-actions-btn\" [title]=\"localeText().actions\"\n (click)=\"openGroupActionsMenu($event, item.groupLabel)\">\u22EE</button>\n }\n <span class=\"ag-group-count\">{{ item.count }}</span>\n @if (getGroupDescription(item.groupLabel); as desc) {\n <span class=\"ag-group-description\">{{ desc }}</span>\n }\n @if (item.aggregates; as aggs) {\n <span class=\"ag-group-aggregates\">\n @for (col of visibleColDefs(); track col.field) {\n @if (hasAggregate(col) && aggs[col.field] !== undefined) {\n <span class=\"ag-group-aggregate\">\n <span class=\"ag-group-aggregate-col\">{{ col.header }}</span>\n <span class=\"ag-group-aggregate-op\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, aggs[col.field]) }}\n </span>\n }\n }\n </span>\n }\n </div>\n </div>\n }\n @if (isDataRowItem(item) && isRowPendingDelete(item.originalIndex)) {\n <div class=\"ag-delete-confirmation\" role=\"alertdialog\" [attr.aria-label]=\"localeText().confirmDeleteRow\"\n [style.left.px]=\"deleteConfirmationLeft()\" [style.width.px]=\"deleteConfirmationWidth() || null\"\n (pointerdown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\n <span class=\"ag-delete-confirmation-text\">{{ localeText().confirmDeleteRow }}</span>\n <button class=\"ag-delete-confirmation-btn ag-delete-confirmation-btn--yes\" type=\"button\"\n (click)=\"confirmPendingRowDelete()\">{{ localeText().confirmYes }}</button>\n <button class=\"ag-delete-confirmation-btn\" type=\"button\" data-delete-confirm-no\n (click)=\"cancelRowDelete()\">{{ localeText().confirmNo }}</button>\n </div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #scrollViewport class=\"ag-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" (scroll)=\"onBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (hasRightPinnedPane()) {\n <div class=\"ag-pinned-pane ag-pinned-pane--right\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (run of rightHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}\n @if (pivotHeaderLabel() && pivotRowColumnField()!==col.field) {\n ({{pivotHeaderLabel()}})\n }\n </span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">'\u229F '</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #rightRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-ghost-row]=\"item === 'ghost'\" [class.ag-group-header-row]=\"isGroupHeaderItem(item)\"\n [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || isLoadingRow(item) || item === 'ghost' ? rightGridTemplateColumns() : null\">\n @if (isDataRowItem(item)) {\n @for (col of rightPinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\" [row]=\"item.row\" [locale]=\"locale()\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\" [attr.data-col-field]=\"col.field\"\n [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex) && !isTreeAggregateCell(item, col)\"\n [error]=\"cellValidationError(item.originalIndex, ci)\" [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\" [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--pinned]=\"true\" [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\" (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (isLoadingRow(item)) {\n <div class=\"ag-loading-row\" aria-hidden=\"true\"><span></span></div>\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">{{ getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #rightPinnedViewport class=\"ag-body ag-pinned-body ag-right-pinned-body\"\n [agridVariableRowSize]=\"itemSizes()\" [style.width.px]=\"rightPinnedPaneWidth()\"\n (scroll)=\"onRightPinnedBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (loading()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ localeText().loading }}</div>\n } @else if (isEmpty()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ emptyTextLabel() }}</div>\n }\n\n @if (showSidebar()) {\n <agrid-sidebar [open]=\"sidebarOpen()\" [activeTab]=\"sidebarTab()\" [columns]=\"colDefs()\"\n [pivotColumns]=\"sidebarPivotColumns()\" [pivotConfig]=\"provider().pivotConfig\" [headerGroups]=\"headerGroups()\"\n [row]=\"sidebarRow()\" [rowIndex]=\"selectedRowIndex()\" [hiddenColumns]=\"sidebarHiddenColumns()\" [locale]=\"locale()\"\n [localeText]=\"localeText()\" [readonlyGrid]=\"readonlyGrid()\" [useSidebarEditor]=\"useSidebarEditor()\"\n [isCellEditable]=\"isCellEditableForRow\" [errors]=\"sidebarValidationErrors()\" (close)=\"toggleSidebar()\"\n (tabChange)=\"onSidebarStripClick($event)\" (toggleColumn)=\"onSidebarToggleColumn($event)\"\n (toggleColumnGroup)=\"onSidebarToggleColumnGroup($event.fields, $event.visible)\"\n (pivotChange)=\"onSidebarPivotChange($event)\" (detailEdit)=\"onSidebarDetailEdit($event)\"\n (save)=\"onSidebarDetailSave($event)\" />\n }\n </div><!-- /.ag-main-area -->\n\n @if (showPagination()) {\n <nav class=\"ag-pagination\" [attr.aria-label]=\"localeText().pagination\">\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().firstPage\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToFirstPage()\">\u00AB</button>\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().previous\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToPrevPage()\">\u2039</button>\n <span class=\"ag-page-info\" aria-live=\"polite\">{{ control()!.currentPage() }} / {{ totalPages() }}</span>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().next\" (click)=\"goToNextPage()\">\u203A</button>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().lastPage\" (click)=\"goToLastPage()\">\u00BB</button>\n <span class=\"ag-page-count\">{{ localeText().rows(filteredRowCount()) }}</span>\n </nav>\n }\n\n @if (findOpen()) {\n <agrid-find-panel [query]=\"findQuery()\" [matchCount]=\"findMatches().length\" [activeIndex]=\"findActiveIndex()\"\n [localeText]=\"localeText()\" (queryChange)=\"onFindInput($event)\" (previous)=\"goToFindMatch(-1)\"\n (next)=\"goToFindMatch(1)\" (close)=\"closeFind()\" />\n }\n\n <!-- Row context menu -->\n @if (contextMenu(); as menu) {\n @if(hasContextMenuEntries()) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @if (!treeConfig()) {\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n }\n @if (!readonlyGrid()) {\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">\n {{ localeText().deleteRow }}\n </button>\n }\n </div>\n }\n }\n\n <!-- Cell context menu -->\n @if (cellContextMenuState(); as menu) {\n @let col = getColDef(menu.field)!;\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyCellToClipboard(menu.rowIndex, col)\">{{\n localeText().copyCellValue\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyRowToClipboard(menu.rowIndex)\">{{ localeText().copyRow\n }}</button>\n @if (!treeConfig()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n }\n @if (allowAddRows() && !readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex)\">{{ localeText().insertRowAbove\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex + 1)\">{{\n localeText().insertRowBelow }}</button>\n }\n @if (!readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">{{\n localeText().deleteRow }}</button>\n }\n @if (cellMenuItems().length) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @for (item of cellMenuItems(); track $index) {\n @if (item === null) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" [class.ag-context-item--danger]=\"item.danger\"\n [disabled]=\"item.disabled\" (click)=\"runCellMenuItem(item, menu)\">{{\n item.label }}</button>\n }\n }\n }\n </div>\n }\n\n <!-- Group actions menu -->\n @if (groupActionsMenu(); as menu) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @for (action of groupActions(); track action.label) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onGroupAction(action, menu.label)\">\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n <!-- Filter dropdown -->\n @if (filterMenu(); as menu) {\n <agrid-column-menu [x]=\"menu.x\" [y]=\"menu.y\" [header]=\"getColDef(menu.field)?.header ?? menu.field\"\n [sortDir]=\"getSort(menu.field)\" [sortable]=\"sortOption() !== 'none'\" [showColumnActions]=\"!!control()\"\n [pinned]=\"getColumnPinState(menu.field)\" [groupable]=\"!!getColDef(menu.field)?.groupable\"\n [grouped]=\"isGroupedByField(menu.field)\" [filterable]=\"!!getColDef(menu.field)?.filterable\"\n [showValueFilter]=\"!serverSideFiltering() || !!getColDef(menu.field)?.values?.length\"\n [filterType]=\"getMenuFilterType(menu.field)\" [operator]=\"getMenuOperator(menu.field)\"\n [operand]=\"getMenuOperand(menu.field)\" [operand2]=\"getMenuOperand2(menu.field)\"\n (operatorChange)=\"onMenuOperatorChange(menu.field, $event)\"\n (operandChange)=\"onMenuOperandChange(menu.field, $event)\"\n (operand2Change)=\"onMenuOperand2Change(menu.field, $event)\" [search]=\"filterMenuSearch()\"\n [allSelected]=\"isMenuAllSelected(menu.field)\" [valueItems]=\"(serverSideFiltering() && !getColDef(menu.field)?.values?.length)\n || !getColDef(menu.field)?.filterable ? [] : columnMenuValueItems()\" [localeText]=\"localeText()\"\n [sortPriority]=\"getSortPriority(menu.field)\" [hasMultiSort]=\"hasMultiSort()\" (sort)=\"onMenuSort(menu.field, $event)\"\n (resetSort)=\"onMenuResetSort(menu.field, $event)\" (autosize)=\"onMenuAutosizeColumn(menu.field)\"\n (togglePin)=\"onMenuTogglePin(menu.field)\" (togglePinRight)=\"onMenuTogglePinRight(menu.field)\"\n (hide)=\"onMenuHideColumn(menu.field)\" (toggleGroup)=\"onMenuToggleGroupBy(menu.field)\"\n (clearFilter)=\"onMenuClearFilter(menu.field)\" (clearAll)=\"onMenuClearAll()\"\n (searchChange)=\"onFilterMenuSearch($event)\" (toggleAll)=\"onMenuToggleAll(menu.field)\"\n (toggleValue)=\"onMenuToggleValue(menu.field, $event)\" [aggregate]=\"getEffectiveAggregate(getColDef(menu.field)!)\"\n (setAggregate)=\"onMenuSetAggregate(menu.field, $event)\" />\n }\n\n @if (columnDragPreview(); as preview) {\n <div class=\"ag-column-drag-preview\" [style.left.px]=\"preview.x\" [style.top.px]=\"preview.y\"\n [style.width.px]=\"preview.width\" [style.height.px]=\"preview.height\">\n <span>{{ preview.label }}</span>\n </div>\n }\n</div>", styles: ["@layer agrid-defaults{:host{--agrid-color-text: #24292f;--agrid-color-text-muted: #57606a;--agrid-color-accent: #1a73e8;--agrid-color-accent-subtle: #e8f0fe;--agrid-color-accent-fg: #1558b0;--agrid-color-accent-border: #c8d8f8;--agrid-color-danger: #d1242f;--agrid-color-danger-subtle: #fff1f0;--agrid-color-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-subtle: #fafbfc;--agrid-color-bg-muted: #f6f8fa;--agrid-color-shadow: rgba(140, 149, 159, .2);--agrid-color-bg-stripe: #f0f2f5;--agrid-color-cell-changed: #f59e0b;--agrid-color-row-marked: #fff8c5}}:host{display:flex;flex-direction:column;min-height:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:var(--agrid-color-text)}.ag-wrapper{position:relative;display:flex;flex-direction:column;flex:1;min-height:0;border:1px solid var(--agrid-color-border);overflow:hidden;outline:none;border-radius:4px}.ag-state-overlay{position:absolute;inset:34px 0 0;display:flex;align-items:center;justify-content:center;color:var(--agrid-color-text-muted);background:color-mix(in srgb,var(--agrid-color-bg) 78%,transparent);pointer-events:none;z-index:3}.ag-header{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-bottom:2px solid var(--agrid-color-border)}.ag-header-groups{display:grid;flex-shrink:0;height:28px;background:color-mix(in srgb,var(--agrid-color-bg-muted) 72%,var(--agrid-color-bg));border-bottom:1px solid var(--agrid-color-border)}.ag-header-group-cell{display:flex;align-items:center;justify-content:center;min-width:0;padding:0 8px;overflow:hidden;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;color:var(--agrid-color-text);font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap;cursor:grab;-webkit-user-select:none;user-select:none;transition:opacity .1s ease}.ag-header-group-cell:last-child{border-right:none}.ag-header-group-cell--empty,.ag-header-group-cell--locked{cursor:default}.ag-header-group-cell--dragging{opacity:.12}.ag-header-cell{position:relative;display:flex;align-items:center;font-weight:600;border-right:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;-webkit-user-select:none;user-select:none;box-sizing:border-box}.ag-header-cell:last-child{border-right:none}.ag-header-cell-top{display:flex;align-items:center;flex:1;min-width:0;padding:0 6px;height:32px;overflow:hidden;white-space:nowrap}.ag-header-cell-label{overflow:hidden;text-overflow:ellipsis;flex:1}.ag-header--with-filters .ag-header-cell{flex-direction:column;align-items:stretch;height:auto;white-space:normal}.ag-header--with-filters .ag-header-cell-top{flex:0 0 32px}.ag-header-cell-filter{height:28px;display:flex;align-items:center;padding:0 2px;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-header-cell{cursor:grab;transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-row>agrid-cell,.ag-ghost-cell,.ag-footer-cell{transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-loading-row{grid-column:1 / -1;display:flex;align-items:center;padding:0 10px;box-sizing:border-box;border-bottom:1px solid var(--agrid-color-border)}.ag-loading-row span{width:min(220px,55%);height:8px;border-radius:999px;background:color-mix(in srgb,var(--agrid-color-text-muted) 18%,transparent)}.ag-header-cell--dragging{opacity:.12;cursor:grabbing}.ag-column-reorder-item--dragging{opacity:.12}.ag-header-cell--drop-before,.ag-header-cell--drop-after{z-index:1}.ag-column-drag-preview{position:fixed;z-index:1000;display:flex;align-items:flex-start;padding:8px 10px;border:1px solid var(--agrid-color-accent);border-radius:5px;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-bg) 94%,var(--agrid-color-accent));box-shadow:0 10px 28px var(--agrid-color-shadow);font-weight:600;line-height:16px;box-sizing:border-box;pointer-events:none;opacity:.96}.ag-sort-badge{font-size:11px;color:var(--agrid-color-accent);flex-shrink:0;line-height:1}.ag-sort-priority{font-size:9px;vertical-align:super;opacity:.75}.ag-resize-handle{position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;z-index:1}.ag-resize-handle:hover{background:var(--agrid-color-accent);opacity:.5}.ag-filter-input{flex:1;min-width:0;height:20px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 4px;background:var(--agrid-color-bg)}.ag-filter-condition-btn{flex:0 0 24px;height:20px;padding:0;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text-muted);cursor:pointer}.ag-filter-condition-btn:hover,.ag-filter-condition-btn:focus-visible{border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg)}.ag-filter-condition-btn--active{border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-filter-input:focus{border-color:var(--agrid-color-accent)}.ag-header-cell--filtered .ag-header-cell-label:after{content:\" \\25be\";font-size:9px;color:var(--agrid-color-accent)}.ag-main-area{display:flex;flex:1;min-height:0;overflow:hidden}.ag-grid-split{display:flex;flex:1;min-width:0;min-height:0;overflow:hidden}.ag-pinned-pane{flex-shrink:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-scroll-pane{flex:1;min-width:0;min-height:0;display:flex}.ag-horizontal-scroll{flex:1;min-width:0;min-height:0;overflow-x:auto;overflow-y:hidden;display:flex;flex-direction:column}.ag-body{flex:1;overflow-x:clip;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-horizontal-scroll{scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-body::-webkit-scrollbar,.ag-horizontal-scroll::-webkit-scrollbar{width:8px;height:8px}.ag-body::-webkit-scrollbar-track,.ag-horizontal-scroll::-webkit-scrollbar-track{background:transparent}.ag-body::-webkit-scrollbar-thumb,.ag-horizontal-scroll::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-body::-webkit-scrollbar-thumb:hover,.ag-horizontal-scroll::-webkit-scrollbar-thumb:hover{background:#00000052;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-pinned-body{overflow:hidden}.ag-right-pinned-body{overflow-y:auto}.ag-has-right-pane .ag-body:not(.ag-pinned-body){scrollbar-width:none}.ag-has-right-pane .ag-body:not(.ag-pinned-body)::-webkit-scrollbar{display:none}.ag-row{display:grid;position:relative}.ag-row--pending-delete>:not(.ag-delete-confirmation){opacity:.2;pointer-events:none;transition:opacity .14s ease}.ag-delete-confirmation{position:absolute;top:0;bottom:0;z-index:7;display:flex;align-items:center;justify-content:center;gap:8px;min-width:min(100%,320px);padding:2px 12px;box-sizing:border-box;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-danger-subtle) 88%,transparent);border-block:1px solid color-mix(in srgb,var(--agrid-color-danger) 35%,transparent)}.ag-delete-confirmation-text{font-weight:600;white-space:nowrap}.ag-delete-confirmation-btn{min-width:42px;padding:3px 9px;border:1px solid var(--agrid-color-border);border-radius:4px;color:var(--agrid-color-text);background:var(--agrid-color-bg);font:inherit;cursor:pointer}.ag-delete-confirmation-btn:hover,.ag-delete-confirmation-btn:focus-visible{border-color:var(--agrid-color-accent);outline:none}.ag-delete-confirmation-btn--yes{color:#fff;border-color:var(--agrid-color-danger);background:var(--agrid-color-danger)}.ag-delete-confirmation-btn--yes:hover,.ag-delete-confirmation-btn--yes:focus-visible{border-color:color-mix(in srgb,var(--agrid-color-danger) 75%,#000);background:color-mix(in srgb,var(--agrid-color-danger) 85%,#000)}:host(.ag-zebra) .ag-row--odd agrid-cell:not(.editing){background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-control-cell{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd:hover .ag-control-cell{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned-first{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned-first{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned-first{background:var(--agrid-color-accent-subtle)}.ag-row:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}.ag-row--marked agrid-cell:not(.editing),.ag-row--marked .ag-control-cell{background:var(--agrid-color-row-marked)}.ag-row--marked{box-shadow:inset 3px 0 #bf8700}.ag-row--selected.ag-row--marked{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--range-selected:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent-border)}.ag-row agrid-cell.ag-cell--find-match:not(.editing){background:#fff7cc}.ag-row agrid-cell.ag-cell--find-active:not(.editing){background:#ffe58a;box-shadow:inset 0 0 0 2px #b7791f}.ag-row agrid-cell.ag-cell--range-selected.selected:not(.editing){box-shadow:inset 0 0 0 2px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-preview:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing){position:relative}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing):after{content:\"\";position:absolute;right:1px;bottom:1px;width:6px;height:6px;background:var(--agrid-color-accent);border:1px solid var(--agrid-color-bg);box-sizing:border-box;cursor:crosshair;z-index:4}.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}.ag-row--selected{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-control-header{z-index:3;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted)}.ag-control-header .ag-header-cell-filter{background:var(--agrid-color-bg-subtle)}.ag-control-cell{z-index:2;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle);cursor:context-menu;display:flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;flex-direction:row-reverse}.ag-control-cell:after{content:\"\\22ee\";font-size:11px;color:var(--agrid-color-border);line-height:1}.ag-control-cell:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder{cursor:grab}.ag-control-cell--reorder:after{content:\"\\283f\";font-size:13px;color:var(--agrid-color-text)}.ag-control-cell--reorder:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder:active{cursor:grabbing}.ag-row-marker{position:relative;z-index:1;width:14px;height:14px;margin:0;accent-color:var(--agrid-color-accent);cursor:pointer}.ag-ghost-row{background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1.5px var(--agrid-color-accent);pointer-events:none;animation:ag-ghost-in .1s ease}@keyframes ag-ghost-in{0%{opacity:0}to{opacity:1}}.ag-ghost-handle:after{color:var(--agrid-color-accent)!important}.ag-ghost-cell{display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-right:1px solid var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-size:13px;box-sizing:border-box;-webkit-user-select:none;user-select:none}.ag-ghost-cell:last-child{border-right:none}.ag-pinned-row-spacer,.ag-pinned-group-spacer{height:100%;border-bottom:1px solid var(--agrid-color-border);box-sizing:border-box}.ag-pinned-group-spacer{background:var(--agrid-color-bg-muted)}.ag-context-menu{position:fixed;z-index:1000;background:var(--agrid-color-bg-subtle);border:1px solid var(--agrid-color-border);border-radius:6px;box-shadow:0 8px 24px var(--agrid-color-shadow);min-width:160px;padding:4px 0;font-size:13px}.ag-context-item{display:block;width:100%;padding:6px 16px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit}.ag-context-item:hover{background:var(--agrid-color-bg-muted)}.ag-context-separator{height:1px;background:var(--agrid-color-border);margin:3px 0}.ag-context-item--danger{color:var(--agrid-color-danger)}.ag-context-item--danger:hover{background:var(--agrid-color-danger-subtle)}.ag-group-header-row{display:flex;align-items:stretch;background:var(--agrid-color-bg-muted);border-bottom:1px solid var(--agrid-color-border);overflow:hidden}.ag-path-tree-content{display:flex;align-items:center;gap:4px;height:100%;box-sizing:border-box;color:var(--agrid-color-text);font-weight:600;border-bottom:1px solid var(--agrid-color-border)}.ag-path-tree-content .ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;color:inherit;background:transparent;cursor:pointer;font-size:10px;transition:transform .12s ease}.ag-path-tree-content .ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-group-header-content{display:flex;align-items:stretch;height:100%;width:100%;-webkit-user-select:none;user-select:none}.ag-group-header-main{display:flex;align-items:center;gap:6px;padding:0 10px;flex:1;min-width:0;cursor:pointer}.ag-group-header-main:hover{background:var(--agrid-color-bg-subtle)}.ag-group-description{font-size:11px;color:var(--agrid-color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-group-actions-btn{display:flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:none;border-radius:3px;cursor:pointer;color:var(--agrid-color-text-muted);font-size:14px;padding:0}.ag-group-actions-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-group-icon{font-size:9px;color:var(--agrid-color-text-muted);line-height:1;display:inline-block;transition:transform .15s ease}.ag-group-icon--expanded{transform:rotate(90deg)}.ag-group-label{font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-group-count{font-size:11px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:10px;padding:0 7px;line-height:16px}.ag-group-aggregates{display:inline-flex;align-items:center;gap:10px;margin-left:4px;flex-wrap:wrap}.ag-group-aggregate{font-size:11px;color:var(--agrid-color-text);white-space:nowrap}.ag-group-aggregate-col{color:var(--agrid-color-text-muted);margin-right:4px}.ag-group-aggregate-op{color:var(--agrid-color-text-muted);margin-right:3px}.ag-footer{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-top:2px solid var(--agrid-color-border)}.ag-footer-cell{display:flex;align-items:center;gap:4px;padding:0 6px;height:30px;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;overflow:hidden;white-space:nowrap;font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-footer-cell:last-child{border-right:none}.ag-footer-cell--control{background:var(--agrid-color-bg-subtle);border-right:1px solid var(--agrid-color-border)}.ag-footer-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-footer-label{font-size:10px;font-weight:400;color:var(--agrid-color-text-muted);flex-shrink:0}.ag-pagination{display:flex;align-items:center;gap:4px;padding:0 10px;height:34px;flex-shrink:0;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);font-size:12px;color:var(--agrid-color-text-muted)}.ag-page-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font-size:13px;cursor:pointer;padding:0;line-height:1}.ag-page-btn:hover:not(:disabled){background:var(--agrid-color-bg-muted);border-color:var(--agrid-color-text-muted)}.ag-page-btn:disabled{opacity:.35;cursor:default}.ag-page-info{padding:0 6px;font-weight:500;color:var(--agrid-color-text);min-width:48px;text-align:center}.ag-page-count{margin-left:auto;color:var(--agrid-color-text-muted)}.ag-add-row{border-bottom:1px dashed var(--agrid-color-border);color:var(--agrid-color-text-muted);cursor:pointer}.ag-add-row:hover .ag-add-row-label{background:var(--agrid-color-bg-muted)}.ag-add-row--selected .ag-add-row-label{outline:2px solid var(--agrid-color-accent);outline-offset:-2px}.ag-add-row-label{display:flex;align-items:center;gap:4px;height:100%;padding:0 6px;font-size:12px;-webkit-user-select:none;user-select:none}.ag-add-row-icon{font-size:16px;line-height:1;color:var(--agrid-color-text-muted)}.ag-pinned-pane--right{border-left:1px solid var(--agrid-color-border);border-right:none;box-shadow:-3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-header-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned-first{box-shadow:none}.ag-cell--pinned{background:var(--agrid-color-bg)}.ag-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-cell--pinned-first{box-shadow:none}.ag-row:hover .ag-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-row--selected .ag-cell--pinned,.ag-row .ag-cell--pinned.ag-cell--range-selected,.ag-row .ag-cell--pinned.ag-cell--fill-preview{background:var(--agrid-color-accent-subtle)}.ag-row .ag-cell--pinned.ag-cell--find-match{background:#fff7cc}.ag-row .ag-cell--pinned.ag-cell--find-active{background:#ffe58a}.ag-header-menu-btn{flex-shrink:0;width:16px;height:16px;padding:0;margin-right:2px;background:none;border:1px solid transparent;border-radius:3px;cursor:pointer;font-size:10px;color:var(--agrid-color-text-muted);display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity .12s;line-height:1}.ag-header-menu-btn--active{color:var(--agrid-color-accent);border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle)}.ag-header-menu-btn:hover{background:var(--agrid-color-bg);border-color:var(--agrid-color-border)}.ag-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;padding:6px 8px;border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle)}.ag-quick-filter{margin-left:auto;width:100%;max-width:260px;height:30px;padding:0 10px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-quick-filter:focus{border-color:var(--agrid-color-accent-border)}.ag-detail-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;cursor:pointer;font-size:9px;line-height:1;color:var(--agrid-color-text-muted);transition:transform .12s ease,color .12s ease}.ag-detail-toggle:hover{color:var(--agrid-color-text)}.ag-detail-toggle--expanded{transform:rotate(90deg)}.ag-detail-row{display:block;position:relative;box-sizing:border-box}.ag-detail-panel{box-sizing:border-box;height:100%;overflow:auto;padding:8px 12px;background:var(--agrid-color-bg-subtle);border-bottom:1px solid var(--agrid-color-border)}.ag-pinned-rows{position:relative;z-index:3;flex:none;background:var(--agrid-color-bg)}.ag-pinned-rows--top{box-shadow:0 2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows--bottom{box-shadow:0 -2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows .ag-row{background:var(--agrid-color-bg-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: AgridVariableRowSizeDirective, selector: "cdk-virtual-scroll-viewport[agridVariableRowSize]", inputs: ["agridVariableRowSize"] }, { kind: "component", type: AgridCellComponent, selector: "agrid-cell", inputs: ["col", "rowIndex", "colIndex", "value", "displayValueOverride", "row", "locale", "selected", "editing", "editable", "showInfoIcon", "error", "treeCell", "treeLevel", "treeExpandable", "treeExpanded", "seedChar", "selectTextOnEdit"], outputs: ["treeToggle", "activate", "startEdit", "booleanToggle", "infoClick", "draftChange"] }, { kind: "component", type: AgridMenuBarComponent, selector: "agrid-menu-bar", inputs: ["items", "context", "label", "openItemId"], outputs: ["action", "openItemIdChange"] }, { kind: "component", type: AgridColumnMenuComponent, selector: "agrid-column-menu", inputs: ["localeText", "x", "y", "header", "sortDir", "sortable", "showColumnActions", "pinned", "groupable", "grouped", "filterable", "showValueFilter", "filterType", "operator", "operand", "operand2", "search", "allSelected", "valueItems", "sortPriority", "hasMultiSort", "aggregate"], outputs: ["operatorChange", "operandChange", "operand2Change", "sort", "resetSort", "autosize", "togglePin", "togglePinRight", "hide", "toggleGroup", "clearFilter", "clearAll", "searchChange", "toggleAll", "toggleValue", "setAggregate"] }, { kind: "component", type: AgridFindPanelComponent, selector: "agrid-find-panel", inputs: ["localeText", "query", "matchCount", "activeIndex"], outputs: ["queryChange", "next", "previous", "close"] }, { kind: "component", type: AgridSidebarComponent, selector: "agrid-sidebar", inputs: ["open", "activeTab", "columns", "pivotColumns", "pivotConfig", "headerGroups", "row", "rowIndex", "hiddenColumns", "locale", "localeText", "readonlyGrid", "useSidebarEditor", "isCellEditable", "errors"], outputs: ["close", "tabChange", "toggleColumn", "toggleColumnGroup", "detailEdit", "save", "pivotChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7232
8036
  }
7233
8037
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AgridComponent, decorators: [{
7234
8038
  type: Component,
@@ -7245,8 +8049,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
7245
8049
  '[class.ag-zebra]': 'zebraStripes()',
7246
8050
  '[style.min-height]': 'minHeight()',
7247
8051
  '[style.max-height]': 'maxHeight()',
7248
- }, template: "<div #wrapper class=\"ag-wrapper\" tabindex=\"0\" role=\"grid\" [attr.aria-label]=\"localeText().grid\"\n [attr.aria-rowcount]=\"ariaRowCount()\" [attr.aria-colcount]=\"ariaColCount()\"\n [attr.aria-multiselectable]=\"rowSelection() === 'multi' ? 'true' : null\"\n [attr.aria-readonly]=\"readonlyGrid() ? 'true' : null\" [attr.aria-busy]=\"loading() ? 'true' : null\"\n (copy)=\"onCopy($event)\" (paste)=\"onPaste($event)\" (focusin)=\"onGridFocusIn($event)\"\n (click)=\"closeContextMenu(); closeCellContextMenu(); closeFilterMenu(); closeGroupActionsMenu(); closeMenuBarMenu()\">\n @if (visibleMenuBarItems().length > 0 || enableQuickFilter()) {\n <div class=\"ag-toolbar\">\n @if (visibleMenuBarItems().length > 0) {\n <agrid-menu-bar [items]=\"visibleMenuBarItems()\" [context]=\"menuBarContext()\"\n [label]=\"localeText().actions\" [openItemId]=\"openMenuBarItemId()\"\n (openItemIdChange)=\"onMenuBarOpenItemChange($event)\" (action)=\"menuBarAction.emit($event)\" />\n }\n @if (enableQuickFilter()) {\n <input class=\"ag-quick-filter\" type=\"search\" [value]=\"quickFilterValue()\" (input)=\"onQuickFilterInput($event)\"\n [placeholder]=\"localeText().quickFilterPlaceholder\" [attr.aria-label]=\"localeText().quickFilterPlaceholder\" />\n }\n </div>\n }\n <div class=\"ag-main-area\">\n <div class=\"ag-grid-split\" [class.ag-has-right-pane]=\"hasRightPinnedPane()\">\n @if (hasPinnedPane()) {\n <div class=\"ag-pinned-pane\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups ag-header-groups--pinned\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-group-cell ag-header-group-cell--empty\"></div>\n }\n @for (run of pinnedHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header ag-header--pinned\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-cell ag-control-header\" role=\"columnheader\" aria-colindex=\"1\">\n <div class=\"ag-header-cell-top\"></div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\"></div>\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-last]=\"isLastPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}</span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\"\n (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #leftRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.data-control]=\"true\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isPinnedPaneRowSelected(item)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || item === 'ghost' ? pinnedGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell\" role=\"rowheader\" aria-colindex=\"1\"\n [class.ag-control-cell--reorder]=\"allowRowReorder()\"\n (contextmenu)=\"onControlContextMenu($event, item.originalIndex)\" (click)=\"$event.stopPropagation()\"\n (pointerdown)=\"onControlPointerDown($event, item.originalIndex)\">\n @if (canToggleDetail(item)) {\n <button class=\"ag-detail-toggle\" type=\"button\"\n [class.ag-detail-toggle--expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-label]=\"localeText().toggleDetail\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"onDetailToggle(item.originalIndex); $event.stopPropagation()\">\u25B6</button>\n }\n @if (enableRowMarking()) {\n <input class=\"ag-row-marker\" type=\"checkbox\" [checked]=\"isRowMarked(item.originalIndex)\"\n [attr.aria-label]=\"localeText().markRow\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\" (change)=\"toggleRowMarked(item.originalIndex)\" />\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\"\n [row]=\"item.row\" [locale]=\"locale()\" [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [attr.data-col-field]=\"col.field\" [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex)\" [error]=\"cellValidationError(item.originalIndex, ci)\"\n [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\"\n [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\" [class.ag-cell--pinned]=\"true\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell ag-control-cell--reorder ag-ghost-handle\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #pinnedViewport class=\"ag-body ag-pinned-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.width.px]=\"pinnedPaneWidth()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\">\n @if (showControlColumn()) {\n <div class=\"ag-footer-cell ag-footer-cell--control\" role=\"gridcell\" aria-colindex=\"1\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"ag-scroll-pane\">\n <div #horizontalScroller class=\"ag-horizontal-scroll\" (scroll)=\"onHorizontalScroll()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (run of scrollableHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-header-cell\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.data-col-field]=\"col.field\" (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}</span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">\u229F</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\"\n (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #bodyRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || item === 'ghost' ? scrollableGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @for (col of scrollableColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\"\n [row]=\"item.row\" [locale]=\"locale()\" [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [attr.data-col-field]=\"col.field\" [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex)\" [error]=\"cellValidationError(item.originalIndex, ci)\"\n [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\"\n [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\"\n (startEdit)=\"onStartEdit(item.originalIndex, ci)\" (draftChange)=\"onDraftChange($event)\"\n (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\" (treeToggle)=\"onTreeToggle(item)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (item === null) {\n <div class=\"ag-add-row-label\" (click)=\"onActivateAddRow()\">\n <span class=\"ag-add-row-icon\">+</span> {{ localeText().addRow }}\n </div>\n } @else if (item === 'ghost') {\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-ghost-cell\" [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else if (isDetailRowItem(item)) {\n <div class=\"ag-detail-panel\" [style.height.px]=\"detailRowHeight()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" [innerHTML]=\"detailHtml(item)\"></div>\n } @else if (isPathTreeNodeItem(item)) {\n <div class=\"ag-path-tree-content\" [style.min-width.px]=\"scrollableTotalWidth()\"\n [style.padding-left.px]=\"treeRowLevel(item) * 16 + 8\"\n (click)=\"onTreeNodeClick(item); $event.stopPropagation()\"\n (dblclick)=\"onTreeNodeDoubleClick(item); $event.stopPropagation()\">\n <button type=\"button\" class=\"ag-tree-twisty\"\n [class.ag-tree-twisty--expanded]=\"treeRowExpanded(item)\"\n [attr.aria-expanded]=\"treeRowExpanded(item)\"\n (click)=\"onTreeToggle(item); $event.stopPropagation()\">\u25B6</button>\n <span>{{ pathTreeLabel(item) }}</span>\n </div>\n } @else {\n <div class=\"ag-group-header-content\" [style.min-width.px]=\"scrollableTotalWidth()\">\n <div class=\"ag-group-header-main\"\n (click)=\"onGroupHeaderClick(item.groupLabel); $event.stopPropagation()\">\n <span class=\"ag-group-icon\" [class.ag-group-icon--expanded]=\"!item.collapsed\">\u25B6</span>\n <span class=\"ag-group-label\">{{ item.groupLabel }}</span>\n @if (groupActions().length > 0) {\n <button class=\"ag-group-actions-btn\" [title]=\"localeText().actions\"\n (click)=\"openGroupActionsMenu($event, item.groupLabel)\">\u22EE</button>\n }\n <span class=\"ag-group-count\">{{ item.count }}</span>\n @if (getGroupDescription(item.groupLabel); as desc) {\n <span class=\"ag-group-description\">{{ desc }}</span>\n }\n @if (item.aggregates; as aggs) {\n <span class=\"ag-group-aggregates\">\n @for (col of visibleColDefs(); track col.field) {\n @if (hasAggregate(col) && aggs[col.field] !== undefined) {\n <span class=\"ag-group-aggregate\">\n <span class=\"ag-group-aggregate-col\">{{ col.header }}</span>\n <span class=\"ag-group-aggregate-op\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, aggs[col.field]) }}\n </span>\n }\n }\n </span>\n }\n </div>\n </div>\n }\n @if (isDataRowItem(item) && isRowPendingDelete(item.originalIndex)) {\n <div class=\"ag-delete-confirmation\" role=\"alertdialog\" [attr.aria-label]=\"localeText().confirmDeleteRow\"\n [style.left.px]=\"deleteConfirmationLeft()\" [style.width.px]=\"deleteConfirmationWidth() || null\"\n (pointerdown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\n <span class=\"ag-delete-confirmation-text\">{{ localeText().confirmDeleteRow }}</span>\n <button class=\"ag-delete-confirmation-btn ag-delete-confirmation-btn--yes\" type=\"button\"\n (click)=\"confirmPendingRowDelete()\">{{ localeText().confirmYes }}</button>\n <button class=\"ag-delete-confirmation-btn\" type=\"button\" data-delete-confirm-no\n (click)=\"cancelRowDelete()\">{{ localeText().confirmNo }}</button>\n </div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #scrollViewport class=\"ag-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" (scroll)=\"onBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (hasRightPinnedPane()) {\n <div class=\"ag-pinned-pane ag-pinned-pane--right\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (run of rightHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}</span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">'\u229F '</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\"\n (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #rightRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-ghost-row]=\"item === 'ghost'\" [class.ag-group-header-row]=\"isGroupHeaderItem(item)\"\n [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || item === 'ghost' ? rightGridTemplateColumns() : null\">\n @if (isDataRowItem(item)) {\n @for (col of rightPinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\"\n [row]=\"item.row\" [locale]=\"locale()\" [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [attr.data-col-field]=\"col.field\" [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex)\" [error]=\"cellValidationError(item.originalIndex, ci)\"\n [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\"\n [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\" [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--pinned]=\"true\" [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">{{ getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #rightPinnedViewport class=\"ag-body ag-pinned-body ag-right-pinned-body\"\n [agridVariableRowSize]=\"itemSizes()\" [style.width.px]=\"rightPinnedPaneWidth()\"\n (scroll)=\"onRightPinnedBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (loading()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ localeText().loading }}</div>\n } @else if (isEmpty()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ emptyTextLabel() }}</div>\n }\n\n @if (showSidebar()) {\n <agrid-sidebar [open]=\"sidebarOpen()\" [activeTab]=\"sidebarTab()\" [columns]=\"colDefs()\"\n [headerGroups]=\"headerGroups()\" [row]=\"sidebarRow()\" [rowIndex]=\"selectedRowIndex()\"\n [hiddenColumns]=\"sidebarHiddenColumns()\" [locale]=\"locale()\"\n [localeText]=\"localeText()\" [readonlyGrid]=\"readonlyGrid()\" [useSidebarEditor]=\"useSidebarEditor()\"\n [isCellEditable]=\"isCellEditableForRow\"\n [errors]=\"sidebarValidationErrors()\" (close)=\"toggleSidebar()\" (tabChange)=\"onSidebarStripClick($event)\"\n (toggleColumn)=\"onSidebarToggleColumn($event)\"\n (toggleColumnGroup)=\"onSidebarToggleColumnGroup($event.fields, $event.visible)\"\n (detailEdit)=\"onSidebarDetailEdit($event)\" (save)=\"onSidebarDetailSave($event)\" />\n }\n </div><!-- /.ag-main-area -->\n\n @if (showPagination()) {\n <nav class=\"ag-pagination\" [attr.aria-label]=\"localeText().pagination\">\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().firstPage\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToFirstPage()\">\u00AB</button>\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().previous\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToPrevPage()\">\u2039</button>\n <span class=\"ag-page-info\" aria-live=\"polite\">{{ control()!.currentPage() }} / {{ totalPages() }}</span>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().next\" (click)=\"goToNextPage()\">\u203A</button>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().lastPage\" (click)=\"goToLastPage()\">\u00BB</button>\n <span class=\"ag-page-count\">{{ localeText().rows(filteredRowCount()) }}</span>\n </nav>\n }\n\n @if (findOpen()) {\n <agrid-find-panel [query]=\"findQuery()\" [matchCount]=\"findMatches().length\" [activeIndex]=\"findActiveIndex()\"\n [localeText]=\"localeText()\" (queryChange)=\"onFindInput($event)\" (previous)=\"goToFindMatch(-1)\"\n (next)=\"goToFindMatch(1)\" (close)=\"closeFind()\" />\n }\n\n <!-- Row context menu -->\n @if (contextMenu(); as menu) {\n @if(hasContextMenuEntries()) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @if (!treeConfig()) {\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n }\n @if (!readonlyGrid()) {\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">\n {{ localeText().deleteRow }}\n </button>\n }\n </div>\n }\n }\n\n <!-- Cell context menu -->\n @if (cellContextMenuState(); as menu) {\n @let col = getColDef(menu.field)!;\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyCellToClipboard(menu.rowIndex, col)\">{{\n localeText().copyCellValue\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyRowToClipboard(menu.rowIndex)\">{{ localeText().copyRow\n }}</button>\n @if (!treeConfig()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n }\n @if (allowAddRows() && !readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex)\">{{ localeText().insertRowAbove\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex + 1)\">{{\n localeText().insertRowBelow }}</button>\n }\n @if (!readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">{{\n localeText().deleteRow }}</button>\n }\n @if (cellMenuItems().length) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @for (item of cellMenuItems(); track $index) {\n @if (item === null) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" [class.ag-context-item--danger]=\"item.danger\"\n [disabled]=\"item.disabled\" (click)=\"runCellMenuItem(item, menu)\">{{\n item.label }}</button>\n }\n }\n }\n </div>\n }\n\n <!-- Group actions menu -->\n @if (groupActionsMenu(); as menu) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @for (action of groupActions(); track action.label) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onGroupAction(action, menu.label)\">\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n <!-- Filter dropdown -->\n @if (filterMenu(); as menu) {\n <agrid-column-menu [x]=\"menu.x\" [y]=\"menu.y\" [header]=\"getColDef(menu.field)?.header ?? menu.field\"\n [sortDir]=\"getSort(menu.field)\" [sortable]=\"sortOption() !== 'none'\" [showColumnActions]=\"!!control()\"\n [pinned]=\"getColumnPinState(menu.field)\" [groupable]=\"!!getColDef(menu.field)?.groupable\"\n [grouped]=\"isGroupedByField(menu.field)\" [filterable]=\"!!getColDef(menu.field)?.filterable\"\n [showValueFilter]=\"!serverSideFiltering()\" [filterType]=\"getMenuFilterType(menu.field)\"\n [operator]=\"getMenuOperator(menu.field)\" [operand]=\"getMenuOperand(menu.field)\"\n [operand2]=\"getMenuOperand2(menu.field)\" (operatorChange)=\"onMenuOperatorChange(menu.field, $event)\"\n (operandChange)=\"onMenuOperandChange(menu.field, $event)\"\n (operand2Change)=\"onMenuOperand2Change(menu.field, $event)\" [search]=\"filterMenuSearch()\"\n [allSelected]=\"isMenuAllSelected(menu.field)\"\n [valueItems]=\"serverSideFiltering() || !getColDef(menu.field)?.filterable ? [] : columnMenuValueItems()\"\n [localeText]=\"localeText()\" [sortPriority]=\"getSortPriority(menu.field)\" [hasMultiSort]=\"hasMultiSort()\"\n (sort)=\"onMenuSort(menu.field, $event)\" (resetSort)=\"onMenuResetSort(menu.field, $event)\"\n (autosize)=\"onMenuAutosizeColumn(menu.field)\" (togglePin)=\"onMenuTogglePin(menu.field)\"\n (togglePinRight)=\"onMenuTogglePinRight(menu.field)\" (hide)=\"onMenuHideColumn(menu.field)\"\n (toggleGroup)=\"onMenuToggleGroupBy(menu.field)\" (clearFilter)=\"onMenuClearFilter(menu.field)\"\n (clearAll)=\"onMenuClearAll()\" (searchChange)=\"onFilterMenuSearch($event)\" (toggleAll)=\"onMenuToggleAll(menu.field)\"\n (toggleValue)=\"onMenuToggleValue(menu.field, $event)\" [aggregate]=\"getEffectiveAggregate(getColDef(menu.field)!)\"\n (setAggregate)=\"onMenuSetAggregate(menu.field, $event)\" />\n }\n\n @if (columnDragPreview(); as preview) {\n <div class=\"ag-column-drag-preview\" [style.left.px]=\"preview.x\" [style.top.px]=\"preview.y\"\n [style.width.px]=\"preview.width\" [style.height.px]=\"preview.height\">\n <span>{{ preview.label }}</span>\n </div>\n }\n</div>\n", styles: ["@layer agrid-defaults{:host{--agrid-color-text: #24292f;--agrid-color-text-muted: #57606a;--agrid-color-accent: #1a73e8;--agrid-color-accent-subtle: #e8f0fe;--agrid-color-accent-fg: #1558b0;--agrid-color-accent-border: #c8d8f8;--agrid-color-danger: #d1242f;--agrid-color-danger-subtle: #fff1f0;--agrid-color-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-subtle: #fafbfc;--agrid-color-bg-muted: #f6f8fa;--agrid-color-shadow: rgba(140, 149, 159, .2);--agrid-color-bg-stripe: #f0f2f5;--agrid-color-cell-changed: #f59e0b;--agrid-color-row-marked: #fff8c5}}:host{display:flex;flex-direction:column;min-height:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:var(--agrid-color-text)}.ag-wrapper{position:relative;display:flex;flex-direction:column;flex:1;min-height:0;border:1px solid var(--agrid-color-border);overflow:hidden;outline:none;border-radius:4px}.ag-state-overlay{position:absolute;inset:34px 0 0;display:flex;align-items:center;justify-content:center;color:var(--agrid-color-text-muted);background:color-mix(in srgb,var(--agrid-color-bg) 78%,transparent);pointer-events:none;z-index:3}.ag-header{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-bottom:2px solid var(--agrid-color-border)}.ag-header-groups{display:grid;flex-shrink:0;height:28px;background:color-mix(in srgb,var(--agrid-color-bg-muted) 72%,var(--agrid-color-bg));border-bottom:1px solid var(--agrid-color-border)}.ag-header-group-cell{display:flex;align-items:center;justify-content:center;min-width:0;padding:0 8px;overflow:hidden;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;color:var(--agrid-color-text);font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap;cursor:grab;-webkit-user-select:none;user-select:none;transition:opacity .1s ease}.ag-header-group-cell:last-child{border-right:none}.ag-header-group-cell--empty,.ag-header-group-cell--locked{cursor:default}.ag-header-group-cell--dragging{opacity:.12}.ag-header-cell{position:relative;display:flex;align-items:center;font-weight:600;border-right:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;-webkit-user-select:none;user-select:none;box-sizing:border-box}.ag-header-cell:last-child{border-right:none}.ag-header-cell-top{display:flex;align-items:center;flex:1;min-width:0;padding:0 6px;height:32px;overflow:hidden;white-space:nowrap}.ag-header-cell-label{overflow:hidden;text-overflow:ellipsis;flex:1}.ag-header--with-filters .ag-header-cell{flex-direction:column;align-items:stretch;height:auto;white-space:normal}.ag-header--with-filters .ag-header-cell-top{flex:0 0 32px}.ag-header-cell-filter{height:28px;display:flex;align-items:center;padding:0 2px;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-header-cell{cursor:grab;transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-row>agrid-cell,.ag-ghost-cell,.ag-footer-cell{transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-header-cell--dragging{opacity:.12;cursor:grabbing}.ag-column-reorder-item--dragging{opacity:.12}.ag-header-cell--drop-before,.ag-header-cell--drop-after{z-index:1}.ag-column-drag-preview{position:fixed;z-index:1000;display:flex;align-items:flex-start;padding:8px 10px;border:1px solid var(--agrid-color-accent);border-radius:5px;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-bg) 94%,var(--agrid-color-accent));box-shadow:0 10px 28px var(--agrid-color-shadow);font-weight:600;line-height:16px;box-sizing:border-box;pointer-events:none;opacity:.96}.ag-sort-badge{font-size:11px;color:var(--agrid-color-accent);flex-shrink:0;line-height:1}.ag-sort-priority{font-size:9px;vertical-align:super;opacity:.75}.ag-resize-handle{position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;z-index:1}.ag-resize-handle:hover{background:var(--agrid-color-accent);opacity:.5}.ag-filter-input{flex:1;min-width:0;height:20px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 4px;background:var(--agrid-color-bg)}.ag-filter-condition-btn{flex:0 0 24px;height:20px;padding:0;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text-muted);cursor:pointer}.ag-filter-condition-btn:hover,.ag-filter-condition-btn:focus-visible{border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg)}.ag-filter-condition-btn--active{border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-filter-input:focus{border-color:var(--agrid-color-accent)}.ag-header-cell--filtered .ag-header-cell-label:after{content:\" \\25be\";font-size:9px;color:var(--agrid-color-accent)}.ag-main-area{display:flex;flex:1;min-height:0;overflow:hidden}.ag-grid-split{display:flex;flex:1;min-width:0;min-height:0;overflow:hidden}.ag-pinned-pane{flex-shrink:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-scroll-pane{flex:1;min-width:0;min-height:0;display:flex}.ag-horizontal-scroll{flex:1;min-width:0;min-height:0;overflow-x:auto;overflow-y:hidden;display:flex;flex-direction:column}.ag-body{flex:1;overflow-x:clip;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-horizontal-scroll{scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-body::-webkit-scrollbar,.ag-horizontal-scroll::-webkit-scrollbar{width:8px;height:8px}.ag-body::-webkit-scrollbar-track,.ag-horizontal-scroll::-webkit-scrollbar-track{background:transparent}.ag-body::-webkit-scrollbar-thumb,.ag-horizontal-scroll::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-body::-webkit-scrollbar-thumb:hover,.ag-horizontal-scroll::-webkit-scrollbar-thumb:hover{background:#00000052;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-pinned-body{overflow:hidden}.ag-right-pinned-body{overflow-y:auto}.ag-has-right-pane .ag-body:not(.ag-pinned-body){scrollbar-width:none}.ag-has-right-pane .ag-body:not(.ag-pinned-body)::-webkit-scrollbar{display:none}.ag-row{display:grid;position:relative}.ag-row--pending-delete>:not(.ag-delete-confirmation){opacity:.2;pointer-events:none;transition:opacity .14s ease}.ag-delete-confirmation{position:absolute;top:0;bottom:0;z-index:7;display:flex;align-items:center;justify-content:center;gap:8px;min-width:min(100%,320px);padding:2px 12px;box-sizing:border-box;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-danger-subtle) 88%,transparent);border-block:1px solid color-mix(in srgb,var(--agrid-color-danger) 35%,transparent)}.ag-delete-confirmation-text{font-weight:600;white-space:nowrap}.ag-delete-confirmation-btn{min-width:42px;padding:3px 9px;border:1px solid var(--agrid-color-border);border-radius:4px;color:var(--agrid-color-text);background:var(--agrid-color-bg);font:inherit;cursor:pointer}.ag-delete-confirmation-btn:hover,.ag-delete-confirmation-btn:focus-visible{border-color:var(--agrid-color-accent);outline:none}.ag-delete-confirmation-btn--yes{color:#fff;border-color:var(--agrid-color-danger);background:var(--agrid-color-danger)}.ag-delete-confirmation-btn--yes:hover,.ag-delete-confirmation-btn--yes:focus-visible{border-color:color-mix(in srgb,var(--agrid-color-danger) 75%,#000);background:color-mix(in srgb,var(--agrid-color-danger) 85%,#000)}:host(.ag-zebra) .ag-row--odd agrid-cell:not(.editing){background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-control-cell{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd:hover .ag-control-cell{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned-first{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned-first{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned-first{background:var(--agrid-color-accent-subtle)}.ag-row:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}.ag-row--marked agrid-cell:not(.editing),.ag-row--marked .ag-control-cell{background:var(--agrid-color-row-marked)}.ag-row--marked{box-shadow:inset 3px 0 #bf8700}.ag-row--selected.ag-row--marked{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--range-selected:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent-border)}.ag-row agrid-cell.ag-cell--find-match:not(.editing){background:#fff7cc}.ag-row agrid-cell.ag-cell--find-active:not(.editing){background:#ffe58a;box-shadow:inset 0 0 0 2px #b7791f}.ag-row agrid-cell.ag-cell--range-selected.selected:not(.editing){box-shadow:inset 0 0 0 2px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-preview:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing){position:relative}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing):after{content:\"\";position:absolute;right:1px;bottom:1px;width:6px;height:6px;background:var(--agrid-color-accent);border:1px solid var(--agrid-color-bg);box-sizing:border-box;cursor:crosshair;z-index:4}.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}.ag-row--selected{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-control-header{z-index:3;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted)}.ag-control-header .ag-header-cell-filter{background:var(--agrid-color-bg-subtle)}.ag-control-cell{z-index:2;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle);cursor:context-menu;display:flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;flex-direction:row-reverse}.ag-control-cell:after{content:\"\\22ee\";font-size:11px;color:var(--agrid-color-border);line-height:1}.ag-control-cell:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder{cursor:grab}.ag-control-cell--reorder:after{content:\"\\283f\";font-size:13px;color:var(--agrid-color-text)}.ag-control-cell--reorder:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder:active{cursor:grabbing}.ag-row-marker{position:relative;z-index:1;width:14px;height:14px;margin:0;accent-color:var(--agrid-color-accent);cursor:pointer}.ag-ghost-row{background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1.5px var(--agrid-color-accent);pointer-events:none;animation:ag-ghost-in .1s ease}@keyframes ag-ghost-in{0%{opacity:0}to{opacity:1}}.ag-ghost-handle:after{color:var(--agrid-color-accent)!important}.ag-ghost-cell{display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-right:1px solid var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-size:13px;box-sizing:border-box;-webkit-user-select:none;user-select:none}.ag-ghost-cell:last-child{border-right:none}.ag-pinned-row-spacer,.ag-pinned-group-spacer{height:100%;border-bottom:1px solid var(--agrid-color-border);box-sizing:border-box}.ag-pinned-group-spacer{background:var(--agrid-color-bg-muted)}.ag-context-menu{position:fixed;z-index:1000;background:var(--agrid-color-bg-subtle);border:1px solid var(--agrid-color-border);border-radius:6px;box-shadow:0 8px 24px var(--agrid-color-shadow);min-width:160px;padding:4px 0;font-size:13px}.ag-context-item{display:block;width:100%;padding:6px 16px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit}.ag-context-item:hover{background:var(--agrid-color-bg-muted)}.ag-context-separator{height:1px;background:var(--agrid-color-border);margin:3px 0}.ag-context-item--danger{color:var(--agrid-color-danger)}.ag-context-item--danger:hover{background:var(--agrid-color-danger-subtle)}.ag-group-header-row{display:flex;align-items:stretch;background:var(--agrid-color-bg-muted);border-bottom:1px solid var(--agrid-color-border);overflow:hidden}.ag-path-tree-content{display:flex;align-items:center;gap:4px;height:100%;box-sizing:border-box;color:var(--agrid-color-text);font-weight:600;border-bottom:1px solid var(--agrid-color-border)}.ag-path-tree-content .ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;color:inherit;background:transparent;cursor:pointer;font-size:10px;transition:transform .12s ease}.ag-path-tree-content .ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-group-header-content{display:flex;align-items:stretch;height:100%;width:100%;-webkit-user-select:none;user-select:none}.ag-group-header-main{display:flex;align-items:center;gap:6px;padding:0 10px;flex:1;min-width:0;cursor:pointer}.ag-group-header-main:hover{background:var(--agrid-color-bg-subtle)}.ag-group-description{font-size:11px;color:var(--agrid-color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-group-actions-btn{display:flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:none;border-radius:3px;cursor:pointer;color:var(--agrid-color-text-muted);font-size:14px;padding:0}.ag-group-actions-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-group-icon{font-size:9px;color:var(--agrid-color-text-muted);line-height:1;display:inline-block;transition:transform .15s ease}.ag-group-icon--expanded{transform:rotate(90deg)}.ag-group-label{font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-group-count{font-size:11px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:10px;padding:0 7px;line-height:16px}.ag-group-aggregates{display:inline-flex;align-items:center;gap:10px;margin-left:4px;flex-wrap:wrap}.ag-group-aggregate{font-size:11px;color:var(--agrid-color-text);white-space:nowrap}.ag-group-aggregate-col{color:var(--agrid-color-text-muted);margin-right:4px}.ag-group-aggregate-op{color:var(--agrid-color-text-muted);margin-right:3px}.ag-footer{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-top:2px solid var(--agrid-color-border)}.ag-footer-cell{display:flex;align-items:center;gap:4px;padding:0 6px;height:30px;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;overflow:hidden;white-space:nowrap;font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-footer-cell:last-child{border-right:none}.ag-footer-cell--control{background:var(--agrid-color-bg-subtle);border-right:1px solid var(--agrid-color-border)}.ag-footer-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-footer-label{font-size:10px;font-weight:400;color:var(--agrid-color-text-muted);flex-shrink:0}.ag-pagination{display:flex;align-items:center;gap:4px;padding:0 10px;height:34px;flex-shrink:0;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);font-size:12px;color:var(--agrid-color-text-muted)}.ag-page-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font-size:13px;cursor:pointer;padding:0;line-height:1}.ag-page-btn:hover:not(:disabled){background:var(--agrid-color-bg-muted);border-color:var(--agrid-color-text-muted)}.ag-page-btn:disabled{opacity:.35;cursor:default}.ag-page-info{padding:0 6px;font-weight:500;color:var(--agrid-color-text);min-width:48px;text-align:center}.ag-page-count{margin-left:auto;color:var(--agrid-color-text-muted)}.ag-add-row{border-bottom:1px dashed var(--agrid-color-border);color:var(--agrid-color-text-muted);cursor:pointer}.ag-add-row:hover .ag-add-row-label{background:var(--agrid-color-bg-muted)}.ag-add-row--selected .ag-add-row-label{outline:2px solid var(--agrid-color-accent);outline-offset:-2px}.ag-add-row-label{display:flex;align-items:center;gap:4px;height:100%;padding:0 6px;font-size:12px;-webkit-user-select:none;user-select:none}.ag-add-row-icon{font-size:16px;line-height:1;color:var(--agrid-color-text-muted)}.ag-pinned-pane--right{border-left:1px solid var(--agrid-color-border);border-right:none;box-shadow:-3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-header-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned-first{box-shadow:none}.ag-cell--pinned{background:var(--agrid-color-bg)}.ag-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-cell--pinned-first{box-shadow:none}.ag-row:hover .ag-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-row--selected .ag-cell--pinned,.ag-row .ag-cell--pinned.ag-cell--range-selected,.ag-row .ag-cell--pinned.ag-cell--fill-preview{background:var(--agrid-color-accent-subtle)}.ag-row .ag-cell--pinned.ag-cell--find-match{background:#fff7cc}.ag-row .ag-cell--pinned.ag-cell--find-active{background:#ffe58a}.ag-header-menu-btn{flex-shrink:0;width:16px;height:16px;padding:0;margin-right:2px;background:none;border:1px solid transparent;border-radius:3px;cursor:pointer;font-size:10px;color:var(--agrid-color-text-muted);display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity .12s;line-height:1}.ag-header-menu-btn--active{color:var(--agrid-color-accent);border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle)}.ag-header-menu-btn:hover{background:var(--agrid-color-bg);border-color:var(--agrid-color-border)}.ag-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;padding:6px 8px;border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle)}.ag-quick-filter{margin-left:auto;width:100%;max-width:260px;height:30px;padding:0 10px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-quick-filter:focus{border-color:var(--agrid-color-accent-border)}.ag-detail-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;cursor:pointer;font-size:9px;line-height:1;color:var(--agrid-color-text-muted);transition:transform .12s ease,color .12s ease}.ag-detail-toggle:hover{color:var(--agrid-color-text)}.ag-detail-toggle--expanded{transform:rotate(90deg)}.ag-detail-row{display:block;position:relative;box-sizing:border-box}.ag-detail-panel{box-sizing:border-box;height:100%;overflow:auto;padding:8px 12px;background:var(--agrid-color-bg-subtle);border-bottom:1px solid var(--agrid-color-border)}.ag-pinned-rows{position:relative;z-index:3;flex:none;background:var(--agrid-color-bg)}.ag-pinned-rows--top{box-shadow:0 2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows--bottom{box-shadow:0 -2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows .ag-row{background:var(--agrid-color-bg-subtle)}\n"] }]
7249
- }], ctorParameters: () => [], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], recordEdit: [{ type: i0.Output, args: ["recordEdit"] }], rowRemoved: [{ type: i0.Output, args: ["rowRemoved"] }], prepareAddRecord: [{ type: i0.Output, args: ["prepareAddRecord"] }], rowReorder: [{ type: i0.Output, args: ["rowReorder"] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }], rowDoubleClicked: [{ type: i0.Output, args: ["rowDoubleClicked"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], treeNodeClick: [{ type: i0.Output, args: ["treeNodeClick"] }], treeNodeDoubleClicked: [{ type: i0.Output, args: ["treeNodeDoubleClicked"] }], rowChanged: [{ type: i0.Output, args: ["rowChanged"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], quickFilterChange: [{ type: i0.Output, args: ["quickFilterChange"] }], validationFailed: [{ type: i0.Output, args: ["validationFailed"] }], cellInfo: [{ type: i0.Output, args: ["cellInfo"] }], menuBarAction: [{ type: i0.Output, args: ["menuBarAction"] }], viewport: [{ type: i0.ViewChild, args: ['scrollViewport', { isSignal: true }] }], pinnedViewport: [{ type: i0.ViewChild, args: ['pinnedViewport', { isSignal: true }] }], rightPinnedViewport: [{ type: i0.ViewChild, args: ['rightPinnedViewport', { isSignal: true }] }], wrapperEl: [{ type: i0.ViewChild, args: ['wrapper', { isSignal: true }] }], horizontalScrollerEl: [{ type: i0.ViewChild, args: ['horizontalScroller', { isSignal: true }] }] } });
8052
+ }, template: "<div #wrapper class=\"ag-wrapper\" tabindex=\"0\" role=\"grid\" [attr.aria-label]=\"localeText().grid\"\n [attr.aria-rowcount]=\"ariaRowCount()\" [attr.aria-colcount]=\"ariaColCount()\"\n [attr.aria-multiselectable]=\"rowSelection() === 'multi' ? 'true' : null\"\n [attr.aria-readonly]=\"readonlyGrid() ? 'true' : null\" [attr.aria-busy]=\"loading() ? 'true' : null\"\n (copy)=\"onCopy($event)\" (paste)=\"onPaste($event)\" (focusin)=\"onGridFocusIn($event)\"\n (click)=\"closeContextMenu(); closeCellContextMenu(); closeFilterMenu(); closeGroupActionsMenu(); closeMenuBarMenu()\">\n @if (visibleMenuBarItems().length > 0 || enableQuickFilter()) {\n <div class=\"ag-toolbar\">\n @if (visibleMenuBarItems().length > 0) {\n <agrid-menu-bar [items]=\"visibleMenuBarItems()\" [context]=\"menuBarContext()\" [label]=\"localeText().actions\"\n [openItemId]=\"openMenuBarItemId()\" (openItemIdChange)=\"onMenuBarOpenItemChange($event)\"\n (action)=\"menuBarAction.emit($event)\" />\n }\n @if (enableQuickFilter()) {\n <input class=\"ag-quick-filter\" type=\"search\" [value]=\"quickFilterValue()\" (input)=\"onQuickFilterInput($event)\"\n [placeholder]=\"localeText().quickFilterPlaceholder\" [attr.aria-label]=\"localeText().quickFilterPlaceholder\" />\n }\n </div>\n }\n <div class=\"ag-main-area\">\n <div class=\"ag-grid-split\" [class.ag-has-right-pane]=\"hasRightPinnedPane()\">\n @if (hasPinnedPane()) {\n <div class=\"ag-pinned-pane\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups ag-header-groups--pinned\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-group-cell ag-header-group-cell--empty\"></div>\n }\n @for (run of pinnedHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header ag-header--pinned\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\" [style.width.px]=\"pinnedPaneWidth()\">\n @if (showControlColumn()) {\n <div class=\"ag-header-cell ag-control-header\" role=\"columnheader\" aria-colindex=\"1\">\n <div class=\"ag-header-cell-top\"></div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\"></div>\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-last]=\"isLastPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}\n @if (pivotHeaderLabel() && pivotRowColumnField()!==col.field) {\n ({{pivotHeaderLabel()}})\n }\n\n </span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #leftRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\" [attr.data-control]=\"true\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isPinnedPaneRowSelected(item)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || isLoadingRow(item) || item === 'ghost' ? pinnedGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell\" role=\"rowheader\" aria-colindex=\"1\"\n [class.ag-control-cell--reorder]=\"allowRowReorder()\"\n (contextmenu)=\"onControlContextMenu($event, item.originalIndex)\" (click)=\"$event.stopPropagation()\"\n (pointerdown)=\"onControlPointerDown($event, item.originalIndex)\">\n @if (canToggleDetail(item)) {\n <button class=\"ag-detail-toggle\" type=\"button\"\n [class.ag-detail-toggle--expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-expanded]=\"isDetailExpanded(item.originalIndex)\"\n [attr.aria-label]=\"localeText().toggleDetail\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"onDetailToggle(item.originalIndex); $event.stopPropagation()\">\u25B6</button>\n }\n @if (enableRowMarking()) {\n <input class=\"ag-row-marker\" type=\"checkbox\" [checked]=\"isRowMarked(item.originalIndex)\"\n [attr.aria-label]=\"localeText().markRow\" (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\" (change)=\"toggleRowMarked(item.originalIndex)\" />\n }\n </div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\" [row]=\"item.row\" [locale]=\"locale()\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\" [attr.data-col-field]=\"col.field\"\n [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex) && !isTreeAggregateCell(item, col)\"\n [error]=\"cellValidationError(item.originalIndex, ci)\" [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\" [class.ag-cell--pinned]=\"true\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\" (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (isLoadingRow(item)) {\n <div class=\"ag-loading-row\" aria-hidden=\"true\"><span></span></div>\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @if (showControlColumn()) {\n <div class=\"ag-control-cell ag-control-cell--reorder ag-ghost-handle\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #pinnedViewport class=\"ag-body ag-pinned-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.width.px]=\"pinnedPaneWidth()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body\" [style.width.px]=\"pinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"leftRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"pinnedGridTemplateColumns()\">\n @if (showControlColumn()) {\n <div class=\"ag-footer-cell ag-footer-cell--control\" role=\"gridcell\" aria-colindex=\"1\"></div>\n }\n @for (col of pinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-last]=\"isLastPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"ag-scroll-pane\">\n <div #horizontalScroller class=\"ag-horizontal-scroll\" (scroll)=\"onHorizontalScroll()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (run of scrollableHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-header-cell\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.data-col-field]=\"col.field\" (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}\n @if (pivotHeaderLabel() && pivotRowColumnField()!==col.field) {\n ({{pivotHeaderLabel()}})\n }\n\n </span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">\u229F</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #bodyRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-add-row--selected]=\"item === null && isAddRowSelected()\" [class.ag-ghost-row]=\"item === 'ghost'\"\n [class.ag-group-header-row]=\"isGroupHeaderItem(item)\" [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || isLoadingRow(item) || item === 'ghost' ? scrollableGridTemplateColumns() : null\"\n (pointerdown)=\"isDataRowItem(item) && onRowPointerDown($event, item.originalIndex)\"\n (click)=\"isDataRowItem(item) && onRowClick($event, item)\">\n @if (isDataRowItem(item)) {\n @for (col of scrollableColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\" [row]=\"item.row\" [locale]=\"locale()\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\" [attr.data-col-field]=\"col.field\"\n [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex) && !isTreeAggregateCell(item, col)\"\n [error]=\"cellValidationError(item.originalIndex, ci)\" [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\"\n (startEdit)=\"onStartEdit(item.originalIndex, ci)\" (draftChange)=\"onDraftChange($event)\"\n (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\" (treeToggle)=\"onTreeToggle(item)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (isLoadingRow(item)) {\n <div class=\"ag-loading-row\" aria-hidden=\"true\"><span></span></div>\n } @else if (item === null) {\n <div class=\"ag-add-row-label\" (click)=\"onActivateAddRow()\">\n <span class=\"ag-add-row-icon\">+</span> {{ localeText().addRow }}\n </div>\n } @else if (item === 'ghost') {\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-ghost-cell\" [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\">{{\n getGhostCellDisplay(col) }}</div>\n }\n } @else if (isDetailRowItem(item)) {\n <div class=\"ag-detail-panel\" [style.height.px]=\"detailRowHeight()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" [innerHTML]=\"detailHtml(item)\"></div>\n } @else if (isPathTreeNodeItem(item)) {\n <div class=\"ag-path-tree-content\" [style.min-width.px]=\"scrollableTotalWidth()\"\n [style.padding-left.px]=\"treeRowLevel(item) * 16 + 8\"\n (click)=\"onTreeNodeClick(item); $event.stopPropagation()\"\n (dblclick)=\"onTreeNodeDoubleClick(item); $event.stopPropagation()\">\n <button type=\"button\" class=\"ag-tree-twisty\" [class.ag-tree-twisty--expanded]=\"treeRowExpanded(item)\"\n [attr.aria-expanded]=\"treeRowExpanded(item)\"\n (click)=\"onTreeToggle(item); $event.stopPropagation()\">\u25B6</button>\n <span>{{ pathTreeLabel(item) }}</span>\n @if (item.aggregates; as aggregates) {\n <span class=\"ag-group-aggregates\">\n @for (col of visibleColDefs(); track col.field) {\n @if (col.field !== treeConfig()!.treeField && aggregates[col.field] !== undefined) {\n <span class=\"ag-group-aggregate\">\n <span class=\"ag-group-aggregate-col\">{{ col.header }}</span>\n <span class=\"ag-group-aggregate-op\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, aggregates[col.field]) }}\n </span>\n }\n }\n </span>\n }\n </div>\n } @else {\n <div class=\"ag-group-header-content\" [style.min-width.px]=\"scrollableTotalWidth()\">\n <div class=\"ag-group-header-main\"\n (click)=\"onGroupHeaderClick(item.groupLabel); $event.stopPropagation()\">\n <span class=\"ag-group-icon\" [class.ag-group-icon--expanded]=\"!item.collapsed\">\u25B6</span>\n <span class=\"ag-group-label\">{{ item.groupLabel }}</span>\n @if (groupActions().length > 0) {\n <button class=\"ag-group-actions-btn\" [title]=\"localeText().actions\"\n (click)=\"openGroupActionsMenu($event, item.groupLabel)\">\u22EE</button>\n }\n <span class=\"ag-group-count\">{{ item.count }}</span>\n @if (getGroupDescription(item.groupLabel); as desc) {\n <span class=\"ag-group-description\">{{ desc }}</span>\n }\n @if (item.aggregates; as aggs) {\n <span class=\"ag-group-aggregates\">\n @for (col of visibleColDefs(); track col.field) {\n @if (hasAggregate(col) && aggs[col.field] !== undefined) {\n <span class=\"ag-group-aggregate\">\n <span class=\"ag-group-aggregate-col\">{{ col.header }}</span>\n <span class=\"ag-group-aggregate-op\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, aggs[col.field]) }}\n </span>\n }\n }\n </span>\n }\n </div>\n </div>\n }\n @if (isDataRowItem(item) && isRowPendingDelete(item.originalIndex)) {\n <div class=\"ag-delete-confirmation\" role=\"alertdialog\" [attr.aria-label]=\"localeText().confirmDeleteRow\"\n [style.left.px]=\"deleteConfirmationLeft()\" [style.width.px]=\"deleteConfirmationWidth() || null\"\n (pointerdown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\n <span class=\"ag-delete-confirmation-text\">{{ localeText().confirmDeleteRow }}</span>\n <button class=\"ag-delete-confirmation-btn ag-delete-confirmation-btn--yes\" type=\"button\"\n (click)=\"confirmPendingRowDelete()\">{{ localeText().confirmYes }}</button>\n <button class=\"ag-delete-confirmation-btn\" type=\"button\" data-delete-confirm-no\n (click)=\"cancelRowDelete()\">{{ localeText().confirmNo }}</button>\n </div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #scrollViewport class=\"ag-body\" [agridVariableRowSize]=\"itemSizes()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" (scroll)=\"onBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom\" [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"bodyRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"scrollableGridTemplateColumns()\"\n [style.min-width.px]=\"scrollableTotalWidth()\">\n @for (col of scrollableColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (hasRightPinnedPane()) {\n <div class=\"ag-pinned-pane ag-pinned-pane--right\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @if (hasHeaderGroups()) {\n <div class=\"ag-header-groups\" role=\"row\" aria-rowindex=\"1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (run of rightHeaderGroupRuns(); track run.key) {\n <div class=\"ag-header-group-cell\" role=\"columnheader\" [attr.data-header-group]=\"run.id\"\n [class.ag-header-group-cell--empty]=\"!run.id\"\n [class.ag-header-group-cell--locked]=\"isHeaderGroupLocked(run.fields)\"\n [class.ag-header-group-cell--dragging]=\"isHeaderGroupDragging(run.fields)\"\n [style.grid-column]=\"'span ' + run.span\"\n (pointerdown)=\"run.id && onHeaderGroupPointerDown($event, run.fields, run.label)\">\n {{ run.label }}\n </div>\n }\n </div>\n }\n <div class=\"ag-header\" role=\"row\" [attr.aria-rowindex]=\"headerRowCount()\"\n [class.ag-header--with-filters]=\"hasFilterableColumns()\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\" [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-header-cell ag-header-cell--pinned\" role=\"columnheader\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [attr.aria-sort]=\"getSort(col.field) === 'asc' ? 'ascending' : getSort(col.field) === 'desc' ? 'descending' : 'none'\"\n [class.ag-header-cell--filtered]=\"hasActiveFilter(col.field)\"\n [class.ag-header-cell--dragging]=\"isColDragging(col.field)\"\n [class.ag-header-cell--drop-before]=\"getColDropSide(col.field) === 'before'\"\n [class.ag-header-cell--drop-after]=\"getColDropSide(col.field) === 'after'\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-header-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\" [attr.data-col-field]=\"col.field\"\n (pointerdown)=\"onColHeaderPointerDown($event, col.field)\">\n <div class=\"ag-header-cell-top\">\n <span class=\"ag-header-cell-label\">{{ col.header }}\n @if (pivotHeaderLabel() && pivotRowColumnField()!==col.field) {\n ({{pivotHeaderLabel()}})\n }\n </span>\n @if (getSort(col.field); as dir) {\n <span class=\"ag-sort-badge\">{{ dir === 'asc' ? '\u2191' : '\u2193' }}@if (hasMultiSort()) {<sup\n class=\"ag-sort-priority\">{{ getSortPriority(col.field) }}</sup>}</span>\n }\n @if (isGroupedByField(col.field)) {\n <span class=\"ag-sort-badge\">'\u229F '</span>\n }\n <button class=\"ag-header-menu-btn\" [class.ag-header-menu-btn--active]=\"hasActiveFilter(col.field)\"\n [title]=\"localeText().columnMenu\" [attr.aria-label]=\"localeText().columnMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" aria-haspopup=\"menu\"\n (click)=\"openFilterMenu($event, col.field)\" (pointerdown)=\"$event.stopPropagation()\">\u25BE</button>\n </div>\n @if (hasFilterableColumns()) {\n <div class=\"ag-header-cell-filter\">\n @if (col.filterable) {\n <input class=\"ag-filter-input\" [value]=\"getTextFilter(col.field)\"\n (input)=\"onTextFilterChange($event, col.field)\" (click)=\"$event.stopPropagation()\"\n [placeholder]=\"localeText().filterPlaceholder\"\n [attr.aria-label]=\"localeText().filterPlaceholder + ' ' + col.header\" />\n @if (getMenuFilterType(col.field)) {\n <button type=\"button\" class=\"ag-filter-condition-btn\"\n [class.ag-filter-condition-btn--active]=\"!!getMenuOperator(col.field)\"\n [title]=\"localeText().filterConditionMenu\"\n [attr.aria-label]=\"localeText().filterConditionMenu + ': ' + col.header\"\n [attr.aria-expanded]=\"filterMenu()?.field === col.field\" (click)=\"openFilterMenu($event, col.field)\"\n (pointerdown)=\"$event.stopPropagation()\">{{ getConditionButtonLabel(col.field) }}</button>\n }\n }\n </div>\n }\n <div class=\"ag-resize-handle\" role=\"separator\" [attr.tabindex]=\"col.locked ? -1 : 0\"\n aria-orientation=\"vertical\" [attr.aria-disabled]=\"col.locked ? 'true' : null\"\n [attr.aria-label]=\"localeText().resizeColumn + ': ' + col.header\"\n [attr.aria-valuenow]=\"getColumnWidth(col)\" (keydown)=\"onResizeKeyDown($event, col)\"\n (mousedown)=\"onResizeStart($event, col)\" (pointerdown)=\"$event.stopPropagation()\"\n (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <ng-template #rightRow let-item let-ariaIndex=\"ariaIndex\">\n <div role=\"row\" [attr.aria-rowindex]=\"ariaIndex\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item) && !isDetailRowItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-ghost-row]=\"item === 'ghost'\" [class.ag-group-header-row]=\"isGroupHeaderItem(item)\"\n [class.ag-detail-row]=\"isDetailRowItem(item)\"\n [class.ag-row--odd]=\"isDataRowItem(item) && dataRowIsOdd().get(item.originalIndex) === true\"\n [class.ag-row--selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex)\"\n [class.ag-row--marked]=\"isDataRowItem(item) && isRowMarked(item.originalIndex)\"\n [class.ag-row--pending-delete]=\"isDataRowItem(item) && isRowPendingDelete(item.originalIndex)\"\n [class]=\"isDataRowItem(item) ? getRowClass(item.row, item.originalIndex) : ''\"\n [style.height.px]=\"rowPx(item)\"\n [style.grid-template-columns]=\"isDataRowItem(item) || isLoadingRow(item) || item === 'ghost' ? rightGridTemplateColumns() : null\">\n @if (isDataRowItem(item)) {\n @for (col of rightPinnedColDefs(); track col.field) {\n @let ci = getVisibleColIndex(col.field);\n <agrid-cell [col]=\"col\" [rowIndex]=\"item.originalIndex\" [colIndex]=\"ci\" [value]=\"item.row[col.field]\"\n [displayValueOverride]=\"treeCellDisplayOverride(item, col)\" [row]=\"item.row\" [locale]=\"locale()\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\" [attr.data-col-field]=\"col.field\"\n [attr.aria-colindex]=\"getAriaColIndex(ci)\"\n [attr.aria-selected]=\"isSelected(item.originalIndex, ci) || isRangeSelected(item.originalIndex, ci)\"\n [selected]=\"isSelected(item.originalIndex, ci)\" [editing]=\"isEditing(item.originalIndex, ci)\"\n [editable]=\"isColEditable(col, item.originalIndex) && !isTreeAggregateCell(item, col)\"\n [error]=\"cellValidationError(item.originalIndex, ci)\" [showInfoIcon]=\"showCellInfoIcon(col, item.row)\"\n [seedChar]=\"getSeedChar(item.originalIndex, ci)\" [selectTextOnEdit]=\"selectTextOnEdit()\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\" [treeExpandable]=\"treeRowExpandable(item)\"\n [treeExpanded]=\"treeRowExpanded(item)\" [class]=\"getCellClass(col, item.row[col.field], item.row)\"\n [class.ag-cell--range-selected]=\"isRangeSelected(item.originalIndex, ci)\"\n [class.ag-cell--fill-preview]=\"isFillPreviewCell(item.originalIndex, ci)\"\n [class.ag-cell--fill-handle]=\"isFillHandleCell(item.originalIndex, ci)\"\n [class.ag-cell--changed]=\"isCellChanged(item.originalIndex, col.field)\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--find-match]=\"isFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--find-active]=\"isActiveFindMatchCell(item.originalIndex, ci)\"\n [class.ag-cell--pinned]=\"true\" [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\"\n (pointerdown)=\"onCellPointerDown($event, item.originalIndex, ci)\"\n (activate)=\"onActivate(item.originalIndex, ci, $event)\" (startEdit)=\"onStartEdit(item.originalIndex, ci)\"\n (draftChange)=\"onDraftChange($event)\" (booleanToggle)=\"onBooleanToggle(item.originalIndex, ci, $event)\"\n (infoClick)=\"onCellInfo(item.originalIndex, col, item.row)\" (treeToggle)=\"onTreeToggle(item)\"\n (contextmenu)=\"onCellContextMenu($event, item.originalIndex, ci, col, item.row)\" />\n }\n } @else if (isLoadingRow(item)) {\n <div class=\"ag-loading-row\" aria-hidden=\"true\"><span></span></div>\n } @else if (item === null) {\n <div class=\"ag-pinned-row-spacer\"></div>\n } @else if (item === 'ghost') {\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-ghost-cell ag-cell--pinned\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [class.ag-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">{{ getGhostCellDisplay(col) }}</div>\n }\n } @else {\n <div class=\"ag-pinned-row-spacer\"></div>\n }\n </div>\n </ng-template>\n\n @if (hasPinnedTopRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--top ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedTopItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + $index }\" />\n }\n </div>\n }\n\n <cdk-virtual-scroll-viewport #rightPinnedViewport class=\"ag-body ag-pinned-body ag-right-pinned-body\"\n [agridVariableRowSize]=\"itemSizes()\" [style.width.px]=\"rightPinnedPaneWidth()\"\n (scroll)=\"onRightPinnedBodyScroll()\">\n <ng-container *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\">\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: di + headerRowCount() + 1 + pinnedTopItems().length }\" />\n </ng-container>\n </cdk-virtual-scroll-viewport>\n\n @if (hasPinnedBottomRows()) {\n <div class=\"ag-pinned-rows ag-pinned-rows--bottom ag-pinned-body ag-right-pinned-body\"\n [style.width.px]=\"rightPinnedPaneWidth()\">\n @for (item of pinnedBottomItems(); track item.originalIndex) {\n <ng-container [ngTemplateOutlet]=\"rightRow\"\n [ngTemplateOutletContext]=\"{ $implicit: item, ariaIndex: headerRowCount() + 1 + pinnedTopItems().length + displayItems().length + $index }\" />\n }\n </div>\n }\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\" [attr.aria-rowindex]=\"displayItems().length + headerRowCount() + 1\"\n [style.grid-template-columns]=\"rightGridTemplateColumns()\">\n @for (col of rightPinnedColDefs(); track col.field) {\n <div class=\"ag-footer-cell\" role=\"gridcell\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\"\n [attr.aria-colindex]=\"getAriaColIndex(getVisibleColIndex(col.field))\"\n [class.ag-footer-cell--pinned-first]=\"isFirstRightPinnedColumn(col.field)\">\n @if (hasAggregate(col)) {\n <span class=\"ag-footer-label\">{{ getAggregateLabel(col) }}</span>\n {{ getFooterDisplay(col, footerValues()[col.field]) }}\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (loading()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ localeText().loading }}</div>\n } @else if (isEmpty()) {\n <div class=\"ag-state-overlay\" role=\"status\" aria-live=\"polite\">{{ emptyTextLabel() }}</div>\n }\n\n @if (showSidebar()) {\n <agrid-sidebar [open]=\"sidebarOpen()\" [activeTab]=\"sidebarTab()\" [columns]=\"colDefs()\"\n [pivotColumns]=\"sidebarPivotColumns()\" [pivotConfig]=\"provider().pivotConfig\" [headerGroups]=\"headerGroups()\"\n [row]=\"sidebarRow()\" [rowIndex]=\"selectedRowIndex()\" [hiddenColumns]=\"sidebarHiddenColumns()\" [locale]=\"locale()\"\n [localeText]=\"localeText()\" [readonlyGrid]=\"readonlyGrid()\" [useSidebarEditor]=\"useSidebarEditor()\"\n [isCellEditable]=\"isCellEditableForRow\" [errors]=\"sidebarValidationErrors()\" (close)=\"toggleSidebar()\"\n (tabChange)=\"onSidebarStripClick($event)\" (toggleColumn)=\"onSidebarToggleColumn($event)\"\n (toggleColumnGroup)=\"onSidebarToggleColumnGroup($event.fields, $event.visible)\"\n (pivotChange)=\"onSidebarPivotChange($event)\" (detailEdit)=\"onSidebarDetailEdit($event)\"\n (save)=\"onSidebarDetailSave($event)\" />\n }\n </div><!-- /.ag-main-area -->\n\n @if (showPagination()) {\n <nav class=\"ag-pagination\" [attr.aria-label]=\"localeText().pagination\">\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().firstPage\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToFirstPage()\">\u00AB</button>\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().previous\" [disabled]=\"control()!.currentPage() <= 1\"\n (click)=\"goToPrevPage()\">\u2039</button>\n <span class=\"ag-page-info\" aria-live=\"polite\">{{ control()!.currentPage() }} / {{ totalPages() }}</span>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().next\" (click)=\"goToNextPage()\">\u203A</button>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().lastPage\" (click)=\"goToLastPage()\">\u00BB</button>\n <span class=\"ag-page-count\">{{ localeText().rows(filteredRowCount()) }}</span>\n </nav>\n }\n\n @if (findOpen()) {\n <agrid-find-panel [query]=\"findQuery()\" [matchCount]=\"findMatches().length\" [activeIndex]=\"findActiveIndex()\"\n [localeText]=\"localeText()\" (queryChange)=\"onFindInput($event)\" (previous)=\"goToFindMatch(-1)\"\n (next)=\"goToFindMatch(1)\" (close)=\"closeFind()\" />\n }\n\n <!-- Row context menu -->\n @if (contextMenu(); as menu) {\n @if(hasContextMenuEntries()) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @if (!treeConfig()) {\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n }\n @if (!readonlyGrid()) {\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">\n {{ localeText().deleteRow }}\n </button>\n }\n </div>\n }\n }\n\n <!-- Cell context menu -->\n @if (cellContextMenuState(); as menu) {\n @let col = getColDef(menu.field)!;\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyCellToClipboard(menu.rowIndex, col)\">{{\n localeText().copyCellValue\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyRowToClipboard(menu.rowIndex)\">{{ localeText().copyRow\n }}</button>\n @if (!treeConfig()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @if (rowPinState(menu.rowIndex)) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, null)\">{{ localeText().unpinRow\n }}</button>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'top')\">{{ localeText().pinRowTop\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onPinRow(menu.rowIndex, 'bottom')\">{{\n localeText().pinRowBottom }}</button>\n }\n }\n @if (allowAddRows() && !readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex)\">{{ localeText().insertRowAbove\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex + 1)\">{{\n localeText().insertRowBelow }}</button>\n }\n @if (!readonlyGrid()) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n <button class=\"ag-context-item ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">{{\n localeText().deleteRow }}</button>\n }\n @if (cellMenuItems().length) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n @for (item of cellMenuItems(); track $index) {\n @if (item === null) {\n <div class=\"ag-context-separator\" role=\"separator\"></div>\n } @else {\n <button class=\"ag-context-item\" role=\"menuitem\" [class.ag-context-item--danger]=\"item.danger\"\n [disabled]=\"item.disabled\" (click)=\"runCellMenuItem(item, menu)\">{{\n item.label }}</button>\n }\n }\n }\n </div>\n }\n\n <!-- Group actions menu -->\n @if (groupActionsMenu(); as menu) {\n <div class=\"ag-context-menu\" role=\"menu\" [style.left.px]=\"menu.x\" [style.top.px]=\"menu.y\"\n (click)=\"$event.stopPropagation()\">\n @for (action of groupActions(); track action.label) {\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"onGroupAction(action, menu.label)\">\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n <!-- Filter dropdown -->\n @if (filterMenu(); as menu) {\n <agrid-column-menu [x]=\"menu.x\" [y]=\"menu.y\" [header]=\"getColDef(menu.field)?.header ?? menu.field\"\n [sortDir]=\"getSort(menu.field)\" [sortable]=\"sortOption() !== 'none'\" [showColumnActions]=\"!!control()\"\n [pinned]=\"getColumnPinState(menu.field)\" [groupable]=\"!!getColDef(menu.field)?.groupable\"\n [grouped]=\"isGroupedByField(menu.field)\" [filterable]=\"!!getColDef(menu.field)?.filterable\"\n [showValueFilter]=\"!serverSideFiltering() || !!getColDef(menu.field)?.values?.length\"\n [filterType]=\"getMenuFilterType(menu.field)\" [operator]=\"getMenuOperator(menu.field)\"\n [operand]=\"getMenuOperand(menu.field)\" [operand2]=\"getMenuOperand2(menu.field)\"\n (operatorChange)=\"onMenuOperatorChange(menu.field, $event)\"\n (operandChange)=\"onMenuOperandChange(menu.field, $event)\"\n (operand2Change)=\"onMenuOperand2Change(menu.field, $event)\" [search]=\"filterMenuSearch()\"\n [allSelected]=\"isMenuAllSelected(menu.field)\" [valueItems]=\"(serverSideFiltering() && !getColDef(menu.field)?.values?.length)\n || !getColDef(menu.field)?.filterable ? [] : columnMenuValueItems()\" [localeText]=\"localeText()\"\n [sortPriority]=\"getSortPriority(menu.field)\" [hasMultiSort]=\"hasMultiSort()\" (sort)=\"onMenuSort(menu.field, $event)\"\n (resetSort)=\"onMenuResetSort(menu.field, $event)\" (autosize)=\"onMenuAutosizeColumn(menu.field)\"\n (togglePin)=\"onMenuTogglePin(menu.field)\" (togglePinRight)=\"onMenuTogglePinRight(menu.field)\"\n (hide)=\"onMenuHideColumn(menu.field)\" (toggleGroup)=\"onMenuToggleGroupBy(menu.field)\"\n (clearFilter)=\"onMenuClearFilter(menu.field)\" (clearAll)=\"onMenuClearAll()\"\n (searchChange)=\"onFilterMenuSearch($event)\" (toggleAll)=\"onMenuToggleAll(menu.field)\"\n (toggleValue)=\"onMenuToggleValue(menu.field, $event)\" [aggregate]=\"getEffectiveAggregate(getColDef(menu.field)!)\"\n (setAggregate)=\"onMenuSetAggregate(menu.field, $event)\" />\n }\n\n @if (columnDragPreview(); as preview) {\n <div class=\"ag-column-drag-preview\" [style.left.px]=\"preview.x\" [style.top.px]=\"preview.y\"\n [style.width.px]=\"preview.width\" [style.height.px]=\"preview.height\">\n <span>{{ preview.label }}</span>\n </div>\n }\n</div>", styles: ["@layer agrid-defaults{:host{--agrid-color-text: #24292f;--agrid-color-text-muted: #57606a;--agrid-color-accent: #1a73e8;--agrid-color-accent-subtle: #e8f0fe;--agrid-color-accent-fg: #1558b0;--agrid-color-accent-border: #c8d8f8;--agrid-color-danger: #d1242f;--agrid-color-danger-subtle: #fff1f0;--agrid-color-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-subtle: #fafbfc;--agrid-color-bg-muted: #f6f8fa;--agrid-color-shadow: rgba(140, 149, 159, .2);--agrid-color-bg-stripe: #f0f2f5;--agrid-color-cell-changed: #f59e0b;--agrid-color-row-marked: #fff8c5}}:host{display:flex;flex-direction:column;min-height:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:var(--agrid-color-text)}.ag-wrapper{position:relative;display:flex;flex-direction:column;flex:1;min-height:0;border:1px solid var(--agrid-color-border);overflow:hidden;outline:none;border-radius:4px}.ag-state-overlay{position:absolute;inset:34px 0 0;display:flex;align-items:center;justify-content:center;color:var(--agrid-color-text-muted);background:color-mix(in srgb,var(--agrid-color-bg) 78%,transparent);pointer-events:none;z-index:3}.ag-header{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-bottom:2px solid var(--agrid-color-border)}.ag-header-groups{display:grid;flex-shrink:0;height:28px;background:color-mix(in srgb,var(--agrid-color-bg-muted) 72%,var(--agrid-color-bg));border-bottom:1px solid var(--agrid-color-border)}.ag-header-group-cell{display:flex;align-items:center;justify-content:center;min-width:0;padding:0 8px;overflow:hidden;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;color:var(--agrid-color-text);font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap;cursor:grab;-webkit-user-select:none;user-select:none;transition:opacity .1s ease}.ag-header-group-cell:last-child{border-right:none}.ag-header-group-cell--empty,.ag-header-group-cell--locked{cursor:default}.ag-header-group-cell--dragging{opacity:.12}.ag-header-cell{position:relative;display:flex;align-items:center;font-weight:600;border-right:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;-webkit-user-select:none;user-select:none;box-sizing:border-box}.ag-header-cell:last-child{border-right:none}.ag-header-cell-top{display:flex;align-items:center;flex:1;min-width:0;padding:0 6px;height:32px;overflow:hidden;white-space:nowrap}.ag-header-cell-label{overflow:hidden;text-overflow:ellipsis;flex:1}.ag-header--with-filters .ag-header-cell{flex-direction:column;align-items:stretch;height:auto;white-space:normal}.ag-header--with-filters .ag-header-cell-top{flex:0 0 32px}.ag-header-cell-filter{height:28px;display:flex;align-items:center;padding:0 2px;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-header-cell{cursor:grab;transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-row>agrid-cell,.ag-ghost-cell,.ag-footer-cell{transition:transform .16s cubic-bezier(.2,0,0,1),opacity .1s ease;will-change:transform}.ag-loading-row{grid-column:1 / -1;display:flex;align-items:center;padding:0 10px;box-sizing:border-box;border-bottom:1px solid var(--agrid-color-border)}.ag-loading-row span{width:min(220px,55%);height:8px;border-radius:999px;background:color-mix(in srgb,var(--agrid-color-text-muted) 18%,transparent)}.ag-header-cell--dragging{opacity:.12;cursor:grabbing}.ag-column-reorder-item--dragging{opacity:.12}.ag-header-cell--drop-before,.ag-header-cell--drop-after{z-index:1}.ag-column-drag-preview{position:fixed;z-index:1000;display:flex;align-items:flex-start;padding:8px 10px;border:1px solid var(--agrid-color-accent);border-radius:5px;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-bg) 94%,var(--agrid-color-accent));box-shadow:0 10px 28px var(--agrid-color-shadow);font-weight:600;line-height:16px;box-sizing:border-box;pointer-events:none;opacity:.96}.ag-sort-badge{font-size:11px;color:var(--agrid-color-accent);flex-shrink:0;line-height:1}.ag-sort-priority{font-size:9px;vertical-align:super;opacity:.75}.ag-resize-handle{position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;z-index:1}.ag-resize-handle:hover{background:var(--agrid-color-accent);opacity:.5}.ag-filter-input{flex:1;min-width:0;height:20px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 4px;background:var(--agrid-color-bg)}.ag-filter-condition-btn{flex:0 0 24px;height:20px;padding:0;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg-subtle);color:var(--agrid-color-text-muted);cursor:pointer}.ag-filter-condition-btn:hover,.ag-filter-condition-btn:focus-visible{border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg)}.ag-filter-condition-btn--active{border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-filter-input:focus{border-color:var(--agrid-color-accent)}.ag-header-cell--filtered .ag-header-cell-label:after{content:\" \\25be\";font-size:9px;color:var(--agrid-color-accent)}.ag-main-area{display:flex;flex:1;min-height:0;overflow:hidden}.ag-grid-split{display:flex;flex:1;min-width:0;min-height:0;overflow:hidden}.ag-pinned-pane{flex-shrink:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg)}.ag-scroll-pane{flex:1;min-width:0;min-height:0;display:flex}.ag-horizontal-scroll{flex:1;min-width:0;min-height:0;overflow-x:auto;overflow-y:hidden;display:flex;flex-direction:column}.ag-body{flex:1;overflow-x:clip;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-horizontal-scroll{scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.18) transparent}.ag-body::-webkit-scrollbar,.ag-horizontal-scroll::-webkit-scrollbar{width:8px;height:8px}.ag-body::-webkit-scrollbar-track,.ag-horizontal-scroll::-webkit-scrollbar-track{background:transparent}.ag-body::-webkit-scrollbar-thumb,.ag-horizontal-scroll::-webkit-scrollbar-thumb{background:#0000002e;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-body::-webkit-scrollbar-thumb:hover,.ag-horizontal-scroll::-webkit-scrollbar-thumb:hover{background:#00000052;border-radius:10px;border:2px solid transparent;background-clip:padding-box}.ag-pinned-body{overflow:hidden}.ag-right-pinned-body{overflow-y:auto}.ag-has-right-pane .ag-body:not(.ag-pinned-body){scrollbar-width:none}.ag-has-right-pane .ag-body:not(.ag-pinned-body)::-webkit-scrollbar{display:none}.ag-row{display:grid;position:relative}.ag-row--pending-delete>:not(.ag-delete-confirmation){opacity:.2;pointer-events:none;transition:opacity .14s ease}.ag-delete-confirmation{position:absolute;top:0;bottom:0;z-index:7;display:flex;align-items:center;justify-content:center;gap:8px;min-width:min(100%,320px);padding:2px 12px;box-sizing:border-box;color:var(--agrid-color-text);background:color-mix(in srgb,var(--agrid-color-danger-subtle) 88%,transparent);border-block:1px solid color-mix(in srgb,var(--agrid-color-danger) 35%,transparent)}.ag-delete-confirmation-text{font-weight:600;white-space:nowrap}.ag-delete-confirmation-btn{min-width:42px;padding:3px 9px;border:1px solid var(--agrid-color-border);border-radius:4px;color:var(--agrid-color-text);background:var(--agrid-color-bg);font:inherit;cursor:pointer}.ag-delete-confirmation-btn:hover,.ag-delete-confirmation-btn:focus-visible{border-color:var(--agrid-color-accent);outline:none}.ag-delete-confirmation-btn--yes{color:#fff;border-color:var(--agrid-color-danger);background:var(--agrid-color-danger)}.ag-delete-confirmation-btn--yes:hover,.ag-delete-confirmation-btn--yes:focus-visible{border-color:color-mix(in srgb,var(--agrid-color-danger) 75%,#000);background:color-mix(in srgb,var(--agrid-color-danger) 85%,#000)}:host(.ag-zebra) .ag-row--odd agrid-cell:not(.editing){background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd .ag-control-cell{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd:hover .ag-control-cell{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned,:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}:host(.ag-zebra) .ag-row--odd .ag-cell--pinned-first{background:var(--agrid-color-bg-stripe)}:host(.ag-zebra) .ag-row--odd:hover .ag-cell--pinned-first{background:var(--agrid-color-bg-muted)}:host(.ag-zebra) .ag-row--odd.ag-row--selected .ag-cell--pinned-first{background:var(--agrid-color-accent-subtle)}.ag-row:hover agrid-cell:not(.editing){background:var(--agrid-color-bg-muted)}.ag-row--selected agrid-cell:not(.editing){background:var(--agrid-color-accent-subtle)}.ag-row--marked agrid-cell:not(.editing),.ag-row--marked .ag-control-cell{background:var(--agrid-color-row-marked)}.ag-row--marked{box-shadow:inset 3px 0 #bf8700}.ag-row--selected.ag-row--marked{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--range-selected:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent-border)}.ag-row agrid-cell.ag-cell--find-match:not(.editing){background:#fff7cc}.ag-row agrid-cell.ag-cell--find-active:not(.editing){background:#ffe58a;box-shadow:inset 0 0 0 2px #b7791f}.ag-row agrid-cell.ag-cell--range-selected.selected:not(.editing){box-shadow:inset 0 0 0 2px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-preview:not(.editing){background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1px var(--agrid-color-accent)}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing){position:relative}.ag-row agrid-cell.ag-cell--fill-handle:not(.editing):after{content:\"\";position:absolute;right:1px;bottom:1px;width:6px;height:6px;background:var(--agrid-color-accent);border:1px solid var(--agrid-color-bg);box-sizing:border-box;cursor:crosshair;z-index:4}.ag-row--selected .ag-control-cell{background:var(--agrid-color-accent-subtle)}.ag-row--selected{box-shadow:inset 3px 0 0 var(--agrid-color-accent)}.ag-control-header{z-index:3;border-right:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted)}.ag-control-header .ag-header-cell-filter{background:var(--agrid-color-bg-subtle)}.ag-control-cell{z-index:2;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle);cursor:context-menu;display:flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;flex-direction:row-reverse}.ag-control-cell:after{content:\"\\22ee\";font-size:11px;color:var(--agrid-color-border);line-height:1}.ag-control-cell:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder{cursor:grab}.ag-control-cell--reorder:after{content:\"\\283f\";font-size:13px;color:var(--agrid-color-text)}.ag-control-cell--reorder:hover:after{color:var(--agrid-color-text-muted)}.ag-control-cell--reorder:active{cursor:grabbing}.ag-row-marker{position:relative;z-index:1;width:14px;height:14px;margin:0;accent-color:var(--agrid-color-accent);cursor:pointer}.ag-ghost-row{background:var(--agrid-color-accent-subtle);box-shadow:inset 0 0 0 1.5px var(--agrid-color-accent);pointer-events:none;animation:ag-ghost-in .1s ease}@keyframes ag-ghost-in{0%{opacity:0}to{opacity:1}}.ag-ghost-handle:after{color:var(--agrid-color-accent)!important}.ag-ghost-cell{display:flex;align-items:center;padding:0 6px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-right:1px solid var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-size:13px;box-sizing:border-box;-webkit-user-select:none;user-select:none}.ag-ghost-cell:last-child{border-right:none}.ag-pinned-row-spacer,.ag-pinned-group-spacer{height:100%;border-bottom:1px solid var(--agrid-color-border);box-sizing:border-box}.ag-pinned-group-spacer{background:var(--agrid-color-bg-muted)}.ag-context-menu{position:fixed;z-index:1000;background:var(--agrid-color-bg-subtle);border:1px solid var(--agrid-color-border);border-radius:6px;box-shadow:0 8px 24px var(--agrid-color-shadow);min-width:160px;padding:4px 0;font-size:13px}.ag-context-item{display:block;width:100%;padding:6px 16px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit}.ag-context-item:hover{background:var(--agrid-color-bg-muted)}.ag-context-separator{height:1px;background:var(--agrid-color-border);margin:3px 0}.ag-context-item--danger{color:var(--agrid-color-danger)}.ag-context-item--danger:hover{background:var(--agrid-color-danger-subtle)}.ag-group-header-row{display:flex;align-items:stretch;background:var(--agrid-color-bg-muted);border-bottom:1px solid var(--agrid-color-border);overflow:hidden}.ag-path-tree-content{display:flex;align-items:center;gap:4px;height:100%;box-sizing:border-box;color:var(--agrid-color-text);font-weight:600;border-bottom:1px solid var(--agrid-color-border)}.ag-path-tree-content .ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;color:inherit;background:transparent;cursor:pointer;font-size:10px;transition:transform .12s ease}.ag-path-tree-content .ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-group-header-content{display:flex;align-items:stretch;height:100%;width:100%;-webkit-user-select:none;user-select:none}.ag-group-header-main{display:flex;align-items:center;gap:6px;padding:0 10px;flex:1;min-width:0;cursor:pointer}.ag-group-header-main:hover{background:var(--agrid-color-bg-subtle)}.ag-group-description{font-size:11px;color:var(--agrid-color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-group-actions-btn{display:flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:none;border-radius:3px;cursor:pointer;color:var(--agrid-color-text-muted);font-size:14px;padding:0}.ag-group-actions-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text)}.ag-group-icon{font-size:9px;color:var(--agrid-color-text-muted);line-height:1;display:inline-block;transition:transform .15s ease}.ag-group-icon--expanded{transform:rotate(90deg)}.ag-group-label{font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-group-count{font-size:11px;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg);border:1px solid var(--agrid-color-border);border-radius:10px;padding:0 7px;line-height:16px}.ag-group-aggregates{display:inline-flex;align-items:center;gap:10px;margin-left:4px;flex-wrap:wrap}.ag-group-aggregate{font-size:11px;color:var(--agrid-color-text);white-space:nowrap}.ag-group-aggregate-col{color:var(--agrid-color-text-muted);margin-right:4px}.ag-group-aggregate-op{color:var(--agrid-color-text-muted);margin-right:3px}.ag-footer{display:grid;flex-shrink:0;background:var(--agrid-color-bg-muted);border-top:2px solid var(--agrid-color-border)}.ag-footer-cell{display:flex;align-items:center;gap:4px;padding:0 6px;height:30px;border-right:1px solid var(--agrid-color-border);box-sizing:border-box;overflow:hidden;white-space:nowrap;font-weight:600;font-size:12px;color:var(--agrid-color-text)}.ag-footer-cell:last-child{border-right:none}.ag-footer-cell--control{background:var(--agrid-color-bg-subtle);border-right:1px solid var(--agrid-color-border)}.ag-footer-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-footer-label{font-size:10px;font-weight:400;color:var(--agrid-color-text-muted);flex-shrink:0}.ag-pagination{display:flex;align-items:center;gap:4px;padding:0 10px;height:34px;flex-shrink:0;border-top:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-muted);font-size:12px;color:var(--agrid-color-text-muted)}.ag-page-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;border:1px solid var(--agrid-color-border);border-radius:3px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font-size:13px;cursor:pointer;padding:0;line-height:1}.ag-page-btn:hover:not(:disabled){background:var(--agrid-color-bg-muted);border-color:var(--agrid-color-text-muted)}.ag-page-btn:disabled{opacity:.35;cursor:default}.ag-page-info{padding:0 6px;font-weight:500;color:var(--agrid-color-text);min-width:48px;text-align:center}.ag-page-count{margin-left:auto;color:var(--agrid-color-text-muted)}.ag-add-row{border-bottom:1px dashed var(--agrid-color-border);color:var(--agrid-color-text-muted);cursor:pointer}.ag-add-row:hover .ag-add-row-label{background:var(--agrid-color-bg-muted)}.ag-add-row--selected .ag-add-row-label{outline:2px solid var(--agrid-color-accent);outline-offset:-2px}.ag-add-row-label{display:flex;align-items:center;gap:4px;height:100%;padding:0 6px;font-size:12px;-webkit-user-select:none;user-select:none}.ag-add-row-icon{font-size:16px;line-height:1;color:var(--agrid-color-text-muted)}.ag-pinned-pane--right{border-left:1px solid var(--agrid-color-border);border-right:none;box-shadow:-3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-header-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-header-cell--pinned-first{box-shadow:none}.ag-cell--pinned{background:var(--agrid-color-bg)}.ag-cell--pinned-last{box-shadow:inset -1px 0 0 var(--agrid-color-border),3px 0 6px -2px var(--agrid-color-shadow)}.ag-cell--pinned-first{box-shadow:none}.ag-row:hover .ag-cell--pinned{background:var(--agrid-color-bg-muted)}.ag-row--selected .ag-cell--pinned,.ag-row .ag-cell--pinned.ag-cell--range-selected,.ag-row .ag-cell--pinned.ag-cell--fill-preview{background:var(--agrid-color-accent-subtle)}.ag-row .ag-cell--pinned.ag-cell--find-match{background:#fff7cc}.ag-row .ag-cell--pinned.ag-cell--find-active{background:#ffe58a}.ag-header-menu-btn{flex-shrink:0;width:16px;height:16px;padding:0;margin-right:2px;background:none;border:1px solid transparent;border-radius:3px;cursor:pointer;font-size:10px;color:var(--agrid-color-text-muted);display:flex;align-items:center;justify-content:center;opacity:1;transition:opacity .12s;line-height:1}.ag-header-menu-btn--active{color:var(--agrid-color-accent);border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle)}.ag-header-menu-btn:hover{background:var(--agrid-color-bg);border-color:var(--agrid-color-border)}.ag-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;padding:6px 8px;border-bottom:1px solid var(--agrid-color-border);background:var(--agrid-color-bg-subtle)}.ag-quick-filter{margin-left:auto;width:100%;max-width:260px;height:30px;padding:0 10px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text)}.ag-quick-filter:focus{border-color:var(--agrid-color-accent-border)}.ag-detail-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;cursor:pointer;font-size:9px;line-height:1;color:var(--agrid-color-text-muted);transition:transform .12s ease,color .12s ease}.ag-detail-toggle:hover{color:var(--agrid-color-text)}.ag-detail-toggle--expanded{transform:rotate(90deg)}.ag-detail-row{display:block;position:relative;box-sizing:border-box}.ag-detail-panel{box-sizing:border-box;height:100%;overflow:auto;padding:8px 12px;background:var(--agrid-color-bg-subtle);border-bottom:1px solid var(--agrid-color-border)}.ag-pinned-rows{position:relative;z-index:3;flex:none;background:var(--agrid-color-bg)}.ag-pinned-rows--top{box-shadow:0 2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows--bottom{box-shadow:0 -2px 4px -2px var(--agrid-color-shadow)}.ag-pinned-rows .ag-row{background:var(--agrid-color-bg-subtle)}\n"] }]
8053
+ }], ctorParameters: () => [], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], recordEdit: [{ type: i0.Output, args: ["recordEdit"] }], rowRemoved: [{ type: i0.Output, args: ["rowRemoved"] }], prepareAddRecord: [{ type: i0.Output, args: ["prepareAddRecord"] }], rowReorder: [{ type: i0.Output, args: ["rowReorder"] }], rowSelect: [{ type: i0.Output, args: ["rowSelect"] }], rowDoubleClicked: [{ type: i0.Output, args: ["rowDoubleClicked"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], treeNodeClick: [{ type: i0.Output, args: ["treeNodeClick"] }], settingsChange: [{ type: i0.Output, args: ["settingsChange"] }], treeNodeDoubleClicked: [{ type: i0.Output, args: ["treeNodeDoubleClicked"] }], rowChanged: [{ type: i0.Output, args: ["rowChanged"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], quickFilterChange: [{ type: i0.Output, args: ["quickFilterChange"] }], validationFailed: [{ type: i0.Output, args: ["validationFailed"] }], cellInfo: [{ type: i0.Output, args: ["cellInfo"] }], menuBarAction: [{ type: i0.Output, args: ["menuBarAction"] }], viewport: [{ type: i0.ViewChild, args: ['scrollViewport', { isSignal: true }] }], pinnedViewport: [{ type: i0.ViewChild, args: ['pinnedViewport', { isSignal: true }] }], rightPinnedViewport: [{ type: i0.ViewChild, args: ['rightPinnedViewport', { isSignal: true }] }], wrapperEl: [{ type: i0.ViewChild, args: ['wrapper', { isSignal: true }] }], horizontalScrollerEl: [{ type: i0.ViewChild, args: ['horizontalScroller', { isSignal: true }] }] } });
7250
8054
 
7251
8055
  let nextPageSelectorId = 0;
7252
8056
  /** Compact previous/input/dropdown/next control for navigating a labeled page list. */
@@ -7631,14 +8435,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
7631
8435
 
7632
8436
  /** Provider-style configuration and datasource container for `<agrid-tree>`. */
7633
8437
  class AgridTreeProvider {
8438
+ /** Rows projected into the standalone tree. */
7634
8439
  datasource;
8440
+ /** Shared hierarchy configuration; column-specific aggregation is not used here. */
7635
8441
  treeConfig;
8442
+ /** Optional host label resolver for parent-linked datasource rows. */
7636
8443
  getLabel;
8444
+ /** Optional host resolver for secondary node text. */
7637
8445
  getDescription;
8446
+ /** Effective node selection behavior. */
7638
8447
  selection;
8448
+ /** Effective fixed node height in pixels. */
7639
8449
  rowHeight;
8450
+ /** Effective accessible name applied to the tree root. */
7640
8451
  ariaLabel;
8452
+ /** Effective empty-state message. */
7641
8453
  emptyText;
8454
+ /** Normalize optional standalone-tree settings and retain the reactive datasource. */
7642
8455
  constructor(config) {
7643
8456
  this.datasource = config.datasource;
7644
8457
  this.treeConfig = config.treeConfig;
@@ -7658,5 +8471,5 @@ const ColDefAutoSize = -1;
7658
8471
  * Generated bundle index. Do not edit.
7659
8472
  */
7660
8473
 
7661
- export { AGRID_LOCALE_TEXT, AgridComponent, AgridControl, AgridDataSource, AgridPageSelectorComponent, AgridProvider, AgridTreeComponent, AgridTreeProvider, ColDefAutoSize };
8474
+ export { AGRID_LOCALE_TEXT, AgridComponent, AgridControl, AgridDataSource, AgridPageSelectorComponent, AgridProvider, AgridServerSideRowModel, AgridTreeComponent, AgridTreeProvider, ColDefAutoSize };
7662
8475
  //# sourceMappingURL=thkl-agrid.mjs.map