@thkl/agrid 0.1.5 → 0.1.9

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,7 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, signal, computed, viewChild, effect, ChangeDetectionStrategy, Component, linkedSignal, isWritableSignal, inject, DestroyRef, ElementRef, afterNextRender } 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';
3
3
  import * as i1 from '@angular/cdk/scrolling';
4
- import { ScrollingModule } from '@angular/cdk/scrolling';
4
+ import { VIRTUAL_SCROLL_STRATEGY, ScrollingModule } from '@angular/cdk/scrolling';
5
+ import { NgTemplateOutlet } from '@angular/common';
6
+ import { Subject, distinctUntilChanged } from 'rxjs';
5
7
 
6
8
  // Display resolution
7
9
  // Matches YYYY-MM-DD with optional time component — strict enough to avoid false positives.
@@ -77,7 +79,11 @@ function getDisplayForField(col, raw, locale) {
77
79
  }
78
80
  /** Returns whether a virtual-scroll item represents a data row. */
79
81
  function isDataRowItem(item) {
80
- return typeof item === 'object' && item !== null && 'row' in item;
82
+ return typeof item === 'object' && item !== null && 'row' in item && !('detailFor' in item);
83
+ }
84
+ /** Returns whether a virtual-scroll item represents a master/detail panel row. */
85
+ function isDetailRowItem(item) {
86
+ return typeof item === 'object' && item !== null && 'detailFor' in item;
81
87
  }
82
88
  /** Returns whether a virtual-scroll item represents a group header. */
83
89
  function isGroupHeaderItem(item) {
@@ -90,7 +96,15 @@ function isGroupHeaderItem(item) {
90
96
  * additionally narrows to the indentation/expansion fields via the `level` discriminator.
91
97
  */
92
98
  function isTreeRowItem(item) {
93
- return typeof item === 'object' && item !== null && 'level' in item;
99
+ return isDataRowItem(item) && 'level' in item;
100
+ }
101
+ /** Returns whether a virtual-scroll item is a generated path-tree branch node. */
102
+ function isPathTreeNodeItem(item) {
103
+ return typeof item === 'object' && item !== null && 'pathNodeId' in item;
104
+ }
105
+ /** Returns whether a tree configuration derives its hierarchy from path segments. */
106
+ function isPathTreeConfig(config) {
107
+ return typeof config.getPath === 'function';
94
108
  }
95
109
  // Filtering
96
110
  /** Apply text substring and value-set filters, returning the surviving row indices. */
@@ -106,9 +120,70 @@ function applyTextAndValueFilters(rows, indices, filters, colMap, locale) {
106
120
  const allowed = new Set(filter.selectedValues);
107
121
  result = result.filter(i => allowed.has(String(rows[i][field] ?? '')));
108
122
  }
123
+ if (filter.operator && filter.operand != null && filter.operand !== '') {
124
+ result = result.filter(i => passesConditionFilter(col, rows[i][field], filter, locale));
125
+ }
109
126
  }
110
127
  return result;
111
128
  }
129
+ /**
130
+ * Evaluate a text, number, or date condition for one cell value.
131
+ */
132
+ function passesConditionFilter(col, raw, filter, locale) {
133
+ if (col?.type !== 'number' && col?.type !== 'date') {
134
+ const value = getDisplayForField(col, raw, locale).toLocaleLowerCase(locale);
135
+ const operand = String(filter.operand ?? '').toLocaleLowerCase(locale);
136
+ switch (filter.operator) {
137
+ case 'eq': return value === operand;
138
+ case 'neq': return value !== operand;
139
+ case 'startsWith': return value.startsWith(operand);
140
+ case 'endsWith': return value.endsWith(operand);
141
+ case 'includes': return value.includes(operand);
142
+ case 'notIncludes': return !value.includes(operand);
143
+ case 'like': {
144
+ const escaped = operand.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
145
+ const pattern = escaped.replace(/%/g, '.*').replace(/_/g, '.');
146
+ return new RegExp(`^${pattern}$`, 'u').test(value);
147
+ }
148
+ default: return true;
149
+ }
150
+ }
151
+ const isDate = col?.type === 'date' || looksLikeDate(raw);
152
+ const toNum = (v) => isDate
153
+ ? (v instanceof Date ? v.getTime() : new Date(v).getTime())
154
+ : Number(v);
155
+ const value = toNum(raw);
156
+ if (Number.isNaN(value))
157
+ return false;
158
+ const a = toNum(filter.operand);
159
+ if (Number.isNaN(a))
160
+ return true;
161
+ switch (filter.operator) {
162
+ case 'eq': return value === a;
163
+ case 'neq': return value !== a;
164
+ case 'gt': return value > a;
165
+ case 'gte': return value >= a;
166
+ case 'lt': return value < a;
167
+ case 'lte': return value <= a;
168
+ case 'between': {
169
+ const b = toNum(filter.operand2);
170
+ if (Number.isNaN(b))
171
+ return value >= a;
172
+ return value >= Math.min(a, b) && value <= Math.max(a, b);
173
+ }
174
+ default: return true;
175
+ }
176
+ }
177
+ /**
178
+ * Keep only the rows where at least one of the given columns' display value contains
179
+ * `text` (case-insensitive). An empty/whitespace `text` returns the indices unchanged.
180
+ */
181
+ function applyQuickFilter(rows, indices, text, cols, locale) {
182
+ const q = text.trim().toLowerCase();
183
+ if (!q)
184
+ return indices;
185
+ return indices.filter(i => cols.some(col => getDisplayForField(col, rows[i][col.field], locale).toLowerCase().includes(q)));
186
+ }
112
187
  // Sorting
113
188
  /**
114
189
  * Sort indices by one or more columns in priority order.
@@ -119,18 +194,18 @@ function applySortToIndices(rows, indices, sortEntries, colMap, locale) {
119
194
  if (sortEntries.length === 0)
120
195
  return indices;
121
196
  const collator = new Intl.Collator(locale, { numeric: true, sensitivity: 'base' });
122
- const fields = sortEntries.map(([field, filter]) => ({
123
- field,
124
- direction: filter.sort === 'desc' ? -1 : 1,
125
- col: colMap.get(field),
126
- }));
127
- const decorated = indices.map((index, position) => ({
128
- index,
129
- position,
130
- keys: fields.map(({ field, col }) => {
197
+ const fields = sortEntries.map(([field, filter]) => {
198
+ const col = colMap.get(field);
199
+ const dateLike = new Uint8Array(indices.length);
200
+ const dateValues = new Float64Array(indices.length);
201
+ const numericValues = new Float64Array(indices.length);
202
+ numericValues.fill(Number.NaN);
203
+ const displayValues = new Array(indices.length);
204
+ for (let position = 0; position < indices.length; position++) {
205
+ const index = indices[position];
131
206
  const raw = rows[index][field];
132
- const dateLike = col?.type === 'date' || looksLikeDate(raw);
133
- const dateValue = dateLike
207
+ const isDateLike = col?.type === 'date' || looksLikeDate(raw);
208
+ const dateValue = isDateLike
134
209
  ? raw instanceof Date ? raw.getTime() : new Date(raw).getTime()
135
210
  : Number.NaN;
136
211
  const numericValue = col?.type === 'number'
@@ -139,44 +214,100 @@ function applySortToIndices(rows, indices, sortEntries, colMap, locale) {
139
214
  && typeof raw === 'number'
140
215
  && Number.isFinite(raw)
141
216
  ? raw
142
- : null;
143
- return {
144
- display: dateLike ? '' : getDisplayForField(col, raw, locale),
145
- dateLike,
146
- dateValue: Number.isNaN(dateValue) ? -1 : dateValue,
147
- numericValue,
148
- };
149
- }),
150
- }));
151
- decorated.sort((a, b) => {
217
+ : Number.NaN;
218
+ dateLike[position] = isDateLike ? 1 : 0;
219
+ dateValues[position] = Number.isNaN(dateValue) ? -1 : dateValue;
220
+ numericValues[position] = numericValue;
221
+ displayValues[position] = isDateLike ? '' : getDisplayForField(col, raw, locale);
222
+ }
223
+ return {
224
+ direction: filter.sort === 'desc' ? -1 : 1,
225
+ dateLike,
226
+ dateValues,
227
+ numericValues,
228
+ displayValues,
229
+ };
230
+ });
231
+ const positions = Array.from({ length: indices.length }, (_, position) => position);
232
+ positions.sort((a, b) => {
152
233
  for (let fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
153
- const keyA = a.keys[fieldIndex];
154
- const keyB = b.keys[fieldIndex];
234
+ const field = fields[fieldIndex];
155
235
  let comparison;
156
- if (keyA.dateLike || keyB.dateLike) {
157
- comparison = keyA.dateValue - keyB.dateValue;
236
+ if (field.dateLike[a] || field.dateLike[b]) {
237
+ comparison = field.dateValues[a] - field.dateValues[b];
158
238
  }
159
- else if (keyA.numericValue !== null && keyB.numericValue !== null) {
160
- comparison = keyA.numericValue - keyB.numericValue;
239
+ else if (!Number.isNaN(field.numericValues[a])
240
+ && !Number.isNaN(field.numericValues[b])) {
241
+ comparison = field.numericValues[a] - field.numericValues[b];
161
242
  }
162
243
  else {
163
- comparison = collator.compare(keyA.display, keyB.display);
244
+ comparison = collator.compare(field.displayValues[a], field.displayValues[b]);
164
245
  }
165
246
  if (comparison !== 0) {
166
- return comparison * fields[fieldIndex].direction;
247
+ return comparison * field.direction;
167
248
  }
168
249
  }
169
- return a.position - b.position;
250
+ return a - b;
170
251
  });
171
- return decorated.map(item => item.index);
252
+ return positions.map(position => indices[position]);
172
253
  }
173
- // Grouping
174
254
  /**
175
- * Bucket rows by the display value of `groupField`, sort group keys alphabetically,
176
- * optionally sort within groups by a secondary sort, and interleave group-header items.
177
- * Does NOT append the add-row null sentinel the caller does that.
255
+ * Compute aggregate values for the given rows across every column that has a static
256
+ * (`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.
178
258
  */
179
- function buildGroupedItems(rows, indices, groupField, colMap, sortEntries, expandedLabels, locale) {
259
+ function computeAggregates(rows, indices, cols, controlAggregates) {
260
+ const result = {};
261
+ for (const col of cols) {
262
+ const aggregate = controlAggregates[col.field] ?? col.aggregate;
263
+ if (!aggregate)
264
+ continue;
265
+ if (typeof aggregate === 'function') {
266
+ const values = indices.map(index => rows[index][col.field]);
267
+ result[col.field] = aggregate(values);
268
+ continue;
269
+ }
270
+ let count = 0;
271
+ let numericCount = 0;
272
+ let sum = 0;
273
+ let min = Infinity;
274
+ let max = -Infinity;
275
+ for (const index of indices) {
276
+ const raw = rows[index][col.field];
277
+ if (raw != null && raw !== '')
278
+ count++;
279
+ const value = Number(raw);
280
+ if (Number.isNaN(value))
281
+ continue;
282
+ numericCount++;
283
+ sum += value;
284
+ if (value < min)
285
+ min = value;
286
+ if (value > max)
287
+ max = value;
288
+ }
289
+ switch (aggregate) {
290
+ case 'sum':
291
+ result[col.field] = sum;
292
+ break;
293
+ case 'avg':
294
+ result[col.field] = numericCount ? sum / numericCount : null;
295
+ break;
296
+ case 'min':
297
+ result[col.field] = numericCount ? min : null;
298
+ break;
299
+ case 'max':
300
+ result[col.field] = numericCount ? max : null;
301
+ break;
302
+ case 'count':
303
+ result[col.field] = count;
304
+ break;
305
+ }
306
+ }
307
+ return result;
308
+ }
309
+ function buildGroupedItems(rows, indices, groupField, colMap, sortEntries, expandedLabels, locale, aggregateCols = [], controlAggregates = {}) {
310
+ const hasAggregates = aggregateCols.some(col => controlAggregates[col.field] ?? col.aggregate);
180
311
  const groupCol = colMap.get(groupField);
181
312
  const groups = new Map();
182
313
  for (const i of indices) {
@@ -197,7 +328,10 @@ function buildGroupedItems(rows, indices, groupField, colMap, sortEntries, expan
197
328
  for (const key of sortedKeys) {
198
329
  const groupRows = groups.get(key);
199
330
  const isExpanded = expandedLabels.has(key);
200
- items.push({ groupLabel: key, count: groupRows.length, collapsed: !isExpanded });
331
+ const aggregates = hasAggregates
332
+ ? computeAggregates(rows, groupRows, aggregateCols, controlAggregates)
333
+ : undefined;
334
+ items.push({ groupLabel: key, count: groupRows.length, collapsed: !isExpanded, aggregates });
201
335
  if (isExpanded) {
202
336
  for (const i of groupRows)
203
337
  items.push({ row: rows[i], originalIndex: i });
@@ -262,6 +396,123 @@ function buildTreeItems(rows, indices, accessors, expandedIds, forceExpandedIds)
262
396
  visit(rootIndex, 0);
263
397
  return items;
264
398
  }
399
+ /** Stable expansion id for a generated path prefix. */
400
+ function pathTreeNodeId(path) {
401
+ return `__agrid_path__${JSON.stringify(path.map(String))}`;
402
+ }
403
+ function pathTreeNodeUuid(path) {
404
+ const source = JSON.stringify(path.map(String));
405
+ let a = 0x9e3779b9;
406
+ let b = 0x85ebca6b;
407
+ let c = 0xc2b2ae35;
408
+ let d = 0x27d4eb2f;
409
+ for (let i = 0; i < source.length; i++) {
410
+ const code = source.charCodeAt(i);
411
+ a = Math.imul(a ^ code, 0x85ebca6b);
412
+ b = Math.imul(b ^ code, 0xc2b2ae35);
413
+ c = Math.imul(c ^ code, 0x27d4eb2f);
414
+ d = Math.imul(d ^ code, 0x165667b1);
415
+ }
416
+ const hex = [a, b, c, d]
417
+ .map(value => (value >>> 0).toString(16).padStart(8, '0'))
418
+ .join('')
419
+ .split('');
420
+ hex[12] = '5';
421
+ hex[16] = ((parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
422
+ 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
+ }
424
+ /** Builds display-only branch nodes and datasource-backed leaves from path segments. */
425
+ function buildPathTreeItems(rows, indices, config, expandedIds, forceExpanded = false) {
426
+ const roots = new Map();
427
+ for (const originalIndex of indices) {
428
+ const rawPath = config.getPath(rows[originalIndex]).filter(segment => String(segment).length > 0);
429
+ if (rawPath.length === 0)
430
+ continue;
431
+ const path = rawPath.map(String);
432
+ const formatSegment = (level, leaf) => config.formatPathSegment?.({
433
+ row: rows[originalIndex],
434
+ segment: rawPath[level],
435
+ level,
436
+ path: rawPath.slice(0, level + 1),
437
+ leaf,
438
+ }) ?? path[level];
439
+ const resolveNodeUuid = (prefix) => String(config.nodeUuid?.(rows[originalIndex])
440
+ ?? config.nodeUUid?.(rows[originalIndex])
441
+ ?? pathTreeNodeUuid(prefix));
442
+ let branches = roots;
443
+ for (let level = 0; level < path.length - 1; level++) {
444
+ const prefix = path.slice(0, level + 1);
445
+ const id = pathTreeNodeId(prefix);
446
+ let branch = branches.get(id);
447
+ if (!branch) {
448
+ branch = {
449
+ id,
450
+ uuid: resolveNodeUuid(prefix),
451
+ label: formatSegment(level, false),
452
+ level,
453
+ children: new Map(),
454
+ leaves: [],
455
+ };
456
+ branches.set(id, branch);
457
+ }
458
+ if (level === path.length - 2) {
459
+ branch.leaves.push({
460
+ originalIndex,
461
+ label: formatSegment(path.length - 1, true),
462
+ });
463
+ }
464
+ branches = branch.children;
465
+ }
466
+ if (path.length === 1) {
467
+ const id = pathTreeNodeId([]);
468
+ let branch = roots.get(id);
469
+ if (!branch) {
470
+ branch = {
471
+ id,
472
+ uuid: resolveNodeUuid([]),
473
+ label: '',
474
+ level: -1,
475
+ children: new Map(),
476
+ leaves: [],
477
+ };
478
+ roots.set(id, branch);
479
+ }
480
+ branch.leaves.push({ originalIndex, label: formatSegment(0, true) });
481
+ }
482
+ }
483
+ const items = [];
484
+ const visit = (branch) => {
485
+ const isHiddenRoot = branch.level < 0;
486
+ const expanded = forceExpanded || expandedIds.has(branch.id);
487
+ if (!isHiddenRoot) {
488
+ items.push({
489
+ uuid: branch.uuid,
490
+ pathNodeId: branch.id,
491
+ pathLabel: branch.label,
492
+ level: branch.level,
493
+ expandable: true,
494
+ expanded,
495
+ });
496
+ }
497
+ if (!isHiddenRoot && !expanded)
498
+ return;
499
+ for (const child of branch.children.values())
500
+ visit(child);
501
+ for (const leaf of branch.leaves) {
502
+ items.push({
503
+ row: rows[leaf.originalIndex],
504
+ originalIndex: leaf.originalIndex,
505
+ level: isHiddenRoot ? 0 : branch.level + 1,
506
+ expandable: false,
507
+ expanded: false,
508
+ treeLabel: leaf.label,
509
+ });
510
+ }
511
+ };
512
+ for (const branch of roots.values())
513
+ visit(branch);
514
+ return items;
515
+ }
265
516
  // Selection range
266
517
  /** Build the set of original indices spanning from `fromOrig` to `toOrig` in display order. */
267
518
  function buildSelectionRange(fromOrig, toOrig, items) {
@@ -278,6 +529,11 @@ function buildSelectionRange(fromOrig, toOrig, items) {
278
529
  }
279
530
  return next;
280
531
  }
532
+ /** Test a complete proposed editor value against a regex input mask. */
533
+ function matchesInputMask(value, mask) {
534
+ const flags = mask.flags.replace(/[gy]/g, '');
535
+ return new RegExp(`^(?:${mask.source})$`, flags).test(String(value ?? ''));
536
+ }
281
537
 
282
538
  /**
283
539
  * Individual cell component used inside `AgridComponent`.
@@ -296,6 +552,8 @@ class AgridCellComponent {
296
552
  colIndex = input.required(...(ngDevMode ? [{ debugName: "colIndex" }] : /* istanbul ignore next */ []));
297
553
  /** Current field value from the data source (displayed when not editing). */
298
554
  value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
555
+ /** Optional display-only text override; editing still uses the original {@link value}. */
556
+ displayValueOverride = input(null, ...(ngDevMode ? [{ debugName: "displayValueOverride" }] : /* istanbul ignore next */ []));
299
557
  /** Full row data — passed to `cellRenderer` when set. */
300
558
  row = input({}, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
301
559
  /** Locale used for built-in date formatting. */
@@ -304,6 +562,12 @@ class AgridCellComponent {
304
562
  selected = input(false, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
305
563
  /** Whether this cell is currently in edit mode. */
306
564
  editing = input(false, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
565
+ /** Whether this cell may be edited (drives the boolean checkbox enabled state). */
566
+ editable = input(true, ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
567
+ /** Whether a right-aligned information button is visible in this cell. */
568
+ showInfoIcon = input(false, ...(ngDevMode ? [{ debugName: "showInfoIcon" }] : /* istanbul ignore next */ []));
569
+ /** Validation error message to show under the editor, or `null` when the value is valid. */
570
+ error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
307
571
  /** Whether this cell is the tree column and should render indentation + a twisty. */
308
572
  treeCell = input(false, ...(ngDevMode ? [{ debugName: "treeCell" }] : /* istanbul ignore next */ []));
309
573
  /** Depth of the row in the tree (drives indentation). Root rows are `0`. */
@@ -321,6 +585,8 @@ class AgridCellComponent {
321
585
  * while the cell is selected (type-to-start-editing behavior).
322
586
  */
323
587
  seedChar = input('', ...(ngDevMode ? [{ debugName: "seedChar" }] : /* istanbul ignore next */ []));
588
+ /** Whether an unseeded text editor should select all text when focused. */
589
+ selectTextOnEdit = input(true, ...(ngDevMode ? [{ debugName: "selectTextOnEdit" }] : /* istanbul ignore next */ []));
324
590
  /**
325
591
  * Emitted on single click — the grid selects this cell.
326
592
  * For `values` columns the grid also enters edit mode immediately.
@@ -328,6 +594,10 @@ class AgridCellComponent {
328
594
  activate = output();
329
595
  /** Emitted on double-click — the grid enters edit mode. */
330
596
  startEdit = output();
597
+ /** Emitted when a boolean-column checkbox is toggled, carrying the new value. */
598
+ booleanToggle = output();
599
+ /** Emitted when the cell's optional information button is clicked. */
600
+ infoClick = output();
331
601
  /**
332
602
  * Emitted on every keystroke inside the edit input or on every select change.
333
603
  * The grid stores the latest value in `currentDraft` so it can commit on Tab / Enter.
@@ -338,10 +608,30 @@ class AgridCellComponent {
338
608
  /** String value accepted by the active native input element. */
339
609
  editorValue = computed(() => {
340
610
  const draft = this.draft();
341
- return this.col().type === 'date'
342
- ? getDateInputValue(draft)
343
- : String(draft ?? '');
611
+ if (this.col().type === 'date') {
612
+ return getDateInputValue(draft);
613
+ }
614
+ return String(draft ?? '');
344
615
  }, ...(ngDevMode ? [{ debugName: "editorValue" }] : /* istanbul ignore next */ []));
616
+ /** Mask selected for the current row and cell, if this is a maskable text column. */
617
+ resolvedInputMask = computed(() => {
618
+ const col = this.col();
619
+ if (!col.inputMask || col.type === 'number' || col.type === 'date' || col.type === 'boolean') {
620
+ return null;
621
+ }
622
+ return col.inputMask({
623
+ row: this.row(),
624
+ value: this.value(),
625
+ column: col,
626
+ }) ?? null;
627
+ }, ...(ngDevMode ? [{ debugName: "resolvedInputMask" }] : /* istanbul ignore next */ []));
628
+ /** Whether this cell renders as an inline boolean checkbox (no edit mode). */
629
+ booleanCell = computed(() => this.col().type === 'boolean' && !this.col().cellRenderer, ...(ngDevMode ? [{ debugName: "booleanCell" }] : /* istanbul ignore next */ []));
630
+ /** Truthiness of the current boolean value (accepts `true`, `'true'`, `1`, `'1'`). */
631
+ booleanChecked = computed(() => {
632
+ const v = this.value();
633
+ return v === true || v === 1 || v === 'true' || v === '1';
634
+ }, ...(ngDevMode ? [{ debugName: "booleanChecked" }] : /* istanbul ignore next */ []));
345
635
  renderedHtml = computed(() => {
346
636
  const renderer = this.col().cellRenderer;
347
637
  if (!renderer)
@@ -363,19 +653,8 @@ class AgridCellComponent {
363
653
  * Priority: ValueOption label → `ColDef.formatter` → raw string.
364
654
  */
365
655
  displayValue = computed(() => {
366
- const raw = this.value();
367
- const col = this.col();
368
- if (col.values?.length) {
369
- const opt = col.values.find(v => typeof v === 'string' ? v === raw : v.value === raw);
370
- if (opt !== undefined)
371
- return typeof opt === 'string' ? opt : opt.label;
372
- }
373
- if (col.formatter)
374
- return col.formatter(raw);
375
- if (col.type === 'date' || looksLikeDate(raw)) {
376
- return formatDateValue(raw, this.locale(), col.type === 'date');
377
- }
378
- return String(raw ?? '');
656
+ return this.displayValueOverride()
657
+ ?? getDisplayForField(this.col(), this.value(), this.locale());
379
658
  }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
380
659
  /**
381
660
  * Index of the currently selected option in `valueOptions`.
@@ -398,16 +677,20 @@ class AgridCellComponent {
398
677
  const initialValue = isDate
399
678
  ? dateInitial
400
679
  : seed !== '' ? seed : this.value();
401
- this.draft.set(initialValue);
680
+ const mask = this.resolvedInputMask();
681
+ const acceptedInitialValue = seed !== '' && mask && !matchesInputMask(initialValue, mask)
682
+ ? this.value()
683
+ : initialValue;
684
+ this.draft.set(acceptedInitialValue);
402
685
  setTimeout(() => {
403
686
  const input = this.inputEl()?.nativeElement;
404
687
  if (input) {
405
688
  const displaySeed = isDate
406
689
  ? dateInitial
407
- : seed !== '' ? seed : String(this.value() ?? '');
690
+ : String(acceptedInitialValue ?? '');
408
691
  input.value = displaySeed;
409
692
  input.focus();
410
- if (input.type === 'text' && !seed)
693
+ if (input.type === 'text' && !seed && this.selectTextOnEdit())
411
694
  input.select();
412
695
  else if (input.type === 'text') {
413
696
  const len = displaySeed.length;
@@ -432,14 +715,35 @@ class AgridCellComponent {
432
715
  }
433
716
  });
434
717
  }
718
+ /** Toggle a boolean cell's value, driven entirely from data (no DOM toggle). */
719
+ onCheckboxClick(event) {
720
+ event.stopPropagation();
721
+ event.preventDefault();
722
+ if (!this.editable())
723
+ return;
724
+ this.booleanToggle.emit(!this.booleanChecked());
725
+ }
435
726
  /** Emit a tree expand/collapse request without selecting or editing the cell. */
436
727
  onTreeToggle(event) {
437
728
  event.stopPropagation();
438
729
  this.treeToggle.emit();
439
730
  }
731
+ /** Emits the information action without activating or editing the cell. */
732
+ onInfoClick(event) {
733
+ event.preventDefault();
734
+ event.stopPropagation();
735
+ this.infoClick.emit();
736
+ }
440
737
  /** Forward `<input>` changes to the grid. */
441
738
  onInput(event) {
442
- const val = event.target.value;
739
+ const input = event.target;
740
+ const val = input.value;
741
+ const mask = this.resolvedInputMask();
742
+ if (mask && !matchesInputMask(val, mask)) {
743
+ input.value = String(this.draft() ?? '');
744
+ input.setSelectionRange(input.value.length, input.value.length);
745
+ return;
746
+ }
443
747
  const draft = this.col().type === 'date'
444
748
  ? coerceDateInputValue(val, this.value())
445
749
  : val;
@@ -458,8 +762,18 @@ class AgridCellComponent {
458
762
  this.draftChange.emit(rawValue);
459
763
  }
460
764
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
461
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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 }, 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 }, 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 } }, outputs: { treeToggle: "treeToggle", activate: "activate", startEdit: "startEdit", 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()", "attr.aria-readonly": "col().editable === false ? \"true\" : 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: `
462
- @if (editing()) {
765
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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: `
766
+ @if (booleanCell()) {
767
+ <input
768
+ type="checkbox"
769
+ class="ag-cell-checkbox"
770
+ [checked]="booleanChecked()"
771
+ [disabled]="!editable()"
772
+ [attr.aria-label]="col().header"
773
+ (click)="onCheckboxClick($event)"
774
+ (dblclick)="$event.stopPropagation()"
775
+ />
776
+ } @else if (editing()) {
463
777
  @if (col().values?.length) {
464
778
  <select
465
779
  #editSelect
@@ -474,11 +788,15 @@ class AgridCellComponent {
474
788
  <input
475
789
  #editInput
476
790
  class="ag-cell-input"
791
+ [class.ag-cell-input--invalid]="!!error()"
477
792
  [type]="col().type === 'date' ? 'date' : col().type === 'number' ? 'number' : 'text'"
478
793
  [value]="editorValue()"
479
794
  (input)="onInput($event)"
480
795
  />
481
796
  }
797
+ @if (error(); as msg) {
798
+ <span class="ag-cell-error" role="alert">{{ msg }}</span>
799
+ }
482
800
  } @else {
483
801
  @if (treeCell()) {
484
802
  <span class="ag-tree-prefix" [style.padding-left.px]="treeLevel() * treeIndent">
@@ -501,7 +819,18 @@ class AgridCellComponent {
501
819
  <span class="ag-cell-value">{{ displayValue() }}</span>
502
820
  }
503
821
  }
504
- `, isInline: true, styles: [":host{position:relative;display:block;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;padding:0 6px;line-height:32px;cursor:default;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box}:host(.ag-cell--changed):after{content:\"\";position:absolute;top:0;right:0;width:0;height:0;border-top:8px solid var(--agrid-color-cell-changed);border-left:8px solid transparent;pointer-events:none}:host(.selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.ag-cell--range-selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.editing){padding:0;background:var(--agrid-color-bg)}.ag-cell-value{display:block;overflow:hidden;text-overflow:ellipsis}:host(.ag-cell--tree){display:flex;align-items:center}:host(.ag-cell--tree) .ag-cell-value{flex:1 1 auto;min-width:0}.ag-tree-prefix{flex:0 0 auto;display:inline-flex;align-items:center;height:100%}.ag-tree-twisty,.ag-tree-twisty-spacer{width:16px;flex:0 0 16px}.ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0;border:none;background:transparent;color:var(--agrid-color-text-muted);font-size:9px;line-height:1;cursor:pointer;transition:transform .15s ease}.ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-cell-input{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 6px;box-sizing:border-box;background:transparent}.ag-cell-select{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 2px;box-sizing:border-box;background:transparent;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
822
+ @if (showInfoIcon() && !editing()) {
823
+ <button
824
+ type="button"
825
+ class="ag-cell-info"
826
+ aria-label="More information"
827
+ title="More information"
828
+ (pointerdown)="$event.stopPropagation()"
829
+ (click)="onInfoClick($event)"
830
+ (dblclick)="$event.stopPropagation()"
831
+ >?</button>
832
+ }
833
+ `, isInline: true, styles: [":host{position:relative;display:block;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;padding:0 6px;line-height:32px;cursor:default;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box}:host(.ag-cell--changed):after{content:\"\";position:absolute;top:0;right:0;width:0;height:0;border-top:8px solid var(--agrid-color-cell-changed);border-left:8px solid transparent;pointer-events:none}:host(.selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.ag-cell--range-selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.editing){padding:0;background:var(--agrid-color-bg)}.ag-cell-value{display:block;overflow:hidden;text-overflow:ellipsis}:host(.ag-cell--tree){display:flex;align-items:center}:host(.ag-cell--tree) .ag-cell-value{flex:1 1 auto;min-width:0}:host(.ag-cell--with-info) .ag-cell-value{padding-right:22px}.ag-cell-info{position:absolute;top:50%;right:4px;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:1px solid var(--agrid-color-border);border-radius:50%;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg);font:600 11px/1 sans-serif;cursor:pointer}.ag-cell-info:hover,.ag-cell-info:focus-visible{color:var(--agrid-color-accent);border-color:var(--agrid-color-accent);outline:none}.ag-tree-prefix{flex:0 0 auto;display:inline-flex;align-items:center;height:100%}.ag-tree-twisty,.ag-tree-twisty-spacer{width:16px;flex:0 0 16px}.ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0;border:none;background:transparent;color:var(--agrid-color-text-muted);font-size:9px;line-height:1;cursor:pointer;transition:transform .15s ease}.ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-cell-input{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 6px;box-sizing:border-box;background:transparent;user-select:text;-webkit-user-select:text}.ag-cell-select{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 2px;box-sizing:border-box;background:transparent;cursor:pointer}.ag-cell-checkbox{display:block;margin:9px auto;cursor:pointer;accent-color:var(--agrid-color-accent)}.ag-cell-checkbox:disabled{cursor:not-allowed;opacity:.6}.ag-cell-input--invalid{outline:2px solid var(--agrid-color-danger);outline-offset:-2px}.ag-cell-error{position:absolute;left:0;top:100%;z-index:5;max-width:240px;padding:3px 8px;font-size:12px;line-height:1.4;white-space:normal;color:#fff;background:var(--agrid-color-danger);border-radius:0 0 4px 4px;box-shadow:0 2px 6px #0000002e;pointer-events:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
505
834
  }
506
835
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridCellComponent, decorators: [{
507
836
  type: Component,
@@ -510,12 +839,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
510
839
  '[class.selected]': 'selected()',
511
840
  '[class.editing]': 'editing()',
512
841
  '[class.ag-cell--tree]': 'treeCell()',
513
- '[attr.aria-readonly]': 'col().editable === false ? "true" : null',
842
+ '[class.ag-cell--with-info]': 'showInfoIcon() && !editing()',
843
+ '[attr.aria-readonly]': '!editable() ? "true" : null',
844
+ '[attr.title]': 'displayValue()',
514
845
  '(click)': 'activate.emit($event)',
515
846
  '(dblclick)': 'startEdit.emit()',
516
847
  tabindex: '-1',
517
848
  }, template: `
518
- @if (editing()) {
849
+ @if (booleanCell()) {
850
+ <input
851
+ type="checkbox"
852
+ class="ag-cell-checkbox"
853
+ [checked]="booleanChecked()"
854
+ [disabled]="!editable()"
855
+ [attr.aria-label]="col().header"
856
+ (click)="onCheckboxClick($event)"
857
+ (dblclick)="$event.stopPropagation()"
858
+ />
859
+ } @else if (editing()) {
519
860
  @if (col().values?.length) {
520
861
  <select
521
862
  #editSelect
@@ -530,11 +871,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
530
871
  <input
531
872
  #editInput
532
873
  class="ag-cell-input"
874
+ [class.ag-cell-input--invalid]="!!error()"
533
875
  [type]="col().type === 'date' ? 'date' : col().type === 'number' ? 'number' : 'text'"
534
876
  [value]="editorValue()"
535
877
  (input)="onInput($event)"
536
878
  />
537
879
  }
880
+ @if (error(); as msg) {
881
+ <span class="ag-cell-error" role="alert">{{ msg }}</span>
882
+ }
538
883
  } @else {
539
884
  @if (treeCell()) {
540
885
  <span class="ag-tree-prefix" [style.padding-left.px]="treeLevel() * treeIndent">
@@ -557,8 +902,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
557
902
  <span class="ag-cell-value">{{ displayValue() }}</span>
558
903
  }
559
904
  }
560
- `, styles: [":host{position:relative;display:block;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;padding:0 6px;line-height:32px;cursor:default;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box}:host(.ag-cell--changed):after{content:\"\";position:absolute;top:0;right:0;width:0;height:0;border-top:8px solid var(--agrid-color-cell-changed);border-left:8px solid transparent;pointer-events:none}:host(.selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.ag-cell--range-selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.editing){padding:0;background:var(--agrid-color-bg)}.ag-cell-value{display:block;overflow:hidden;text-overflow:ellipsis}:host(.ag-cell--tree){display:flex;align-items:center}:host(.ag-cell--tree) .ag-cell-value{flex:1 1 auto;min-width:0}.ag-tree-prefix{flex:0 0 auto;display:inline-flex;align-items:center;height:100%}.ag-tree-twisty,.ag-tree-twisty-spacer{width:16px;flex:0 0 16px}.ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0;border:none;background:transparent;color:var(--agrid-color-text-muted);font-size:9px;line-height:1;cursor:pointer;transition:transform .15s ease}.ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-cell-input{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 6px;box-sizing:border-box;background:transparent}.ag-cell-select{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 2px;box-sizing:border-box;background:transparent;cursor:pointer}\n"] }]
561
- }], ctorParameters: () => [], propDecorators: { col: [{ type: i0.Input, args: [{ isSignal: true, alias: "col", required: true }] }], rowIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIndex", required: true }] }], colIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "colIndex", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], treeCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeCell", required: false }] }], treeLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeLevel", required: false }] }], treeExpandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeExpandable", required: false }] }], treeExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeExpanded", required: false }] }], treeToggle: [{ type: i0.Output, args: ["treeToggle"] }], seedChar: [{ type: i0.Input, args: [{ isSignal: true, alias: "seedChar", required: false }] }], activate: [{ type: i0.Output, args: ["activate"] }], startEdit: [{ type: i0.Output, args: ["startEdit"] }], draftChange: [{ type: i0.Output, args: ["draftChange"] }], inputEl: [{ type: i0.ViewChild, args: ['editInput', { isSignal: true }] }], selectEl: [{ type: i0.ViewChild, args: ['editSelect', { isSignal: true }] }] } });
905
+ @if (showInfoIcon() && !editing()) {
906
+ <button
907
+ type="button"
908
+ class="ag-cell-info"
909
+ aria-label="More information"
910
+ title="More information"
911
+ (pointerdown)="$event.stopPropagation()"
912
+ (click)="onInfoClick($event)"
913
+ (dblclick)="$event.stopPropagation()"
914
+ >?</button>
915
+ }
916
+ `, styles: [":host{position:relative;display:block;border-right:1px solid var(--agrid-color-border);border-bottom:1px solid var(--agrid-color-border);overflow:hidden;white-space:nowrap;padding:0 6px;line-height:32px;cursor:default;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box}:host(.ag-cell--changed):after{content:\"\";position:absolute;top:0;right:0;width:0;height:0;border-top:8px solid var(--agrid-color-cell-changed);border-left:8px solid transparent;pointer-events:none}:host(.selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.ag-cell--range-selected){outline:2px solid var(--agrid-color-accent);outline-offset:-2px}:host(.editing){padding:0;background:var(--agrid-color-bg)}.ag-cell-value{display:block;overflow:hidden;text-overflow:ellipsis}:host(.ag-cell--tree){display:flex;align-items:center}:host(.ag-cell--tree) .ag-cell-value{flex:1 1 auto;min-width:0}:host(.ag-cell--with-info) .ag-cell-value{padding-right:22px}.ag-cell-info{position:absolute;top:50%;right:4px;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:1px solid var(--agrid-color-border);border-radius:50%;color:var(--agrid-color-text-muted);background:var(--agrid-color-bg);font:600 11px/1 sans-serif;cursor:pointer}.ag-cell-info:hover,.ag-cell-info:focus-visible{color:var(--agrid-color-accent);border-color:var(--agrid-color-accent);outline:none}.ag-tree-prefix{flex:0 0 auto;display:inline-flex;align-items:center;height:100%}.ag-tree-twisty,.ag-tree-twisty-spacer{width:16px;flex:0 0 16px}.ag-tree-twisty{display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0;border:none;background:transparent;color:var(--agrid-color-text-muted);font-size:9px;line-height:1;cursor:pointer;transition:transform .15s ease}.ag-tree-twisty--expanded{transform:rotate(90deg)}.ag-cell-input{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 6px;box-sizing:border-box;background:transparent;user-select:text;-webkit-user-select:text}.ag-cell-select{width:100%;height:100%;border:none;outline:none;font:inherit;padding:0 2px;box-sizing:border-box;background:transparent;cursor:pointer}.ag-cell-checkbox{display:block;margin:9px auto;cursor:pointer;accent-color:var(--agrid-color-accent)}.ag-cell-checkbox:disabled{cursor:not-allowed;opacity:.6}.ag-cell-input--invalid{outline:2px solid var(--agrid-color-danger);outline-offset:-2px}.ag-cell-error{position:absolute;left:0;top:100%;z-index:5;max-width:240px;padding:3px 8px;font-size:12px;line-height:1.4;white-space:normal;color:#fff;background:var(--agrid-color-danger);border-radius:0 0 4px 4px;box-shadow:0 2px 6px #0000002e;pointer-events:none}\n"] }]
917
+ }], ctorParameters: () => [], propDecorators: { col: [{ type: i0.Input, args: [{ isSignal: true, alias: "col", required: true }] }], rowIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIndex", required: true }] }], colIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "colIndex", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], displayValueOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayValueOverride", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], showInfoIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInfoIcon", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], treeCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeCell", required: false }] }], treeLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeLevel", required: false }] }], treeExpandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeExpandable", required: false }] }], treeExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "treeExpanded", required: false }] }], treeToggle: [{ type: i0.Output, args: ["treeToggle"] }], seedChar: [{ type: i0.Input, args: [{ isSignal: true, alias: "seedChar", required: false }] }], selectTextOnEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectTextOnEdit", required: false }] }], activate: [{ type: i0.Output, args: ["activate"] }], startEdit: [{ type: i0.Output, args: ["startEdit"] }], booleanToggle: [{ type: i0.Output, args: ["booleanToggle"] }], infoClick: [{ type: i0.Output, args: ["infoClick"] }], draftChange: [{ type: i0.Output, args: ["draftChange"] }], inputEl: [{ type: i0.ViewChild, args: ['editInput', { isSignal: true }] }], selectEl: [{ type: i0.ViewChild, args: ['editSelect', { isSignal: true }] }] } });
562
918
 
563
919
  /** Guarded access to browser-only APIs used by the grid. @internal */
564
920
  class AgridBrowserAdapter {
@@ -603,6 +959,10 @@ class AgridBrowserAdapter {
603
959
  viewportWidth() {
604
960
  return this.win?.innerWidth ?? Number.POSITIVE_INFINITY;
605
961
  }
962
+ /** Returns the viewport height or infinity during server rendering. */
963
+ viewportHeight() {
964
+ return this.win?.innerHeight ?? Number.POSITIVE_INFINITY;
965
+ }
606
966
  /** Returns computed styles when a window is available. */
607
967
  computedStyle(element) {
608
968
  return this.win?.getComputedStyle?.(element) ?? null;
@@ -746,7 +1106,7 @@ class AgridClipboardHandler {
746
1106
  for (let colOffset = 0; colOffset < rows[rowOffset].length; colOffset++) {
747
1107
  const colIndex = start.colIndex + colOffset;
748
1108
  const col = cols[colIndex];
749
- if (!col || !this.opts.isCellEditable(col))
1109
+ if (!col || !this.opts.isCellEditable(col, item.originalIndex))
750
1110
  continue;
751
1111
  const oldValue = dataSource.getRow(item.originalIndex)[col.field];
752
1112
  const newValue = this.coercePastedValue(rows[rowOffset][colOffset], col);
@@ -1024,7 +1384,27 @@ const AGRID_LOCALE_TEXT = {
1024
1384
  columnMenu: 'Column menu',
1025
1385
  columns: 'Columns',
1026
1386
  detail: 'Detail',
1387
+ toggleDetail: 'Toggle detail panel',
1027
1388
  hiddenColumn: '(hidden)',
1389
+ filterCondition: 'Condition',
1390
+ filterConditionMenu: 'Filter condition',
1391
+ filterValue: 'Value',
1392
+ filterNoCondition: 'No condition',
1393
+ filterOpEquals: 'Equals',
1394
+ filterOpNotEquals: 'Not equal',
1395
+ filterOpGreater: 'Greater than',
1396
+ filterOpGreaterEqual: 'Greater or equal',
1397
+ filterOpLess: 'Less than',
1398
+ filterOpLessEqual: 'Less or equal',
1399
+ filterOpBetween: 'Between',
1400
+ filterOpBefore: 'Before',
1401
+ filterOpAfter: 'After',
1402
+ filterOpOn: 'On',
1403
+ filterOpLike: 'Like (% and _ wildcards)',
1404
+ filterOpStartsWith: 'Starts with',
1405
+ filterOpEndsWith: 'Ends with',
1406
+ filterOpIncludes: 'Includes',
1407
+ filterOpNotIncludes: 'Does not include',
1028
1408
  copyCellValue: 'Copy cell',
1029
1409
  copyRow: 'Copy row',
1030
1410
  confirmDeleteRow: 'Sure to delete?',
@@ -1048,9 +1428,13 @@ const AGRID_LOCALE_TEXT = {
1048
1428
  pinColumn: 'Pin left',
1049
1429
  pinColumnRight: 'Pin right',
1050
1430
  unpinColumnRight: 'Unpin right',
1431
+ pinRowTop: 'Pin row to top',
1432
+ pinRowBottom: 'Pin row to bottom',
1433
+ unpinRow: 'Unpin row',
1051
1434
  previous: 'Previous',
1052
1435
  resizeColumn: 'Resize column',
1053
1436
  rows: count => `${count} ${count === 1 ? 'row' : 'rows'}`,
1437
+ quickFilterPlaceholder: 'Search all columns...',
1054
1438
  searchValuesPlaceholder: 'Search values...',
1055
1439
  selectAll: '(Select All)',
1056
1440
  sortOnlyByThis: 'Sort only by this column',
@@ -1078,7 +1462,27 @@ const AGRID_LOCALE_TEXT = {
1078
1462
  columnMenu: 'Spaltenmenü',
1079
1463
  columns: 'Spalten',
1080
1464
  detail: 'Details',
1465
+ toggleDetail: 'Detailbereich umschalten',
1081
1466
  hiddenColumn: '(ausgeblendet)',
1467
+ filterCondition: 'Bedingung',
1468
+ filterConditionMenu: 'Filterbedingung',
1469
+ filterValue: 'Wert',
1470
+ filterNoCondition: 'Keine Bedingung',
1471
+ filterOpEquals: 'Gleich',
1472
+ filterOpNotEquals: 'Ungleich',
1473
+ filterOpGreater: 'Größer als',
1474
+ filterOpGreaterEqual: 'Größer oder gleich',
1475
+ filterOpLess: 'Kleiner als',
1476
+ filterOpLessEqual: 'Kleiner oder gleich',
1477
+ filterOpBetween: 'Zwischen',
1478
+ filterOpBefore: 'Vor',
1479
+ filterOpAfter: 'Nach',
1480
+ filterOpOn: 'Am',
1481
+ filterOpLike: 'Wie (% und _ Platzhalter)',
1482
+ filterOpStartsWith: 'Beginnt mit',
1483
+ filterOpEndsWith: 'Endet mit',
1484
+ filterOpIncludes: 'Enthält',
1485
+ filterOpNotIncludes: 'Enthält nicht',
1082
1486
  copyCellValue: 'Zelle kopieren',
1083
1487
  copyRow: 'Zeile kopieren',
1084
1488
  confirmDeleteRow: 'Wirklich löschen?',
@@ -1102,9 +1506,13 @@ const AGRID_LOCALE_TEXT = {
1102
1506
  pinColumn: 'Links fixieren',
1103
1507
  pinColumnRight: 'Rechts fixieren',
1104
1508
  unpinColumnRight: 'Rechts lösen',
1509
+ pinRowTop: 'Zeile oben fixieren',
1510
+ pinRowBottom: 'Zeile unten fixieren',
1511
+ unpinRow: 'Zeilenfixierung lösen',
1105
1512
  previous: 'Zurück',
1106
1513
  resizeColumn: 'Spaltenbreite ändern',
1107
1514
  rows: count => `${count} ${count === 1 ? 'Zeile' : 'Zeilen'}`,
1515
+ quickFilterPlaceholder: 'Alle Spalten durchsuchen...',
1108
1516
  searchValuesPlaceholder: 'Werte suchen...',
1109
1517
  selectAll: '(Alle auswählen)',
1110
1518
  sortOnlyByThis: 'Nur nach dieser Spalte sortieren',
@@ -1146,14 +1554,27 @@ function resolveAgridLocaleText(locale, localizations) {
1146
1554
  return { ...base, ...overrides };
1147
1555
  }
1148
1556
 
1557
+ /** Clamp a floating menu so it remains inside the browser viewport. @internal */
1558
+ function fitColumnMenuToViewport(x, y, width, height, viewportWidth, viewportHeight, margin = 8) {
1559
+ return {
1560
+ x: Math.max(margin, Math.min(x, viewportWidth - width - margin)),
1561
+ y: Math.max(margin, Math.min(y, viewportHeight - height - margin)),
1562
+ };
1563
+ }
1149
1564
  /** Floating column menu for sort, column actions, grouping, and value filters. */
1150
1565
  class AgridColumnMenuComponent {
1566
+ elementRef = inject(ElementRef);
1567
+ destroyRef = inject(DestroyRef);
1568
+ browser = new AgridBrowserAdapter();
1569
+ resizeObserver = null;
1151
1570
  /** Localized labels used in the menu. */
1152
1571
  localeText = input(AGRID_LOCALE_TEXT.en, ...(ngDevMode ? [{ debugName: "localeText" }] : /* istanbul ignore next */ []));
1153
1572
  /** Fixed viewport x-position for the menu. */
1154
1573
  x = input.required(...(ngDevMode ? [{ debugName: "x" }] : /* istanbul ignore next */ []));
1155
1574
  /** Fixed viewport y-position for the menu. */
1156
1575
  y = input.required(...(ngDevMode ? [{ debugName: "y" }] : /* istanbul ignore next */ []));
1576
+ /** Viewport-fitted coordinates used by the rendered menu. */
1577
+ position = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
1157
1578
  /** Header label for the active column. */
1158
1579
  header = input.required(...(ngDevMode ? [{ debugName: "header" }] : /* istanbul ignore next */ []));
1159
1580
  /** Current sort direction for the active column. */
@@ -1172,6 +1593,20 @@ class AgridColumnMenuComponent {
1172
1593
  filterable = input(false, ...(ngDevMode ? [{ debugName: "filterable" }] : /* istanbul ignore next */ []));
1173
1594
  /** Whether to show the Excel-style distinct-value picker. */
1174
1595
  showValueFilter = input(true, ...(ngDevMode ? [{ debugName: "showValueFilter" }] : /* istanbul ignore next */ []));
1596
+ /** Condition-filter input type for the active column, or `null` to hide the condition UI. */
1597
+ filterType = input(null, ...(ngDevMode ? [{ debugName: "filterType" }] : /* istanbul ignore next */ []));
1598
+ /** Current condition operator, or `null` when none is selected. */
1599
+ operator = input(null, ...(ngDevMode ? [{ debugName: "operator" }] : /* istanbul ignore next */ []));
1600
+ /** Current primary condition operand. */
1601
+ operand = input('', ...(ngDevMode ? [{ debugName: "operand" }] : /* istanbul ignore next */ []));
1602
+ /** Current secondary condition operand (used by `between`). */
1603
+ operand2 = input('', ...(ngDevMode ? [{ debugName: "operand2" }] : /* istanbul ignore next */ []));
1604
+ /** Emits the selected condition operator, or `null` to clear it. */
1605
+ operatorChange = output();
1606
+ /** Emits the primary condition operand text. */
1607
+ operandChange = output();
1608
+ /** Emits the secondary condition operand text (used by `between`). */
1609
+ operand2Change = output();
1175
1610
  /** Current search text for the value-filter option list. */
1176
1611
  search = input('', ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
1177
1612
  /** Whether the value filter currently allows every value. */
@@ -1223,13 +1658,75 @@ class AgridColumnMenuComponent {
1223
1658
  { value: 'count', symbol: '#', label: t.aggregateCount },
1224
1659
  ];
1225
1660
  }, ...(ngDevMode ? [{ debugName: "aggregateOptions" }] : /* istanbul ignore next */ []));
1661
+ /** Condition operators offered for the active column, labeled per column type. */
1662
+ operatorOptions = computed(() => {
1663
+ const t = this.localeText();
1664
+ if (this.filterType() === 'date') {
1665
+ return [
1666
+ { value: 'eq', label: t.filterOpOn },
1667
+ { value: 'lt', label: t.filterOpBefore },
1668
+ { value: 'gt', label: t.filterOpAfter },
1669
+ { value: 'between', label: t.filterOpBetween },
1670
+ ];
1671
+ }
1672
+ if (this.filterType() === 'text') {
1673
+ return [
1674
+ { value: 'eq', label: t.filterOpEquals },
1675
+ { value: 'neq', label: t.filterOpNotEquals },
1676
+ { value: 'like', label: t.filterOpLike },
1677
+ { value: 'startsWith', label: t.filterOpStartsWith },
1678
+ { value: 'endsWith', label: t.filterOpEndsWith },
1679
+ { value: 'includes', label: t.filterOpIncludes },
1680
+ { value: 'notIncludes', label: t.filterOpNotIncludes },
1681
+ ];
1682
+ }
1683
+ return [
1684
+ { value: 'eq', label: t.filterOpEquals },
1685
+ { value: 'neq', label: t.filterOpNotEquals },
1686
+ { value: 'gt', label: t.filterOpGreater },
1687
+ { value: 'gte', label: t.filterOpGreaterEqual },
1688
+ { value: 'lt', label: t.filterOpLess },
1689
+ { value: 'lte', label: t.filterOpLessEqual },
1690
+ { value: 'between', label: t.filterOpBetween },
1691
+ ];
1692
+ }, ...(ngDevMode ? [{ debugName: "operatorOptions" }] : /* istanbul ignore next */ []));
1693
+ /** Native input type for operand fields (date columns use a date picker). */
1694
+ operandInputType = computed(() => this.filterType() === 'date' ? 'date' : this.filterType() === 'number' ? 'number' : 'text', ...(ngDevMode ? [{ debugName: "operandInputType" }] : /* istanbul ignore next */ []));
1695
+ constructor() {
1696
+ effect(() => {
1697
+ this.position.set({ x: this.x(), y: this.y() });
1698
+ this.scheduleViewportFit();
1699
+ });
1700
+ afterNextRender(() => {
1701
+ const menu = this.menuElement();
1702
+ this.fitToViewport();
1703
+ if (menu && typeof ResizeObserver !== 'undefined') {
1704
+ this.resizeObserver = new ResizeObserver(() => this.fitToViewport());
1705
+ this.resizeObserver.observe(menu);
1706
+ }
1707
+ });
1708
+ this.destroyRef.onDestroy(() => this.resizeObserver?.disconnect());
1709
+ }
1710
+ scheduleViewportFit() {
1711
+ setTimeout(() => this.fitToViewport());
1712
+ }
1713
+ fitToViewport() {
1714
+ const menu = this.menuElement();
1715
+ if (!menu || !this.browser.available)
1716
+ return;
1717
+ const rect = menu.getBoundingClientRect();
1718
+ this.position.set(fitColumnMenuToViewport(this.x(), this.y(), rect.width, rect.height, this.browser.viewportWidth(), this.browser.viewportHeight()));
1719
+ }
1720
+ menuElement() {
1721
+ return this.elementRef.nativeElement.querySelector('.ag-filter-menu');
1722
+ }
1226
1723
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridColumnMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1227
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: AgridColumnMenuComponent, isStandalone: true, selector: "agrid-column-menu", inputs: { localeText: { classPropertyName: "localeText", publicName: "localeText", isSignal: true, isRequired: false, transformFunction: null }, x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: true, transformFunction: null }, sortDir: { classPropertyName: "sortDir", publicName: "sortDir", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, showColumnActions: { classPropertyName: "showColumnActions", publicName: "showColumnActions", isSignal: true, isRequired: false, transformFunction: null }, pinned: { classPropertyName: "pinned", publicName: "pinned", isSignal: true, isRequired: false, transformFunction: null }, groupable: { classPropertyName: "groupable", publicName: "groupable", isSignal: true, isRequired: false, transformFunction: null }, grouped: { classPropertyName: "grouped", publicName: "grouped", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, showValueFilter: { classPropertyName: "showValueFilter", publicName: "showValueFilter", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, allSelected: { classPropertyName: "allSelected", publicName: "allSelected", isSignal: true, isRequired: false, transformFunction: null }, valueItems: { classPropertyName: "valueItems", publicName: "valueItems", isSignal: true, isRequired: false, transformFunction: null }, sortPriority: { classPropertyName: "sortPriority", publicName: "sortPriority", isSignal: true, isRequired: false, transformFunction: null }, hasMultiSort: { classPropertyName: "hasMultiSort", publicName: "hasMultiSort", isSignal: true, isRequired: false, transformFunction: null }, aggregate: { classPropertyName: "aggregate", publicName: "aggregate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sort: "sort", resetSort: "resetSort", autosize: "autosize", togglePin: "togglePin", togglePinRight: "togglePinRight", hide: "hide", toggleGroup: "toggleGroup", clearFilter: "clearFilter", clearAll: "clearAll", searchChange: "searchChange", toggleAll: "toggleAll", toggleValue: "toggleValue", setAggregate: "setAggregate" }, ngImport: i0, template: "<div\n class=\"ag-filter-menu\"\n role=\"dialog\"\n [attr.aria-label]=\"localeText().columnMenu + ': ' + header()\"\n [style.left.px]=\"x()\"\n [style.top.px]=\"y()\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (sortable()) {\n <div class=\"ag-filter-menu-section\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'asc'\"\n (click)=\"sort.emit('asc')\"\n >\n \u2191 {{ localeText().sortAscending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'desc'\"\n (click)=\"sort.emit('desc')\"\n >\n \u2193 {{ localeText().sortDescending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n @if (hasMultiSort() && sortPriority() > 0) {\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"resetSort.emit(sortDir()!)\">{{ localeText().sortOnlyByThis }}</button>\n }\n @if (sortDir()) {\n <button class=\"ag-filter-menu-item\" (click)=\"sort.emit(sortDir()!)\">{{ localeText().clearSort }}</button>\n }\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item\" (click)=\"autosize.emit()\">\u2194 {{ localeText().autosizeColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'left'\"\n (click)=\"togglePin.emit()\"\n >\u22A3 {{ pinned() === 'left' ? localeText().unpinColumn : localeText().pinColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'right'\"\n (click)=\"togglePinRight.emit()\"\n >\u22A2 {{ pinned() === 'right' ? localeText().unpinColumnRight : localeText().pinColumnRight }}</button>\n <button class=\"ag-filter-menu-item\" (click)=\"hide.emit()\">{{ localeText().hideColumn }}</button>\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <span class=\"ag-filter-menu-aggregate-label\">{{ localeText().aggregate }}</span>\n <div class=\"ag-filter-menu-aggregate-btns\">\n @for (opt of aggregateOptions(); track opt.value) {\n <button\n class=\"ag-aggregate-btn\"\n [class.ag-aggregate-btn--active]=\"aggregate() === opt.value\"\n [title]=\"opt.label\"\n (click)=\"setAggregate.emit(aggregate() === opt.value ? null : opt.value)\"\n >{{ opt.symbol }}</button>\n }\n </div>\n </div>\n }\n\n @if (groupable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"grouped()\"\n (click)=\"toggleGroup.emit()\"\n >{{ grouped() ? '\u229F ' + localeText().ungroup : '\u229E ' + localeText().groupBy(header()) }}</button>\n </div>\n }\n\n @if (filterable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearFilter.emit()\">\n \u2715 {{ localeText().clearFilter }}\n </button>\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearAll.emit()\">\n \u2715 {{ localeText().clearAllFilters }}\n </button>\n </div>\n\n @if (showValueFilter()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <input\n class=\"ag-filter-menu-search\"\n [value]=\"search()\"\n (input)=\"searchChange.emit($any($event.target).value)\"\n [placeholder]=\"localeText().searchValuesPlaceholder\"\n (click)=\"$event.stopPropagation()\"\n />\n </div>\n\n <div class=\"ag-filter-menu-values\">\n <label class=\"ag-filter-menu-value\">\n <input\n type=\"checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleAll.emit()\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ localeText().selectAll }}</span>\n </label>\n @for (item of valueItems(); track item.rawStr) {\n <label\n class=\"ag-filter-menu-value\"\n [class.ag-filter-menu-value--inactive]=\"!item.active\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"item.selected\"\n [disabled]=\"!item.active\"\n (change)=\"toggleValue.emit(item.rawStr)\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ item.label || localeText().blank }}</span>\n </label>\n }\n </div>\n }\n }\n</div>\n", styles: [".ag-filter-menu{position:fixed;z-index:1001;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:200px;font-size:13px;overflow:hidden}.ag-filter-menu-section{padding:4px 0}.ag-filter-menu-section--border{border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-item{display:block;width:100%;padding:5px 14px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit;font-size:12px}.ag-filter-menu-item:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-item--active{color:var(--agrid-color-accent);font-weight:600}.ag-filter-menu-item--muted{color:var(--agrid-color-text-muted)}.ag-filter-menu-item--muted:hover{background:var(--mat-toolbar-container-background-color);color:var(--agrid-color-text-muted);cursor:not-allowed}.ag-filter-menu-search{background:var(--agrid-color-bg-subtle);display:block;width:calc(100% - 16px);margin:0 8px;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 6px}.ag-filter-menu-search:focus{border-color:var(--agrid-color-accent)}.ag-filter-menu-values{max-height:200px;overflow-y:auto;padding:4px 0;border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-value{display:flex;align-items:center;gap:6px;padding:3px 14px;cursor:pointer;font-size:12px}.ag-filter-menu-value:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-value input[type=checkbox]{cursor:pointer;margin:0}.ag-filter-menu-value--inactive{opacity:.38;cursor:default}.ag-filter-menu-value--inactive input[type=checkbox]{cursor:not-allowed}.ag-filter-menu-value-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-sort-priority-badge{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--agrid-color-accent);color:#fff;font-size:9px;font-weight:700;margin-left:auto}.ag-filter-menu-aggregate-label{display:block;padding:4px 14px 2px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none}.ag-filter-menu-aggregate-btns{display:flex;gap:4px;padding:2px 14px 6px}.ag-aggregate-btn{flex:1;height:26px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text-muted);font-size:13px;cursor:pointer;padding:0;transition:background 80ms,color 80ms}.ag-aggregate-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text);border-color:var(--agrid-color-text-muted)}.ag-aggregate-btn--active{background:var(--agrid-color-accent-subtle);border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-weight:700}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1724
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: AgridColumnMenuComponent, isStandalone: true, selector: "agrid-column-menu", inputs: { localeText: { classPropertyName: "localeText", publicName: "localeText", isSignal: true, isRequired: false, transformFunction: null }, x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: true, transformFunction: null }, sortDir: { classPropertyName: "sortDir", publicName: "sortDir", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, showColumnActions: { classPropertyName: "showColumnActions", publicName: "showColumnActions", isSignal: true, isRequired: false, transformFunction: null }, pinned: { classPropertyName: "pinned", publicName: "pinned", isSignal: true, isRequired: false, transformFunction: null }, groupable: { classPropertyName: "groupable", publicName: "groupable", isSignal: true, isRequired: false, transformFunction: null }, grouped: { classPropertyName: "grouped", publicName: "grouped", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, showValueFilter: { classPropertyName: "showValueFilter", publicName: "showValueFilter", isSignal: true, isRequired: false, transformFunction: null }, filterType: { classPropertyName: "filterType", publicName: "filterType", isSignal: true, isRequired: false, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, operand: { classPropertyName: "operand", publicName: "operand", isSignal: true, isRequired: false, transformFunction: null }, operand2: { classPropertyName: "operand2", publicName: "operand2", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, allSelected: { classPropertyName: "allSelected", publicName: "allSelected", isSignal: true, isRequired: false, transformFunction: null }, valueItems: { classPropertyName: "valueItems", publicName: "valueItems", isSignal: true, isRequired: false, transformFunction: null }, sortPriority: { classPropertyName: "sortPriority", publicName: "sortPriority", isSignal: true, isRequired: false, transformFunction: null }, hasMultiSort: { classPropertyName: "hasMultiSort", publicName: "hasMultiSort", isSignal: true, isRequired: false, transformFunction: null }, aggregate: { classPropertyName: "aggregate", publicName: "aggregate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operatorChange: "operatorChange", operandChange: "operandChange", operand2Change: "operand2Change", sort: "sort", resetSort: "resetSort", autosize: "autosize", togglePin: "togglePin", togglePinRight: "togglePinRight", hide: "hide", toggleGroup: "toggleGroup", clearFilter: "clearFilter", clearAll: "clearAll", searchChange: "searchChange", toggleAll: "toggleAll", toggleValue: "toggleValue", setAggregate: "setAggregate" }, ngImport: i0, template: "<div\n class=\"ag-filter-menu\"\n role=\"dialog\"\n [attr.aria-label]=\"localeText().columnMenu + ': ' + header()\"\n [style.left.px]=\"position().x\"\n [style.top.px]=\"position().y\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (sortable()) {\n <div class=\"ag-filter-menu-section\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'asc'\"\n (click)=\"sort.emit('asc')\"\n >\n \u2191 {{ localeText().sortAscending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'desc'\"\n (click)=\"sort.emit('desc')\"\n >\n \u2193 {{ localeText().sortDescending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n @if (hasMultiSort() && sortPriority() > 0) {\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"resetSort.emit(sortDir()!)\">{{ localeText().sortOnlyByThis }}</button>\n }\n @if (sortDir()) {\n <button class=\"ag-filter-menu-item\" (click)=\"sort.emit(sortDir()!)\">{{ localeText().clearSort }}</button>\n }\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item\" (click)=\"autosize.emit()\">\u2194 {{ localeText().autosizeColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'left'\"\n (click)=\"togglePin.emit()\"\n >\u22A3 {{ pinned() === 'left' ? localeText().unpinColumn : localeText().pinColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'right'\"\n (click)=\"togglePinRight.emit()\"\n >\u22A2 {{ pinned() === 'right' ? localeText().unpinColumnRight : localeText().pinColumnRight }}</button>\n <button class=\"ag-filter-menu-item\" (click)=\"hide.emit()\">{{ localeText().hideColumn }}</button>\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <span class=\"ag-filter-menu-aggregate-label\">{{ localeText().aggregate }}</span>\n <div class=\"ag-filter-menu-aggregate-btns\">\n @for (opt of aggregateOptions(); track opt.value) {\n <button\n class=\"ag-aggregate-btn\"\n [class.ag-aggregate-btn--active]=\"aggregate() === opt.value\"\n [title]=\"opt.label\"\n (click)=\"setAggregate.emit(aggregate() === opt.value ? null : opt.value)\"\n >{{ opt.symbol }}</button>\n }\n </div>\n </div>\n }\n\n @if (groupable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"grouped()\"\n (click)=\"toggleGroup.emit()\"\n >{{ grouped() ? '\u229F ' + localeText().ungroup : '\u229E ' + localeText().groupBy(header()) }}</button>\n </div>\n }\n\n @if (filterable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearFilter.emit()\">\n \u2715 {{ localeText().clearFilter }}\n </button>\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearAll.emit()\">\n \u2715 {{ localeText().clearAllFilters }}\n </button>\n </div>\n\n @if (filterType()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border ag-filter-menu-condition\">\n <span class=\"ag-filter-menu-aggregate-label\">{{ localeText().filterCondition }}</span>\n <div class=\"ag-filter-menu-operators\">\n <button\n type=\"button\"\n class=\"ag-filter-menu-operator-btn ag-filter-menu-operator-btn--clear\"\n [class.ag-filter-menu-operator-btn--active]=\"!operator()\"\n (click)=\"operatorChange.emit(null)\"\n >{{ localeText().filterNoCondition }}</button>\n @for (op of operatorOptions(); track op.value) {\n <button\n type=\"button\"\n class=\"ag-filter-menu-operator-btn\"\n [class.ag-filter-menu-operator-btn--active]=\"op.value === operator()\"\n (click)=\"operatorChange.emit(op.value)\"\n >{{ op.label }}</button>\n }\n </div>\n @if (operator()) {\n <label class=\"ag-filter-menu-operand-label\" for=\"agrid-filter-operand\">\n {{ localeText().filterValue }}\n </label>\n <input\n id=\"agrid-filter-operand\"\n class=\"ag-filter-menu-operand\"\n [type]=\"operandInputType()\"\n [value]=\"operand()\"\n (input)=\"operandChange.emit($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n @if (operator() === 'between') {\n <input\n class=\"ag-filter-menu-operand\"\n [type]=\"operandInputType()\"\n [value]=\"operand2()\"\n (input)=\"operand2Change.emit($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n }\n </div>\n }\n\n @if (showValueFilter()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <input\n class=\"ag-filter-menu-search\"\n [value]=\"search()\"\n (input)=\"searchChange.emit($any($event.target).value)\"\n [placeholder]=\"localeText().searchValuesPlaceholder\"\n (click)=\"$event.stopPropagation()\"\n />\n </div>\n\n <div class=\"ag-filter-menu-values\">\n <label class=\"ag-filter-menu-value\">\n <input\n type=\"checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleAll.emit()\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ localeText().selectAll }}</span>\n </label>\n @for (item of valueItems(); track item.rawStr) {\n <label\n class=\"ag-filter-menu-value\"\n [class.ag-filter-menu-value--inactive]=\"!item.active\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"item.selected\"\n [disabled]=\"!item.active\"\n (change)=\"toggleValue.emit(item.rawStr)\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ item.label || localeText().blank }}</span>\n </label>\n }\n </div>\n }\n }\n</div>\n", styles: [".ag-filter-menu{position:fixed;z-index:1001;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:200px;max-height:calc(100vh - 16px);font-size:13px;overflow-y:auto}.ag-filter-menu-section{padding:4px 0}.ag-filter-menu-section--border{border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-item{display:block;width:100%;padding:5px 14px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit;font-size:12px}.ag-filter-menu-item:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-item--active{color:var(--agrid-color-accent);font-weight:600}.ag-filter-menu-item--muted{color:var(--agrid-color-text-muted)}.ag-filter-menu-item--muted:hover{background:var(--mat-toolbar-container-background-color);color:var(--agrid-color-text-muted);cursor:not-allowed}.ag-filter-menu-search{background:var(--agrid-color-bg-subtle);display:block;width:calc(100% - 16px);margin:0 8px;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 6px}.ag-filter-menu-search:focus{border-color:var(--agrid-color-accent)}.ag-filter-menu-values{max-height:120px;overflow-y:auto;padding:4px 0;border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-value{display:flex;align-items:center;gap:6px;padding:3px 14px;cursor:pointer;font-size:12px}.ag-filter-menu-value:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-value input[type=checkbox]{cursor:pointer;margin:0}.ag-filter-menu-value--inactive{opacity:.38;cursor:default}.ag-filter-menu-value--inactive input[type=checkbox]{cursor:not-allowed}.ag-filter-menu-value-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-sort-priority-badge{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--agrid-color-accent);color:#fff;font-size:9px;font-weight:700;margin-left:auto}.ag-filter-menu-aggregate-label{display:block;padding:4px 14px 2px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none}.ag-filter-menu-aggregate-btns{display:flex;gap:4px;padding:2px 14px 6px}.ag-aggregate-btn{flex:1;height:26px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text-muted);font-size:13px;cursor:pointer;padding:0;transition:background 80ms,color 80ms}.ag-aggregate-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text);border-color:var(--agrid-color-text-muted)}.ag-aggregate-btn--active{background:var(--agrid-color-accent-subtle);border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-weight:700}.ag-filter-menu-condition{display:flex;flex-direction:column;gap:6px;padding:6px 14px 8px}.ag-filter-menu-operand{height:28px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font-size:13px;padding:0 8px;box-sizing:border-box;width:100%}.ag-filter-menu-operand:focus{outline:none;border-color:var(--agrid-color-accent-border)}.ag-filter-menu-operators{display:grid;grid-template-columns:1fr 1fr;gap:4px}.ag-filter-menu-operator-btn{min-height:26px;padding:3px 6px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font:inherit;font-size:11px;line-height:1.15;text-align:left;cursor:pointer}.ag-filter-menu-operator-btn:hover,.ag-filter-menu-operator-btn:focus-visible{border-color:var(--agrid-color-accent-border);outline:none}.ag-filter-menu-operator-btn--active{border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-filter-menu-operator-btn--clear{grid-column:1 / -1}.ag-filter-menu-operand-label{font-size:11px;color:var(--agrid-color-text-muted)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1228
1725
  }
1229
1726
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridColumnMenuComponent, decorators: [{
1230
1727
  type: Component,
1231
- args: [{ selector: 'agrid-column-menu', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ag-filter-menu\"\n role=\"dialog\"\n [attr.aria-label]=\"localeText().columnMenu + ': ' + header()\"\n [style.left.px]=\"x()\"\n [style.top.px]=\"y()\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (sortable()) {\n <div class=\"ag-filter-menu-section\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'asc'\"\n (click)=\"sort.emit('asc')\"\n >\n \u2191 {{ localeText().sortAscending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'desc'\"\n (click)=\"sort.emit('desc')\"\n >\n \u2193 {{ localeText().sortDescending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n @if (hasMultiSort() && sortPriority() > 0) {\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"resetSort.emit(sortDir()!)\">{{ localeText().sortOnlyByThis }}</button>\n }\n @if (sortDir()) {\n <button class=\"ag-filter-menu-item\" (click)=\"sort.emit(sortDir()!)\">{{ localeText().clearSort }}</button>\n }\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item\" (click)=\"autosize.emit()\">\u2194 {{ localeText().autosizeColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'left'\"\n (click)=\"togglePin.emit()\"\n >\u22A3 {{ pinned() === 'left' ? localeText().unpinColumn : localeText().pinColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'right'\"\n (click)=\"togglePinRight.emit()\"\n >\u22A2 {{ pinned() === 'right' ? localeText().unpinColumnRight : localeText().pinColumnRight }}</button>\n <button class=\"ag-filter-menu-item\" (click)=\"hide.emit()\">{{ localeText().hideColumn }}</button>\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <span class=\"ag-filter-menu-aggregate-label\">{{ localeText().aggregate }}</span>\n <div class=\"ag-filter-menu-aggregate-btns\">\n @for (opt of aggregateOptions(); track opt.value) {\n <button\n class=\"ag-aggregate-btn\"\n [class.ag-aggregate-btn--active]=\"aggregate() === opt.value\"\n [title]=\"opt.label\"\n (click)=\"setAggregate.emit(aggregate() === opt.value ? null : opt.value)\"\n >{{ opt.symbol }}</button>\n }\n </div>\n </div>\n }\n\n @if (groupable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"grouped()\"\n (click)=\"toggleGroup.emit()\"\n >{{ grouped() ? '\u229F ' + localeText().ungroup : '\u229E ' + localeText().groupBy(header()) }}</button>\n </div>\n }\n\n @if (filterable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearFilter.emit()\">\n \u2715 {{ localeText().clearFilter }}\n </button>\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearAll.emit()\">\n \u2715 {{ localeText().clearAllFilters }}\n </button>\n </div>\n\n @if (showValueFilter()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <input\n class=\"ag-filter-menu-search\"\n [value]=\"search()\"\n (input)=\"searchChange.emit($any($event.target).value)\"\n [placeholder]=\"localeText().searchValuesPlaceholder\"\n (click)=\"$event.stopPropagation()\"\n />\n </div>\n\n <div class=\"ag-filter-menu-values\">\n <label class=\"ag-filter-menu-value\">\n <input\n type=\"checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleAll.emit()\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ localeText().selectAll }}</span>\n </label>\n @for (item of valueItems(); track item.rawStr) {\n <label\n class=\"ag-filter-menu-value\"\n [class.ag-filter-menu-value--inactive]=\"!item.active\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"item.selected\"\n [disabled]=\"!item.active\"\n (change)=\"toggleValue.emit(item.rawStr)\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ item.label || localeText().blank }}</span>\n </label>\n }\n </div>\n }\n }\n</div>\n", styles: [".ag-filter-menu{position:fixed;z-index:1001;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:200px;font-size:13px;overflow:hidden}.ag-filter-menu-section{padding:4px 0}.ag-filter-menu-section--border{border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-item{display:block;width:100%;padding:5px 14px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit;font-size:12px}.ag-filter-menu-item:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-item--active{color:var(--agrid-color-accent);font-weight:600}.ag-filter-menu-item--muted{color:var(--agrid-color-text-muted)}.ag-filter-menu-item--muted:hover{background:var(--mat-toolbar-container-background-color);color:var(--agrid-color-text-muted);cursor:not-allowed}.ag-filter-menu-search{background:var(--agrid-color-bg-subtle);display:block;width:calc(100% - 16px);margin:0 8px;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 6px}.ag-filter-menu-search:focus{border-color:var(--agrid-color-accent)}.ag-filter-menu-values{max-height:200px;overflow-y:auto;padding:4px 0;border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-value{display:flex;align-items:center;gap:6px;padding:3px 14px;cursor:pointer;font-size:12px}.ag-filter-menu-value:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-value input[type=checkbox]{cursor:pointer;margin:0}.ag-filter-menu-value--inactive{opacity:.38;cursor:default}.ag-filter-menu-value--inactive input[type=checkbox]{cursor:not-allowed}.ag-filter-menu-value-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-sort-priority-badge{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--agrid-color-accent);color:#fff;font-size:9px;font-weight:700;margin-left:auto}.ag-filter-menu-aggregate-label{display:block;padding:4px 14px 2px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none}.ag-filter-menu-aggregate-btns{display:flex;gap:4px;padding:2px 14px 6px}.ag-aggregate-btn{flex:1;height:26px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text-muted);font-size:13px;cursor:pointer;padding:0;transition:background 80ms,color 80ms}.ag-aggregate-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text);border-color:var(--agrid-color-text-muted)}.ag-aggregate-btn--active{background:var(--agrid-color-accent-subtle);border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-weight:700}\n"] }]
1232
- }], propDecorators: { localeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeText", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: true }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: true }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: true }] }], sortDir: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortDir", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], showColumnActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnActions", required: false }] }], pinned: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinned", required: false }] }], groupable: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupable", required: false }] }], grouped: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouped", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], showValueFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValueFilter", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], allSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSelected", required: false }] }], valueItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueItems", required: false }] }], sortPriority: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortPriority", required: false }] }], hasMultiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMultiSort", required: false }] }], sort: [{ type: i0.Output, args: ["sort"] }], resetSort: [{ type: i0.Output, args: ["resetSort"] }], autosize: [{ type: i0.Output, args: ["autosize"] }], togglePin: [{ type: i0.Output, args: ["togglePin"] }], togglePinRight: [{ type: i0.Output, args: ["togglePinRight"] }], hide: [{ type: i0.Output, args: ["hide"] }], toggleGroup: [{ type: i0.Output, args: ["toggleGroup"] }], clearFilter: [{ type: i0.Output, args: ["clearFilter"] }], clearAll: [{ type: i0.Output, args: ["clearAll"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], toggleAll: [{ type: i0.Output, args: ["toggleAll"] }], toggleValue: [{ type: i0.Output, args: ["toggleValue"] }], aggregate: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregate", required: false }] }], setAggregate: [{ type: i0.Output, args: ["setAggregate"] }] } });
1728
+ args: [{ selector: 'agrid-column-menu', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ag-filter-menu\"\n role=\"dialog\"\n [attr.aria-label]=\"localeText().columnMenu + ': ' + header()\"\n [style.left.px]=\"position().x\"\n [style.top.px]=\"position().y\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (sortable()) {\n <div class=\"ag-filter-menu-section\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'asc'\"\n (click)=\"sort.emit('asc')\"\n >\n \u2191 {{ localeText().sortAscending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"sortDir() === 'desc'\"\n (click)=\"sort.emit('desc')\"\n >\n \u2193 {{ localeText().sortDescending }}\n @if (sortPriority() > 0) {\n <span class=\"ag-sort-priority-badge\">{{ sortPriority() }}</span>\n }\n </button>\n @if (hasMultiSort() && sortPriority() > 0) {\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"resetSort.emit(sortDir()!)\">{{ localeText().sortOnlyByThis }}</button>\n }\n @if (sortDir()) {\n <button class=\"ag-filter-menu-item\" (click)=\"sort.emit(sortDir()!)\">{{ localeText().clearSort }}</button>\n }\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item\" (click)=\"autosize.emit()\">\u2194 {{ localeText().autosizeColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'left'\"\n (click)=\"togglePin.emit()\"\n >\u22A3 {{ pinned() === 'left' ? localeText().unpinColumn : localeText().pinColumn }}</button>\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"pinned() === 'right'\"\n (click)=\"togglePinRight.emit()\"\n >\u22A2 {{ pinned() === 'right' ? localeText().unpinColumnRight : localeText().pinColumnRight }}</button>\n <button class=\"ag-filter-menu-item\" (click)=\"hide.emit()\">{{ localeText().hideColumn }}</button>\n </div>\n }\n\n @if (showColumnActions()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <span class=\"ag-filter-menu-aggregate-label\">{{ localeText().aggregate }}</span>\n <div class=\"ag-filter-menu-aggregate-btns\">\n @for (opt of aggregateOptions(); track opt.value) {\n <button\n class=\"ag-aggregate-btn\"\n [class.ag-aggregate-btn--active]=\"aggregate() === opt.value\"\n [title]=\"opt.label\"\n (click)=\"setAggregate.emit(aggregate() === opt.value ? null : opt.value)\"\n >{{ opt.symbol }}</button>\n }\n </div>\n </div>\n }\n\n @if (groupable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button\n class=\"ag-filter-menu-item\"\n [class.ag-filter-menu-item--active]=\"grouped()\"\n (click)=\"toggleGroup.emit()\"\n >{{ grouped() ? '\u229F ' + localeText().ungroup : '\u229E ' + localeText().groupBy(header()) }}</button>\n </div>\n }\n\n @if (filterable()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearFilter.emit()\">\n \u2715 {{ localeText().clearFilter }}\n </button>\n <button class=\"ag-filter-menu-item ag-filter-menu-item--muted\" (click)=\"clearAll.emit()\">\n \u2715 {{ localeText().clearAllFilters }}\n </button>\n </div>\n\n @if (filterType()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border ag-filter-menu-condition\">\n <span class=\"ag-filter-menu-aggregate-label\">{{ localeText().filterCondition }}</span>\n <div class=\"ag-filter-menu-operators\">\n <button\n type=\"button\"\n class=\"ag-filter-menu-operator-btn ag-filter-menu-operator-btn--clear\"\n [class.ag-filter-menu-operator-btn--active]=\"!operator()\"\n (click)=\"operatorChange.emit(null)\"\n >{{ localeText().filterNoCondition }}</button>\n @for (op of operatorOptions(); track op.value) {\n <button\n type=\"button\"\n class=\"ag-filter-menu-operator-btn\"\n [class.ag-filter-menu-operator-btn--active]=\"op.value === operator()\"\n (click)=\"operatorChange.emit(op.value)\"\n >{{ op.label }}</button>\n }\n </div>\n @if (operator()) {\n <label class=\"ag-filter-menu-operand-label\" for=\"agrid-filter-operand\">\n {{ localeText().filterValue }}\n </label>\n <input\n id=\"agrid-filter-operand\"\n class=\"ag-filter-menu-operand\"\n [type]=\"operandInputType()\"\n [value]=\"operand()\"\n (input)=\"operandChange.emit($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n @if (operator() === 'between') {\n <input\n class=\"ag-filter-menu-operand\"\n [type]=\"operandInputType()\"\n [value]=\"operand2()\"\n (input)=\"operand2Change.emit($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n }\n </div>\n }\n\n @if (showValueFilter()) {\n <div class=\"ag-filter-menu-section ag-filter-menu-section--border\">\n <input\n class=\"ag-filter-menu-search\"\n [value]=\"search()\"\n (input)=\"searchChange.emit($any($event.target).value)\"\n [placeholder]=\"localeText().searchValuesPlaceholder\"\n (click)=\"$event.stopPropagation()\"\n />\n </div>\n\n <div class=\"ag-filter-menu-values\">\n <label class=\"ag-filter-menu-value\">\n <input\n type=\"checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleAll.emit()\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ localeText().selectAll }}</span>\n </label>\n @for (item of valueItems(); track item.rawStr) {\n <label\n class=\"ag-filter-menu-value\"\n [class.ag-filter-menu-value--inactive]=\"!item.active\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"item.selected\"\n [disabled]=\"!item.active\"\n (change)=\"toggleValue.emit(item.rawStr)\"\n />\n <span class=\"ag-filter-menu-value-label\">{{ item.label || localeText().blank }}</span>\n </label>\n }\n </div>\n }\n }\n</div>\n", styles: [".ag-filter-menu{position:fixed;z-index:1001;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:200px;max-height:calc(100vh - 16px);font-size:13px;overflow-y:auto}.ag-filter-menu-section{padding:4px 0}.ag-filter-menu-section--border{border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-item{display:block;width:100%;padding:5px 14px;text-align:left;background:none;border:none;cursor:pointer;color:var(--agrid-color-text);font:inherit;font-size:12px}.ag-filter-menu-item:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-item--active{color:var(--agrid-color-accent);font-weight:600}.ag-filter-menu-item--muted{color:var(--agrid-color-text-muted)}.ag-filter-menu-item--muted:hover{background:var(--mat-toolbar-container-background-color);color:var(--agrid-color-text-muted);cursor:not-allowed}.ag-filter-menu-search{background:var(--agrid-color-bg-subtle);display:block;width:calc(100% - 16px);margin:0 8px;height:24px;border:1px solid var(--agrid-color-border);border-radius:3px;outline:none;font:inherit;font-size:12px;padding:0 6px}.ag-filter-menu-search:focus{border-color:var(--agrid-color-accent)}.ag-filter-menu-values{max-height:120px;overflow-y:auto;padding:4px 0;border-top:1px solid var(--agrid-color-border)}.ag-filter-menu-value{display:flex;align-items:center;gap:6px;padding:3px 14px;cursor:pointer;font-size:12px}.ag-filter-menu-value:hover{background:var(--agrid-color-bg-muted)}.ag-filter-menu-value input[type=checkbox]{cursor:pointer;margin:0}.ag-filter-menu-value--inactive{opacity:.38;cursor:default}.ag-filter-menu-value--inactive input[type=checkbox]{cursor:not-allowed}.ag-filter-menu-value-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-sort-priority-badge{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--agrid-color-accent);color:#fff;font-size:9px;font-weight:700;margin-left:auto}.ag-filter-menu-aggregate-label{display:block;padding:4px 14px 2px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--agrid-color-text-muted);-webkit-user-select:none;user-select:none}.ag-filter-menu-aggregate-btns{display:flex;gap:4px;padding:2px 14px 6px}.ag-aggregate-btn{flex:1;height:26px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text-muted);font-size:13px;cursor:pointer;padding:0;transition:background 80ms,color 80ms}.ag-aggregate-btn:hover{background:var(--agrid-color-bg-muted);color:var(--agrid-color-text);border-color:var(--agrid-color-text-muted)}.ag-aggregate-btn--active{background:var(--agrid-color-accent-subtle);border-color:var(--agrid-color-accent-border);color:var(--agrid-color-accent-fg);font-weight:700}.ag-filter-menu-condition{display:flex;flex-direction:column;gap:6px;padding:6px 14px 8px}.ag-filter-menu-operand{height:28px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font-size:13px;padding:0 8px;box-sizing:border-box;width:100%}.ag-filter-menu-operand:focus{outline:none;border-color:var(--agrid-color-accent-border)}.ag-filter-menu-operators{display:grid;grid-template-columns:1fr 1fr;gap:4px}.ag-filter-menu-operator-btn{min-height:26px;padding:3px 6px;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg);color:var(--agrid-color-text);font:inherit;font-size:11px;line-height:1.15;text-align:left;cursor:pointer}.ag-filter-menu-operator-btn:hover,.ag-filter-menu-operator-btn:focus-visible{border-color:var(--agrid-color-accent-border);outline:none}.ag-filter-menu-operator-btn--active{border-color:var(--agrid-color-accent-border);background:var(--agrid-color-accent-subtle);color:var(--agrid-color-accent-fg)}.ag-filter-menu-operator-btn--clear{grid-column:1 / -1}.ag-filter-menu-operand-label{font-size:11px;color:var(--agrid-color-text-muted)}\n"] }]
1729
+ }], ctorParameters: () => [], propDecorators: { localeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeText", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: true }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: true }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: true }] }], sortDir: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortDir", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], showColumnActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnActions", required: false }] }], pinned: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinned", required: false }] }], groupable: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupable", required: false }] }], grouped: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouped", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], showValueFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValueFilter", required: false }] }], filterType: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterType", required: false }] }], operator: [{ type: i0.Input, args: [{ isSignal: true, alias: "operator", required: false }] }], operand: [{ type: i0.Input, args: [{ isSignal: true, alias: "operand", required: false }] }], operand2: [{ type: i0.Input, args: [{ isSignal: true, alias: "operand2", required: false }] }], operatorChange: [{ type: i0.Output, args: ["operatorChange"] }], operandChange: [{ type: i0.Output, args: ["operandChange"] }], operand2Change: [{ type: i0.Output, args: ["operand2Change"] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], allSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSelected", required: false }] }], valueItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueItems", required: false }] }], sortPriority: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortPriority", required: false }] }], hasMultiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMultiSort", required: false }] }], sort: [{ type: i0.Output, args: ["sort"] }], resetSort: [{ type: i0.Output, args: ["resetSort"] }], autosize: [{ type: i0.Output, args: ["autosize"] }], togglePin: [{ type: i0.Output, args: ["togglePin"] }], togglePinRight: [{ type: i0.Output, args: ["togglePinRight"] }], hide: [{ type: i0.Output, args: ["hide"] }], toggleGroup: [{ type: i0.Output, args: ["toggleGroup"] }], clearFilter: [{ type: i0.Output, args: ["clearFilter"] }], clearAll: [{ type: i0.Output, args: ["clearAll"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], toggleAll: [{ type: i0.Output, args: ["toggleAll"] }], toggleValue: [{ type: i0.Output, args: ["toggleValue"] }], aggregate: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregate", required: false }] }], setAggregate: [{ type: i0.Output, args: ["setAggregate"] }] } });
1233
1730
 
1234
1731
  /** Owns column-menu state, filters, sorting, and menu-triggered column mutations. @internal */
1235
1732
  class AgridColumnMenuController {
@@ -1287,6 +1784,10 @@ class AgridColumnMenuController {
1287
1784
  const allowed = new Set(filter.selectedValues);
1288
1785
  indices = indices.filter(index => allowed.has(String(rows[index][field] ?? '')));
1289
1786
  }
1787
+ if (filter.operator && filter.operand != null && filter.operand !== '') {
1788
+ const filterCol = this.getColDef(field);
1789
+ indices = indices.filter(index => passesConditionFilter(filterCol, rows[index][field], filter));
1790
+ }
1290
1791
  }
1291
1792
  }
1292
1793
  return new Set(indices.map(index => String(rows[index][menu.field] ?? '')));
@@ -1315,6 +1816,86 @@ class AgridColumnMenuController {
1315
1816
  getTextFilter(field) {
1316
1817
  return this.opts.control()?.getFilter(field).text ?? '';
1317
1818
  }
1819
+ /** Returns the condition-filter input type for a filterable field. */
1820
+ getFilterType(field) {
1821
+ const col = this.getColDef(field);
1822
+ if (!col?.filterable || col.type === 'boolean')
1823
+ return null;
1824
+ return col.type === 'number' || col.type === 'date' ? col.type : 'text';
1825
+ }
1826
+ /** Returns the condition operator for a field, or `null`. */
1827
+ getFilterOperator(field) {
1828
+ return this.opts.control()?.getFilter(field).operator ?? null;
1829
+ }
1830
+ /** Returns the primary condition operand for a field. */
1831
+ getFilterOperand(field) {
1832
+ return this.opts.control()?.getFilter(field).operand ?? '';
1833
+ }
1834
+ /** Returns the secondary condition operand (used by `between`). */
1835
+ getFilterOperand2(field) {
1836
+ return this.opts.control()?.getFilter(field).operand2 ?? '';
1837
+ }
1838
+ /** Sets the condition operator; clearing it (`null`) also drops the operands. */
1839
+ setFilterOperator(field, operator) {
1840
+ const control = this.opts.control();
1841
+ if (!control)
1842
+ return;
1843
+ if (!operator) {
1844
+ control.setRangeFilter(field, null, null, null);
1845
+ }
1846
+ else {
1847
+ const current = control.getFilter(field);
1848
+ control.setRangeFilter(field, operator, current.operand ?? '', current.operand2 ?? '');
1849
+ }
1850
+ this.emitRangeServer(field, false);
1851
+ }
1852
+ /** Sets the primary condition operand. */
1853
+ setFilterOperand(field, value) {
1854
+ const control = this.opts.control();
1855
+ if (!control)
1856
+ return;
1857
+ const current = control.getFilter(field);
1858
+ control.setRangeFilter(field, current.operator ?? 'eq', value, current.operand2 ?? '');
1859
+ this.emitRangeServer(field, true);
1860
+ }
1861
+ /** Sets the secondary condition operand (used by `between`). */
1862
+ setFilterOperand2(field, value) {
1863
+ const control = this.opts.control();
1864
+ if (!control)
1865
+ return;
1866
+ const current = control.getFilter(field);
1867
+ control.setRangeFilter(field, current.operator ?? 'between', current.operand ?? '', value);
1868
+ this.emitRangeServer(field, true);
1869
+ }
1870
+ /**
1871
+ * In server-side filtering mode, emit a {@link FilterChangeEvent} carrying the current
1872
+ * condition so the host can refetch. Operand edits are debounced; operator changes are immediate.
1873
+ * No-op in client mode (the projection layer filters locally).
1874
+ */
1875
+ emitRangeServer(field, debounce) {
1876
+ if (!this.opts.serverSideFiltering())
1877
+ return;
1878
+ const emit = () => {
1879
+ const f = this.opts.control()?.getFilter(field);
1880
+ this.opts.onFilterChange({
1881
+ field,
1882
+ value: '',
1883
+ operator: f?.operator ?? null,
1884
+ operand: f?.operand ?? null,
1885
+ operand2: f?.operand2 ?? null,
1886
+ });
1887
+ };
1888
+ this.cancelFilterDebounce(field);
1889
+ const delay = this.opts.filterDebounceMs();
1890
+ if (!debounce || delay === 0) {
1891
+ emit();
1892
+ return;
1893
+ }
1894
+ this.filterDebounces.set(field, setTimeout(() => {
1895
+ this.filterDebounces.delete(field);
1896
+ emit();
1897
+ }, delay));
1898
+ }
1318
1899
  /** Returns the active sort direction, respecting disabled sorting. */
1319
1900
  getSort(field) {
1320
1901
  return this.opts.sortOption() === 'none'
@@ -1394,6 +1975,9 @@ class AgridColumnMenuController {
1394
1975
  if (previous.selectedValues !== null) {
1395
1976
  control.setSelectedValues(field, previous.selectedValues);
1396
1977
  }
1978
+ if (previous.operator) {
1979
+ control.setRangeFilter(field, previous.operator, previous.operand ?? null, previous.operand2 ?? null);
1980
+ }
1397
1981
  if (this.opts.serverSideFiltering()) {
1398
1982
  this.opts.onSortChange({ field, direction: null });
1399
1983
  }
@@ -1427,6 +2011,9 @@ class AgridColumnMenuController {
1427
2011
  if (this.opts.serverSideFiltering()) {
1428
2012
  if (previous.text)
1429
2013
  this.opts.onFilterChange({ field, value: '' });
2014
+ if (previous.operator) {
2015
+ this.opts.onFilterChange({ field, value: '', operator: null, operand: null, operand2: null });
2016
+ }
1430
2017
  if (previous.sort)
1431
2018
  this.opts.onSortChange({ field, direction: null });
1432
2019
  }
@@ -1468,6 +2055,9 @@ class AgridColumnMenuController {
1468
2055
  for (const [field, filter] of Object.entries(previous)) {
1469
2056
  if (filter.text)
1470
2057
  this.opts.onFilterChange({ field, value: '' });
2058
+ if (filter.operator) {
2059
+ this.opts.onFilterChange({ field, value: '', operator: null, operand: null, operand2: null });
2060
+ }
1471
2061
  if (filter.sort)
1472
2062
  this.opts.onSortChange({ field, direction: null });
1473
2063
  }
@@ -1704,7 +2294,7 @@ class AgridColumnReorderController {
1704
2294
  const root = header?.closest('.ag-wrapper') ?? header?.ownerDocument;
1705
2295
  if (!root)
1706
2296
  return [];
1707
- return [...root.querySelectorAll('.ag-header-cell[data-col-field]')]
2297
+ return Array.from(root.querySelectorAll('.ag-header-cell[data-col-field]'))
1708
2298
  .map(element => {
1709
2299
  const field = element.dataset['colField'];
1710
2300
  const rect = element.getBoundingClientRect();
@@ -1877,13 +2467,15 @@ class AgridColumnStateService {
1877
2467
  constructor(opts) {
1878
2468
  this.opts = opts;
1879
2469
  }
2470
+ columnsByField = computed(() => new Map(this.opts.colDefs().map(col => [col.field, col])), ...(ngDevMode ? [{ debugName: "columnsByField" }] : /* istanbul ignore next */ []));
2471
+ visibleIndexByField = computed(() => new Map(this.opts.visibleColDefs().map((col, index) => [col.field, index])), ...(ngDevMode ? [{ debugName: "visibleIndexByField" }] : /* istanbul ignore next */ []));
1880
2472
  /** Finds a column definition by field name. */
1881
2473
  getColDef(field) {
1882
- return this.opts.colDefs().find(col => col.field === field);
2474
+ return this.columnsByField().get(field);
1883
2475
  }
1884
2476
  /** Returns a field's zero-based index among visible columns. */
1885
2477
  getVisibleColIndex(field) {
1886
- return this.opts.visibleColDefs().findIndex(col => col.field === field);
2478
+ return this.visibleIndexByField().get(field) ?? -1;
1887
2479
  }
1888
2480
  /** Converts a visible data-column index to a one-based ARIA index. */
1889
2481
  getAriaColIndex(colIndex) {
@@ -2098,22 +2690,39 @@ class AgridEditController {
2098
2690
  editingCell = signal(null, ...(ngDevMode ? [{ debugName: "editingCell" }] : /* istanbul ignore next */ []));
2099
2691
  currentDraft = signal(null, ...(ngDevMode ? [{ debugName: "currentDraft" }] : /* istanbul ignore next */ []));
2100
2692
  editSeedChar = signal('', ...(ngDevMode ? [{ debugName: "editSeedChar" }] : /* istanbul ignore next */ []));
2693
+ /** Whether a newly focused text editor should select all text. */
2694
+ selectTextOnEdit = signal(true, ...(ngDevMode ? [{ debugName: "selectTextOnEdit" }] : /* istanbul ignore next */ []));
2695
+ /** The active cell validation error, or `null` when the last commit was accepted. */
2696
+ validationError = signal(null, ...(ngDevMode ? [{ debugName: "validationError" }] : /* istanbul ignore next */ []));
2101
2697
  constructor(opts) {
2102
2698
  this.opts = opts;
2103
2699
  }
2104
- /** Returns whether a column can be edited in the current grid state. */
2105
- isCellEditable(col) {
2106
- return !!col && !this.opts.readonlyGrid() && col.editable !== false;
2700
+ /** Returns whether a cell can be edited in the current grid state. */
2701
+ isCellEditable(col, originalIndex) {
2702
+ if (!col || this.opts.readonlyGrid() || col.editable === false)
2703
+ return false;
2704
+ if (originalIndex === undefined || !col.cellReadonly)
2705
+ return true;
2706
+ const row = this.opts.dataSource().getRow(originalIndex);
2707
+ if (!row)
2708
+ return false;
2709
+ return !col.cellReadonly({
2710
+ row,
2711
+ value: row[col.field],
2712
+ column: col,
2713
+ originalIndex,
2714
+ });
2107
2715
  }
2108
2716
  /** Replaces the value currently staged by the active editor. */
2109
2717
  setDraft(value) {
2110
2718
  this.currentDraft.set(value);
2111
2719
  }
2112
2720
  /** Starts editing a cell, optionally seeded by a typed character. */
2113
- start(originalIndex, colIndex, seedChar) {
2721
+ start(originalIndex, colIndex, seedChar, selectText = true) {
2114
2722
  const col = this.opts.visibleColDefs()[colIndex];
2115
- if (!this.isCellEditable(col))
2723
+ if (!this.isCellEditable(col, originalIndex))
2116
2724
  return;
2725
+ this.validationError.set(null);
2117
2726
  const currentValue = this.opts.dataSource().getRow(originalIndex)[col.field];
2118
2727
  this.opts.selectedRange.set(null);
2119
2728
  this.opts.selectedCell.set({ rowIndex: originalIndex, colIndex });
@@ -2129,24 +2738,40 @@ class AgridEditController {
2129
2738
  }
2130
2739
  this.currentDraft.set(initialDraft);
2131
2740
  this.editSeedChar.set(seedChar);
2741
+ this.selectTextOnEdit.set(selectText);
2132
2742
  this.editingCell.set({ rowIndex: originalIndex, colIndex });
2133
2743
  const displayIndex = this.opts.findDisplayIndex(originalIndex);
2134
2744
  if (displayIndex >= 0)
2135
2745
  this.opts.scrollToCell(displayIndex, colIndex);
2136
2746
  }
2137
- /** Commits the staged value and records the edit in history. */
2747
+ /**
2748
+ * Commits the staged value and records the edit in history.
2749
+ * Returns `false` (keeping the cell in edit mode) when a `ColDef.validate` hook rejects the
2750
+ * value; `true` when the edit is committed or there was no change.
2751
+ */
2138
2752
  commit() {
2139
2753
  const position = this.editingCell();
2140
2754
  if (!position)
2141
- return;
2755
+ return true;
2142
2756
  const col = this.opts.visibleColDefs()[position.colIndex];
2143
2757
  if (!col) {
2144
2758
  this.cancel();
2145
- return;
2759
+ return true;
2760
+ }
2761
+ const row = this.opts.dataSource().getRow(position.rowIndex);
2762
+ if (!this.isCellEditable(col, position.rowIndex)) {
2763
+ this.cancel();
2764
+ return true;
2146
2765
  }
2147
- const oldValue = this.opts.dataSource().getRow(position.rowIndex)[col.field];
2766
+ const oldValue = row[col.field];
2148
2767
  const newValue = this.currentDraft();
2149
2768
  if (oldValue !== newValue) {
2769
+ const message = col.validate?.(newValue, row) ?? null;
2770
+ if (message) {
2771
+ this.validationError.set({ ...position, field: col.field, message });
2772
+ this.opts.onValidationFailed({ rowIndex: position.rowIndex, field: col.field, value: newValue, message });
2773
+ return false;
2774
+ }
2150
2775
  this.opts.dataSource().patchRow(position.rowIndex, { [col.field]: newValue });
2151
2776
  this.opts.control()?.pushEdit({
2152
2777
  rowIndex: position.rowIndex,
@@ -2156,11 +2781,43 @@ class AgridEditController {
2156
2781
  });
2157
2782
  this.opts.onCellEdit({ position, field: col.field, oldValue, newValue });
2158
2783
  }
2784
+ this.validationError.set(null);
2159
2785
  this.clearEditState();
2160
2786
  this.opts.focusGrid();
2787
+ return true;
2788
+ }
2789
+ /**
2790
+ * Commit a value directly to a cell without entering edit mode (e.g. a boolean checkbox
2791
+ * toggle). Records the change in history and emits the edit just like {@link commit}.
2792
+ */
2793
+ setCellValue(rowIndex, colIndex, newValue) {
2794
+ const col = this.opts.visibleColDefs()[colIndex];
2795
+ if (!this.isCellEditable(col, rowIndex))
2796
+ return false;
2797
+ const row = this.opts.dataSource().getRow(rowIndex);
2798
+ const oldValue = row[col.field];
2799
+ if (oldValue === newValue)
2800
+ return true;
2801
+ const message = col.validate?.(newValue, row) ?? null;
2802
+ if (message) {
2803
+ this.validationError.set({ rowIndex, colIndex, field: col.field, message });
2804
+ this.opts.onValidationFailed({ rowIndex, field: col.field, value: newValue, message });
2805
+ return false;
2806
+ }
2807
+ this.opts.dataSource().patchRow(rowIndex, { [col.field]: newValue });
2808
+ this.opts.control()?.pushEdit({ rowIndex, field: col.field, oldValue, newValue });
2809
+ this.opts.onCellEdit({
2810
+ position: { rowIndex, colIndex },
2811
+ field: col.field,
2812
+ oldValue,
2813
+ newValue,
2814
+ });
2815
+ this.validationError.set(null);
2816
+ return true;
2161
2817
  }
2162
2818
  /** Discards the active edit without changing row data. */
2163
2819
  cancel() {
2820
+ this.validationError.set(null);
2164
2821
  this.clearEditState();
2165
2822
  }
2166
2823
  /** Applies the previous edit-history value to the data source. */
@@ -2205,6 +2862,7 @@ class AgridEditController {
2205
2862
  clearEditState() {
2206
2863
  this.editingCell.set(null);
2207
2864
  this.editSeedChar.set('');
2865
+ this.selectTextOnEdit.set(true);
2208
2866
  }
2209
2867
  }
2210
2868
 
@@ -2219,15 +2877,17 @@ class AgridFindController {
2219
2877
  if (!query)
2220
2878
  return [];
2221
2879
  const cols = this.opts.visibleColDefs();
2880
+ const rows = this.opts.dataSource().rows();
2222
2881
  const matches = [];
2223
- this.opts.filteredItems().forEach((item, displayIndex) => {
2224
- if (!isDataRowItem(item))
2882
+ this.opts.filteredSortedIndices().forEach(rowIndex => {
2883
+ const row = rows[rowIndex];
2884
+ if (!row)
2225
2885
  return;
2226
2886
  for (let colIndex = 0; colIndex < cols.length; colIndex++) {
2227
2887
  const col = cols[colIndex];
2228
- const value = getDisplayForField(col, item.row[col.field], this.opts.locale()).toLowerCase();
2888
+ const value = getDisplayForField(col, row[col.field], this.opts.locale()).toLowerCase();
2229
2889
  if (value.includes(query)) {
2230
- matches.push({ rowIndex: item.originalIndex, displayIndex, colIndex });
2890
+ matches.push({ rowIndex, colIndex });
2231
2891
  }
2232
2892
  }
2233
2893
  });
@@ -2238,6 +2898,9 @@ class AgridFindController {
2238
2898
  }
2239
2899
  /** Opens the find panel. */
2240
2900
  show() {
2901
+ this.opts.selectedCell.set(null);
2902
+ this.opts.selectedRange.set(null);
2903
+ this.activeIndex.set(-1);
2241
2904
  this.open.set(true);
2242
2905
  }
2243
2906
  /** Closes the find panel and returns focus to the grid. */
@@ -2245,11 +2908,10 @@ class AgridFindController {
2245
2908
  this.open.set(false);
2246
2909
  this.opts.focusGrid();
2247
2910
  }
2248
- /** Replaces the search query and selects its first match. */
2911
+ /** Replaces the search query without moving focus away from the find input. */
2249
2912
  setQuery(value) {
2250
2913
  this.query.set(value);
2251
2914
  this.activeIndex.set(-1);
2252
- this.goToMatch(1);
2253
2915
  }
2254
2916
  /** Selects the next or previous match, wrapping at either end. */
2255
2917
  goToMatch(direction) {
@@ -2266,7 +2928,7 @@ class AgridFindController {
2266
2928
  const match = matches[next];
2267
2929
  this.opts.selectedRange.set(null);
2268
2930
  this.opts.selectedCell.set({ rowIndex: match.rowIndex, colIndex: match.colIndex });
2269
- this.opts.scrollToCell(match.displayIndex, match.colIndex);
2931
+ this.opts.revealMatch(match.rowIndex, match.colIndex);
2270
2932
  }
2271
2933
  /** Returns whether a cell matches the current query. */
2272
2934
  isMatchCell(originalIndex, colIndex) {
@@ -2490,7 +3152,7 @@ class AgridNavigationController {
2490
3152
  }
2491
3153
  /** Handles grid-level navigation, editing, history, find, and add-row shortcuts. */
2492
3154
  handleKeyDown(event) {
2493
- if (event.target?.closest('.ag-sidebar, .ag-filter-input, .ag-filter-menu'))
3155
+ if (event.target?.closest('.ag-sidebar, .ag-filter-input, .ag-filter-menu, .ag-menu-bar'))
2494
3156
  return;
2495
3157
  if ((event.ctrlKey || event.metaKey) && !event.altKey && event.key.toLowerCase() === 'f') {
2496
3158
  event.preventDefault();
@@ -2509,17 +3171,27 @@ class AgridNavigationController {
2509
3171
  return;
2510
3172
  }
2511
3173
  }
3174
+ const sel = this.opts.selectedCell();
3175
+ if (event.key === 'Enter'
3176
+ && (event.ctrlKey || event.metaKey)
3177
+ && !event.altKey
3178
+ && !event.shiftKey
3179
+ && sel
3180
+ && this.opts.toggleTreeCell(sel.rowIndex, sel.colIndex)) {
3181
+ event.preventDefault();
3182
+ return;
3183
+ }
2512
3184
  if (this.opts.editingCell()) {
2513
3185
  switch (event.key) {
2514
3186
  case 'Tab':
2515
3187
  event.preventDefault();
2516
- this.opts.commitEdit();
2517
- this.moveSelection(0, event.shiftKey ? -1 : 1);
3188
+ if (this.opts.commitEdit())
3189
+ this.moveSelection(0, event.shiftKey ? -1 : 1);
2518
3190
  break;
2519
3191
  case 'Enter':
2520
3192
  event.preventDefault();
2521
- this.opts.commitEdit();
2522
- this.moveSelection(1, 0);
3193
+ if (this.opts.commitEdit())
3194
+ this.applyEnterEditAction(event.shiftKey);
2523
3195
  break;
2524
3196
  case 'Escape':
2525
3197
  event.preventDefault();
@@ -2529,7 +3201,6 @@ class AgridNavigationController {
2529
3201
  }
2530
3202
  return;
2531
3203
  }
2532
- const sel = this.opts.selectedCell();
2533
3204
  const isOnAddRow = this.opts.allowAddRows()
2534
3205
  && !this.opts.autoAddRows()
2535
3206
  && sel?.rowIndex === this.opts.dataSource().length;
@@ -2555,6 +3226,14 @@ class AgridNavigationController {
2555
3226
  this.moveSelection(0, event.shiftKey ? -1 : 1);
2556
3227
  break;
2557
3228
  case 'Enter':
3229
+ event.preventDefault();
3230
+ if (sel) {
3231
+ if (isOnAddRow)
3232
+ this.addRowAndSelect();
3233
+ else
3234
+ this.opts.startEdit(sel.rowIndex, sel.colIndex, '');
3235
+ }
3236
+ break;
2558
3237
  case 'F2':
2559
3238
  event.preventDefault();
2560
3239
  if (sel) {
@@ -2573,6 +3252,63 @@ class AgridNavigationController {
2573
3252
  }
2574
3253
  }
2575
3254
  }
3255
+ applyEnterEditAction(reverse) {
3256
+ switch (this.opts.enterEditAction()) {
3257
+ case 'nextColumn':
3258
+ this.moveToNextEditableColumn(reverse ? -1 : 1);
3259
+ break;
3260
+ case 'nextRow':
3261
+ this.moveSelection(reverse ? -1 : 1, 0);
3262
+ break;
3263
+ case 'nothing':
3264
+ this.opts.focusGrid();
3265
+ break;
3266
+ }
3267
+ }
3268
+ moveToNextEditableColumn(direction) {
3269
+ const items = this.opts.filteredItems();
3270
+ const cols = this.opts.visibleColDefs();
3271
+ const selected = this.opts.selectedCell();
3272
+ if (!selected || items.length === 0 || cols.length === 0) {
3273
+ this.opts.focusGrid();
3274
+ return;
3275
+ }
3276
+ const currentDisplayIndex = this.selectedDisplayIndex();
3277
+ if (currentDisplayIndex < 0) {
3278
+ this.opts.focusGrid();
3279
+ return;
3280
+ }
3281
+ let displayIndex = currentDisplayIndex;
3282
+ let colIndex = selected.colIndex + direction;
3283
+ while (displayIndex >= 0 && displayIndex < items.length) {
3284
+ while (colIndex >= 0 && colIndex < cols.length) {
3285
+ const item = items[displayIndex];
3286
+ const col = cols[colIndex];
3287
+ if (isDataRowItem(item) && this.opts.isCellEditable(col, item.originalIndex)) {
3288
+ this.opts.selectedRange.set(null);
3289
+ this.opts.selectedCell.set({ rowIndex: item.originalIndex, colIndex });
3290
+ this.scrollToKeepVisible(displayIndex, colIndex);
3291
+ if (this.isTextColumn(col))
3292
+ this.opts.startEdit(item.originalIndex, colIndex, '', false);
3293
+ else
3294
+ this.opts.focusGrid();
3295
+ return;
3296
+ }
3297
+ colIndex += direction;
3298
+ }
3299
+ displayIndex += direction;
3300
+ colIndex = direction > 0 ? 0 : cols.length - 1;
3301
+ while (displayIndex >= 0
3302
+ && displayIndex < items.length
3303
+ && isGroupHeaderItem(items[displayIndex])) {
3304
+ displayIndex += direction;
3305
+ }
3306
+ }
3307
+ this.opts.focusGrid();
3308
+ }
3309
+ isTextColumn(col) {
3310
+ return !col.values?.length && (col.type === undefined || col.type === 'text');
3311
+ }
2576
3312
  /** Finds a data row's current index in the projected display items. */
2577
3313
  findDisplayIndex(originalIndex) {
2578
3314
  return this.opts.filteredItems().findIndex(item => isDataRowItem(item) && item.originalIndex === originalIndex);
@@ -2722,6 +3458,10 @@ class AgridPresentationService {
2722
3458
  getCellClass(col, value, row) {
2723
3459
  return col.cellClass?.({ value, row }) ?? '';
2724
3460
  }
3461
+ /** Resolves dynamic CSS classes configured for a whole data row. */
3462
+ getRowClass(row, index) {
3463
+ return this.opts.getRowClass?.()?.({ row, index }) ?? '';
3464
+ }
2725
3465
  /** Returns the compact label for a built-in aggregate. */
2726
3466
  getAggregateLabel(col) {
2727
3467
  const aggregate = this.opts.control()?.aggregates()[col.field] ?? col.aggregate;
@@ -2761,6 +3501,91 @@ class AgridPresentationService {
2761
3501
  }
2762
3502
  }
2763
3503
 
3504
+ /** Internal renderer for provider-configured grid commands and dropdown items. */
3505
+ class AgridMenuBarComponent {
3506
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
3507
+ context = input.required(...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
3508
+ label = input('Actions', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
3509
+ openItemId = input(null, ...(ngDevMode ? [{ debugName: "openItemId" }] : /* istanbul ignore next */ []));
3510
+ action = output();
3511
+ openItemIdChange = output();
3512
+ resolveState(state, fallback) {
3513
+ if (typeof state === 'function')
3514
+ return state(this.context());
3515
+ return state ?? fallback;
3516
+ }
3517
+ isActive(item) {
3518
+ return this.resolveState(item.active, false);
3519
+ }
3520
+ isDisabled(item) {
3521
+ return this.resolveState(item.disabled, false);
3522
+ }
3523
+ visibleChildren(item) {
3524
+ return (item.items ?? []).filter(child => this.resolveState(child.visible, true));
3525
+ }
3526
+ runAction(event, item) {
3527
+ event.stopPropagation();
3528
+ if (!this.resolveState(item.visible, true) || this.isDisabled(item))
3529
+ return;
3530
+ this.action.emit(item.id);
3531
+ this.openItemIdChange.emit(null);
3532
+ }
3533
+ toggleMenu(event, item) {
3534
+ event.preventDefault();
3535
+ event.stopPropagation();
3536
+ if (this.isDisabled(item) || this.visibleChildren(item).length === 0)
3537
+ return;
3538
+ this.openItemIdChange.emit(this.openItemId() === item.id ? null : item.id);
3539
+ }
3540
+ onTriggerKeydown(event, item) {
3541
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')
3542
+ return;
3543
+ event.preventDefault();
3544
+ event.stopPropagation();
3545
+ if (this.isDisabled(item) || this.visibleChildren(item).length === 0)
3546
+ return;
3547
+ this.openItemIdChange.emit(item.id);
3548
+ const group = event.currentTarget.closest('.ag-menu-bar-group');
3549
+ setTimeout(() => {
3550
+ const enabled = group?.querySelectorAll('.ag-menu-bar-dropdown [role="menuitem"]:not(:disabled)');
3551
+ const target = event.key === 'ArrowUp' ? enabled?.[enabled.length - 1] : enabled?.[0];
3552
+ target?.focus();
3553
+ });
3554
+ }
3555
+ onMenuKeydown(event) {
3556
+ const menu = event.currentTarget;
3557
+ const items = Array.from(menu.querySelectorAll('[role="menuitem"]:not(:disabled)'));
3558
+ if (event.key === 'Escape') {
3559
+ event.preventDefault();
3560
+ event.stopPropagation();
3561
+ this.openItemIdChange.emit(null);
3562
+ menu.closest('.ag-menu-bar-group')
3563
+ ?.querySelector('.ag-menu-bar-trigger')
3564
+ ?.focus();
3565
+ return;
3566
+ }
3567
+ if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) || items.length === 0)
3568
+ return;
3569
+ event.preventDefault();
3570
+ event.stopPropagation();
3571
+ const current = items.indexOf(event.target);
3572
+ const next = event.key === 'Home'
3573
+ ? 0
3574
+ : event.key === 'End'
3575
+ ? items.length - 1
3576
+ : event.key === 'ArrowDown'
3577
+ ? (current + 1 + items.length) % items.length
3578
+ : (current - 1 + items.length) % items.length;
3579
+ items[next].focus();
3580
+ }
3581
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridMenuBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3582
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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 });
3583
+ }
3584
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridMenuBarComponent, decorators: [{
3585
+ 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"] }]
3587
+ }], 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
+
2764
3589
  /**
2765
3590
  * Signal-based container for mutable grid UI state such as column widths and active filters.
2766
3591
  *
@@ -2781,6 +3606,7 @@ class AgridPresentationService {
2781
3606
  class AgridControl {
2782
3607
  _columnWidths = signal({}, ...(ngDevMode ? [{ debugName: "_columnWidths" }] : /* istanbul ignore next */ []));
2783
3608
  _filters = signal({}, ...(ngDevMode ? [{ debugName: "_filters" }] : /* istanbul ignore next */ []));
3609
+ _quickFilter = signal('', ...(ngDevMode ? [{ debugName: "_quickFilter" }] : /* istanbul ignore next */ []));
2784
3610
  _allowRowReorder = signal(false, ...(ngDevMode ? [{ debugName: "_allowRowReorder" }] : /* istanbul ignore next */ []));
2785
3611
  _groupByField = signal(null, ...(ngDevMode ? [{ debugName: "_groupByField" }] : /* istanbul ignore next */ []));
2786
3612
  _hiddenColumns = signal(new Set(), ...(ngDevMode ? [{ debugName: "_hiddenColumns" }] : /* istanbul ignore next */ []));
@@ -2798,6 +3624,8 @@ class AgridControl {
2798
3624
  this._columnWidths.set({ ...state.columnWidths });
2799
3625
  if (state?.filters)
2800
3626
  this._filters.set({ ...state.filters });
3627
+ if (state?.quickFilter)
3628
+ this._quickFilter.set(state.quickFilter);
2801
3629
  if (state?.allowRowReorder)
2802
3630
  this._allowRowReorder.set(state.allowRowReorder);
2803
3631
  if (state?.groupByField !== undefined)
@@ -2842,6 +3670,16 @@ class AgridControl {
2842
3670
  setGroupBy(field) {
2843
3671
  this._groupByField.set(field);
2844
3672
  }
3673
+ /**
3674
+ * Global quick-filter text. When non-empty (and not in server-side filtering mode),
3675
+ * the grid keeps only rows where at least one visible column's display value contains
3676
+ * this text (case-insensitive).
3677
+ */
3678
+ quickFilter = this._quickFilter.asReadonly();
3679
+ /** Set the global quick-filter text. Pass an empty string to clear it. */
3680
+ setQuickFilter(text) {
3681
+ this._quickFilter.set(text);
3682
+ }
2845
3683
  /**
2846
3684
  * Reactive set of field names that are currently hidden.
2847
3685
  * An empty set means all columns are visible.
@@ -3114,9 +3952,21 @@ class AgridControl {
3114
3952
  [field]: { ...this.getFilter(field), selectedValues: values },
3115
3953
  }));
3116
3954
  }
3117
- /** Ordered list of sorted field names, from highest to lowest priority. */
3118
- sortOrder = this._sortOrder.asReadonly();
3119
- /** Return the 1-based sort priority of a field, or `0` if it is not sorted. */
3955
+ /**
3956
+ * Set a column condition filter. Text columns support string operators while number/date
3957
+ * columns support comparison operators.
3958
+ * Pass `operator: null` (or an empty `operand`) to clear it. `operand2` is only used
3959
+ * by the `'between'` operator.
3960
+ */
3961
+ setRangeFilter(field, operator, operand, operand2 = null) {
3962
+ this._filters.update(f => ({
3963
+ ...f,
3964
+ [field]: { ...this.getFilter(field), operator, operand, operand2 },
3965
+ }));
3966
+ }
3967
+ /** Ordered list of sorted field names, from highest to lowest priority. */
3968
+ sortOrder = this._sortOrder.asReadonly();
3969
+ /** Return the 1-based sort priority of a field, or `0` if it is not sorted. */
3120
3970
  getSortPriority(field) {
3121
3971
  const idx = this._sortOrder().indexOf(field);
3122
3972
  return idx === -1 ? 0 : idx + 1;
@@ -3161,10 +4011,11 @@ class AgridControl {
3161
4011
  });
3162
4012
  this._sortOrder.update(o => o.filter(f => f !== field));
3163
4013
  }
3164
- /** Remove all active filters and sorts for every column. */
4014
+ /** Remove all active filters and sorts for every column, including the quick filter. */
3165
4015
  clearAllFilters() {
3166
4016
  this._filters.set({});
3167
4017
  this._sortOrder.set([]);
4018
+ this._quickFilter.set('');
3168
4019
  }
3169
4020
  /**
3170
4021
  * Return `true` when the given field has any active filter or sort.
@@ -3172,11 +4023,13 @@ class AgridControl {
3172
4023
  */
3173
4024
  hasActiveFilter(field) {
3174
4025
  const f = this.getFilter(field);
3175
- return !!(f.text || f.selectedValues !== null || f.sort);
4026
+ const hasRange = !!f.operator && f.operand != null && f.operand !== '';
4027
+ return !!(f.text || f.selectedValues !== null || f.sort || hasRange);
3176
4028
  }
3177
- /** Return `true` when ANY column has an active filter or sort. */
4029
+ /** Return `true` when the quick filter or ANY column has an active filter or sort. */
3178
4030
  hasAnyActiveFilter() {
3179
- return Object.values(this._filters()).some(f => f.text || f.selectedValues !== null || f.sort);
4031
+ return !!this._quickFilter() || Object.values(this._filters()).some(f => f.text || f.selectedValues !== null || f.sort
4032
+ || (!!f.operator && f.operand != null && f.operand !== ''));
3180
4033
  }
3181
4034
  // ── Serialization ──────────────────────────────────────────────────────────
3182
4035
  /** Serialize current state to a plain object suitable for JSON storage. */
@@ -3184,6 +4037,7 @@ class AgridControl {
3184
4037
  return {
3185
4038
  columnWidths: { ...this._columnWidths() },
3186
4039
  filters: { ...this._filters() },
4040
+ quickFilter: this._quickFilter() || undefined,
3187
4041
  allowRowReorder: this._allowRowReorder(),
3188
4042
  groupByField: this._groupByField() ?? undefined,
3189
4043
  hiddenColumns: [...this._hiddenColumns()],
@@ -3401,12 +4255,18 @@ class AgridProvider {
3401
4255
  serverSideFiltering;
3402
4256
  /** Delay before server-side filter events are emitted. */
3403
4257
  filterDebounceMs;
4258
+ /** Whether the global quick-filter box is shown above the grid. */
4259
+ enableQuickFilter;
4260
+ /** Commands rendered in the optional menu bar above the column headers. */
4261
+ menuBarItems;
3404
4262
  /** Enabled sorting mode. */
3405
4263
  sortOption;
3406
4264
  /** Toggle auto-add-rows without recreating the provider. @default signal(false) */
3407
4265
  autoAddRows;
3408
4266
  /** Enabled row-selection mode. */
3409
4267
  rowSelection;
4268
+ /** Behavior after pressing Enter while an inline cell editor is active. */
4269
+ enterEditAction;
3410
4270
  /** Optional description shown beside each group heading. */
3411
4271
  groupDescription;
3412
4272
  /** Actions available from group headers. */
@@ -3423,6 +4283,16 @@ class AgridProvider {
3423
4283
  emptyText;
3424
4284
  /** Whether edits are restricted to the sidebar editor. */
3425
4285
  useSidebarEditor;
4286
+ /** Optional callback returning CSS classes for a whole data row. */
4287
+ getRowClass;
4288
+ /** Optional callback designating rows pinned to the top or bottom of the body. */
4289
+ pinRow;
4290
+ /** Whether master/detail expandable detail rows are enabled. */
4291
+ masterDetail;
4292
+ /** Returns the sanitized HTML rendered inside an expanded detail panel. */
4293
+ detailRenderer;
4294
+ /** Fixed height in pixels of an expanded detail panel row. */
4295
+ detailRowHeight;
3426
4296
  /** Toggle the loading overlay without recreating the provider. @default signal(false) */
3427
4297
  loading;
3428
4298
  /** Toggle readonly mode without recreating the provider. @default signal(false) */
@@ -3446,8 +4316,11 @@ class AgridProvider {
3446
4316
  this.autoOpenDetail = config.autoOpenDetail ?? false;
3447
4317
  this.serverSideFiltering = config.serverSideFiltering ?? false;
3448
4318
  this.filterDebounceMs = Math.max(0, config.filterDebounceMs ?? 300);
4319
+ this.enableQuickFilter = config.enableQuickFilter ?? false;
4320
+ this.menuBarItems = config.menuBarItems ?? [];
3449
4321
  this.sortOption = config.sortOption ?? 'multi';
3450
4322
  this.rowSelection = config.rowSelection ?? 'none';
4323
+ this.enterEditAction = config.enterEditAction ?? 'nextRow';
3451
4324
  this.groupDescription = config.groupDescription ?? null;
3452
4325
  this.groupActions = config.groupActions ?? [];
3453
4326
  this.cellMenuItems = config.cellMenuItems ?? [];
@@ -3458,6 +4331,11 @@ class AgridProvider {
3458
4331
  this.loading = signal(config.loading ?? false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
3459
4332
  this.readonlyGrid = signal(config.readonly ?? false, ...(ngDevMode ? [{ debugName: "readonlyGrid" }] : /* istanbul ignore next */ []));
3460
4333
  this.useSidebarEditor = config.useSidebarEditor ?? false;
4334
+ this.getRowClass = config.getRowClass;
4335
+ this.pinRow = config.pinRow;
4336
+ this.masterDetail = config.masterDetail ?? false;
4337
+ this.detailRenderer = config.detailRenderer;
4338
+ this.detailRowHeight = config.detailRowHeight ?? 200;
3461
4339
  }
3462
4340
  /** Returns the current reactive row array. */
3463
4341
  getGridData() {
@@ -3484,6 +4362,10 @@ class AgridProjectionModel {
3484
4362
  return indices;
3485
4363
  const filters = control.filters();
3486
4364
  indices = applyTextAndValueFilters(rows, indices, filters, colMap, this.opts.locale());
4365
+ const quick = control.quickFilter();
4366
+ if (quick) {
4367
+ indices = applyQuickFilter(rows, indices, quick, this.opts.visibleColDefs(), this.opts.locale());
4368
+ }
3487
4369
  if (control.groupByField())
3488
4370
  return indices;
3489
4371
  const sortEntries = this.sortEntries(filters);
@@ -3493,6 +4375,41 @@ class AgridProjectionModel {
3493
4375
  }, ...(ngDevMode ? [{ debugName: "filteredSortedIndices" }] : /* istanbul ignore next */ []));
3494
4376
  /** Total filtered row count, unaffected by client-side pagination. */
3495
4377
  filteredRowCount = computed(() => this.filteredSortedIndices().length, ...(ngDevMode ? [{ debugName: "filteredRowCount" }] : /* istanbul ignore next */ []));
4378
+ /** 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 */ []));
4380
+ /**
4381
+ * Partitions the filtered+sorted indices into top-pinned, bottom-pinned, and a body set.
4382
+ * Pinned rows keep their real source index, so editing/selection over them is unchanged.
4383
+ */
4384
+ partitionPinned = computed(() => {
4385
+ if (!this.pinningActive())
4386
+ return { top: [], bottom: [], bodySet: new Set() };
4387
+ const rows = this.opts.dataSource().rows();
4388
+ const pinRow = this.opts.pinRow();
4389
+ const top = [];
4390
+ const bottom = [];
4391
+ const bodySet = new Set();
4392
+ for (const index of this.filteredSortedIndices()) {
4393
+ const where = pinRow(rows[index], index);
4394
+ if (where === 'top')
4395
+ top.push(index);
4396
+ else if (where === 'bottom')
4397
+ bottom.push(index);
4398
+ else
4399
+ bodySet.add(index);
4400
+ }
4401
+ return { top, bottom, bodySet };
4402
+ }, ...(ngDevMode ? [{ debugName: "partitionPinned" }] : /* istanbul ignore next */ []));
4403
+ /** Top-pinned rows as data-row items, in filtered+sorted order. */
4404
+ pinnedTopItems = computed(() => {
4405
+ const rows = this.opts.dataSource().rows();
4406
+ return this.partitionPinned().top.map(originalIndex => ({ row: rows[originalIndex], originalIndex }));
4407
+ }, ...(ngDevMode ? [{ debugName: "pinnedTopItems" }] : /* istanbul ignore next */ []));
4408
+ /** Bottom-pinned rows as data-row items, in filtered+sorted order. */
4409
+ pinnedBottomItems = computed(() => {
4410
+ const rows = this.opts.dataSource().rows();
4411
+ return this.partitionPinned().bottom.map(originalIndex => ({ row: rows[originalIndex], originalIndex }));
4412
+ }, ...(ngDevMode ? [{ debugName: "pinnedBottomItems" }] : /* istanbul ignore next */ []));
3496
4413
  /** All source indices in active sort order, ignoring filters. Used for tree ancestor ordering. */
3497
4414
  allSortedIndices = computed(() => {
3498
4415
  const rows = this.opts.dataSource().rows();
@@ -3529,43 +4446,7 @@ class AgridProjectionModel {
3529
4446
  return this.opts.visibleColDefs().some(col => col.aggregate || aggregates[col.field]);
3530
4447
  }, ...(ngDevMode ? [{ debugName: "showFooter" }] : /* istanbul ignore next */ []));
3531
4448
  /** Aggregate values computed over all filtered rows, before pagination. */
3532
- footerValues = computed(() => {
3533
- const rows = this.opts.dataSource().rows();
3534
- const indices = this.filteredSortedIndices();
3535
- const controlAggregates = this.opts.control()?.aggregates() ?? {};
3536
- const result = {};
3537
- for (const col of this.opts.visibleColDefs()) {
3538
- const aggregate = controlAggregates[col.field] ?? col.aggregate;
3539
- if (!aggregate)
3540
- continue;
3541
- const values = indices.map(index => rows[index][col.field]);
3542
- if (typeof aggregate === 'function') {
3543
- result[col.field] = aggregate(values);
3544
- continue;
3545
- }
3546
- const numbers = values.map(Number).filter(value => !Number.isNaN(value));
3547
- switch (aggregate) {
3548
- case 'sum':
3549
- result[col.field] = numbers.reduce((sum, value) => sum + value, 0);
3550
- break;
3551
- case 'avg':
3552
- result[col.field] = numbers.length
3553
- ? numbers.reduce((sum, value) => sum + value, 0) / numbers.length
3554
- : null;
3555
- break;
3556
- case 'min':
3557
- result[col.field] = numbers.length ? Math.min(...numbers) : null;
3558
- break;
3559
- case 'max':
3560
- result[col.field] = numbers.length ? Math.max(...numbers) : null;
3561
- break;
3562
- case 'count':
3563
- result[col.field] = values.filter(value => value != null && value !== '').length;
3564
- break;
3565
- }
3566
- }
3567
- return result;
3568
- }, ...(ngDevMode ? [{ debugName: "footerValues" }] : /* istanbul ignore next */ []));
4449
+ footerValues = computed(() => computeAggregates(this.opts.dataSource().rows(), this.filteredSortedIndices(), this.opts.visibleColDefs(), this.opts.control()?.aggregates() ?? {}), ...(ngDevMode ? [{ debugName: "footerValues" }] : /* istanbul ignore next */ []));
3569
4450
  /** Filtered, sorted, paginated, and optionally grouped virtual-scroll items. */
3570
4451
  filteredItems = computed(() => {
3571
4452
  const rows = this.opts.dataSource().rows();
@@ -3581,6 +4462,11 @@ class AgridProjectionModel {
3581
4462
  const filters = control?.filters() ?? {};
3582
4463
  const filterActive = !this.opts.serverSideFiltering()
3583
4464
  && Object.values(filters).some(f => f.text || f.selectedValues !== null);
4465
+ if (isPathTreeConfig(treeConfig)) {
4466
+ const items = this.appendTreeDetailItems(buildPathTreeItems(rows, indices, treeConfig, expandedIds, filterActive && treeConfig.keepAncestorsOnFilter !== false), rows, treeConfig);
4467
+ this.appendAddRow(items);
4468
+ return items;
4469
+ }
3584
4470
  if (filterActive && treeConfig.keepAncestorsOnFilter !== false) {
3585
4471
  const { getId, getParentId } = treeConfig;
3586
4472
  const idToIndex = new Map();
@@ -3599,14 +4485,19 @@ class AgridProjectionModel {
3599
4485
  }
3600
4486
  }
3601
4487
  const ordered = this.allSortedIndices().filter(index => visible.has(index));
3602
- const items = buildTreeItems(rows, ordered, treeConfig, expandedIds, forced);
4488
+ const items = this.appendTreeDetailItems(buildTreeItems(rows, ordered, treeConfig, expandedIds, forced), rows, treeConfig);
3603
4489
  this.appendAddRow(items);
3604
4490
  return items;
3605
4491
  }
3606
- const items = buildTreeItems(rows, indices, treeConfig, expandedIds);
4492
+ const items = this.appendTreeDetailItems(buildTreeItems(rows, indices, treeConfig, expandedIds), rows, treeConfig);
3607
4493
  this.appendAddRow(items);
3608
4494
  return items;
3609
4495
  }
4496
+ // Pull pinned rows out of the body; they render in fixed top/bottom containers instead.
4497
+ if (this.pinningActive()) {
4498
+ const bodySet = this.partitionPinned().bodySet;
4499
+ indices = indices.filter(index => bodySet.has(index));
4500
+ }
3610
4501
  if (control) {
3611
4502
  const groupField = control.groupByField();
3612
4503
  const pageSize = control.pageSize();
@@ -3624,15 +4515,20 @@ class AgridProjectionModel {
3624
4515
  const sortEntries = this.opts.serverSideFiltering()
3625
4516
  ? []
3626
4517
  : this.sortEntries(filters);
3627
- const items = buildGroupedItems(rows, indices, groupField, this.columnMap(), sortEntries, expandedLabels, this.opts.locale());
4518
+ const items = buildGroupedItems(rows, indices, groupField, this.columnMap(), sortEntries, expandedLabels, this.opts.locale(), this.opts.visibleColDefs(), control.aggregates());
3628
4519
  this.appendAddRow(items);
3629
4520
  return items;
3630
4521
  }
3631
4522
  }
3632
- const items = indices.map(originalIndex => ({
3633
- row: rows[originalIndex],
3634
- originalIndex,
3635
- }));
4523
+ // Flat mode: optionally interleave a master/detail panel item after each expanded row.
4524
+ const expandedDetail = this.opts.masterDetail?.() ? this.opts.expandedDetailIds?.() : null;
4525
+ const items = [];
4526
+ for (const originalIndex of indices) {
4527
+ const row = rows[originalIndex];
4528
+ items.push({ row, originalIndex });
4529
+ if (expandedDetail?.has(originalIndex))
4530
+ items.push({ detailFor: originalIndex, row });
4531
+ }
3636
4532
  this.appendAddRow(items);
3637
4533
  return items;
3638
4534
  }, ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
@@ -3652,6 +4548,29 @@ class AgridProjectionModel {
3652
4548
  .map(field => [field, filters[field]])
3653
4549
  .filter(([, filter]) => !!filter?.sort);
3654
4550
  }
4551
+ appendTreeDetailItems(items, rows, treeConfig) {
4552
+ const expandedDetail = this.opts.masterDetail?.() ? this.opts.expandedDetailIds?.() : null;
4553
+ if (!expandedDetail?.size)
4554
+ return items;
4555
+ const parentIds = !isPathTreeConfig(treeConfig)
4556
+ ? new Set(rows
4557
+ .map(row => treeConfig.getParentId(row))
4558
+ .filter((id) => id !== null && id !== undefined))
4559
+ : new Set();
4560
+ const result = [];
4561
+ for (const item of items) {
4562
+ result.push(item);
4563
+ if (item
4564
+ && typeof item === 'object'
4565
+ && 'originalIndex' in item
4566
+ && (isPathTreeConfig(treeConfig)
4567
+ || !parentIds.has(treeConfig.getId(item.row)))
4568
+ && expandedDetail.has(item.originalIndex)) {
4569
+ result.push({ detailFor: item.originalIndex, row: item.row });
4570
+ }
4571
+ }
4572
+ return result;
4573
+ }
3655
4574
  appendAddRow(items) {
3656
4575
  if (this.opts.allowAddRows() && !this.opts.autoAddRows())
3657
4576
  items.push(null);
@@ -3786,7 +4705,7 @@ class AgridRangeController {
3786
4705
  if (insideSource)
3787
4706
  continue;
3788
4707
  const col = cols[colIndex];
3789
- if (!col || !this.opts.isCellEditable(col))
4708
+ if (!col || !this.opts.isCellEditable(col, item.originalIndex))
3790
4709
  continue;
3791
4710
  const sourceRowIndex = rowOffset % sourceValues.length;
3792
4711
  const sourceRowValues = sourceValues[sourceRowIndex];
@@ -3909,6 +4828,17 @@ class AgridRangeController {
3909
4828
  }
3910
4829
  }
3911
4830
 
4831
+ /**
4832
+ * Whether a pointer event originated inside an editable form control. Used to avoid calling
4833
+ * `preventDefault()` on a row pointerdown, which would otherwise cancel native text selection
4834
+ * (and caret placement) inside an active cell editor.
4835
+ */
4836
+ function isEditableEventTarget(target) {
4837
+ return target instanceof HTMLInputElement
4838
+ || target instanceof HTMLTextAreaElement
4839
+ || target instanceof HTMLSelectElement
4840
+ || (target instanceof HTMLElement && target.isContentEditable);
4841
+ }
3912
4842
  /**
3913
4843
  * Owns row selection, row/cell context menus, and row-level operations.
3914
4844
  * @internal
@@ -3972,7 +4902,7 @@ class AgridRowController {
3972
4902
  this.emitSelection();
3973
4903
  }
3974
4904
  else {
3975
- if (!(event.target instanceof HTMLSelectElement))
4905
+ if (!isEditableEventTarget(event.target))
3976
4906
  event.preventDefault();
3977
4907
  this.selectedIndices.set(new Set([originalIndex]));
3978
4908
  this.selectionPivot = originalIndex;
@@ -4101,9 +5031,21 @@ class AgridSidebarController {
4101
5031
  return index === null ? null : this.opts.dataSource().rows()[index] ?? null;
4102
5032
  }, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
4103
5033
  hiddenColumns = computed(() => this.opts.control()?.hiddenColumns() ?? new Set(), ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : /* istanbul ignore next */ []));
5034
+ /** Per-field validation messages for rejected sidebar edits (`field → message`). */
5035
+ validationErrors = signal({}, ...(ngDevMode ? [{ debugName: "validationErrors" }] : /* istanbul ignore next */ []));
4104
5036
  constructor(opts) {
4105
5037
  this.opts = opts;
4106
5038
  }
5039
+ /** Remove the stored validation message for one field, if any. */
5040
+ clearFieldError(field) {
5041
+ if (!(field in this.validationErrors()))
5042
+ return;
5043
+ this.validationErrors.update(errors => {
5044
+ const next = { ...errors };
5045
+ delete next[field];
5046
+ return next;
5047
+ });
5048
+ }
4107
5049
  /** Opens the detail tab when automatic detail display is enabled. */
4108
5050
  syncAutoOpen() {
4109
5051
  if (this.opts.autoOpenDetail() && this.opts.selectedRowIndex() !== null) {
@@ -4146,6 +5088,8 @@ class AgridSidebarController {
4146
5088
  const index = this.opts.selectedRowIndex();
4147
5089
  if (index === null)
4148
5090
  return;
5091
+ if (!this.opts.isCellEditable(col, index))
5092
+ return;
4149
5093
  let newValue = stringValue;
4150
5094
  if (col.type === 'number') {
4151
5095
  newValue = stringValue === '' ? null : Number(stringValue);
@@ -4161,9 +5105,17 @@ class AgridSidebarController {
4161
5105
  ? stringValue
4162
5106
  : typeof option === 'string' ? option : option.value;
4163
5107
  }
4164
- const oldValue = this.opts.dataSource().getRow(index)[field];
5108
+ const row = this.opts.dataSource().getRow(index);
5109
+ const oldValue = row[field];
4165
5110
  if (oldValue === newValue)
4166
5111
  return;
5112
+ const message = col.validate?.(newValue, row) ?? null;
5113
+ if (message) {
5114
+ this.validationErrors.update(errors => ({ ...errors, [field]: message }));
5115
+ this.opts.onValidationFailed({ rowIndex: index, field, value: newValue, message });
5116
+ return;
5117
+ }
5118
+ this.clearFieldError(field);
4167
5119
  this.opts.dataSource().patchRow(index, { [field]: newValue });
4168
5120
  const colIndex = this.opts.visibleColDefs().findIndex(column => column.field === field);
4169
5121
  this.opts.control()?.pushEdit({ rowIndex: index, field, oldValue, newValue });
@@ -4187,11 +5139,15 @@ class AgridSidebarComponent {
4187
5139
  columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
4188
5140
  headerGroups = input([], ...(ngDevMode ? [{ debugName: "headerGroups" }] : /* istanbul ignore next */ []));
4189
5141
  row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
5142
+ rowIndex = input(null, ...(ngDevMode ? [{ debugName: "rowIndex" }] : /* istanbul ignore next */ []));
4190
5143
  hiddenColumns = input(new Set(), ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : /* istanbul ignore next */ []));
4191
5144
  locale = input(undefined, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
4192
5145
  localeText = input(AGRID_LOCALE_TEXT.en, ...(ngDevMode ? [{ debugName: "localeText" }] : /* istanbul ignore next */ []));
4193
5146
  readonlyGrid = input(false, ...(ngDevMode ? [{ debugName: "readonlyGrid" }] : /* istanbul ignore next */ []));
4194
5147
  useSidebarEditor = input(false, ...(ngDevMode ? [{ debugName: "useSidebarEditor" }] : /* istanbul ignore next */ []));
5148
+ isCellEditable = input(col => col.editable !== false, ...(ngDevMode ? [{ debugName: "isCellEditable" }] : /* istanbul ignore next */ []));
5149
+ /** Per-field validation messages (`field → message`) for rejected detail edits. */
5150
+ errors = input({}, ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
4195
5151
  close = output();
4196
5152
  tabChange = output();
4197
5153
  toggleColumn = output();
@@ -4237,6 +5193,32 @@ class AgridSidebarComponent {
4237
5193
  visible: event.target.checked,
4238
5194
  });
4239
5195
  }
5196
+ /** Apply the row-aware input mask while preserving the sidebar's change-to-commit behavior. */
5197
+ onDetailMaskInput(field, event) {
5198
+ const input = event.target;
5199
+ const row = this.row();
5200
+ if (row && field.col.inputMask && field.col.type !== 'number' && field.col.type !== 'date') {
5201
+ const mask = field.col.inputMask({
5202
+ row,
5203
+ value: field.rawValue,
5204
+ column: field.col,
5205
+ });
5206
+ if (mask && !matchesInputMask(input.value, mask)) {
5207
+ input.value = input.dataset['agridMaskValue'] ?? field.inputValue;
5208
+ return;
5209
+ }
5210
+ input.dataset['agridMaskValue'] = input.value;
5211
+ }
5212
+ }
5213
+ /** Forward the final sidebar value after native change/blur. */
5214
+ onDetailChange(field, event) {
5215
+ this.onDetailMaskInput(field, event);
5216
+ this.detailEdit.emit({
5217
+ field: field.col.field,
5218
+ col: field.col,
5219
+ value: event.target.value,
5220
+ });
5221
+ }
4240
5222
  detailFields = computed(() => {
4241
5223
  const row = this.row();
4242
5224
  if (!row)
@@ -4244,6 +5226,7 @@ class AgridSidebarComponent {
4244
5226
  const locale = this.locale();
4245
5227
  const readonlyGrid = this.readonlyGrid();
4246
5228
  const hiddenColumns = this.hiddenColumns();
5229
+ const rowIndex = this.rowIndex();
4247
5230
  return this.columns().map(col => {
4248
5231
  const rawValue = row[col.field];
4249
5232
  let inputValue = String(rawValue ?? '');
@@ -4256,18 +5239,160 @@ class AgridSidebarComponent {
4256
5239
  rawValue,
4257
5240
  inputValue,
4258
5241
  hidden: hiddenColumns.has(col.field),
4259
- editable: !readonlyGrid && col.editable !== false,
5242
+ editable: !readonlyGrid && rowIndex !== null && this.isCellEditable()(col, rowIndex),
4260
5243
  col,
4261
5244
  };
4262
5245
  });
4263
5246
  }, ...(ngDevMode ? [{ debugName: "detailFields" }] : /* istanbul ignore next */ []));
4264
5247
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4265
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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 }, 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 } }, 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 || useSidebarEditor()) && field.col.values?.length) {\n <select\n class=\"ag-detail-input\"\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 || useSidebarEditor()) {\n <input\n class=\"ag-detail-input\"\n [type]=\"field.col.type === 'number' ? 'number' : field.col.type === 'date' ? 'date' : 'text'\"\n [value]=\"field.inputValue\"\n (change)=\"detailEdit.emit({\n field: field.col.field,\n col: field.col,\n value: $any($event.target).value\n })\"\n />\n } @else {\n <span class=\"ag-detail-value\">{{ field.value }}</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}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5248
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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 });
4266
5249
  }
4267
5250
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridSidebarComponent, decorators: [{
4268
5251
  type: Component,
4269
- 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 || useSidebarEditor()) && field.col.values?.length) {\n <select\n class=\"ag-detail-input\"\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 || useSidebarEditor()) {\n <input\n class=\"ag-detail-input\"\n [type]=\"field.col.type === 'number' ? 'number' : field.col.type === 'date' ? 'date' : 'text'\"\n [value]=\"field.inputValue\"\n (change)=\"detailEdit.emit({\n field: field.col.field,\n col: field.col,\n value: $any($event.target).value\n })\"\n />\n } @else {\n <span class=\"ag-detail-value\">{{ field.value }}</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}\n"] }]
4270
- }], 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 }] }], 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 }] }], 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"] }] } });
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"] }] } });
5254
+
5255
+ /** Pixels rendered above and below the viewport so scrolling does not reveal blank rows. */
5256
+ const BUFFER_PX = 200;
5257
+ /**
5258
+ * A {@link VirtualScrollStrategy} that supports a different height per item instead of the single
5259
+ * fixed `itemSize` used by CDK's {@link FixedSizeVirtualScrollStrategy}.
5260
+ *
5261
+ * Item heights are supplied as a flat array (one entry per virtual item) and converted into a
5262
+ * prefix-sum offset table, so any row — a normal data row or a tall master/detail panel — can have
5263
+ * its own height while scroll position math stays O(log n) via binary search.
5264
+ *
5265
+ * When every item shares the same height (the common case with no detail rows open) the behavior
5266
+ * is equivalent to the fixed-size strategy, so the default code path is regression-safe.
5267
+ * @internal
5268
+ */
5269
+ class AgridVariableRowSizeStrategy {
5270
+ viewport = null;
5271
+ sizes = [];
5272
+ /** Cumulative offsets; `offsets[i]` is the top of item `i`, `offsets[n]` is the total size. */
5273
+ offsets = [0];
5274
+ indexChange = new Subject();
5275
+ scrolledIndexChange = this.indexChange.pipe(distinctUntilChanged());
5276
+ attach(viewport) {
5277
+ this.viewport = viewport;
5278
+ this.updateTotalContentSize();
5279
+ this.updateRenderedRange();
5280
+ }
5281
+ detach() {
5282
+ this.indexChange.complete();
5283
+ this.viewport = null;
5284
+ }
5285
+ /** Replace the per-item heights and re-measure. Called by the directive when inputs change. */
5286
+ updateItemSizes(sizes) {
5287
+ this.sizes = sizes;
5288
+ const offsets = new Array(sizes.length + 1);
5289
+ offsets[0] = 0;
5290
+ for (let i = 0; i < sizes.length; i++)
5291
+ offsets[i + 1] = offsets[i] + sizes[i];
5292
+ this.offsets = offsets;
5293
+ if (this.viewport) {
5294
+ this.updateTotalContentSize();
5295
+ this.updateRenderedRange();
5296
+ }
5297
+ }
5298
+ onContentScrolled() {
5299
+ this.updateRenderedRange();
5300
+ }
5301
+ onDataLengthChanged() {
5302
+ this.updateTotalContentSize();
5303
+ this.updateRenderedRange();
5304
+ }
5305
+ onContentRendered() {
5306
+ /* no-op: heights are supplied, not measured from the DOM */
5307
+ }
5308
+ onRenderedOffsetChanged() {
5309
+ /* no-op */
5310
+ }
5311
+ scrollToIndex(index, behavior) {
5312
+ if (!this.viewport)
5313
+ return;
5314
+ const clamped = Math.max(0, Math.min(index, this.sizes.length));
5315
+ this.viewport.scrollToOffset(this.offsets[clamped] ?? 0, behavior);
5316
+ }
5317
+ get totalSize() {
5318
+ return this.offsets[this.offsets.length - 1] ?? 0;
5319
+ }
5320
+ updateTotalContentSize() {
5321
+ this.viewport?.setTotalContentSize(this.totalSize);
5322
+ }
5323
+ /** Largest item index whose top offset is `<= offset` (binary search over the prefix sums). */
5324
+ indexAt(offset) {
5325
+ const o = this.offsets;
5326
+ let lo = 0;
5327
+ let hi = o.length - 1;
5328
+ while (lo < hi) {
5329
+ const mid = (lo + hi + 1) >> 1;
5330
+ if (o[mid] <= offset)
5331
+ lo = mid;
5332
+ else
5333
+ hi = mid - 1;
5334
+ }
5335
+ return lo;
5336
+ }
5337
+ updateRenderedRange() {
5338
+ const viewport = this.viewport;
5339
+ if (!viewport)
5340
+ return;
5341
+ const dataLength = viewport.getDataLength();
5342
+ const n = Math.min(this.sizes.length, dataLength);
5343
+ if (n === 0) {
5344
+ viewport.setRenderedRange({ start: 0, end: 0 });
5345
+ viewport.setRenderedContentOffset(0);
5346
+ this.indexChange.next(0);
5347
+ return;
5348
+ }
5349
+ const scrollOffset = viewport.measureScrollOffset();
5350
+ const viewportSize = viewport.getViewportSize();
5351
+ const start = Math.max(0, this.indexAt(scrollOffset - BUFFER_PX));
5352
+ const end = Math.min(n, this.indexAt(scrollOffset + viewportSize + BUFFER_PX) + 1);
5353
+ const range = { start, end };
5354
+ viewport.setRenderedRange(range);
5355
+ viewport.setRenderedContentOffset(this.offsets[start] ?? 0);
5356
+ this.indexChange.next(this.indexAt(scrollOffset));
5357
+ }
5358
+ }
5359
+ /**
5360
+ * Wires {@link AgridVariableRowSizeStrategy} into a `cdk-virtual-scroll-viewport`.
5361
+ *
5362
+ * Apply alongside an `agridVariableRowSize` binding carrying the per-item height array. Replaces
5363
+ * the viewport's `[itemSize]` binding. The same height array is fed to every (left/body/right)
5364
+ * viewport so the panes stay row-aligned.
5365
+ * @internal
5366
+ */
5367
+ class AgridVariableRowSizeDirective {
5368
+ /** Per-item heights in pixels, one entry per virtual-scroll item. */
5369
+ itemSizes = input([], { ...(ngDevMode ? { debugName: "itemSizes" } : /* istanbul ignore next */ {}), alias: 'agridVariableRowSize' });
5370
+ strategy = new AgridVariableRowSizeStrategy();
5371
+ ngOnChanges() {
5372
+ this.strategy.updateItemSizes(this.itemSizes());
5373
+ }
5374
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridVariableRowSizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5375
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.15", type: AgridVariableRowSizeDirective, isStandalone: true, selector: "cdk-virtual-scroll-viewport[agridVariableRowSize]", inputs: { itemSizes: { classPropertyName: "itemSizes", publicName: "agridVariableRowSize", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
5376
+ {
5377
+ provide: VIRTUAL_SCROLL_STRATEGY,
5378
+ useFactory: (d) => d.strategy,
5379
+ deps: [forwardRef(() => AgridVariableRowSizeDirective)],
5380
+ },
5381
+ ], usesOnChanges: true, ngImport: i0 });
5382
+ }
5383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridVariableRowSizeDirective, decorators: [{
5384
+ type: Directive,
5385
+ args: [{
5386
+ selector: 'cdk-virtual-scroll-viewport[agridVariableRowSize]',
5387
+ providers: [
5388
+ {
5389
+ provide: VIRTUAL_SCROLL_STRATEGY,
5390
+ useFactory: (d) => d.strategy,
5391
+ deps: [forwardRef(() => AgridVariableRowSizeDirective)],
5392
+ },
5393
+ ],
5394
+ }]
5395
+ }], propDecorators: { itemSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "agridVariableRowSize", required: false }] }] } });
4271
5396
 
4272
5397
  /**
4273
5398
  * Excel-like data grid for Angular 21.
@@ -4282,10 +5407,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
4282
5407
  * |-----|--------|
4283
5408
  * | Arrow keys | Move selection |
4284
5409
  * | Tab / Shift+Tab | Move right / left (wraps rows) |
4285
- * | Enter / F2 | Enter edit mode |
5410
+ * | Enter | Enter edit mode |
5411
+ * | Ctrl/Cmd+Enter | Toggle an expandable tree node |
5412
+ * | F2 | Enter edit mode |
4286
5413
  * | Printable key | Enter edit mode with seeded character |
4287
- * | Escape | Cancel edit |
4288
- * | Tab / Enter (while editing) | Commit and move right / down |
5414
+ * | Escape | Close any open menu or cancel edit |
5415
+ * | Tab / Enter (while editing) | Commit and move according to navigation settings |
4289
5416
  */
4290
5417
  class AgridComponent {
4291
5418
  // ── Inputs ───────────────────────────────────────────────────────────────────
@@ -4298,14 +5425,18 @@ class AgridComponent {
4298
5425
  allowAddRows = computed(() => this.provider().allowAddRows, ...(ngDevMode ? [{ debugName: "allowAddRows" }] : /* istanbul ignore next */ []));
4299
5426
  autoAddRows = computed(() => this.provider().autoAddRows(), ...(ngDevMode ? [{ debugName: "autoAddRows" }] : /* istanbul ignore next */ []));
4300
5427
  enableRowMarking = computed(() => this.provider().enableRowMarking, ...(ngDevMode ? [{ debugName: "enableRowMarking" }] : /* istanbul ignore next */ []));
4301
- showControlColumn = computed(() => this.provider().showControlColumn || this.enableRowMarking(), ...(ngDevMode ? [{ debugName: "showControlColumn" }] : /* istanbul ignore next */ []));
5428
+ showControlColumn = computed(() => this.provider().showControlColumn || this.enableRowMarking() || this.masterDetail(), ...(ngDevMode ? [{ debugName: "showControlColumn" }] : /* istanbul ignore next */ []));
4302
5429
  controlColumnWidth = computed(() => this.enableRowMarking() ? 48 : 24, ...(ngDevMode ? [{ debugName: "controlColumnWidth" }] : /* istanbul ignore next */ []));
4303
5430
  showSidebar = computed(() => this.provider().showSidebar, ...(ngDevMode ? [{ debugName: "showSidebar" }] : /* istanbul ignore next */ []));
4304
5431
  autoOpenDetail = computed(() => this.provider().autoOpenDetail, ...(ngDevMode ? [{ debugName: "autoOpenDetail" }] : /* istanbul ignore next */ []));
4305
5432
  serverSideFiltering = computed(() => this.provider().serverSideFiltering, ...(ngDevMode ? [{ debugName: "serverSideFiltering" }] : /* istanbul ignore next */ []));
4306
5433
  filterDebounceMs = computed(() => this.provider().filterDebounceMs, ...(ngDevMode ? [{ debugName: "filterDebounceMs" }] : /* istanbul ignore next */ []));
5434
+ enableQuickFilter = computed(() => this.provider().enableQuickFilter, ...(ngDevMode ? [{ debugName: "enableQuickFilter" }] : /* istanbul ignore next */ []));
5435
+ menuBarItems = computed(() => this.provider().menuBarItems, ...(ngDevMode ? [{ debugName: "menuBarItems" }] : /* istanbul ignore next */ []));
5436
+ quickFilterValue = computed(() => this.control()?.quickFilter() ?? '', ...(ngDevMode ? [{ debugName: "quickFilterValue" }] : /* istanbul ignore next */ []));
4307
5437
  sortOption = computed(() => this.provider().sortOption, ...(ngDevMode ? [{ debugName: "sortOption" }] : /* istanbul ignore next */ []));
4308
5438
  rowSelection = computed(() => this.provider().rowSelection, ...(ngDevMode ? [{ debugName: "rowSelection" }] : /* istanbul ignore next */ []));
5439
+ enterEditAction = computed(() => this.provider().enterEditAction, ...(ngDevMode ? [{ debugName: "enterEditAction" }] : /* istanbul ignore next */ []));
4309
5440
  groupDescription = computed(() => this.provider().groupDescription, ...(ngDevMode ? [{ debugName: "groupDescription" }] : /* istanbul ignore next */ []));
4310
5441
  groupActions = computed(() => this.provider().groupActions, ...(ngDevMode ? [{ debugName: "groupActions" }] : /* istanbul ignore next */ []));
4311
5442
  cellMenuItems = computed(() => this.provider().cellMenuItems, ...(ngDevMode ? [{ debugName: "cellMenuItems" }] : /* istanbul ignore next */ []));
@@ -4318,10 +5449,48 @@ class AgridComponent {
4318
5449
  loading = computed(() => this.provider().loading(), ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
4319
5450
  emptyText = computed(() => this.provider().emptyText, ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
4320
5451
  useSidebarEditor = computed(() => this.provider().useSidebarEditor, ...(ngDevMode ? [{ debugName: "useSidebarEditor" }] : /* istanbul ignore next */ []));
5452
+ /** Host callback for per-row CSS classes, or `undefined`. */
5453
+ rowClassFn = computed(() => this.provider().getRowClass, ...(ngDevMode ? [{ debugName: "rowClassFn" }] : /* istanbul ignore next */ []));
5454
+ /** Host callback designating pinned rows, or `undefined`. */
5455
+ pinRowFn = computed(() => this.provider().pinRow, ...(ngDevMode ? [{ debugName: "pinRowFn" }] : /* istanbul ignore next */ []));
5456
+ /**
5457
+ * Effective pin resolver fed to the projection: a runtime UI override wins (including an explicit
5458
+ * `null` unpin), otherwise the provider `pinRow` predicate decides. Returns `undefined` when
5459
+ * neither pinning source is active, so the projection's pinning path stays off.
5460
+ */
5461
+ effectivePinRow = computed(() => {
5462
+ const predicate = this.pinRowFn();
5463
+ const overrides = this._pinnedRows();
5464
+ if (!predicate && overrides.size === 0)
5465
+ return undefined;
5466
+ return (row, index) => {
5467
+ const override = overrides.get(index);
5468
+ if (override !== undefined)
5469
+ return override ?? undefined;
5470
+ return predicate?.(row, index);
5471
+ };
5472
+ }, ...(ngDevMode ? [{ debugName: "effectivePinRow" }] : /* istanbul ignore next */ []));
5473
+ /** Whether master/detail is enabled and applicable (flat rows or tree leaves; not grouped). */
5474
+ masterDetail = computed(() => this.provider().masterDetail && !!this.provider().detailRenderer
5475
+ && !this.control()?.groupByField(), ...(ngDevMode ? [{ debugName: "masterDetail" }] : /* istanbul ignore next */ []));
5476
+ /** Fixed detail-panel height in pixels. */
5477
+ detailRowHeight = computed(() => this.provider().detailRowHeight, ...(ngDevMode ? [{ debugName: "detailRowHeight" }] : /* istanbul ignore next */ []));
4321
5478
  /** Column definitions from the active provider. */
4322
5479
  colDefs = computed(() => this.provider().columns(), ...(ngDevMode ? [{ debugName: "colDefs" }] : /* istanbul ignore next */ []));
4323
5480
  /** Signal-based data container from the active provider. */
4324
5481
  dataSource = computed(() => this.provider().datasource, ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
5482
+ treeParentIds = computed(() => {
5483
+ const config = this.treeConfig();
5484
+ if (!config || isPathTreeConfig(config))
5485
+ return new Set();
5486
+ const ids = new Set();
5487
+ for (const row of this.dataSource().rows()) {
5488
+ const parentId = config.getParentId(row);
5489
+ if (parentId !== null && parentId !== undefined)
5490
+ ids.add(parentId);
5491
+ }
5492
+ return ids;
5493
+ }, ...(ngDevMode ? [{ debugName: "treeParentIds" }] : /* istanbul ignore next */ []));
4325
5494
  /** Grid UI state container from the active provider. */
4326
5495
  control = computed(() => this.provider().control, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
4327
5496
  /** Resolved locale code used for date formatting and built-in localization lookup. 'auto' is replaced with navigator.language. */
@@ -4349,6 +5518,10 @@ class AgridComponent {
4349
5518
  rowDoubleClicked = output();
4350
5519
  /** Emitted when the user single-clicks a data row. */
4351
5520
  rowClick = output();
5521
+ /** Emitted when the user single-clicks a generated path-tree branch node. */
5522
+ treeNodeClick = output();
5523
+ /** Emitted when the user double-clicks a generated path-tree branch node. */
5524
+ treeNodeDoubleClicked = output();
4352
5525
  /**
4353
5526
  * Emitted once after a changed row is left during inline editing, or when the sidebar editor
4354
5527
  * save button is used.
@@ -4364,11 +5537,31 @@ class AgridComponent {
4364
5537
  filterChange = output();
4365
5538
  /** Emitted when a column sort changes in server-side filtering mode. */
4366
5539
  sortChange = output();
5540
+ /**
5541
+ * Emitted (debounced) when the global quick-filter text changes in server-side filtering mode.
5542
+ * The host should refetch rows matching the text. Not emitted in client mode, where the grid
5543
+ * filters locally.
5544
+ */
5545
+ quickFilterChange = output();
5546
+ /** Emitted when a `ColDef.validate` hook rejects a committed value (inline or sidebar). */
5547
+ validationFailed = output();
5548
+ /** Emitted when a column's optional cell information button is clicked. */
5549
+ cellInfo = output();
5550
+ /** Emitted for every enabled menu-bar button or dropdown item, carrying its configured id. */
5551
+ menuBarAction = output();
4367
5552
  // ── Public state ─────────────────────────────────────────────────────────────
4368
5553
  /** Currently focused cell, or `null`. */
4369
5554
  selectedCell = signal(null, ...(ngDevMode ? [{ debugName: "selectedCell" }] : /* istanbul ignore next */ []));
4370
5555
  /** Original index of the row awaiting delete confirmation, or `null`. */
4371
5556
  pendingDeleteRow = signal(null, ...(ngDevMode ? [{ debugName: "pendingDeleteRow" }] : /* istanbul ignore next */ []));
5557
+ /** Original indices of rows whose master/detail panel is currently expanded. */
5558
+ _expandedDetailIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "_expandedDetailIds" }] : /* istanbul ignore next */ []));
5559
+ /**
5560
+ * Runtime per-row pin overrides set through the UI (keyed by original index). A `null` value
5561
+ * explicitly unpins a row that the `pinRow` predicate would otherwise pin. Merged with the
5562
+ * provider predicate by {@link effectivePinRow}.
5563
+ */
5564
+ _pinnedRows = signal(new Map(), ...(ngDevMode ? [{ debugName: "_pinnedRows" }] : /* istanbul ignore next */ []));
4372
5565
  markedIndices = signal(new Set(), ...(ngDevMode ? [{ debugName: "markedIndices" }] : /* istanbul ignore next */ []));
4373
5566
  /** Original datasource indices marked for inclusion in copy operations. */
4374
5567
  markedRowIndices = this.markedIndices.asReadonly();
@@ -4378,6 +5571,8 @@ class AgridComponent {
4378
5571
  deleteConfirmationWidth = signal(0, ...(ngDevMode ? [{ debugName: "deleteConfirmationWidth" }] : /* istanbul ignore next */ []));
4379
5572
  /** Rectangular cell range selected by Shift+arrow or Shift+click. */
4380
5573
  selectedRange = signal(null, ...(ngDevMode ? [{ debugName: "selectedRange" }] : /* istanbul ignore next */ []));
5574
+ /** @internal Stable callback passed to child components for row-aware editability checks. */
5575
+ isCellEditableForRow = (col, originalIndex) => this.isCellEditable(col, originalIndex);
4381
5576
  /** Fill-handle drag preview bounds, in visible row/column coordinates. */
4382
5577
  get fillPreviewBounds() { return this.rangeController.fillPreviewBounds; }
4383
5578
  /** Position of the cell in edit mode, or `null`. */
@@ -4386,6 +5581,8 @@ class AgridComponent {
4386
5581
  get currentDraft() { return this.editController.currentDraft; }
4387
5582
  /** Seed character typed to enter edit mode (e.g. pressing 'A'). */
4388
5583
  get editSeedChar() { return this.editController.editSeedChar; }
5584
+ /** Whether the active text editor should select all text when it opens. */
5585
+ get selectTextOnEdit() { return this.editController.selectTextOnEdit; }
4389
5586
  /** Toggle the sidebar open/closed. */
4390
5587
  toggleSidebar() { this.sidebarController.toggle(); }
4391
5588
  /** @internal */
@@ -4532,6 +5729,9 @@ class AgridComponent {
4532
5729
  expandedGroups: this.groupController.expandedGroups,
4533
5730
  treeConfig: this.treeConfig,
4534
5731
  expandedTreeIds: this.treeController.expandedIds,
5732
+ pinRow: this.effectivePinRow,
5733
+ masterDetail: this.masterDetail,
5734
+ expandedDetailIds: this._expandedDetailIds,
4535
5735
  });
4536
5736
  editController = new AgridEditController({
4537
5737
  control: this.control,
@@ -4544,6 +5744,7 @@ class AgridComponent {
4544
5744
  scrollToCell: (displayIndex, colIndex) => this.scrollToKeepVisible(displayIndex, colIndex),
4545
5745
  focusGrid: () => this.wrapperEl().nativeElement.focus(),
4546
5746
  onCellEdit: event => this.emitEditEvents(event),
5747
+ onValidationFailed: event => this.validationFailed.emit({ ...event, source: 'inline' }),
4547
5748
  });
4548
5749
  /** Total filtered row count regardless of current page. */
4549
5750
  filteredRowCount = this.projection.filteredRowCount;
@@ -4552,8 +5753,9 @@ class AgridComponent {
4552
5753
  showPagination = this.projection.showPagination;
4553
5754
  /** Number of semantic header rows currently rendered. */
4554
5755
  headerRowCount = computed(() => this.hasHeaderGroups() ? 2 : 1, ...(ngDevMode ? [{ debugName: "headerRowCount" }] : /* istanbul ignore next */ []));
4555
- /** Number of rendered semantic rows, including header rows. */
4556
- ariaRowCount = computed(() => this.displayItems().length + this.headerRowCount() + (this.showFooter() ? 1 : 0), ...(ngDevMode ? [{ debugName: "ariaRowCount" }] : /* istanbul ignore next */ []));
5756
+ /** Number of rendered semantic rows, including header and pinned rows. */
5757
+ ariaRowCount = computed(() => this.displayItems().length + this.headerRowCount() + (this.showFooter() ? 1 : 0)
5758
+ + this.pinnedTopItems().length + this.pinnedBottomItems().length, ...(ngDevMode ? [{ debugName: "ariaRowCount" }] : /* istanbul ignore next */ []));
4557
5759
  /** Number of visible semantic columns, including the optional control column. */
4558
5760
  ariaColCount = computed(() => this.visibleColDefs().length + (this.showControlColumn() ? 1 : 0), ...(ngDevMode ? [{ debugName: "ariaColCount" }] : /* istanbul ignore next */ []));
4559
5761
  /** True when no rows or group headers are visible (ignores add-row sentinel and ghost). */
@@ -4615,6 +5817,24 @@ class AgridComponent {
4615
5817
  result.splice(insertAt, 0, 'ghost');
4616
5818
  return result;
4617
5819
  }, ...(ngDevMode ? [{ debugName: "displayItems" }] : /* istanbul ignore next */ []));
5820
+ /** Rows pinned to the top of the body (rendered in a fixed container, outside virtual scroll). */
5821
+ pinnedTopItems = this.projection.pinnedTopItems;
5822
+ /** Rows pinned to the bottom of the body (rendered in a fixed container, outside virtual scroll). */
5823
+ pinnedBottomItems = this.projection.pinnedBottomItems;
5824
+ /** Whether any top-pinned rows are present. */
5825
+ hasPinnedTopRows = computed(() => this.pinnedTopItems().length > 0, ...(ngDevMode ? [{ debugName: "hasPinnedTopRows" }] : /* istanbul ignore next */ []));
5826
+ /** Whether any bottom-pinned rows are present. */
5827
+ hasPinnedBottomRows = computed(() => this.pinnedBottomItems().length > 0, ...(ngDevMode ? [{ debugName: "hasPinnedBottomRows" }] : /* istanbul ignore next */ []));
5828
+ /**
5829
+ * Per-item heights fed to the variable-size virtual-scroll strategy: a detail panel uses the
5830
+ * configured detail height, every other row uses the standard row height. With no detail rows
5831
+ * open the array is uniform, so scrolling matches the fixed-size strategy.
5832
+ */
5833
+ itemSizes = computed(() => {
5834
+ const h = this.rowHeight();
5835
+ const dh = this.detailRowHeight();
5836
+ return this.displayItems().map(item => (isDetailRowItem(item) ? dh : h));
5837
+ }, ...(ngDevMode ? [{ debugName: "itemSizes" }] : /* istanbul ignore next */ []));
4618
5838
  // ── Menu signals ─────────────────────────────────────────────────────────────
4619
5839
  groupActionsMenu = this.groupController.actionsMenu;
4620
5840
  // ── Infrastructure ────────────────────────────────────────────────────────────
@@ -4633,7 +5853,7 @@ class AgridComponent {
4633
5853
  visibleColDefs: this.visibleColDefs,
4634
5854
  selectedCell: this.selectedCell,
4635
5855
  selectedRange: this.selectedRange,
4636
- isCellEditable: col => this.isCellEditable(col),
5856
+ isCellEditable: (col, originalIndex) => this.isCellEditable(col, originalIndex),
4637
5857
  cancelEdit: () => this.cancelCurrent(),
4638
5858
  findDisplayIndex: originalIndex => this.findDisplayIndex(originalIndex),
4639
5859
  scrollToCell: (displayIndex, colIndex) => this.scrollToKeepVisible(displayIndex, colIndex),
@@ -4672,14 +5892,16 @@ class AgridComponent {
4672
5892
  visibleColDefs: this.visibleColDefs,
4673
5893
  filteredItems: this.filteredItems,
4674
5894
  locale: this.locale,
5895
+ getRowClass: this.rowClassFn,
4675
5896
  });
4676
5897
  findController = new AgridFindController({
4677
- filteredItems: this.filteredItems,
5898
+ dataSource: this.dataSource,
5899
+ filteredSortedIndices: this.projection.filteredSortedIndices,
4678
5900
  visibleColDefs: this.visibleColDefs,
4679
5901
  locale: this.locale,
4680
5902
  selectedCell: this.selectedCell,
4681
5903
  selectedRange: this.selectedRange,
4682
- scrollToCell: (displayIndex, colIndex) => this.navigationController.scrollToKeepVisible(displayIndex, colIndex),
5904
+ revealMatch: (originalIndex, colIndex) => this.revealFindMatch(originalIndex, colIndex),
4683
5905
  focusGrid: () => this.wrapperEl().nativeElement.focus(),
4684
5906
  });
4685
5907
  findOpen = this.findController.open;
@@ -4696,11 +5918,14 @@ class AgridComponent {
4696
5918
  rowHeight: this.rowHeight,
4697
5919
  allowAddRows: this.allowAddRows,
4698
5920
  autoAddRows: this.autoAddRows,
5921
+ enterEditAction: this.enterEditAction,
4699
5922
  selectedCell: this.selectedCell,
4700
5923
  selectedRange: this.selectedRange,
4701
5924
  editingCell: this.editController.editingCell,
4702
5925
  isEditing: (originalIndex, colIndex) => this.isEditing(originalIndex, colIndex),
4703
- startEdit: (originalIndex, colIndex, seedChar) => this.editController.start(originalIndex, colIndex, seedChar),
5926
+ isCellEditable: (col, originalIndex) => this.editController.isCellEditable(col, originalIndex),
5927
+ toggleTreeCell: (originalIndex, colIndex) => this.toggleTreeCell(originalIndex, colIndex),
5928
+ startEdit: (originalIndex, colIndex, seedChar, selectText) => this.editController.start(originalIndex, colIndex, seedChar, selectText),
4704
5929
  commitEdit: () => this.editController.commit(),
4705
5930
  cancelEdit: () => this.editController.cancel(),
4706
5931
  undoEdit: () => this.editController.undo(),
@@ -4745,6 +5970,26 @@ class AgridComponent {
4745
5970
  selectedRowIndex = this.rowController.selectedRowIndex;
4746
5971
  contextMenu = this.rowController.contextMenu;
4747
5972
  cellContextMenuState = this.rowController.cellContextMenu;
5973
+ /** Id of the menu-bar button whose dropdown is open, or `null`. */
5974
+ openMenuBarItemId = signal(null, ...(ngDevMode ? [{ debugName: "openMenuBarItemId" }] : /* istanbul ignore next */ []));
5975
+ /** Runtime state passed to menu-bar visibility, active, and disabled resolvers. */
5976
+ menuBarContext = computed(() => {
5977
+ const datasource = this.dataSource();
5978
+ const rows = datasource.rows();
5979
+ const selectedRows = [...this.selectedRowIndices()]
5980
+ .sort((a, b) => a - b)
5981
+ .map(originalIndex => ({ row: rows[originalIndex], originalIndex }))
5982
+ .filter((entry) => !!entry.row);
5983
+ return {
5984
+ rows,
5985
+ selectedRows,
5986
+ selectedCell: this.selectedCell(),
5987
+ provider: this.provider(),
5988
+ datasource,
5989
+ };
5990
+ }, ...(ngDevMode ? [{ debugName: "menuBarContext" }] : /* istanbul ignore next */ []));
5991
+ /** Menu-bar buttons currently allowed by their visibility resolvers. */
5992
+ visibleMenuBarItems = computed(() => this.menuBarItems().filter(item => this.isMenuBarItemVisible(item)), ...(ngDevMode ? [{ debugName: "visibleMenuBarItems" }] : /* istanbul ignore next */ []));
4748
5993
  sidebarController = new AgridSidebarController({
4749
5994
  control: this.control,
4750
5995
  dataSource: this.dataSource,
@@ -4753,10 +5998,14 @@ class AgridComponent {
4753
5998
  selectedRowIndex: this.selectedRowIndex,
4754
5999
  autoOpenDetail: this.autoOpenDetail,
4755
6000
  useSidebarEditor: this.useSidebarEditor,
6001
+ isCellEditable: (col, originalIndex) => this.isCellEditable(col, originalIndex),
4756
6002
  onFieldChange: event => this.markCellChanged(event),
4757
6003
  onCellEdit: event => this.emitSidebarEditEvents(event),
6004
+ onValidationFailed: event => this.validationFailed.emit({ ...event, source: 'sidebar' }),
4758
6005
  });
4759
6006
  sidebarOpen = this.sidebarController.open;
6007
+ /** @internal Per-field sidebar validation messages. */
6008
+ sidebarValidationErrors = this.sidebarController.validationErrors;
4760
6009
  sidebarTab = this.sidebarController.tab;
4761
6010
  sidebarRow = this.sidebarController.row;
4762
6011
  sidebarHiddenColumns = this.sidebarController.hiddenColumns;
@@ -4769,7 +6018,7 @@ class AgridComponent {
4769
6018
  selectedCell: this.selectedCell,
4770
6019
  selectedRange: this.selectedRange,
4771
6020
  markedRowIndices: this.markedRowIndices,
4772
- isCellEditable: col => this.isCellEditable(col),
6021
+ isCellEditable: (col, originalIndex) => this.isCellEditable(col, originalIndex),
4773
6022
  onCellEdit: event => this.emitEditEvents(event),
4774
6023
  scrollToCell: (displayIndex, colIndex) => this.scrollToKeepVisible(displayIndex, colIndex),
4775
6024
  });
@@ -4818,6 +6067,10 @@ class AgridComponent {
4818
6067
  getColReorderOffset(field) {
4819
6068
  return this.columnReorder.getHeaderOffset(field);
4820
6069
  }
6070
+ hasContextMenuEntries() {
6071
+ const hme = (!this.readonlyGrid() && !this.treeConfig());
6072
+ return hme;
6073
+ }
4821
6074
  // ── Setup ─────────────────────────────────────────────────────────────────────
4822
6075
  _seededControls = new WeakSet();
4823
6076
  dirtyInlineRows = new Set();
@@ -4942,6 +6195,15 @@ class AgridComponent {
4942
6195
  wrapper.addEventListener('keydown', onKeyDown, { capture: true });
4943
6196
  this.destroyRef.onDestroy(() => wrapper.removeEventListener('keydown', onKeyDown, { capture: true }));
4944
6197
  });
6198
+ const onDocumentKeyDown = (event) => {
6199
+ if (event.key !== 'Escape' || event.defaultPrevented)
6200
+ return;
6201
+ if (this.closeOpenMenus()) {
6202
+ event.preventDefault();
6203
+ event.stopPropagation();
6204
+ }
6205
+ };
6206
+ this.browser.addDocumentListener('keydown', onDocumentKeyDown);
4945
6207
  // Emit pageChange whenever page or pageSize changes in server-side pagination mode.
4946
6208
  effect(() => {
4947
6209
  const ctrl = this.control();
@@ -4962,20 +6224,25 @@ class AgridComponent {
4962
6224
  });
4963
6225
  // Deselect when clicking outside the grid.
4964
6226
  const onOutsidePointerDown = (e) => {
4965
- if (!this._hostEl.nativeElement.contains(e.target)) {
6227
+ const isInsideGrid = this._hostEl.nativeElement.contains(e.target);
6228
+ if (!isInsideGrid) {
6229
+ this.closeOpenMenus();
4966
6230
  queueMicrotask(() => this.flushDirtyInlineRows());
4967
6231
  }
4968
6232
  if (this.rowSelection() === 'none')
4969
6233
  return;
4970
6234
  if (this.selectedRowIndices().size === 0)
4971
6235
  return;
4972
- if (this._hostEl.nativeElement.contains(e.target))
6236
+ if (isInsideGrid)
4973
6237
  return;
4974
6238
  this.rowController.clearSelection();
4975
6239
  };
4976
6240
  this.browser.addDocumentListener('pointerdown', onOutsidePointerDown);
4977
6241
  this.destroyRef.onDestroy(() => {
6242
+ this.browser.removeDocumentListener('keydown', onDocumentKeyDown);
4978
6243
  this.browser.removeDocumentListener('pointerdown', onOutsidePointerDown);
6244
+ if (this.quickFilterTimer !== null)
6245
+ clearTimeout(this.quickFilterTimer);
4979
6246
  });
4980
6247
  // Re-sync pinned pane scroll after displayItems changes — CDK independently adjusts
4981
6248
  // each viewport when item count changes (group/ungroup, collapse), which can leave
@@ -5019,9 +6286,94 @@ class AgridComponent {
5019
6286
  return isGroupHeaderItem(item);
5020
6287
  }
5021
6288
  /** @internal */
6289
+ isPathTreeNodeItem(item) {
6290
+ return isPathTreeNodeItem(item);
6291
+ }
6292
+ /** @internal */
5022
6293
  getItemOriginalIndex(item) {
5023
6294
  return isDataRowItem(item) ? item.originalIndex : null;
5024
6295
  }
6296
+ /** @internal True when the item is a master/detail panel row. */
6297
+ isDetailRowItem(item) {
6298
+ return isDetailRowItem(item);
6299
+ }
6300
+ /** @internal Rendered pixel height of a virtual-scroll item (detail panels are taller). */
6301
+ rowPx(item) {
6302
+ return isDetailRowItem(item) ? this.detailRowHeight() : this.rowHeight();
6303
+ }
6304
+ /** @internal Resolved HTML for an expanded detail panel (auto-sanitized by `[innerHTML]`). */
6305
+ detailHtml(item) {
6306
+ if (!isDetailRowItem(item))
6307
+ return '';
6308
+ return this.provider().detailRenderer?.({ row: item.row }) ?? '';
6309
+ }
6310
+ /** @internal Resolved per-row CSS classes from the host `getRowClass` callback. */
6311
+ getRowClass(row, index) {
6312
+ return this.presentation.getRowClass(row, index);
6313
+ }
6314
+ /** Whether the master/detail panel for `originalIndex` is currently expanded. */
6315
+ isDetailExpanded(originalIndex) {
6316
+ return this._expandedDetailIds().has(originalIndex);
6317
+ }
6318
+ /** @internal Whether a data row may show a master/detail panel. */
6319
+ canToggleDetail(item) {
6320
+ if (!this.masterDetail() || !isDataRowItem(item))
6321
+ return false;
6322
+ const config = this.treeConfig();
6323
+ return !config
6324
+ || isPathTreeConfig(config)
6325
+ || !this.treeParentIds().has(config.getId(item.row));
6326
+ }
6327
+ /** Toggle the master/detail panel for a row by its original (data-source) index. */
6328
+ toggleDetail(originalIndex) {
6329
+ const row = this.dataSource().getRow(originalIndex);
6330
+ const config = this.treeConfig();
6331
+ if (!row
6332
+ || (config
6333
+ && !isPathTreeConfig(config)
6334
+ && this.treeParentIds().has(config.getId(row))))
6335
+ return;
6336
+ this._expandedDetailIds.update(ids => {
6337
+ const next = new Set(ids);
6338
+ if (next.has(originalIndex))
6339
+ next.delete(originalIndex);
6340
+ else
6341
+ next.add(originalIndex);
6342
+ return next;
6343
+ });
6344
+ }
6345
+ /** @internal Template handler for the detail expander chevron. */
6346
+ onDetailToggle(originalIndex) {
6347
+ this.toggleDetail(originalIndex);
6348
+ }
6349
+ /** Effective pin position of a row (`'top'`, `'bottom'`, or `undefined`). */
6350
+ rowPinState(originalIndex) {
6351
+ const resolver = this.effectivePinRow();
6352
+ if (!resolver)
6353
+ return undefined;
6354
+ return resolver(this.dataSource().rows()[originalIndex], originalIndex);
6355
+ }
6356
+ /**
6357
+ * Pin a row to the top or bottom of the body, or unpin it with `null`.
6358
+ * Keyed by the row's original (data-source) index; the pinned row stays fully interactive.
6359
+ */
6360
+ pinRowTo(originalIndex, position) {
6361
+ this._pinnedRows.update(map => {
6362
+ const next = new Map(map);
6363
+ // When no predicate could re-pin the row, an unpin can simply drop the override.
6364
+ if (position === null && !this.pinRowFn())
6365
+ next.delete(originalIndex);
6366
+ else
6367
+ next.set(originalIndex, position);
6368
+ return next;
6369
+ });
6370
+ }
6371
+ /** @internal Template handler for the pin/unpin context-menu items; closes the open menus. */
6372
+ onPinRow(originalIndex, position) {
6373
+ this.pinRowTo(originalIndex, position);
6374
+ this.rowController.closeContextMenu();
6375
+ this.rowController.closeCellContextMenu();
6376
+ }
5025
6377
  // ── Template helpers — tree ───────────────────────────────────────────────────
5026
6378
  /** @internal True when `col` is the configured tree column. */
5027
6379
  isTreeCell(col) {
@@ -5029,28 +6381,101 @@ class AgridComponent {
5029
6381
  }
5030
6382
  /** @internal Tree depth of a row item (0 when not a tree row). */
5031
6383
  treeRowLevel(item) {
5032
- return isTreeRowItem(item) ? item.level : 0;
6384
+ return isTreeRowItem(item) || isPathTreeNodeItem(item) ? item.level : 0;
5033
6385
  }
5034
6386
  /** @internal Whether a tree row has children and can be expanded. */
5035
6387
  treeRowExpandable(item) {
5036
- return isTreeRowItem(item) && item.expandable;
6388
+ return (isTreeRowItem(item) || isPathTreeNodeItem(item)) && item.expandable;
5037
6389
  }
5038
6390
  /** @internal Whether a tree row is currently expanded. */
5039
6391
  treeRowExpanded(item) {
5040
- return isTreeRowItem(item) && item.expanded;
6392
+ return (isTreeRowItem(item) || isPathTreeNodeItem(item)) && item.expanded;
6393
+ }
6394
+ /** @internal Display-only final path segment for a datasource-backed path-tree leaf. */
6395
+ treeCellDisplayOverride(item, col) {
6396
+ return isTreeRowItem(item) && this.isTreeCell(col) ? item.treeLabel ?? null : null;
6397
+ }
6398
+ /** @internal Whether the configured info action is visible for this cell. */
6399
+ showCellInfoIcon(col, row) {
6400
+ return typeof col.infoIcon === 'function'
6401
+ ? col.infoIcon({ value: row[col.field], row })
6402
+ : col.infoIcon === true;
6403
+ }
6404
+ /** @internal Emits the typed cell information action. */
6405
+ onCellInfo(originalIndex, col, row) {
6406
+ this.cellInfo.emit({
6407
+ row: row,
6408
+ field: col.field,
6409
+ value: row[col.field],
6410
+ originalIndex,
6411
+ column: col,
6412
+ });
6413
+ }
6414
+ /** @internal Label of a generated path-tree branch. */
6415
+ pathTreeLabel(item) {
6416
+ return isPathTreeNodeItem(item) ? item.pathLabel : '';
5041
6417
  }
5042
6418
  /** @internal Toggle the expand/collapse state of a tree row from its twisty. */
5043
6419
  onTreeToggle(item) {
5044
6420
  const config = this.treeConfig();
5045
- if (!config || !isTreeRowItem(item))
6421
+ if (!config)
5046
6422
  return;
6423
+ if (isPathTreeNodeItem(item)) {
6424
+ this.treeController.toggle(item.pathNodeId);
6425
+ }
6426
+ else if (isTreeRowItem(item) && !isPathTreeConfig(config)) {
6427
+ this.treeController.toggle(config.getId(item.row));
6428
+ }
6429
+ }
6430
+ /** @internal Emits the generated path-tree branch click event. */
6431
+ onTreeNodeClick(item) {
6432
+ if (!isPathTreeNodeItem(item))
6433
+ return;
6434
+ this.treeNodeClick.emit(this.toTreeNodeClickEvent(item));
6435
+ }
6436
+ /** @internal Emits the generated path-tree branch double-click event. */
6437
+ onTreeNodeDoubleClick(item) {
6438
+ if (!isPathTreeNodeItem(item))
6439
+ return;
6440
+ this.treeNodeDoubleClicked.emit(this.toTreeNodeClickEvent(item));
6441
+ }
6442
+ toTreeNodeClickEvent(item) {
6443
+ return {
6444
+ uuid: item.uuid,
6445
+ pathNodeId: item.pathNodeId,
6446
+ pathLabel: item.pathLabel,
6447
+ level: item.level,
6448
+ expanded: item.expanded,
6449
+ node: { ...item },
6450
+ };
6451
+ }
6452
+ toggleTreeCell(originalIndex, colIndex) {
6453
+ const config = this.treeConfig();
6454
+ const col = this.visibleColDefs()[colIndex];
6455
+ if (!config || !col || config.treeField !== col.field)
6456
+ return false;
6457
+ const item = this.displayItems().find(candidate => isDataRowItem(candidate) && candidate.originalIndex === originalIndex);
6458
+ if (!item || !isTreeRowItem(item) || !item.expandable || isPathTreeConfig(config))
6459
+ return false;
5047
6460
  this.treeController.toggle(config.getId(item.row));
6461
+ return true;
5048
6462
  }
5049
6463
  /** Expand every expandable node in the tree. No-op when not in tree mode. */
5050
6464
  expandAllNodes() {
5051
6465
  const config = this.treeConfig();
5052
6466
  if (!config)
5053
6467
  return;
6468
+ if (isPathTreeConfig(config)) {
6469
+ const ids = new Set();
6470
+ for (const row of this.dataSource().rows()) {
6471
+ const path = config.getPath(row).map(String).filter(Boolean);
6472
+ for (let length = 1; length < path.length; length++) {
6473
+ ids.add(pathTreeNodeId(path.slice(0, length)));
6474
+ }
6475
+ }
6476
+ this.treeController.expandAll(ids);
6477
+ return;
6478
+ }
5054
6479
  const expandable = new Set();
5055
6480
  for (const row of this.dataSource().rows()) {
5056
6481
  const parentId = config.getParentId(row);
@@ -5071,6 +6496,10 @@ class AgridComponent {
5071
6496
  return -1;
5072
6497
  if (isGroupHeaderItem(item))
5073
6498
  return `__group__${item.groupLabel}`;
6499
+ if (isDetailRowItem(item))
6500
+ return `__detail__${item.detailFor}`;
6501
+ if (isPathTreeNodeItem(item))
6502
+ return item.pathNodeId;
5074
6503
  return item.originalIndex;
5075
6504
  };
5076
6505
  // ── Template helpers — cell/selection state ───────────────────────────────────
@@ -5110,6 +6539,50 @@ class AgridComponent {
5110
6539
  return this.columnMenuController.hasMultiSort();
5111
6540
  }
5112
6541
  getTextFilter(field) { return this.columnMenuController.getTextFilter(field); }
6542
+ /** @internal Condition input type for a column, or `null` when unsupported. */
6543
+ getMenuFilterType(field) {
6544
+ return this.columnMenuController.getFilterType(field);
6545
+ }
6546
+ /** @internal Short label for an active header condition. */
6547
+ getConditionButtonLabel(field) {
6548
+ switch (this.getMenuOperator(field)) {
6549
+ case 'eq': return '=';
6550
+ case 'neq': return '≠';
6551
+ case 'gt': return '>';
6552
+ case 'gte': return '≥';
6553
+ case 'lt': return '<';
6554
+ case 'lte': return '≤';
6555
+ case 'between': return '↔';
6556
+ case 'like': return '~';
6557
+ case 'startsWith': return 'A…';
6558
+ case 'endsWith': return '…Z';
6559
+ case 'includes': return '⊃';
6560
+ case 'notIncludes': return '⊅';
6561
+ default: return '⋯';
6562
+ }
6563
+ }
6564
+ /** @internal */
6565
+ getMenuOperator(field) {
6566
+ return this.columnMenuController.getFilterOperator(field);
6567
+ }
6568
+ /** @internal */
6569
+ getMenuOperand(field) { return this.columnMenuController.getFilterOperand(field); }
6570
+ /** @internal */
6571
+ getMenuOperand2(field) {
6572
+ return this.columnMenuController.getFilterOperand2(field);
6573
+ }
6574
+ /** @internal */
6575
+ onMenuOperatorChange(field, operator) {
6576
+ this.columnMenuController.setFilterOperator(field, operator);
6577
+ }
6578
+ /** @internal */
6579
+ onMenuOperandChange(field, value) {
6580
+ this.columnMenuController.setFilterOperand(field, value);
6581
+ }
6582
+ /** @internal */
6583
+ onMenuOperand2Change(field, value) {
6584
+ this.columnMenuController.setFilterOperand2(field, value);
6585
+ }
5113
6586
  /** @internal */
5114
6587
  getSort(field) {
5115
6588
  return this.columnMenuController.getSort(field);
@@ -5190,12 +6663,52 @@ class AgridComponent {
5190
6663
  }
5191
6664
  /** @internal */
5192
6665
  onDraftChange(value) { this.editController.setDraft(value); }
6666
+ quickFilterTimer = null;
6667
+ /**
6668
+ * @internal Quick-filter input handler. Stores the text on the control (drives the bound value
6669
+ * and client-side filtering) and, in server mode, emits a debounced `quickFilterChange` instead.
6670
+ */
6671
+ onQuickFilterInput(event) {
6672
+ const text = event.target.value;
6673
+ this.control()?.setQuickFilter(text);
6674
+ if (!this.serverSideFiltering())
6675
+ return;
6676
+ if (this.quickFilterTimer !== null)
6677
+ clearTimeout(this.quickFilterTimer);
6678
+ const delay = this.filterDebounceMs();
6679
+ if (delay === 0) {
6680
+ this.quickFilterChange.emit(text);
6681
+ return;
6682
+ }
6683
+ this.quickFilterTimer = setTimeout(() => {
6684
+ this.quickFilterTimer = null;
6685
+ this.quickFilterChange.emit(text);
6686
+ }, delay);
6687
+ }
6688
+ /** @internal Whether a column is editable in the current grid state (drives boolean checkboxes). */
6689
+ isColEditable(col, originalIndex) {
6690
+ return this.editController.isCellEditable(col, originalIndex);
6691
+ }
6692
+ /** @internal Inline validation message for a cell, or `null` when the cell has no active error. */
6693
+ cellValidationError(originalIndex, ci) {
6694
+ const error = this.editController.validationError();
6695
+ return error && error.rowIndex === originalIndex && error.colIndex === ci ? error.message : null;
6696
+ }
6697
+ /** @internal Commit a boolean-column checkbox toggle directly to the data source. */
6698
+ onBooleanToggle(originalIndex, ci, value) {
6699
+ this.editController.setCellValue(originalIndex, ci, value);
6700
+ }
5193
6701
  /** @internal Starts a fill-handle drag from the bottom-right corner of the selection. */
5194
6702
  onCellPointerDown(event, originalIndex, colIndex) {
5195
6703
  this.rangeController.startFill(event, originalIndex, colIndex);
5196
6704
  }
5197
6705
  /** @internal Main keyboard handler delegated from the wrapper div. */
5198
6706
  onKeyDown(event) {
6707
+ if (event.key === 'Escape' && this.closeOpenMenus()) {
6708
+ event.preventDefault();
6709
+ event.stopPropagation();
6710
+ return;
6711
+ }
5199
6712
  if (event.key === 'Escape' && this.pendingDeleteRow() !== null) {
5200
6713
  event.preventDefault();
5201
6714
  event.stopPropagation();
@@ -5226,6 +6739,47 @@ class AgridComponent {
5226
6739
  goToFindMatch(direction) {
5227
6740
  this.findController.goToMatch(direction);
5228
6741
  }
6742
+ revealFindMatch(originalIndex, colIndex) {
6743
+ const config = this.treeConfig();
6744
+ if (config) {
6745
+ const rows = this.dataSource().rows();
6746
+ const expanded = new Set(this.treeController.expandedIds());
6747
+ if (isPathTreeConfig(config)) {
6748
+ const path = config.getPath(rows[originalIndex]).map(String).filter(Boolean);
6749
+ for (let length = 1; length < path.length; length++) {
6750
+ expanded.add(pathTreeNodeId(path.slice(0, length)));
6751
+ }
6752
+ }
6753
+ else {
6754
+ const idToRow = new Map(rows.map(row => [config.getId(row), row]));
6755
+ const visited = new Set();
6756
+ let parentId = config.getParentId(rows[originalIndex]);
6757
+ while (parentId !== null && parentId !== undefined && !visited.has(parentId)) {
6758
+ visited.add(parentId);
6759
+ expanded.add(parentId);
6760
+ const parent = idToRow.get(parentId);
6761
+ if (!parent)
6762
+ break;
6763
+ parentId = config.getParentId(parent);
6764
+ }
6765
+ }
6766
+ this.treeController.expandAll(expanded);
6767
+ }
6768
+ else {
6769
+ const control = this.control();
6770
+ const pageSize = control?.pageSize() ?? 0;
6771
+ const filteredIndex = this.projection.filteredSortedIndices().indexOf(originalIndex);
6772
+ if (control && pageSize > 0 && control.totalRows() === 0 && filteredIndex >= 0) {
6773
+ control.setPage(Math.floor(filteredIndex / pageSize) + 1);
6774
+ }
6775
+ }
6776
+ setTimeout(() => {
6777
+ const displayIndex = this.navigationController.findDisplayIndex(originalIndex);
6778
+ if (displayIndex >= 0) {
6779
+ this.navigationController.scrollToKeepVisible(displayIndex, colIndex);
6780
+ }
6781
+ });
6782
+ }
5229
6783
  // ── Row reorder ───────────────────────────────────────────────────────────────
5230
6784
  /** @internal Ghost cell display during a reorder drag. */
5231
6785
  getGhostCellDisplay(col) { return this.dragHandler.getGhostDisplay(col); }
@@ -5238,7 +6792,7 @@ class AgridComponent {
5238
6792
  /** @internal Handles the control column without letting the row receive a second pointer event. */
5239
6793
  onControlPointerDown(event, originalIndex) {
5240
6794
  event.stopPropagation();
5241
- if (this.allowRowReorder()) {
6795
+ if (this.allowRowReorder() && event.pointerType === 'mouse' && event.button === 0) {
5242
6796
  this.onHandlePointerDown(event, originalIndex);
5243
6797
  return;
5244
6798
  }
@@ -5306,6 +6860,121 @@ class AgridComponent {
5306
6860
  }
5307
6861
  /** @internal */
5308
6862
  closeCellContextMenu() { this.rowController.closeCellContextMenu(); }
6863
+ /** @internal Closes any row, cell, menu-bar, group-action, or column menu owned by this grid. */
6864
+ closeOpenMenus() {
6865
+ const hadOpenMenu = this.contextMenu() !== null
6866
+ || this.cellContextMenuState() !== null
6867
+ || this.openMenuBarItemId() !== null
6868
+ || this.groupActionsMenu() !== null
6869
+ || this.filterMenu() !== null;
6870
+ if (!hadOpenMenu)
6871
+ return false;
6872
+ this.rowController.closeContextMenu();
6873
+ this.rowController.closeCellContextMenu();
6874
+ this.closeMenuBarMenu();
6875
+ this.groupController.closeActionsMenu();
6876
+ this.columnMenuController.close();
6877
+ return true;
6878
+ }
6879
+ /** @internal Resolves a menu-bar state callback against the current grid state. */
6880
+ resolveMenuBarState(state, fallback) {
6881
+ if (typeof state === 'function')
6882
+ return state(this.menuBarContext());
6883
+ return state ?? fallback;
6884
+ }
6885
+ /** @internal Whether a menu-bar button or dropdown item should be rendered. */
6886
+ isMenuBarItemVisible(item) {
6887
+ return this.resolveMenuBarState(item.visible, true);
6888
+ }
6889
+ /** @internal Whether a menu-bar button or dropdown item is active. */
6890
+ isMenuBarItemActive(item) {
6891
+ return this.resolveMenuBarState(item.active, false);
6892
+ }
6893
+ /** @internal Whether a menu-bar button or dropdown item is disabled. */
6894
+ isMenuBarItemDisabled(item) {
6895
+ return this.resolveMenuBarState(item.disabled, false);
6896
+ }
6897
+ /** @internal Visible dropdown entries for a menu-bar button. */
6898
+ visibleMenuBarChildren(item) {
6899
+ return (item.items ?? []).filter(child => this.isMenuBarItemVisible(child));
6900
+ }
6901
+ /** @internal Emits one menu-bar action and closes its dropdown. */
6902
+ runMenuBarAction(event, item) {
6903
+ event.stopPropagation();
6904
+ if (!this.isMenuBarItemVisible(item) || this.isMenuBarItemDisabled(item))
6905
+ return;
6906
+ this.menuBarAction.emit(item.id);
6907
+ this.closeMenuBarMenu();
6908
+ }
6909
+ /** @internal Opens or closes a split button's additional command menu. */
6910
+ toggleMenuBarMenu(event, item) {
6911
+ event.preventDefault();
6912
+ event.stopPropagation();
6913
+ if (this.visibleMenuBarChildren(item).length === 0)
6914
+ return;
6915
+ this.rowController.closeContextMenu();
6916
+ this.rowController.closeCellContextMenu();
6917
+ this.groupController.closeActionsMenu();
6918
+ this.columnMenuController.close();
6919
+ this.openMenuBarItemId.update(id => id === item.id ? null : item.id);
6920
+ }
6921
+ /** @internal Opens a dropdown from the keyboard and focuses its first/last enabled item. */
6922
+ onMenuBarTriggerKeydown(event, item) {
6923
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')
6924
+ return;
6925
+ event.preventDefault();
6926
+ event.stopPropagation();
6927
+ if (this.visibleMenuBarChildren(item).length === 0)
6928
+ return;
6929
+ this.openMenuBarItemId.set(item.id);
6930
+ const group = event.currentTarget.closest('.ag-menu-bar-group');
6931
+ setTimeout(() => {
6932
+ const enabled = group?.querySelectorAll('.ag-menu-bar-dropdown [role="menuitem"]:not(:disabled)');
6933
+ const target = event.key === 'ArrowUp' ? enabled?.[enabled.length - 1] : enabled?.[0];
6934
+ target?.focus();
6935
+ });
6936
+ }
6937
+ /** @internal Provides standard keyboard navigation within an open menu-bar dropdown. */
6938
+ onMenuBarMenuKeydown(event) {
6939
+ const menu = event.currentTarget;
6940
+ const items = Array.from(menu.querySelectorAll('[role="menuitem"]:not(:disabled)'));
6941
+ if (event.key === 'Escape') {
6942
+ event.preventDefault();
6943
+ event.stopPropagation();
6944
+ this.closeMenuBarMenu();
6945
+ menu.closest('.ag-menu-bar-group')
6946
+ ?.querySelector('.ag-menu-bar-trigger')
6947
+ ?.focus();
6948
+ return;
6949
+ }
6950
+ if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key) || items.length === 0)
6951
+ return;
6952
+ event.preventDefault();
6953
+ event.stopPropagation();
6954
+ const current = items.indexOf(event.target);
6955
+ const next = event.key === 'Home'
6956
+ ? 0
6957
+ : event.key === 'End'
6958
+ ? items.length - 1
6959
+ : event.key === 'ArrowDown'
6960
+ ? (current + 1 + items.length) % items.length
6961
+ : (current - 1 + items.length) % items.length;
6962
+ items[next].focus();
6963
+ }
6964
+ /** @internal Closes the currently open menu-bar dropdown. */
6965
+ closeMenuBarMenu() {
6966
+ this.openMenuBarItemId.set(null);
6967
+ }
6968
+ /** @internal Synchronizes dropdown state and closes competing grid menus when one opens. */
6969
+ onMenuBarOpenItemChange(id) {
6970
+ if (id !== null) {
6971
+ this.rowController.closeContextMenu();
6972
+ this.rowController.closeCellContextMenu();
6973
+ this.groupController.closeActionsMenu();
6974
+ this.columnMenuController.close();
6975
+ }
6976
+ this.openMenuBarItemId.set(id);
6977
+ }
5309
6978
  /** @internal Runs a typed provider context-menu action against erased controller state. */
5310
6979
  runCellMenuItem(item, menu) {
5311
6980
  item.action({
@@ -5519,11 +7188,11 @@ class AgridComponent {
5519
7188
  return index;
5520
7189
  })));
5521
7190
  }
5522
- isCellEditable(col) {
5523
- return this.editController.isCellEditable(col);
7191
+ isCellEditable(col, originalIndex) {
7192
+ return this.editController.isCellEditable(col, originalIndex);
5524
7193
  }
5525
- enterEdit(originalIndex, ci, seedChar) {
5526
- this.editController.start(originalIndex, ci, seedChar);
7194
+ enterEdit(originalIndex, ci, seedChar, selectText = true) {
7195
+ this.editController.start(originalIndex, ci, seedChar, selectText);
5527
7196
  }
5528
7197
  cancelCurrent() {
5529
7198
  this.editController.cancel();
@@ -5559,13 +7228,16 @@ class AgridComponent {
5559
7228
  return this.columnSizing.getWidthToken(col);
5560
7229
  }
5561
7230
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5562
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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", rowChanged: "rowChanged", pageChange: "pageChange", filterChange: "filterChange", sortChange: "sortChange" }, 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\"\n [attr.aria-busy]=\"loading() ? 'true' : null\" (copy)=\"onCopy($event)\" (paste)=\"onPaste($event)\"\n (focusin)=\"onGridFocusIn($event)\"\n (click)=\"closeContextMenu(); closeCellContextMenu(); closeFilterMenu(); closeGroupActionsMenu()\">\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\"\n [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)\"\n (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 }\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)\"\n (pointerdown)=\"$event.stopPropagation()\" (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <cdk-virtual-scroll-viewport #pinnedViewport class=\"ag-body ag-pinned-body\" [itemSize]=\"rowHeight()\"\n [style.width.px]=\"pinnedPaneWidth()\">\n <div *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\" role=\"row\"\n [attr.aria-rowindex]=\"di + headerRowCount() + 1\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(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)\"\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 [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowHeight()\"\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 (enableRowMarking()) {\n <input class=\"ag-row-marker\" type=\"checkbox\" [checked]=\"isRowMarked(item.originalIndex)\"\n [attr.aria-label]=\"localeText().markRow\"\n (pointerdown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\"\n (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 [row]=\"item.row\" [locale]=\"locale()\" [attr.title]=\"getCellTitle(col, item.row[col.field])\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [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 [seedChar]=\"getSeedChar(item.originalIndex, ci)\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\"\n [treeExpandable]=\"treeRowExpandable(item)\" [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)\" (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-group-spacer\"></div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\"\n [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\"\n [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)\"\n (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 }\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)\"\n (pointerdown)=\"$event.stopPropagation()\" (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <cdk-virtual-scroll-viewport #scrollViewport class=\"ag-body\" [itemSize]=\"rowHeight()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" (scroll)=\"onBodyScroll()\">\n <div *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\" role=\"row\"\n [attr.aria-rowindex]=\"di + headerRowCount() + 1\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(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)\"\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 [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowHeight()\"\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 [row]=\"item.row\" [locale]=\"locale()\" [attr.title]=\"getCellTitle(col, item.row[col.field])\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [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 [seedChar]=\"getSeedChar(item.originalIndex, ci)\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\"\n [treeExpandable]=\"treeRowExpandable(item)\" [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 (treeToggle)=\"onTreeToggle(item)\"\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\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\">{{\n getGhostCellDisplay(col) }}</div>\n }\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 </div>\n </div>\n }\n @if (isDataRowItem(item) && isRowPendingDelete(item.originalIndex)) {\n <div class=\"ag-delete-confirmation\" role=\"alertdialog\"\n [attr.aria-label]=\"localeText().confirmDeleteRow\"\n [style.left.px]=\"deleteConfirmationLeft()\"\n [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\"\n type=\"button\" (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 </cdk-virtual-scroll-viewport>\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\"\n [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\"\n [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)\"\n (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 }\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)\"\n (pointerdown)=\"$event.stopPropagation()\" (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <cdk-virtual-scroll-viewport #rightPinnedViewport class=\"ag-body ag-pinned-body ag-right-pinned-body\"\n [itemSize]=\"rowHeight()\" [style.width.px]=\"rightPinnedPaneWidth()\" (scroll)=\"onRightPinnedBodyScroll()\">\n <div *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\" role=\"row\"\n [attr.aria-rowindex]=\"di + headerRowCount() + 1\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-ghost-row]=\"item === 'ghost'\" [class.ag-group-header-row]=\"isGroupHeaderItem(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 [style.height.px]=\"rowHeight()\"\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 [row]=\"item.row\" [locale]=\"locale()\" [attr.title]=\"getCellTitle(col, item.row[col.field])\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [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 [seedChar]=\"getSeedChar(item.originalIndex, ci)\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\"\n [treeExpandable]=\"treeRowExpandable(item)\" [treeExpanded]=\"treeRowExpanded(item)\"\n [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)\" (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-group-spacer\"></div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\"\n [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\n [open]=\"sidebarOpen()\"\n [activeTab]=\"sidebarTab()\"\n [columns]=\"colDefs()\"\n [headerGroups]=\"headerGroups()\"\n [row]=\"sidebarRow()\"\n [hiddenColumns]=\"sidebarHiddenColumns()\"\n [locale]=\"locale()\"\n [localeText]=\"localeText()\"\n [readonlyGrid]=\"readonlyGrid()\"\n [useSidebarEditor]=\"useSidebarEditor()\"\n (close)=\"toggleSidebar()\"\n (tabChange)=\"onSidebarStripClick($event)\"\n (toggleColumn)=\"onSidebarToggleColumn($event)\"\n (toggleColumnGroup)=\"onSidebarToggleColumnGroup($event.fields, $event.visible)\"\n (detailEdit)=\"onSidebarDetailEdit($event)\"\n (save)=\"onSidebarDetailSave($event)\"\n />\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\"\n [disabled]=\"control()!.currentPage() <= 1\" (click)=\"goToFirstPage()\">\u00AB</button>\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().previous\"\n [disabled]=\"control()!.currentPage() <= 1\" (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\"\n (click)=\"goToNextPage()\">\u203A</button>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().lastPage\"\n (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 <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 ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">\n {{ localeText().deleteRow }}\n </button>\n </div>\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)\">{{ localeText().copyCellValue\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyRowToClipboard(menu.rowIndex)\">{{ localeText().copyRow }}</button>\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 }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex + 1)\">{{ 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\"\n (click)=\"deleteRow(menu.rowIndex)\">{{ 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\" [disabled]=\"item.disabled\"\n (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'\"\n [showColumnActions]=\"!!control()\" [pinned]=\"getColumnPinState(menu.field)\"\n [groupable]=\"!!getColDef(menu.field)?.groupable\" [grouped]=\"isGroupedByField(menu.field)\"\n [filterable]=\"!!getColDef(menu.field)?.filterable\" [showValueFilter]=\"!serverSideFiltering()\"\n [search]=\"filterMenuSearch()\"\n [allSelected]=\"isMenuAllSelected(menu.field)\"\n [valueItems]=\"serverSideFiltering() || !getColDef(menu.field)?.filterable ? [] : columnMenuValueItems()\"\n [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\"\n [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-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-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-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)}\n"], dependencies: [{ kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { 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: "component", type: AgridCellComponent, selector: "agrid-cell", inputs: ["col", "rowIndex", "colIndex", "value", "row", "locale", "selected", "editing", "treeCell", "treeLevel", "treeExpandable", "treeExpanded", "seedChar"], outputs: ["treeToggle", "activate", "startEdit", "draftChange"] }, { kind: "component", type: AgridColumnMenuComponent, selector: "agrid-column-menu", inputs: ["localeText", "x", "y", "header", "sortDir", "sortable", "showColumnActions", "pinned", "groupable", "grouped", "filterable", "showValueFilter", "search", "allSelected", "valueItems", "sortPriority", "hasMultiSort", "aggregate"], outputs: ["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", "hiddenColumns", "locale", "localeText", "readonlyGrid", "useSidebarEditor"], outputs: ["close", "tabChange", "toggleColumn", "toggleColumnGroup", "detailEdit", "save"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7231
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", 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 });
5563
7232
  }
5564
7233
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridComponent, decorators: [{
5565
7234
  type: Component,
5566
7235
  args: [{ selector: 'agrid', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
5567
7236
  ScrollingModule,
7237
+ NgTemplateOutlet,
7238
+ AgridVariableRowSizeDirective,
5568
7239
  AgridCellComponent,
7240
+ AgridMenuBarComponent,
5569
7241
  AgridColumnMenuComponent,
5570
7242
  AgridFindPanelComponent,
5571
7243
  AgridSidebarComponent,
@@ -5573,8 +7245,411 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
5573
7245
  '[class.ag-zebra]': 'zebraStripes()',
5574
7246
  '[style.min-height]': 'minHeight()',
5575
7247
  '[style.max-height]': 'maxHeight()',
5576
- }, 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\"\n [attr.aria-busy]=\"loading() ? 'true' : null\" (copy)=\"onCopy($event)\" (paste)=\"onPaste($event)\"\n (focusin)=\"onGridFocusIn($event)\"\n (click)=\"closeContextMenu(); closeCellContextMenu(); closeFilterMenu(); closeGroupActionsMenu()\">\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\"\n [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)\"\n (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 }\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)\"\n (pointerdown)=\"$event.stopPropagation()\" (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <cdk-virtual-scroll-viewport #pinnedViewport class=\"ag-body ag-pinned-body\" [itemSize]=\"rowHeight()\"\n [style.width.px]=\"pinnedPaneWidth()\">\n <div *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\" role=\"row\"\n [attr.aria-rowindex]=\"di + headerRowCount() + 1\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(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)\"\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 [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowHeight()\"\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 (enableRowMarking()) {\n <input class=\"ag-row-marker\" type=\"checkbox\" [checked]=\"isRowMarked(item.originalIndex)\"\n [attr.aria-label]=\"localeText().markRow\"\n (pointerdown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\"\n (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 [row]=\"item.row\" [locale]=\"locale()\" [attr.title]=\"getCellTitle(col, item.row[col.field])\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [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 [seedChar]=\"getSeedChar(item.originalIndex, ci)\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\"\n [treeExpandable]=\"treeRowExpandable(item)\" [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)\" (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-group-spacer\"></div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\"\n [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\"\n [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)\"\n (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 }\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)\"\n (pointerdown)=\"$event.stopPropagation()\" (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <cdk-virtual-scroll-viewport #scrollViewport class=\"ag-body\" [itemSize]=\"rowHeight()\"\n [style.min-width.px]=\"scrollableTotalWidth()\" (scroll)=\"onBodyScroll()\">\n <div *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\" role=\"row\"\n [attr.aria-rowindex]=\"di + headerRowCount() + 1\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(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)\"\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 [attr.data-original-index]=\"getItemOriginalIndex(item)\" [style.height.px]=\"rowHeight()\"\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 [row]=\"item.row\" [locale]=\"locale()\" [attr.title]=\"getCellTitle(col, item.row[col.field])\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [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 [seedChar]=\"getSeedChar(item.originalIndex, ci)\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\"\n [treeExpandable]=\"treeRowExpandable(item)\" [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 (treeToggle)=\"onTreeToggle(item)\"\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\"\n [class.ag-column-reorder-item--dragging]=\"isColDragging(col.field)\"\n [style.transform]=\"'translateX(' + getColReorderOffset(col.field) + 'px)'\">{{\n getGhostCellDisplay(col) }}</div>\n }\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 </div>\n </div>\n }\n @if (isDataRowItem(item) && isRowPendingDelete(item.originalIndex)) {\n <div class=\"ag-delete-confirmation\" role=\"alertdialog\"\n [attr.aria-label]=\"localeText().confirmDeleteRow\"\n [style.left.px]=\"deleteConfirmationLeft()\"\n [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\"\n type=\"button\" (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 </cdk-virtual-scroll-viewport>\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\"\n [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\"\n [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)\"\n (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 }\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)\"\n (pointerdown)=\"$event.stopPropagation()\" (dblclick)=\"onAutosizeColumn($event, col)\"></div>\n </div>\n }\n </div>\n\n <cdk-virtual-scroll-viewport #rightPinnedViewport class=\"ag-body ag-pinned-body ag-right-pinned-body\"\n [itemSize]=\"rowHeight()\" [style.width.px]=\"rightPinnedPaneWidth()\" (scroll)=\"onRightPinnedBodyScroll()\">\n <div *cdkVirtualFor=\"let item of displayItems(); let di = index; trackBy: trackByItem\" role=\"row\"\n [attr.aria-rowindex]=\"di + headerRowCount() + 1\"\n [attr.aria-selected]=\"isDataRowItem(item) && isRowSelected(item.originalIndex) ? 'true' : null\"\n [class.ag-row]=\"!isGroupHeaderItem(item)\" [class.ag-add-row]=\"item === null\"\n [class.ag-ghost-row]=\"item === 'ghost'\" [class.ag-group-header-row]=\"isGroupHeaderItem(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 [style.height.px]=\"rowHeight()\"\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 [row]=\"item.row\" [locale]=\"locale()\" [attr.title]=\"getCellTitle(col, item.row[col.field])\"\n [attr.data-cell-row]=\"item.originalIndex\" [attr.data-cell-col]=\"ci\"\n [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 [seedChar]=\"getSeedChar(item.originalIndex, ci)\"\n [treeCell]=\"isTreeCell(col)\" [treeLevel]=\"treeRowLevel(item)\"\n [treeExpandable]=\"treeRowExpandable(item)\" [treeExpanded]=\"treeRowExpanded(item)\"\n [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)\" (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-group-spacer\"></div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n\n @if (showFooter()) {\n <div class=\"ag-footer\" role=\"row\"\n [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\n [open]=\"sidebarOpen()\"\n [activeTab]=\"sidebarTab()\"\n [columns]=\"colDefs()\"\n [headerGroups]=\"headerGroups()\"\n [row]=\"sidebarRow()\"\n [hiddenColumns]=\"sidebarHiddenColumns()\"\n [locale]=\"locale()\"\n [localeText]=\"localeText()\"\n [readonlyGrid]=\"readonlyGrid()\"\n [useSidebarEditor]=\"useSidebarEditor()\"\n (close)=\"toggleSidebar()\"\n (tabChange)=\"onSidebarStripClick($event)\"\n (toggleColumn)=\"onSidebarToggleColumn($event)\"\n (toggleColumnGroup)=\"onSidebarToggleColumnGroup($event.fields, $event.visible)\"\n (detailEdit)=\"onSidebarDetailEdit($event)\"\n (save)=\"onSidebarDetailSave($event)\"\n />\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\"\n [disabled]=\"control()!.currentPage() <= 1\" (click)=\"goToFirstPage()\">\u00AB</button>\n <button class=\"ag-page-btn\" [attr.aria-label]=\"localeText().previous\"\n [disabled]=\"control()!.currentPage() <= 1\" (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\"\n (click)=\"goToNextPage()\">\u203A</button>\n <button class=\"ag-page-btn\" [disabled]=\"control()!.currentPage() >= totalPages()\"\n [attr.aria-label]=\"localeText().lastPage\"\n (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 <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 ag-context-item--danger\" role=\"menuitem\" (click)=\"deleteRow(menu.rowIndex)\">\n {{ localeText().deleteRow }}\n </button>\n </div>\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)\">{{ localeText().copyCellValue\n }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"copyRowToClipboard(menu.rowIndex)\">{{ localeText().copyRow }}</button>\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 }}</button>\n <button class=\"ag-context-item\" role=\"menuitem\" (click)=\"insertRowAt(menu.rowIndex + 1)\">{{ 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\"\n (click)=\"deleteRow(menu.rowIndex)\">{{ 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\" [disabled]=\"item.disabled\"\n (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'\"\n [showColumnActions]=\"!!control()\" [pinned]=\"getColumnPinState(menu.field)\"\n [groupable]=\"!!getColDef(menu.field)?.groupable\" [grouped]=\"isGroupedByField(menu.field)\"\n [filterable]=\"!!getColDef(menu.field)?.filterable\" [showValueFilter]=\"!serverSideFiltering()\"\n [search]=\"filterMenuSearch()\"\n [allSelected]=\"isMenuAllSelected(menu.field)\"\n [valueItems]=\"serverSideFiltering() || !getColDef(menu.field)?.filterable ? [] : columnMenuValueItems()\"\n [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\"\n [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-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-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-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)}\n"] }]
5577
- }], 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"] }], rowChanged: [{ type: i0.Output, args: ["rowChanged"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], 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 }] }] } });
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 }] }] } });
7250
+
7251
+ let nextPageSelectorId = 0;
7252
+ /** Compact previous/input/dropdown/next control for navigating a labeled page list. */
7253
+ class AgridPageSelectorComponent {
7254
+ elementRef = inject(ElementRef);
7255
+ listboxId = `agrid-page-options-${nextPageSelectorId++}`;
7256
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
7257
+ selectedId = input(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
7258
+ selectedPageNumber = input(null, ...(ngDevMode ? [{ debugName: "selectedPageNumber" }] : /* istanbul ignore next */ []));
7259
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7260
+ previousLabel = input('Previous page', ...(ngDevMode ? [{ debugName: "previousLabel" }] : /* istanbul ignore next */ []));
7261
+ nextLabel = input('Next page', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
7262
+ inputLabel = input('Page ID', ...(ngDevMode ? [{ debugName: "inputLabel" }] : /* istanbul ignore next */ []));
7263
+ menuLabel = input('Choose a page', ...(ngDevMode ? [{ debugName: "menuLabel" }] : /* istanbul ignore next */ []));
7264
+ emptyText = input('No pages', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
7265
+ selectPage = output();
7266
+ menuOpen = signal(false, ...(ngDevMode ? [{ debugName: "menuOpen" }] : /* istanbul ignore next */ []));
7267
+ draft = signal('', ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
7268
+ invalid = signal(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
7269
+ activeId = signal(null, ...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
7270
+ focusedOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedOptionIndex" }] : /* istanbul ignore next */ []));
7271
+ activeIndex = computed(() => this.items().findIndex(item => this.idsEqual(item.id, this.activeId())), ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
7272
+ hasPrevious = computed(() => !this.disabled() && this.activeIndex() > 0, ...(ngDevMode ? [{ debugName: "hasPrevious" }] : /* istanbul ignore next */ []));
7273
+ hasNext = computed(() => {
7274
+ const index = this.activeIndex();
7275
+ return !this.disabled() && index >= 0 && index < this.items().length - 1;
7276
+ }, ...(ngDevMode ? [{ debugName: "hasNext" }] : /* istanbul ignore next */ []));
7277
+ constructor() {
7278
+ effect(() => {
7279
+ const id = this.selectedId();
7280
+ this.activeId.set(id);
7281
+ const item = this.items().find(i => i.id === id);
7282
+ this.draft.set(item?.pageNumber == null ? '' : String(item?.pageNumber));
7283
+ this.draft.set(id == null ? '' : String(id));
7284
+ this.invalid.set(false);
7285
+ });
7286
+ effect(() => {
7287
+ const nr = this.selectedPageNumber();
7288
+ // fetch the page
7289
+ const item = this.items().find(i => i.pageNumber === nr);
7290
+ this.activeId.set(item?.id ?? null);
7291
+ this.draft.set(item?.pageNumber == null ? '' : String(item?.pageNumber));
7292
+ this.invalid.set(false);
7293
+ });
7294
+ }
7295
+ previous() {
7296
+ if (!this.hasPrevious())
7297
+ return;
7298
+ this.choose(this.items()[this.activeIndex() - 1]);
7299
+ }
7300
+ next() {
7301
+ if (!this.hasNext())
7302
+ return;
7303
+ this.choose(this.items()[this.activeIndex() + 1]);
7304
+ }
7305
+ toggleMenu(event) {
7306
+ event.preventDefault();
7307
+ event.stopPropagation();
7308
+ if (this.disabled() || this.items().length === 0)
7309
+ return;
7310
+ this.menuOpen() ? this.closeMenu() : this.openMenu();
7311
+ }
7312
+ openMenu() {
7313
+ if (this.disabled() || this.items().length === 0)
7314
+ return;
7315
+ const current = this.activeIndex();
7316
+ this.focusedOptionIndex.set(current >= 0 ? current : 0);
7317
+ this.menuOpen.set(true);
7318
+ this.scrollFocusedOptionIntoView();
7319
+ }
7320
+ closeMenu() {
7321
+ this.menuOpen.set(false);
7322
+ this.focusedOptionIndex.set(-1);
7323
+ }
7324
+ onInput(event) {
7325
+ this.draft.set(event.target.value);
7326
+ this.invalid.set(false);
7327
+ this.closeMenu();
7328
+ }
7329
+ onInputKeydown(event) {
7330
+ if (event.key === 'Escape') {
7331
+ event.preventDefault();
7332
+ this.closeMenu();
7333
+ this.resetDraft();
7334
+ return;
7335
+ }
7336
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
7337
+ event.preventDefault();
7338
+ if (!this.menuOpen())
7339
+ this.openMenu();
7340
+ else
7341
+ this.moveOption(event.key === 'ArrowDown' ? 1 : -1);
7342
+ return;
7343
+ }
7344
+ if (event.key === 'Home' && this.menuOpen()) {
7345
+ event.preventDefault();
7346
+ this.focusedOptionIndex.set(0);
7347
+ this.scrollFocusedOptionIntoView();
7348
+ return;
7349
+ }
7350
+ if (event.key === 'End' && this.menuOpen()) {
7351
+ event.preventDefault();
7352
+ this.focusedOptionIndex.set(this.items().length - 1);
7353
+ this.scrollFocusedOptionIntoView();
7354
+ return;
7355
+ }
7356
+ if (event.key !== 'Enter')
7357
+ return;
7358
+ event.preventDefault();
7359
+ const focused = this.menuOpen() ? this.items()[this.focusedOptionIndex()] : undefined;
7360
+ if (focused)
7361
+ this.choose(focused);
7362
+ else
7363
+ this.selectDraft();
7364
+ }
7365
+ choose(item) {
7366
+ if (this.disabled())
7367
+ return;
7368
+ this.activeId.set(item.id);
7369
+ this.draft.set(String(item.id));
7370
+ this.invalid.set(false);
7371
+ this.closeMenu();
7372
+ this.selectPage.emit(item);
7373
+ }
7374
+ optionId(index) {
7375
+ return `${this.listboxId}-option-${index}`;
7376
+ }
7377
+ isSelected(item) {
7378
+ return this.idsEqual(item.id, this.activeId());
7379
+ }
7380
+ onDocumentPointerDown(event) {
7381
+ if (!this.menuOpen())
7382
+ return;
7383
+ if (!this.elementRef.nativeElement.contains(event.target))
7384
+ this.closeMenu();
7385
+ }
7386
+ selectDraft() {
7387
+ const value = this.draft().trim();
7388
+ const item = this.items().find(candidate => String(candidate.id) === value);
7389
+ if (item)
7390
+ this.choose(item);
7391
+ else
7392
+ this.invalid.set(true);
7393
+ }
7394
+ resetDraft() {
7395
+ const id = this.activeId();
7396
+ this.draft.set(id == null ? '' : String(id));
7397
+ this.invalid.set(false);
7398
+ }
7399
+ moveOption(direction) {
7400
+ const count = this.items().length;
7401
+ if (count === 0)
7402
+ return;
7403
+ const current = this.focusedOptionIndex();
7404
+ this.focusedOptionIndex.set((current + direction + count) % count);
7405
+ this.scrollFocusedOptionIntoView();
7406
+ }
7407
+ scrollFocusedOptionIntoView() {
7408
+ queueMicrotask(() => this.elementRef.nativeElement
7409
+ .querySelector(`#${this.optionId(this.focusedOptionIndex())}`)
7410
+ ?.scrollIntoView?.({ block: 'nearest' }));
7411
+ }
7412
+ idsEqual(left, right) {
7413
+ return right !== null && left === right;
7414
+ }
7415
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridPageSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7416
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: AgridPageSelectorComponent, isStandalone: true, selector: "agrid-page-selector", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, selectedId: { classPropertyName: "selectedId", publicName: "selectedId", isSignal: true, isRequired: false, transformFunction: null }, selectedPageNumber: { classPropertyName: "selectedPageNumber", publicName: "selectedPageNumber", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, previousLabel: { classPropertyName: "previousLabel", publicName: "previousLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, inputLabel: { classPropertyName: "inputLabel", publicName: "inputLabel", isSignal: true, isRequired: false, transformFunction: null }, menuLabel: { classPropertyName: "menuLabel", publicName: "menuLabel", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectPage: "selectPage" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" } }, ngImport: i0, template: "<div class=\"ag-page-selector\" [class.ag-page-selector--disabled]=\"disabled()\">\n <button type=\"button\" class=\"ag-page-nav\" [disabled]=\"!hasPrevious()\"\n [attr.aria-label]=\"previousLabel()\" [title]=\"previousLabel()\" (click)=\"previous()\">\n <span aria-hidden=\"true\">&#8249;</span>\n </button>\n\n <div class=\"ag-page-combo\">\n <div class=\"ag-page-input-group\" [class.ag-page-input-group--open]=\"menuOpen()\"\n [class.ag-page-input-group--invalid]=\"invalid()\">\n <input class=\"ag-page-input\" type=\"text\" inputmode=\"text\" autocomplete=\"off\"\n role=\"combobox\" [value]=\"draft()\" [disabled]=\"disabled()\"\n [attr.aria-label]=\"inputLabel()\" [attr.aria-expanded]=\"menuOpen()\"\n [attr.aria-controls]=\"menuOpen() ? listboxId : null\"\n [attr.aria-activedescendant]=\"menuOpen() && focusedOptionIndex() >= 0 ? optionId(focusedOptionIndex()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n (input)=\"onInput($event)\" (keydown)=\"onInputKeydown($event)\" />\n <button type=\"button\" class=\"ag-page-trigger\" [disabled]=\"disabled() || items().length === 0\"\n [attr.aria-label]=\"menuLabel()\" [title]=\"menuLabel()\"\n [attr.aria-expanded]=\"menuOpen()\" aria-haspopup=\"listbox\"\n (click)=\"toggleMenu($event)\">\n <span aria-hidden=\"true\">&#9662;</span>\n </button>\n </div>\n\n @if (menuOpen()) {\n <div [id]=\"listboxId\" class=\"ag-page-options\" role=\"listbox\"\n [attr.aria-label]=\"menuLabel()\" (pointerdown)=\"$event.stopPropagation()\">\n @for (item of items(); track item.id; let index = $index) {\n <button type=\"button\" class=\"ag-page-option\" role=\"option\" [id]=\"optionId(index)\"\n [class.ag-page-option--selected]=\"isSelected(item)\"\n [class.ag-page-option--focused]=\"focusedOptionIndex() === index\"\n [attr.aria-selected]=\"isSelected(item)\"\n (mouseenter)=\"focusedOptionIndex.set(index)\" (click)=\"choose(item)\">\n <span class=\"ag-page-option-id\">{{ item.pageNumber }}</span>\n <span class=\"ag-page-option-label\">{{ item.label }}</span>\n </button>\n } @empty {\n <div class=\"ag-page-empty\">{{ emptyText() }}</div>\n }\n </div>\n }\n </div>\n\n <button type=\"button\" class=\"ag-page-nav\" [disabled]=\"!hasNext()\"\n [attr.aria-label]=\"nextLabel()\" [title]=\"nextLabel()\" (click)=\"next()\">\n <span aria-hidden=\"true\">&#8250;</span>\n </button>\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-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-muted: #f6f8fa;--agrid-color-shadow: rgba(140, 149, 159, .2)}}:host{display:inline-block;color:var(--agrid-color-text);font:13px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.ag-page-selector{display:inline-flex;align-items:center;gap:4px}.ag-page-nav,.ag-page-trigger{display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0;border:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);color:var(--agrid-color-text-muted);cursor:pointer;font:inherit;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-page-nav{width:32px;border-radius:4px;font-size:20px}.ag-page-trigger{width:30px;border-width:0 0 0 1px;font-size:10px}.ag-page-nav:active:not(:disabled),.ag-page-trigger:active:not(:disabled),.ag-page-option:active{transform:scale(.97)}.ag-page-nav:disabled,.ag-page-trigger:disabled,.ag-page-input:disabled{opacity:.48;cursor:not-allowed}.ag-page-nav:focus-visible,.ag-page-trigger:focus-visible,.ag-page-input:focus-visible,.ag-page-option:focus-visible{position:relative;z-index:2;outline:2px solid var(--agrid-color-accent);outline-offset:1px}.ag-page-combo{position:relative;width:clamp(150px,24vw,230px)}.ag-page-input-group{display:flex;height:32px;overflow:hidden;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg)}.ag-page-input-group:focus-within,.ag-page-input-group--open{border-color:var(--agrid-color-accent)}.ag-page-input-group--invalid{border-color:var(--agrid-color-danger, #d1242f)}.ag-page-input{min-width:0;flex:1;padding:0 9px;border:0;outline:0;background:transparent;color:var(--agrid-color-text);font:inherit}.ag-page-options{position:absolute;z-index:20;top:calc(100% + 4px);left:0;width:max(100%,220px);max-width:min(320px,calc(100vw - 24px));max-height:280px;overflow-y:auto;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 right}.ag-page-option{display:grid;grid-template-columns:minmax(32px,auto) minmax(0,1fr);gap:10px;align-items:center;width:100%;min-height:34px;padding:5px 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-page-option--selected{color:var(--agrid-color-accent-fg);background:var(--agrid-color-accent-subtle)}.ag-page-option-id{color:var(--agrid-color-text-muted);font-variant-numeric:tabular-nums}.ag-page-option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-page-empty{padding:12px 8px;color:var(--agrid-color-text-muted)}@media(hover:hover)and (pointer:fine){.ag-page-nav:hover:not(:disabled),.ag-page-trigger:hover:not(:disabled),.ag-page-option--focused:not(.ag-page-option--selected){background:var(--agrid-color-bg-muted)}}@media(max-width:420px){.ag-page-combo{width:min(210px,calc(100vw - 92px))}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7417
+ }
7418
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridPageSelectorComponent, decorators: [{
7419
+ type: Component,
7420
+ args: [{ selector: 'agrid-page-selector', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ag-page-selector\" [class.ag-page-selector--disabled]=\"disabled()\">\n <button type=\"button\" class=\"ag-page-nav\" [disabled]=\"!hasPrevious()\"\n [attr.aria-label]=\"previousLabel()\" [title]=\"previousLabel()\" (click)=\"previous()\">\n <span aria-hidden=\"true\">&#8249;</span>\n </button>\n\n <div class=\"ag-page-combo\">\n <div class=\"ag-page-input-group\" [class.ag-page-input-group--open]=\"menuOpen()\"\n [class.ag-page-input-group--invalid]=\"invalid()\">\n <input class=\"ag-page-input\" type=\"text\" inputmode=\"text\" autocomplete=\"off\"\n role=\"combobox\" [value]=\"draft()\" [disabled]=\"disabled()\"\n [attr.aria-label]=\"inputLabel()\" [attr.aria-expanded]=\"menuOpen()\"\n [attr.aria-controls]=\"menuOpen() ? listboxId : null\"\n [attr.aria-activedescendant]=\"menuOpen() && focusedOptionIndex() >= 0 ? optionId(focusedOptionIndex()) : null\"\n [attr.aria-invalid]=\"invalid() ? 'true' : null\"\n (input)=\"onInput($event)\" (keydown)=\"onInputKeydown($event)\" />\n <button type=\"button\" class=\"ag-page-trigger\" [disabled]=\"disabled() || items().length === 0\"\n [attr.aria-label]=\"menuLabel()\" [title]=\"menuLabel()\"\n [attr.aria-expanded]=\"menuOpen()\" aria-haspopup=\"listbox\"\n (click)=\"toggleMenu($event)\">\n <span aria-hidden=\"true\">&#9662;</span>\n </button>\n </div>\n\n @if (menuOpen()) {\n <div [id]=\"listboxId\" class=\"ag-page-options\" role=\"listbox\"\n [attr.aria-label]=\"menuLabel()\" (pointerdown)=\"$event.stopPropagation()\">\n @for (item of items(); track item.id; let index = $index) {\n <button type=\"button\" class=\"ag-page-option\" role=\"option\" [id]=\"optionId(index)\"\n [class.ag-page-option--selected]=\"isSelected(item)\"\n [class.ag-page-option--focused]=\"focusedOptionIndex() === index\"\n [attr.aria-selected]=\"isSelected(item)\"\n (mouseenter)=\"focusedOptionIndex.set(index)\" (click)=\"choose(item)\">\n <span class=\"ag-page-option-id\">{{ item.pageNumber }}</span>\n <span class=\"ag-page-option-label\">{{ item.label }}</span>\n </button>\n } @empty {\n <div class=\"ag-page-empty\">{{ emptyText() }}</div>\n }\n </div>\n }\n </div>\n\n <button type=\"button\" class=\"ag-page-nav\" [disabled]=\"!hasNext()\"\n [attr.aria-label]=\"nextLabel()\" [title]=\"nextLabel()\" (click)=\"next()\">\n <span aria-hidden=\"true\">&#8250;</span>\n </button>\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-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-muted: #f6f8fa;--agrid-color-shadow: rgba(140, 149, 159, .2)}}:host{display:inline-block;color:var(--agrid-color-text);font:13px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.ag-page-selector{display:inline-flex;align-items:center;gap:4px}.ag-page-nav,.ag-page-trigger{display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0;border:1px solid var(--agrid-color-border);background:var(--agrid-color-bg);color:var(--agrid-color-text-muted);cursor:pointer;font:inherit;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-page-nav{width:32px;border-radius:4px;font-size:20px}.ag-page-trigger{width:30px;border-width:0 0 0 1px;font-size:10px}.ag-page-nav:active:not(:disabled),.ag-page-trigger:active:not(:disabled),.ag-page-option:active{transform:scale(.97)}.ag-page-nav:disabled,.ag-page-trigger:disabled,.ag-page-input:disabled{opacity:.48;cursor:not-allowed}.ag-page-nav:focus-visible,.ag-page-trigger:focus-visible,.ag-page-input:focus-visible,.ag-page-option:focus-visible{position:relative;z-index:2;outline:2px solid var(--agrid-color-accent);outline-offset:1px}.ag-page-combo{position:relative;width:clamp(150px,24vw,230px)}.ag-page-input-group{display:flex;height:32px;overflow:hidden;border:1px solid var(--agrid-color-border);border-radius:4px;background:var(--agrid-color-bg)}.ag-page-input-group:focus-within,.ag-page-input-group--open{border-color:var(--agrid-color-accent)}.ag-page-input-group--invalid{border-color:var(--agrid-color-danger, #d1242f)}.ag-page-input{min-width:0;flex:1;padding:0 9px;border:0;outline:0;background:transparent;color:var(--agrid-color-text);font:inherit}.ag-page-options{position:absolute;z-index:20;top:calc(100% + 4px);left:0;width:max(100%,220px);max-width:min(320px,calc(100vw - 24px));max-height:280px;overflow-y:auto;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 right}.ag-page-option{display:grid;grid-template-columns:minmax(32px,auto) minmax(0,1fr);gap:10px;align-items:center;width:100%;min-height:34px;padding:5px 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-page-option--selected{color:var(--agrid-color-accent-fg);background:var(--agrid-color-accent-subtle)}.ag-page-option-id{color:var(--agrid-color-text-muted);font-variant-numeric:tabular-nums}.ag-page-option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ag-page-empty{padding:12px 8px;color:var(--agrid-color-text-muted)}@media(hover:hover)and (pointer:fine){.ag-page-nav:hover:not(:disabled),.ag-page-trigger:hover:not(:disabled),.ag-page-option--focused:not(.ag-page-option--selected){background:var(--agrid-color-bg-muted)}}@media(max-width:420px){.ag-page-combo{width:min(210px,calc(100vw - 92px))}}\n"] }]
7421
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], selectedId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedId", required: false }] }], selectedPageNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedPageNumber", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], inputLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputLabel", required: false }] }], menuLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuLabel", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], selectPage: [{ type: i0.Output, args: ["selectPage"] }], onDocumentPointerDown: [{
7422
+ type: HostListener,
7423
+ args: ['document:pointerdown', ['$event']]
7424
+ }] } });
7425
+
7426
+ function isPathNode(item) {
7427
+ return 'pathNodeId' in item;
7428
+ }
7429
+ /** Standalone accessible tree control backed by the same projection logic as `AgridComponent`. */
7430
+ class AgridTreeComponent {
7431
+ provider = input.required(...(ngDevMode ? [{ debugName: "provider" }] : /* istanbul ignore next */ []));
7432
+ nodeClick = output();
7433
+ nodeDoubleClicked = output();
7434
+ selectionChange = output();
7435
+ treeController = new AgridTreeController();
7436
+ treeElement = viewChild('tree', ...(ngDevMode ? [{ debugName: "treeElement" }] : /* istanbul ignore next */ []));
7437
+ initializedProvider = null;
7438
+ focusedIndex = signal(0, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : /* istanbul ignore next */ []));
7439
+ selectedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
7440
+ expandedIds = this.treeController.expandedIds;
7441
+ items = computed(() => {
7442
+ const provider = this.provider();
7443
+ const rows = provider.datasource.rows();
7444
+ const indices = rows.map((_, index) => index);
7445
+ const config = provider.treeConfig;
7446
+ const projected = isPathTreeConfig(config)
7447
+ ? buildPathTreeItems(rows, indices, config, this.expandedIds())
7448
+ : buildTreeItems(rows, indices, config, this.expandedIds());
7449
+ return projected.filter((item) => item !== null && typeof item === 'object' && 'level' in item
7450
+ && ('pathNodeId' in item || 'row' in item));
7451
+ }, ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
7452
+ constructor() {
7453
+ effect(() => {
7454
+ const provider = this.provider();
7455
+ if (provider === this.initializedProvider)
7456
+ return;
7457
+ this.initializedProvider = provider;
7458
+ this.selectedKeys.set(new Set());
7459
+ this.focusedIndex.set(0);
7460
+ provider.treeConfig.defaultExpanded ? this.expandAllNodes() : this.collapseAllNodes();
7461
+ });
7462
+ }
7463
+ /** Expands every branch currently represented by the datasource. */
7464
+ expandAllNodes() {
7465
+ const provider = this.provider();
7466
+ const config = provider.treeConfig;
7467
+ const rows = provider.datasource.rows();
7468
+ if (isPathTreeConfig(config)) {
7469
+ const ids = new Set();
7470
+ for (const row of rows) {
7471
+ const path = config.getPath(row).map(String).filter(Boolean);
7472
+ for (let length = 1; length < path.length; length++) {
7473
+ ids.add(pathTreeNodeId(path.slice(0, length)));
7474
+ }
7475
+ }
7476
+ this.treeController.expandAll(ids);
7477
+ return;
7478
+ }
7479
+ const ids = new Set();
7480
+ for (const row of rows) {
7481
+ const parentId = config.getParentId(row);
7482
+ if (parentId != null)
7483
+ ids.add(parentId);
7484
+ }
7485
+ this.treeController.expandAll(ids);
7486
+ }
7487
+ /** Collapses every branch. */
7488
+ collapseAllNodes() {
7489
+ this.treeController.collapseAll();
7490
+ }
7491
+ /** Toggles one expandable node. */
7492
+ toggleNode(item) {
7493
+ if (!item.expandable)
7494
+ return;
7495
+ this.treeController.toggle(this.expansionId(item));
7496
+ }
7497
+ /** @internal */
7498
+ label(item) {
7499
+ if (isPathNode(item))
7500
+ return item.pathLabel;
7501
+ if (item.treeLabel !== undefined)
7502
+ return item.treeLabel;
7503
+ const provider = this.provider();
7504
+ return provider.getLabel?.(item.row) ?? String(item.row[provider.treeConfig.treeField] ?? '');
7505
+ }
7506
+ /** @internal */
7507
+ description(item) {
7508
+ return isPathNode(item) ? undefined : this.provider().getDescription?.(item.row);
7509
+ }
7510
+ /** @internal */
7511
+ isSelected(item) {
7512
+ return this.selectedKeys().has(this.selectionKey(item));
7513
+ }
7514
+ /** @internal */
7515
+ onNodeClick(event, item, index) {
7516
+ event.stopPropagation();
7517
+ this.focusedIndex.set(index);
7518
+ this.select(item, event.metaKey || event.ctrlKey);
7519
+ this.nodeClick.emit(this.toEvent(item));
7520
+ }
7521
+ /** @internal */
7522
+ onNodeDoubleClick(event, item) {
7523
+ event.stopPropagation();
7524
+ this.nodeDoubleClicked.emit(this.toEvent(item));
7525
+ }
7526
+ /** @internal */
7527
+ onKeydown(event, item, index) {
7528
+ const items = this.items();
7529
+ if (event.key === 'ArrowDown')
7530
+ return this.moveFocus(event, Math.min(index + 1, items.length - 1));
7531
+ if (event.key === 'ArrowUp')
7532
+ return this.moveFocus(event, Math.max(index - 1, 0));
7533
+ if (event.key === 'Home')
7534
+ return this.moveFocus(event, 0);
7535
+ if (event.key === 'End')
7536
+ return this.moveFocus(event, items.length - 1);
7537
+ if (event.key === 'ArrowRight' && item.expandable) {
7538
+ event.preventDefault();
7539
+ if (!item.expanded)
7540
+ this.toggleNode(item);
7541
+ else
7542
+ this.moveFocus(event, Math.min(index + 1, items.length - 1));
7543
+ return;
7544
+ }
7545
+ if (event.key === 'ArrowLeft') {
7546
+ event.preventDefault();
7547
+ if (item.expandable && item.expanded)
7548
+ this.toggleNode(item);
7549
+ else {
7550
+ const parent = this.findParentIndex(index, item.level);
7551
+ if (parent >= 0)
7552
+ this.focusNode(parent);
7553
+ }
7554
+ return;
7555
+ }
7556
+ if (event.key === 'Enter' || event.key === ' ') {
7557
+ event.preventDefault();
7558
+ this.select(item, event.metaKey || event.ctrlKey);
7559
+ this.nodeClick.emit(this.toEvent(item));
7560
+ }
7561
+ }
7562
+ /** @internal */
7563
+ trackItem(_index, item) {
7564
+ return this.selectionKey(item);
7565
+ }
7566
+ select(item, additive) {
7567
+ const mode = this.provider().selection;
7568
+ if (mode === 'none')
7569
+ return;
7570
+ const key = this.selectionKey(item);
7571
+ this.selectedKeys.update(current => {
7572
+ const next = mode === 'multi' && additive ? new Set(current) : new Set();
7573
+ if (mode === 'multi' && additive && next.has(key))
7574
+ next.delete(key);
7575
+ else
7576
+ next.add(key);
7577
+ return next;
7578
+ });
7579
+ const selected = this.items().filter(candidate => this.selectedKeys().has(this.selectionKey(candidate)));
7580
+ this.selectionChange.emit({ nodes: selected.map(candidate => this.toEvent(candidate)) });
7581
+ }
7582
+ toEvent(item) {
7583
+ if (isPathNode(item)) {
7584
+ return {
7585
+ kind: 'branch', id: item.pathNodeId, uuid: item.uuid, label: item.pathLabel,
7586
+ level: item.level, expandable: true, expanded: item.expanded,
7587
+ };
7588
+ }
7589
+ const config = this.provider().treeConfig;
7590
+ const id = isPathTreeConfig(config) ? item.originalIndex : config.getId(item.row);
7591
+ return {
7592
+ kind: 'row', id, label: this.label(item), level: item.level,
7593
+ expandable: item.expandable, expanded: item.expanded,
7594
+ row: item.row, originalIndex: item.originalIndex,
7595
+ };
7596
+ }
7597
+ expansionId(item) {
7598
+ if (isPathNode(item))
7599
+ return item.pathNodeId;
7600
+ const config = this.provider().treeConfig;
7601
+ return isPathTreeConfig(config) ? item.originalIndex : config.getId(item.row);
7602
+ }
7603
+ selectionKey(item) {
7604
+ return isPathNode(item)
7605
+ ? `branch:${item.pathNodeId}`
7606
+ : `row:${this.expansionId(item)}`;
7607
+ }
7608
+ moveFocus(event, index) {
7609
+ event.preventDefault();
7610
+ this.focusNode(index);
7611
+ }
7612
+ focusNode(index) {
7613
+ this.focusedIndex.set(index);
7614
+ queueMicrotask(() => this.treeElement()?.nativeElement
7615
+ .querySelector(`[data-tree-index="${index}"]`)?.focus());
7616
+ }
7617
+ findParentIndex(index, level) {
7618
+ for (let candidate = index - 1; candidate >= 0; candidate--) {
7619
+ if (this.items()[candidate].level < level)
7620
+ return candidate;
7621
+ }
7622
+ return -1;
7623
+ }
7624
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7625
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: AgridTreeComponent, isStandalone: true, selector: "agrid-tree", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { nodeClick: "nodeClick", nodeDoubleClicked: "nodeDoubleClicked", selectionChange: "selectionChange" }, host: { properties: { "style.--agrid-tree-row-height.px": "provider().rowHeight" } }, viewQueries: [{ propertyName: "treeElement", first: true, predicate: ["tree"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #tree class=\"ag-tree\" role=\"tree\" [attr.aria-label]=\"provider().ariaLabel\"\n [attr.aria-multiselectable]=\"provider().selection === 'multi' ? 'true' : null\">\n @for (item of items(); track trackItem($index, item); let index = $index) {\n <div class=\"ag-tree-node\" role=\"treeitem\" [attr.data-tree-index]=\"index\"\n [attr.tabindex]=\"focusedIndex() === index ? 0 : -1\"\n [attr.aria-level]=\"item.level + 1\"\n [attr.aria-expanded]=\"item.expandable ? item.expanded : null\"\n [attr.aria-selected]=\"provider().selection !== 'none' ? isSelected(item) : null\"\n [class.ag-tree-node--selected]=\"isSelected(item)\"\n [style.padding-left.px]=\"8 + item.level * 20\"\n (click)=\"onNodeClick($event, item, index)\"\n (dblclick)=\"onNodeDoubleClick($event, item)\"\n (keydown)=\"onKeydown($event, item, index)\">\n <button class=\"ag-tree-toggle\" type=\"button\" tabindex=\"-1\"\n [class.ag-tree-toggle--expanded]=\"item.expanded\"\n [class.ag-tree-toggle--hidden]=\"!item.expandable\"\n [attr.aria-label]=\"(item.expanded ? 'Collapse ' : 'Expand ') + label(item)\"\n (click)=\"toggleNode(item); $event.stopPropagation()\">&#9656;</button>\n <span class=\"ag-tree-content\">\n <span class=\"ag-tree-label\">{{ label(item) }}</span>\n @if (description(item); as detail) {\n <span class=\"ag-tree-description\">{{ detail }}</span>\n }\n </span>\n </div>\n } @empty {\n <div class=\"ag-tree-empty\">{{ provider().emptyText }}</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-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-muted: #f6f8fa}}:host{display:block;min-width:180px;min-height:0;overflow:auto;color:var(--agrid-color-text);background:var(--agrid-color-bg);font:13px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.ag-tree{min-width:max-content;padding:4px}.ag-tree-node{display:flex;align-items:center;min-width:100%;height:var(--agrid-tree-row-height);padding-right:10px;border-radius:4px;box-sizing:border-box;cursor:default;outline:none;-webkit-user-select:none;user-select:none}.ag-tree-node--selected{color:var(--agrid-color-accent-fg);background:var(--agrid-color-accent-subtle)}.ag-tree-node:focus-visible{box-shadow:inset 0 0 0 2px var(--agrid-color-accent)}.ag-tree-toggle{display:inline-flex;flex:0 0 24px;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:4px;color:var(--agrid-color-text-muted);background:transparent;font:inherit;cursor:pointer;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-tree-toggle--expanded{transform:rotate(90deg)}.ag-tree-toggle--hidden{visibility:hidden}.ag-tree-toggle:active{scale:.92}.ag-tree-content{display:flex;min-width:0;flex-direction:column;justify-content:center}.ag-tree-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.ag-tree-description{overflow:hidden;color:var(--agrid-color-text-muted);text-overflow:ellipsis;white-space:nowrap;font-size:11px}.ag-tree-empty{padding:16px 12px;color:var(--agrid-color-text-muted)}@media(hover:hover)and (pointer:fine){.ag-tree-node:hover:not(.ag-tree-node--selected),.ag-tree-toggle:hover{background:var(--agrid-color-bg-muted)}}@media(prefers-reduced-motion:reduce){.ag-tree-toggle{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7626
+ }
7627
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AgridTreeComponent, decorators: [{
7628
+ type: Component,
7629
+ args: [{ selector: 'agrid-tree', changeDetection: ChangeDetectionStrategy.OnPush, host: { '[style.--agrid-tree-row-height.px]': 'provider().rowHeight' }, template: "<div #tree class=\"ag-tree\" role=\"tree\" [attr.aria-label]=\"provider().ariaLabel\"\n [attr.aria-multiselectable]=\"provider().selection === 'multi' ? 'true' : null\">\n @for (item of items(); track trackItem($index, item); let index = $index) {\n <div class=\"ag-tree-node\" role=\"treeitem\" [attr.data-tree-index]=\"index\"\n [attr.tabindex]=\"focusedIndex() === index ? 0 : -1\"\n [attr.aria-level]=\"item.level + 1\"\n [attr.aria-expanded]=\"item.expandable ? item.expanded : null\"\n [attr.aria-selected]=\"provider().selection !== 'none' ? isSelected(item) : null\"\n [class.ag-tree-node--selected]=\"isSelected(item)\"\n [style.padding-left.px]=\"8 + item.level * 20\"\n (click)=\"onNodeClick($event, item, index)\"\n (dblclick)=\"onNodeDoubleClick($event, item)\"\n (keydown)=\"onKeydown($event, item, index)\">\n <button class=\"ag-tree-toggle\" type=\"button\" tabindex=\"-1\"\n [class.ag-tree-toggle--expanded]=\"item.expanded\"\n [class.ag-tree-toggle--hidden]=\"!item.expandable\"\n [attr.aria-label]=\"(item.expanded ? 'Collapse ' : 'Expand ') + label(item)\"\n (click)=\"toggleNode(item); $event.stopPropagation()\">&#9656;</button>\n <span class=\"ag-tree-content\">\n <span class=\"ag-tree-label\">{{ label(item) }}</span>\n @if (description(item); as detail) {\n <span class=\"ag-tree-description\">{{ detail }}</span>\n }\n </span>\n </div>\n } @empty {\n <div class=\"ag-tree-empty\">{{ provider().emptyText }}</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-border: #d0d7de;--agrid-color-bg: #ffffff;--agrid-color-bg-muted: #f6f8fa}}:host{display:block;min-width:180px;min-height:0;overflow:auto;color:var(--agrid-color-text);background:var(--agrid-color-bg);font:13px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.ag-tree{min-width:max-content;padding:4px}.ag-tree-node{display:flex;align-items:center;min-width:100%;height:var(--agrid-tree-row-height);padding-right:10px;border-radius:4px;box-sizing:border-box;cursor:default;outline:none;-webkit-user-select:none;user-select:none}.ag-tree-node--selected{color:var(--agrid-color-accent-fg);background:var(--agrid-color-accent-subtle)}.ag-tree-node:focus-visible{box-shadow:inset 0 0 0 2px var(--agrid-color-accent)}.ag-tree-toggle{display:inline-flex;flex:0 0 24px;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:4px;color:var(--agrid-color-text-muted);background:transparent;font:inherit;cursor:pointer;transition:transform .12s cubic-bezier(.23,1,.32,1)}.ag-tree-toggle--expanded{transform:rotate(90deg)}.ag-tree-toggle--hidden{visibility:hidden}.ag-tree-toggle:active{scale:.92}.ag-tree-content{display:flex;min-width:0;flex-direction:column;justify-content:center}.ag-tree-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.ag-tree-description{overflow:hidden;color:var(--agrid-color-text-muted);text-overflow:ellipsis;white-space:nowrap;font-size:11px}.ag-tree-empty{padding:16px 12px;color:var(--agrid-color-text-muted)}@media(hover:hover)and (pointer:fine){.ag-tree-node:hover:not(.ag-tree-node--selected),.ag-tree-toggle:hover{background:var(--agrid-color-bg-muted)}}@media(prefers-reduced-motion:reduce){.ag-tree-toggle{transition:none}}\n"] }]
7630
+ }], ctorParameters: () => [], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: true }] }], nodeClick: [{ type: i0.Output, args: ["nodeClick"] }], nodeDoubleClicked: [{ type: i0.Output, args: ["nodeDoubleClicked"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], treeElement: [{ type: i0.ViewChild, args: ['tree', { isSignal: true }] }] } });
7631
+
7632
+ /** Provider-style configuration and datasource container for `<agrid-tree>`. */
7633
+ class AgridTreeProvider {
7634
+ datasource;
7635
+ treeConfig;
7636
+ getLabel;
7637
+ getDescription;
7638
+ selection;
7639
+ rowHeight;
7640
+ ariaLabel;
7641
+ emptyText;
7642
+ constructor(config) {
7643
+ this.datasource = config.datasource;
7644
+ this.treeConfig = config.treeConfig;
7645
+ this.getLabel = config.getLabel;
7646
+ this.getDescription = config.getDescription;
7647
+ this.selection = config.selection ?? 'single';
7648
+ this.rowHeight = config.rowHeight ?? 36;
7649
+ this.ariaLabel = config.ariaLabel ?? 'Tree';
7650
+ this.emptyText = config.emptyText ?? 'No items';
7651
+ }
7652
+ }
5578
7653
 
5579
7654
  /** Width sentinel that makes a column fill the remaining horizontal space. */
5580
7655
  const ColDefAutoSize = -1;
@@ -5583,5 +7658,5 @@ const ColDefAutoSize = -1;
5583
7658
  * Generated bundle index. Do not edit.
5584
7659
  */
5585
7660
 
5586
- export { AGRID_LOCALE_TEXT, AgridComponent, AgridControl, AgridDataSource, AgridProvider, ColDefAutoSize };
7661
+ export { AGRID_LOCALE_TEXT, AgridComponent, AgridControl, AgridDataSource, AgridPageSelectorComponent, AgridProvider, AgridTreeComponent, AgridTreeProvider, ColDefAutoSize };
5587
7662
  //# sourceMappingURL=thkl-agrid.mjs.map