@one-grid-core/angular 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +55 -9
  2. package/esm2022/lib/components/one-grid/one-grid.component.mjs +244 -11
  3. package/esm2022/lib/components/one-grid-body/one-grid-body.component.mjs +446 -34
  4. package/esm2022/lib/components/one-grid-cell-currency/one-grid-cell-currency.component.mjs +6 -5
  5. package/esm2022/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.mjs +3 -3
  6. package/esm2022/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.mjs +3 -3
  7. package/esm2022/lib/components/one-grid-checkbox/one-grid-checkbox.component.mjs +7 -18
  8. package/esm2022/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.mjs +6 -6
  9. package/esm2022/lib/components/one-grid-header/one-grid-header.component.mjs +221 -17
  10. package/esm2022/lib/components/one-grid-header-menu/one-grid-header-menu.component.mjs +137 -0
  11. package/esm2022/lib/components/one-grid-pagination/one-grid-pagination.component.mjs +6 -5
  12. package/esm2022/lib/components/one-grid-row-action/one-grid-row-action.component.mjs +4 -4
  13. package/esm2022/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.mjs +5 -3
  14. package/esm2022/lib/components/one-grid-status/one-grid-status.component.mjs +4 -4
  15. package/esm2022/lib/core/directives/one-cell-input-validator.directive.mjs +3 -3
  16. package/esm2022/lib/core/directives/one-cell-renderer-component.directive.mjs +3 -3
  17. package/esm2022/lib/core/directives/one-cell-text-expand.directive.mjs +3 -3
  18. package/esm2022/lib/core/icons/one-grid-icons.mjs +38 -0
  19. package/esm2022/lib/core/instants/one-grid-api.class.mjs +296 -3
  20. package/esm2022/lib/core/models/one-column-def-types.model.mjs +1 -1
  21. package/esm2022/lib/core/models/one-event-types.model.mjs +1 -1
  22. package/esm2022/lib/core/models/one-filter.model.mjs +1 -1
  23. package/esm2022/lib/core/pipes/aria-sort.pipe.mjs +31 -0
  24. package/esm2022/lib/core/pipes/cell-action-button-enable.pipe.mjs +3 -3
  25. package/esm2022/lib/core/pipes/cell-editable.pipe.mjs +3 -3
  26. package/esm2022/lib/core/pipes/cell-invalid.pipe.mjs +3 -3
  27. package/esm2022/lib/core/pipes/cell-key.pipe.mjs +3 -3
  28. package/esm2022/lib/core/pipes/cell-level-padding.pipe.mjs +3 -3
  29. package/esm2022/lib/core/pipes/cell-selected.pipe.mjs +3 -3
  30. package/esm2022/lib/core/pipes/cell-state-renderer.pipe.mjs +3 -3
  31. package/esm2022/lib/core/pipes/column-sort-state.pipe.mjs +3 -3
  32. package/esm2022/lib/core/pipes/header-checkbox-checked.pipe.mjs +3 -3
  33. package/esm2022/lib/core/pipes/header-checkbox-disabled.pipe.mjs +3 -3
  34. package/esm2022/lib/core/pipes/header-checkbox-indeterminate.pipe.mjs +3 -3
  35. package/esm2022/lib/core/services/og-column.service.mjs +153 -30
  36. package/esm2022/lib/core/services/one-grid-cell.service.mjs +39 -15
  37. package/esm2022/lib/helpers/one-cell.helper.mjs +30 -1
  38. package/esm2022/lib/helpers/one-column-def.helper.mjs +16 -1
  39. package/esm2022/lib/helpers/one-csv.helper.mjs +58 -0
  40. package/esm2022/lib/helpers/one-filter.helper.mjs +11 -1
  41. package/esm2022/lib/helpers/one-row-grouping.helper.mjs +103 -0
  42. package/esm2022/lib/index.mjs +3 -1
  43. package/esm2022/lib/one-grid.module.mjs +24 -114
  44. package/fesm2022/one-grid-core-angular.mjs +2322 -823
  45. package/fesm2022/one-grid-core-angular.mjs.map +1 -1
  46. package/lib/components/one-grid/one-grid.component.d.ts +101 -4
  47. package/lib/components/one-grid-body/one-grid-body.component.d.ts +83 -2
  48. package/lib/components/one-grid-cell-currency/one-grid-cell-currency.component.d.ts +1 -1
  49. package/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.d.ts +1 -1
  50. package/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.d.ts +1 -1
  51. package/lib/components/one-grid-checkbox/one-grid-checkbox.component.d.ts +1 -1
  52. package/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.d.ts +1 -1
  53. package/lib/components/one-grid-header/one-grid-header.component.d.ts +69 -4
  54. package/lib/components/one-grid-header-menu/one-grid-header-menu.component.d.ts +52 -0
  55. package/lib/components/one-grid-pagination/one-grid-pagination.component.d.ts +1 -1
  56. package/lib/components/one-grid-row-action/one-grid-row-action.component.d.ts +1 -1
  57. package/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.d.ts +1 -1
  58. package/lib/components/one-grid-status/one-grid-status.component.d.ts +1 -1
  59. package/lib/core/directives/one-cell-input-validator.directive.d.ts +1 -1
  60. package/lib/core/directives/one-cell-renderer-component.directive.d.ts +1 -1
  61. package/lib/core/directives/one-cell-text-expand.directive.d.ts +1 -1
  62. package/lib/core/icons/one-grid-icons.d.ts +35 -0
  63. package/lib/core/instants/one-grid-api.class.d.ts +115 -1
  64. package/lib/core/models/one-column-def-types.model.d.ts +77 -2
  65. package/lib/core/models/one-event-types.model.d.ts +19 -0
  66. package/lib/core/models/one-filter.model.d.ts +8 -1
  67. package/lib/core/pipes/aria-sort.pipe.d.ts +15 -0
  68. package/lib/core/pipes/cell-action-button-enable.pipe.d.ts +1 -1
  69. package/lib/core/pipes/cell-editable.pipe.d.ts +1 -1
  70. package/lib/core/pipes/cell-invalid.pipe.d.ts +1 -1
  71. package/lib/core/pipes/cell-key.pipe.d.ts +1 -1
  72. package/lib/core/pipes/cell-level-padding.pipe.d.ts +1 -1
  73. package/lib/core/pipes/cell-selected.pipe.d.ts +1 -1
  74. package/lib/core/pipes/cell-state-renderer.pipe.d.ts +1 -1
  75. package/lib/core/pipes/column-sort-state.pipe.d.ts +1 -1
  76. package/lib/core/pipes/header-checkbox-checked.pipe.d.ts +1 -1
  77. package/lib/core/pipes/header-checkbox-disabled.pipe.d.ts +1 -1
  78. package/lib/core/pipes/header-checkbox-indeterminate.pipe.d.ts +1 -1
  79. package/lib/core/services/og-column.service.d.ts +46 -2
  80. package/lib/core/services/one-grid-cell.service.d.ts +16 -3
  81. package/lib/helpers/one-cell.helper.d.ts +17 -0
  82. package/lib/helpers/one-column-def.helper.d.ts +9 -0
  83. package/lib/helpers/one-csv.helper.d.ts +29 -0
  84. package/lib/helpers/one-row-grouping.helper.d.ts +35 -0
  85. package/lib/index.d.ts +2 -0
  86. package/lib/one-grid.module.d.ts +15 -37
  87. package/package.json +1 -1
  88. package/scss/_one-grid.scss +127 -0
  89. package/esm2022/lib/core/pipes/cell-action-button-visible.pipe.mjs +0 -17
  90. package/esm2022/lib/core/pipes/cell-field-value-empty.pipe.mjs +0 -26
  91. package/esm2022/lib/core/pipes/cell-field-value.pipe.mjs +0 -25
  92. package/esm2022/lib/core/pipes/cell-field.pipe.mjs +0 -20
  93. package/esm2022/lib/core/pipes/cell-number-value.pipe.mjs +0 -19
  94. package/esm2022/lib/core/pipes/cell-state-group-renderer.pipe.mjs +0 -21
  95. package/esm2022/lib/core/pipes/cell-value-getter.pipe.mjs +0 -23
  96. package/lib/core/pipes/cell-action-button-visible.pipe.d.ts +0 -9
  97. package/lib/core/pipes/cell-field-value-empty.pipe.d.ts +0 -7
  98. package/lib/core/pipes/cell-field-value.pipe.d.ts +0 -7
  99. package/lib/core/pipes/cell-field.pipe.d.ts +0 -7
  100. package/lib/core/pipes/cell-number-value.pipe.d.ts +0 -7
  101. package/lib/core/pipes/cell-state-group-renderer.pipe.d.ts +0 -9
  102. package/lib/core/pipes/cell-value-getter.pipe.d.ts +0 -9
@@ -1,17 +1,16 @@
1
1
  import { v4 } from 'uuid';
2
2
  import * as i0 from '@angular/core';
3
- import { Injectable, Pipe, inject, NgZone, Input, Component, ViewChild, EventEmitter, Output, HostListener, Directive, ViewChildren, ChangeDetectionStrategy, NgModule } from '@angular/core';
3
+ import { Injectable, inject, NgZone, Pipe, Input, Component, EventEmitter, HostListener, ViewChildren, Output, ViewChild, ChangeDetectionStrategy, Directive, NgModule } from '@angular/core';
4
4
  import { BehaviorSubject, Subject, Subscription } from 'rxjs';
5
5
  import _ from 'lodash';
6
- import { filter, map } from 'rxjs/operators';
7
- import * as i2 from '@angular/common';
8
- import { CommonModule } from '@angular/common';
9
- import * as i2$1 from 'lucide-angular';
10
- import { LucideAngularModule, icons } from 'lucide-angular';
11
- import * as i6 from '@angular/cdk/scrolling';
12
- import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
6
+ import { filter, map, debounceTime, take } from 'rxjs/operators';
7
+ import { NgClass, NgStyle, NgTemplateOutlet, DecimalPipe } from '@angular/common';
8
+ import * as i2 from 'lucide-angular';
9
+ import { GripVertical, ChevronRight, ChevronLeft, ChevronLast, ChevronFirst, ArrowUpAZ, ArrowDownZA, ArrowDownUp, LucideIconProvider, LUCIDE_ICONS, LucideAngularModule } from 'lucide-angular';
10
+ import * as i5 from '@angular/cdk/scrolling';
11
+ import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
13
12
  import * as i3 from '@angular/forms';
14
- import { FormsModule, ReactiveFormsModule } from '@angular/forms';
13
+ import { FormsModule } from '@angular/forms';
15
14
 
16
15
  /** Width used when a column defines neither `width` nor `minWidth`. */
17
16
  const ONE_DEFAULT_COLUMN_WIDTH = 150;
@@ -65,6 +64,182 @@ function resolveOneColumDefParams(prop, params) {
65
64
  function resolveOneValueGetterParams(prop, params) {
66
65
  return typeof prop === 'function' ? prop(params) : prop;
67
66
  }
67
+ /**
68
+ * Run a column's `valueGetter` for one row.
69
+ *
70
+ * Was `CellValueGetterPipe`, which existed only to call this resolver from a
71
+ * service. The pipe added a module declaration and an indirection for a
72
+ * template use that never existed.
73
+ */
74
+ function resolveCellValueGetter(data, node, column, context) {
75
+ return resolveOneValueGetterParams(column?.valueGetter, {
76
+ data,
77
+ rowIndex: node?.rowIndex,
78
+ node,
79
+ context,
80
+ });
81
+ }
82
+
83
+ /**
84
+ * The columns an export can say anything about: those that read a row —
85
+ * through a field or a valueGetter. Action columns have neither, and the
86
+ * row-number column reads state the data does not hold; both are skipped.
87
+ */
88
+ function exportableColumns(columns) {
89
+ return (columns ?? []).filter(column => (column.field && column.field !== '_oneRowNumber') || column.valueGetter);
90
+ }
91
+ /**
92
+ * Serialize rows to RFC 4180 CSV.
93
+ *
94
+ * Each cell goes through the same value pipeline the grid renders with:
95
+ * `valueGetter` when declared, the field otherwise, then `valueFormatter` —
96
+ * so the file says what the screen says. Null and undefined export as empty
97
+ * cells, before the formatter, exactly as the grid leaves them to the
98
+ * placeholder.
99
+ */
100
+ function buildCsv(nodes, columns, context = null, options = {}) {
101
+ const separator = options.columnSeparator ?? ',';
102
+ const exportable = exportableColumns(columns);
103
+ const lines = [];
104
+ if (options.includeHeaders ?? true) {
105
+ lines.push(exportable.map(column => escapeCsvCell(column.headerName ?? column.field ?? '', separator)).join(separator));
106
+ }
107
+ nodes?.forEach((node, rowIndex) => {
108
+ const cells = exportable.map(column => {
109
+ let value = column.valueGetter
110
+ ? column.valueGetter({ data: node.data, rowIndex, node, context })
111
+ : node.data?.[column.field];
112
+ if (value === null || value === undefined)
113
+ return '';
114
+ if (column.valueFormatter) {
115
+ value = column.valueFormatter({
116
+ value,
117
+ data: node.data,
118
+ node,
119
+ rowIndex,
120
+ field: column.field,
121
+ columnDef: column,
122
+ context,
123
+ });
124
+ }
125
+ return escapeCsvCell(value, separator);
126
+ });
127
+ lines.push(cells.join(separator));
128
+ });
129
+ // CRLF is what RFC 4180 asks for, and what spreadsheet apps expect.
130
+ return lines.join('\r\n');
131
+ }
132
+ /** Quote a cell when it holds the separator, a quote, or a line break. */
133
+ function escapeCsvCell(value, separator) {
134
+ const text = `${value}`;
135
+ if (text.includes(separator) || text.includes('"') || text.includes('\n') || text.includes('\r')) {
136
+ return `"${text.replace(/"/g, '""')}"`;
137
+ }
138
+ return text;
139
+ }
140
+
141
+ /** Marks a synthetic group row, so anything can tell it from real data. */
142
+ const ONE_GROUP_FLAG = '__oneGroup';
143
+ /** Stable id for a group row: its value path. Survives every rebuild. */
144
+ const ONE_GROUP_ID_FIELD = '__oneGroupId';
145
+ /** The label the auto group column shows for a group row. */
146
+ const ONE_GROUP_VALUE_FIELD = '__oneGroupValue';
147
+ /** Where a group row keeps its children, for the tree pipeline to recurse. */
148
+ const ONE_GROUP_CHILDREN_FIELD = '__oneGroupChildren';
149
+ /** What a group of rows with no value in the grouped field is labelled. */
150
+ const ONE_GROUP_BLANK_LABEL = '(Blank)';
151
+ /** The `rowGroup` fields of a column set, in definition order. */
152
+ function rowGroupFields(columns) {
153
+ return (columns ?? [])
154
+ .filter(column => column.rowGroup && column.field)
155
+ .map(column => column.field);
156
+ }
157
+ /** The `aggFunc` columns of a column set. */
158
+ function aggColumns(columns) {
159
+ return (columns ?? [])
160
+ .filter(column => column.aggFunc && column.field)
161
+ .map(column => ({ field: column.field, aggFunc: column.aggFunc }));
162
+ }
163
+ /**
164
+ * Fold flat rows into a tree of synthetic group rows, one level per group
165
+ * field, in first-appearance order. Each group row carries:
166
+ *
167
+ * - the marker, id and label fields above;
168
+ * - its own grouped field's value, so sorting and filtering read it naturally;
169
+ * - one aggregated value per `aggFunc` column, computed over the group's
170
+ * leaves (not its child groups), stored under the column's own field so the
171
+ * ordinary cell pipeline renders it.
172
+ *
173
+ * Pure: the input rows are referenced, never mutated.
174
+ */
175
+ function buildGroupedRows(rows, groupFields, aggregations = []) {
176
+ if (!groupFields?.length)
177
+ return [...(rows ?? [])];
178
+ return groupLevel(rows ?? [], groupFields, 0, aggregations, 'og-group');
179
+ }
180
+ function groupLevel(rows, groupFields, level, aggregations, idPrefix) {
181
+ const field = groupFields[level];
182
+ // Insertion-ordered buckets, keyed by the value's string form. The raw
183
+ // value is kept as well, so the group row exposes the real value under the
184
+ // grouped field, not its stringification.
185
+ const buckets = new Map();
186
+ for (const row of rows) {
187
+ const value = row?.[field];
188
+ const key = value === null || value === undefined || value === '' ? ONE_GROUP_BLANK_LABEL : String(value);
189
+ const bucket = buckets.get(key);
190
+ if (bucket) {
191
+ bucket.rows.push(row);
192
+ }
193
+ else {
194
+ buckets.set(key, { value, rows: [row] });
195
+ }
196
+ }
197
+ return [...buckets.entries()].map(([key, bucket]) => {
198
+ const id = `${idPrefix}:${key}`;
199
+ const children = level + 1 < groupFields.length
200
+ ? groupLevel(bucket.rows, groupFields, level + 1, aggregations, id)
201
+ : bucket.rows;
202
+ const groupRow = {
203
+ [ONE_GROUP_FLAG]: true,
204
+ [ONE_GROUP_ID_FIELD]: id,
205
+ [ONE_GROUP_VALUE_FIELD]: key,
206
+ [field]: bucket.value,
207
+ [ONE_GROUP_CHILDREN_FIELD]: children,
208
+ };
209
+ // Aggregates always read the original leaves, so a two-level group's
210
+ // parent is not averaging averages.
211
+ for (const aggregation of aggregations) {
212
+ groupRow[aggregation.field] = aggregate(bucket.rows, aggregation.field, aggregation.aggFunc);
213
+ }
214
+ return groupRow;
215
+ });
216
+ }
217
+ /** Apply one aggregation over a set of leaf rows. */
218
+ function aggregate(rows, field, aggFunc) {
219
+ const values = rows
220
+ .map(row => row?.[field])
221
+ .filter(value => value !== null && value !== undefined);
222
+ if (typeof aggFunc === 'function') {
223
+ return aggFunc({ values, rows: rows, field });
224
+ }
225
+ switch (aggFunc) {
226
+ case 'count': return values.length;
227
+ case 'first': return values.length ? values[0] : null;
228
+ case 'last': return values.length ? values[values.length - 1] : null;
229
+ }
230
+ // The numeric aggregates ignore what does not parse, so one stray string in
231
+ // a column does not turn the whole group's total into NaN.
232
+ const numbers = values.map(Number).filter(Number.isFinite);
233
+ if (!numbers.length)
234
+ return null;
235
+ switch (aggFunc) {
236
+ case 'sum': return numbers.reduce((total, value) => total + value, 0);
237
+ case 'min': return Math.min(...numbers);
238
+ case 'max': return Math.max(...numbers);
239
+ case 'avg': return numbers.reduce((total, value) => total + value, 0) / numbers.length;
240
+ default: return null;
241
+ }
242
+ }
68
243
 
69
244
  /**
70
245
  * One Grid Validators
@@ -371,13 +546,23 @@ function matchesAdvancedFilters(node, filters) {
371
546
  const val = (node.data[filter.field] ?? '').toString().toLowerCase();
372
547
  const results = filter.conditions.map(condition => {
373
548
  const cmp = (condition.value ?? '').toString().toLowerCase();
549
+ const cmpTo = (condition.valueTo ?? '').toString().toLowerCase();
374
550
  switch (condition.operator) {
375
551
  case 'equals': return val === cmp;
552
+ case 'notEqual': return val !== cmp;
376
553
  case 'contains': return val.includes(cmp);
554
+ case 'notContains': return !val.includes(cmp);
377
555
  case 'startsWith': return val.startsWith(cmp);
378
556
  case 'endsWith': return val.endsWith(cmp);
379
557
  case 'greaterThan': return compare(val, cmp) > 0;
558
+ case 'greaterThanOrEqual': return compare(val, cmp) >= 0;
380
559
  case 'lessThan': return compare(val, cmp) < 0;
560
+ case 'lessThanOrEqual': return compare(val, cmp) <= 0;
561
+ case 'inRange': return compare(val, cmp) >= 0 && compare(val, cmpTo) <= 0;
562
+ // `val` has already been through `?? ''`, so null and undefined land
563
+ // here as the empty string. 0 stringifies to '0' and does not.
564
+ case 'blank': return val === '';
565
+ case 'notBlank': return val !== '';
381
566
  default: return true; // Unknown operator = ignore
382
567
  }
383
568
  });
@@ -474,6 +659,35 @@ function resolveCellClass(column, row) {
474
659
  ? column.cellClass(row)
475
660
  : column.cellClass;
476
661
  }
662
+ /**
663
+ * The cell's raw field value, with blank treated as absent.
664
+ *
665
+ * Was `CellFieldValuePipe`. It lived as an Angular pipe but was only ever
666
+ * instantiated inside a service — a utility wearing a `@Pipe` decorator, which
667
+ * also cost it a module declaration for a template use that never existed.
668
+ */
669
+ function readCellValue(data, field) {
670
+ const value = getFieldValue(data, field);
671
+ if (value === null || value === undefined)
672
+ return null;
673
+ if (typeof value === 'string' && value.trim() === '')
674
+ return null;
675
+ return value;
676
+ }
677
+ /**
678
+ * Whether a cell has nothing to show.
679
+ *
680
+ * Was `CellFieldValueEmptyPipe`. `field` is optional on `OneColumnDef` — an
681
+ * action-only column legitimately has none — and reading it unguarded used to
682
+ * throw while the grid built cell state, taking the whole grid down. No field
683
+ * means nothing to read, which is empty by definition.
684
+ */
685
+ function isCellEmpty(data, field) {
686
+ if (!data || !field)
687
+ return true;
688
+ const value = getFieldValue(data, field);
689
+ return value === null || value === undefined || value === '';
690
+ }
477
691
 
478
692
  class OgColumnService {
479
693
  constructor() {
@@ -483,6 +697,14 @@ class OgColumnService {
483
697
  * callbacks do. Set during init; null in a bare service.
484
698
  */
485
699
  this.context = null;
700
+ /**
701
+ * Every column, hidden ones included, in display order. `columnDefs$` below
702
+ * carries only the visible subset — it is what the header and body render
703
+ * from, so hiding a column means leaving it out of the emission, and every
704
+ * consumer keeps working with no notion of visibility at all. This list is
705
+ * what remembers a hidden column's place, width and pin for when it returns.
706
+ */
707
+ this.allColumnDefs = [];
486
708
  this.columnDefs$ = new BehaviorSubject([]);
487
709
  this.groupColumnDefs$ = new BehaviorSubject([]);
488
710
  }
@@ -510,9 +732,20 @@ class OgColumnService {
510
732
  };
511
733
  return this.resolveHeaderPresentation(column);
512
734
  });
735
+ this.commit(columns);
736
+ }
737
+ /**
738
+ * Re-band, re-measure and publish a new column list.
739
+ *
740
+ * Every mutation funnels here so the master list and the visible emission
741
+ * cannot drift apart.
742
+ */
743
+ commit(columns) {
513
744
  const ordered = this.applyPinning(columns);
514
- this.recalculateGroups(ordered);
515
- this.columnDefs$.next(ordered);
745
+ this.allColumnDefs = ordered;
746
+ const visible = ordered.filter(column => !column.hide);
747
+ this.recalculateGroups(visible);
748
+ this.columnDefs$.next(visible);
516
749
  }
517
750
  /**
518
751
  * Move pinned columns to their edge and measure how far in each one sits.
@@ -536,11 +769,14 @@ class OgColumnService {
536
769
  return columns;
537
770
  }
538
771
  const center = columns.filter(column => column.pinned !== 'left' && column.pinned !== 'right');
772
+ // Hidden columns keep their place in the band but take up no space, so
773
+ // they are skipped when measuring offsets and never carry the divider.
539
774
  let offset = 0;
540
775
  for (const column of left) {
541
776
  column._pinnedOffset = offset;
542
777
  column._pinnedEdge = false;
543
- offset += resolveColumnWidth(column);
778
+ if (!column.hide)
779
+ offset += resolveColumnWidth(column);
544
780
  }
545
781
  // Right-pinned columns are measured from the right edge inwards, so the
546
782
  // last declared one sits flush and the earlier ones stack outside it.
@@ -548,46 +784,146 @@ class OgColumnService {
548
784
  for (let index = right.length - 1; index >= 0; index--) {
549
785
  right[index]._pinnedOffset = offset;
550
786
  right[index]._pinnedEdge = false;
551
- offset += resolveColumnWidth(right[index]);
787
+ if (!right[index].hide)
788
+ offset += resolveColumnWidth(right[index]);
552
789
  }
553
790
  for (const column of center) {
554
791
  column._pinnedOffset = null;
555
792
  column._pinnedEdge = false;
556
793
  }
557
- // Only the innermost column of each band carries the divider, so stacked
558
- // pinned columns do not each cast a shadow onto the next.
559
- if (left.length)
560
- left[left.length - 1]._pinnedEdge = true;
561
- if (right.length)
562
- right[0]._pinnedEdge = true;
794
+ // Only the innermost visible column of each band carries the divider, so
795
+ // stacked pinned columns do not each cast a shadow onto the next.
796
+ const leftVisible = left.filter(column => !column.hide);
797
+ const rightVisible = right.filter(column => !column.hide);
798
+ if (leftVisible.length)
799
+ leftVisible[leftVisible.length - 1]._pinnedEdge = true;
800
+ if (rightVisible.length)
801
+ rightVisible[0]._pinnedEdge = true;
563
802
  return [...left, ...center, ...right];
564
803
  }
565
- /** Total width of the columns frozen against the given edge. */
804
+ /** Total width of the visible columns frozen against the given edge. */
566
805
  getPinnedWidth(side) {
567
806
  return this.columnDefs$.getValue()
568
807
  .filter(column => column.pinned === side)
569
808
  .reduce((total, column) => total + resolveColumnWidth(column), 0);
570
809
  }
571
- // Update single column
810
+ // Update single column. Works on the master list, so a hidden column can be
811
+ // patched — including `hide` itself, which is how visibility toggles.
572
812
  updateColumnDef(id, changes) {
573
- const columns = this.getColumnState().columns;
574
- const updatedColumns = columns.map(col => col.id === id ? { ...col, ...changes } : col);
575
- // Re-pinned, not just re-grouped: resizing a pinned column changes where
576
- // the ones inside it have to sit, and this is the path a column resize
577
- // takes.
578
- const ordered = this.applyPinning(updatedColumns);
579
- this.recalculateGroups(ordered);
580
- this.columnDefs$.next(ordered);
581
- }
582
- // Reorder columns
813
+ const updatedColumns = this.allColumnDefs.map(col => col.id === id ? { ...col, ...changes } : col);
814
+ // Re-pinned (inside commit), not just re-grouped: resizing a pinned column
815
+ // changes where the ones inside it have to sit, and this is the path a
816
+ // column resize takes.
817
+ this.commit(updatedColumns);
818
+ }
819
+ /**
820
+ * Reorder columns. The indexes are *visible* positions — they come from the
821
+ * header, which only renders visible columns — so they are mapped onto the
822
+ * master list before splicing. A hidden column between the two keeps its
823
+ * place relative to its neighbours.
824
+ */
583
825
  reorderColumnDefs(fromIndex, toIndex) {
584
- const columns = _.clone(this.getColumnState().columns);
585
- const [moved] = columns.splice(fromIndex, 1);
586
- columns.splice(toIndex, 0, moved);
587
- // Dragging can drop a column between two pinned ones; re-pinning puts the
588
- // bands back in order rather than leaving them interleaved, which would
589
- // make the computed offsets meaningless.
590
- this.columnDefs$.next(this.applyPinning(columns));
826
+ const columns = _.clone(this.allColumnDefs);
827
+ const visible = columns.filter(column => !column.hide);
828
+ const moved = visible[fromIndex];
829
+ if (!moved)
830
+ return;
831
+ columns.splice(columns.indexOf(moved), 1);
832
+ // Land before the visible column now at the target index — the same splice
833
+ // the visible-only list used to do after removal — or at the end when the
834
+ // target is past the last visible column.
835
+ const visibleAfter = visible.filter(column => column !== moved);
836
+ const anchor = visibleAfter[toIndex];
837
+ columns.splice(anchor ? columns.indexOf(anchor) : columns.length, 0, moved);
838
+ // Dragging can drop a column between two pinned ones; re-pinning (inside
839
+ // commit) puts the bands back in order rather than leaving them
840
+ // interleaved, which would make the computed offsets meaningless.
841
+ this.commit(columns);
842
+ }
843
+ /**
844
+ * Distribute the viewport width left over by fixed columns among the flex
845
+ * ones, in proportion to their `flex` values and clamped to their bounds.
846
+ *
847
+ * @returns Whether any width actually changed — the caller only dispatches
848
+ * (and the body only re-triggers) on a real change, which is what terminates
849
+ * the columnChanged → flex → columnChanged loop.
850
+ */
851
+ applyFlexWidths(availableWidth) {
852
+ const visible = this.columnDefs$.getValue();
853
+ const flexColumns = visible.filter(column => (column.flex ?? 0) > 0);
854
+ if (!flexColumns.length || !(availableWidth > 0))
855
+ return false;
856
+ const fixedWidth = visible
857
+ .filter(column => !((column.flex ?? 0) > 0))
858
+ .reduce((total, column) => total + resolveColumnWidth(column), 0);
859
+ let remaining = Math.max(availableWidth - fixedWidth, 0);
860
+ // Clamped columns stop flexing and release their share to the rest, so
861
+ // the pass repeats until the split is stable. Each pass settles at least
862
+ // one column; the loop is bounded by the column count.
863
+ const widths = new Map();
864
+ let unsettled = [...flexColumns];
865
+ while (unsettled.length) {
866
+ const totalFlex = unsettled.reduce((total, column) => total + (column.flex ?? 0), 0);
867
+ const share = remaining / totalFlex;
868
+ const clamped = unsettled.filter(column => {
869
+ const ideal = share * (column.flex ?? 0);
870
+ const min = Math.max(column.minWidth ?? 0, 50);
871
+ const max = column.maxWidth ?? Number.POSITIVE_INFINITY;
872
+ const width = Math.min(Math.max(ideal, min), max);
873
+ if (width === ideal)
874
+ return false;
875
+ widths.set(column.id, Math.round(width));
876
+ remaining = Math.max(remaining - width, 0);
877
+ return true;
878
+ });
879
+ unsettled = unsettled.filter(column => !clamped.includes(column));
880
+ if (!clamped.length) {
881
+ unsettled.forEach(column => widths.set(column.id, Math.floor(share * (column.flex ?? 0))));
882
+ break;
883
+ }
884
+ }
885
+ const changed = flexColumns.some(column => widths.get(column.id) !== resolveColumnWidth(column));
886
+ if (!changed)
887
+ return false;
888
+ this.commit(this.allColumnDefs.map(column => widths.has(column.id) ? { ...column, width: widths.get(column.id) } : column));
889
+ return true;
890
+ }
891
+ /**
892
+ * One JSON-safe entry per column — hidden ones included — in display order.
893
+ */
894
+ getColumnStateSnapshot() {
895
+ return this.allColumnDefs.map(column => {
896
+ const entry = { id: column.id, hide: !!column.hide, pinned: column.pinned ?? null };
897
+ if (column.width != null)
898
+ entry.width = column.width;
899
+ return entry;
900
+ });
901
+ }
902
+ /**
903
+ * Restore a saved snapshot: order, width, pin and visibility.
904
+ *
905
+ * Entries are applied in the order given and become the front of the new
906
+ * column order; columns the snapshot does not mention keep their current
907
+ * relative order behind them, and ids the grid no longer has are skipped —
908
+ * so a stale snapshot degrades to a partial restore instead of an error.
909
+ * Only the properties present on an entry are applied.
910
+ */
911
+ applyColumnState(entries) {
912
+ const byId = new Map(this.allColumnDefs.map(column => [column.id, column]));
913
+ const mentioned = (entries ?? []).filter(entry => entry && byId.has(entry.id));
914
+ const mentionedIds = new Set(mentioned.map(entry => entry.id));
915
+ const restored = mentioned.map(entry => {
916
+ const column = { ...byId.get(entry.id) };
917
+ if (entry.width != null)
918
+ column.width = entry.width;
919
+ if ('pinned' in entry)
920
+ column.pinned = entry.pinned ?? undefined;
921
+ if ('hide' in entry)
922
+ column.hide = !!entry.hide;
923
+ return column;
924
+ });
925
+ const rest = this.allColumnDefs.filter(column => !mentionedIds.has(column.id));
926
+ this.commit([...restored, ...rest]);
591
927
  }
592
928
  /**
593
929
  * Group Header Columns
@@ -633,6 +969,7 @@ class OgColumnService {
633
969
  col._resolvedSortable = resolveOneCellRendererParams(col.sortable, params);
634
970
  col._resolvedResizable = resolveOneCellRendererParams(col.resizable, params);
635
971
  col._resolvedDragable = resolveOneCellRendererParams(col.dragable, params);
972
+ col._resolvedFilterable = resolveOneCellRendererParams(col.filterable, params);
636
973
  col._resolvedSuppressHeaderMenuButton = resolveOneCellRendererParams(col.suppressHeaderMenuButton, params);
637
974
  return col;
638
975
  }
@@ -684,76 +1021,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
684
1021
  type: Injectable
685
1022
  }] });
686
1023
 
687
- class CellFieldValueEmptyPipe {
688
- transform(data, field) {
689
- // `field` is optional on OneColumnDef — an action-only column legitimately
690
- // has none. This used to call `field.split('.')` unguarded and throw a
691
- // TypeError while the grid was building cell state, taking the whole grid
692
- // down. No field means nothing to read, which is empty by definition.
693
- if (!data || !field) {
694
- return true;
695
- }
696
- const value = getFieldValue(data, field);
697
- return value === null || value === undefined || value === '';
698
- }
699
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
700
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, name: "cellFieldValueEmpty" }); }
701
- }
702
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, decorators: [{
703
- type: Pipe,
704
- args: [{
705
- standalone: false,
706
- name: 'cellFieldValueEmpty'
707
- }]
708
- }] });
709
-
710
- class CellValueGetterPipe {
711
- transform(data, node, column, context) {
712
- return resolveOneValueGetterParams(column.valueGetter, {
713
- data,
714
- rowIndex: node.rowIndex,
715
- node: node,
716
- context: context
717
- });
718
- }
719
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellValueGetterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
720
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellValueGetterPipe, name: "cellValueGetter" }); }
721
- }
722
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellValueGetterPipe, decorators: [{
723
- type: Pipe,
724
- args: [{
725
- standalone: false,
726
- name: 'cellValueGetter'
727
- }]
728
- }] });
729
-
730
- class CellFieldValuePipe {
731
- transform(data, field) {
732
- const value = getFieldValue(data, field);
733
- if (value === null || value === undefined) {
734
- return null;
735
- }
736
- if (typeof value === 'string' && value.trim() === '') {
737
- return null;
738
- }
739
- return value;
740
- }
741
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
742
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValuePipe, name: "cellFieldValue" }); }
743
- }
744
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValuePipe, decorators: [{
745
- type: Pipe,
746
- args: [{
747
- standalone: false,
748
- name: 'cellFieldValue'
749
- }]
750
- }] });
751
-
752
1024
  class OneGridCellService {
753
1025
  constructor() {
754
- this._cellFieldValueEmpty = new CellFieldValueEmptyPipe();
755
- this._cellValueGetter = new CellValueGetterPipe();
756
- this._cellFieldValue = new CellFieldValuePipe();
757
1026
  this._columnDefs = [];
758
1027
  this._api = null;
759
1028
  // ---------------------------------------------------------------------------
@@ -848,8 +1117,8 @@ class OneGridCellService {
848
1117
  */
849
1118
  resolveValue(node, column) {
850
1119
  return column?.valueGetter
851
- ? this._cellValueGetter.transform(node.data, node, column, this._context)
852
- : this._cellFieldValue.transform(node.data, column.field);
1120
+ ? resolveCellValueGetter(node.data, node, column, this._context)
1121
+ : readCellValue(node.data, column.field);
853
1122
  }
854
1123
  /**
855
1124
  * Apply the column's `valueFormatter`.
@@ -872,6 +1141,28 @@ class OneGridCellService {
872
1141
  // is anything to format, and the formatter must see that same value.
873
1142
  return column.valueFormatter({ ...this.cellParams(column, node), value });
874
1143
  }
1144
+ /**
1145
+ * The placeholder for an empty cell, or `undefined` when this row's tree
1146
+ * depth may not be edited.
1147
+ *
1148
+ * `cellRendererParams.placeholder` is a prompt, not data: it renders at half
1149
+ * opacity where the value would go, which reads as "click here and fill this
1150
+ * in". On the auto group column `editableLevels` decides which depths accept
1151
+ * an edit at all, so a depth outside that list was advertising an edit the
1152
+ * grid then refuses — the cell offers no input and the prompt never clears.
1153
+ *
1154
+ * The suppression is scoped to columns that actually declare `editableLevels`.
1155
+ * The list is opt-in, and a column without one has no per-level rules rather
1156
+ * than no editable levels, so plain grids and untouched tree columns keep
1157
+ * showing their placeholder exactly as before.
1158
+ */
1159
+ resolvePlaceholder(node, column) {
1160
+ const placeholder = column?.cellRendererParams?.placeholder;
1161
+ const editableLevels = column?.editableLevels;
1162
+ if (!column?.autoGroupField || !Array.isArray(editableLevels))
1163
+ return placeholder;
1164
+ return editableLevels.includes(node?.level) ? placeholder : undefined;
1165
+ }
875
1166
  /**
876
1167
  * Generate New Node State Based on Column Field
877
1168
  */
@@ -885,7 +1176,7 @@ class OneGridCellService {
885
1176
  if (!field) {
886
1177
  return { type: 'none', value: '' };
887
1178
  }
888
- const isEmpty = this._cellFieldValueEmpty.transform(node.data, field);
1179
+ const isEmpty = isCellEmpty(node.data, field);
889
1180
  const isGroup = node.group || node.leafGroup;
890
1181
  const isAutoGroup = column?.autoGroupField;
891
1182
  if (column && column.cellRenderer && !isGroup) {
@@ -897,7 +1188,7 @@ class OneGridCellService {
897
1188
  }
898
1189
  // 1. Cell Group Placeholder
899
1190
  if (isEmpty && isGroup && isAutoGroup) {
900
- return { type: 'placeholder', value: column?.cellRendererParams?.placeholder ?? this._cellValueGetter.transform(node.data, node, column, this._context) ?? '' };
1191
+ return { type: 'placeholder', value: this.resolvePlaceholder(node, column) ?? resolveCellValueGetter(node.data, node, column, this._context) ?? '' };
901
1192
  }
902
1193
  // 2. Cell Group Text Value
903
1194
  if (isGroup && isAutoGroup) {
@@ -912,11 +1203,11 @@ class OneGridCellService {
912
1203
  // indented by level. Excluding it dropped every leaf through to case 5 and
913
1204
  // rendered the group column blank for the whole bottom row of a tree.
914
1205
  if (isEmpty && !isGroup) {
915
- const valueGetter = this._cellValueGetter.transform(node.data, node, column, this._context);
1206
+ const valueGetter = resolveCellValueGetter(node.data, node, column, this._context);
916
1207
  if (valueGetter) {
917
1208
  return { type: 'text', value: this.formatValue(node, column, valueGetter) };
918
1209
  }
919
- const placeholder = column?.cellRendererParams?.placeholder ?? '';
1210
+ const placeholder = this.resolvePlaceholder(node, column) ?? '';
920
1211
  return { type: 'placeholder', value: placeholder };
921
1212
  }
922
1213
  // 4. Cell Text Value (leaves, including the auto group column — see above)
@@ -924,7 +1215,14 @@ class OneGridCellService {
924
1215
  const value = this.formatValue(node, column, this.resolveValue(node, column));
925
1216
  return { type: 'text', value: value ?? '' };
926
1217
  }
927
- // 5. Default
1218
+ // 5. A group row outside the group column: render the value when the row
1219
+ // actually carries one — aggregates land in these cells, and a tree
1220
+ // parent's own data belongs on screen too. Blank stays blank.
1221
+ if (!isEmpty) {
1222
+ const value = this.formatValue(node, column, this.resolveValue(node, column));
1223
+ return { type: 'text', value: value ?? '' };
1224
+ }
1225
+ // 6. Default
928
1226
  return { type: 'none', value: '' };
929
1227
  }
930
1228
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -1127,6 +1425,23 @@ class OneGridApi {
1127
1425
  this.treeDataChildrenField = null;
1128
1426
  this.groupDefaultExpanded = 0;
1129
1427
  this.masterDetail = false;
1428
+ // ---------------------------------------------------------------------------
1429
+ // Row grouping
1430
+ // ---------------------------------------------------------------------------
1431
+ this._rowGroupFields = [];
1432
+ this._aggColumns = [];
1433
+ // ---------------------------------------------------------------------------
1434
+ // Undo / redo of cell edits
1435
+ // ---------------------------------------------------------------------------
1436
+ this._undoLimit = 0;
1437
+ this._undoStack = [];
1438
+ this._redoStack = [];
1439
+ // ---------------------------------------------------------------------------
1440
+ // Client-side pagination
1441
+ // ---------------------------------------------------------------------------
1442
+ this._clientPagination = false;
1443
+ this._pageSize = 25;
1444
+ this._currentPage = 1;
1130
1445
  }
1131
1446
  /**
1132
1447
  * Stand the grid up. Called by `OneGridComponent` from `ngOnInit`.
@@ -1149,7 +1464,13 @@ class OneGridApi {
1149
1464
  this.detailRendererComponent = detailRendererComponent;
1150
1465
  this.treeData = treeData;
1151
1466
  this.selectionService.setSelectionMode(rowSelection);
1152
- this.rowService.setGetRowIdFn(context?.componentParent?.getRowId ?? null);
1467
+ // Synthetic group rows carry their own stable id — the path of group
1468
+ // values — which no host getRowId knows how to derive. The wrapper reads
1469
+ // it first and defers to the host's function (or the uuid fallback) for
1470
+ // real rows, so grouping keeps expansion across rebuilds even when the
1471
+ // host provided no id function at all.
1472
+ const hostGetRowId = context?.componentParent?.getRowId ?? null;
1473
+ this.rowService.setGetRowIdFn((params) => params?.data?.[ONE_GROUP_ID_FIELD] ?? hostGetRowId?.(params) ?? null);
1153
1474
  this.cellService.applyColumnDefs(columnDefs, context);
1154
1475
  this.cellService.setGridApi(this);
1155
1476
  this.columnService.initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs, context);
@@ -1187,6 +1508,12 @@ class OneGridApi {
1187
1508
  this.selectionService.deselectAll();
1188
1509
  this.expansionService.clear();
1189
1510
  }
1511
+ // Row grouping is a transform on the way in: the host keeps handing the
1512
+ // grid flat rows, and the tree pipeline below sees synthetic group rows
1513
+ // with children — the same shape tree data arrives in.
1514
+ if (this._rowGroupFields.length) {
1515
+ rowData = buildGroupedRows(rowData, this._rowGroupFields, this._aggColumns);
1516
+ }
1190
1517
  const validRowIds = new Set();
1191
1518
  this.buildRowNodes(rowData, null, 0, validRowIds);
1192
1519
  // Re-apply the active sort. `buildRowNodes` rebuilds the row list in the
@@ -1440,7 +1767,7 @@ class OneGridApi {
1440
1767
  return;
1441
1768
  this._sortModel = sortModel;
1442
1769
  this.applySorting();
1443
- this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes });
1770
+ this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes, sortModel: this._sortModel });
1444
1771
  }
1445
1772
  applySorting() {
1446
1773
  if (this._sortModel.length === 0) {
@@ -1514,6 +1841,50 @@ class OneGridApi {
1514
1841
  });
1515
1842
  }
1516
1843
  }
1844
+ /**
1845
+ * Select or deselect one row, honouring the grid's selection mode.
1846
+ *
1847
+ * The mode rule used to live in the checkbox component, which meant the only
1848
+ * way to select a row was to click that checkbox — anything else reaching for
1849
+ * `toggleSelection` got multi-select behaviour on a single-select grid. It
1850
+ * lives here so every path into selection obeys the same rule.
1851
+ *
1852
+ * @returns `false` when the grid has no selection mode, so a caller can tell
1853
+ * a refused request from an applied one.
1854
+ */
1855
+ toggleRowSelection(rowId, selected) {
1856
+ if (this._destroyed || !this._rowSelection)
1857
+ return false;
1858
+ const node = this.getRowNodeById(rowId);
1859
+ if (!node)
1860
+ return false;
1861
+ // Single select is "this row and no other", which means clearing first.
1862
+ if (this._rowSelection === 'single')
1863
+ this.deselectAll();
1864
+ this.toggleSelection(rowId, selected);
1865
+ node.cellStates = { ...node.cellStates };
1866
+ return true;
1867
+ }
1868
+ /**
1869
+ * Select every row between two, inclusive, in display order.
1870
+ *
1871
+ * What a shift-extended keyboard selection needs. Multi-select only — a
1872
+ * single-select grid has no meaningful range.
1873
+ */
1874
+ selectRowRange(fromRowId, toRowId) {
1875
+ if (this._destroyed || this._rowSelection !== 'multiple')
1876
+ return false;
1877
+ const order = this._visibleRowNodes.length ? this._visibleRowNodes : this._rowNodes;
1878
+ const from = order.findIndex(node => node.rowId === fromRowId);
1879
+ const to = order.findIndex(node => node.rowId === toRowId);
1880
+ if (from === -1 || to === -1)
1881
+ return false;
1882
+ const ids = order
1883
+ .slice(Math.min(from, to), Math.max(from, to) + 1)
1884
+ .map(node => node.rowId);
1885
+ this.setSelectedByIds(ids, true);
1886
+ return true;
1887
+ }
1517
1888
  toggleSelection(rowId, selected) {
1518
1889
  const node = this.getRowNodeById(rowId);
1519
1890
  if (node) {
@@ -1738,19 +2109,237 @@ class OneGridApi {
1738
2109
  this.columnService.setColumnDefs(columnDefs);
1739
2110
  const columnState = this.columnService.getColumnState();
1740
2111
  this.cellService.applyColumnDefs(columnState.columns, this._context);
2112
+ // Cell state is derived from the columns, so replacing them wholesale
2113
+ // stales every node built before this ran. Tree and grouping mode hit
2114
+ // this on every load: the group column registers from a deferred
2115
+ // setColumnDefs, after the host has typically already loaded rows —
2116
+ // hosts used to have to defer setRowData themselves to dodge it.
2117
+ this.regenerateCellStates();
1741
2118
  this.eventService.dispatchEvent('columnChanged', columnState);
1742
2119
  }
1743
2120
  updateColumn(id, changes) {
2121
+ const before = this.visibleColumnFields();
1744
2122
  this.columnService.updateColumnDef(id, changes);
1745
2123
  const columnState = this.columnService.getColumnState();
1746
2124
  this.cellService.applyColumnDefs(columnState.columns, this._context);
2125
+ // Only when the set of visible columns changed (a hide, a show): a cell
2126
+ // state can appear or disappear then. Width patches — this method runs
2127
+ // per mousemove during a resize — skip the O(rows × columns) pass.
2128
+ if (this.visibleColumnFields() !== before)
2129
+ this.regenerateCellStates();
1747
2130
  this.eventService.dispatchEvent('columnChanged', columnState);
1748
2131
  }
2132
+ /** The visible fields as one comparable key. */
2133
+ visibleColumnFields() {
2134
+ return this.columnService.getColumnState().columns?.map(column => column.field).join('|') ?? '';
2135
+ }
2136
+ /** Recompute every node's cell states against the current columns. */
2137
+ regenerateCellStates() {
2138
+ const refresh = (node) => node.setCellStates(this.cellService.generateCellStates(node));
2139
+ this._rowNodes.forEach(refresh);
2140
+ this._pinnedRowNodes.top.forEach(refresh);
2141
+ this._pinnedRowNodes.bottom.forEach(refresh);
2142
+ }
1749
2143
  reorderColumnDefs(fromIndex, toIndex) {
1750
2144
  this.columnService.reorderColumnDefs(fromIndex, toIndex);
1751
2145
  const columnState = this.columnService.getColumnState();
1752
2146
  this.eventService.dispatchEvent('columnChanged', columnState);
1753
2147
  }
2148
+ /** The fields the rows are grouped by, in nesting order. Empty when off. */
2149
+ get rowGroupFields() {
2150
+ return this._rowGroupFields;
2151
+ }
2152
+ /**
2153
+ * Turn row grouping on. Called by `OneGridComponent` when column
2154
+ * definitions declare `rowGroup`; the grid then behaves as a tree over
2155
+ * synthetic group rows, and `setRowData` / `updateRowData` keep taking the
2156
+ * flat rows they always took.
2157
+ */
2158
+ setRowGrouping(groupFields, aggregations = []) {
2159
+ this._rowGroupFields = groupFields ?? [];
2160
+ this._aggColumns = aggregations ?? [];
2161
+ }
2162
+ /**
2163
+ * Start recording committed cell edits for undo/redo. Called by
2164
+ * `OneGridComponent` from `[undoRedoCellEditing]`; the stack keeps the last
2165
+ * `limit` edits. Programmatic `setDataValue` writes are not recorded — only
2166
+ * commits, the ones that raise `cellValueChanged` without a source.
2167
+ */
2168
+ enableUndoRedoCellEditing(limit = 10) {
2169
+ if (this._undoLimit > 0)
2170
+ return;
2171
+ this._undoLimit = Math.max(Math.floor(limit), 1);
2172
+ this.eventService.addEventListener('cellValueChanged', (event) => {
2173
+ if (event.source)
2174
+ return;
2175
+ this._undoStack.push({ rowId: event.node.rowId, field: event.field, oldValue: event.oldValue, newValue: event.newValue });
2176
+ if (this._undoStack.length > this._undoLimit)
2177
+ this._undoStack.shift();
2178
+ // A new edit forks history; what was undone can no longer be redone.
2179
+ this._redoStack = [];
2180
+ });
2181
+ }
2182
+ /** Revert the most recent committed edit. Returns whether one was undone. */
2183
+ undoCellEditing() {
2184
+ return this.replayEdit(this._undoStack, this._redoStack, 'undo');
2185
+ }
2186
+ /** Re-apply the most recently undone edit. Returns whether one was redone. */
2187
+ redoCellEditing() {
2188
+ return this.replayEdit(this._redoStack, this._undoStack, 'redo');
2189
+ }
2190
+ getCurrentUndoSize() {
2191
+ return this._undoStack.length;
2192
+ }
2193
+ getCurrentRedoSize() {
2194
+ return this._redoStack.length;
2195
+ }
2196
+ replayEdit(from, to, source) {
2197
+ if (this._destroyed)
2198
+ return false;
2199
+ const edit = from.pop();
2200
+ if (!edit)
2201
+ return false;
2202
+ const node = this.getRowNodeById(edit.rowId);
2203
+ // The row can be gone — a setRowData replaced the data under the stack.
2204
+ // The edit is dropped rather than replayed onto nothing.
2205
+ if (!node)
2206
+ return false;
2207
+ const value = source === 'undo' ? edit.oldValue : edit.newValue;
2208
+ const previous = source === 'undo' ? edit.newValue : edit.oldValue;
2209
+ node.data[edit.field] = value;
2210
+ this.updateCellStates(node);
2211
+ to.push(edit);
2212
+ const column = this.columnService.getColumnState().columns?.find(col => col.field === edit.field) ?? null;
2213
+ this.eventService.dispatchEvent('cellValueChanged', {
2214
+ node,
2215
+ data: node.data,
2216
+ column: column,
2217
+ field: edit.field,
2218
+ oldValue: previous,
2219
+ newValue: value,
2220
+ source,
2221
+ });
2222
+ return true;
2223
+ }
2224
+ /** The paging state, when the grid pages its own rows. */
2225
+ get paginationState() {
2226
+ return { enabled: this._clientPagination, pageSize: this._pageSize, currentPage: this._currentPage };
2227
+ }
2228
+ /**
2229
+ * Turn grid-owned paging on or off. Called by `OneGridComponent` from its
2230
+ * `clientSidePagination` input; the display slice is what changes — the
2231
+ * full dataset stays behind the API (selection, filters and sorting keep
2232
+ * operating on all of it).
2233
+ */
2234
+ setClientSidePagination(enabled, pageSize) {
2235
+ this._clientPagination = enabled;
2236
+ if (pageSize && pageSize > 0)
2237
+ this._pageSize = pageSize;
2238
+ this._currentPage = 1;
2239
+ }
2240
+ /** Change the page size and return to the first page. */
2241
+ setPageSize(pageSize) {
2242
+ if (this._destroyed || !(pageSize > 0))
2243
+ return;
2244
+ this._pageSize = Math.floor(pageSize);
2245
+ this._currentPage = 1;
2246
+ this.applyFilters();
2247
+ }
2248
+ /** Move to a page (1-based). Clamped to at least 1; the top end clamps when the slice is taken. */
2249
+ setCurrentPage(page) {
2250
+ if (this._destroyed)
2251
+ return;
2252
+ const target = Math.max(Math.floor(page), 1);
2253
+ if (target === this._currentPage)
2254
+ return;
2255
+ this._currentPage = target;
2256
+ this.applyFilters();
2257
+ }
2258
+ /**
2259
+ * The slice of rows the current page shows — a passthrough while paging is
2260
+ * off. The body routes its render list through here, after reporting the
2261
+ * full count, so `rowCountChanged` keeps meaning "rows after filtering"
2262
+ * whether or not the grid pages.
2263
+ */
2264
+ pageNodes(nodes) {
2265
+ if (!this._clientPagination)
2266
+ return (nodes ?? []);
2267
+ const lastPage = Math.max(Math.ceil((nodes?.length ?? 0) / this._pageSize), 1);
2268
+ // A shrinking dataset (a filter, a delete) can strand the page past the
2269
+ // end; clamping here shows the last page instead of an empty one.
2270
+ if (this._currentPage > lastPage)
2271
+ this._currentPage = lastPage;
2272
+ const start = (this._currentPage - 1) * this._pageSize;
2273
+ return (nodes ?? []).slice(start, start + this._pageSize);
2274
+ }
2275
+ /**
2276
+ * Size the flex columns into the given width.
2277
+ *
2278
+ * Called by the grid body with its own viewport width — on first render, on
2279
+ * container resize and after column changes — so `columnDef.flex` works
2280
+ * without the host doing anything. Also callable directly with any width.
2281
+ * A no-op when no visible column declares `flex` or nothing would change.
2282
+ */
2283
+ applyFlexWidths(availableWidth) {
2284
+ if (this._destroyed)
2285
+ return;
2286
+ if (!this.columnService.applyFlexWidths(availableWidth))
2287
+ return;
2288
+ const columnState = this.columnService.getColumnState();
2289
+ this.cellService.applyColumnDefs(columnState.columns, this._context);
2290
+ this.eventService.dispatchEvent('columnChanged', columnState);
2291
+ }
2292
+ /**
2293
+ * The current grid as CSV: visible columns in display order, visible rows
2294
+ * in current sort order, values through the same getter/formatter pipeline
2295
+ * the cells render with. Pinned rows and detail grids are not data and are
2296
+ * not included.
2297
+ */
2298
+ getDataAsCsv(options = {}) {
2299
+ const nodes = options.onlySelected ? this.getSelectedRowNodes() : this._visibleRowNodes;
2300
+ return buildCsv(nodes, this.columnService.getColumnState().columns, this._context, options);
2301
+ }
2302
+ /** Build the CSV and hand it to the browser as a download. */
2303
+ exportDataAsCsv(options = {}) {
2304
+ if (typeof document === 'undefined')
2305
+ return;
2306
+ const blob = new Blob([this.getDataAsCsv(options)], { type: 'text/csv;charset=utf-8;' });
2307
+ const link = document.createElement('a');
2308
+ link.href = URL.createObjectURL(blob);
2309
+ link.download = options.fileName ?? 'export.csv';
2310
+ link.click();
2311
+ URL.revokeObjectURL(link.href);
2312
+ }
2313
+ /**
2314
+ * Show or hide one column. A hidden column keeps its order, width and pin,
2315
+ * so showing it again restores it exactly.
2316
+ */
2317
+ setColumnVisible(id, visible) {
2318
+ this.updateColumn(id, { hide: !visible });
2319
+ }
2320
+ /**
2321
+ * Persistable layout state: one JSON-safe entry per column — hidden ones
2322
+ * included — in display order. Feed it back through `applyColumnState`.
2323
+ */
2324
+ getColumnStateSnapshot() {
2325
+ return this.columnService.getColumnStateSnapshot();
2326
+ }
2327
+ /**
2328
+ * Restore a snapshot from `getColumnStateSnapshot`: order, width, pin and
2329
+ * visibility. Ids the grid no longer has are skipped, so a stale snapshot
2330
+ * degrades to a partial restore rather than an error.
2331
+ */
2332
+ applyColumnState(entries) {
2333
+ if (this._destroyed)
2334
+ return;
2335
+ this.columnService.applyColumnState(entries);
2336
+ const columnState = this.columnService.getColumnState();
2337
+ this.cellService.applyColumnDefs(columnState.columns, this._context);
2338
+ // A restore can show columns that were hidden when the rows were built,
2339
+ // and their cells have no state yet.
2340
+ this.regenerateCellStates();
2341
+ this.eventService.dispatchEvent('columnChanged', columnState);
2342
+ }
1754
2343
  /**
1755
2344
  * Update checkbox selection features
1756
2345
  */
@@ -2337,47 +2926,84 @@ function resolveRowHeight(params, getRowHeight, rowHeight = 26) {
2337
2926
  return rowHeight;
2338
2927
  }
2339
2928
 
2340
- class HeaderCheckboxDisabledPipe {
2341
- transform(rowNodes, selectionMode, allowedEdit) {
2342
- return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
2343
- }
2344
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2345
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, name: "headerCheckboxDisabled" }); }
2346
- }
2347
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, decorators: [{
2348
- type: Pipe,
2349
- args: [{
2350
- standalone: false,
2351
- name: 'headerCheckboxDisabled'
2352
- }]
2353
- }] });
2929
+ /**
2930
+ * The Lucide icons the grid draws, and only those.
2931
+ *
2932
+ * Every component that renders a `<lucide-icon>` picks this set itself, so the
2933
+ * icons travel with the component. That matters now the components are
2934
+ * standalone: a consumer importing `OneGridComponent` directly never touches
2935
+ * `OneGridModule`, and previously got a runtime error — "The 'chevron-first'
2936
+ * icon has not been provided by any available icon providers" — the moment the
2937
+ * pagination bar rendered.
2938
+ *
2939
+ * Eight icons rather than `LucideAngularModule.pick(icons)`, which registered
2940
+ * the entire Lucide set. Nothing else in the grid uses one, and the rest were
2941
+ * dead weight in every consumer's bundle.
2942
+ */
2943
+ const ONE_GRID_ICONS = {
2944
+ ArrowDownUp,
2945
+ ArrowDownZA,
2946
+ ArrowUpAZ,
2947
+ ChevronFirst,
2948
+ ChevronLast,
2949
+ ChevronLeft,
2950
+ ChevronRight,
2951
+ GripVertical,
2952
+ };
2953
+ /**
2954
+ * Providers that register {@link ONE_GRID_ICONS} for one standalone component.
2955
+ *
2956
+ * `LucideAngularModule.pick()` cannot be used here: it returns a
2957
+ * `ModuleWithProviders`, and a standalone component's `imports` only accepts
2958
+ * components, directives, pipes and NgModule classes. This is the same
2959
+ * registration `pick` performs, expressed as providers so it can sit on the
2960
+ * component instead.
2961
+ */
2962
+ const ONE_GRID_ICON_PROVIDERS = [
2963
+ { provide: LUCIDE_ICONS, multi: true, useValue: new LucideIconProvider(ONE_GRID_ICONS) },
2964
+ ];
2354
2965
 
2355
2966
  class HeaderCheckboxCheckedPipe {
2356
2967
  transform(rowNodes, selectionMode, state) {
2357
2968
  return rowNodes?.length > 0 && selectionMode === 'multiple' && state === 'checked';
2358
2969
  }
2359
2970
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2360
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, name: "headerCheckboxChecked" }); }
2971
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, isStandalone: true, name: "headerCheckboxChecked" }); }
2361
2972
  }
2362
2973
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, decorators: [{
2363
2974
  type: Pipe,
2364
2975
  args: [{
2365
- standalone: false,
2976
+ standalone: true,
2366
2977
  name: 'headerCheckboxChecked'
2367
2978
  }]
2368
2979
  }] });
2369
2980
 
2981
+ class HeaderCheckboxDisabledPipe {
2982
+ transform(rowNodes, selectionMode, allowedEdit) {
2983
+ return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
2984
+ }
2985
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2986
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, isStandalone: true, name: "headerCheckboxDisabled" }); }
2987
+ }
2988
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, decorators: [{
2989
+ type: Pipe,
2990
+ args: [{
2991
+ standalone: true,
2992
+ name: 'headerCheckboxDisabled'
2993
+ }]
2994
+ }] });
2995
+
2370
2996
  class HeaderCheckboxIndeterminatePipe {
2371
2997
  transform(rowNodes, selectionMode, state) {
2372
2998
  return rowNodes?.length > 0 && selectionMode === 'multiple' && state === 'indeterminate';
2373
2999
  }
2374
3000
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2375
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, name: "headerCheckboxIndeterminate" }); }
3001
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, isStandalone: true, name: "headerCheckboxIndeterminate" }); }
2376
3002
  }
2377
3003
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, decorators: [{
2378
3004
  type: Pipe,
2379
3005
  args: [{
2380
- standalone: false,
3006
+ standalone: true,
2381
3007
  name: 'headerCheckboxIndeterminate'
2382
3008
  }]
2383
3009
  }] });
@@ -2435,36 +3061,200 @@ class OneGridCheckboxHeaderComponent {
2435
3061
  }
2436
3062
  }
2437
3063
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxHeaderComponent, deps: [{ token: OneGridEventService }, { token: OneGridSelectionService }], target: i0.ɵɵFactoryTarget.Component }); }
2438
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: { api: "api", allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>", styles: [""], dependencies: [{ kind: "pipe", type: HeaderCheckboxDisabledPipe, name: "headerCheckboxDisabled" }, { kind: "pipe", type: HeaderCheckboxCheckedPipe, name: "headerCheckboxChecked" }, { kind: "pipe", type: HeaderCheckboxIndeterminatePipe, name: "headerCheckboxIndeterminate" }] }); }
3064
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxHeaderComponent, isStandalone: true, selector: "one-grid-checkbox-header", inputs: { api: "api", allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>", styles: [""], dependencies: [{ kind: "pipe", type: HeaderCheckboxCheckedPipe, name: "headerCheckboxChecked" }, { kind: "pipe", type: HeaderCheckboxDisabledPipe, name: "headerCheckboxDisabled" }, { kind: "pipe", type: HeaderCheckboxIndeterminatePipe, name: "headerCheckboxIndeterminate" }] }); }
2439
3065
  }
2440
3066
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxHeaderComponent, decorators: [{
2441
3067
  type: Component,
2442
- args: [{ standalone: false, selector: 'one-grid-checkbox-header', template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>" }]
3068
+ args: [{ standalone: true, imports: [HeaderCheckboxCheckedPipe, HeaderCheckboxDisabledPipe, HeaderCheckboxIndeterminatePipe], selector: 'one-grid-checkbox-header', template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>" }]
2443
3069
  }], ctorParameters: () => [{ type: OneGridEventService }, { type: OneGridSelectionService }], propDecorators: { api: [{
2444
3070
  type: Input
2445
3071
  }], allowedEdit: [{
2446
3072
  type: Input
2447
3073
  }] } });
2448
3074
 
2449
- class ColumnSortStatePipe {
2450
- // `readonly`, because this is fed straight from `api.sortModel`, which is
2451
- // now a readonly view rather than the mutable array it used to hand out.
2452
- transform(field, sortModel) {
2453
- if (!sortModel || !field)
2454
- return null;
2455
- return sortModel.find(s => s.colId === field)?.sort ?? null;
2456
- }
2457
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2458
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, name: "columnSortState" }); }
2459
- }
2460
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, decorators: [{
3075
+ /**
3076
+ * The grid's default column menu, opened by the header's ⋮ button when the
3077
+ * grid sets `[headerMenu]="true"`.
3078
+ *
3079
+ * Entries follow the column: sorting only on a sortable column and only the
3080
+ * transitions that make sense from the current state, pinning without the
3081
+ * side it is already on, hiding never for the last visible column. The menu
3082
+ * only reports the chosen action — the header executes it against the API,
3083
+ * for the same reason the cell option menu does not mutate rows itself.
3084
+ *
3085
+ * Keyboard behaviour is the menu pattern the cell option menu established:
3086
+ * focus moves in on open, arrows rove, Escape and outside clicks close.
3087
+ */
3088
+ class OneGridHeaderMenuComponent {
3089
+ get menuLabel() {
3090
+ return `Column menu for ${this.column?.headerName ?? this.column?.field ?? 'this column'}`;
3091
+ }
3092
+ constructor(elementRef) {
3093
+ this.elementRef = elementRef;
3094
+ this.column = null;
3095
+ /** The column's current sort, so the menu offers only the other states. */
3096
+ this.sortState = null;
3097
+ /** False when this is the last visible column, which must stay visible. */
3098
+ this.canHide = true;
3099
+ this.menuAction = new EventEmitter();
3100
+ this.menuClose = new EventEmitter();
3101
+ this.entries = [];
3102
+ this.activeIndex = 0;
3103
+ }
3104
+ /**
3105
+ * Built once on open rather than in a getter: the entries feed the roving
3106
+ * tabindex, and a list that re-derives mid-interaction would move focus.
3107
+ */
3108
+ ngOnInit() {
3109
+ const entries = [];
3110
+ const column = this.column;
3111
+ if (column?._resolvedSortable && column.field) {
3112
+ if (this.sortState !== 'asc')
3113
+ entries.push({ action: 'sortAsc', label: 'Sort ascending' });
3114
+ if (this.sortState !== 'desc')
3115
+ entries.push({ action: 'sortDesc', label: 'Sort descending' });
3116
+ if (this.sortState)
3117
+ entries.push({ action: 'clearSort', label: 'Clear sort' });
3118
+ }
3119
+ if (column?.pinned !== 'left')
3120
+ entries.push({ action: 'pinLeft', label: 'Pin left' });
3121
+ if (column?.pinned !== 'right')
3122
+ entries.push({ action: 'pinRight', label: 'Pin right' });
3123
+ if (column?.pinned)
3124
+ entries.push({ action: 'unpin', label: 'Unpin' });
3125
+ if (this.canHide)
3126
+ entries.push({ action: 'hide', label: 'Hide column' });
3127
+ this.entries = entries;
3128
+ }
3129
+ ngAfterViewInit() {
3130
+ this.focusItem(0);
3131
+ }
3132
+ onEntrySelected(entry) {
3133
+ this.menuAction.emit(entry.action);
3134
+ this.menuClose.emit();
3135
+ }
3136
+ onItemKeydown(event, index, entry) {
3137
+ switch (event.key) {
3138
+ case 'ArrowDown':
3139
+ event.preventDefault();
3140
+ this.focusItem((index + 1) % this.entries.length);
3141
+ return;
3142
+ case 'ArrowUp':
3143
+ event.preventDefault();
3144
+ this.focusItem((index - 1 + this.entries.length) % this.entries.length);
3145
+ return;
3146
+ case 'Home':
3147
+ event.preventDefault();
3148
+ this.focusItem(0);
3149
+ return;
3150
+ case 'End':
3151
+ event.preventDefault();
3152
+ this.focusItem(this.entries.length - 1);
3153
+ return;
3154
+ case 'Enter':
3155
+ case ' ':
3156
+ event.preventDefault();
3157
+ this.onEntrySelected(entry);
3158
+ return;
3159
+ default:
3160
+ return;
3161
+ }
3162
+ }
3163
+ focusItem(index) {
3164
+ if (!this.entries.length)
3165
+ return;
3166
+ this.activeIndex = index;
3167
+ this.items?.get(index)?.nativeElement?.focus();
3168
+ }
3169
+ onDocumentClick(event) {
3170
+ if (!this.elementRef.nativeElement.contains(event.target)) {
3171
+ this.menuClose.emit();
3172
+ }
3173
+ }
3174
+ onEscape() {
3175
+ this.menuClose.emit();
3176
+ }
3177
+ onScroll() {
3178
+ this.menuClose.emit();
3179
+ }
3180
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
3181
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderMenuComponent, isStandalone: true, selector: "one-grid-header-menu", inputs: { column: "column", sortState: "sortState", canHide: "canHide" }, outputs: { menuAction: "menuAction", menuClose: "menuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<div class=\"one-header-menu\" role=\"menu\" [attr.aria-label]=\"menuLabel\">\n @for (entry of entries; let index = $index; track entry.action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"one-header-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onEntrySelected(entry)\"\n (keydown)=\"onItemKeydown($event, index, entry)\">\n <span>{{ entry.label }}</span>\n </button>\n }\n</div>\n" }); }
3182
+ }
3183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderMenuComponent, decorators: [{
3184
+ type: Component,
3185
+ args: [{ selector: 'one-grid-header-menu', standalone: true, template: "<div class=\"one-header-menu\" role=\"menu\" [attr.aria-label]=\"menuLabel\">\n @for (entry of entries; let index = $index; track entry.action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"one-header-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onEntrySelected(entry)\"\n (keydown)=\"onItemKeydown($event, index, entry)\">\n <span>{{ entry.label }}</span>\n </button>\n }\n</div>\n" }]
3186
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
3187
+ type: Input
3188
+ }], sortState: [{
3189
+ type: Input
3190
+ }], canHide: [{
3191
+ type: Input
3192
+ }], menuAction: [{
3193
+ type: Output
3194
+ }], menuClose: [{
3195
+ type: Output
3196
+ }], items: [{
3197
+ type: ViewChildren,
3198
+ args: ['item']
3199
+ }], onDocumentClick: [{
3200
+ type: HostListener,
3201
+ args: ['document:click', ['$event']]
3202
+ }], onEscape: [{
3203
+ type: HostListener,
3204
+ args: ['document:keydown.escape']
3205
+ }], onScroll: [{
3206
+ type: HostListener,
3207
+ args: ['document:scroll']
3208
+ }] } });
3209
+
3210
+ class ColumnSortStatePipe {
3211
+ // `readonly`, because this is fed straight from `api.sortModel`, which is
3212
+ // now a readonly view rather than the mutable array it used to hand out.
3213
+ transform(field, sortModel) {
3214
+ if (!sortModel || !field)
3215
+ return null;
3216
+ return sortModel.find(s => s.colId === field)?.sort ?? null;
3217
+ }
3218
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3219
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, isStandalone: true, name: "columnSortState" }); }
3220
+ }
3221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, decorators: [{
2461
3222
  type: Pipe,
2462
3223
  args: [{
2463
- standalone: false,
3224
+ standalone: true,
2464
3225
  name: 'columnSortState'
2465
3226
  }]
2466
3227
  }] });
2467
3228
 
3229
+ /**
3230
+ * Maps the grid's sort state onto the values `aria-sort` accepts.
3231
+ *
3232
+ * The attribute takes `ascending` / `descending` / `none`, not the `asc` /
3233
+ * `desc` the sort model uses. `none` is only correct on a column that *can* be
3234
+ * sorted — on one that cannot, the attribute is omitted entirely, because
3235
+ * "not sorted" and "not sortable" are different statements.
3236
+ */
3237
+ class AriaSortPipe {
3238
+ transform(sort, sortable) {
3239
+ if (!sortable)
3240
+ return null;
3241
+ if (sort === 'asc')
3242
+ return 'ascending';
3243
+ if (sort === 'desc')
3244
+ return 'descending';
3245
+ return 'none';
3246
+ }
3247
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3248
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, isStandalone: true, name: "ariaSort" }); }
3249
+ }
3250
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, decorators: [{
3251
+ type: Pipe,
3252
+ args: [{
3253
+ standalone: true,
3254
+ name: 'ariaSort',
3255
+ }]
3256
+ }] });
3257
+
2468
3258
  class OneGridHeaderComponent {
2469
3259
  /**
2470
3260
  * Horizontal offset of the body, mirrored here so the header tracks it.
@@ -2480,9 +3270,10 @@ class OneGridHeaderComponent {
2480
3270
  get scrollLeft() {
2481
3271
  return this._scrollLeft;
2482
3272
  }
2483
- constructor(eventService, cdr) {
3273
+ constructor(eventService, cdr, elementRef) {
2484
3274
  this.eventService = eventService;
2485
3275
  this.cdr = cdr;
3276
+ this.elementRef = elementRef;
2486
3277
  this._scrollLeft = 0;
2487
3278
  this.allowedEdit = false;
2488
3279
  this.enableGroupColumnDefs = false;
@@ -2491,11 +3282,51 @@ class OneGridHeaderComponent {
2491
3282
  this.sortModel = [];
2492
3283
  this.subscriptions = new Subscription();
2493
3284
  this.draggedIndex = null;
3285
+ /** Where the drag started, for reporting the completed move as one event. */
3286
+ this.dragSourceIndex = null;
2494
3287
  // Track resize state
2495
3288
  this.resizing = false;
2496
3289
  this.resizeColumnIndex = null;
2497
3290
  this.startX = 0;
2498
3291
  this.startWidth = 0;
3292
+ /**
3293
+ * Render a filter input under each column whose `filterable` resolves true.
3294
+ *
3295
+ * The inputs build a contains-filter per column and hand it to
3296
+ * `api.setFilterModel`, debounced. Fields without a filter input are left
3297
+ * alone: a programmatic filter on a non-filterable column survives typing
3298
+ * here, while the filterable fields belong to the row — last writer wins.
3299
+ */
3300
+ this.filterRow = false;
3301
+ /** Current filter text per field. */
3302
+ this.filterTexts = {};
3303
+ this.filterInput$ = new Subject();
3304
+ /**
3305
+ * Minimum a column can be resized to, by pointer or keyboard.
3306
+ *
3307
+ * Exposed to the template for `aria-valuemin`, so what the resizer announces
3308
+ * and what it enforces cannot drift apart.
3309
+ */
3310
+ this.MIN_COLUMN_WIDTH = 50;
3311
+ /**
3312
+ * The header's ⋮ button was a div with an empty handler — it rendered,
3313
+ * looked interactive, and did nothing at all. Making it keyboard-reachable
3314
+ * without giving it a purpose would only have added a dead tab stop per
3315
+ * column, so it now reports the press the same way every row action does and
3316
+ * the host decides what a column menu means.
3317
+ */
3318
+ this.headerMenuClick = new EventEmitter();
3319
+ /**
3320
+ * Render the grid's default column menu behind the ⋮ button.
3321
+ *
3322
+ * Opt-in: hosts that bound `(headerMenuClick)` to open their own menu keep
3323
+ * exactly the behaviour they had — the intent still emits either way.
3324
+ */
3325
+ this.headerMenu = false;
3326
+ /** The column whose default menu is open, or null. */
3327
+ this.openMenuColumn = null;
3328
+ /** Distance from the header's right edge to the open column's, in px. */
3329
+ this.menuOffsetRight = 0;
2499
3330
  // Arrow function to preserve 'this' context
2500
3331
  this.onResizeMove = (event) => {
2501
3332
  if (!this.resizing || this.resizeColumnIndex === null)
@@ -2503,11 +3334,21 @@ class OneGridHeaderComponent {
2503
3334
  // Calculate how far the mouse has moved
2504
3335
  const deltaX = event.clientX - this.startX;
2505
3336
  // Calculate new width (with minimum of 50px)
2506
- const newWidth = Math.max(50, this.startWidth + deltaX);
2507
- // Update the column width
2508
- this.api.updateColumn(this.columnDefs[this.resizeColumnIndex].id, { width: newWidth });
3337
+ const newWidth = Math.max(this.MIN_COLUMN_WIDTH, this.startWidth + deltaX);
3338
+ // Update the column width. A hand-resized column stops flexing, or the
3339
+ // next reflow would overwrite the width the user is dragging out.
3340
+ this.api.updateColumn(this.columnDefs[this.resizeColumnIndex].id, { width: newWidth, flex: undefined });
2509
3341
  };
2510
3342
  this.onResizeEnd = () => {
3343
+ // One event per completed gesture, not one per mousemove — and none at all
3344
+ // for a click on the handle that never moved.
3345
+ if (this.resizeColumnIndex !== null) {
3346
+ const column = this.columnDefs[this.resizeColumnIndex];
3347
+ const newWidth = column?.width ?? this.startWidth;
3348
+ if (column && newWidth !== this.startWidth) {
3349
+ this.eventService.dispatchEvent('columnResized', { columnId: column.id, newWidth });
3350
+ }
3351
+ }
2511
3352
  // Clean up
2512
3353
  this.resizing = false;
2513
3354
  this.resizeColumnIndex = null;
@@ -2517,7 +3358,30 @@ class OneGridHeaderComponent {
2517
3358
  document.removeEventListener('mouseup', this.onResizeEnd);
2518
3359
  };
2519
3360
  }
3361
+ /** Debounce for the filter row, in ms. */
3362
+ static { this.FILTER_DEBOUNCE = 200; }
3363
+ onFilterInput(col, event) {
3364
+ this.filterTexts[col.field] = event.target.value;
3365
+ this.filterInput$.next();
3366
+ }
3367
+ applyFilterRow() {
3368
+ const managed = new Set(this.columnDefs
3369
+ .filter(column => column._resolvedFilterable && column.field)
3370
+ .map(column => column.field));
3371
+ const preserved = this.api.getFilterModel().filter(entry => !managed.has(entry.field));
3372
+ const own = Object.entries(this.filterTexts)
3373
+ .filter(([field, text]) => managed.has(field) && text.trim() !== '')
3374
+ .map(([field, text]) => ({
3375
+ field,
3376
+ logicOperator: 'AND',
3377
+ conditions: [{ operator: 'contains', value: text.trim() }],
3378
+ }));
3379
+ this.api.setFilterModel([...preserved, ...own]);
3380
+ }
2520
3381
  ngOnInit() {
3382
+ this.subscriptions.add(this.filterInput$
3383
+ .pipe(debounceTime(OneGridHeaderComponent.FILTER_DEBOUNCE))
3384
+ .subscribe(() => this.applyFilterRow()));
2521
3385
  // Listen Column Changed Event
2522
3386
  this.subscriptions.add(this.eventService.addEventListener('columnChanged', (payload) => {
2523
3387
  const { groups, columns } = payload;
@@ -2540,7 +3404,100 @@ class OneGridHeaderComponent {
2540
3404
  this.headerViewport.nativeElement.scrollLeft = this._scrollLeft;
2541
3405
  }
2542
3406
  }
2543
- onHeaderMenuOptionClick(columnDef) {
3407
+ onHeaderMenuOptionClick(columnDef, event) {
3408
+ this.headerMenuClick.emit(columnDef);
3409
+ if (!this.headerMenu)
3410
+ return;
3411
+ if (this.openMenuColumn?.id === columnDef.id) {
3412
+ this.openMenuColumn = null;
3413
+ return;
3414
+ }
3415
+ // Right-aligned under the opening column. Measured because the menu
3416
+ // cannot render inside the column — every box in the header clips — so
3417
+ // it sits outside and has to be told where the column is.
3418
+ const columnElement = event?.target?.closest('.one-grid-header-column');
3419
+ const hostElement = this.elementRef.nativeElement;
3420
+ this.menuOffsetRight = columnElement
3421
+ ? Math.max(hostElement.getBoundingClientRect().right - columnElement.getBoundingClientRect().right, 0)
3422
+ : 0;
3423
+ this.openMenuColumn = columnDef;
3424
+ }
3425
+ /** The open column's current sort, so the menu offers the other states. */
3426
+ menuSortState(col) {
3427
+ return this.api.sortModel.find(entry => entry.colId === col.field)?.sort ?? null;
3428
+ }
3429
+ onHeaderMenuAction(action, col) {
3430
+ switch (action) {
3431
+ case 'sortAsc':
3432
+ case 'sortDesc': {
3433
+ // Mirrors a sort-button click: a single-column model. The header's own
3434
+ // cycle state follows, so the sort button keeps cycling from here.
3435
+ this.sortModel = [{ colId: col.field, sort: action === 'sortAsc' ? 'asc' : 'desc' }];
3436
+ this.api.onSortChanged(this.sortModel);
3437
+ return;
3438
+ }
3439
+ case 'clearSort':
3440
+ this.sortModel = [];
3441
+ this.api.onSortChanged(this.sortModel);
3442
+ return;
3443
+ case 'pinLeft':
3444
+ this.api.updateColumn(col.id, { pinned: 'left' });
3445
+ return;
3446
+ case 'pinRight':
3447
+ this.api.updateColumn(col.id, { pinned: 'right' });
3448
+ return;
3449
+ case 'unpin':
3450
+ this.api.updateColumn(col.id, { pinned: undefined });
3451
+ return;
3452
+ case 'hide':
3453
+ this.api.setColumnVisible(col.id, false);
3454
+ return;
3455
+ }
3456
+ }
3457
+ closeHeaderMenu() {
3458
+ this.openMenuColumn = null;
3459
+ }
3460
+ /**
3461
+ * Resize from the keyboard.
3462
+ *
3463
+ * The pointer path is a drag, and WCAG 2.5.7 requires a single-pointer
3464
+ * alternative that is not one. Arrows step, shift steps coarser, and
3465
+ * Home/End go to the column's own bounds.
3466
+ */
3467
+ onResizeKeydown(event, columnIndex) {
3468
+ const column = this.columnDefs[columnIndex];
3469
+ if (!column)
3470
+ return;
3471
+ const step = event.shiftKey ? 50 : 10;
3472
+ const current = column.width ?? resolveColumnWidth(column);
3473
+ let width;
3474
+ switch (event.key) {
3475
+ case 'ArrowLeft':
3476
+ width = current - step;
3477
+ break;
3478
+ case 'ArrowRight':
3479
+ width = current + step;
3480
+ break;
3481
+ case 'Home':
3482
+ width = column.minWidth ?? this.MIN_COLUMN_WIDTH;
3483
+ break;
3484
+ case 'End':
3485
+ width = column.maxWidth ?? current + step;
3486
+ break;
3487
+ default: return;
3488
+ }
3489
+ event.preventDefault();
3490
+ const min = Math.max(column.minWidth ?? 0, this.MIN_COLUMN_WIDTH);
3491
+ const max = column.maxWidth ?? Number.POSITIVE_INFINITY;
3492
+ const newWidth = Math.min(Math.max(width, min), max);
3493
+ // Home at the minimum, End at the maximum: already clamped there, nothing
3494
+ // changed, nothing to announce.
3495
+ if (newWidth === current)
3496
+ return;
3497
+ // A hand-resized column stops flexing — otherwise the next reflow would
3498
+ // overwrite the width the user just chose.
3499
+ this.api.updateColumn(column.id, { width: newWidth, flex: undefined });
3500
+ this.eventService.dispatchEvent('columnResized', { columnId: column.id, newWidth });
2544
3501
  }
2545
3502
  onHeaderMenuSortClick(colId) {
2546
3503
  const currentSort = this.sortModel.find(sort => sort.colId === colId)?.sort ?? null;
@@ -2580,6 +3537,7 @@ class OneGridHeaderComponent {
2580
3537
  // Track the dragged column index
2581
3538
  onDragColumnStart(event, index) {
2582
3539
  this.draggedIndex = index;
3540
+ this.dragSourceIndex = index;
2583
3541
  // Optional: Set drag image/effect
2584
3542
  if (event.dataTransfer) {
2585
3543
  event.dataTransfer.effectAllowed = 'move';
@@ -2597,11 +3555,27 @@ class OneGridHeaderComponent {
2597
3555
  this.draggedIndex = targetIndex;
2598
3556
  }
2599
3557
  onDragColumnEnd(event) {
2600
- this.draggedIndex = null;
3558
+ this.finishColumnDrag();
2601
3559
  }
2602
3560
  onDropColumn(event) {
2603
3561
  event.preventDefault();
3562
+ this.finishColumnDrag();
3563
+ }
3564
+ /**
3565
+ * The reorder itself already happened, step by step, during dragover — this
3566
+ * only reports the completed move. Both `drop` and `dragend` funnel here and
3567
+ * both can fire for one gesture; nulling the indexes makes the second call a
3568
+ * no-op, so one gesture reports once.
3569
+ */
3570
+ finishColumnDrag() {
3571
+ if (this.draggedIndex !== null && this.dragSourceIndex !== null && this.draggedIndex !== this.dragSourceIndex) {
3572
+ const column = this.columnDefs[this.draggedIndex];
3573
+ if (column) {
3574
+ this.eventService.dispatchEvent('columnMoved', { columnId: column.id, toIndex: this.draggedIndex });
3575
+ }
3576
+ }
2604
3577
  this.draggedIndex = null;
3578
+ this.dragSourceIndex = null;
2605
3579
  }
2606
3580
  ngOnDestroy() {
2607
3581
  // This completed an unused Subject and left the real subscription running.
@@ -2614,13 +3588,22 @@ class OneGridHeaderComponent {
2614
3588
  this.onResizeEnd();
2615
3589
  }
2616
3590
  }
2617
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, deps: [{ token: OneGridEventService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
2618
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: { scrollLeft: "scrollLeft", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", api: "api" }, viewQueries: [{ propertyName: "headerViewport", first: true, predicate: ["headerViewport"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"one-grid-header\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n </div>\n</div>\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <div class=\"og-resize-handle\" (mousedown)=\"onResizeStart($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </div>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuOptionClick(col)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </div>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: ["api", "allowedEdit"] }, { kind: "pipe", type: ColumnSortStatePipe, name: "columnSortState" }] }); }
3591
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, deps: [{ token: OneGridEventService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
3592
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderComponent, isStandalone: true, selector: "one-grid-header", inputs: { scrollLeft: "scrollLeft", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", api: "api", filterRow: "filterRow", headerMenu: "headerMenu" }, outputs: { headerMenuClick: "headerMenuClick" }, host: { attributes: { "role": "presentation" } }, providers: [ONE_GRID_ICON_PROVIDERS], viewQueries: [{ propertyName: "headerViewport", first: true, predicate: ["headerViewport"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"one-grid-header\" role=\"rowgroup\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" role=\"presentation\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\" role=\"presentation\">\n <div class=\"one-header-row\" role=\"row\" aria-rowindex=\"1\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n\n <!-- Filter Row (grid opt-in). Lives inside the header viewport so it\n scrolls with the columns and its pinned cells stick the same way. -->\n @if (filterRow) {\n <div class=\"one-filter-row\" role=\"row\" aria-rowindex=\"2\">\n @for (col of columnDefs; track col.id) {\n <div class=\"one-filter-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\">\n @if (col._resolvedFilterable && col.field) {\n <input class=\"one-filter-input\"\n type=\"text\"\n [attr.aria-label]=\"'Filter ' + (col.headerName || col.field)\"\n [value]=\"filterTexts[col.field] ?? ''\"\n (input)=\"onFilterInput(col, $event)\" />\n }\n </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- The default column menu (grid opt-in). A sibling of the header block\n because everything inside it clips with overflow: hidden; the host\n positions it just below, right-aligned to the opening column. -->\n@if (headerMenu && openMenuColumn) {\n <one-grid-header-menu\n [style.right.px]=\"menuOffsetRight\"\n [column]=\"openMenuColumn\"\n [sortState]=\"menuSortState(openMenuColumn)\"\n [canHide]=\"columnDefs.length > 1\"\n (menuAction)=\"onHeaderMenuAction($event, openMenuColumn)\"\n (menuClose)=\"closeHeaderMenu()\" />\n}\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.aria-sort]=\"col.field | columnSortState:api.sortModel | ariaSort:col._resolvedSortable\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <!-- `separator` is the ARIA window-splitter pattern, which is\n what a column resizer is. Focusable and arrow-operable\n because WCAG 2.5.7 requires everything achievable by\n dragging to have a non-dragging alternative. -->\n <div\n class=\"og-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'Resize ' + (col.headerName || col.field)\"\n [attr.aria-valuenow]=\"col.width\"\n [attr.aria-valuemin]=\"col.minWidth || MIN_COLUMN_WIDTH\"\n [attr.aria-valuemax]=\"col.maxWidth || null\"\n (mousedown)=\"onResizeStart($event, colIndex)\"\n (keydown)=\"onResizeKeydown($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <!-- A button, not a div with a click handler: sorting was\n pointer-only. The current state is not repeated here \u2014\n it is on the columnheader's `aria-sort`, and saying it\n twice makes a screen reader read it twice. -->\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Sort by ' + (col.headerName || col.field)\"\n [title]=\"'Sort by ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </button>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Options for ' + (col.headerName || col.field)\"\n [attr.aria-haspopup]=\"headerMenu ? 'menu' : null\"\n [attr.aria-expanded]=\"headerMenu ? openMenuColumn?.id === col.id : null\"\n [title]=\"'Options for ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuOptionClick(col, $event)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}one-grid-header-menu{position:absolute;top:100%;z-index:1000}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: ["api", "allowedEdit"] }, { kind: "component", type: OneGridHeaderMenuComponent, selector: "one-grid-header-menu", inputs: ["column", "sortState", "canHide"], outputs: ["menuAction", "menuClose"] }, { kind: "pipe", type: ColumnSortStatePipe, name: "columnSortState" }, { kind: "pipe", type: AriaSortPipe, name: "ariaSort" }] }); }
2619
3593
  }
2620
3594
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, decorators: [{
2621
3595
  type: Component,
2622
- args: [{ standalone: false, selector: 'one-grid-header', template: "<div class=\"one-grid-header\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n </div>\n</div>\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <div class=\"og-resize-handle\" (mousedown)=\"onResizeStart($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </div>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuOptionClick(col)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </div>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n" }]
2623
- }], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }], propDecorators: { headerViewport: [{
3596
+ args: [{ standalone: true, imports: [
3597
+ NgClass,
3598
+ NgStyle,
3599
+ NgTemplateOutlet,
3600
+ LucideAngularModule,
3601
+ OneGridCheckboxHeaderComponent,
3602
+ OneGridHeaderMenuComponent,
3603
+ ColumnSortStatePipe,
3604
+ AriaSortPipe,
3605
+ ], selector: 'one-grid-header', host: { 'role': 'presentation' }, providers: [ONE_GRID_ICON_PROVIDERS], template: "<div class=\"one-grid-header\" role=\"rowgroup\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" role=\"presentation\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\" role=\"presentation\">\n <div class=\"one-header-row\" role=\"row\" aria-rowindex=\"1\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n\n <!-- Filter Row (grid opt-in). Lives inside the header viewport so it\n scrolls with the columns and its pinned cells stick the same way. -->\n @if (filterRow) {\n <div class=\"one-filter-row\" role=\"row\" aria-rowindex=\"2\">\n @for (col of columnDefs; track col.id) {\n <div class=\"one-filter-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\">\n @if (col._resolvedFilterable && col.field) {\n <input class=\"one-filter-input\"\n type=\"text\"\n [attr.aria-label]=\"'Filter ' + (col.headerName || col.field)\"\n [value]=\"filterTexts[col.field] ?? ''\"\n (input)=\"onFilterInput(col, $event)\" />\n }\n </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- The default column menu (grid opt-in). A sibling of the header block\n because everything inside it clips with overflow: hidden; the host\n positions it just below, right-aligned to the opening column. -->\n@if (headerMenu && openMenuColumn) {\n <one-grid-header-menu\n [style.right.px]=\"menuOffsetRight\"\n [column]=\"openMenuColumn\"\n [sortState]=\"menuSortState(openMenuColumn)\"\n [canHide]=\"columnDefs.length > 1\"\n (menuAction)=\"onHeaderMenuAction($event, openMenuColumn)\"\n (menuClose)=\"closeHeaderMenu()\" />\n}\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.aria-sort]=\"col.field | columnSortState:api.sortModel | ariaSort:col._resolvedSortable\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <!-- `separator` is the ARIA window-splitter pattern, which is\n what a column resizer is. Focusable and arrow-operable\n because WCAG 2.5.7 requires everything achievable by\n dragging to have a non-dragging alternative. -->\n <div\n class=\"og-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'Resize ' + (col.headerName || col.field)\"\n [attr.aria-valuenow]=\"col.width\"\n [attr.aria-valuemin]=\"col.minWidth || MIN_COLUMN_WIDTH\"\n [attr.aria-valuemax]=\"col.maxWidth || null\"\n (mousedown)=\"onResizeStart($event, colIndex)\"\n (keydown)=\"onResizeKeydown($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <!-- A button, not a div with a click handler: sorting was\n pointer-only. The current state is not repeated here \u2014\n it is on the columnheader's `aria-sort`, and saying it\n twice makes a screen reader read it twice. -->\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Sort by ' + (col.headerName || col.field)\"\n [title]=\"'Sort by ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </button>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Options for ' + (col.headerName || col.field)\"\n [attr.aria-haspopup]=\"headerMenu ? 'menu' : null\"\n [attr.aria-expanded]=\"headerMenu ? openMenuColumn?.id === col.id : null\"\n [title]=\"'Options for ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuOptionClick(col, $event)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}one-grid-header-menu{position:absolute;top:100%;z-index:1000}\n"] }]
3606
+ }], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { headerViewport: [{
2624
3607
  type: ViewChild,
2625
3608
  args: ['headerViewport', { static: true }]
2626
3609
  }], scrollLeft: [{
@@ -2631,6 +3614,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2631
3614
  type: Input
2632
3615
  }], api: [{
2633
3616
  type: Input
3617
+ }], filterRow: [{
3618
+ type: Input
3619
+ }], headerMenuClick: [{
3620
+ type: Output
3621
+ }], headerMenu: [{
3622
+ type: Input
2634
3623
  }] } });
2635
3624
 
2636
3625
  class CellEditablePipe {
@@ -2651,13 +3640,13 @@ class CellEditablePipe {
2651
3640
  return false;
2652
3641
  }
2653
3642
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2654
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, name: "cellEditable" }); }
3643
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, isStandalone: true, name: "cellEditable" }); }
2655
3644
  }
2656
3645
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, decorators: [{
2657
3646
  type: Pipe,
2658
3647
  args: [{
2659
3648
  name: 'cellEditable',
2660
- standalone: false
3649
+ standalone: true
2661
3650
  }]
2662
3651
  }] });
2663
3652
 
@@ -2671,29 +3660,18 @@ class OneGridCheckboxComponent {
2671
3660
  onToggleSelection(event) {
2672
3661
  const isChecked = event.target.checked;
2673
3662
  const { rowId, rowIndex, data } = this.node;
2674
- const selectionMode = this.selectionService.getSelectionMode();
2675
- if (selectionMode === 'single') {
2676
- this.api.deselectAll();
2677
- if (isChecked) {
2678
- this.node.setSelected(true);
2679
- }
2680
- this.api.toggleSelection(rowId, isChecked);
2681
- }
2682
- else {
2683
- // Multi-select mode
2684
- this.selectionService.toggleSelection(rowId, isChecked);
2685
- this.node.setSelected(isChecked);
2686
- }
2687
- this.node.cellStates = { ...this.node.cellStates };
2688
- this.eventService.dispatchEvent('selectionChanged', { rowIndex, selected: isChecked, data, node: this.node });
3663
+ // The mode rule (single clears the rest, multiple toggles) lives on the api
3664
+ // now, so pressing Space on a focused row and clicking this checkbox cannot
3665
+ // drift apart. It dispatches `selectionChanged` itself.
3666
+ this.api.toggleRowSelection(rowId, isChecked);
2689
3667
  this.onRowSelection.emit({ rowIndex, selected: isChecked, data, node: this.node });
2690
3668
  }
2691
3669
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxComponent, deps: [{ token: OneGridSelectionService }, { token: OneGridEventService }], target: i0.ɵɵFactoryTarget.Component }); }
2692
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: { node: "node", allowedEdit: "allowedEdit", api: "api" }, outputs: { onRowSelection: "onRowSelection" }, ngImport: i0, template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>", styles: [""] }); }
3670
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxComponent, isStandalone: true, selector: "one-grid-checkbox", inputs: { node: "node", allowedEdit: "allowedEdit", api: "api" }, outputs: { onRowSelection: "onRowSelection" }, ngImport: i0, template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>", styles: [""] }); }
2693
3671
  }
2694
3672
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxComponent, decorators: [{
2695
3673
  type: Component,
2696
- args: [{ standalone: false, selector: 'one-grid-checkbox', template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>" }]
3674
+ args: [{ standalone: true, selector: 'one-grid-checkbox', template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>" }]
2697
3675
  }], ctorParameters: () => [{ type: OneGridSelectionService }, { type: OneGridEventService }], propDecorators: { node: [{
2698
3676
  type: Input
2699
3677
  }], allowedEdit: [{
@@ -2704,67 +3682,220 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2704
3682
  type: Output
2705
3683
  }] } });
2706
3684
 
2707
- class OneGridRowActionComponent {
2708
- constructor(cellService) {
2709
- this.cellService = cellService;
2710
- this.allowedEdit = false;
3685
+ class OneGridCellGroupRendererComponent {
3686
+ constructor() {
2711
3687
  this.masterDetail = false;
2712
- this.masterNode = null;
2713
- this.gridType = null;
2714
- this.context = null;
2715
- /**
2716
- * Bind Event to Parent Component
2717
- */
2718
- this.onRowGotoDetail = new EventEmitter();
2719
- this.onRowAddNew = new EventEmitter();
2720
- this.onRowView = new EventEmitter();
2721
- this.onRowLocation = new EventEmitter();
2722
- this.onRowDocument = new EventEmitter();
2723
- this.onRowCopy = new EventEmitter();
2724
- this.onRowDelete = new EventEmitter();
2725
- this.onRowGotoList = new EventEmitter();
2726
- this.onRowEdit = new EventEmitter();
2727
- this.onRowPrint = new EventEmitter();
2728
- this.onRowHistory = new EventEmitter();
2729
- this.onRowOption = new EventEmitter();
2730
- this.onRowLanguage = new EventEmitter();
2731
- this.onRowCellOption = new EventEmitter();
3688
+ this.expansionChanged = new EventEmitter();
3689
+ }
3690
+ /**
3691
+ * Presentational only: report the click and let the body, which holds the
3692
+ * API, perform the toggle. The click is stopped from bubbling so it is not
3693
+ * also read as a cell click by the cell underneath.
3694
+ */
3695
+ invokeChangedClick(event) {
3696
+ event.stopPropagation();
3697
+ this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
3698
+ }
3699
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3700
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellGroupRendererComponent, isStandalone: true, selector: "one-grid-cell-group-renderer", inputs: { node: "node", masterDetail: "masterDetail", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent" }, outputs: { expansionChanged: "expansionChanged" }, ngImport: i0, template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n", styles: [""] }); }
3701
+ }
3702
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
3703
+ type: Component,
3704
+ args: [{ standalone: true, selector: 'one-grid-cell-group-renderer', template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n" }]
3705
+ }], propDecorators: { node: [{
3706
+ type: Input
3707
+ }], masterDetail: [{
3708
+ type: Input
3709
+ }], detailCellRendererParams: [{
3710
+ type: Input
3711
+ }], detailRendererComponent: [{
3712
+ type: Input
3713
+ }], expansionChanged: [{
3714
+ type: Output
3715
+ }] } });
3716
+
3717
+ class OneGridCellOptionMenuComponent {
3718
+ get menuLabel() {
3719
+ return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
3720
+ }
3721
+ constructor(elementRef) {
3722
+ this.elementRef = elementRef;
3723
+ this.column = null;
3724
+ this.node = null;
3725
+ this.options = [];
3726
+ this.cellOptionMenuSelect = new EventEmitter();
3727
+ this.cellOptionMenuClose = new EventEmitter();
2732
3728
  /**
2733
- * Accessible names for the action buttons.
2734
- *
2735
- * Every one of them is an icon with no text, and none carried a label — a
2736
- * screen reader announced "button" and nothing more, fourteen times per row.
2737
- * These are also the `title`, so the icons are identifiable by pointer too.
3729
+ * Roving tabindex: one item is tabbable and the arrows move which. Standard
3730
+ * for a menu — a list of separately tabbable buttons makes a keyboard user
3731
+ * tab through every option to get past the menu.
2738
3732
  */
2739
- this.labels = {
2740
- gotoDetail: 'Open detail',
2741
- addNew: 'Add new row',
2742
- view: 'View row',
2743
- location: 'Show location',
2744
- document: 'Open document',
2745
- copy: 'Copy row',
2746
- delete: 'Delete row',
2747
- gotoList: 'Open list',
2748
- edit: 'Edit row',
2749
- print: 'Print row',
2750
- history: 'View history',
2751
- option: 'Row options',
2752
- language: 'Change language',
2753
- cellOption: 'Cell options',
2754
- };
3733
+ this.activeIndex = 0;
2755
3734
  }
2756
3735
  /**
2757
- * Whether this column's action buttons are hidden for this row.
3736
+ * Move focus into the menu once it exists.
2758
3737
  *
2759
- * The templates read `column?.hideActionButton` directly, which is truthy for
2760
- * any function so a predicate asking to hide the buttons showed them, and
2761
- * one asking to show them showed them too. Resolved here instead, with the
2762
- * same params every other row-scoped predicate gets.
3738
+ * Opening a menu and leaving focus on the trigger strands a keyboard user:
3739
+ * the menu is on screen and nothing they press reaches it.
2763
3740
  */
2764
- get hideActionButton() {
2765
- return this.cellService.hideActionButtonFn(this.column, this.node);
3741
+ ngAfterViewInit() {
3742
+ this.focusItem(0);
2766
3743
  }
2767
- /**
3744
+ onItemKeydown(event, index, option) {
3745
+ switch (event.key) {
3746
+ case 'ArrowDown':
3747
+ event.preventDefault();
3748
+ this.focusItem((index + 1) % this.options.length);
3749
+ return;
3750
+ case 'ArrowUp':
3751
+ event.preventDefault();
3752
+ this.focusItem((index - 1 + this.options.length) % this.options.length);
3753
+ return;
3754
+ case 'Home':
3755
+ event.preventDefault();
3756
+ this.focusItem(0);
3757
+ return;
3758
+ case 'End':
3759
+ event.preventDefault();
3760
+ this.focusItem(this.options.length - 1);
3761
+ return;
3762
+ case 'Enter':
3763
+ case ' ':
3764
+ // A <button> already activates on both, but only once the default runs.
3765
+ // Handling it here keeps the menu's behaviour independent of that.
3766
+ event.preventDefault();
3767
+ this.onOptionSelected(option);
3768
+ return;
3769
+ default:
3770
+ return;
3771
+ }
3772
+ }
3773
+ focusItem(index) {
3774
+ if (!this.options?.length)
3775
+ return;
3776
+ this.activeIndex = index;
3777
+ this.items?.get(index)?.nativeElement?.focus();
3778
+ }
3779
+ // Click outside
3780
+ onDocumentClick(event) {
3781
+ const target = event.target;
3782
+ if (!this.elementRef.nativeElement.contains(target)) {
3783
+ this.close();
3784
+ }
3785
+ }
3786
+ // Escape key
3787
+ onEscape() {
3788
+ this.close();
3789
+ }
3790
+ // Scroll closes menu (prevents misalignment)
3791
+ onScroll() {
3792
+ this.close();
3793
+ }
3794
+ // Option selected
3795
+ onOptionSelected(option) {
3796
+ this.cellOptionMenuSelect.emit({
3797
+ column: this.column,
3798
+ node: this.node,
3799
+ option: option
3800
+ });
3801
+ this.close();
3802
+ }
3803
+ // Single close method
3804
+ close() {
3805
+ this.node.cellOptions[this.column.field] = false;
3806
+ this.cellOptionMenuClose.emit();
3807
+ }
3808
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
3809
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellOptionMenuComponent, isStandalone: true, selector: "one-grid-cell-option-menu", inputs: { column: "column", node: "node", options: "options" }, outputs: { cellOptionMenuSelect: "cellOptionMenuSelect", cellOptionMenuClose: "cellOptionMenuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n", styles: [""] }); }
3810
+ }
3811
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
3812
+ type: Component,
3813
+ args: [{ selector: 'one-grid-cell-option-menu', standalone: true, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n" }]
3814
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
3815
+ type: Input
3816
+ }], node: [{
3817
+ type: Input
3818
+ }], options: [{
3819
+ type: Input
3820
+ }], cellOptionMenuSelect: [{
3821
+ type: Output
3822
+ }], cellOptionMenuClose: [{
3823
+ type: Output
3824
+ }], items: [{
3825
+ type: ViewChildren,
3826
+ args: ['item']
3827
+ }], onDocumentClick: [{
3828
+ type: HostListener,
3829
+ args: ['document:click', ['$event']]
3830
+ }], onEscape: [{
3831
+ type: HostListener,
3832
+ args: ['document:keydown.escape']
3833
+ }], onScroll: [{
3834
+ type: HostListener,
3835
+ args: ['document:scroll']
3836
+ }] } });
3837
+
3838
+ class OneGridRowActionComponent {
3839
+ constructor(cellService) {
3840
+ this.cellService = cellService;
3841
+ this.allowedEdit = false;
3842
+ this.masterDetail = false;
3843
+ this.masterNode = null;
3844
+ this.gridType = null;
3845
+ this.context = null;
3846
+ /**
3847
+ * Bind Event to Parent Component
3848
+ */
3849
+ this.onRowGotoDetail = new EventEmitter();
3850
+ this.onRowAddNew = new EventEmitter();
3851
+ this.onRowView = new EventEmitter();
3852
+ this.onRowLocation = new EventEmitter();
3853
+ this.onRowDocument = new EventEmitter();
3854
+ this.onRowCopy = new EventEmitter();
3855
+ this.onRowDelete = new EventEmitter();
3856
+ this.onRowGotoList = new EventEmitter();
3857
+ this.onRowEdit = new EventEmitter();
3858
+ this.onRowPrint = new EventEmitter();
3859
+ this.onRowHistory = new EventEmitter();
3860
+ this.onRowOption = new EventEmitter();
3861
+ this.onRowLanguage = new EventEmitter();
3862
+ this.onRowCellOption = new EventEmitter();
3863
+ /**
3864
+ * Accessible names for the action buttons.
3865
+ *
3866
+ * Every one of them is an icon with no text, and none carried a label — a
3867
+ * screen reader announced "button" and nothing more, fourteen times per row.
3868
+ * These are also the `title`, so the icons are identifiable by pointer too.
3869
+ */
3870
+ this.labels = {
3871
+ gotoDetail: 'Open detail',
3872
+ addNew: 'Add new row',
3873
+ view: 'View row',
3874
+ location: 'Show location',
3875
+ document: 'Open document',
3876
+ copy: 'Copy row',
3877
+ delete: 'Delete row',
3878
+ gotoList: 'Open list',
3879
+ edit: 'Edit row',
3880
+ print: 'Print row',
3881
+ history: 'View history',
3882
+ option: 'Row options',
3883
+ language: 'Change language',
3884
+ cellOption: 'Cell options',
3885
+ };
3886
+ }
3887
+ /**
3888
+ * Whether this column's action buttons are hidden for this row.
3889
+ *
3890
+ * The templates read `column?.hideActionButton` directly, which is truthy for
3891
+ * any function — so a predicate asking to hide the buttons showed them, and
3892
+ * one asking to show them showed them too. Resolved here instead, with the
3893
+ * same params every other row-scoped predicate gets.
3894
+ */
3895
+ get hideActionButton() {
3896
+ return this.cellService.hideActionButtonFn(this.column, this.node);
3897
+ }
3898
+ /**
2768
3899
  * Whether this column's action buttons are disabled for this row.
2769
3900
  *
2770
3901
  * `disable` used to be resolved by `CellDisableResolverPipe`, which built its
@@ -2885,11 +4016,11 @@ class OneGridRowActionComponent {
2885
4016
  this.onRowCellOption.emit(data);
2886
4017
  }
2887
4018
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, deps: [{ token: OneGridCellService }], target: i0.ɵɵFactoryTarget.Component }); }
2888
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: { column: "column", node: "node", allowedEdit: "allowedEdit", masterDetail: "masterDetail", masterNode: "masterNode", cellRendererParams: "cellRendererParams", gridType: "gridType", context: "context" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption" }, ngImport: i0, template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
4019
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridRowActionComponent, isStandalone: true, selector: "one-grid-row-action", inputs: { column: "column", node: "node", allowedEdit: "allowedEdit", masterDetail: "masterDetail", masterNode: "masterNode", cellRendererParams: "cellRendererParams", gridType: "gridType", context: "context" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption" }, ngImport: i0, template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
2889
4020
  }
2890
4021
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, decorators: [{
2891
4022
  type: Component,
2892
- args: [{ standalone: false, selector: 'one-grid-row-action', template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}" }]
4023
+ args: [{ standalone: true, imports: [NgClass], selector: 'one-grid-row-action', template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}" }]
2893
4024
  }], ctorParameters: () => [{ type: OneGridCellService }], propDecorators: { column: [{
2894
4025
  type: Input,
2895
4026
  args: [{ required: true }]
@@ -2939,35 +4070,109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2939
4070
  type: Output
2940
4071
  }] } });
2941
4072
 
2942
- class OneGridCellGroupRendererComponent {
2943
- constructor() {
2944
- this.masterDetail = false;
2945
- this.expansionChanged = new EventEmitter();
4073
+ /** Why the handle is disabled, in words a user can act on. */
4074
+ const SUPPRESSED_TEXT = {
4075
+ sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
4076
+ filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
4077
+ treeData: 'Rows cannot be reordered in a tree grid.',
4078
+ };
4079
+ /**
4080
+ * The grab handle for one row.
4081
+ *
4082
+ * A real `<button>`, not a styled div: it has to be focusable and operable from
4083
+ * the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
4084
+ * to have a non-dragging alternative. Space picks the row up, the arrows move
4085
+ * it, space drops it and escape puts it back.
4086
+ *
4087
+ * When reordering is unavailable the handle is disabled and says why, rather
4088
+ * than disappearing — a control that vanishes when the user sorts looks like a
4089
+ * bug, and one that silently does nothing is worse.
4090
+ */
4091
+ class OneGridRowDragHandleComponent {
4092
+ constructor(dragService) {
4093
+ this.dragService = dragService;
4094
+ /** Non-null when the grid's order is not its own; see the model docs. */
4095
+ this.suppressedReason = null;
4096
+ this.dragEnd = new EventEmitter();
4097
+ }
4098
+ get disabled() {
4099
+ return this.suppressedReason !== null;
4100
+ }
4101
+ get label() {
4102
+ if (this.suppressedReason)
4103
+ return SUPPRESSED_TEXT[this.suppressedReason];
4104
+ return this.grabbed
4105
+ ? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
4106
+ : 'Drag to reorder row, or press space to move it with the arrow keys.';
4107
+ }
4108
+ /** True while this specific row is the one being moved. */
4109
+ get grabbed() {
4110
+ return this.dragService.state?.rowId === this.node.rowId;
4111
+ }
4112
+ onPointerDown(event) {
4113
+ if (this.disabled)
4114
+ return;
4115
+ // The body starts a cell range selection on mousedown; the handle is not
4116
+ // part of that, and a press here must not also select.
4117
+ event.stopPropagation();
4118
+ this.dragService.startPointerDrag(event, this.node, { onEnd: (result) => this.emit(result) });
4119
+ }
4120
+ onKeyDown(event) {
4121
+ if (this.disabled)
4122
+ return;
4123
+ switch (event.key) {
4124
+ case ' ':
4125
+ case 'Enter':
4126
+ event.preventDefault();
4127
+ this.dragService.toggleKeyboardDrag(this.node, { onEnd: (result) => this.emit(result) });
4128
+ return;
4129
+ case 'ArrowUp':
4130
+ if (!this.grabbed)
4131
+ return;
4132
+ event.preventDefault();
4133
+ this.dragService.moveKeyboard(-1);
4134
+ return;
4135
+ case 'ArrowDown':
4136
+ if (!this.grabbed)
4137
+ return;
4138
+ event.preventDefault();
4139
+ this.dragService.moveKeyboard(1);
4140
+ return;
4141
+ case 'Escape':
4142
+ if (!this.grabbed)
4143
+ return;
4144
+ event.preventDefault();
4145
+ this.dragService.cancel();
4146
+ return;
4147
+ default:
4148
+ return;
4149
+ }
2946
4150
  }
2947
4151
  /**
2948
- * Presentational only: report the click and let the body, which holds the
2949
- * API, perform the toggle. The click is stopped from bubbling so it is not
2950
- * also read as a cell click by the cell underneath.
4152
+ * A row that is picked up and dropped where it started produces no event.
4153
+ * Blur while grabbed would otherwise strand the row mid-move, so it cancels.
2951
4154
  */
2952
- invokeChangedClick(event) {
2953
- event.stopPropagation();
2954
- this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
4155
+ onBlur() {
4156
+ if (this.grabbed && this.dragService.state?.source === 'keyboard') {
4157
+ this.dragService.cancel();
4158
+ }
2955
4159
  }
2956
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2957
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: { node: "node", masterDetail: "masterDetail", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent" }, outputs: { expansionChanged: "expansionChanged" }, ngImport: i0, template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n", styles: [""] }); }
4160
+ emit(result) {
4161
+ if (result)
4162
+ this.dragEnd.emit(result);
4163
+ }
4164
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, deps: [{ token: OneGridRowDragService }], target: i0.ɵɵFactoryTarget.Component }); }
4165
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridRowDragHandleComponent, isStandalone: true, selector: "one-grid-row-drag-handle", inputs: { node: "node", suppressedReason: "suppressedReason" }, outputs: { dragEnd: "dragEnd" }, providers: [ONE_GRID_ICON_PROVIDERS], ngImport: i0, template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2958
4166
  }
2959
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
4167
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, decorators: [{
2960
4168
  type: Component,
2961
- args: [{ standalone: false, selector: 'one-grid-cell-group-renderer', template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n" }]
2962
- }], propDecorators: { node: [{
2963
- type: Input
2964
- }], masterDetail: [{
2965
- type: Input
2966
- }], detailCellRendererParams: [{
2967
- type: Input
2968
- }], detailRendererComponent: [{
4169
+ args: [{ standalone: true, imports: [LucideAngularModule], selector: 'one-grid-row-drag-handle', changeDetection: ChangeDetectionStrategy.OnPush, providers: [ONE_GRID_ICON_PROVIDERS], template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"] }]
4170
+ }], ctorParameters: () => [{ type: OneGridRowDragService }], propDecorators: { node: [{
4171
+ type: Input,
4172
+ args: [{ required: true }]
4173
+ }], suppressedReason: [{
2969
4174
  type: Input
2970
- }], expansionChanged: [{
4175
+ }], dragEnd: [{
2971
4176
  type: Output
2972
4177
  }] } });
2973
4178
 
@@ -3020,12 +4225,12 @@ class OneCellInputValidatorDirective {
3020
4225
  }
3021
4226
  }
3022
4227
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellInputValidatorDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
3023
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: { validationType: "validationType", regex: "regex" }, host: { listeners: { "keypress": "onKeyPress($event)", "keydown": "onKeyDown($event)" } }, ngImport: i0 }); }
4228
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellInputValidatorDirective, isStandalone: true, selector: "[oneCellInputValidator]", inputs: { validationType: "validationType", regex: "regex" }, host: { listeners: { "keypress": "onKeyPress($event)", "keydown": "onKeyDown($event)" } }, ngImport: i0 }); }
3024
4229
  }
3025
4230
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellInputValidatorDirective, decorators: [{
3026
4231
  type: Directive,
3027
4232
  args: [{
3028
- standalone: false,
4233
+ standalone: true,
3029
4234
  selector: '[oneCellInputValidator]'
3030
4235
  }]
3031
4236
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { validationType: [{
@@ -3042,408 +4247,146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3042
4247
 
3043
4248
  class OneCellRendererComponentDirective {
3044
4249
  constructor(vcr) {
3045
- this.vcr = vcr;
3046
- this.initialized = false;
3047
- }
3048
- ngOnInit() {
3049
- this.initialized = true;
3050
- this.createOrUpdateComponent(true);
3051
- }
3052
- ngOnChanges(changes) {
3053
- if (!this.initialized)
3054
- return;
3055
- const componentChanged = changes['cellRendererComponent'];
3056
- const paramsChanged = changes['params'];
3057
- if (componentChanged) {
3058
- this.destroyComponent();
3059
- this.createOrUpdateComponent(true);
3060
- }
3061
- else if (paramsChanged) {
3062
- this.updateComponentInputs();
3063
- }
3064
- }
3065
- createOrUpdateComponent(forceCreate = false) {
3066
- if (!this.cellRendererComponent)
3067
- return;
3068
- if (forceCreate || !this.compRef) {
3069
- this.vcr.clear();
3070
- this.compRef = this.vcr.createComponent(this.cellRendererComponent);
3071
- }
3072
- this.updateComponentInputs();
3073
- }
3074
- updateComponentInputs() {
3075
- if (!this.compRef?.instance)
3076
- return;
3077
- const instance = this.compRef.instance;
3078
- if (typeof instance.oneInit === 'function') {
3079
- // The body supplies `value` already, and it honours the column's
3080
- // valueGetter. Only derive it here when nothing did — this used to
3081
- // overwrite unconditionally with the raw field, so a column with both a
3082
- // valueGetter and a component renderer showed the underlying field
3083
- // instead of the value every other part of the grid displays.
3084
- const newParams = { ...this.params };
3085
- if (newParams.value === undefined) {
3086
- newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
3087
- }
3088
- instance.oneInit(newParams);
3089
- }
3090
- else {
3091
- Object.assign(instance, this.params);
3092
- }
3093
- // Optional: call change detector if needed
3094
- // if (this.compRef.changeDetectorRef) {
3095
- // this.compRef.changeDetectorRef.detectChanges();
3096
- // }
3097
- }
3098
- destroyComponent() {
3099
- if (this.compRef) {
3100
- this.compRef.destroy();
3101
- this.compRef = undefined;
3102
- }
3103
- }
3104
- ngOnDestroy() {
3105
- this.destroyComponent();
3106
- }
3107
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
3108
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: { cellRendererComponent: "cellRendererComponent", params: "params" }, usesOnChanges: true, ngImport: i0 }); }
3109
- }
3110
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, decorators: [{
3111
- type: Directive,
3112
- args: [{
3113
- standalone: false,
3114
- selector: '[oneCellRendererComponent]'
3115
- }]
3116
- }], ctorParameters: () => [{ type: i0.ViewContainerRef }], propDecorators: { cellRendererComponent: [{
3117
- type: Input,
3118
- args: [{ required: true }]
3119
- }], params: [{
3120
- type: Input,
3121
- args: [{ required: true }]
3122
- }] } });
3123
-
3124
- /**
3125
- * Reveals a truncated cell in full while the pointer rests on it.
3126
- *
3127
- * Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
3128
- * used to be applied as inline styles here, one of which was
3129
- * `background: var(--ins-card-bg)` — a token from the application this library
3130
- * was extracted from, which no consumer defines. The expanded cell therefore
3131
- * had a transparent background and its text overlapped the next column. The
3132
- * inline rules also set `z-index` on a statically positioned element, where it
3133
- * does nothing.
3134
- *
3135
- * Overflow is measured on hover rather than after every view check. The old
3136
- * version measured on init and on every text change, and reading `scrollWidth`
3137
- * forces a synchronous layout — several hundred of them per render for a
3138
- * normal viewport, to answer a question only the hovered cell ever asks.
3139
- */
3140
- class OneCellTextExpandDirective {
3141
- static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
3142
- static { this.HOVER_DELAY_MS = 800; }
3143
- constructor(cell, renderer) {
3144
- this.cell = cell;
3145
- this.renderer = renderer;
3146
- }
3147
- onMouseEnter() {
3148
- const element = this.cell.nativeElement;
3149
- // Measured here, so the layout read happens once for the cell being
3150
- // hovered instead of for every cell on screen.
3151
- if (element.scrollWidth <= element.clientWidth)
3152
- return;
3153
- this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
3154
- }
3155
- onMouseLeave() {
3156
- this.collapse();
3157
- }
3158
- ngOnDestroy() {
3159
- this.collapse();
3160
- }
3161
- expand() {
3162
- this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
3163
- }
3164
- collapse() {
3165
- clearTimeout(this.timer);
3166
- this.timer = undefined;
3167
- this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
3168
- }
3169
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
3170
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]", host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 }); }
3171
- }
3172
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
3173
- type: Directive,
3174
- args: [{
3175
- standalone: false,
3176
- selector: '[oneCellTextExpand]'
3177
- }]
3178
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
3179
- type: HostListener,
3180
- args: ['mouseenter']
3181
- }], onMouseLeave: [{
3182
- type: HostListener,
3183
- args: ['mouseleave']
3184
- }] } });
3185
-
3186
- class OneGridCellOptionMenuComponent {
3187
- get menuLabel() {
3188
- return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
3189
- }
3190
- constructor(elementRef) {
3191
- this.elementRef = elementRef;
3192
- this.column = null;
3193
- this.node = null;
3194
- this.options = [];
3195
- this.cellOptionMenuSelect = new EventEmitter();
3196
- this.cellOptionMenuClose = new EventEmitter();
3197
- /**
3198
- * Roving tabindex: one item is tabbable and the arrows move which. Standard
3199
- * for a menu — a list of separately tabbable buttons makes a keyboard user
3200
- * tab through every option to get past the menu.
3201
- */
3202
- this.activeIndex = 0;
3203
- }
3204
- /**
3205
- * Move focus into the menu once it exists.
3206
- *
3207
- * Opening a menu and leaving focus on the trigger strands a keyboard user:
3208
- * the menu is on screen and nothing they press reaches it.
3209
- */
3210
- ngAfterViewInit() {
3211
- this.focusItem(0);
3212
- }
3213
- onItemKeydown(event, index, option) {
3214
- switch (event.key) {
3215
- case 'ArrowDown':
3216
- event.preventDefault();
3217
- this.focusItem((index + 1) % this.options.length);
3218
- return;
3219
- case 'ArrowUp':
3220
- event.preventDefault();
3221
- this.focusItem((index - 1 + this.options.length) % this.options.length);
3222
- return;
3223
- case 'Home':
3224
- event.preventDefault();
3225
- this.focusItem(0);
3226
- return;
3227
- case 'End':
3228
- event.preventDefault();
3229
- this.focusItem(this.options.length - 1);
3230
- return;
3231
- case 'Enter':
3232
- case ' ':
3233
- // A <button> already activates on both, but only once the default runs.
3234
- // Handling it here keeps the menu's behaviour independent of that.
3235
- event.preventDefault();
3236
- this.onOptionSelected(option);
3237
- return;
3238
- default:
3239
- return;
3240
- }
3241
- }
3242
- focusItem(index) {
3243
- if (!this.options?.length)
3244
- return;
3245
- this.activeIndex = index;
3246
- this.items?.get(index)?.nativeElement?.focus();
3247
- }
3248
- // Click outside
3249
- onDocumentClick(event) {
3250
- const target = event.target;
3251
- if (!this.elementRef.nativeElement.contains(target)) {
3252
- this.close();
3253
- }
3254
- }
3255
- // Escape key
3256
- onEscape() {
3257
- this.close();
3258
- }
3259
- // Scroll closes menu (prevents misalignment)
3260
- onScroll() {
3261
- this.close();
3262
- }
3263
- // Option selected
3264
- onOptionSelected(option) {
3265
- this.cellOptionMenuSelect.emit({
3266
- column: this.column,
3267
- node: this.node,
3268
- option: option
3269
- });
3270
- this.close();
3271
- }
3272
- // Single close method
3273
- close() {
3274
- this.node.cellOptions[this.column.field] = false;
3275
- this.cellOptionMenuClose.emit();
3276
- }
3277
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
3278
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: { column: "column", node: "node", options: "options" }, outputs: { cellOptionMenuSelect: "cellOptionMenuSelect", cellOptionMenuClose: "cellOptionMenuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n", styles: [""] }); }
3279
- }
3280
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
3281
- type: Component,
3282
- args: [{ selector: 'one-grid-cell-option-menu', standalone: false, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n" }]
3283
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
3284
- type: Input
3285
- }], node: [{
3286
- type: Input
3287
- }], options: [{
3288
- type: Input
3289
- }], cellOptionMenuSelect: [{
3290
- type: Output
3291
- }], cellOptionMenuClose: [{
3292
- type: Output
3293
- }], items: [{
3294
- type: ViewChildren,
3295
- args: ['item']
3296
- }], onDocumentClick: [{
3297
- type: HostListener,
3298
- args: ['document:click', ['$event']]
3299
- }], onEscape: [{
3300
- type: HostListener,
3301
- args: ['document:keydown.escape']
3302
- }], onScroll: [{
3303
- type: HostListener,
3304
- args: ['document:scroll']
3305
- }] } });
3306
-
3307
- /** Why the handle is disabled, in words a user can act on. */
3308
- const SUPPRESSED_TEXT = {
3309
- sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
3310
- filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
3311
- treeData: 'Rows cannot be reordered in a tree grid.',
3312
- };
3313
- /**
3314
- * The grab handle for one row.
3315
- *
3316
- * A real `<button>`, not a styled div: it has to be focusable and operable from
3317
- * the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
3318
- * to have a non-dragging alternative. Space picks the row up, the arrows move
3319
- * it, space drops it and escape puts it back.
3320
- *
3321
- * When reordering is unavailable the handle is disabled and says why, rather
3322
- * than disappearing — a control that vanishes when the user sorts looks like a
3323
- * bug, and one that silently does nothing is worse.
3324
- */
3325
- class OneGridRowDragHandleComponent {
3326
- constructor(dragService) {
3327
- this.dragService = dragService;
3328
- /** Non-null when the grid's order is not its own; see the model docs. */
3329
- this.suppressedReason = null;
3330
- this.dragEnd = new EventEmitter();
3331
- }
3332
- get disabled() {
3333
- return this.suppressedReason !== null;
4250
+ this.vcr = vcr;
4251
+ this.initialized = false;
3334
4252
  }
3335
- get label() {
3336
- if (this.suppressedReason)
3337
- return SUPPRESSED_TEXT[this.suppressedReason];
3338
- return this.grabbed
3339
- ? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
3340
- : 'Drag to reorder row, or press space to move it with the arrow keys.';
4253
+ ngOnInit() {
4254
+ this.initialized = true;
4255
+ this.createOrUpdateComponent(true);
3341
4256
  }
3342
- /** True while this specific row is the one being moved. */
3343
- get grabbed() {
3344
- return this.dragService.state?.rowId === this.node.rowId;
4257
+ ngOnChanges(changes) {
4258
+ if (!this.initialized)
4259
+ return;
4260
+ const componentChanged = changes['cellRendererComponent'];
4261
+ const paramsChanged = changes['params'];
4262
+ if (componentChanged) {
4263
+ this.destroyComponent();
4264
+ this.createOrUpdateComponent(true);
4265
+ }
4266
+ else if (paramsChanged) {
4267
+ this.updateComponentInputs();
4268
+ }
3345
4269
  }
3346
- onPointerDown(event) {
3347
- if (this.disabled)
4270
+ createOrUpdateComponent(forceCreate = false) {
4271
+ if (!this.cellRendererComponent)
3348
4272
  return;
3349
- // The body starts a cell range selection on mousedown; the handle is not
3350
- // part of that, and a press here must not also select.
3351
- event.stopPropagation();
3352
- this.dragService.startPointerDrag(event, this.node, { onEnd: (result) => this.emit(result) });
4273
+ if (forceCreate || !this.compRef) {
4274
+ this.vcr.clear();
4275
+ this.compRef = this.vcr.createComponent(this.cellRendererComponent);
4276
+ }
4277
+ this.updateComponentInputs();
3353
4278
  }
3354
- onKeyDown(event) {
3355
- if (this.disabled)
4279
+ updateComponentInputs() {
4280
+ if (!this.compRef?.instance)
3356
4281
  return;
3357
- switch (event.key) {
3358
- case ' ':
3359
- case 'Enter':
3360
- event.preventDefault();
3361
- this.dragService.toggleKeyboardDrag(this.node, { onEnd: (result) => this.emit(result) });
3362
- return;
3363
- case 'ArrowUp':
3364
- if (!this.grabbed)
3365
- return;
3366
- event.preventDefault();
3367
- this.dragService.moveKeyboard(-1);
3368
- return;
3369
- case 'ArrowDown':
3370
- if (!this.grabbed)
3371
- return;
3372
- event.preventDefault();
3373
- this.dragService.moveKeyboard(1);
3374
- return;
3375
- case 'Escape':
3376
- if (!this.grabbed)
3377
- return;
3378
- event.preventDefault();
3379
- this.dragService.cancel();
3380
- return;
3381
- default:
3382
- return;
4282
+ const instance = this.compRef.instance;
4283
+ if (typeof instance.oneInit === 'function') {
4284
+ // The body supplies `value` already, and it honours the column's
4285
+ // valueGetter. Only derive it here when nothing did — this used to
4286
+ // overwrite unconditionally with the raw field, so a column with both a
4287
+ // valueGetter and a component renderer showed the underlying field
4288
+ // instead of the value every other part of the grid displays.
4289
+ const newParams = { ...this.params };
4290
+ if (newParams.value === undefined) {
4291
+ newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
4292
+ }
4293
+ instance.oneInit(newParams);
4294
+ }
4295
+ else {
4296
+ Object.assign(instance, this.params);
3383
4297
  }
4298
+ // Optional: call change detector if needed
4299
+ // if (this.compRef.changeDetectorRef) {
4300
+ // this.compRef.changeDetectorRef.detectChanges();
4301
+ // }
3384
4302
  }
3385
- /**
3386
- * A row that is picked up and dropped where it started produces no event.
3387
- * Blur while grabbed would otherwise strand the row mid-move, so it cancels.
3388
- */
3389
- onBlur() {
3390
- if (this.grabbed && this.dragService.state?.source === 'keyboard') {
3391
- this.dragService.cancel();
4303
+ destroyComponent() {
4304
+ if (this.compRef) {
4305
+ this.compRef.destroy();
4306
+ this.compRef = undefined;
3392
4307
  }
3393
4308
  }
3394
- emit(result) {
3395
- if (result)
3396
- this.dragEnd.emit(result);
4309
+ ngOnDestroy() {
4310
+ this.destroyComponent();
3397
4311
  }
3398
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, deps: [{ token: OneGridRowDragService }], target: i0.ɵɵFactoryTarget.Component }); }
3399
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: { node: "node", suppressedReason: "suppressedReason" }, outputs: { dragEnd: "dragEnd" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"], dependencies: [{ kind: "component", type: i2$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4312
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
4313
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellRendererComponentDirective, isStandalone: true, selector: "[oneCellRendererComponent]", inputs: { cellRendererComponent: "cellRendererComponent", params: "params" }, usesOnChanges: true, ngImport: i0 }); }
3400
4314
  }
3401
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, decorators: [{
3402
- type: Component,
3403
- args: [{ standalone: false, selector: 'one-grid-row-drag-handle', changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"] }]
3404
- }], ctorParameters: () => [{ type: OneGridRowDragService }], propDecorators: { node: [{
4315
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, decorators: [{
4316
+ type: Directive,
4317
+ args: [{
4318
+ standalone: true,
4319
+ selector: '[oneCellRendererComponent]'
4320
+ }]
4321
+ }], ctorParameters: () => [{ type: i0.ViewContainerRef }], propDecorators: { cellRendererComponent: [{
4322
+ type: Input,
4323
+ args: [{ required: true }]
4324
+ }], params: [{
3405
4325
  type: Input,
3406
4326
  args: [{ required: true }]
3407
- }], suppressedReason: [{
3408
- type: Input
3409
- }], dragEnd: [{
3410
- type: Output
3411
4327
  }] } });
3412
4328
 
3413
- class CellInvalidPipe {
3414
- transform(cellStates, value, column, node, allowedEdit, submitted) {
3415
- if (!column.validationRules?.length) {
3416
- return false;
3417
- }
3418
- return getCellError(column, node, value, node.data) !== null && !(node.editing[column.field]) && !(column?.cellRendererParams?.validateSelectedRowOnly && !node?.selected) && allowedEdit && submitted;
4329
+ /**
4330
+ * Reveals a truncated cell in full while the pointer rests on it.
4331
+ *
4332
+ * Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
4333
+ * used to be applied as inline styles here, one of which was
4334
+ * `background: var(--ins-card-bg)` a token from the application this library
4335
+ * was extracted from, which no consumer defines. The expanded cell therefore
4336
+ * had a transparent background and its text overlapped the next column. The
4337
+ * inline rules also set `z-index` on a statically positioned element, where it
4338
+ * does nothing.
4339
+ *
4340
+ * Overflow is measured on hover rather than after every view check. The old
4341
+ * version measured on init and on every text change, and reading `scrollWidth`
4342
+ * forces a synchronous layout — several hundred of them per render for a
4343
+ * normal viewport, to answer a question only the hovered cell ever asks.
4344
+ */
4345
+ class OneCellTextExpandDirective {
4346
+ static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
4347
+ static { this.HOVER_DELAY_MS = 800; }
4348
+ constructor(cell, renderer) {
4349
+ this.cell = cell;
4350
+ this.renderer = renderer;
3419
4351
  }
3420
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3421
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, name: "cellInvalid" }); }
3422
- }
3423
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, decorators: [{
3424
- type: Pipe,
3425
- args: [{
3426
- standalone: false,
3427
- name: 'cellInvalid'
3428
- }]
3429
- }] });
3430
-
3431
- class CellSelectedPipe {
3432
- transform(selectedCells, rowId, field) {
3433
- if (!selectedCells)
3434
- return false;
3435
- return selectedCells.has(getCellKey(rowId, field));
4352
+ onMouseEnter() {
4353
+ const element = this.cell.nativeElement;
4354
+ // Measured here, so the layout read happens once for the cell being
4355
+ // hovered instead of for every cell on screen.
4356
+ if (element.scrollWidth <= element.clientWidth)
4357
+ return;
4358
+ this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
3436
4359
  }
3437
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3438
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, name: "cellSelected" }); }
4360
+ onMouseLeave() {
4361
+ this.collapse();
4362
+ }
4363
+ ngOnDestroy() {
4364
+ this.collapse();
4365
+ }
4366
+ expand() {
4367
+ this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
4368
+ }
4369
+ collapse() {
4370
+ clearTimeout(this.timer);
4371
+ this.timer = undefined;
4372
+ this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
4373
+ }
4374
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
4375
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, isStandalone: true, selector: "[oneCellTextExpand]", host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 }); }
3439
4376
  }
3440
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, decorators: [{
3441
- type: Pipe,
4377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
4378
+ type: Directive,
3442
4379
  args: [{
3443
- standalone: false,
3444
- name: 'cellSelected'
4380
+ standalone: true,
4381
+ selector: '[oneCellTextExpand]'
3445
4382
  }]
3446
- }] });
4383
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
4384
+ type: HostListener,
4385
+ args: ['mouseenter']
4386
+ }], onMouseLeave: [{
4387
+ type: HostListener,
4388
+ args: ['mouseleave']
4389
+ }] } });
3447
4390
 
3448
4391
  class CellActionButtonEnablePipe {
3449
4392
  transform(params) {
@@ -3454,16 +4397,34 @@ class CellActionButtonEnablePipe {
3454
4397
  return statusValues.some(status => status === 1);
3455
4398
  }
3456
4399
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3457
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }); }
4400
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, isStandalone: true, name: "cellActionButtonEnable" }); }
3458
4401
  }
3459
4402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, decorators: [{
3460
4403
  type: Pipe,
3461
4404
  args: [{
3462
- standalone: false,
4405
+ standalone: true,
3463
4406
  name: 'cellActionButtonEnable'
3464
4407
  }]
3465
4408
  }] });
3466
4409
 
4410
+ class CellInvalidPipe {
4411
+ transform(cellStates, value, column, node, allowedEdit, submitted) {
4412
+ if (!column.validationRules?.length) {
4413
+ return false;
4414
+ }
4415
+ return getCellError(column, node, value, node.data) !== null && !(node.editing[column.field]) && !(column?.cellRendererParams?.validateSelectedRowOnly && !node?.selected) && allowedEdit && submitted;
4416
+ }
4417
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4418
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, isStandalone: true, name: "cellInvalid" }); }
4419
+ }
4420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, decorators: [{
4421
+ type: Pipe,
4422
+ args: [{
4423
+ standalone: true,
4424
+ name: 'cellInvalid'
4425
+ }]
4426
+ }] });
4427
+
3467
4428
  class CellKeyPipe {
3468
4429
  transform(rowId, field) {
3469
4430
  if (!rowId || !field) {
@@ -3472,12 +4433,12 @@ class CellKeyPipe {
3472
4433
  return getCellKey(rowId, field);
3473
4434
  }
3474
4435
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3475
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, name: "cellKey" }); }
4436
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, isStandalone: true, name: "cellKey" }); }
3476
4437
  }
3477
4438
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, decorators: [{
3478
4439
  type: Pipe,
3479
4440
  args: [{
3480
- standalone: false,
4441
+ standalone: true,
3481
4442
  name: 'cellKey'
3482
4443
  }]
3483
4444
  }] });
@@ -3488,16 +4449,33 @@ class CellLevelPaddingPipe {
3488
4449
  return level * factor;
3489
4450
  }
3490
4451
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3491
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, name: "cellLevelPadding" }); }
4452
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, isStandalone: true, name: "cellLevelPadding" }); }
3492
4453
  }
3493
4454
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, decorators: [{
3494
4455
  type: Pipe,
3495
4456
  args: [{
3496
- standalone: false,
4457
+ standalone: true,
3497
4458
  name: 'cellLevelPadding'
3498
4459
  }]
3499
4460
  }] });
3500
4461
 
4462
+ class CellSelectedPipe {
4463
+ transform(selectedCells, rowId, field) {
4464
+ if (!selectedCells)
4465
+ return false;
4466
+ return selectedCells.has(getCellKey(rowId, field));
4467
+ }
4468
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4469
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, isStandalone: true, name: "cellSelected" }); }
4470
+ }
4471
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, decorators: [{
4472
+ type: Pipe,
4473
+ args: [{
4474
+ standalone: true,
4475
+ name: 'cellSelected'
4476
+ }]
4477
+ }] });
4478
+
3501
4479
  class CellStateRendererPipe {
3502
4480
  transform(cellStates, field) {
3503
4481
  const value = cellStates?.[field]?.value;
@@ -3510,12 +4488,12 @@ class CellStateRendererPipe {
3510
4488
  return value;
3511
4489
  }
3512
4490
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3513
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, name: "cellStateRenderer" }); }
4491
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, isStandalone: true, name: "cellStateRenderer" }); }
3514
4492
  }
3515
4493
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, decorators: [{
3516
4494
  type: Pipe,
3517
4495
  args: [{
3518
- standalone: false,
4496
+ standalone: true,
3519
4497
  name: 'cellStateRenderer'
3520
4498
  }]
3521
4499
  }] });
@@ -3532,6 +4510,12 @@ class OneGridBodyComponent {
3532
4510
  this.allowedEdit = true;
3533
4511
  this.enableGroupColumnDefs = false;
3534
4512
  this.submitted = false;
4513
+ /**
4514
+ * `'fullRow'`: a double-click opens every editable cell of the row, Enter or
4515
+ * focus leaving the row commits them together, Escape closes them all
4516
+ * without committing. Default: one cell at a time.
4517
+ */
4518
+ this.editType = null;
3535
4519
  this.context = null;
3536
4520
  /**
3537
4521
  * Pinned rows are bound only so the component re-renders when they change;
@@ -3632,6 +4616,22 @@ class OneGridBodyComponent {
3632
4616
  * MAX_BUFFER_PX keeps roughly twenty rows of overscan either side instead.
3633
4617
  */
3634
4618
  this.MAX_BUFFER_PX = 600;
4619
+ // ---------------------------------------------------------------------------
4620
+ // Cell navigation
4621
+ //
4622
+ // The WAI-ARIA grid pattern: the whole grid is one tab stop, and the arrows
4623
+ // move a focused cell within it. Before this the only tab stop was the scroll
4624
+ // viewport, so a keyboard user could scroll the rows and reach nothing in
4625
+ // them — and a screen reader had no cursor to read a cell from.
4626
+ //
4627
+ // Focus is real DOM focus on a roving tabindex rather than
4628
+ // `aria-activedescendant`, because the rows are virtualised: the element a
4629
+ // descendant id pointed at would keep being destroyed underneath it.
4630
+ // ---------------------------------------------------------------------------
4631
+ /** The cell holding the grid's tab stop. Null until something is focused. */
4632
+ this.activeCell = null;
4633
+ /** The row a shift-extended selection is measured from. */
4634
+ this.selectionAnchorRowId = null;
3635
4635
  /**
3636
4636
  * Where to paint the drop indicator, in pixels from the top of the body.
3637
4637
  *
@@ -3687,13 +4687,20 @@ class OneGridBodyComponent {
3687
4687
  this.columnDefs = [...payload.columns];
3688
4688
  this.cellRenderer = this.columnDefs.some(col => col.cellRendererComponent);
3689
4689
  this.rowWidth = this.columnDefs.reduce((sum, col) => sum + resolveColumnWidth(col), 0);
4690
+ // A column change can alter the width left for flex columns (a hide, a
4691
+ // resize, new defs). Re-flexing only dispatches when a width actually
4692
+ // changes, so this cannot ping-pong.
4693
+ this.applyFlexWidths();
3690
4694
  this.cdr.markForCheck();
3691
4695
  }));
3692
4696
  /**
3693
4697
  * Listen Row Data Changed Event
3694
4698
  */
3695
- this.subscriptions.add(this.eventService.addEventListener('rowDataChanged', ({ nodes }) => {
3696
- this.rowCountChanged.emit(nodes?.length ?? 0);
4699
+ this.subscriptions.add(this.eventService.addEventListener('rowDataChanged', ({ nodes: allNodes }) => {
4700
+ // The full post-filter count, reported before the page slice is taken,
4701
+ // so the host's paging totals see everything.
4702
+ this.rowCountChanged.emit(allNodes?.length ?? 0);
4703
+ const nodes = this.api.pageNodes(allNodes);
3697
4704
  setTimeout(() => {
3698
4705
  this.viewport?.checkViewportSize();
3699
4706
  this.setVirtualScroll(nodes?.length);
@@ -3733,7 +4740,7 @@ class OneGridBodyComponent {
3733
4740
  * Listen Sort Changed Event
3734
4741
  */
3735
4742
  this.subscriptions.add(this.eventService.addEventListener('sortChanged', ({ nodes }) => {
3736
- this.nodes = nodes;
4743
+ this.nodes = this.api.pageNodes(nodes);
3737
4744
  // Sorting takes ownership of the order away from the user, so the handles
3738
4745
  // have to disable in the same frame the rows re-sort.
3739
4746
  this.refreshRowDragSuppressed();
@@ -3747,7 +4754,7 @@ class OneGridBodyComponent {
3747
4754
  * for a move. A reorder is the same nodes in a different order.
3748
4755
  */
3749
4756
  this.subscriptions.add(this.eventService.addEventListener('rowOrderChanged', ({ nodes }) => {
3750
- this.nodes = [...nodes];
4757
+ this.nodes = this.api.pageNodes([...nodes]);
3751
4758
  this.cdr.markForCheck();
3752
4759
  }));
3753
4760
  /**
@@ -4065,13 +5072,28 @@ class OneGridBodyComponent {
4065
5072
  }
4066
5073
  let rowNode = this.api.getRowNodeById(node.rowId);
4067
5074
  if (rowNode) {
4068
- rowNode.editing[column.field] = true;
5075
+ if (this.editType === 'fullRow') {
5076
+ // The whole row edits together, so every cell that would accept its
5077
+ // own double-click opens now. The clicked one still takes focus below.
5078
+ for (const col of this.columnDefs) {
5079
+ if (!col.field)
5080
+ continue;
5081
+ if (this.cellEditablePipe.transform(rowNode.cellStates, col, rowNode, this.cellService.editableFn, this.cellService.updatableFn, this.allowedEdit)) {
5082
+ rowNode.editing[col.field] = true;
5083
+ }
5084
+ }
5085
+ }
5086
+ else {
5087
+ rowNode.editing[column.field] = true;
5088
+ }
4069
5089
  setTimeout(() => {
4070
- const input = document.querySelector(`[data-cell-id="${getCellKey(node.rowId, column.field)}"] input`);
5090
+ const input = document.querySelector(`[data-cell-id="${getCellKey(node.rowId, column.field)}"] input, [data-cell-id="${getCellKey(node.rowId, column.field)}"] select`);
4071
5091
  if (input) {
4072
5092
  input.focus();
4073
5093
  const value = input.value;
4074
- if (value && ["text"].includes(input.type)) {
5094
+ // A select reports type 'select-one', so only a text input gets its
5095
+ // caret placed.
5096
+ if (value && ["text"].includes(input.type) && 'setSelectionRange' in input) {
4075
5097
  input.setSelectionRange(value.length, value.length);
4076
5098
  }
4077
5099
  }
@@ -4081,19 +5103,72 @@ class OneGridBodyComponent {
4081
5103
  /**
4082
5104
  * Save Cell Edit Mode
4083
5105
  */
4084
- stopEdit(node, column, event) {
5106
+ stopEdit(node, column, event) {
5107
+ if (this.editType === 'fullRow') {
5108
+ this.stopRowEditFromEvent(node, event);
5109
+ return;
5110
+ }
5111
+ let rowNode = this.api.getRowNodeById(node.rowId);
5112
+ // The editing guard makes a second commit for one edit a no-op: a select
5113
+ // commits on (change), which closes the editor, and the (blur) that can
5114
+ // follow must not commit again.
5115
+ if (rowNode && rowNode.editing[column.field]) {
5116
+ event.preventDefault();
5117
+ event.stopPropagation();
5118
+ const value = event.target.value ?? '';
5119
+ const oldValue = rowNode.data[column.field];
5120
+ this.setCellValueByRowId(node.rowId, column, value);
5121
+ this.finishEdit(rowNode, column, oldValue);
5122
+ }
5123
+ }
5124
+ /**
5125
+ * Save a `select` editor's choice.
5126
+ *
5127
+ * Separate from `stopEdit` because the value is not the element's string:
5128
+ * the chosen index maps back into `selectOptions`, so the committed value
5129
+ * keeps whatever type the option declared.
5130
+ */
5131
+ stopEditSelect(node, column, event) {
5132
+ if (this.editType === 'fullRow') {
5133
+ // (change) is not a commit in full-row mode — the choice sits in the
5134
+ // select until the row commits and reads every editor at once. Only a
5135
+ // blur that actually leaves the row closes it.
5136
+ if (event.type === 'blur')
5137
+ this.stopRowEditFromEvent(node, event);
5138
+ return;
5139
+ }
4085
5140
  let rowNode = this.api.getRowNodeById(node.rowId);
4086
- if (rowNode) {
4087
- event.preventDefault();
5141
+ if (rowNode && rowNode.editing[column.field]) {
4088
5142
  event.stopPropagation();
4089
- const value = event.target.value ?? '';
4090
- this.setCellValueByRowId(node.rowId, column, value);
4091
- rowNode.cellStates = this.cellService.generateCellStates(rowNode);
4092
- rowNode.editing[column.field] = false;
4093
- this.eventService.dispatchEvent('cellStateChanged', {
4094
- data: rowNode.data,
5143
+ const options = column?.cellRendererParams?.selectOptions ?? [];
5144
+ const option = options[event.target.selectedIndex];
5145
+ if (!option)
5146
+ return;
5147
+ const oldValue = rowNode.data[column.field];
5148
+ rowNode.data[column.field] = option.value;
5149
+ this.finishEdit(rowNode, column, oldValue);
5150
+ }
5151
+ }
5152
+ /** Close the editor and announce the commit. Shared by both editor kinds. */
5153
+ finishEdit(rowNode, column, oldValue) {
5154
+ const newValue = rowNode.data[column.field];
5155
+ rowNode.cellStates = this.cellService.generateCellStates(rowNode);
5156
+ rowNode.editing[column.field] = false;
5157
+ this.eventService.dispatchEvent('cellStateChanged', {
5158
+ data: rowNode.data,
5159
+ node: rowNode,
5160
+ column: column
5161
+ });
5162
+ // Compared after normalisation ('' -> null, number coercion), so a blur
5163
+ // that leaves the value as it was does not announce an edit.
5164
+ if (newValue !== oldValue) {
5165
+ this.eventService.dispatchEvent('cellValueChanged', {
4095
5166
  node: rowNode,
4096
- column: column
5167
+ data: rowNode.data,
5168
+ column: column,
5169
+ field: column.field,
5170
+ oldValue,
5171
+ newValue,
4097
5172
  });
4098
5173
  }
4099
5174
  }
@@ -4103,9 +5178,63 @@ class OneGridBodyComponent {
4103
5178
  cancelEdit(node, column) {
4104
5179
  let rowNode = this.api.getRowNodeById(node.rowId);
4105
5180
  if (rowNode) {
5181
+ if (this.editType === 'fullRow') {
5182
+ // Escape abandons the whole row: every editor closes, nothing commits.
5183
+ Object.keys(rowNode.editing).forEach(field => rowNode.editing[field] = false);
5184
+ return;
5185
+ }
4106
5186
  rowNode.editing[column.field] = false;
4107
5187
  }
4108
5188
  }
5189
+ /**
5190
+ * Full-row commit, routed from any one editor's Enter or blur.
5191
+ *
5192
+ * A blur that lands on another editor in the same row is the user tabbing
5193
+ * between fields, not leaving the row — the editors stay open and their
5194
+ * typed values stay in the DOM until the row commits.
5195
+ */
5196
+ stopRowEditFromEvent(node, event) {
5197
+ if (event.type === 'blur') {
5198
+ const next = event.relatedTarget;
5199
+ if (next?.matches?.('input.one-cell-input, select.one-cell-input')
5200
+ && next.closest?.(`[data-cell-id^="${node.rowId}--"]`)) {
5201
+ return;
5202
+ }
5203
+ }
5204
+ event.preventDefault?.();
5205
+ event.stopPropagation?.();
5206
+ this.stopRowEdit(node);
5207
+ }
5208
+ /** Commit every open editor of one row, reading each from the DOM. */
5209
+ stopRowEdit(node) {
5210
+ const rowNode = this.api.getRowNodeById(node.rowId);
5211
+ if (!rowNode)
5212
+ return;
5213
+ for (const column of this.columnDefs) {
5214
+ const field = column.field;
5215
+ if (!field || !rowNode.editing[field])
5216
+ continue;
5217
+ const key = getCellKey(rowNode.rowId, field);
5218
+ const editor = document.querySelector(`[data-cell-id="${key}"] input, [data-cell-id="${key}"] select`);
5219
+ const oldValue = rowNode.data[field];
5220
+ if (editor?.tagName === 'SELECT') {
5221
+ const options = column.cellRendererParams?.selectOptions ?? [];
5222
+ const option = options[editor.selectedIndex];
5223
+ if (option)
5224
+ rowNode.data[field] = option.value;
5225
+ }
5226
+ else if (editor) {
5227
+ const raw = editor.value ?? '';
5228
+ rowNode.data[field] = raw === ''
5229
+ ? null
5230
+ : column.cellRendererParams?.inputType === 'number' ? Number(raw) : raw;
5231
+ }
5232
+ // finishEdit closes the editor, refreshes the cell state and raises
5233
+ // cellValueChanged when the value moved — one event per changed cell,
5234
+ // the same contract as single-cell editing.
5235
+ this.finishEdit(rowNode, column, oldValue);
5236
+ }
5237
+ }
4109
5238
  /**
4110
5239
  * Set Cell Value
4111
5240
  */
@@ -4151,6 +5280,24 @@ class OneGridBodyComponent {
4151
5280
  },
4152
5281
  });
4153
5282
  this.refreshRowDragSuppressed();
5283
+ // The CDK inserts its own content wrapper between the viewport and the
5284
+ // rows. It has no template of ours to annotate, and left generic it breaks
5285
+ // the `grid > row` ownership the roles depend on.
5286
+ const wrapper = element.querySelector('.cdk-virtual-scroll-content-wrapper');
5287
+ wrapper?.setAttribute('role', 'presentation');
5288
+ // Flex columns size against this viewport, now and whenever it resizes.
5289
+ // applyFlexWidths is a no-op without flex columns and dispatches only on a
5290
+ // real change, so grids that never use flex pay nothing here.
5291
+ this.applyFlexWidths();
5292
+ if (typeof ResizeObserver !== 'undefined') {
5293
+ this.flexResizeObserver = new ResizeObserver(() => this.applyFlexWidths());
5294
+ this.flexResizeObserver.observe(element);
5295
+ }
5296
+ }
5297
+ applyFlexWidths() {
5298
+ const width = this.viewport?.elementRef?.nativeElement?.clientWidth ?? 0;
5299
+ if (width > 0)
5300
+ this.api.applyFlexWidths(width);
4154
5301
  }
4155
5302
  /**
4156
5303
  * The params every per-cell callback receives.
@@ -4174,6 +5321,226 @@ class OneGridBodyComponent {
4174
5321
  rendererParams(node, column) {
4175
5322
  return { ...(column?.cellRendererParams ?? {}), ...this.cellParams(node, column) };
4176
5323
  }
5324
+ /**
5325
+ * `aria-rowindex` is 1-based over the whole grid, not over what is rendered.
5326
+ *
5327
+ * That is the point of it: the body virtualises, so the DOM holds a sliding
5328
+ * window of rows and the index is how a screen reader knows where the window
5329
+ * sits. Row 1 is the header, then any pinned-top rows, then the data.
5330
+ */
5331
+ dataRowAriaIndex(node) {
5332
+ return 2 + this.pinnedTopNodes.length + node.rowIndex;
5333
+ }
5334
+ /** Pinned rows bracket the data: top ones follow the header, bottom ones end the grid. */
5335
+ pinnedRowAriaIndex(node, side) {
5336
+ const index = (side === 'top' ? this.pinnedTopNodes : this.pinnedBottomNodes).indexOf(node);
5337
+ return side === 'top'
5338
+ ? 2 + index
5339
+ : 2 + this.pinnedTopNodes.length + this.nodes.length + index;
5340
+ }
5341
+ /**
5342
+ * Whether rows can be selected at all.
5343
+ *
5344
+ * `aria-selected="false"` on every row of a grid nobody can select announces
5345
+ * a selection affordance that does not exist, so the attribute is omitted
5346
+ * rather than set false.
5347
+ */
5348
+ get rowSelectionEnabled() {
5349
+ return !!this.api?.rowSelection;
5350
+ }
5351
+ isActiveCell(node, column) {
5352
+ if (this.activeCell) {
5353
+ return this.activeCell.rowId === node.rowId && this.activeCell.field === column.field;
5354
+ }
5355
+ // Nothing focused yet, so the first cell holds the tab stop. Without this
5356
+ // fallback a grid nobody has clicked has *no* tabbable cell, and a keyboard
5357
+ // user cannot get into it at all — the roving tabindex has to start
5358
+ // somewhere.
5359
+ return node === this.nodes[0] && column === this.navigableColumns()[0];
5360
+ }
5361
+ /**
5362
+ * Adopt whatever the user focused, however they got there — click, tab, or
5363
+ * the arrow keys below. Keeping this in one place is what stops the roving
5364
+ * tabindex drifting away from the element that actually has focus.
5365
+ */
5366
+ onCellFocus(node, column) {
5367
+ if (this.isActiveCell(node, column))
5368
+ return;
5369
+ this.activeCell = { rowId: node.rowId, field: column.field };
5370
+ this.cdr.markForCheck();
5371
+ }
5372
+ onGridKeydown(event) {
5373
+ // Undo/redo of committed edits, anywhere in the grid except inside an
5374
+ // open editor — a text input's Ctrl+Z is the browser's, and it operates
5375
+ // on the uncommitted text.
5376
+ if ((event.ctrlKey || event.metaKey) && ['z', 'y'].includes(event.key.toLowerCase())) {
5377
+ const tag = event.target?.tagName?.toLowerCase();
5378
+ if (tag === 'input' || tag === 'select' || tag === 'textarea')
5379
+ return;
5380
+ event.preventDefault();
5381
+ const redo = event.key.toLowerCase() === 'y' || event.shiftKey;
5382
+ const applied = redo ? this.api.redoCellEditing() : this.api.undoCellEditing();
5383
+ if (applied)
5384
+ this.cdr.markForCheck();
5385
+ return;
5386
+ }
5387
+ const columns = this.navigableColumns();
5388
+ if (!this.nodes.length || !columns.length)
5389
+ return;
5390
+ // Navigate only when a cell itself holds focus. Anything focusable *inside*
5391
+ // a cell owns its own keys — a text editor's caret, the row drag handle's
5392
+ // reorder keys, the option menu's arrows — and all of them bubble to here.
5393
+ // Checking the target rather than blocklisting tag names is what keeps this
5394
+ // correct as more controls are added to cells.
5395
+ const target = event.target;
5396
+ if (!target?.matches?.('[role="gridcell"]'))
5397
+ return;
5398
+ const current = this.activeCellPosition(columns);
5399
+ let row = current.row;
5400
+ let col = current.col;
5401
+ switch (event.key) {
5402
+ case 'ArrowDown':
5403
+ row += 1;
5404
+ break;
5405
+ case 'ArrowUp':
5406
+ row -= 1;
5407
+ break;
5408
+ case 'ArrowRight':
5409
+ col += 1;
5410
+ break;
5411
+ case 'ArrowLeft':
5412
+ col -= 1;
5413
+ break;
5414
+ // Selection. Space is the grid pattern's toggle, and it has to be caught
5415
+ // here or the browser scrolls the page with it.
5416
+ case ' ': {
5417
+ const node = this.nodes[current.row];
5418
+ if (node) {
5419
+ event.preventDefault();
5420
+ this.toggleRowSelection(node);
5421
+ }
5422
+ return;
5423
+ }
5424
+ case 'a':
5425
+ case 'A':
5426
+ if (!event.ctrlKey && !event.metaKey)
5427
+ return;
5428
+ event.preventDefault();
5429
+ this.api.selectAll();
5430
+ this.cdr.markForCheck();
5431
+ return;
5432
+ case 'Home':
5433
+ col = 0;
5434
+ if (event.ctrlKey || event.metaKey)
5435
+ row = 0;
5436
+ break;
5437
+ case 'End':
5438
+ col = columns.length - 1;
5439
+ if (event.ctrlKey || event.metaKey)
5440
+ row = this.nodes.length - 1;
5441
+ break;
5442
+ // A page is what the viewport can show, which is the only definition that
5443
+ // matches what the user sees move.
5444
+ case 'PageDown':
5445
+ row += this.rowsPerPage();
5446
+ break;
5447
+ case 'PageUp':
5448
+ row -= this.rowsPerPage();
5449
+ break;
5450
+ case 'Enter':
5451
+ case 'F2': {
5452
+ const node = this.nodes[current.row];
5453
+ const column = columns[current.col];
5454
+ if (node && column) {
5455
+ event.preventDefault();
5456
+ this.startEdit({ target: event.target }, node, column);
5457
+ }
5458
+ return;
5459
+ }
5460
+ default: return;
5461
+ }
5462
+ event.preventDefault();
5463
+ const targetRow = Math.min(Math.max(row, 0), this.nodes.length - 1);
5464
+ this.focusCell(targetRow, Math.min(Math.max(col, 0), columns.length - 1));
5465
+ // Shift with a vertical move extends the selection, anchored where the
5466
+ // extension began — the same gesture a file list uses.
5467
+ if (event.shiftKey && targetRow !== current.row) {
5468
+ this.extendSelectionTo(current.row, targetRow);
5469
+ }
5470
+ }
5471
+ toggleRowSelection(node) {
5472
+ if (!this.api.toggleRowSelection(node.rowId, !node.selected))
5473
+ return;
5474
+ // Where a subsequent shift-extension counts from.
5475
+ this.selectionAnchorRowId = node.rowId;
5476
+ this.cdr.markForCheck();
5477
+ }
5478
+ extendSelectionTo(fromRow, toRow) {
5479
+ const target = this.nodes[toRow];
5480
+ if (!target)
5481
+ return;
5482
+ // With nothing selected yet the row the extension started on becomes the
5483
+ // anchor, so shift+arrow selects both ends rather than only the far one.
5484
+ const anchor = this.selectionAnchorRowId ?? this.nodes[fromRow]?.rowId;
5485
+ if (!anchor)
5486
+ return;
5487
+ this.selectionAnchorRowId = anchor;
5488
+ this.api.selectRowRange(anchor, target.rowId);
5489
+ this.cdr.markForCheck();
5490
+ }
5491
+ /**
5492
+ * Columns a cell cursor can land on.
5493
+ *
5494
+ * A column with no field renders no cell state and is keyed by field, so it
5495
+ * cannot be addressed — an action-only column is a legitimate example.
5496
+ */
5497
+ navigableColumns() {
5498
+ return this.columnDefs.filter(column => !!column?.field);
5499
+ }
5500
+ activeCellPosition(columns) {
5501
+ if (!this.activeCell)
5502
+ return { row: 0, col: 0 };
5503
+ const row = this.nodes.findIndex(node => node.rowId === this.activeCell.rowId);
5504
+ const col = columns.findIndex(column => column.field === this.activeCell.field);
5505
+ return { row: row < 0 ? 0 : row, col: col < 0 ? 0 : col };
5506
+ }
5507
+ rowsPerPage() {
5508
+ const height = this.viewport?.elementRef?.nativeElement?.clientHeight ?? 0;
5509
+ return Math.max(1, Math.floor(height / this.itemSize));
5510
+ }
5511
+ /**
5512
+ * Move the cursor, scrolling the row into view first when it is outside the
5513
+ * rendered window.
5514
+ *
5515
+ * The scroll is the reason focusing is deferred: a virtualised row that is
5516
+ * not on screen has no element to focus until the viewport has rendered it.
5517
+ */
5518
+ focusCell(rowIndex, colIndex) {
5519
+ const node = this.nodes[rowIndex];
5520
+ const column = this.navigableColumns()[colIndex];
5521
+ if (!node || !column)
5522
+ return;
5523
+ this.activeCell = { rowId: node.rowId, field: column.field };
5524
+ this.cdr.markForCheck();
5525
+ const range = this.viewport?.getRenderedRange();
5526
+ const rendered = range && rowIndex >= range.start && rowIndex < range.end;
5527
+ if (rendered) {
5528
+ setTimeout(() => this.focusCellElement(node, column));
5529
+ return;
5530
+ }
5531
+ this.viewport?.scrollToIndex(rowIndex);
5532
+ // Scrolling is not enough: the CDK renders on its own schedule, so the row
5533
+ // has no element to focus until it has. Waiting for the rendered range to
5534
+ // actually contain the row is the only reliable signal — a fixed delay
5535
+ // races the viewport and silently drops focus when it loses.
5536
+ this.subscriptions.add(this.viewport?.renderedRangeStream
5537
+ .pipe(filter(next => rowIndex >= next.start && rowIndex < next.end), take(1))
5538
+ .subscribe(() => setTimeout(() => this.focusCellElement(node, column))));
5539
+ }
5540
+ focusCellElement(node, column) {
5541
+ const key = getCellKey(node.rowId, column.field);
5542
+ this.bodyRef?.nativeElement?.querySelector(`[data-cell-id="${key}"]`)?.focus();
5543
+ }
4177
5544
  /** Is this the row currently being moved? */
4178
5545
  isRowDragging(node) {
4179
5546
  return this.dragState?.rowId === node.rowId;
@@ -4199,14 +5566,35 @@ class OneGridBodyComponent {
4199
5566
  this.cdr.markForCheck();
4200
5567
  }
4201
5568
  ngOnDestroy() {
5569
+ this.flexResizeObserver?.disconnect();
4202
5570
  this.subscriptions.unsubscribe();
4203
5571
  }
4204
5572
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, deps: [{ token: OgColumnService }, { token: OneGridCellService }, { token: OneGridEventService }, { token: OneGridRowDragService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
4205
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridBodyComponent, selector: "one-grid-body", inputs: { rowData: "rowData", rowNumber: "rowNumber", api: "api", pagination: "pagination", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", submitted: "submitted", context: "context", getRowId: "getRowId", gridCopy$: "gridCopy$", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailRowMaxHeight: "detailRowMaxHeight", detailCellRendererParams: "detailCellRendererParams", detailGridTemplate: "detailGridTemplate", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowCopy: "onRowCopy", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", expansionChanged: "expansionChanged", bodyScroll: "bodyScroll", cellOptionSelect: "cellOptionSelect", rowDragEnd: "rowDragEnd" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:mouseup": "onGlobalMouseUp()" } }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodyRef"], descendants: true, static: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "pinnedViewports", predicate: ["pinnedViewport"], descendants: true }], ngImport: i0, template: "<div #bodyRef class=\"one-grid-body\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n tabindex=\"0\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\" [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\">\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i6.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i6.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i6.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: ["node", "allowedEdit", "api"], outputs: ["onRowSelection"] }, { kind: "component", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: ["column", "node", "allowedEdit", "masterDetail", "masterNode", "cellRendererParams", "gridType", "context"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption"] }, { kind: "component", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: ["node", "masterDetail", "detailCellRendererParams", "detailRendererComponent"], outputs: ["expansionChanged"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "directive", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: ["cellRendererComponent", "params"] }, { kind: "directive", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]" }, { kind: "component", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: ["column", "node", "options"], outputs: ["cellOptionMenuSelect", "cellOptionMenuClose"] }, { kind: "component", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: ["node", "suppressedReason"], outputs: ["dragEnd"] }, { kind: "pipe", type: CellEditablePipe, name: "cellEditable" }, { kind: "pipe", type: CellInvalidPipe, name: "cellInvalid" }, { kind: "pipe", type: CellSelectedPipe, name: "cellSelected" }, { kind: "pipe", type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }, { kind: "pipe", type: CellKeyPipe, name: "cellKey" }, { kind: "pipe", type: CellLevelPaddingPipe, name: "cellLevelPadding" }, { kind: "pipe", type: CellStateRendererPipe, name: "cellStateRenderer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5573
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridBodyComponent, isStandalone: true, selector: "one-grid-body", inputs: { rowData: "rowData", rowNumber: "rowNumber", api: "api", pagination: "pagination", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", submitted: "submitted", editType: "editType", context: "context", getRowId: "getRowId", gridCopy$: "gridCopy$", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailRowMaxHeight: "detailRowMaxHeight", detailCellRendererParams: "detailCellRendererParams", detailGridTemplate: "detailGridTemplate", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowCopy: "onRowCopy", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", expansionChanged: "expansionChanged", bodyScroll: "bodyScroll", cellOptionSelect: "cellOptionSelect", rowDragEnd: "rowDragEnd" }, host: { attributes: { "role": "presentation" }, listeners: { "document:click": "onDocumentClick($event)", "document:mouseup": "onGlobalMouseUp()" } }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodyRef"], descendants: true, static: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "pinnedViewports", predicate: ["pinnedViewport"], descendants: true }], ngImport: i0, template: "<div #bodyRef class=\"one-grid-body\" role=\"presentation\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes, side: 'top' }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n role=\"rowgroup\"\n [attr.tabindex]=\"nodes?.length ? null : 0\"\n (keydown)=\"onGridKeydown($event)\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"dataRowAriaIndex(node)\"\n [attr.aria-selected]=\"rowSelectionEnabled ? node.selected : null\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col, colIndex: $index }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes, side: 'bottom' }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\" let-side=\"side\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\"\n role=\"row\"\n [attr.aria-rowindex]=\"pinnedRowAriaIndex(node, side)\"\n [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\" let-colIndex=\"colIndex\">\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.tabindex]=\"isActiveCell(node, column) ? 0 : -1\"\n [class.one-cell-active]=\"isActiveCell(node, column)\"\n (focus)=\"onCellFocus(node, column)\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n @if (column?.cellRendererParams?.inputType === 'select') {\n <!-- Cell Editing Select. Commits on change \u2014 picking an option\n is the whole edit \u2014 with blur as the keyboard fallback. -->\n <select class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n (change)=\"stopEditSelect(node, column, $event)\"\n (blur)=\"stopEditSelect(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\">\n @for (option of column?.cellRendererParams?.selectOptions ?? []; track $index) {\n <option [selected]=\"option.value === node.data[column.field]\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i5.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i5.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i5.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: ["node", "allowedEdit", "api"], outputs: ["onRowSelection"] }, { kind: "component", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: ["node", "masterDetail", "detailCellRendererParams", "detailRendererComponent"], outputs: ["expansionChanged"] }, { kind: "component", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: ["column", "node", "options"], outputs: ["cellOptionMenuSelect", "cellOptionMenuClose"] }, { kind: "component", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: ["column", "node", "allowedEdit", "masterDetail", "masterNode", "cellRendererParams", "gridType", "context"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption"] }, { kind: "component", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: ["node", "suppressedReason"], outputs: ["dragEnd"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "directive", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: ["cellRendererComponent", "params"] }, { kind: "directive", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]" }, { kind: "pipe", type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }, { kind: "pipe", type: CellEditablePipe, name: "cellEditable" }, { kind: "pipe", type: CellInvalidPipe, name: "cellInvalid" }, { kind: "pipe", type: CellKeyPipe, name: "cellKey" }, { kind: "pipe", type: CellLevelPaddingPipe, name: "cellLevelPadding" }, { kind: "pipe", type: CellSelectedPipe, name: "cellSelected" }, { kind: "pipe", type: CellStateRendererPipe, name: "cellStateRenderer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4206
5574
  }
4207
5575
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, decorators: [{
4208
5576
  type: Component,
4209
- args: [{ standalone: false, selector: 'one-grid-body', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bodyRef class=\"one-grid-body\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n tabindex=\"0\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\" [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\">\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n" }]
5577
+ args: [{ standalone: true, imports: [
5578
+ NgClass,
5579
+ NgStyle,
5580
+ NgTemplateOutlet,
5581
+ ScrollingModule,
5582
+ OneGridCheckboxComponent,
5583
+ OneGridCellGroupRendererComponent,
5584
+ OneGridCellOptionMenuComponent,
5585
+ OneGridRowActionComponent,
5586
+ OneGridRowDragHandleComponent,
5587
+ OneCellInputValidatorDirective,
5588
+ OneCellRendererComponentDirective,
5589
+ OneCellTextExpandDirective,
5590
+ CellActionButtonEnablePipe,
5591
+ CellEditablePipe,
5592
+ CellInvalidPipe,
5593
+ CellKeyPipe,
5594
+ CellLevelPaddingPipe,
5595
+ CellSelectedPipe,
5596
+ CellStateRendererPipe,
5597
+ ], selector: 'one-grid-body', host: { 'role': 'presentation' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bodyRef class=\"one-grid-body\" role=\"presentation\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes, side: 'top' }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n role=\"rowgroup\"\n [attr.tabindex]=\"nodes?.length ? null : 0\"\n (keydown)=\"onGridKeydown($event)\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"dataRowAriaIndex(node)\"\n [attr.aria-selected]=\"rowSelectionEnabled ? node.selected : null\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col, colIndex: $index }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes, side: 'bottom' }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\" let-side=\"side\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\"\n role=\"row\"\n [attr.aria-rowindex]=\"pinnedRowAriaIndex(node, side)\"\n [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\" let-colIndex=\"colIndex\">\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.tabindex]=\"isActiveCell(node, column) ? 0 : -1\"\n [class.one-cell-active]=\"isActiveCell(node, column)\"\n (focus)=\"onCellFocus(node, column)\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n @if (column?.cellRendererParams?.inputType === 'select') {\n <!-- Cell Editing Select. Commits on change \u2014 picking an option\n is the whole edit \u2014 with blur as the keyboard fallback. -->\n <select class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n (change)=\"stopEditSelect(node, column, $event)\"\n (blur)=\"stopEditSelect(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\">\n @for (option of column?.cellRendererParams?.selectOptions ?? []; track $index) {\n <option [selected]=\"option.value === node.data[column.field]\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n" }]
4210
5598
  }], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridRowDragService }, { type: i0.ChangeDetectorRef }], propDecorators: { bodyRef: [{
4211
5599
  type: ViewChild,
4212
5600
  args: ['bodyRef', { static: true }]
@@ -4225,6 +5613,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4225
5613
  type: Input
4226
5614
  }], submitted: [{
4227
5615
  type: Input
5616
+ }], editType: [{
5617
+ type: Input
4228
5618
  }], context: [{
4229
5619
  type: Input
4230
5620
  }], getRowId: [{
@@ -4390,11 +5780,11 @@ class OneGridPaginationComponent {
4390
5780
  }
4391
5781
  }
4392
5782
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4393
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: { paginate: "paginate" }, outputs: { goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, ngImport: i0, template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n &nbsp;<span>/</span>&nbsp;\n <span>{{ paginate?.total }}</span>\n </div>\n </div>\n }\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `type=\"button\"` so the bar cannot submit a surrounding form,\n a real `disabled` rather than a class that only stops the\n mouse, and a name on every icon-only control. -->\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"First page\"\n title=\"First page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToFirstPage()\">\n <lucide-icon name=\"chevron-first\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Previous page\"\n title=\"Previous page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToPreviousPage()\">\n <lucide-icon name=\"chevron-left\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <!-- Announced as a live region: the numbers are the only feedback a\n screen reader gets that the page actually changed. -->\n <div class=\"one-pagination-column\" role=\"status\" aria-live=\"polite\">\n &nbsp;<span class=\"one-grid-visually-hidden\">Page </span>{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }}&nbsp;\n </div>\n <div class=\"one-pagination-column\">\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Next page\"\n title=\"Next page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToNextPage()\">\n <lucide-icon name=\"chevron-right\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Last page\"\n title=\"Last page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToLastPage()\">\n <lucide-icon name=\"chevron-last\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `[selected]` per option, not `[value]` on the select:\n with options rendered by `@for`, the select's value is\n set before they exist and does not stick. -->\n <select name=\"row-per-page\" aria-label=\"Rows per page\" (change)=\"onPageSizeChange($event)\">\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\" [selected]=\"size === pageSize\">{{ size }} rows</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: i2$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] }); }
5783
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridPaginationComponent, isStandalone: true, selector: "one-grid-pagination", inputs: { paginate: "paginate" }, outputs: { goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [ONE_GRID_ICON_PROVIDERS], ngImport: i0, template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n &nbsp;<span>/</span>&nbsp;\n <span>{{ paginate?.total }}</span>\n </div>\n </div>\n }\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `type=\"button\"` so the bar cannot submit a surrounding form,\n a real `disabled` rather than a class that only stops the\n mouse, and a name on every icon-only control. -->\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"First page\"\n title=\"First page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToFirstPage()\">\n <lucide-icon name=\"chevron-first\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Previous page\"\n title=\"Previous page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToPreviousPage()\">\n <lucide-icon name=\"chevron-left\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <!-- Announced as a live region: the numbers are the only feedback a\n screen reader gets that the page actually changed. -->\n <div class=\"one-pagination-column\" role=\"status\" aria-live=\"polite\">\n &nbsp;<span class=\"one-grid-visually-hidden\">Page </span>{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }}&nbsp;\n </div>\n <div class=\"one-pagination-column\">\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Next page\"\n title=\"Next page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToNextPage()\">\n <lucide-icon name=\"chevron-right\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Last page\"\n title=\"Last page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToLastPage()\">\n <lucide-icon name=\"chevron-last\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `[selected]` per option, not `[value]` on the select:\n with options rendered by `@for`, the select's value is\n set before they exist and does not stick. -->\n <select name=\"row-per-page\" aria-label=\"Rows per page\" (change)=\"onPageSizeChange($event)\">\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\" [selected]=\"size === pageSize\">{{ size }} rows</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] }); }
4394
5784
  }
4395
5785
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, decorators: [{
4396
5786
  type: Component,
4397
- args: [{ standalone: false, selector: 'one-grid-pagination', template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n &nbsp;<span>/</span>&nbsp;\n <span>{{ paginate?.total }}</span>\n </div>\n </div>\n }\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `type=\"button\"` so the bar cannot submit a surrounding form,\n a real `disabled` rather than a class that only stops the\n mouse, and a name on every icon-only control. -->\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"First page\"\n title=\"First page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToFirstPage()\">\n <lucide-icon name=\"chevron-first\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Previous page\"\n title=\"Previous page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToPreviousPage()\">\n <lucide-icon name=\"chevron-left\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <!-- Announced as a live region: the numbers are the only feedback a\n screen reader gets that the page actually changed. -->\n <div class=\"one-pagination-column\" role=\"status\" aria-live=\"polite\">\n &nbsp;<span class=\"one-grid-visually-hidden\">Page </span>{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }}&nbsp;\n </div>\n <div class=\"one-pagination-column\">\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Next page\"\n title=\"Next page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToNextPage()\">\n <lucide-icon name=\"chevron-right\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Last page\"\n title=\"Last page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToLastPage()\">\n <lucide-icon name=\"chevron-last\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `[selected]` per option, not `[value]` on the select:\n with options rendered by `@for`, the select's value is\n set before they exist and does not stick. -->\n <select name=\"row-per-page\" aria-label=\"Rows per page\" (change)=\"onPageSizeChange($event)\">\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\" [selected]=\"size === pageSize\">{{ size }} rows</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
5787
+ args: [{ standalone: true, imports: [LucideAngularModule], selector: 'one-grid-pagination', providers: [ONE_GRID_ICON_PROVIDERS], template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n &nbsp;<span>/</span>&nbsp;\n <span>{{ paginate?.total }}</span>\n </div>\n </div>\n }\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `type=\"button\"` so the bar cannot submit a surrounding form,\n a real `disabled` rather than a class that only stops the\n mouse, and a name on every icon-only control. -->\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"First page\"\n title=\"First page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToFirstPage()\">\n <lucide-icon name=\"chevron-first\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Previous page\"\n title=\"Previous page\"\n [disabled]=\"!hasPrevious\"\n [class.disabled]=\"!hasPrevious\"\n (click)=\"goToPreviousPage()\">\n <lucide-icon name=\"chevron-left\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <!-- Announced as a live region: the numbers are the only feedback a\n screen reader gets that the page actually changed. -->\n <div class=\"one-pagination-column\" role=\"status\" aria-live=\"polite\">\n &nbsp;<span class=\"one-grid-visually-hidden\">Page </span>{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }}&nbsp;\n </div>\n <div class=\"one-pagination-column\">\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Next page\"\n title=\"Next page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToNextPage()\">\n <lucide-icon name=\"chevron-right\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n type=\"button\"\n class=\"one-pagination-btn\"\n aria-label=\"Last page\"\n title=\"Last page\"\n [disabled]=\"!hasNext\"\n [class.disabled]=\"!hasNext\"\n (click)=\"goToLastPage()\">\n <lucide-icon name=\"chevron-last\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <!-- `[selected]` per option, not `[value]` on the select:\n with options rendered by `@for`, the select's value is\n set before they exist and does not stick. -->\n <select name=\"row-per-page\" aria-label=\"Rows per page\" (change)=\"onPageSizeChange($event)\">\n @for (size of pageSizeOptions; track size) {\n <option [value]=\"size\" [selected]=\"size === pageSize\">{{ size }} rows</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
4398
5788
  }], propDecorators: { paginate: [{
4399
5789
  type: Input
4400
5790
  }], goToFirst: [{
@@ -4410,16 +5800,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4410
5800
  }] } });
4411
5801
 
4412
5802
  class OneGridComponent {
4413
- constructor(api, eventService, elementRef) {
5803
+ /**
5804
+ * Recompute the bar's numbers from the filtered row count and the API's
5805
+ * paging state. Deferred a tick for the same reason the aria counts are:
5806
+ * the count arrives while the first change detection pass is still running.
5807
+ */
5808
+ refreshClientPagination(total) {
5809
+ const { pageSize, currentPage } = this.api.paginationState;
5810
+ const lastPage = Math.max(Math.ceil(total / pageSize), 1);
5811
+ const page = Math.min(currentPage, lastPage);
5812
+ setTimeout(() => {
5813
+ this.internalPaginate = {
5814
+ total,
5815
+ currentPage: page,
5816
+ lastPage,
5817
+ perPage: pageSize,
5818
+ firstRowIndex: total === 0 ? 0 : (page - 1) * pageSize + 1,
5819
+ lastRowIndex: Math.min(page * pageSize, total),
5820
+ pageSizeOptions: this.paginate?.pageSizeOptions,
5821
+ };
5822
+ this.cdr.markForCheck();
5823
+ });
5824
+ }
5825
+ constructor(api, eventService, elementRef, cdr) {
4414
5826
  this.api = api;
4415
5827
  this.eventService = eventService;
4416
5828
  this.elementRef = elementRef;
5829
+ this.cdr = cdr;
4417
5830
  this.rowNumber = false;
4418
5831
  this.allowedEdit = true;
4419
5832
  this.submitted = false;
4420
5833
  this.context = null;
4421
5834
  this.rowData = [];
4422
5835
  this.rowSelection = null;
5836
+ /**
5837
+ * The grid's accessible name.
5838
+ *
5839
+ * A `role="grid"` with no name is announced as an unlabelled grid, which
5840
+ * tells a screen-reader user nothing about what they have landed in. Set it
5841
+ * to whatever the grid is showing — "Employees", "Order lines".
5842
+ */
5843
+ this.gridLabel = 'Data grid';
4423
5844
  this.enableGroupColumnDefs = false;
4424
5845
  this.groupColumnDefs = [];
4425
5846
  this.defaultColumnDef = {};
@@ -4454,6 +5875,41 @@ class OneGridComponent {
4454
5875
  * pointer is in flight — and only when the position actually changed.
4455
5876
  */
4456
5877
  this.rowDragEnd = new EventEmitter();
5878
+ /**
5879
+ * The header's ⋮ button was pressed for this column.
5880
+ *
5881
+ * The grid has no column menu of its own — this reports the intent so the
5882
+ * host can show whatever a column menu means in its app. Set
5883
+ * `columnDef.suppressHeaderMenuButton` to hide the button entirely.
5884
+ */
5885
+ this.headerMenuClick = new EventEmitter();
5886
+ /**
5887
+ * Render the grid's own column menu behind the header ⋮ button: sort, pin,
5888
+ * hide. Opt-in — a host that built its menu on `(headerMenuClick)` keeps
5889
+ * exactly what it had, and the intent still emits either way.
5890
+ */
5891
+ this.headerMenu = false;
5892
+ /**
5893
+ * Render a filter input under every column whose `filterable` resolves
5894
+ * true. The inputs feed `api.setFilterModel` with contains-conditions,
5895
+ * debounced; filters on fields without an input are left untouched.
5896
+ */
5897
+ this.filterRow = false;
5898
+ /**
5899
+ * Record committed cell edits so `api.undoCellEditing` /
5900
+ * `api.redoCellEditing` (and Ctrl+Z / Ctrl+Y in the grid) can walk them.
5901
+ * Programmatic `setDataValue` writes are not recorded.
5902
+ */
5903
+ this.undoRedoCellEditing = false;
5904
+ /** How many edits the undo stack keeps. */
5905
+ this.undoRedoCellEditingLimit = 10;
5906
+ /**
5907
+ * `'fullRow'` opens every editable cell of the row on a double-click and
5908
+ * commits them together — Enter anywhere in the row, or focus leaving the
5909
+ * row. Escape closes the whole row without committing. Default: cells edit
5910
+ * one at a time.
5911
+ */
5912
+ this.editType = null;
4457
5913
  this.onGridReady = new EventEmitter();
4458
5914
  this.onRowGotoDetail = new EventEmitter();
4459
5915
  this.onRowAddNew = new EventEmitter();
@@ -4473,6 +5929,22 @@ class OneGridComponent {
4473
5929
  this.rowCountChanged = new EventEmitter();
4474
5930
  this.selectionChanged = new EventEmitter();
4475
5931
  this.expansionChanged = new EventEmitter();
5932
+ /**
5933
+ * The sort model changed — a header click or `api.onSortChanged`. Carries
5934
+ * the re-sorted nodes and the model that produced them; `api.sortModel`
5935
+ * stays readable between emissions.
5936
+ */
5937
+ this.sortChanged = new EventEmitter();
5938
+ /**
5939
+ * An inline edit was committed with a value different from the one it
5940
+ * started with. Programmatic `node.setDataValue` does not emit here — the
5941
+ * caller already holds both values.
5942
+ */
5943
+ this.cellValueChanged = new EventEmitter();
5944
+ /** A header drag finished somewhere other than where it started. */
5945
+ this.columnMoved = new EventEmitter();
5946
+ /** A resize gesture finished — one event per gesture, not per pixel. */
5947
+ this.columnResized = new EventEmitter();
4476
5948
  // For context menu option
4477
5949
  this.cellOptionSelect = new EventEmitter();
4478
5950
  this.onRowCellOption = new EventEmitter();
@@ -4480,17 +5952,42 @@ class OneGridComponent {
4480
5952
  * Bind One Grid Pagination Feature
4481
5953
  */
4482
5954
  this.pagination = false;
5955
+ /**
5956
+ * Let the grid page its own rows. The bar renders, the grid slices the
5957
+ * filtered rows to the current page, and the navigation buttons work
5958
+ * without the host handling anything — `[paginate]` is ignored in this
5959
+ * mode (the grid owns those numbers) except for `pageSizeOptions`. The
5960
+ * navigation outputs still emit, as notifications rather than intents.
5961
+ * Selection, filtering and sorting keep operating on the whole dataset.
5962
+ */
5963
+ this.clientSidePagination = false;
5964
+ /** Rows per page for `clientSidePagination`. */
5965
+ this.pageSize = 25;
4483
5966
  this.goToFirst = new EventEmitter();
4484
5967
  this.goToPrevious = new EventEmitter();
4485
5968
  this.goToNext = new EventEmitter();
4486
5969
  this.goToLast = new EventEmitter();
4487
5970
  this.pageSizeChange = new EventEmitter();
5971
+ /** The paging numbers the bar shows when the grid pages its own rows. */
5972
+ this.internalPaginate = {
5973
+ total: 0, currentPage: 1, lastPage: 1, perPage: 25, firstRowIndex: 0, lastRowIndex: 0,
5974
+ };
4488
5975
  /**
4489
5976
  * One Grid Properties
4490
5977
  */
4491
5978
  this.bodyScrollLeft = 0;
4492
5979
  this._oneRowNumberColumnDef = null;
4493
5980
  this.subscriptions = new Subscription();
5981
+ /**
5982
+ * Totals for `aria-rowcount` / `aria-colcount`.
5983
+ *
5984
+ * Held as fields rather than computed in the template: they are read on every
5985
+ * change detection pass and only actually change when the data or the columns
5986
+ * do. `-1` is the ARIA value for "unknown", which is the honest answer before
5987
+ * the first row arrives.
5988
+ */
5989
+ this.ariaRowCount = -1;
5990
+ this.ariaColCount = -1;
4494
5991
  }
4495
5992
  ngOnChanges(changes) {
4496
5993
  if (changes['masterNode'] && changes['masterNode'].isFirstChange) {
@@ -4534,6 +6031,35 @@ class OneGridComponent {
4534
6031
  maxWidth: 200
4535
6032
  };
4536
6033
  }
6034
+ /**
6035
+ * Row grouping: column defs declaring `rowGroup` turn the grid into a
6036
+ * tree over synthetic group rows. Configured before `api.init` because
6037
+ * everything downstream — the treeData flag the API captures, the group
6038
+ * column the tree block below prepends — has to see the tree shape.
6039
+ */
6040
+ const rowGroupColumnFields = this.treeData || this.masterDetail
6041
+ ? []
6042
+ : rowGroupFields(this.columnDefs);
6043
+ if (rowGroupColumnFields.length) {
6044
+ this.treeData = true;
6045
+ this.treeDataChildrenField = ONE_GROUP_CHILDREN_FIELD;
6046
+ // The group column: host-tuned through autoGroupColumnDef, but its
6047
+ // field is always the synthetic label — that is where a group row
6048
+ // carries its display value.
6049
+ this.autoGroupColumnDef = {
6050
+ headerName: 'Group',
6051
+ minWidth: 200,
6052
+ ...this.autoGroupColumnDef,
6053
+ field: ONE_GROUP_VALUE_FIELD,
6054
+ // The marker the tree machinery keys on: the expander renders on this
6055
+ // column and group rows print their label here.
6056
+ autoGroupField: true,
6057
+ };
6058
+ // A grouped-by column's value is on the group row; showing the flat
6059
+ // column too repeats it down every leaf. Hidden, not removed — the
6060
+ // host can bring it back with api.setColumnVisible.
6061
+ this.columnDefs = this.columnDefs.map(column => column.rowGroup ? { ...column, hide: true } : column);
6062
+ }
4537
6063
  /**
4538
6064
  * Initialize One Grid API
4539
6065
  */
@@ -4549,6 +6075,15 @@ class OneGridComponent {
4549
6075
  detailRendererComponent: this.detailRendererComponent,
4550
6076
  treeData: this.treeData,
4551
6077
  });
6078
+ if (rowGroupColumnFields.length) {
6079
+ this.api.setRowGrouping(rowGroupColumnFields, aggColumns(this.columnDefs));
6080
+ }
6081
+ if (this.undoRedoCellEditing) {
6082
+ this.api.enableUndoRedoCellEditing(this.undoRedoCellEditingLimit);
6083
+ }
6084
+ if (this.clientSidePagination) {
6085
+ this.api.setClientSidePagination(true, this.paginate?.perPage ?? this.pageSize);
6086
+ }
4552
6087
  /**
4553
6088
  * Setup Master Detail Configuration
4554
6089
  */
@@ -4571,9 +6106,28 @@ class OneGridComponent {
4571
6106
  setTimeout(() => this.api.setColumnDefs(columnDefs));
4572
6107
  }
4573
6108
  // Listen Selection Changed Event
6109
+ // Columns can be replaced at runtime through `api.setColumnDefs`, and
6110
+ // `aria-colcount` has to follow.
6111
+ this.subscriptions.add(this.eventService.addEventListener('columnChanged', () => {
6112
+ this.ariaColCount = this.api?.getColumnState()?.columns?.length ?? -1;
6113
+ }));
4574
6114
  this.subscriptions.add(this.eventService.addEventListener('selectionChanged', (payload) => {
4575
6115
  this.selectionChanged.emit(payload);
4576
6116
  }));
6117
+ // The internal events a host can act on, surfaced as outputs. Same
6118
+ // forwarding pattern as selectionChanged above.
6119
+ this.subscriptions.add(this.eventService.addEventListener('sortChanged', (payload) => {
6120
+ this.sortChanged.emit(payload);
6121
+ }));
6122
+ this.subscriptions.add(this.eventService.addEventListener('cellValueChanged', (payload) => {
6123
+ this.cellValueChanged.emit(payload);
6124
+ }));
6125
+ this.subscriptions.add(this.eventService.addEventListener('columnMoved', (payload) => {
6126
+ this.columnMoved.emit(payload);
6127
+ }));
6128
+ this.subscriptions.add(this.eventService.addEventListener('columnResized', (payload) => {
6129
+ this.columnResized.emit(payload);
6130
+ }));
4577
6131
  }
4578
6132
  ngAfterViewInit() {
4579
6133
  // The header follows the body through (bodyScroll) -> [scrollLeft]. A second
@@ -4665,8 +6219,34 @@ class OneGridComponent {
4665
6219
  this.onShiftDoubleClick.emit(event);
4666
6220
  }
4667
6221
  invokeRowCountChanged(length) {
6222
+ this.refreshAriaCounts(length);
6223
+ if (this.clientSidePagination)
6224
+ this.refreshClientPagination(length);
4668
6225
  this.rowCountChanged.emit(length);
4669
6226
  }
6227
+ /**
6228
+ * Recompute the totals a screen reader is told about.
6229
+ *
6230
+ * `aria-rowcount` counts every row the grid represents, not the handful the
6231
+ * virtual scroller currently has in the DOM — the header row, the pinned rows
6232
+ * either side, and all the data rows.
6233
+ */
6234
+ refreshAriaCounts(dataRows) {
6235
+ const pinned = (this.pinnedTopRowData?.length ?? 0) + (this.pinnedBottomRowData?.length ?? 0);
6236
+ const rows = 1 + pinned + dataRows;
6237
+ const cols = this.api?.getColumnState()?.columns?.length ?? -1;
6238
+ if (rows === this.ariaRowCount && cols === this.ariaColCount)
6239
+ return;
6240
+ // Deferred a tick. The body emits its row count while the first change
6241
+ // detection pass is still running, after this component's own attributes
6242
+ // were checked — assigning here directly is the textbook NG0100. The body
6243
+ // defers its node list for the same reason.
6244
+ setTimeout(() => {
6245
+ this.ariaRowCount = rows;
6246
+ this.ariaColCount = cols;
6247
+ this.cdr.markForCheck();
6248
+ });
6249
+ }
4670
6250
  cellOptionMenuSelected(event) {
4671
6251
  this.cellOptionSelect.emit(event);
4672
6252
  }
@@ -4674,18 +6254,28 @@ class OneGridComponent {
4674
6254
  * Bing Pagination Events to Parent Component
4675
6255
  */
4676
6256
  invokeGoToFirst() {
6257
+ if (this.clientSidePagination)
6258
+ this.api.setCurrentPage(1);
4677
6259
  this.goToFirst.emit();
4678
6260
  }
4679
6261
  invokeGoToPrevious() {
6262
+ if (this.clientSidePagination)
6263
+ this.api.setCurrentPage(this.api.paginationState.currentPage - 1);
4680
6264
  this.goToPrevious.emit();
4681
6265
  }
4682
6266
  invokeGoToNext() {
6267
+ if (this.clientSidePagination)
6268
+ this.api.setCurrentPage(this.api.paginationState.currentPage + 1);
4683
6269
  this.goToNext.emit();
4684
6270
  }
4685
6271
  invokeGoToLast() {
6272
+ if (this.clientSidePagination)
6273
+ this.api.setCurrentPage(this.internalPaginate.lastPage ?? 1);
4686
6274
  this.goToLast.emit();
4687
6275
  }
4688
6276
  invokePageSizeChange(pageSize) {
6277
+ if (this.clientSidePagination)
6278
+ this.api.setPageSize(pageSize);
4689
6279
  this.pageSizeChange.emit(pageSize);
4690
6280
  }
4691
6281
  invokeRowDragEnd(event) {
@@ -4702,8 +6292,8 @@ class OneGridComponent {
4702
6292
  this.api.destroy();
4703
6293
  this.subscriptions.unsubscribe();
4704
6294
  }
4705
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, deps: [{ token: OneGridApi }, { token: OneGridEventService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
4706
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridComponent, selector: "one-grid", inputs: { rowNumber: "rowNumber", allowedEdit: "allowedEdit", submitted: "submitted", context: "context", rowData: "rowData", rowSelection: "rowSelection", enableGroupColumnDefs: "enableGroupColumnDefs", groupColumnDefs: "groupColumnDefs", defaultColumnDef: "defaultColumnDef", columnDefs: "columnDefs", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded", pagination: "pagination", paginate: "paginate" }, outputs: { rowDragEnd: "rowDragEnd", onGridReady: "onGridReady", onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", selectionChanged: "selectionChanged", expansionChanged: "expansionChanged", cellOptionSelect: "cellOptionSelect", onRowCellOption: "onRowCellOption", goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [
6295
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, deps: [{ token: OneGridApi }, { token: OneGridEventService }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
6296
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridComponent, isStandalone: true, selector: "one-grid", inputs: { rowNumber: "rowNumber", allowedEdit: "allowedEdit", submitted: "submitted", context: "context", rowData: "rowData", rowSelection: "rowSelection", gridLabel: "gridLabel", enableGroupColumnDefs: "enableGroupColumnDefs", groupColumnDefs: "groupColumnDefs", defaultColumnDef: "defaultColumnDef", columnDefs: "columnDefs", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded", headerMenu: "headerMenu", filterRow: "filterRow", undoRedoCellEditing: "undoRedoCellEditing", undoRedoCellEditingLimit: "undoRedoCellEditingLimit", editType: "editType", pagination: "pagination", paginate: "paginate", clientSidePagination: "clientSidePagination", pageSize: "pageSize" }, outputs: { rowDragEnd: "rowDragEnd", headerMenuClick: "headerMenuClick", onGridReady: "onGridReady", onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", selectionChanged: "selectionChanged", expansionChanged: "expansionChanged", sortChanged: "sortChanged", cellValueChanged: "cellValueChanged", columnMoved: "columnMoved", columnResized: "columnResized", cellOptionSelect: "cellOptionSelect", onRowCellOption: "onRowCellOption", goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [
4707
6297
  OneGridEventService,
4708
6298
  OneGridApi,
4709
6299
  OgColumnService,
@@ -4712,11 +6302,16 @@ class OneGridComponent {
4712
6302
  OneGridRowService,
4713
6303
  OneGridCellService,
4714
6304
  OneGridRowDragService
4715
- ], viewQueries: [{ propertyName: "ogRef", first: true, predicate: ["ogRef"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<div #ogRef class=\"one-grid\" (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination) {\n <one-grid-pagination\n [paginate]=\"paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>", styles: [""], dependencies: [{ kind: "component", type: OneGridComponent, selector: "one-grid", inputs: ["rowNumber", "allowedEdit", "submitted", "context", "rowData", "rowSelection", "enableGroupColumnDefs", "groupColumnDefs", "defaultColumnDef", "columnDefs", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailCellRendererParams", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded", "pagination", "paginate"], outputs: ["rowDragEnd", "onGridReady", "onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "selectionChanged", "expansionChanged", "cellOptionSelect", "onRowCellOption", "goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }, { kind: "component", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: ["scrollLeft", "allowedEdit", "enableGroupColumnDefs", "api"] }, { kind: "component", type: OneGridBodyComponent, selector: "one-grid-body", inputs: ["rowData", "rowNumber", "api", "pagination", "allowedEdit", "enableGroupColumnDefs", "submitted", "context", "getRowId", "gridCopy$", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailRowMaxHeight", "detailCellRendererParams", "detailGridTemplate", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowCopy", "onRowLocation", "onRowDocument", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "expansionChanged", "bodyScroll", "cellOptionSelect", "rowDragEnd"] }, { kind: "component", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: ["paginate"], outputs: ["goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }] }); }
6305
+ ], viewQueries: [{ propertyName: "ogRef", first: true, predicate: ["ogRef"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<!-- role=\"grid\" is what turns this from a pile of divs into something a screen\n reader can navigate. The counts are the *total* \u2014 `aria-rowcount` includes\n the header row and any pinned rows, and both stay correct while the body is\n virtualised and most rows are not in the DOM, which is exactly what these\n attributes exist to communicate. -->\n<div #ogRef\n class=\"one-grid\"\n role=\"grid\"\n [attr.aria-label]=\"gridLabel\"\n [attr.aria-rowcount]=\"ariaRowCount\"\n [attr.aria-colcount]=\"ariaColCount\"\n [attr.aria-multiselectable]=\"rowSelection === 'multiple' ? true : null\"\n (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n [headerMenu]=\"headerMenu\"\n [filterRow]=\"filterRow\"\n (headerMenuClick)=\"headerMenuClick.emit($event)\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [editType]=\"editType\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination || clientSidePagination) {\n <one-grid-pagination\n [paginate]=\"clientSidePagination ? internalPaginate : paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>", styles: [""], dependencies: [{ kind: "component", type: OneGridComponent, selector: "one-grid", inputs: ["rowNumber", "allowedEdit", "submitted", "context", "rowData", "rowSelection", "gridLabel", "enableGroupColumnDefs", "groupColumnDefs", "defaultColumnDef", "columnDefs", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailCellRendererParams", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded", "headerMenu", "filterRow", "undoRedoCellEditing", "undoRedoCellEditingLimit", "editType", "pagination", "paginate", "clientSidePagination", "pageSize"], outputs: ["rowDragEnd", "headerMenuClick", "onGridReady", "onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "selectionChanged", "expansionChanged", "sortChanged", "cellValueChanged", "columnMoved", "columnResized", "cellOptionSelect", "onRowCellOption", "goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }, { kind: "component", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: ["scrollLeft", "allowedEdit", "enableGroupColumnDefs", "api", "filterRow", "headerMenu"], outputs: ["headerMenuClick"] }, { kind: "component", type: OneGridBodyComponent, selector: "one-grid-body", inputs: ["rowData", "rowNumber", "api", "pagination", "allowedEdit", "enableGroupColumnDefs", "submitted", "editType", "context", "getRowId", "gridCopy$", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailRowMaxHeight", "detailCellRendererParams", "detailGridTemplate", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowCopy", "onRowLocation", "onRowDocument", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "expansionChanged", "bodyScroll", "cellOptionSelect", "rowDragEnd"] }, { kind: "component", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: ["paginate"], outputs: ["goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }] }); }
4716
6306
  }
4717
6307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, decorators: [{
4718
6308
  type: Component,
4719
- args: [{ standalone: false, selector: 'one-grid', providers: [
6309
+ args: [{ standalone: true, imports: [
6310
+ NgTemplateOutlet,
6311
+ OneGridHeaderComponent,
6312
+ OneGridBodyComponent,
6313
+ OneGridPaginationComponent,
6314
+ ], selector: 'one-grid', providers: [
4720
6315
  OneGridEventService,
4721
6316
  OneGridApi,
4722
6317
  OgColumnService,
@@ -4725,8 +6320,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4725
6320
  OneGridRowService,
4726
6321
  OneGridCellService,
4727
6322
  OneGridRowDragService
4728
- ], template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<div #ogRef class=\"one-grid\" (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination) {\n <one-grid-pagination\n [paginate]=\"paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>" }]
4729
- }], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }], propDecorators: { ogRef: [{
6323
+ ], template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<!-- role=\"grid\" is what turns this from a pile of divs into something a screen\n reader can navigate. The counts are the *total* \u2014 `aria-rowcount` includes\n the header row and any pinned rows, and both stay correct while the body is\n virtualised and most rows are not in the DOM, which is exactly what these\n attributes exist to communicate. -->\n<div #ogRef\n class=\"one-grid\"\n role=\"grid\"\n [attr.aria-label]=\"gridLabel\"\n [attr.aria-rowcount]=\"ariaRowCount\"\n [attr.aria-colcount]=\"ariaColCount\"\n [attr.aria-multiselectable]=\"rowSelection === 'multiple' ? true : null\"\n (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n [headerMenu]=\"headerMenu\"\n [filterRow]=\"filterRow\"\n (headerMenuClick)=\"headerMenuClick.emit($event)\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [editType]=\"editType\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination || clientSidePagination) {\n <one-grid-pagination\n [paginate]=\"clientSidePagination ? internalPaginate : paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>" }]
6324
+ }], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { ogRef: [{
4730
6325
  type: ViewChild,
4731
6326
  args: ['ogRef', { static: true }]
4732
6327
  }], rowNumber: [{
@@ -4741,6 +6336,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4741
6336
  type: Input
4742
6337
  }], rowSelection: [{
4743
6338
  type: Input
6339
+ }], gridLabel: [{
6340
+ type: Input
4744
6341
  }], enableGroupColumnDefs: [{
4745
6342
  type: Input
4746
6343
  }], groupColumnDefs: [{
@@ -4773,6 +6370,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4773
6370
  type: Input
4774
6371
  }], rowDragEnd: [{
4775
6372
  type: Output
6373
+ }], headerMenuClick: [{
6374
+ type: Output
6375
+ }], headerMenu: [{
6376
+ type: Input
6377
+ }], filterRow: [{
6378
+ type: Input
6379
+ }], undoRedoCellEditing: [{
6380
+ type: Input
6381
+ }], undoRedoCellEditingLimit: [{
6382
+ type: Input
6383
+ }], editType: [{
6384
+ type: Input
4776
6385
  }], onGridReady: [{
4777
6386
  type: Output
4778
6387
  }], onRowGotoDetail: [{
@@ -4811,6 +6420,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4811
6420
  type: Output
4812
6421
  }], expansionChanged: [{
4813
6422
  type: Output
6423
+ }], sortChanged: [{
6424
+ type: Output
6425
+ }], cellValueChanged: [{
6426
+ type: Output
6427
+ }], columnMoved: [{
6428
+ type: Output
6429
+ }], columnResized: [{
6430
+ type: Output
4814
6431
  }], cellOptionSelect: [{
4815
6432
  type: Output
4816
6433
  }], onRowCellOption: [{
@@ -4819,6 +6436,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4819
6436
  type: Input
4820
6437
  }], paginate: [{
4821
6438
  type: Input
6439
+ }], clientSidePagination: [{
6440
+ type: Input
6441
+ }], pageSize: [{
6442
+ type: Input
4822
6443
  }], goToFirst: [{
4823
6444
  type: Output
4824
6445
  }], goToPrevious: [{
@@ -4856,11 +6477,11 @@ class OneGridStatusComponent {
4856
6477
  return labels[type][status === 1 ? 1 : 0];
4857
6478
  }
4858
6479
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4859
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridStatusComponent, selector: "one-grid-status", ngImport: i0, template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
6480
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridStatusComponent, isStandalone: true, selector: "one-grid-status", ngImport: i0, template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
4860
6481
  }
4861
6482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridStatusComponent, decorators: [{
4862
6483
  type: Component,
4863
- args: [{ standalone: false, selector: 'one-grid-status', template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n" }]
6484
+ args: [{ standalone: true, imports: [NgClass], selector: 'one-grid-status', template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n" }]
4864
6485
  }] });
4865
6486
 
4866
6487
  class OneGridCellCurrencyComponent {
@@ -4935,172 +6556,50 @@ class OneGridCellCurrencyComponent {
4935
6556
  this.unsubscribe?.();
4936
6557
  }
4937
6558
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, deps: [{ token: OneGridCellService }, { token: OneGridEventService }], target: i0.ɵɵFactoryTarget.Component }); }
4938
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellCurrencyComponent, selector: "app-one-grid-cell-currency", inputs: { allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"], dependencies: [{ kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }] }); }
6559
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellCurrencyComponent, isStandalone: true, selector: "app-one-grid-cell-currency", inputs: { allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"], dependencies: [{ kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }] }); }
4939
6560
  }
4940
6561
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, decorators: [{
4941
6562
  type: Component,
4942
- args: [{ standalone: false, selector: 'app-one-grid-cell-currency', template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"] }]
6563
+ args: [{ standalone: true, imports: [DecimalPipe, FormsModule, OneCellInputValidatorDirective], selector: 'app-one-grid-cell-currency', template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"] }]
4943
6564
  }], ctorParameters: () => [{ type: OneGridCellService }, { type: OneGridEventService }], propDecorators: { allowedEdit: [{
4944
6565
  type: Input
4945
6566
  }] } });
4946
6567
 
4947
- class CellActionButtonVisiblePipe {
4948
- transform(column, node) {
4949
- return (node?.leaf && !(node?.group && node?.leafGroup) && !column?.autoGroupField) || (!node?.leaf && (node?.group || node?.leafGroup) && column?.autoGroupField && column?.editableLevels?.includes(node.level));
4950
- }
4951
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonVisiblePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4952
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonVisiblePipe, name: "cellActionButtonVisible" }); }
4953
- }
4954
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonVisiblePipe, decorators: [{
4955
- type: Pipe,
4956
- args: [{
4957
- standalone: false,
4958
- name: 'cellActionButtonVisible'
4959
- }]
4960
- }] });
4961
-
4962
- class CellNumberValuePipe {
4963
- transform(value) {
4964
- if (value === null || value === undefined)
4965
- return undefined;
4966
- return Number(value);
4967
- }
4968
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4969
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, name: "cellNumberValue" }); }
4970
- }
4971
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, decorators: [{
4972
- type: Pipe,
4973
- args: [{
4974
- standalone: false,
4975
- name: 'cellNumberValue'
4976
- }]
4977
- }] });
4978
-
4979
- class CellFieldPipe {
4980
- transform(rowIndex, field) {
4981
- if (rowIndex === null || rowIndex === undefined || !field)
4982
- return '';
4983
- return getCellField(rowIndex, field);
4984
- }
4985
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4986
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, name: "cellField" }); }
4987
- }
4988
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, decorators: [{
4989
- type: Pipe,
4990
- args: [{
4991
- standalone: false,
4992
- name: 'cellField'
4993
- }]
4994
- }] });
4995
-
4996
- class CellStateGroupRendererPipe {
4997
- constructor() {
4998
- this._cellFieldValue = new CellFieldValuePipe();
4999
- }
5000
- transform(value, data, column) {
5001
- return value ? this._cellFieldValue.transform(data, column.field) : column?.cellRendererParams?.placeholder ?? '';
5002
- }
5003
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5004
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, name: "cellStateGroupRenderer" }); }
5005
- }
5006
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, decorators: [{
5007
- type: Pipe,
5008
- args: [{
5009
- standalone: false,
5010
- name: 'cellStateGroupRenderer'
5011
- }]
5012
- }] });
5013
-
6568
+ /**
6569
+ * Compatibility shim.
6570
+ *
6571
+ * Every component, directive and pipe in the library is standalone, so this
6572
+ * module declares nothing it exists so `imports: [OneGridModule]` keeps
6573
+ * working for anyone already on it. New code can import `OneGridComponent`
6574
+ * directly instead.
6575
+ *
6576
+ * Icons no longer need registering here: each component that draws one
6577
+ * provides the eight the grid uses, so they travel with the component whichever
6578
+ * way it is imported.
6579
+ */
5014
6580
  class OneGridModule {
5015
6581
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
5016
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, declarations: [OneGridComponent,
5017
- OneGridHeaderComponent,
5018
- OneGridBodyComponent,
5019
- OneGridCheckboxComponent,
5020
- OneGridCheckboxHeaderComponent,
5021
- OneGridRowActionComponent,
5022
- OneGridPaginationComponent,
6582
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [OneGridComponent,
5023
6583
  OneGridStatusComponent,
5024
- OneGridCellGroupRendererComponent,
5025
- CellFieldValueEmptyPipe,
5026
- CellValueGetterPipe,
5027
- CellFieldValuePipe,
5028
- CellActionButtonVisiblePipe,
5029
- CellEditablePipe,
5030
- CellInvalidPipe,
5031
- CellSelectedPipe,
5032
- CellActionButtonEnablePipe,
5033
- CellNumberValuePipe,
5034
- CellFieldPipe,
5035
- CellKeyPipe,
5036
- CellLevelPaddingPipe,
5037
- ColumnSortStatePipe,
5038
- HeaderCheckboxDisabledPipe,
5039
- HeaderCheckboxCheckedPipe,
5040
- HeaderCheckboxIndeterminatePipe,
5041
- CellStateRendererPipe,
5042
- CellStateGroupRendererPipe,
5043
- OneCellInputValidatorDirective,
5044
- OneCellRendererComponentDirective,
5045
- OneCellTextExpandDirective,
5046
- OneGridCellCurrencyComponent,
5047
- OneGridCellOptionMenuComponent,
5048
- OneGridRowDragHandleComponent], imports: [CommonModule,
5049
- FormsModule,
5050
- ScrollingModule,
5051
- ReactiveFormsModule, i2$1.LucideAngularModule], exports: [OneGridComponent] }); }
5052
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [CommonModule,
5053
- FormsModule,
5054
- ScrollingModule,
5055
- ReactiveFormsModule,
5056
- LucideAngularModule.pick(icons)] }); }
6584
+ OneGridCellCurrencyComponent], exports: [OneGridComponent,
6585
+ OneGridStatusComponent,
6586
+ OneGridCellCurrencyComponent] }); }
6587
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [OneGridComponent,
6588
+ OneGridCellCurrencyComponent] }); }
5057
6589
  }
5058
6590
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, decorators: [{
5059
6591
  type: NgModule,
5060
6592
  args: [{
5061
- declarations: [
6593
+ imports: [
5062
6594
  OneGridComponent,
5063
- OneGridHeaderComponent,
5064
- OneGridBodyComponent,
5065
- OneGridCheckboxComponent,
5066
- OneGridCheckboxHeaderComponent,
5067
- OneGridRowActionComponent,
5068
- OneGridPaginationComponent,
5069
6595
  OneGridStatusComponent,
5070
- OneGridCellGroupRendererComponent,
5071
- CellFieldValueEmptyPipe,
5072
- CellValueGetterPipe,
5073
- CellFieldValuePipe,
5074
- CellActionButtonVisiblePipe,
5075
- CellEditablePipe,
5076
- CellInvalidPipe,
5077
- CellSelectedPipe,
5078
- CellActionButtonEnablePipe,
5079
- CellNumberValuePipe,
5080
- CellFieldPipe,
5081
- CellKeyPipe,
5082
- CellLevelPaddingPipe,
5083
- ColumnSortStatePipe,
5084
- HeaderCheckboxDisabledPipe,
5085
- HeaderCheckboxCheckedPipe,
5086
- HeaderCheckboxIndeterminatePipe,
5087
- CellStateRendererPipe,
5088
- CellStateGroupRendererPipe,
5089
- OneCellInputValidatorDirective,
5090
- OneCellRendererComponentDirective,
5091
- OneCellTextExpandDirective,
5092
6596
  OneGridCellCurrencyComponent,
5093
- OneGridCellOptionMenuComponent,
5094
- OneGridRowDragHandleComponent
5095
6597
  ],
5096
- imports: [
5097
- CommonModule,
5098
- FormsModule,
5099
- ScrollingModule,
5100
- ReactiveFormsModule,
5101
- LucideAngularModule.pick(icons),
6598
+ exports: [
6599
+ OneGridComponent,
6600
+ OneGridStatusComponent,
6601
+ OneGridCellCurrencyComponent,
5102
6602
  ],
5103
- exports: [OneGridComponent]
5104
6603
  }]
5105
6604
  }] });
5106
6605
 
@@ -5112,5 +6611,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
5112
6611
  * Generated bundle index. Do not edit.
5113
6612
  */
5114
6613
 
5115
- export { ONE_DEFAULT_COLUMN_WIDTH, OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridRowDragService, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, applyDefaultColumnDef, getCellError, resolveColumnWidth, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams, resolveRowHeight };
6614
+ export { ONE_DEFAULT_COLUMN_WIDTH, ONE_GROUP_BLANK_LABEL, ONE_GROUP_CHILDREN_FIELD, ONE_GROUP_FLAG, ONE_GROUP_ID_FIELD, ONE_GROUP_VALUE_FIELD, OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridRowDragService, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, aggColumns, aggregate, applyDefaultColumnDef, buildCsv, buildGroupedRows, exportableColumns, getCellError, resolveCellValueGetter, resolveColumnWidth, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams, resolveRowHeight, rowGroupFields };
5116
6615
  //# sourceMappingURL=one-grid-core-angular.mjs.map