@one-grid-core/angular 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +339 -8
  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 +16 -14
  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 +2365 -850
  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 +0 -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`.
@@ -907,7 +1176,7 @@ class OneGridCellService {
907
1176
  if (!field) {
908
1177
  return { type: 'none', value: '' };
909
1178
  }
910
- const isEmpty = this._cellFieldValueEmpty.transform(node.data, field);
1179
+ const isEmpty = isCellEmpty(node.data, field);
911
1180
  const isGroup = node.group || node.leafGroup;
912
1181
  const isAutoGroup = column?.autoGroupField;
913
1182
  if (column && column.cellRenderer && !isGroup) {
@@ -919,7 +1188,7 @@ class OneGridCellService {
919
1188
  }
920
1189
  // 1. Cell Group Placeholder
921
1190
  if (isEmpty && isGroup && isAutoGroup) {
922
- return { type: 'placeholder', value: this.resolvePlaceholder(node, column) ?? 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) ?? '' };
923
1192
  }
924
1193
  // 2. Cell Group Text Value
925
1194
  if (isGroup && isAutoGroup) {
@@ -934,7 +1203,7 @@ class OneGridCellService {
934
1203
  // indented by level. Excluding it dropped every leaf through to case 5 and
935
1204
  // rendered the group column blank for the whole bottom row of a tree.
936
1205
  if (isEmpty && !isGroup) {
937
- const valueGetter = this._cellValueGetter.transform(node.data, node, column, this._context);
1206
+ const valueGetter = resolveCellValueGetter(node.data, node, column, this._context);
938
1207
  if (valueGetter) {
939
1208
  return { type: 'text', value: this.formatValue(node, column, valueGetter) };
940
1209
  }
@@ -946,7 +1215,14 @@ class OneGridCellService {
946
1215
  const value = this.formatValue(node, column, this.resolveValue(node, column));
947
1216
  return { type: 'text', value: value ?? '' };
948
1217
  }
949
- // 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
950
1226
  return { type: 'none', value: '' };
951
1227
  }
952
1228
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -1149,6 +1425,23 @@ class OneGridApi {
1149
1425
  this.treeDataChildrenField = null;
1150
1426
  this.groupDefaultExpanded = 0;
1151
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;
1152
1445
  }
1153
1446
  /**
1154
1447
  * Stand the grid up. Called by `OneGridComponent` from `ngOnInit`.
@@ -1171,7 +1464,13 @@ class OneGridApi {
1171
1464
  this.detailRendererComponent = detailRendererComponent;
1172
1465
  this.treeData = treeData;
1173
1466
  this.selectionService.setSelectionMode(rowSelection);
1174
- 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);
1175
1474
  this.cellService.applyColumnDefs(columnDefs, context);
1176
1475
  this.cellService.setGridApi(this);
1177
1476
  this.columnService.initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs, context);
@@ -1209,8 +1508,19 @@ class OneGridApi {
1209
1508
  this.selectionService.deselectAll();
1210
1509
  this.expansionService.clear();
1211
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
+ }
1212
1517
  const validRowIds = new Set();
1213
1518
  this.buildRowNodes(rowData, null, 0, validRowIds);
1519
+ // Prune BEFORE sorting and filtering: both read `_originalRowNodes`, and
1520
+ // with the prune last, a data swap while a sort was active resurrected
1521
+ // the removed rows into `_rowNodes` for one whole render — the old and
1522
+ // the new dataset on screen together until the next rebuild.
1523
+ this.pruneRemovedNodes(validRowIds);
1214
1524
  // Re-apply the active sort. `buildRowNodes` rebuilds the row list in the
1215
1525
  // order the data arrived, so without this a data update silently returned
1216
1526
  // the rows to that order while `sortModel` — and therefore the header's
@@ -1219,12 +1529,11 @@ class OneGridApi {
1219
1529
  if (this._sortModel.length > 0)
1220
1530
  this.applySorting();
1221
1531
  this.applyFilters();
1222
- this.pruneRemovedNodes(validRowIds);
1223
1532
  }
1224
1533
  /**
1225
1534
  * Recursive Build Row Nodes (Optimized)
1226
1535
  */
1227
- buildRowNodes(rowData, parent = null, level = 0, validRowIds = new Set(), expandedIds = new Set(this.expansionService.getExpandedIds()), collapsedIds = new Set(this.expansionService.getCollapsedIds()), selectedIds = new Set(this.selectionService.getSelectedIds())) {
1536
+ buildRowNodes(rowData, parent = null, level = 0, validRowIds = new Set(), expandedIds = new Set(this.expansionService.getExpandedIds()), collapsedIds = new Set(this.expansionService.getCollapsedIds()), selectedIds = new Set(this.selectionService.getSelectedIds()), visitedData = new Set()) {
1228
1537
  if (parent === null) {
1229
1538
  this._rowNodes = [];
1230
1539
  this._currentIndex = 0;
@@ -1234,6 +1543,16 @@ class OneGridApi {
1234
1543
  const nodes = [];
1235
1544
  for (const data of rowData) {
1236
1545
  const rowId = this.rowService.generateRowId(data, this._currentIndex);
1546
+ // A cycle in the children graph — a row reachable from its own subtree,
1547
+ // the classic ORM back-reference — used to recurse until the stack blew,
1548
+ // freezing the app; and with generated row ids it did so while minting
1549
+ // nodes the whole way down. The same guard also drops a duplicate row id
1550
+ // (or the same row object appearing twice): first occurrence wins.
1551
+ if (visitedData.has(data) || validRowIds.has(rowId)) {
1552
+ console.warn(`[one-grid] cyclic or duplicate row (id "${rowId}") — skipped, first occurrence wins. Tree data must be acyclic and row ids unique.`);
1553
+ continue;
1554
+ }
1555
+ visitedData.add(data);
1237
1556
  const children = data[this.treeDataChildrenField] || null;
1238
1557
  let node = this._originalRowNodes.get(rowId);
1239
1558
  validRowIds.add(rowId);
@@ -1270,7 +1589,7 @@ class OneGridApi {
1270
1589
  this._currentIndex++;
1271
1590
  this._rowNodes.push(node);
1272
1591
  nodes.push(node);
1273
- node.children = this.treeData && childrenExist ? this.buildRowNodes(children || [], node, level + 1, validRowIds, expandedIds, collapsedIds, selectedIds) : [];
1592
+ node.children = this.treeData && childrenExist ? this.buildRowNodes(children || [], node, level + 1, validRowIds, expandedIds, collapsedIds, selectedIds, visitedData) : [];
1274
1593
  }
1275
1594
  return nodes;
1276
1595
  }
@@ -1462,14 +1781,14 @@ class OneGridApi {
1462
1781
  return;
1463
1782
  this._sortModel = sortModel;
1464
1783
  this.applySorting();
1465
- this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes });
1784
+ this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes, sortModel: this._sortModel });
1466
1785
  }
1467
1786
  applySorting() {
1468
1787
  if (this._sortModel.length === 0) {
1469
1788
  this._rowNodes = [...this._originalRowNodes.values()];
1470
1789
  return;
1471
1790
  }
1472
- this._rowNodes = [...this._originalRowNodes.values()].sort((a, b) => {
1791
+ const comparator = (a, b) => {
1473
1792
  for (const sortItem of this._sortModel) {
1474
1793
  const { colId, sort } = sortItem;
1475
1794
  const valA = getFieldValue(a.data, colId);
@@ -1480,7 +1799,31 @@ class OneGridApi {
1480
1799
  }
1481
1800
  }
1482
1801
  return 0;
1483
- });
1802
+ };
1803
+ if (!this.treeData) {
1804
+ this._rowNodes = [...this._originalRowNodes.values()].sort(comparator);
1805
+ return;
1806
+ }
1807
+ // A tree sorts siblings within their parent, never across it — a flat
1808
+ // sort of the whole list interleaved parents with other parents' children
1809
+ // and scrambled the hierarchy. Sorting the sibling arrays and re-walking
1810
+ // depth-first keeps every parent immediately above its own children,
1811
+ // which `refreshVisibleNodes` also depends on for its visibility cache.
1812
+ // Group rows carry their aggregates in `data`, so sorting a grouped grid
1813
+ // on an aggregated column orders the groups by their aggregate.
1814
+ const flattened = [];
1815
+ const walk = (siblings) => {
1816
+ const sorted = [...siblings].sort(comparator);
1817
+ for (const node of sorted) {
1818
+ flattened.push(node);
1819
+ if (node.children?.length) {
1820
+ node.children = walk(node.children);
1821
+ }
1822
+ }
1823
+ return sorted;
1824
+ };
1825
+ walk([...this._originalRowNodes.values()].filter(node => !node.parent));
1826
+ this._rowNodes = flattened;
1484
1827
  }
1485
1828
  compareValues(valA, valB, sortDirection) {
1486
1829
  // `undefined` has to be treated as empty alongside `null`: getFieldValue
@@ -1536,6 +1879,50 @@ class OneGridApi {
1536
1879
  });
1537
1880
  }
1538
1881
  }
1882
+ /**
1883
+ * Select or deselect one row, honouring the grid's selection mode.
1884
+ *
1885
+ * The mode rule used to live in the checkbox component, which meant the only
1886
+ * way to select a row was to click that checkbox — anything else reaching for
1887
+ * `toggleSelection` got multi-select behaviour on a single-select grid. It
1888
+ * lives here so every path into selection obeys the same rule.
1889
+ *
1890
+ * @returns `false` when the grid has no selection mode, so a caller can tell
1891
+ * a refused request from an applied one.
1892
+ */
1893
+ toggleRowSelection(rowId, selected) {
1894
+ if (this._destroyed || !this._rowSelection)
1895
+ return false;
1896
+ const node = this.getRowNodeById(rowId);
1897
+ if (!node)
1898
+ return false;
1899
+ // Single select is "this row and no other", which means clearing first.
1900
+ if (this._rowSelection === 'single')
1901
+ this.deselectAll();
1902
+ this.toggleSelection(rowId, selected);
1903
+ node.cellStates = { ...node.cellStates };
1904
+ return true;
1905
+ }
1906
+ /**
1907
+ * Select every row between two, inclusive, in display order.
1908
+ *
1909
+ * What a shift-extended keyboard selection needs. Multi-select only — a
1910
+ * single-select grid has no meaningful range.
1911
+ */
1912
+ selectRowRange(fromRowId, toRowId) {
1913
+ if (this._destroyed || this._rowSelection !== 'multiple')
1914
+ return false;
1915
+ const order = this._visibleRowNodes.length ? this._visibleRowNodes : this._rowNodes;
1916
+ const from = order.findIndex(node => node.rowId === fromRowId);
1917
+ const to = order.findIndex(node => node.rowId === toRowId);
1918
+ if (from === -1 || to === -1)
1919
+ return false;
1920
+ const ids = order
1921
+ .slice(Math.min(from, to), Math.max(from, to) + 1)
1922
+ .map(node => node.rowId);
1923
+ this.setSelectedByIds(ids, true);
1924
+ return true;
1925
+ }
1539
1926
  toggleSelection(rowId, selected) {
1540
1927
  const node = this.getRowNodeById(rowId);
1541
1928
  if (node) {
@@ -1760,19 +2147,237 @@ class OneGridApi {
1760
2147
  this.columnService.setColumnDefs(columnDefs);
1761
2148
  const columnState = this.columnService.getColumnState();
1762
2149
  this.cellService.applyColumnDefs(columnState.columns, this._context);
2150
+ // Cell state is derived from the columns, so replacing them wholesale
2151
+ // stales every node built before this ran. Tree and grouping mode hit
2152
+ // this on every load: the group column registers from a deferred
2153
+ // setColumnDefs, after the host has typically already loaded rows —
2154
+ // hosts used to have to defer setRowData themselves to dodge it.
2155
+ this.regenerateCellStates();
1763
2156
  this.eventService.dispatchEvent('columnChanged', columnState);
1764
2157
  }
1765
2158
  updateColumn(id, changes) {
2159
+ const before = this.visibleColumnFields();
1766
2160
  this.columnService.updateColumnDef(id, changes);
1767
2161
  const columnState = this.columnService.getColumnState();
1768
2162
  this.cellService.applyColumnDefs(columnState.columns, this._context);
2163
+ // Only when the set of visible columns changed (a hide, a show): a cell
2164
+ // state can appear or disappear then. Width patches — this method runs
2165
+ // per mousemove during a resize — skip the O(rows × columns) pass.
2166
+ if (this.visibleColumnFields() !== before)
2167
+ this.regenerateCellStates();
1769
2168
  this.eventService.dispatchEvent('columnChanged', columnState);
1770
2169
  }
2170
+ /** The visible fields as one comparable key. */
2171
+ visibleColumnFields() {
2172
+ return this.columnService.getColumnState().columns?.map(column => column.field).join('|') ?? '';
2173
+ }
2174
+ /** Recompute every node's cell states against the current columns. */
2175
+ regenerateCellStates() {
2176
+ const refresh = (node) => node.setCellStates(this.cellService.generateCellStates(node));
2177
+ this._rowNodes.forEach(refresh);
2178
+ this._pinnedRowNodes.top.forEach(refresh);
2179
+ this._pinnedRowNodes.bottom.forEach(refresh);
2180
+ }
1771
2181
  reorderColumnDefs(fromIndex, toIndex) {
1772
2182
  this.columnService.reorderColumnDefs(fromIndex, toIndex);
1773
2183
  const columnState = this.columnService.getColumnState();
1774
2184
  this.eventService.dispatchEvent('columnChanged', columnState);
1775
2185
  }
2186
+ /** The fields the rows are grouped by, in nesting order. Empty when off. */
2187
+ get rowGroupFields() {
2188
+ return this._rowGroupFields;
2189
+ }
2190
+ /**
2191
+ * Turn row grouping on. Called by `OneGridComponent` when column
2192
+ * definitions declare `rowGroup`; the grid then behaves as a tree over
2193
+ * synthetic group rows, and `setRowData` / `updateRowData` keep taking the
2194
+ * flat rows they always took.
2195
+ */
2196
+ setRowGrouping(groupFields, aggregations = []) {
2197
+ this._rowGroupFields = groupFields ?? [];
2198
+ this._aggColumns = aggregations ?? [];
2199
+ }
2200
+ /**
2201
+ * Start recording committed cell edits for undo/redo. Called by
2202
+ * `OneGridComponent` from `[undoRedoCellEditing]`; the stack keeps the last
2203
+ * `limit` edits. Programmatic `setDataValue` writes are not recorded — only
2204
+ * commits, the ones that raise `cellValueChanged` without a source.
2205
+ */
2206
+ enableUndoRedoCellEditing(limit = 10) {
2207
+ if (this._undoLimit > 0)
2208
+ return;
2209
+ this._undoLimit = Math.max(Math.floor(limit), 1);
2210
+ this.eventService.addEventListener('cellValueChanged', (event) => {
2211
+ if (event.source)
2212
+ return;
2213
+ this._undoStack.push({ rowId: event.node.rowId, field: event.field, oldValue: event.oldValue, newValue: event.newValue });
2214
+ if (this._undoStack.length > this._undoLimit)
2215
+ this._undoStack.shift();
2216
+ // A new edit forks history; what was undone can no longer be redone.
2217
+ this._redoStack = [];
2218
+ });
2219
+ }
2220
+ /** Revert the most recent committed edit. Returns whether one was undone. */
2221
+ undoCellEditing() {
2222
+ return this.replayEdit(this._undoStack, this._redoStack, 'undo');
2223
+ }
2224
+ /** Re-apply the most recently undone edit. Returns whether one was redone. */
2225
+ redoCellEditing() {
2226
+ return this.replayEdit(this._redoStack, this._undoStack, 'redo');
2227
+ }
2228
+ getCurrentUndoSize() {
2229
+ return this._undoStack.length;
2230
+ }
2231
+ getCurrentRedoSize() {
2232
+ return this._redoStack.length;
2233
+ }
2234
+ replayEdit(from, to, source) {
2235
+ if (this._destroyed)
2236
+ return false;
2237
+ const edit = from.pop();
2238
+ if (!edit)
2239
+ return false;
2240
+ const node = this.getRowNodeById(edit.rowId);
2241
+ // The row can be gone — a setRowData replaced the data under the stack.
2242
+ // The edit is dropped rather than replayed onto nothing.
2243
+ if (!node)
2244
+ return false;
2245
+ const value = source === 'undo' ? edit.oldValue : edit.newValue;
2246
+ const previous = source === 'undo' ? edit.newValue : edit.oldValue;
2247
+ node.data[edit.field] = value;
2248
+ this.updateCellStates(node);
2249
+ to.push(edit);
2250
+ const column = this.columnService.getColumnState().columns?.find(col => col.field === edit.field) ?? null;
2251
+ this.eventService.dispatchEvent('cellValueChanged', {
2252
+ node,
2253
+ data: node.data,
2254
+ column: column,
2255
+ field: edit.field,
2256
+ oldValue: previous,
2257
+ newValue: value,
2258
+ source,
2259
+ });
2260
+ return true;
2261
+ }
2262
+ /** The paging state, when the grid pages its own rows. */
2263
+ get paginationState() {
2264
+ return { enabled: this._clientPagination, pageSize: this._pageSize, currentPage: this._currentPage };
2265
+ }
2266
+ /**
2267
+ * Turn grid-owned paging on or off. Called by `OneGridComponent` from its
2268
+ * `clientSidePagination` input; the display slice is what changes — the
2269
+ * full dataset stays behind the API (selection, filters and sorting keep
2270
+ * operating on all of it).
2271
+ */
2272
+ setClientSidePagination(enabled, pageSize) {
2273
+ this._clientPagination = enabled;
2274
+ if (pageSize && pageSize > 0)
2275
+ this._pageSize = pageSize;
2276
+ this._currentPage = 1;
2277
+ }
2278
+ /** Change the page size and return to the first page. */
2279
+ setPageSize(pageSize) {
2280
+ if (this._destroyed || !(pageSize > 0))
2281
+ return;
2282
+ this._pageSize = Math.floor(pageSize);
2283
+ this._currentPage = 1;
2284
+ this.applyFilters();
2285
+ }
2286
+ /** Move to a page (1-based). Clamped to at least 1; the top end clamps when the slice is taken. */
2287
+ setCurrentPage(page) {
2288
+ if (this._destroyed)
2289
+ return;
2290
+ const target = Math.max(Math.floor(page), 1);
2291
+ if (target === this._currentPage)
2292
+ return;
2293
+ this._currentPage = target;
2294
+ this.applyFilters();
2295
+ }
2296
+ /**
2297
+ * The slice of rows the current page shows — a passthrough while paging is
2298
+ * off. The body routes its render list through here, after reporting the
2299
+ * full count, so `rowCountChanged` keeps meaning "rows after filtering"
2300
+ * whether or not the grid pages.
2301
+ */
2302
+ pageNodes(nodes) {
2303
+ if (!this._clientPagination)
2304
+ return (nodes ?? []);
2305
+ const lastPage = Math.max(Math.ceil((nodes?.length ?? 0) / this._pageSize), 1);
2306
+ // A shrinking dataset (a filter, a delete) can strand the page past the
2307
+ // end; clamping here shows the last page instead of an empty one.
2308
+ if (this._currentPage > lastPage)
2309
+ this._currentPage = lastPage;
2310
+ const start = (this._currentPage - 1) * this._pageSize;
2311
+ return (nodes ?? []).slice(start, start + this._pageSize);
2312
+ }
2313
+ /**
2314
+ * Size the flex columns into the given width.
2315
+ *
2316
+ * Called by the grid body with its own viewport width — on first render, on
2317
+ * container resize and after column changes — so `columnDef.flex` works
2318
+ * without the host doing anything. Also callable directly with any width.
2319
+ * A no-op when no visible column declares `flex` or nothing would change.
2320
+ */
2321
+ applyFlexWidths(availableWidth) {
2322
+ if (this._destroyed)
2323
+ return;
2324
+ if (!this.columnService.applyFlexWidths(availableWidth))
2325
+ return;
2326
+ const columnState = this.columnService.getColumnState();
2327
+ this.cellService.applyColumnDefs(columnState.columns, this._context);
2328
+ this.eventService.dispatchEvent('columnChanged', columnState);
2329
+ }
2330
+ /**
2331
+ * The current grid as CSV: visible columns in display order, visible rows
2332
+ * in current sort order, values through the same getter/formatter pipeline
2333
+ * the cells render with. Pinned rows and detail grids are not data and are
2334
+ * not included.
2335
+ */
2336
+ getDataAsCsv(options = {}) {
2337
+ const nodes = options.onlySelected ? this.getSelectedRowNodes() : this._visibleRowNodes;
2338
+ return buildCsv(nodes, this.columnService.getColumnState().columns, this._context, options);
2339
+ }
2340
+ /** Build the CSV and hand it to the browser as a download. */
2341
+ exportDataAsCsv(options = {}) {
2342
+ if (typeof document === 'undefined')
2343
+ return;
2344
+ const blob = new Blob([this.getDataAsCsv(options)], { type: 'text/csv;charset=utf-8;' });
2345
+ const link = document.createElement('a');
2346
+ link.href = URL.createObjectURL(blob);
2347
+ link.download = options.fileName ?? 'export.csv';
2348
+ link.click();
2349
+ URL.revokeObjectURL(link.href);
2350
+ }
2351
+ /**
2352
+ * Show or hide one column. A hidden column keeps its order, width and pin,
2353
+ * so showing it again restores it exactly.
2354
+ */
2355
+ setColumnVisible(id, visible) {
2356
+ this.updateColumn(id, { hide: !visible });
2357
+ }
2358
+ /**
2359
+ * Persistable layout state: one JSON-safe entry per column — hidden ones
2360
+ * included — in display order. Feed it back through `applyColumnState`.
2361
+ */
2362
+ getColumnStateSnapshot() {
2363
+ return this.columnService.getColumnStateSnapshot();
2364
+ }
2365
+ /**
2366
+ * Restore a snapshot from `getColumnStateSnapshot`: order, width, pin and
2367
+ * visibility. Ids the grid no longer has are skipped, so a stale snapshot
2368
+ * degrades to a partial restore rather than an error.
2369
+ */
2370
+ applyColumnState(entries) {
2371
+ if (this._destroyed)
2372
+ return;
2373
+ this.columnService.applyColumnState(entries);
2374
+ const columnState = this.columnService.getColumnState();
2375
+ this.cellService.applyColumnDefs(columnState.columns, this._context);
2376
+ // A restore can show columns that were hidden when the rows were built,
2377
+ // and their cells have no state yet.
2378
+ this.regenerateCellStates();
2379
+ this.eventService.dispatchEvent('columnChanged', columnState);
2380
+ }
1776
2381
  /**
1777
2382
  * Update checkbox selection features
1778
2383
  */
@@ -2359,47 +2964,84 @@ function resolveRowHeight(params, getRowHeight, rowHeight = 26) {
2359
2964
  return rowHeight;
2360
2965
  }
2361
2966
 
2362
- class HeaderCheckboxDisabledPipe {
2363
- transform(rowNodes, selectionMode, allowedEdit) {
2364
- return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
2365
- }
2366
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2367
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, name: "headerCheckboxDisabled" }); }
2368
- }
2369
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, decorators: [{
2370
- type: Pipe,
2371
- args: [{
2372
- standalone: false,
2373
- name: 'headerCheckboxDisabled'
2374
- }]
2375
- }] });
2967
+ /**
2968
+ * The Lucide icons the grid draws, and only those.
2969
+ *
2970
+ * Every component that renders a `<lucide-icon>` picks this set itself, so the
2971
+ * icons travel with the component. That matters now the components are
2972
+ * standalone: a consumer importing `OneGridComponent` directly never touches
2973
+ * `OneGridModule`, and previously got a runtime error — "The 'chevron-first'
2974
+ * icon has not been provided by any available icon providers" — the moment the
2975
+ * pagination bar rendered.
2976
+ *
2977
+ * Eight icons rather than `LucideAngularModule.pick(icons)`, which registered
2978
+ * the entire Lucide set. Nothing else in the grid uses one, and the rest were
2979
+ * dead weight in every consumer's bundle.
2980
+ */
2981
+ const ONE_GRID_ICONS = {
2982
+ ArrowDownUp,
2983
+ ArrowDownZA,
2984
+ ArrowUpAZ,
2985
+ ChevronFirst,
2986
+ ChevronLast,
2987
+ ChevronLeft,
2988
+ ChevronRight,
2989
+ GripVertical,
2990
+ };
2991
+ /**
2992
+ * Providers that register {@link ONE_GRID_ICONS} for one standalone component.
2993
+ *
2994
+ * `LucideAngularModule.pick()` cannot be used here: it returns a
2995
+ * `ModuleWithProviders`, and a standalone component's `imports` only accepts
2996
+ * components, directives, pipes and NgModule classes. This is the same
2997
+ * registration `pick` performs, expressed as providers so it can sit on the
2998
+ * component instead.
2999
+ */
3000
+ const ONE_GRID_ICON_PROVIDERS = [
3001
+ { provide: LUCIDE_ICONS, multi: true, useValue: new LucideIconProvider(ONE_GRID_ICONS) },
3002
+ ];
2376
3003
 
2377
3004
  class HeaderCheckboxCheckedPipe {
2378
3005
  transform(rowNodes, selectionMode, state) {
2379
3006
  return rowNodes?.length > 0 && selectionMode === 'multiple' && state === 'checked';
2380
3007
  }
2381
3008
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2382
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, name: "headerCheckboxChecked" }); }
3009
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, isStandalone: true, name: "headerCheckboxChecked" }); }
2383
3010
  }
2384
3011
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, decorators: [{
2385
3012
  type: Pipe,
2386
3013
  args: [{
2387
- standalone: false,
3014
+ standalone: true,
2388
3015
  name: 'headerCheckboxChecked'
2389
3016
  }]
2390
3017
  }] });
2391
3018
 
3019
+ class HeaderCheckboxDisabledPipe {
3020
+ transform(rowNodes, selectionMode, allowedEdit) {
3021
+ return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
3022
+ }
3023
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3024
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, isStandalone: true, name: "headerCheckboxDisabled" }); }
3025
+ }
3026
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, decorators: [{
3027
+ type: Pipe,
3028
+ args: [{
3029
+ standalone: true,
3030
+ name: 'headerCheckboxDisabled'
3031
+ }]
3032
+ }] });
3033
+
2392
3034
  class HeaderCheckboxIndeterminatePipe {
2393
3035
  transform(rowNodes, selectionMode, state) {
2394
3036
  return rowNodes?.length > 0 && selectionMode === 'multiple' && state === 'indeterminate';
2395
3037
  }
2396
3038
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2397
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, name: "headerCheckboxIndeterminate" }); }
3039
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, isStandalone: true, name: "headerCheckboxIndeterminate" }); }
2398
3040
  }
2399
3041
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, decorators: [{
2400
3042
  type: Pipe,
2401
3043
  args: [{
2402
- standalone: false,
3044
+ standalone: true,
2403
3045
  name: 'headerCheckboxIndeterminate'
2404
3046
  }]
2405
3047
  }] });
@@ -2457,36 +3099,200 @@ class OneGridCheckboxHeaderComponent {
2457
3099
  }
2458
3100
  }
2459
3101
  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 }); }
2460
- 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" }] }); }
3102
+ 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" }] }); }
2461
3103
  }
2462
3104
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxHeaderComponent, decorators: [{
2463
3105
  type: Component,
2464
- 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>" }]
3106
+ 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>" }]
2465
3107
  }], ctorParameters: () => [{ type: OneGridEventService }, { type: OneGridSelectionService }], propDecorators: { api: [{
2466
3108
  type: Input
2467
3109
  }], allowedEdit: [{
2468
3110
  type: Input
2469
3111
  }] } });
2470
3112
 
2471
- class ColumnSortStatePipe {
2472
- // `readonly`, because this is fed straight from `api.sortModel`, which is
2473
- // now a readonly view rather than the mutable array it used to hand out.
2474
- transform(field, sortModel) {
2475
- if (!sortModel || !field)
3113
+ /**
3114
+ * The grid's default column menu, opened by the header's ⋮ button when the
3115
+ * grid sets `[headerMenu]="true"`.
3116
+ *
3117
+ * Entries follow the column: sorting only on a sortable column and only the
3118
+ * transitions that make sense from the current state, pinning without the
3119
+ * side it is already on, hiding never for the last visible column. The menu
3120
+ * only reports the chosen action — the header executes it against the API,
3121
+ * for the same reason the cell option menu does not mutate rows itself.
3122
+ *
3123
+ * Keyboard behaviour is the menu pattern the cell option menu established:
3124
+ * focus moves in on open, arrows rove, Escape and outside clicks close.
3125
+ */
3126
+ class OneGridHeaderMenuComponent {
3127
+ get menuLabel() {
3128
+ return `Column menu for ${this.column?.headerName ?? this.column?.field ?? 'this column'}`;
3129
+ }
3130
+ constructor(elementRef) {
3131
+ this.elementRef = elementRef;
3132
+ this.column = null;
3133
+ /** The column's current sort, so the menu offers only the other states. */
3134
+ this.sortState = null;
3135
+ /** False when this is the last visible column, which must stay visible. */
3136
+ this.canHide = true;
3137
+ this.menuAction = new EventEmitter();
3138
+ this.menuClose = new EventEmitter();
3139
+ this.entries = [];
3140
+ this.activeIndex = 0;
3141
+ }
3142
+ /**
3143
+ * Built once on open rather than in a getter: the entries feed the roving
3144
+ * tabindex, and a list that re-derives mid-interaction would move focus.
3145
+ */
3146
+ ngOnInit() {
3147
+ const entries = [];
3148
+ const column = this.column;
3149
+ if (column?._resolvedSortable && column.field) {
3150
+ if (this.sortState !== 'asc')
3151
+ entries.push({ action: 'sortAsc', label: 'Sort ascending' });
3152
+ if (this.sortState !== 'desc')
3153
+ entries.push({ action: 'sortDesc', label: 'Sort descending' });
3154
+ if (this.sortState)
3155
+ entries.push({ action: 'clearSort', label: 'Clear sort' });
3156
+ }
3157
+ if (column?.pinned !== 'left')
3158
+ entries.push({ action: 'pinLeft', label: 'Pin left' });
3159
+ if (column?.pinned !== 'right')
3160
+ entries.push({ action: 'pinRight', label: 'Pin right' });
3161
+ if (column?.pinned)
3162
+ entries.push({ action: 'unpin', label: 'Unpin' });
3163
+ if (this.canHide)
3164
+ entries.push({ action: 'hide', label: 'Hide column' });
3165
+ this.entries = entries;
3166
+ }
3167
+ ngAfterViewInit() {
3168
+ this.focusItem(0);
3169
+ }
3170
+ onEntrySelected(entry) {
3171
+ this.menuAction.emit(entry.action);
3172
+ this.menuClose.emit();
3173
+ }
3174
+ onItemKeydown(event, index, entry) {
3175
+ switch (event.key) {
3176
+ case 'ArrowDown':
3177
+ event.preventDefault();
3178
+ this.focusItem((index + 1) % this.entries.length);
3179
+ return;
3180
+ case 'ArrowUp':
3181
+ event.preventDefault();
3182
+ this.focusItem((index - 1 + this.entries.length) % this.entries.length);
3183
+ return;
3184
+ case 'Home':
3185
+ event.preventDefault();
3186
+ this.focusItem(0);
3187
+ return;
3188
+ case 'End':
3189
+ event.preventDefault();
3190
+ this.focusItem(this.entries.length - 1);
3191
+ return;
3192
+ case 'Enter':
3193
+ case ' ':
3194
+ event.preventDefault();
3195
+ this.onEntrySelected(entry);
3196
+ return;
3197
+ default:
3198
+ return;
3199
+ }
3200
+ }
3201
+ focusItem(index) {
3202
+ if (!this.entries.length)
3203
+ return;
3204
+ this.activeIndex = index;
3205
+ this.items?.get(index)?.nativeElement?.focus();
3206
+ }
3207
+ onDocumentClick(event) {
3208
+ if (!this.elementRef.nativeElement.contains(event.target)) {
3209
+ this.menuClose.emit();
3210
+ }
3211
+ }
3212
+ onEscape() {
3213
+ this.menuClose.emit();
3214
+ }
3215
+ onScroll() {
3216
+ this.menuClose.emit();
3217
+ }
3218
+ 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 }); }
3219
+ 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" }); }
3220
+ }
3221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderMenuComponent, decorators: [{
3222
+ type: Component,
3223
+ 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" }]
3224
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
3225
+ type: Input
3226
+ }], sortState: [{
3227
+ type: Input
3228
+ }], canHide: [{
3229
+ type: Input
3230
+ }], menuAction: [{
3231
+ type: Output
3232
+ }], menuClose: [{
3233
+ type: Output
3234
+ }], items: [{
3235
+ type: ViewChildren,
3236
+ args: ['item']
3237
+ }], onDocumentClick: [{
3238
+ type: HostListener,
3239
+ args: ['document:click', ['$event']]
3240
+ }], onEscape: [{
3241
+ type: HostListener,
3242
+ args: ['document:keydown.escape']
3243
+ }], onScroll: [{
3244
+ type: HostListener,
3245
+ args: ['document:scroll']
3246
+ }] } });
3247
+
3248
+ class ColumnSortStatePipe {
3249
+ // `readonly`, because this is fed straight from `api.sortModel`, which is
3250
+ // now a readonly view rather than the mutable array it used to hand out.
3251
+ transform(field, sortModel) {
3252
+ if (!sortModel || !field)
2476
3253
  return null;
2477
3254
  return sortModel.find(s => s.colId === field)?.sort ?? null;
2478
3255
  }
2479
3256
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2480
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, name: "columnSortState" }); }
3257
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, isStandalone: true, name: "columnSortState" }); }
2481
3258
  }
2482
3259
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, decorators: [{
2483
3260
  type: Pipe,
2484
3261
  args: [{
2485
- standalone: false,
3262
+ standalone: true,
2486
3263
  name: 'columnSortState'
2487
3264
  }]
2488
3265
  }] });
2489
3266
 
3267
+ /**
3268
+ * Maps the grid's sort state onto the values `aria-sort` accepts.
3269
+ *
3270
+ * The attribute takes `ascending` / `descending` / `none`, not the `asc` /
3271
+ * `desc` the sort model uses. `none` is only correct on a column that *can* be
3272
+ * sorted — on one that cannot, the attribute is omitted entirely, because
3273
+ * "not sorted" and "not sortable" are different statements.
3274
+ */
3275
+ class AriaSortPipe {
3276
+ transform(sort, sortable) {
3277
+ if (!sortable)
3278
+ return null;
3279
+ if (sort === 'asc')
3280
+ return 'ascending';
3281
+ if (sort === 'desc')
3282
+ return 'descending';
3283
+ return 'none';
3284
+ }
3285
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3286
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, isStandalone: true, name: "ariaSort" }); }
3287
+ }
3288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, decorators: [{
3289
+ type: Pipe,
3290
+ args: [{
3291
+ standalone: true,
3292
+ name: 'ariaSort',
3293
+ }]
3294
+ }] });
3295
+
2490
3296
  class OneGridHeaderComponent {
2491
3297
  /**
2492
3298
  * Horizontal offset of the body, mirrored here so the header tracks it.
@@ -2502,9 +3308,10 @@ class OneGridHeaderComponent {
2502
3308
  get scrollLeft() {
2503
3309
  return this._scrollLeft;
2504
3310
  }
2505
- constructor(eventService, cdr) {
3311
+ constructor(eventService, cdr, elementRef) {
2506
3312
  this.eventService = eventService;
2507
3313
  this.cdr = cdr;
3314
+ this.elementRef = elementRef;
2508
3315
  this._scrollLeft = 0;
2509
3316
  this.allowedEdit = false;
2510
3317
  this.enableGroupColumnDefs = false;
@@ -2513,11 +3320,51 @@ class OneGridHeaderComponent {
2513
3320
  this.sortModel = [];
2514
3321
  this.subscriptions = new Subscription();
2515
3322
  this.draggedIndex = null;
3323
+ /** Where the drag started, for reporting the completed move as one event. */
3324
+ this.dragSourceIndex = null;
2516
3325
  // Track resize state
2517
3326
  this.resizing = false;
2518
3327
  this.resizeColumnIndex = null;
2519
3328
  this.startX = 0;
2520
3329
  this.startWidth = 0;
3330
+ /**
3331
+ * Render a filter input under each column whose `filterable` resolves true.
3332
+ *
3333
+ * The inputs build a contains-filter per column and hand it to
3334
+ * `api.setFilterModel`, debounced. Fields without a filter input are left
3335
+ * alone: a programmatic filter on a non-filterable column survives typing
3336
+ * here, while the filterable fields belong to the row — last writer wins.
3337
+ */
3338
+ this.filterRow = false;
3339
+ /** Current filter text per field. */
3340
+ this.filterTexts = {};
3341
+ this.filterInput$ = new Subject();
3342
+ /**
3343
+ * Minimum a column can be resized to, by pointer or keyboard.
3344
+ *
3345
+ * Exposed to the template for `aria-valuemin`, so what the resizer announces
3346
+ * and what it enforces cannot drift apart.
3347
+ */
3348
+ this.MIN_COLUMN_WIDTH = 50;
3349
+ /**
3350
+ * The header's ⋮ button was a div with an empty handler — it rendered,
3351
+ * looked interactive, and did nothing at all. Making it keyboard-reachable
3352
+ * without giving it a purpose would only have added a dead tab stop per
3353
+ * column, so it now reports the press the same way every row action does and
3354
+ * the host decides what a column menu means.
3355
+ */
3356
+ this.headerMenuClick = new EventEmitter();
3357
+ /**
3358
+ * Render the grid's default column menu behind the ⋮ button.
3359
+ *
3360
+ * Opt-in: hosts that bound `(headerMenuClick)` to open their own menu keep
3361
+ * exactly the behaviour they had — the intent still emits either way.
3362
+ */
3363
+ this.headerMenu = false;
3364
+ /** The column whose default menu is open, or null. */
3365
+ this.openMenuColumn = null;
3366
+ /** Distance from the header's right edge to the open column's, in px. */
3367
+ this.menuOffsetRight = 0;
2521
3368
  // Arrow function to preserve 'this' context
2522
3369
  this.onResizeMove = (event) => {
2523
3370
  if (!this.resizing || this.resizeColumnIndex === null)
@@ -2525,11 +3372,21 @@ class OneGridHeaderComponent {
2525
3372
  // Calculate how far the mouse has moved
2526
3373
  const deltaX = event.clientX - this.startX;
2527
3374
  // Calculate new width (with minimum of 50px)
2528
- const newWidth = Math.max(50, this.startWidth + deltaX);
2529
- // Update the column width
2530
- this.api.updateColumn(this.columnDefs[this.resizeColumnIndex].id, { width: newWidth });
3375
+ const newWidth = Math.max(this.MIN_COLUMN_WIDTH, this.startWidth + deltaX);
3376
+ // Update the column width. A hand-resized column stops flexing, or the
3377
+ // next reflow would overwrite the width the user is dragging out.
3378
+ this.api.updateColumn(this.columnDefs[this.resizeColumnIndex].id, { width: newWidth, flex: undefined });
2531
3379
  };
2532
3380
  this.onResizeEnd = () => {
3381
+ // One event per completed gesture, not one per mousemove — and none at all
3382
+ // for a click on the handle that never moved.
3383
+ if (this.resizeColumnIndex !== null) {
3384
+ const column = this.columnDefs[this.resizeColumnIndex];
3385
+ const newWidth = column?.width ?? this.startWidth;
3386
+ if (column && newWidth !== this.startWidth) {
3387
+ this.eventService.dispatchEvent('columnResized', { columnId: column.id, newWidth });
3388
+ }
3389
+ }
2533
3390
  // Clean up
2534
3391
  this.resizing = false;
2535
3392
  this.resizeColumnIndex = null;
@@ -2539,7 +3396,30 @@ class OneGridHeaderComponent {
2539
3396
  document.removeEventListener('mouseup', this.onResizeEnd);
2540
3397
  };
2541
3398
  }
3399
+ /** Debounce for the filter row, in ms. */
3400
+ static { this.FILTER_DEBOUNCE = 200; }
3401
+ onFilterInput(col, event) {
3402
+ this.filterTexts[col.field] = event.target.value;
3403
+ this.filterInput$.next();
3404
+ }
3405
+ applyFilterRow() {
3406
+ const managed = new Set(this.columnDefs
3407
+ .filter(column => column._resolvedFilterable && column.field)
3408
+ .map(column => column.field));
3409
+ const preserved = this.api.getFilterModel().filter(entry => !managed.has(entry.field));
3410
+ const own = Object.entries(this.filterTexts)
3411
+ .filter(([field, text]) => managed.has(field) && text.trim() !== '')
3412
+ .map(([field, text]) => ({
3413
+ field,
3414
+ logicOperator: 'AND',
3415
+ conditions: [{ operator: 'contains', value: text.trim() }],
3416
+ }));
3417
+ this.api.setFilterModel([...preserved, ...own]);
3418
+ }
2542
3419
  ngOnInit() {
3420
+ this.subscriptions.add(this.filterInput$
3421
+ .pipe(debounceTime(OneGridHeaderComponent.FILTER_DEBOUNCE))
3422
+ .subscribe(() => this.applyFilterRow()));
2543
3423
  // Listen Column Changed Event
2544
3424
  this.subscriptions.add(this.eventService.addEventListener('columnChanged', (payload) => {
2545
3425
  const { groups, columns } = payload;
@@ -2562,7 +3442,100 @@ class OneGridHeaderComponent {
2562
3442
  this.headerViewport.nativeElement.scrollLeft = this._scrollLeft;
2563
3443
  }
2564
3444
  }
2565
- onHeaderMenuOptionClick(columnDef) {
3445
+ onHeaderMenuOptionClick(columnDef, event) {
3446
+ this.headerMenuClick.emit(columnDef);
3447
+ if (!this.headerMenu)
3448
+ return;
3449
+ if (this.openMenuColumn?.id === columnDef.id) {
3450
+ this.openMenuColumn = null;
3451
+ return;
3452
+ }
3453
+ // Right-aligned under the opening column. Measured because the menu
3454
+ // cannot render inside the column — every box in the header clips — so
3455
+ // it sits outside and has to be told where the column is.
3456
+ const columnElement = event?.target?.closest('.one-grid-header-column');
3457
+ const hostElement = this.elementRef.nativeElement;
3458
+ this.menuOffsetRight = columnElement
3459
+ ? Math.max(hostElement.getBoundingClientRect().right - columnElement.getBoundingClientRect().right, 0)
3460
+ : 0;
3461
+ this.openMenuColumn = columnDef;
3462
+ }
3463
+ /** The open column's current sort, so the menu offers the other states. */
3464
+ menuSortState(col) {
3465
+ return this.api.sortModel.find(entry => entry.colId === col.field)?.sort ?? null;
3466
+ }
3467
+ onHeaderMenuAction(action, col) {
3468
+ switch (action) {
3469
+ case 'sortAsc':
3470
+ case 'sortDesc': {
3471
+ // Mirrors a sort-button click: a single-column model. The header's own
3472
+ // cycle state follows, so the sort button keeps cycling from here.
3473
+ this.sortModel = [{ colId: col.field, sort: action === 'sortAsc' ? 'asc' : 'desc' }];
3474
+ this.api.onSortChanged(this.sortModel);
3475
+ return;
3476
+ }
3477
+ case 'clearSort':
3478
+ this.sortModel = [];
3479
+ this.api.onSortChanged(this.sortModel);
3480
+ return;
3481
+ case 'pinLeft':
3482
+ this.api.updateColumn(col.id, { pinned: 'left' });
3483
+ return;
3484
+ case 'pinRight':
3485
+ this.api.updateColumn(col.id, { pinned: 'right' });
3486
+ return;
3487
+ case 'unpin':
3488
+ this.api.updateColumn(col.id, { pinned: undefined });
3489
+ return;
3490
+ case 'hide':
3491
+ this.api.setColumnVisible(col.id, false);
3492
+ return;
3493
+ }
3494
+ }
3495
+ closeHeaderMenu() {
3496
+ this.openMenuColumn = null;
3497
+ }
3498
+ /**
3499
+ * Resize from the keyboard.
3500
+ *
3501
+ * The pointer path is a drag, and WCAG 2.5.7 requires a single-pointer
3502
+ * alternative that is not one. Arrows step, shift steps coarser, and
3503
+ * Home/End go to the column's own bounds.
3504
+ */
3505
+ onResizeKeydown(event, columnIndex) {
3506
+ const column = this.columnDefs[columnIndex];
3507
+ if (!column)
3508
+ return;
3509
+ const step = event.shiftKey ? 50 : 10;
3510
+ const current = column.width ?? resolveColumnWidth(column);
3511
+ let width;
3512
+ switch (event.key) {
3513
+ case 'ArrowLeft':
3514
+ width = current - step;
3515
+ break;
3516
+ case 'ArrowRight':
3517
+ width = current + step;
3518
+ break;
3519
+ case 'Home':
3520
+ width = column.minWidth ?? this.MIN_COLUMN_WIDTH;
3521
+ break;
3522
+ case 'End':
3523
+ width = column.maxWidth ?? current + step;
3524
+ break;
3525
+ default: return;
3526
+ }
3527
+ event.preventDefault();
3528
+ const min = Math.max(column.minWidth ?? 0, this.MIN_COLUMN_WIDTH);
3529
+ const max = column.maxWidth ?? Number.POSITIVE_INFINITY;
3530
+ const newWidth = Math.min(Math.max(width, min), max);
3531
+ // Home at the minimum, End at the maximum: already clamped there, nothing
3532
+ // changed, nothing to announce.
3533
+ if (newWidth === current)
3534
+ return;
3535
+ // A hand-resized column stops flexing — otherwise the next reflow would
3536
+ // overwrite the width the user just chose.
3537
+ this.api.updateColumn(column.id, { width: newWidth, flex: undefined });
3538
+ this.eventService.dispatchEvent('columnResized', { columnId: column.id, newWidth });
2566
3539
  }
2567
3540
  onHeaderMenuSortClick(colId) {
2568
3541
  const currentSort = this.sortModel.find(sort => sort.colId === colId)?.sort ?? null;
@@ -2602,6 +3575,7 @@ class OneGridHeaderComponent {
2602
3575
  // Track the dragged column index
2603
3576
  onDragColumnStart(event, index) {
2604
3577
  this.draggedIndex = index;
3578
+ this.dragSourceIndex = index;
2605
3579
  // Optional: Set drag image/effect
2606
3580
  if (event.dataTransfer) {
2607
3581
  event.dataTransfer.effectAllowed = 'move';
@@ -2619,11 +3593,27 @@ class OneGridHeaderComponent {
2619
3593
  this.draggedIndex = targetIndex;
2620
3594
  }
2621
3595
  onDragColumnEnd(event) {
2622
- this.draggedIndex = null;
3596
+ this.finishColumnDrag();
2623
3597
  }
2624
3598
  onDropColumn(event) {
2625
3599
  event.preventDefault();
3600
+ this.finishColumnDrag();
3601
+ }
3602
+ /**
3603
+ * The reorder itself already happened, step by step, during dragover — this
3604
+ * only reports the completed move. Both `drop` and `dragend` funnel here and
3605
+ * both can fire for one gesture; nulling the indexes makes the second call a
3606
+ * no-op, so one gesture reports once.
3607
+ */
3608
+ finishColumnDrag() {
3609
+ if (this.draggedIndex !== null && this.dragSourceIndex !== null && this.draggedIndex !== this.dragSourceIndex) {
3610
+ const column = this.columnDefs[this.draggedIndex];
3611
+ if (column) {
3612
+ this.eventService.dispatchEvent('columnMoved', { columnId: column.id, toIndex: this.draggedIndex });
3613
+ }
3614
+ }
2626
3615
  this.draggedIndex = null;
3616
+ this.dragSourceIndex = null;
2627
3617
  }
2628
3618
  ngOnDestroy() {
2629
3619
  // This completed an unused Subject and left the real subscription running.
@@ -2636,13 +3626,22 @@ class OneGridHeaderComponent {
2636
3626
  this.onResizeEnd();
2637
3627
  }
2638
3628
  }
2639
- 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 }); }
2640
- 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" }] }); }
3629
+ 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 }); }
3630
+ 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" }] }); }
2641
3631
  }
2642
3632
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, decorators: [{
2643
3633
  type: Component,
2644
- 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" }]
2645
- }], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }], propDecorators: { headerViewport: [{
3634
+ args: [{ standalone: true, imports: [
3635
+ NgClass,
3636
+ NgStyle,
3637
+ NgTemplateOutlet,
3638
+ LucideAngularModule,
3639
+ OneGridCheckboxHeaderComponent,
3640
+ OneGridHeaderMenuComponent,
3641
+ ColumnSortStatePipe,
3642
+ AriaSortPipe,
3643
+ ], 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"] }]
3644
+ }], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { headerViewport: [{
2646
3645
  type: ViewChild,
2647
3646
  args: ['headerViewport', { static: true }]
2648
3647
  }], scrollLeft: [{
@@ -2653,6 +3652,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2653
3652
  type: Input
2654
3653
  }], api: [{
2655
3654
  type: Input
3655
+ }], filterRow: [{
3656
+ type: Input
3657
+ }], headerMenuClick: [{
3658
+ type: Output
3659
+ }], headerMenu: [{
3660
+ type: Input
2656
3661
  }] } });
2657
3662
 
2658
3663
  class CellEditablePipe {
@@ -2673,13 +3678,13 @@ class CellEditablePipe {
2673
3678
  return false;
2674
3679
  }
2675
3680
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2676
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, name: "cellEditable" }); }
3681
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, isStandalone: true, name: "cellEditable" }); }
2677
3682
  }
2678
3683
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, decorators: [{
2679
3684
  type: Pipe,
2680
3685
  args: [{
2681
3686
  name: 'cellEditable',
2682
- standalone: false
3687
+ standalone: true
2683
3688
  }]
2684
3689
  }] });
2685
3690
 
@@ -2693,29 +3698,18 @@ class OneGridCheckboxComponent {
2693
3698
  onToggleSelection(event) {
2694
3699
  const isChecked = event.target.checked;
2695
3700
  const { rowId, rowIndex, data } = this.node;
2696
- const selectionMode = this.selectionService.getSelectionMode();
2697
- if (selectionMode === 'single') {
2698
- this.api.deselectAll();
2699
- if (isChecked) {
2700
- this.node.setSelected(true);
2701
- }
2702
- this.api.toggleSelection(rowId, isChecked);
2703
- }
2704
- else {
2705
- // Multi-select mode
2706
- this.selectionService.toggleSelection(rowId, isChecked);
2707
- this.node.setSelected(isChecked);
2708
- }
2709
- this.node.cellStates = { ...this.node.cellStates };
2710
- this.eventService.dispatchEvent('selectionChanged', { rowIndex, selected: isChecked, data, node: this.node });
3701
+ // The mode rule (single clears the rest, multiple toggles) lives on the api
3702
+ // now, so pressing Space on a focused row and clicking this checkbox cannot
3703
+ // drift apart. It dispatches `selectionChanged` itself.
3704
+ this.api.toggleRowSelection(rowId, isChecked);
2711
3705
  this.onRowSelection.emit({ rowIndex, selected: isChecked, data, node: this.node });
2712
3706
  }
2713
3707
  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 }); }
2714
- 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: [""] }); }
3708
+ 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: [""] }); }
2715
3709
  }
2716
3710
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxComponent, decorators: [{
2717
3711
  type: Component,
2718
- 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>" }]
3712
+ 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>" }]
2719
3713
  }], ctorParameters: () => [{ type: OneGridSelectionService }, { type: OneGridEventService }], propDecorators: { node: [{
2720
3714
  type: Input
2721
3715
  }], allowedEdit: [{
@@ -2726,76 +3720,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2726
3720
  type: Output
2727
3721
  }] } });
2728
3722
 
2729
- class OneGridRowActionComponent {
2730
- constructor(cellService) {
2731
- this.cellService = cellService;
2732
- this.allowedEdit = false;
3723
+ class OneGridCellGroupRendererComponent {
3724
+ constructor() {
2733
3725
  this.masterDetail = false;
2734
- this.masterNode = null;
2735
- this.gridType = null;
2736
- this.context = null;
2737
- /**
2738
- * Bind Event to Parent Component
2739
- */
2740
- this.onRowGotoDetail = new EventEmitter();
2741
- this.onRowAddNew = new EventEmitter();
2742
- this.onRowView = new EventEmitter();
2743
- this.onRowLocation = new EventEmitter();
2744
- this.onRowDocument = new EventEmitter();
2745
- this.onRowCopy = new EventEmitter();
2746
- this.onRowDelete = new EventEmitter();
2747
- this.onRowGotoList = new EventEmitter();
2748
- this.onRowEdit = new EventEmitter();
2749
- this.onRowPrint = new EventEmitter();
2750
- this.onRowHistory = new EventEmitter();
2751
- this.onRowOption = new EventEmitter();
2752
- this.onRowLanguage = new EventEmitter();
2753
- this.onRowCellOption = new EventEmitter();
2754
- /**
2755
- * Accessible names for the action buttons.
2756
- *
2757
- * Every one of them is an icon with no text, and none carried a label — a
2758
- * screen reader announced "button" and nothing more, fourteen times per row.
2759
- * These are also the `title`, so the icons are identifiable by pointer too.
2760
- */
2761
- this.labels = {
2762
- gotoDetail: 'Open detail',
2763
- addNew: 'Add new row',
2764
- view: 'View row',
2765
- location: 'Show location',
2766
- document: 'Open document',
2767
- copy: 'Copy row',
2768
- delete: 'Delete row',
2769
- gotoList: 'Open list',
2770
- edit: 'Edit row',
2771
- print: 'Print row',
2772
- history: 'View history',
2773
- option: 'Row options',
2774
- language: 'Change language',
2775
- cellOption: 'Cell options',
2776
- };
3726
+ this.expansionChanged = new EventEmitter();
2777
3727
  }
2778
3728
  /**
2779
- * Whether this column's action buttons are hidden for this row.
2780
- *
2781
- * The templates read `column?.hideActionButton` directly, which is truthy for
2782
- * any function — so a predicate asking to hide the buttons showed them, and
2783
- * one asking to show them showed them too. Resolved here instead, with the
2784
- * same params every other row-scoped predicate gets.
3729
+ * Presentational only: report the click and let the body, which holds the
3730
+ * API, perform the toggle. The click is stopped from bubbling so it is not
3731
+ * also read as a cell click by the cell underneath.
2785
3732
  */
2786
- get hideActionButton() {
2787
- return this.cellService.hideActionButtonFn(this.column, this.node);
3733
+ invokeChangedClick(event) {
3734
+ event.stopPropagation();
3735
+ this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
3736
+ }
3737
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3738
+ 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: [""] }); }
3739
+ }
3740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
3741
+ type: Component,
3742
+ 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" }]
3743
+ }], propDecorators: { node: [{
3744
+ type: Input
3745
+ }], masterDetail: [{
3746
+ type: Input
3747
+ }], detailCellRendererParams: [{
3748
+ type: Input
3749
+ }], detailRendererComponent: [{
3750
+ type: Input
3751
+ }], expansionChanged: [{
3752
+ type: Output
3753
+ }] } });
3754
+
3755
+ class OneGridCellOptionMenuComponent {
3756
+ get menuLabel() {
3757
+ return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
3758
+ }
3759
+ constructor(elementRef) {
3760
+ this.elementRef = elementRef;
3761
+ this.column = null;
3762
+ this.node = null;
3763
+ this.options = [];
3764
+ this.cellOptionMenuSelect = new EventEmitter();
3765
+ this.cellOptionMenuClose = new EventEmitter();
3766
+ /**
3767
+ * Roving tabindex: one item is tabbable and the arrows move which. Standard
3768
+ * for a menu — a list of separately tabbable buttons makes a keyboard user
3769
+ * tab through every option to get past the menu.
3770
+ */
3771
+ this.activeIndex = 0;
2788
3772
  }
2789
3773
  /**
2790
- * Whether this column's action buttons are disabled for this row.
3774
+ * Move focus into the menu once it exists.
2791
3775
  *
2792
- * `disable` used to be resolved by `CellDisableResolverPipe`, which built its
2793
- * own params a different set from every other row-scoped predicate. It goes
2794
- * through the shared builder now, so `disable` sees exactly what `editable`
2795
- * and `hideActionButton` see.
3776
+ * Opening a menu and leaving focus on the trigger strands a keyboard user:
3777
+ * the menu is on screen and nothing they press reaches it.
2796
3778
  */
2797
- get actionDisabled() {
2798
- return this.cellService.disableFn(this.column, this.node) || !this.allowedEdit;
3779
+ ngAfterViewInit() {
3780
+ this.focusItem(0);
3781
+ }
3782
+ onItemKeydown(event, index, option) {
3783
+ switch (event.key) {
3784
+ case 'ArrowDown':
3785
+ event.preventDefault();
3786
+ this.focusItem((index + 1) % this.options.length);
3787
+ return;
3788
+ case 'ArrowUp':
3789
+ event.preventDefault();
3790
+ this.focusItem((index - 1 + this.options.length) % this.options.length);
3791
+ return;
3792
+ case 'Home':
3793
+ event.preventDefault();
3794
+ this.focusItem(0);
3795
+ return;
3796
+ case 'End':
3797
+ event.preventDefault();
3798
+ this.focusItem(this.options.length - 1);
3799
+ return;
3800
+ case 'Enter':
3801
+ case ' ':
3802
+ // A <button> already activates on both, but only once the default runs.
3803
+ // Handling it here keeps the menu's behaviour independent of that.
3804
+ event.preventDefault();
3805
+ this.onOptionSelected(option);
3806
+ return;
3807
+ default:
3808
+ return;
3809
+ }
3810
+ }
3811
+ focusItem(index) {
3812
+ if (!this.options?.length)
3813
+ return;
3814
+ this.activeIndex = index;
3815
+ this.items?.get(index)?.nativeElement?.focus();
3816
+ }
3817
+ // Click outside
3818
+ onDocumentClick(event) {
3819
+ const target = event.target;
3820
+ if (!this.elementRef.nativeElement.contains(target)) {
3821
+ this.close();
3822
+ }
3823
+ }
3824
+ // Escape key
3825
+ onEscape() {
3826
+ this.close();
3827
+ }
3828
+ // Scroll closes menu (prevents misalignment)
3829
+ onScroll() {
3830
+ this.close();
3831
+ }
3832
+ // Option selected
3833
+ onOptionSelected(option) {
3834
+ this.cellOptionMenuSelect.emit({
3835
+ column: this.column,
3836
+ node: this.node,
3837
+ option: option
3838
+ });
3839
+ this.close();
3840
+ }
3841
+ // Single close method
3842
+ close() {
3843
+ this.node.cellOptions[this.column.field] = false;
3844
+ this.cellOptionMenuClose.emit();
3845
+ }
3846
+ 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 }); }
3847
+ 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: [""] }); }
3848
+ }
3849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
3850
+ type: Component,
3851
+ 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" }]
3852
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
3853
+ type: Input
3854
+ }], node: [{
3855
+ type: Input
3856
+ }], options: [{
3857
+ type: Input
3858
+ }], cellOptionMenuSelect: [{
3859
+ type: Output
3860
+ }], cellOptionMenuClose: [{
3861
+ type: Output
3862
+ }], items: [{
3863
+ type: ViewChildren,
3864
+ args: ['item']
3865
+ }], onDocumentClick: [{
3866
+ type: HostListener,
3867
+ args: ['document:click', ['$event']]
3868
+ }], onEscape: [{
3869
+ type: HostListener,
3870
+ args: ['document:keydown.escape']
3871
+ }], onScroll: [{
3872
+ type: HostListener,
3873
+ args: ['document:scroll']
3874
+ }] } });
3875
+
3876
+ class OneGridRowActionComponent {
3877
+ constructor(cellService) {
3878
+ this.cellService = cellService;
3879
+ this.allowedEdit = false;
3880
+ this.masterDetail = false;
3881
+ this.masterNode = null;
3882
+ this.gridType = null;
3883
+ this.context = null;
3884
+ /**
3885
+ * Bind Event to Parent Component
3886
+ */
3887
+ this.onRowGotoDetail = new EventEmitter();
3888
+ this.onRowAddNew = new EventEmitter();
3889
+ this.onRowView = new EventEmitter();
3890
+ this.onRowLocation = new EventEmitter();
3891
+ this.onRowDocument = new EventEmitter();
3892
+ this.onRowCopy = new EventEmitter();
3893
+ this.onRowDelete = new EventEmitter();
3894
+ this.onRowGotoList = new EventEmitter();
3895
+ this.onRowEdit = new EventEmitter();
3896
+ this.onRowPrint = new EventEmitter();
3897
+ this.onRowHistory = new EventEmitter();
3898
+ this.onRowOption = new EventEmitter();
3899
+ this.onRowLanguage = new EventEmitter();
3900
+ this.onRowCellOption = new EventEmitter();
3901
+ /**
3902
+ * Accessible names for the action buttons.
3903
+ *
3904
+ * Every one of them is an icon with no text, and none carried a label — a
3905
+ * screen reader announced "button" and nothing more, fourteen times per row.
3906
+ * These are also the `title`, so the icons are identifiable by pointer too.
3907
+ */
3908
+ this.labels = {
3909
+ gotoDetail: 'Open detail',
3910
+ addNew: 'Add new row',
3911
+ view: 'View row',
3912
+ location: 'Show location',
3913
+ document: 'Open document',
3914
+ copy: 'Copy row',
3915
+ delete: 'Delete row',
3916
+ gotoList: 'Open list',
3917
+ edit: 'Edit row',
3918
+ print: 'Print row',
3919
+ history: 'View history',
3920
+ option: 'Row options',
3921
+ language: 'Change language',
3922
+ cellOption: 'Cell options',
3923
+ };
3924
+ }
3925
+ /**
3926
+ * Whether this column's action buttons are hidden for this row.
3927
+ *
3928
+ * The templates read `column?.hideActionButton` directly, which is truthy for
3929
+ * any function — so a predicate asking to hide the buttons showed them, and
3930
+ * one asking to show them showed them too. Resolved here instead, with the
3931
+ * same params every other row-scoped predicate gets.
3932
+ */
3933
+ get hideActionButton() {
3934
+ return this.cellService.hideActionButtonFn(this.column, this.node);
3935
+ }
3936
+ /**
3937
+ * Whether this column's action buttons are disabled for this row.
3938
+ *
3939
+ * `disable` used to be resolved by `CellDisableResolverPipe`, which built its
3940
+ * own params — a different set from every other row-scoped predicate. It goes
3941
+ * through the shared builder now, so `disable` sees exactly what `editable`
3942
+ * and `hideActionButton` see.
3943
+ */
3944
+ get actionDisabled() {
3945
+ return this.cellService.disableFn(this.column, this.node) || !this.allowedEdit;
2799
3946
  }
2800
3947
  ngOnInit() {
2801
3948
  this.detailActiveExcludeLevels = new Set(this.column.cellRendererParams?.detailActiveExcludeLevels ?? []);
@@ -2907,11 +4054,11 @@ class OneGridRowActionComponent {
2907
4054
  this.onRowCellOption.emit(data);
2908
4055
  }
2909
4056
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, deps: [{ token: OneGridCellService }], target: i0.ɵɵFactoryTarget.Component }); }
2910
- 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"] }] }); }
4057
+ 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"] }] }); }
2911
4058
  }
2912
4059
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, decorators: [{
2913
4060
  type: Component,
2914
- 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}" }]
4061
+ 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}" }]
2915
4062
  }], ctorParameters: () => [{ type: OneGridCellService }], propDecorators: { column: [{
2916
4063
  type: Input,
2917
4064
  args: [{ required: true }]
@@ -2961,51 +4108,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2961
4108
  type: Output
2962
4109
  }] } });
2963
4110
 
2964
- class OneGridCellGroupRendererComponent {
2965
- constructor() {
2966
- this.masterDetail = false;
2967
- this.expansionChanged = new EventEmitter();
4111
+ /** Why the handle is disabled, in words a user can act on. */
4112
+ const SUPPRESSED_TEXT = {
4113
+ sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
4114
+ filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
4115
+ treeData: 'Rows cannot be reordered in a tree grid.',
4116
+ };
4117
+ /**
4118
+ * The grab handle for one row.
4119
+ *
4120
+ * A real `<button>`, not a styled div: it has to be focusable and operable from
4121
+ * the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
4122
+ * to have a non-dragging alternative. Space picks the row up, the arrows move
4123
+ * it, space drops it and escape puts it back.
4124
+ *
4125
+ * When reordering is unavailable the handle is disabled and says why, rather
4126
+ * than disappearing — a control that vanishes when the user sorts looks like a
4127
+ * bug, and one that silently does nothing is worse.
4128
+ */
4129
+ class OneGridRowDragHandleComponent {
4130
+ constructor(dragService) {
4131
+ this.dragService = dragService;
4132
+ /** Non-null when the grid's order is not its own; see the model docs. */
4133
+ this.suppressedReason = null;
4134
+ this.dragEnd = new EventEmitter();
2968
4135
  }
2969
- /**
2970
- * Presentational only: report the click and let the body, which holds the
2971
- * API, perform the toggle. The click is stopped from bubbling so it is not
2972
- * also read as a cell click by the cell underneath.
2973
- */
2974
- invokeChangedClick(event) {
2975
- event.stopPropagation();
2976
- this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
4136
+ get disabled() {
4137
+ return this.suppressedReason !== null;
2977
4138
  }
2978
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2979
- 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: [""] }); }
2980
- }
2981
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
2982
- type: Component,
2983
- 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" }]
2984
- }], propDecorators: { node: [{
2985
- type: Input
2986
- }], masterDetail: [{
2987
- type: Input
2988
- }], detailCellRendererParams: [{
2989
- type: Input
2990
- }], detailRendererComponent: [{
2991
- type: Input
2992
- }], expansionChanged: [{
2993
- type: Output
2994
- }] } });
2995
-
2996
- class OneCellInputValidatorDirective {
2997
- constructor(el) {
2998
- this.el = el;
2999
- this.regex = '';
3000
- this.MAX_CODE_LENGTH = 36;
3001
- this.KHMER_CHARACTER_RANGE = /[\u1780-\u17FF]/;
4139
+ get label() {
4140
+ if (this.suppressedReason)
4141
+ return SUPPRESSED_TEXT[this.suppressedReason];
4142
+ return this.grabbed
4143
+ ? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
4144
+ : 'Drag to reorder row, or press space to move it with the arrow keys.';
3002
4145
  }
3003
- onKeyPress(event) {
3004
- let isValid = false;
3005
- const char = event.key;
3006
- const inputElement = this.el.nativeElement;
3007
- switch (this.validationType) {
3008
- case 'disabled':
4146
+ /** True while this specific row is the one being moved. */
4147
+ get grabbed() {
4148
+ return this.dragService.state?.rowId === this.node.rowId;
4149
+ }
4150
+ onPointerDown(event) {
4151
+ if (this.disabled)
4152
+ return;
4153
+ // The body starts a cell range selection on mousedown; the handle is not
4154
+ // part of that, and a press here must not also select.
4155
+ event.stopPropagation();
4156
+ this.dragService.startPointerDrag(event, this.node, { onEnd: (result) => this.emit(result) });
4157
+ }
4158
+ onKeyDown(event) {
4159
+ if (this.disabled)
4160
+ return;
4161
+ switch (event.key) {
4162
+ case ' ':
4163
+ case 'Enter':
4164
+ event.preventDefault();
4165
+ this.dragService.toggleKeyboardDrag(this.node, { onEnd: (result) => this.emit(result) });
4166
+ return;
4167
+ case 'ArrowUp':
4168
+ if (!this.grabbed)
4169
+ return;
4170
+ event.preventDefault();
4171
+ this.dragService.moveKeyboard(-1);
4172
+ return;
4173
+ case 'ArrowDown':
4174
+ if (!this.grabbed)
4175
+ return;
4176
+ event.preventDefault();
4177
+ this.dragService.moveKeyboard(1);
4178
+ return;
4179
+ case 'Escape':
4180
+ if (!this.grabbed)
4181
+ return;
4182
+ event.preventDefault();
4183
+ this.dragService.cancel();
4184
+ return;
4185
+ default:
4186
+ return;
4187
+ }
4188
+ }
4189
+ /**
4190
+ * A row that is picked up and dropped where it started produces no event.
4191
+ * Blur while grabbed would otherwise strand the row mid-move, so it cancels.
4192
+ */
4193
+ onBlur() {
4194
+ if (this.grabbed && this.dragService.state?.source === 'keyboard') {
4195
+ this.dragService.cancel();
4196
+ }
4197
+ }
4198
+ emit(result) {
4199
+ if (result)
4200
+ this.dragEnd.emit(result);
4201
+ }
4202
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, deps: [{ token: OneGridRowDragService }], target: i0.ɵɵFactoryTarget.Component }); }
4203
+ 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 }); }
4204
+ }
4205
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, decorators: [{
4206
+ type: Component,
4207
+ 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"] }]
4208
+ }], ctorParameters: () => [{ type: OneGridRowDragService }], propDecorators: { node: [{
4209
+ type: Input,
4210
+ args: [{ required: true }]
4211
+ }], suppressedReason: [{
4212
+ type: Input
4213
+ }], dragEnd: [{
4214
+ type: Output
4215
+ }] } });
4216
+
4217
+ class OneCellInputValidatorDirective {
4218
+ constructor(el) {
4219
+ this.el = el;
4220
+ this.regex = '';
4221
+ this.MAX_CODE_LENGTH = 36;
4222
+ this.KHMER_CHARACTER_RANGE = /[\u1780-\u17FF]/;
4223
+ }
4224
+ onKeyPress(event) {
4225
+ let isValid = false;
4226
+ const char = event.key;
4227
+ const inputElement = this.el.nativeElement;
4228
+ switch (this.validationType) {
4229
+ case 'disabled':
3009
4230
  event.preventDefault();
3010
4231
  return;
3011
4232
  case 'integer':
@@ -3042,12 +4263,12 @@ class OneCellInputValidatorDirective {
3042
4263
  }
3043
4264
  }
3044
4265
  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 }); }
3045
- 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 }); }
4266
+ 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 }); }
3046
4267
  }
3047
4268
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellInputValidatorDirective, decorators: [{
3048
4269
  type: Directive,
3049
4270
  args: [{
3050
- standalone: false,
4271
+ standalone: true,
3051
4272
  selector: '[oneCellInputValidator]'
3052
4273
  }]
3053
4274
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { validationType: [{
@@ -3064,408 +4285,146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3064
4285
 
3065
4286
  class OneCellRendererComponentDirective {
3066
4287
  constructor(vcr) {
3067
- this.vcr = vcr;
3068
- this.initialized = false;
3069
- }
3070
- ngOnInit() {
3071
- this.initialized = true;
3072
- this.createOrUpdateComponent(true);
3073
- }
3074
- ngOnChanges(changes) {
3075
- if (!this.initialized)
3076
- return;
3077
- const componentChanged = changes['cellRendererComponent'];
3078
- const paramsChanged = changes['params'];
3079
- if (componentChanged) {
3080
- this.destroyComponent();
3081
- this.createOrUpdateComponent(true);
3082
- }
3083
- else if (paramsChanged) {
3084
- this.updateComponentInputs();
3085
- }
3086
- }
3087
- createOrUpdateComponent(forceCreate = false) {
3088
- if (!this.cellRendererComponent)
3089
- return;
3090
- if (forceCreate || !this.compRef) {
3091
- this.vcr.clear();
3092
- this.compRef = this.vcr.createComponent(this.cellRendererComponent);
3093
- }
3094
- this.updateComponentInputs();
3095
- }
3096
- updateComponentInputs() {
3097
- if (!this.compRef?.instance)
3098
- return;
3099
- const instance = this.compRef.instance;
3100
- if (typeof instance.oneInit === 'function') {
3101
- // The body supplies `value` already, and it honours the column's
3102
- // valueGetter. Only derive it here when nothing did — this used to
3103
- // overwrite unconditionally with the raw field, so a column with both a
3104
- // valueGetter and a component renderer showed the underlying field
3105
- // instead of the value every other part of the grid displays.
3106
- const newParams = { ...this.params };
3107
- if (newParams.value === undefined) {
3108
- newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
3109
- }
3110
- instance.oneInit(newParams);
3111
- }
3112
- else {
3113
- Object.assign(instance, this.params);
3114
- }
3115
- // Optional: call change detector if needed
3116
- // if (this.compRef.changeDetectorRef) {
3117
- // this.compRef.changeDetectorRef.detectChanges();
3118
- // }
3119
- }
3120
- destroyComponent() {
3121
- if (this.compRef) {
3122
- this.compRef.destroy();
3123
- this.compRef = undefined;
3124
- }
3125
- }
3126
- ngOnDestroy() {
3127
- this.destroyComponent();
3128
- }
3129
- 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 }); }
3130
- 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 }); }
3131
- }
3132
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, decorators: [{
3133
- type: Directive,
3134
- args: [{
3135
- standalone: false,
3136
- selector: '[oneCellRendererComponent]'
3137
- }]
3138
- }], ctorParameters: () => [{ type: i0.ViewContainerRef }], propDecorators: { cellRendererComponent: [{
3139
- type: Input,
3140
- args: [{ required: true }]
3141
- }], params: [{
3142
- type: Input,
3143
- args: [{ required: true }]
3144
- }] } });
3145
-
3146
- /**
3147
- * Reveals a truncated cell in full while the pointer rests on it.
3148
- *
3149
- * Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
3150
- * used to be applied as inline styles here, one of which was
3151
- * `background: var(--ins-card-bg)` — a token from the application this library
3152
- * was extracted from, which no consumer defines. The expanded cell therefore
3153
- * had a transparent background and its text overlapped the next column. The
3154
- * inline rules also set `z-index` on a statically positioned element, where it
3155
- * does nothing.
3156
- *
3157
- * Overflow is measured on hover rather than after every view check. The old
3158
- * version measured on init and on every text change, and reading `scrollWidth`
3159
- * forces a synchronous layout — several hundred of them per render for a
3160
- * normal viewport, to answer a question only the hovered cell ever asks.
3161
- */
3162
- class OneCellTextExpandDirective {
3163
- static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
3164
- static { this.HOVER_DELAY_MS = 800; }
3165
- constructor(cell, renderer) {
3166
- this.cell = cell;
3167
- this.renderer = renderer;
3168
- }
3169
- onMouseEnter() {
3170
- const element = this.cell.nativeElement;
3171
- // Measured here, so the layout read happens once for the cell being
3172
- // hovered instead of for every cell on screen.
3173
- if (element.scrollWidth <= element.clientWidth)
3174
- return;
3175
- this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
3176
- }
3177
- onMouseLeave() {
3178
- this.collapse();
3179
- }
3180
- ngOnDestroy() {
3181
- this.collapse();
3182
- }
3183
- expand() {
3184
- this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
3185
- }
3186
- collapse() {
3187
- clearTimeout(this.timer);
3188
- this.timer = undefined;
3189
- this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
3190
- }
3191
- 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 }); }
3192
- 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 }); }
3193
- }
3194
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
3195
- type: Directive,
3196
- args: [{
3197
- standalone: false,
3198
- selector: '[oneCellTextExpand]'
3199
- }]
3200
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
3201
- type: HostListener,
3202
- args: ['mouseenter']
3203
- }], onMouseLeave: [{
3204
- type: HostListener,
3205
- args: ['mouseleave']
3206
- }] } });
3207
-
3208
- class OneGridCellOptionMenuComponent {
3209
- get menuLabel() {
3210
- return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
3211
- }
3212
- constructor(elementRef) {
3213
- this.elementRef = elementRef;
3214
- this.column = null;
3215
- this.node = null;
3216
- this.options = [];
3217
- this.cellOptionMenuSelect = new EventEmitter();
3218
- this.cellOptionMenuClose = new EventEmitter();
3219
- /**
3220
- * Roving tabindex: one item is tabbable and the arrows move which. Standard
3221
- * for a menu — a list of separately tabbable buttons makes a keyboard user
3222
- * tab through every option to get past the menu.
3223
- */
3224
- this.activeIndex = 0;
3225
- }
3226
- /**
3227
- * Move focus into the menu once it exists.
3228
- *
3229
- * Opening a menu and leaving focus on the trigger strands a keyboard user:
3230
- * the menu is on screen and nothing they press reaches it.
3231
- */
3232
- ngAfterViewInit() {
3233
- this.focusItem(0);
3234
- }
3235
- onItemKeydown(event, index, option) {
3236
- switch (event.key) {
3237
- case 'ArrowDown':
3238
- event.preventDefault();
3239
- this.focusItem((index + 1) % this.options.length);
3240
- return;
3241
- case 'ArrowUp':
3242
- event.preventDefault();
3243
- this.focusItem((index - 1 + this.options.length) % this.options.length);
3244
- return;
3245
- case 'Home':
3246
- event.preventDefault();
3247
- this.focusItem(0);
3248
- return;
3249
- case 'End':
3250
- event.preventDefault();
3251
- this.focusItem(this.options.length - 1);
3252
- return;
3253
- case 'Enter':
3254
- case ' ':
3255
- // A <button> already activates on both, but only once the default runs.
3256
- // Handling it here keeps the menu's behaviour independent of that.
3257
- event.preventDefault();
3258
- this.onOptionSelected(option);
3259
- return;
3260
- default:
3261
- return;
3262
- }
3263
- }
3264
- focusItem(index) {
3265
- if (!this.options?.length)
3266
- return;
3267
- this.activeIndex = index;
3268
- this.items?.get(index)?.nativeElement?.focus();
3269
- }
3270
- // Click outside
3271
- onDocumentClick(event) {
3272
- const target = event.target;
3273
- if (!this.elementRef.nativeElement.contains(target)) {
3274
- this.close();
3275
- }
3276
- }
3277
- // Escape key
3278
- onEscape() {
3279
- this.close();
3280
- }
3281
- // Scroll closes menu (prevents misalignment)
3282
- onScroll() {
3283
- this.close();
3284
- }
3285
- // Option selected
3286
- onOptionSelected(option) {
3287
- this.cellOptionMenuSelect.emit({
3288
- column: this.column,
3289
- node: this.node,
3290
- option: option
3291
- });
3292
- this.close();
3293
- }
3294
- // Single close method
3295
- close() {
3296
- this.node.cellOptions[this.column.field] = false;
3297
- this.cellOptionMenuClose.emit();
3298
- }
3299
- 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 }); }
3300
- 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: [""] }); }
3301
- }
3302
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
3303
- type: Component,
3304
- 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" }]
3305
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
3306
- type: Input
3307
- }], node: [{
3308
- type: Input
3309
- }], options: [{
3310
- type: Input
3311
- }], cellOptionMenuSelect: [{
3312
- type: Output
3313
- }], cellOptionMenuClose: [{
3314
- type: Output
3315
- }], items: [{
3316
- type: ViewChildren,
3317
- args: ['item']
3318
- }], onDocumentClick: [{
3319
- type: HostListener,
3320
- args: ['document:click', ['$event']]
3321
- }], onEscape: [{
3322
- type: HostListener,
3323
- args: ['document:keydown.escape']
3324
- }], onScroll: [{
3325
- type: HostListener,
3326
- args: ['document:scroll']
3327
- }] } });
3328
-
3329
- /** Why the handle is disabled, in words a user can act on. */
3330
- const SUPPRESSED_TEXT = {
3331
- sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
3332
- filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
3333
- treeData: 'Rows cannot be reordered in a tree grid.',
3334
- };
3335
- /**
3336
- * The grab handle for one row.
3337
- *
3338
- * A real `<button>`, not a styled div: it has to be focusable and operable from
3339
- * the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
3340
- * to have a non-dragging alternative. Space picks the row up, the arrows move
3341
- * it, space drops it and escape puts it back.
3342
- *
3343
- * When reordering is unavailable the handle is disabled and says why, rather
3344
- * than disappearing — a control that vanishes when the user sorts looks like a
3345
- * bug, and one that silently does nothing is worse.
3346
- */
3347
- class OneGridRowDragHandleComponent {
3348
- constructor(dragService) {
3349
- this.dragService = dragService;
3350
- /** Non-null when the grid's order is not its own; see the model docs. */
3351
- this.suppressedReason = null;
3352
- this.dragEnd = new EventEmitter();
3353
- }
3354
- get disabled() {
3355
- return this.suppressedReason !== null;
4288
+ this.vcr = vcr;
4289
+ this.initialized = false;
3356
4290
  }
3357
- get label() {
3358
- if (this.suppressedReason)
3359
- return SUPPRESSED_TEXT[this.suppressedReason];
3360
- return this.grabbed
3361
- ? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
3362
- : 'Drag to reorder row, or press space to move it with the arrow keys.';
4291
+ ngOnInit() {
4292
+ this.initialized = true;
4293
+ this.createOrUpdateComponent(true);
3363
4294
  }
3364
- /** True while this specific row is the one being moved. */
3365
- get grabbed() {
3366
- return this.dragService.state?.rowId === this.node.rowId;
4295
+ ngOnChanges(changes) {
4296
+ if (!this.initialized)
4297
+ return;
4298
+ const componentChanged = changes['cellRendererComponent'];
4299
+ const paramsChanged = changes['params'];
4300
+ if (componentChanged) {
4301
+ this.destroyComponent();
4302
+ this.createOrUpdateComponent(true);
4303
+ }
4304
+ else if (paramsChanged) {
4305
+ this.updateComponentInputs();
4306
+ }
3367
4307
  }
3368
- onPointerDown(event) {
3369
- if (this.disabled)
4308
+ createOrUpdateComponent(forceCreate = false) {
4309
+ if (!this.cellRendererComponent)
3370
4310
  return;
3371
- // The body starts a cell range selection on mousedown; the handle is not
3372
- // part of that, and a press here must not also select.
3373
- event.stopPropagation();
3374
- this.dragService.startPointerDrag(event, this.node, { onEnd: (result) => this.emit(result) });
4311
+ if (forceCreate || !this.compRef) {
4312
+ this.vcr.clear();
4313
+ this.compRef = this.vcr.createComponent(this.cellRendererComponent);
4314
+ }
4315
+ this.updateComponentInputs();
3375
4316
  }
3376
- onKeyDown(event) {
3377
- if (this.disabled)
4317
+ updateComponentInputs() {
4318
+ if (!this.compRef?.instance)
3378
4319
  return;
3379
- switch (event.key) {
3380
- case ' ':
3381
- case 'Enter':
3382
- event.preventDefault();
3383
- this.dragService.toggleKeyboardDrag(this.node, { onEnd: (result) => this.emit(result) });
3384
- return;
3385
- case 'ArrowUp':
3386
- if (!this.grabbed)
3387
- return;
3388
- event.preventDefault();
3389
- this.dragService.moveKeyboard(-1);
3390
- return;
3391
- case 'ArrowDown':
3392
- if (!this.grabbed)
3393
- return;
3394
- event.preventDefault();
3395
- this.dragService.moveKeyboard(1);
3396
- return;
3397
- case 'Escape':
3398
- if (!this.grabbed)
3399
- return;
3400
- event.preventDefault();
3401
- this.dragService.cancel();
3402
- return;
3403
- default:
3404
- return;
4320
+ const instance = this.compRef.instance;
4321
+ if (typeof instance.oneInit === 'function') {
4322
+ // The body supplies `value` already, and it honours the column's
4323
+ // valueGetter. Only derive it here when nothing did — this used to
4324
+ // overwrite unconditionally with the raw field, so a column with both a
4325
+ // valueGetter and a component renderer showed the underlying field
4326
+ // instead of the value every other part of the grid displays.
4327
+ const newParams = { ...this.params };
4328
+ if (newParams.value === undefined) {
4329
+ newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
4330
+ }
4331
+ instance.oneInit(newParams);
4332
+ }
4333
+ else {
4334
+ Object.assign(instance, this.params);
3405
4335
  }
4336
+ // Optional: call change detector if needed
4337
+ // if (this.compRef.changeDetectorRef) {
4338
+ // this.compRef.changeDetectorRef.detectChanges();
4339
+ // }
3406
4340
  }
3407
- /**
3408
- * A row that is picked up and dropped where it started produces no event.
3409
- * Blur while grabbed would otherwise strand the row mid-move, so it cancels.
3410
- */
3411
- onBlur() {
3412
- if (this.grabbed && this.dragService.state?.source === 'keyboard') {
3413
- this.dragService.cancel();
4341
+ destroyComponent() {
4342
+ if (this.compRef) {
4343
+ this.compRef.destroy();
4344
+ this.compRef = undefined;
3414
4345
  }
3415
4346
  }
3416
- emit(result) {
3417
- if (result)
3418
- this.dragEnd.emit(result);
4347
+ ngOnDestroy() {
4348
+ this.destroyComponent();
3419
4349
  }
3420
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, deps: [{ token: OneGridRowDragService }], target: i0.ɵɵFactoryTarget.Component }); }
3421
- 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 }); }
4350
+ 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 }); }
4351
+ 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 }); }
3422
4352
  }
3423
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, decorators: [{
3424
- type: Component,
3425
- 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"] }]
3426
- }], ctorParameters: () => [{ type: OneGridRowDragService }], propDecorators: { node: [{
4353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, decorators: [{
4354
+ type: Directive,
4355
+ args: [{
4356
+ standalone: true,
4357
+ selector: '[oneCellRendererComponent]'
4358
+ }]
4359
+ }], ctorParameters: () => [{ type: i0.ViewContainerRef }], propDecorators: { cellRendererComponent: [{
4360
+ type: Input,
4361
+ args: [{ required: true }]
4362
+ }], params: [{
3427
4363
  type: Input,
3428
4364
  args: [{ required: true }]
3429
- }], suppressedReason: [{
3430
- type: Input
3431
- }], dragEnd: [{
3432
- type: Output
3433
4365
  }] } });
3434
4366
 
3435
- class CellInvalidPipe {
3436
- transform(cellStates, value, column, node, allowedEdit, submitted) {
3437
- if (!column.validationRules?.length) {
3438
- return false;
3439
- }
3440
- return getCellError(column, node, value, node.data) !== null && !(node.editing[column.field]) && !(column?.cellRendererParams?.validateSelectedRowOnly && !node?.selected) && allowedEdit && submitted;
4367
+ /**
4368
+ * Reveals a truncated cell in full while the pointer rests on it.
4369
+ *
4370
+ * Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
4371
+ * used to be applied as inline styles here, one of which was
4372
+ * `background: var(--ins-card-bg)` a token from the application this library
4373
+ * was extracted from, which no consumer defines. The expanded cell therefore
4374
+ * had a transparent background and its text overlapped the next column. The
4375
+ * inline rules also set `z-index` on a statically positioned element, where it
4376
+ * does nothing.
4377
+ *
4378
+ * Overflow is measured on hover rather than after every view check. The old
4379
+ * version measured on init and on every text change, and reading `scrollWidth`
4380
+ * forces a synchronous layout — several hundred of them per render for a
4381
+ * normal viewport, to answer a question only the hovered cell ever asks.
4382
+ */
4383
+ class OneCellTextExpandDirective {
4384
+ static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
4385
+ static { this.HOVER_DELAY_MS = 800; }
4386
+ constructor(cell, renderer) {
4387
+ this.cell = cell;
4388
+ this.renderer = renderer;
3441
4389
  }
3442
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3443
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, name: "cellInvalid" }); }
3444
- }
3445
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, decorators: [{
3446
- type: Pipe,
3447
- args: [{
3448
- standalone: false,
3449
- name: 'cellInvalid'
3450
- }]
3451
- }] });
3452
-
3453
- class CellSelectedPipe {
3454
- transform(selectedCells, rowId, field) {
3455
- if (!selectedCells)
3456
- return false;
3457
- return selectedCells.has(getCellKey(rowId, field));
4390
+ onMouseEnter() {
4391
+ const element = this.cell.nativeElement;
4392
+ // Measured here, so the layout read happens once for the cell being
4393
+ // hovered instead of for every cell on screen.
4394
+ if (element.scrollWidth <= element.clientWidth)
4395
+ return;
4396
+ this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
3458
4397
  }
3459
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3460
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, name: "cellSelected" }); }
4398
+ onMouseLeave() {
4399
+ this.collapse();
4400
+ }
4401
+ ngOnDestroy() {
4402
+ this.collapse();
4403
+ }
4404
+ expand() {
4405
+ this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
4406
+ }
4407
+ collapse() {
4408
+ clearTimeout(this.timer);
4409
+ this.timer = undefined;
4410
+ this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
4411
+ }
4412
+ 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 }); }
4413
+ 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 }); }
3461
4414
  }
3462
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, decorators: [{
3463
- type: Pipe,
4415
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
4416
+ type: Directive,
3464
4417
  args: [{
3465
- standalone: false,
3466
- name: 'cellSelected'
4418
+ standalone: true,
4419
+ selector: '[oneCellTextExpand]'
3467
4420
  }]
3468
- }] });
4421
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
4422
+ type: HostListener,
4423
+ args: ['mouseenter']
4424
+ }], onMouseLeave: [{
4425
+ type: HostListener,
4426
+ args: ['mouseleave']
4427
+ }] } });
3469
4428
 
3470
4429
  class CellActionButtonEnablePipe {
3471
4430
  transform(params) {
@@ -3476,16 +4435,34 @@ class CellActionButtonEnablePipe {
3476
4435
  return statusValues.some(status => status === 1);
3477
4436
  }
3478
4437
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3479
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }); }
4438
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, isStandalone: true, name: "cellActionButtonEnable" }); }
3480
4439
  }
3481
4440
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, decorators: [{
3482
4441
  type: Pipe,
3483
4442
  args: [{
3484
- standalone: false,
4443
+ standalone: true,
3485
4444
  name: 'cellActionButtonEnable'
3486
4445
  }]
3487
4446
  }] });
3488
4447
 
4448
+ class CellInvalidPipe {
4449
+ transform(cellStates, value, column, node, allowedEdit, submitted) {
4450
+ if (!column.validationRules?.length) {
4451
+ return false;
4452
+ }
4453
+ return getCellError(column, node, value, node.data) !== null && !(node.editing[column.field]) && !(column?.cellRendererParams?.validateSelectedRowOnly && !node?.selected) && allowedEdit && submitted;
4454
+ }
4455
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4456
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, isStandalone: true, name: "cellInvalid" }); }
4457
+ }
4458
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, decorators: [{
4459
+ type: Pipe,
4460
+ args: [{
4461
+ standalone: true,
4462
+ name: 'cellInvalid'
4463
+ }]
4464
+ }] });
4465
+
3489
4466
  class CellKeyPipe {
3490
4467
  transform(rowId, field) {
3491
4468
  if (!rowId || !field) {
@@ -3494,12 +4471,12 @@ class CellKeyPipe {
3494
4471
  return getCellKey(rowId, field);
3495
4472
  }
3496
4473
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3497
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, name: "cellKey" }); }
4474
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, isStandalone: true, name: "cellKey" }); }
3498
4475
  }
3499
4476
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, decorators: [{
3500
4477
  type: Pipe,
3501
4478
  args: [{
3502
- standalone: false,
4479
+ standalone: true,
3503
4480
  name: 'cellKey'
3504
4481
  }]
3505
4482
  }] });
@@ -3510,16 +4487,33 @@ class CellLevelPaddingPipe {
3510
4487
  return level * factor;
3511
4488
  }
3512
4489
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3513
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, name: "cellLevelPadding" }); }
4490
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, isStandalone: true, name: "cellLevelPadding" }); }
3514
4491
  }
3515
4492
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, decorators: [{
3516
4493
  type: Pipe,
3517
4494
  args: [{
3518
- standalone: false,
4495
+ standalone: true,
3519
4496
  name: 'cellLevelPadding'
3520
4497
  }]
3521
4498
  }] });
3522
4499
 
4500
+ class CellSelectedPipe {
4501
+ transform(selectedCells, rowId, field) {
4502
+ if (!selectedCells)
4503
+ return false;
4504
+ return selectedCells.has(getCellKey(rowId, field));
4505
+ }
4506
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4507
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, isStandalone: true, name: "cellSelected" }); }
4508
+ }
4509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, decorators: [{
4510
+ type: Pipe,
4511
+ args: [{
4512
+ standalone: true,
4513
+ name: 'cellSelected'
4514
+ }]
4515
+ }] });
4516
+
3523
4517
  class CellStateRendererPipe {
3524
4518
  transform(cellStates, field) {
3525
4519
  const value = cellStates?.[field]?.value;
@@ -3532,12 +4526,12 @@ class CellStateRendererPipe {
3532
4526
  return value;
3533
4527
  }
3534
4528
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3535
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, name: "cellStateRenderer" }); }
4529
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, isStandalone: true, name: "cellStateRenderer" }); }
3536
4530
  }
3537
4531
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, decorators: [{
3538
4532
  type: Pipe,
3539
4533
  args: [{
3540
- standalone: false,
4534
+ standalone: true,
3541
4535
  name: 'cellStateRenderer'
3542
4536
  }]
3543
4537
  }] });
@@ -3554,6 +4548,12 @@ class OneGridBodyComponent {
3554
4548
  this.allowedEdit = true;
3555
4549
  this.enableGroupColumnDefs = false;
3556
4550
  this.submitted = false;
4551
+ /**
4552
+ * `'fullRow'`: a double-click opens every editable cell of the row, Enter or
4553
+ * focus leaving the row commits them together, Escape closes them all
4554
+ * without committing. Default: one cell at a time.
4555
+ */
4556
+ this.editType = null;
3557
4557
  this.context = null;
3558
4558
  /**
3559
4559
  * Pinned rows are bound only so the component re-renders when they change;
@@ -3654,6 +4654,22 @@ class OneGridBodyComponent {
3654
4654
  * MAX_BUFFER_PX keeps roughly twenty rows of overscan either side instead.
3655
4655
  */
3656
4656
  this.MAX_BUFFER_PX = 600;
4657
+ // ---------------------------------------------------------------------------
4658
+ // Cell navigation
4659
+ //
4660
+ // The WAI-ARIA grid pattern: the whole grid is one tab stop, and the arrows
4661
+ // move a focused cell within it. Before this the only tab stop was the scroll
4662
+ // viewport, so a keyboard user could scroll the rows and reach nothing in
4663
+ // them — and a screen reader had no cursor to read a cell from.
4664
+ //
4665
+ // Focus is real DOM focus on a roving tabindex rather than
4666
+ // `aria-activedescendant`, because the rows are virtualised: the element a
4667
+ // descendant id pointed at would keep being destroyed underneath it.
4668
+ // ---------------------------------------------------------------------------
4669
+ /** The cell holding the grid's tab stop. Null until something is focused. */
4670
+ this.activeCell = null;
4671
+ /** The row a shift-extended selection is measured from. */
4672
+ this.selectionAnchorRowId = null;
3657
4673
  /**
3658
4674
  * Where to paint the drop indicator, in pixels from the top of the body.
3659
4675
  *
@@ -3709,13 +4725,20 @@ class OneGridBodyComponent {
3709
4725
  this.columnDefs = [...payload.columns];
3710
4726
  this.cellRenderer = this.columnDefs.some(col => col.cellRendererComponent);
3711
4727
  this.rowWidth = this.columnDefs.reduce((sum, col) => sum + resolveColumnWidth(col), 0);
4728
+ // A column change can alter the width left for flex columns (a hide, a
4729
+ // resize, new defs). Re-flexing only dispatches when a width actually
4730
+ // changes, so this cannot ping-pong.
4731
+ this.applyFlexWidths();
3712
4732
  this.cdr.markForCheck();
3713
4733
  }));
3714
4734
  /**
3715
4735
  * Listen Row Data Changed Event
3716
4736
  */
3717
- this.subscriptions.add(this.eventService.addEventListener('rowDataChanged', ({ nodes }) => {
3718
- this.rowCountChanged.emit(nodes?.length ?? 0);
4737
+ this.subscriptions.add(this.eventService.addEventListener('rowDataChanged', ({ nodes: allNodes }) => {
4738
+ // The full post-filter count, reported before the page slice is taken,
4739
+ // so the host's paging totals see everything.
4740
+ this.rowCountChanged.emit(allNodes?.length ?? 0);
4741
+ const nodes = this.api.pageNodes(allNodes);
3719
4742
  setTimeout(() => {
3720
4743
  this.viewport?.checkViewportSize();
3721
4744
  this.setVirtualScroll(nodes?.length);
@@ -3755,7 +4778,7 @@ class OneGridBodyComponent {
3755
4778
  * Listen Sort Changed Event
3756
4779
  */
3757
4780
  this.subscriptions.add(this.eventService.addEventListener('sortChanged', ({ nodes }) => {
3758
- this.nodes = nodes;
4781
+ this.nodes = this.api.pageNodes(nodes);
3759
4782
  // Sorting takes ownership of the order away from the user, so the handles
3760
4783
  // have to disable in the same frame the rows re-sort.
3761
4784
  this.refreshRowDragSuppressed();
@@ -3769,7 +4792,7 @@ class OneGridBodyComponent {
3769
4792
  * for a move. A reorder is the same nodes in a different order.
3770
4793
  */
3771
4794
  this.subscriptions.add(this.eventService.addEventListener('rowOrderChanged', ({ nodes }) => {
3772
- this.nodes = [...nodes];
4795
+ this.nodes = this.api.pageNodes([...nodes]);
3773
4796
  this.cdr.markForCheck();
3774
4797
  }));
3775
4798
  /**
@@ -4087,13 +5110,28 @@ class OneGridBodyComponent {
4087
5110
  }
4088
5111
  let rowNode = this.api.getRowNodeById(node.rowId);
4089
5112
  if (rowNode) {
4090
- rowNode.editing[column.field] = true;
5113
+ if (this.editType === 'fullRow') {
5114
+ // The whole row edits together, so every cell that would accept its
5115
+ // own double-click opens now. The clicked one still takes focus below.
5116
+ for (const col of this.columnDefs) {
5117
+ if (!col.field)
5118
+ continue;
5119
+ if (this.cellEditablePipe.transform(rowNode.cellStates, col, rowNode, this.cellService.editableFn, this.cellService.updatableFn, this.allowedEdit)) {
5120
+ rowNode.editing[col.field] = true;
5121
+ }
5122
+ }
5123
+ }
5124
+ else {
5125
+ rowNode.editing[column.field] = true;
5126
+ }
4091
5127
  setTimeout(() => {
4092
- const input = document.querySelector(`[data-cell-id="${getCellKey(node.rowId, column.field)}"] input`);
5128
+ const input = document.querySelector(`[data-cell-id="${getCellKey(node.rowId, column.field)}"] input, [data-cell-id="${getCellKey(node.rowId, column.field)}"] select`);
4093
5129
  if (input) {
4094
5130
  input.focus();
4095
5131
  const value = input.value;
4096
- if (value && ["text"].includes(input.type)) {
5132
+ // A select reports type 'select-one', so only a text input gets its
5133
+ // caret placed.
5134
+ if (value && ["text"].includes(input.type) && 'setSelectionRange' in input) {
4097
5135
  input.setSelectionRange(value.length, value.length);
4098
5136
  }
4099
5137
  }
@@ -4103,19 +5141,72 @@ class OneGridBodyComponent {
4103
5141
  /**
4104
5142
  * Save Cell Edit Mode
4105
5143
  */
4106
- stopEdit(node, column, event) {
5144
+ stopEdit(node, column, event) {
5145
+ if (this.editType === 'fullRow') {
5146
+ this.stopRowEditFromEvent(node, event);
5147
+ return;
5148
+ }
5149
+ let rowNode = this.api.getRowNodeById(node.rowId);
5150
+ // The editing guard makes a second commit for one edit a no-op: a select
5151
+ // commits on (change), which closes the editor, and the (blur) that can
5152
+ // follow must not commit again.
5153
+ if (rowNode && rowNode.editing[column.field]) {
5154
+ event.preventDefault();
5155
+ event.stopPropagation();
5156
+ const value = event.target.value ?? '';
5157
+ const oldValue = rowNode.data[column.field];
5158
+ this.setCellValueByRowId(node.rowId, column, value);
5159
+ this.finishEdit(rowNode, column, oldValue);
5160
+ }
5161
+ }
5162
+ /**
5163
+ * Save a `select` editor's choice.
5164
+ *
5165
+ * Separate from `stopEdit` because the value is not the element's string:
5166
+ * the chosen index maps back into `selectOptions`, so the committed value
5167
+ * keeps whatever type the option declared.
5168
+ */
5169
+ stopEditSelect(node, column, event) {
5170
+ if (this.editType === 'fullRow') {
5171
+ // (change) is not a commit in full-row mode — the choice sits in the
5172
+ // select until the row commits and reads every editor at once. Only a
5173
+ // blur that actually leaves the row closes it.
5174
+ if (event.type === 'blur')
5175
+ this.stopRowEditFromEvent(node, event);
5176
+ return;
5177
+ }
4107
5178
  let rowNode = this.api.getRowNodeById(node.rowId);
4108
- if (rowNode) {
4109
- event.preventDefault();
5179
+ if (rowNode && rowNode.editing[column.field]) {
4110
5180
  event.stopPropagation();
4111
- const value = event.target.value ?? '';
4112
- this.setCellValueByRowId(node.rowId, column, value);
4113
- rowNode.cellStates = this.cellService.generateCellStates(rowNode);
4114
- rowNode.editing[column.field] = false;
4115
- this.eventService.dispatchEvent('cellStateChanged', {
4116
- data: rowNode.data,
5181
+ const options = column?.cellRendererParams?.selectOptions ?? [];
5182
+ const option = options[event.target.selectedIndex];
5183
+ if (!option)
5184
+ return;
5185
+ const oldValue = rowNode.data[column.field];
5186
+ rowNode.data[column.field] = option.value;
5187
+ this.finishEdit(rowNode, column, oldValue);
5188
+ }
5189
+ }
5190
+ /** Close the editor and announce the commit. Shared by both editor kinds. */
5191
+ finishEdit(rowNode, column, oldValue) {
5192
+ const newValue = rowNode.data[column.field];
5193
+ rowNode.cellStates = this.cellService.generateCellStates(rowNode);
5194
+ rowNode.editing[column.field] = false;
5195
+ this.eventService.dispatchEvent('cellStateChanged', {
5196
+ data: rowNode.data,
5197
+ node: rowNode,
5198
+ column: column
5199
+ });
5200
+ // Compared after normalisation ('' -> null, number coercion), so a blur
5201
+ // that leaves the value as it was does not announce an edit.
5202
+ if (newValue !== oldValue) {
5203
+ this.eventService.dispatchEvent('cellValueChanged', {
4117
5204
  node: rowNode,
4118
- column: column
5205
+ data: rowNode.data,
5206
+ column: column,
5207
+ field: column.field,
5208
+ oldValue,
5209
+ newValue,
4119
5210
  });
4120
5211
  }
4121
5212
  }
@@ -4125,9 +5216,63 @@ class OneGridBodyComponent {
4125
5216
  cancelEdit(node, column) {
4126
5217
  let rowNode = this.api.getRowNodeById(node.rowId);
4127
5218
  if (rowNode) {
5219
+ if (this.editType === 'fullRow') {
5220
+ // Escape abandons the whole row: every editor closes, nothing commits.
5221
+ Object.keys(rowNode.editing).forEach(field => rowNode.editing[field] = false);
5222
+ return;
5223
+ }
4128
5224
  rowNode.editing[column.field] = false;
4129
5225
  }
4130
5226
  }
5227
+ /**
5228
+ * Full-row commit, routed from any one editor's Enter or blur.
5229
+ *
5230
+ * A blur that lands on another editor in the same row is the user tabbing
5231
+ * between fields, not leaving the row — the editors stay open and their
5232
+ * typed values stay in the DOM until the row commits.
5233
+ */
5234
+ stopRowEditFromEvent(node, event) {
5235
+ if (event.type === 'blur') {
5236
+ const next = event.relatedTarget;
5237
+ if (next?.matches?.('input.one-cell-input, select.one-cell-input')
5238
+ && next.closest?.(`[data-cell-id^="${node.rowId}--"]`)) {
5239
+ return;
5240
+ }
5241
+ }
5242
+ event.preventDefault?.();
5243
+ event.stopPropagation?.();
5244
+ this.stopRowEdit(node);
5245
+ }
5246
+ /** Commit every open editor of one row, reading each from the DOM. */
5247
+ stopRowEdit(node) {
5248
+ const rowNode = this.api.getRowNodeById(node.rowId);
5249
+ if (!rowNode)
5250
+ return;
5251
+ for (const column of this.columnDefs) {
5252
+ const field = column.field;
5253
+ if (!field || !rowNode.editing[field])
5254
+ continue;
5255
+ const key = getCellKey(rowNode.rowId, field);
5256
+ const editor = document.querySelector(`[data-cell-id="${key}"] input, [data-cell-id="${key}"] select`);
5257
+ const oldValue = rowNode.data[field];
5258
+ if (editor?.tagName === 'SELECT') {
5259
+ const options = column.cellRendererParams?.selectOptions ?? [];
5260
+ const option = options[editor.selectedIndex];
5261
+ if (option)
5262
+ rowNode.data[field] = option.value;
5263
+ }
5264
+ else if (editor) {
5265
+ const raw = editor.value ?? '';
5266
+ rowNode.data[field] = raw === ''
5267
+ ? null
5268
+ : column.cellRendererParams?.inputType === 'number' ? Number(raw) : raw;
5269
+ }
5270
+ // finishEdit closes the editor, refreshes the cell state and raises
5271
+ // cellValueChanged when the value moved — one event per changed cell,
5272
+ // the same contract as single-cell editing.
5273
+ this.finishEdit(rowNode, column, oldValue);
5274
+ }
5275
+ }
4131
5276
  /**
4132
5277
  * Set Cell Value
4133
5278
  */
@@ -4173,6 +5318,24 @@ class OneGridBodyComponent {
4173
5318
  },
4174
5319
  });
4175
5320
  this.refreshRowDragSuppressed();
5321
+ // The CDK inserts its own content wrapper between the viewport and the
5322
+ // rows. It has no template of ours to annotate, and left generic it breaks
5323
+ // the `grid > row` ownership the roles depend on.
5324
+ const wrapper = element.querySelector('.cdk-virtual-scroll-content-wrapper');
5325
+ wrapper?.setAttribute('role', 'presentation');
5326
+ // Flex columns size against this viewport, now and whenever it resizes.
5327
+ // applyFlexWidths is a no-op without flex columns and dispatches only on a
5328
+ // real change, so grids that never use flex pay nothing here.
5329
+ this.applyFlexWidths();
5330
+ if (typeof ResizeObserver !== 'undefined') {
5331
+ this.flexResizeObserver = new ResizeObserver(() => this.applyFlexWidths());
5332
+ this.flexResizeObserver.observe(element);
5333
+ }
5334
+ }
5335
+ applyFlexWidths() {
5336
+ const width = this.viewport?.elementRef?.nativeElement?.clientWidth ?? 0;
5337
+ if (width > 0)
5338
+ this.api.applyFlexWidths(width);
4176
5339
  }
4177
5340
  /**
4178
5341
  * The params every per-cell callback receives.
@@ -4196,6 +5359,226 @@ class OneGridBodyComponent {
4196
5359
  rendererParams(node, column) {
4197
5360
  return { ...(column?.cellRendererParams ?? {}), ...this.cellParams(node, column) };
4198
5361
  }
5362
+ /**
5363
+ * `aria-rowindex` is 1-based over the whole grid, not over what is rendered.
5364
+ *
5365
+ * That is the point of it: the body virtualises, so the DOM holds a sliding
5366
+ * window of rows and the index is how a screen reader knows where the window
5367
+ * sits. Row 1 is the header, then any pinned-top rows, then the data.
5368
+ */
5369
+ dataRowAriaIndex(node) {
5370
+ return 2 + this.pinnedTopNodes.length + node.rowIndex;
5371
+ }
5372
+ /** Pinned rows bracket the data: top ones follow the header, bottom ones end the grid. */
5373
+ pinnedRowAriaIndex(node, side) {
5374
+ const index = (side === 'top' ? this.pinnedTopNodes : this.pinnedBottomNodes).indexOf(node);
5375
+ return side === 'top'
5376
+ ? 2 + index
5377
+ : 2 + this.pinnedTopNodes.length + this.nodes.length + index;
5378
+ }
5379
+ /**
5380
+ * Whether rows can be selected at all.
5381
+ *
5382
+ * `aria-selected="false"` on every row of a grid nobody can select announces
5383
+ * a selection affordance that does not exist, so the attribute is omitted
5384
+ * rather than set false.
5385
+ */
5386
+ get rowSelectionEnabled() {
5387
+ return !!this.api?.rowSelection;
5388
+ }
5389
+ isActiveCell(node, column) {
5390
+ if (this.activeCell) {
5391
+ return this.activeCell.rowId === node.rowId && this.activeCell.field === column.field;
5392
+ }
5393
+ // Nothing focused yet, so the first cell holds the tab stop. Without this
5394
+ // fallback a grid nobody has clicked has *no* tabbable cell, and a keyboard
5395
+ // user cannot get into it at all — the roving tabindex has to start
5396
+ // somewhere.
5397
+ return node === this.nodes[0] && column === this.navigableColumns()[0];
5398
+ }
5399
+ /**
5400
+ * Adopt whatever the user focused, however they got there — click, tab, or
5401
+ * the arrow keys below. Keeping this in one place is what stops the roving
5402
+ * tabindex drifting away from the element that actually has focus.
5403
+ */
5404
+ onCellFocus(node, column) {
5405
+ if (this.isActiveCell(node, column))
5406
+ return;
5407
+ this.activeCell = { rowId: node.rowId, field: column.field };
5408
+ this.cdr.markForCheck();
5409
+ }
5410
+ onGridKeydown(event) {
5411
+ // Undo/redo of committed edits, anywhere in the grid except inside an
5412
+ // open editor — a text input's Ctrl+Z is the browser's, and it operates
5413
+ // on the uncommitted text.
5414
+ if ((event.ctrlKey || event.metaKey) && ['z', 'y'].includes(event.key.toLowerCase())) {
5415
+ const tag = event.target?.tagName?.toLowerCase();
5416
+ if (tag === 'input' || tag === 'select' || tag === 'textarea')
5417
+ return;
5418
+ event.preventDefault();
5419
+ const redo = event.key.toLowerCase() === 'y' || event.shiftKey;
5420
+ const applied = redo ? this.api.redoCellEditing() : this.api.undoCellEditing();
5421
+ if (applied)
5422
+ this.cdr.markForCheck();
5423
+ return;
5424
+ }
5425
+ const columns = this.navigableColumns();
5426
+ if (!this.nodes.length || !columns.length)
5427
+ return;
5428
+ // Navigate only when a cell itself holds focus. Anything focusable *inside*
5429
+ // a cell owns its own keys — a text editor's caret, the row drag handle's
5430
+ // reorder keys, the option menu's arrows — and all of them bubble to here.
5431
+ // Checking the target rather than blocklisting tag names is what keeps this
5432
+ // correct as more controls are added to cells.
5433
+ const target = event.target;
5434
+ if (!target?.matches?.('[role="gridcell"]'))
5435
+ return;
5436
+ const current = this.activeCellPosition(columns);
5437
+ let row = current.row;
5438
+ let col = current.col;
5439
+ switch (event.key) {
5440
+ case 'ArrowDown':
5441
+ row += 1;
5442
+ break;
5443
+ case 'ArrowUp':
5444
+ row -= 1;
5445
+ break;
5446
+ case 'ArrowRight':
5447
+ col += 1;
5448
+ break;
5449
+ case 'ArrowLeft':
5450
+ col -= 1;
5451
+ break;
5452
+ // Selection. Space is the grid pattern's toggle, and it has to be caught
5453
+ // here or the browser scrolls the page with it.
5454
+ case ' ': {
5455
+ const node = this.nodes[current.row];
5456
+ if (node) {
5457
+ event.preventDefault();
5458
+ this.toggleRowSelection(node);
5459
+ }
5460
+ return;
5461
+ }
5462
+ case 'a':
5463
+ case 'A':
5464
+ if (!event.ctrlKey && !event.metaKey)
5465
+ return;
5466
+ event.preventDefault();
5467
+ this.api.selectAll();
5468
+ this.cdr.markForCheck();
5469
+ return;
5470
+ case 'Home':
5471
+ col = 0;
5472
+ if (event.ctrlKey || event.metaKey)
5473
+ row = 0;
5474
+ break;
5475
+ case 'End':
5476
+ col = columns.length - 1;
5477
+ if (event.ctrlKey || event.metaKey)
5478
+ row = this.nodes.length - 1;
5479
+ break;
5480
+ // A page is what the viewport can show, which is the only definition that
5481
+ // matches what the user sees move.
5482
+ case 'PageDown':
5483
+ row += this.rowsPerPage();
5484
+ break;
5485
+ case 'PageUp':
5486
+ row -= this.rowsPerPage();
5487
+ break;
5488
+ case 'Enter':
5489
+ case 'F2': {
5490
+ const node = this.nodes[current.row];
5491
+ const column = columns[current.col];
5492
+ if (node && column) {
5493
+ event.preventDefault();
5494
+ this.startEdit({ target: event.target }, node, column);
5495
+ }
5496
+ return;
5497
+ }
5498
+ default: return;
5499
+ }
5500
+ event.preventDefault();
5501
+ const targetRow = Math.min(Math.max(row, 0), this.nodes.length - 1);
5502
+ this.focusCell(targetRow, Math.min(Math.max(col, 0), columns.length - 1));
5503
+ // Shift with a vertical move extends the selection, anchored where the
5504
+ // extension began — the same gesture a file list uses.
5505
+ if (event.shiftKey && targetRow !== current.row) {
5506
+ this.extendSelectionTo(current.row, targetRow);
5507
+ }
5508
+ }
5509
+ toggleRowSelection(node) {
5510
+ if (!this.api.toggleRowSelection(node.rowId, !node.selected))
5511
+ return;
5512
+ // Where a subsequent shift-extension counts from.
5513
+ this.selectionAnchorRowId = node.rowId;
5514
+ this.cdr.markForCheck();
5515
+ }
5516
+ extendSelectionTo(fromRow, toRow) {
5517
+ const target = this.nodes[toRow];
5518
+ if (!target)
5519
+ return;
5520
+ // With nothing selected yet the row the extension started on becomes the
5521
+ // anchor, so shift+arrow selects both ends rather than only the far one.
5522
+ const anchor = this.selectionAnchorRowId ?? this.nodes[fromRow]?.rowId;
5523
+ if (!anchor)
5524
+ return;
5525
+ this.selectionAnchorRowId = anchor;
5526
+ this.api.selectRowRange(anchor, target.rowId);
5527
+ this.cdr.markForCheck();
5528
+ }
5529
+ /**
5530
+ * Columns a cell cursor can land on.
5531
+ *
5532
+ * A column with no field renders no cell state and is keyed by field, so it
5533
+ * cannot be addressed — an action-only column is a legitimate example.
5534
+ */
5535
+ navigableColumns() {
5536
+ return this.columnDefs.filter(column => !!column?.field);
5537
+ }
5538
+ activeCellPosition(columns) {
5539
+ if (!this.activeCell)
5540
+ return { row: 0, col: 0 };
5541
+ const row = this.nodes.findIndex(node => node.rowId === this.activeCell.rowId);
5542
+ const col = columns.findIndex(column => column.field === this.activeCell.field);
5543
+ return { row: row < 0 ? 0 : row, col: col < 0 ? 0 : col };
5544
+ }
5545
+ rowsPerPage() {
5546
+ const height = this.viewport?.elementRef?.nativeElement?.clientHeight ?? 0;
5547
+ return Math.max(1, Math.floor(height / this.itemSize));
5548
+ }
5549
+ /**
5550
+ * Move the cursor, scrolling the row into view first when it is outside the
5551
+ * rendered window.
5552
+ *
5553
+ * The scroll is the reason focusing is deferred: a virtualised row that is
5554
+ * not on screen has no element to focus until the viewport has rendered it.
5555
+ */
5556
+ focusCell(rowIndex, colIndex) {
5557
+ const node = this.nodes[rowIndex];
5558
+ const column = this.navigableColumns()[colIndex];
5559
+ if (!node || !column)
5560
+ return;
5561
+ this.activeCell = { rowId: node.rowId, field: column.field };
5562
+ this.cdr.markForCheck();
5563
+ const range = this.viewport?.getRenderedRange();
5564
+ const rendered = range && rowIndex >= range.start && rowIndex < range.end;
5565
+ if (rendered) {
5566
+ setTimeout(() => this.focusCellElement(node, column));
5567
+ return;
5568
+ }
5569
+ this.viewport?.scrollToIndex(rowIndex);
5570
+ // Scrolling is not enough: the CDK renders on its own schedule, so the row
5571
+ // has no element to focus until it has. Waiting for the rendered range to
5572
+ // actually contain the row is the only reliable signal — a fixed delay
5573
+ // races the viewport and silently drops focus when it loses.
5574
+ this.subscriptions.add(this.viewport?.renderedRangeStream
5575
+ .pipe(filter(next => rowIndex >= next.start && rowIndex < next.end), take(1))
5576
+ .subscribe(() => setTimeout(() => this.focusCellElement(node, column))));
5577
+ }
5578
+ focusCellElement(node, column) {
5579
+ const key = getCellKey(node.rowId, column.field);
5580
+ this.bodyRef?.nativeElement?.querySelector(`[data-cell-id="${key}"]`)?.focus();
5581
+ }
4199
5582
  /** Is this the row currently being moved? */
4200
5583
  isRowDragging(node) {
4201
5584
  return this.dragState?.rowId === node.rowId;
@@ -4221,14 +5604,35 @@ class OneGridBodyComponent {
4221
5604
  this.cdr.markForCheck();
4222
5605
  }
4223
5606
  ngOnDestroy() {
5607
+ this.flexResizeObserver?.disconnect();
4224
5608
  this.subscriptions.unsubscribe();
4225
5609
  }
4226
5610
  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 }); }
4227
- 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 }); }
5611
+ 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 }); }
4228
5612
  }
4229
5613
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, decorators: [{
4230
5614
  type: Component,
4231
- 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" }]
5615
+ args: [{ standalone: true, imports: [
5616
+ NgClass,
5617
+ NgStyle,
5618
+ NgTemplateOutlet,
5619
+ ScrollingModule,
5620
+ OneGridCheckboxComponent,
5621
+ OneGridCellGroupRendererComponent,
5622
+ OneGridCellOptionMenuComponent,
5623
+ OneGridRowActionComponent,
5624
+ OneGridRowDragHandleComponent,
5625
+ OneCellInputValidatorDirective,
5626
+ OneCellRendererComponentDirective,
5627
+ OneCellTextExpandDirective,
5628
+ CellActionButtonEnablePipe,
5629
+ CellEditablePipe,
5630
+ CellInvalidPipe,
5631
+ CellKeyPipe,
5632
+ CellLevelPaddingPipe,
5633
+ CellSelectedPipe,
5634
+ CellStateRendererPipe,
5635
+ ], 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" }]
4232
5636
  }], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridRowDragService }, { type: i0.ChangeDetectorRef }], propDecorators: { bodyRef: [{
4233
5637
  type: ViewChild,
4234
5638
  args: ['bodyRef', { static: true }]
@@ -4247,6 +5651,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4247
5651
  type: Input
4248
5652
  }], submitted: [{
4249
5653
  type: Input
5654
+ }], editType: [{
5655
+ type: Input
4250
5656
  }], context: [{
4251
5657
  type: Input
4252
5658
  }], getRowId: [{
@@ -4412,11 +5818,11 @@ class OneGridPaginationComponent {
4412
5818
  }
4413
5819
  }
4414
5820
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4415
- 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"] }] }); }
5821
+ 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"] }] }); }
4416
5822
  }
4417
5823
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, decorators: [{
4418
5824
  type: Component,
4419
- 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" }]
5825
+ 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" }]
4420
5826
  }], propDecorators: { paginate: [{
4421
5827
  type: Input
4422
5828
  }], goToFirst: [{
@@ -4432,16 +5838,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4432
5838
  }] } });
4433
5839
 
4434
5840
  class OneGridComponent {
4435
- constructor(api, eventService, elementRef) {
5841
+ /**
5842
+ * Recompute the bar's numbers from the filtered row count and the API's
5843
+ * paging state. Deferred a tick for the same reason the aria counts are:
5844
+ * the count arrives while the first change detection pass is still running.
5845
+ */
5846
+ refreshClientPagination(total) {
5847
+ const { pageSize, currentPage } = this.api.paginationState;
5848
+ const lastPage = Math.max(Math.ceil(total / pageSize), 1);
5849
+ const page = Math.min(currentPage, lastPage);
5850
+ setTimeout(() => {
5851
+ this.internalPaginate = {
5852
+ total,
5853
+ currentPage: page,
5854
+ lastPage,
5855
+ perPage: pageSize,
5856
+ firstRowIndex: total === 0 ? 0 : (page - 1) * pageSize + 1,
5857
+ lastRowIndex: Math.min(page * pageSize, total),
5858
+ pageSizeOptions: this.paginate?.pageSizeOptions,
5859
+ };
5860
+ this.cdr.markForCheck();
5861
+ });
5862
+ }
5863
+ constructor(api, eventService, elementRef, cdr) {
4436
5864
  this.api = api;
4437
5865
  this.eventService = eventService;
4438
5866
  this.elementRef = elementRef;
5867
+ this.cdr = cdr;
4439
5868
  this.rowNumber = false;
4440
5869
  this.allowedEdit = true;
4441
5870
  this.submitted = false;
4442
5871
  this.context = null;
4443
5872
  this.rowData = [];
4444
5873
  this.rowSelection = null;
5874
+ /**
5875
+ * The grid's accessible name.
5876
+ *
5877
+ * A `role="grid"` with no name is announced as an unlabelled grid, which
5878
+ * tells a screen-reader user nothing about what they have landed in. Set it
5879
+ * to whatever the grid is showing — "Employees", "Order lines".
5880
+ */
5881
+ this.gridLabel = 'Data grid';
4445
5882
  this.enableGroupColumnDefs = false;
4446
5883
  this.groupColumnDefs = [];
4447
5884
  this.defaultColumnDef = {};
@@ -4476,6 +5913,41 @@ class OneGridComponent {
4476
5913
  * pointer is in flight — and only when the position actually changed.
4477
5914
  */
4478
5915
  this.rowDragEnd = new EventEmitter();
5916
+ /**
5917
+ * The header's ⋮ button was pressed for this column.
5918
+ *
5919
+ * The grid has no column menu of its own — this reports the intent so the
5920
+ * host can show whatever a column menu means in its app. Set
5921
+ * `columnDef.suppressHeaderMenuButton` to hide the button entirely.
5922
+ */
5923
+ this.headerMenuClick = new EventEmitter();
5924
+ /**
5925
+ * Render the grid's own column menu behind the header ⋮ button: sort, pin,
5926
+ * hide. Opt-in — a host that built its menu on `(headerMenuClick)` keeps
5927
+ * exactly what it had, and the intent still emits either way.
5928
+ */
5929
+ this.headerMenu = false;
5930
+ /**
5931
+ * Render a filter input under every column whose `filterable` resolves
5932
+ * true. The inputs feed `api.setFilterModel` with contains-conditions,
5933
+ * debounced; filters on fields without an input are left untouched.
5934
+ */
5935
+ this.filterRow = false;
5936
+ /**
5937
+ * Record committed cell edits so `api.undoCellEditing` /
5938
+ * `api.redoCellEditing` (and Ctrl+Z / Ctrl+Y in the grid) can walk them.
5939
+ * Programmatic `setDataValue` writes are not recorded.
5940
+ */
5941
+ this.undoRedoCellEditing = false;
5942
+ /** How many edits the undo stack keeps. */
5943
+ this.undoRedoCellEditingLimit = 10;
5944
+ /**
5945
+ * `'fullRow'` opens every editable cell of the row on a double-click and
5946
+ * commits them together — Enter anywhere in the row, or focus leaving the
5947
+ * row. Escape closes the whole row without committing. Default: cells edit
5948
+ * one at a time.
5949
+ */
5950
+ this.editType = null;
4479
5951
  this.onGridReady = new EventEmitter();
4480
5952
  this.onRowGotoDetail = new EventEmitter();
4481
5953
  this.onRowAddNew = new EventEmitter();
@@ -4495,6 +5967,22 @@ class OneGridComponent {
4495
5967
  this.rowCountChanged = new EventEmitter();
4496
5968
  this.selectionChanged = new EventEmitter();
4497
5969
  this.expansionChanged = new EventEmitter();
5970
+ /**
5971
+ * The sort model changed — a header click or `api.onSortChanged`. Carries
5972
+ * the re-sorted nodes and the model that produced them; `api.sortModel`
5973
+ * stays readable between emissions.
5974
+ */
5975
+ this.sortChanged = new EventEmitter();
5976
+ /**
5977
+ * An inline edit was committed with a value different from the one it
5978
+ * started with. Programmatic `node.setDataValue` does not emit here — the
5979
+ * caller already holds both values.
5980
+ */
5981
+ this.cellValueChanged = new EventEmitter();
5982
+ /** A header drag finished somewhere other than where it started. */
5983
+ this.columnMoved = new EventEmitter();
5984
+ /** A resize gesture finished — one event per gesture, not per pixel. */
5985
+ this.columnResized = new EventEmitter();
4498
5986
  // For context menu option
4499
5987
  this.cellOptionSelect = new EventEmitter();
4500
5988
  this.onRowCellOption = new EventEmitter();
@@ -4502,17 +5990,42 @@ class OneGridComponent {
4502
5990
  * Bind One Grid Pagination Feature
4503
5991
  */
4504
5992
  this.pagination = false;
5993
+ /**
5994
+ * Let the grid page its own rows. The bar renders, the grid slices the
5995
+ * filtered rows to the current page, and the navigation buttons work
5996
+ * without the host handling anything — `[paginate]` is ignored in this
5997
+ * mode (the grid owns those numbers) except for `pageSizeOptions`. The
5998
+ * navigation outputs still emit, as notifications rather than intents.
5999
+ * Selection, filtering and sorting keep operating on the whole dataset.
6000
+ */
6001
+ this.clientSidePagination = false;
6002
+ /** Rows per page for `clientSidePagination`. */
6003
+ this.pageSize = 25;
4505
6004
  this.goToFirst = new EventEmitter();
4506
6005
  this.goToPrevious = new EventEmitter();
4507
6006
  this.goToNext = new EventEmitter();
4508
6007
  this.goToLast = new EventEmitter();
4509
6008
  this.pageSizeChange = new EventEmitter();
6009
+ /** The paging numbers the bar shows when the grid pages its own rows. */
6010
+ this.internalPaginate = {
6011
+ total: 0, currentPage: 1, lastPage: 1, perPage: 25, firstRowIndex: 0, lastRowIndex: 0,
6012
+ };
4510
6013
  /**
4511
6014
  * One Grid Properties
4512
6015
  */
4513
6016
  this.bodyScrollLeft = 0;
4514
6017
  this._oneRowNumberColumnDef = null;
4515
6018
  this.subscriptions = new Subscription();
6019
+ /**
6020
+ * Totals for `aria-rowcount` / `aria-colcount`.
6021
+ *
6022
+ * Held as fields rather than computed in the template: they are read on every
6023
+ * change detection pass and only actually change when the data or the columns
6024
+ * do. `-1` is the ARIA value for "unknown", which is the honest answer before
6025
+ * the first row arrives.
6026
+ */
6027
+ this.ariaRowCount = -1;
6028
+ this.ariaColCount = -1;
4516
6029
  }
4517
6030
  ngOnChanges(changes) {
4518
6031
  if (changes['masterNode'] && changes['masterNode'].isFirstChange) {
@@ -4556,6 +6069,35 @@ class OneGridComponent {
4556
6069
  maxWidth: 200
4557
6070
  };
4558
6071
  }
6072
+ /**
6073
+ * Row grouping: column defs declaring `rowGroup` turn the grid into a
6074
+ * tree over synthetic group rows. Configured before `api.init` because
6075
+ * everything downstream — the treeData flag the API captures, the group
6076
+ * column the tree block below prepends — has to see the tree shape.
6077
+ */
6078
+ const rowGroupColumnFields = this.treeData || this.masterDetail
6079
+ ? []
6080
+ : rowGroupFields(this.columnDefs);
6081
+ if (rowGroupColumnFields.length) {
6082
+ this.treeData = true;
6083
+ this.treeDataChildrenField = ONE_GROUP_CHILDREN_FIELD;
6084
+ // The group column: host-tuned through autoGroupColumnDef, but its
6085
+ // field is always the synthetic label — that is where a group row
6086
+ // carries its display value.
6087
+ this.autoGroupColumnDef = {
6088
+ headerName: 'Group',
6089
+ minWidth: 200,
6090
+ ...this.autoGroupColumnDef,
6091
+ field: ONE_GROUP_VALUE_FIELD,
6092
+ // The marker the tree machinery keys on: the expander renders on this
6093
+ // column and group rows print their label here.
6094
+ autoGroupField: true,
6095
+ };
6096
+ // A grouped-by column's value is on the group row; showing the flat
6097
+ // column too repeats it down every leaf. Hidden, not removed — the
6098
+ // host can bring it back with api.setColumnVisible.
6099
+ this.columnDefs = this.columnDefs.map(column => column.rowGroup ? { ...column, hide: true } : column);
6100
+ }
4559
6101
  /**
4560
6102
  * Initialize One Grid API
4561
6103
  */
@@ -4571,6 +6113,15 @@ class OneGridComponent {
4571
6113
  detailRendererComponent: this.detailRendererComponent,
4572
6114
  treeData: this.treeData,
4573
6115
  });
6116
+ if (rowGroupColumnFields.length) {
6117
+ this.api.setRowGrouping(rowGroupColumnFields, aggColumns(this.columnDefs));
6118
+ }
6119
+ if (this.undoRedoCellEditing) {
6120
+ this.api.enableUndoRedoCellEditing(this.undoRedoCellEditingLimit);
6121
+ }
6122
+ if (this.clientSidePagination) {
6123
+ this.api.setClientSidePagination(true, this.paginate?.perPage ?? this.pageSize);
6124
+ }
4574
6125
  /**
4575
6126
  * Setup Master Detail Configuration
4576
6127
  */
@@ -4593,9 +6144,28 @@ class OneGridComponent {
4593
6144
  setTimeout(() => this.api.setColumnDefs(columnDefs));
4594
6145
  }
4595
6146
  // Listen Selection Changed Event
6147
+ // Columns can be replaced at runtime through `api.setColumnDefs`, and
6148
+ // `aria-colcount` has to follow.
6149
+ this.subscriptions.add(this.eventService.addEventListener('columnChanged', () => {
6150
+ this.ariaColCount = this.api?.getColumnState()?.columns?.length ?? -1;
6151
+ }));
4596
6152
  this.subscriptions.add(this.eventService.addEventListener('selectionChanged', (payload) => {
4597
6153
  this.selectionChanged.emit(payload);
4598
6154
  }));
6155
+ // The internal events a host can act on, surfaced as outputs. Same
6156
+ // forwarding pattern as selectionChanged above.
6157
+ this.subscriptions.add(this.eventService.addEventListener('sortChanged', (payload) => {
6158
+ this.sortChanged.emit(payload);
6159
+ }));
6160
+ this.subscriptions.add(this.eventService.addEventListener('cellValueChanged', (payload) => {
6161
+ this.cellValueChanged.emit(payload);
6162
+ }));
6163
+ this.subscriptions.add(this.eventService.addEventListener('columnMoved', (payload) => {
6164
+ this.columnMoved.emit(payload);
6165
+ }));
6166
+ this.subscriptions.add(this.eventService.addEventListener('columnResized', (payload) => {
6167
+ this.columnResized.emit(payload);
6168
+ }));
4599
6169
  }
4600
6170
  ngAfterViewInit() {
4601
6171
  // The header follows the body through (bodyScroll) -> [scrollLeft]. A second
@@ -4687,8 +6257,34 @@ class OneGridComponent {
4687
6257
  this.onShiftDoubleClick.emit(event);
4688
6258
  }
4689
6259
  invokeRowCountChanged(length) {
6260
+ this.refreshAriaCounts(length);
6261
+ if (this.clientSidePagination)
6262
+ this.refreshClientPagination(length);
4690
6263
  this.rowCountChanged.emit(length);
4691
6264
  }
6265
+ /**
6266
+ * Recompute the totals a screen reader is told about.
6267
+ *
6268
+ * `aria-rowcount` counts every row the grid represents, not the handful the
6269
+ * virtual scroller currently has in the DOM — the header row, the pinned rows
6270
+ * either side, and all the data rows.
6271
+ */
6272
+ refreshAriaCounts(dataRows) {
6273
+ const pinned = (this.pinnedTopRowData?.length ?? 0) + (this.pinnedBottomRowData?.length ?? 0);
6274
+ const rows = 1 + pinned + dataRows;
6275
+ const cols = this.api?.getColumnState()?.columns?.length ?? -1;
6276
+ if (rows === this.ariaRowCount && cols === this.ariaColCount)
6277
+ return;
6278
+ // Deferred a tick. The body emits its row count while the first change
6279
+ // detection pass is still running, after this component's own attributes
6280
+ // were checked — assigning here directly is the textbook NG0100. The body
6281
+ // defers its node list for the same reason.
6282
+ setTimeout(() => {
6283
+ this.ariaRowCount = rows;
6284
+ this.ariaColCount = cols;
6285
+ this.cdr.markForCheck();
6286
+ });
6287
+ }
4692
6288
  cellOptionMenuSelected(event) {
4693
6289
  this.cellOptionSelect.emit(event);
4694
6290
  }
@@ -4696,18 +6292,28 @@ class OneGridComponent {
4696
6292
  * Bing Pagination Events to Parent Component
4697
6293
  */
4698
6294
  invokeGoToFirst() {
6295
+ if (this.clientSidePagination)
6296
+ this.api.setCurrentPage(1);
4699
6297
  this.goToFirst.emit();
4700
6298
  }
4701
6299
  invokeGoToPrevious() {
6300
+ if (this.clientSidePagination)
6301
+ this.api.setCurrentPage(this.api.paginationState.currentPage - 1);
4702
6302
  this.goToPrevious.emit();
4703
6303
  }
4704
6304
  invokeGoToNext() {
6305
+ if (this.clientSidePagination)
6306
+ this.api.setCurrentPage(this.api.paginationState.currentPage + 1);
4705
6307
  this.goToNext.emit();
4706
6308
  }
4707
6309
  invokeGoToLast() {
6310
+ if (this.clientSidePagination)
6311
+ this.api.setCurrentPage(this.internalPaginate.lastPage ?? 1);
4708
6312
  this.goToLast.emit();
4709
6313
  }
4710
6314
  invokePageSizeChange(pageSize) {
6315
+ if (this.clientSidePagination)
6316
+ this.api.setPageSize(pageSize);
4711
6317
  this.pageSizeChange.emit(pageSize);
4712
6318
  }
4713
6319
  invokeRowDragEnd(event) {
@@ -4724,8 +6330,8 @@ class OneGridComponent {
4724
6330
  this.api.destroy();
4725
6331
  this.subscriptions.unsubscribe();
4726
6332
  }
4727
- 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 }); }
4728
- 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: [
6333
+ 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 }); }
6334
+ 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: [
4729
6335
  OneGridEventService,
4730
6336
  OneGridApi,
4731
6337
  OgColumnService,
@@ -4734,11 +6340,16 @@ class OneGridComponent {
4734
6340
  OneGridRowService,
4735
6341
  OneGridCellService,
4736
6342
  OneGridRowDragService
4737
- ], 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"] }] }); }
6343
+ ], 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"] }] }); }
4738
6344
  }
4739
6345
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, decorators: [{
4740
6346
  type: Component,
4741
- args: [{ standalone: false, selector: 'one-grid', providers: [
6347
+ args: [{ standalone: true, imports: [
6348
+ NgTemplateOutlet,
6349
+ OneGridHeaderComponent,
6350
+ OneGridBodyComponent,
6351
+ OneGridPaginationComponent,
6352
+ ], selector: 'one-grid', providers: [
4742
6353
  OneGridEventService,
4743
6354
  OneGridApi,
4744
6355
  OgColumnService,
@@ -4747,8 +6358,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4747
6358
  OneGridRowService,
4748
6359
  OneGridCellService,
4749
6360
  OneGridRowDragService
4750
- ], 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>" }]
4751
- }], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }], propDecorators: { ogRef: [{
6361
+ ], 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>" }]
6362
+ }], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { ogRef: [{
4752
6363
  type: ViewChild,
4753
6364
  args: ['ogRef', { static: true }]
4754
6365
  }], rowNumber: [{
@@ -4763,6 +6374,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4763
6374
  type: Input
4764
6375
  }], rowSelection: [{
4765
6376
  type: Input
6377
+ }], gridLabel: [{
6378
+ type: Input
4766
6379
  }], enableGroupColumnDefs: [{
4767
6380
  type: Input
4768
6381
  }], groupColumnDefs: [{
@@ -4795,6 +6408,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4795
6408
  type: Input
4796
6409
  }], rowDragEnd: [{
4797
6410
  type: Output
6411
+ }], headerMenuClick: [{
6412
+ type: Output
6413
+ }], headerMenu: [{
6414
+ type: Input
6415
+ }], filterRow: [{
6416
+ type: Input
6417
+ }], undoRedoCellEditing: [{
6418
+ type: Input
6419
+ }], undoRedoCellEditingLimit: [{
6420
+ type: Input
6421
+ }], editType: [{
6422
+ type: Input
4798
6423
  }], onGridReady: [{
4799
6424
  type: Output
4800
6425
  }], onRowGotoDetail: [{
@@ -4833,6 +6458,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4833
6458
  type: Output
4834
6459
  }], expansionChanged: [{
4835
6460
  type: Output
6461
+ }], sortChanged: [{
6462
+ type: Output
6463
+ }], cellValueChanged: [{
6464
+ type: Output
6465
+ }], columnMoved: [{
6466
+ type: Output
6467
+ }], columnResized: [{
6468
+ type: Output
4836
6469
  }], cellOptionSelect: [{
4837
6470
  type: Output
4838
6471
  }], onRowCellOption: [{
@@ -4841,6 +6474,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4841
6474
  type: Input
4842
6475
  }], paginate: [{
4843
6476
  type: Input
6477
+ }], clientSidePagination: [{
6478
+ type: Input
6479
+ }], pageSize: [{
6480
+ type: Input
4844
6481
  }], goToFirst: [{
4845
6482
  type: Output
4846
6483
  }], goToPrevious: [{
@@ -4878,11 +6515,11 @@ class OneGridStatusComponent {
4878
6515
  return labels[type][status === 1 ? 1 : 0];
4879
6516
  }
4880
6517
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4881
- 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"] }] }); }
6518
+ 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"] }] }); }
4882
6519
  }
4883
6520
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridStatusComponent, decorators: [{
4884
6521
  type: Component,
4885
- 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" }]
6522
+ 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" }]
4886
6523
  }] });
4887
6524
 
4888
6525
  class OneGridCellCurrencyComponent {
@@ -4957,172 +6594,50 @@ class OneGridCellCurrencyComponent {
4957
6594
  this.unsubscribe?.();
4958
6595
  }
4959
6596
  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 }); }
4960
- 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" }] }); }
6597
+ 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"] }] }); }
4961
6598
  }
4962
6599
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, decorators: [{
4963
6600
  type: Component,
4964
- 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"] }]
6601
+ 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"] }]
4965
6602
  }], ctorParameters: () => [{ type: OneGridCellService }, { type: OneGridEventService }], propDecorators: { allowedEdit: [{
4966
6603
  type: Input
4967
6604
  }] } });
4968
6605
 
4969
- class CellActionButtonVisiblePipe {
4970
- transform(column, node) {
4971
- return (node?.leaf && !(node?.group && node?.leafGroup) && !column?.autoGroupField) || (!node?.leaf && (node?.group || node?.leafGroup) && column?.autoGroupField && column?.editableLevels?.includes(node.level));
4972
- }
4973
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonVisiblePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4974
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonVisiblePipe, name: "cellActionButtonVisible" }); }
4975
- }
4976
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonVisiblePipe, decorators: [{
4977
- type: Pipe,
4978
- args: [{
4979
- standalone: false,
4980
- name: 'cellActionButtonVisible'
4981
- }]
4982
- }] });
4983
-
4984
- class CellNumberValuePipe {
4985
- transform(value) {
4986
- if (value === null || value === undefined)
4987
- return undefined;
4988
- return Number(value);
4989
- }
4990
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
4991
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, name: "cellNumberValue" }); }
4992
- }
4993
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, decorators: [{
4994
- type: Pipe,
4995
- args: [{
4996
- standalone: false,
4997
- name: 'cellNumberValue'
4998
- }]
4999
- }] });
5000
-
5001
- class CellFieldPipe {
5002
- transform(rowIndex, field) {
5003
- if (rowIndex === null || rowIndex === undefined || !field)
5004
- return '';
5005
- return getCellField(rowIndex, field);
5006
- }
5007
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5008
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, name: "cellField" }); }
5009
- }
5010
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, decorators: [{
5011
- type: Pipe,
5012
- args: [{
5013
- standalone: false,
5014
- name: 'cellField'
5015
- }]
5016
- }] });
5017
-
5018
- class CellStateGroupRendererPipe {
5019
- constructor() {
5020
- this._cellFieldValue = new CellFieldValuePipe();
5021
- }
5022
- transform(value, data, column) {
5023
- return value ? this._cellFieldValue.transform(data, column.field) : column?.cellRendererParams?.placeholder ?? '';
5024
- }
5025
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5026
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, name: "cellStateGroupRenderer" }); }
5027
- }
5028
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, decorators: [{
5029
- type: Pipe,
5030
- args: [{
5031
- standalone: false,
5032
- name: 'cellStateGroupRenderer'
5033
- }]
5034
- }] });
5035
-
6606
+ /**
6607
+ * Compatibility shim.
6608
+ *
6609
+ * Every component, directive and pipe in the library is standalone, so this
6610
+ * module declares nothing it exists so `imports: [OneGridModule]` keeps
6611
+ * working for anyone already on it. New code can import `OneGridComponent`
6612
+ * directly instead.
6613
+ *
6614
+ * Icons no longer need registering here: each component that draws one
6615
+ * provides the eight the grid uses, so they travel with the component whichever
6616
+ * way it is imported.
6617
+ */
5036
6618
  class OneGridModule {
5037
6619
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
5038
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, declarations: [OneGridComponent,
5039
- OneGridHeaderComponent,
5040
- OneGridBodyComponent,
5041
- OneGridCheckboxComponent,
5042
- OneGridCheckboxHeaderComponent,
5043
- OneGridRowActionComponent,
5044
- OneGridPaginationComponent,
6620
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [OneGridComponent,
5045
6621
  OneGridStatusComponent,
5046
- OneGridCellGroupRendererComponent,
5047
- CellFieldValueEmptyPipe,
5048
- CellValueGetterPipe,
5049
- CellFieldValuePipe,
5050
- CellActionButtonVisiblePipe,
5051
- CellEditablePipe,
5052
- CellInvalidPipe,
5053
- CellSelectedPipe,
5054
- CellActionButtonEnablePipe,
5055
- CellNumberValuePipe,
5056
- CellFieldPipe,
5057
- CellKeyPipe,
5058
- CellLevelPaddingPipe,
5059
- ColumnSortStatePipe,
5060
- HeaderCheckboxDisabledPipe,
5061
- HeaderCheckboxCheckedPipe,
5062
- HeaderCheckboxIndeterminatePipe,
5063
- CellStateRendererPipe,
5064
- CellStateGroupRendererPipe,
5065
- OneCellInputValidatorDirective,
5066
- OneCellRendererComponentDirective,
5067
- OneCellTextExpandDirective,
5068
- OneGridCellCurrencyComponent,
5069
- OneGridCellOptionMenuComponent,
5070
- OneGridRowDragHandleComponent], imports: [CommonModule,
5071
- FormsModule,
5072
- ScrollingModule,
5073
- ReactiveFormsModule, i2$1.LucideAngularModule], exports: [OneGridComponent] }); }
5074
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [CommonModule,
5075
- FormsModule,
5076
- ScrollingModule,
5077
- ReactiveFormsModule,
5078
- LucideAngularModule.pick(icons)] }); }
6622
+ OneGridCellCurrencyComponent], exports: [OneGridComponent,
6623
+ OneGridStatusComponent,
6624
+ OneGridCellCurrencyComponent] }); }
6625
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [OneGridComponent,
6626
+ OneGridCellCurrencyComponent] }); }
5079
6627
  }
5080
6628
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, decorators: [{
5081
6629
  type: NgModule,
5082
6630
  args: [{
5083
- declarations: [
6631
+ imports: [
5084
6632
  OneGridComponent,
5085
- OneGridHeaderComponent,
5086
- OneGridBodyComponent,
5087
- OneGridCheckboxComponent,
5088
- OneGridCheckboxHeaderComponent,
5089
- OneGridRowActionComponent,
5090
- OneGridPaginationComponent,
5091
6633
  OneGridStatusComponent,
5092
- OneGridCellGroupRendererComponent,
5093
- CellFieldValueEmptyPipe,
5094
- CellValueGetterPipe,
5095
- CellFieldValuePipe,
5096
- CellActionButtonVisiblePipe,
5097
- CellEditablePipe,
5098
- CellInvalidPipe,
5099
- CellSelectedPipe,
5100
- CellActionButtonEnablePipe,
5101
- CellNumberValuePipe,
5102
- CellFieldPipe,
5103
- CellKeyPipe,
5104
- CellLevelPaddingPipe,
5105
- ColumnSortStatePipe,
5106
- HeaderCheckboxDisabledPipe,
5107
- HeaderCheckboxCheckedPipe,
5108
- HeaderCheckboxIndeterminatePipe,
5109
- CellStateRendererPipe,
5110
- CellStateGroupRendererPipe,
5111
- OneCellInputValidatorDirective,
5112
- OneCellRendererComponentDirective,
5113
- OneCellTextExpandDirective,
5114
6634
  OneGridCellCurrencyComponent,
5115
- OneGridCellOptionMenuComponent,
5116
- OneGridRowDragHandleComponent
5117
6635
  ],
5118
- imports: [
5119
- CommonModule,
5120
- FormsModule,
5121
- ScrollingModule,
5122
- ReactiveFormsModule,
5123
- LucideAngularModule.pick(icons),
6636
+ exports: [
6637
+ OneGridComponent,
6638
+ OneGridStatusComponent,
6639
+ OneGridCellCurrencyComponent,
5124
6640
  ],
5125
- exports: [OneGridComponent]
5126
6641
  }]
5127
6642
  }] });
5128
6643
 
@@ -5134,5 +6649,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
5134
6649
  * Generated bundle index. Do not edit.
5135
6650
  */
5136
6651
 
5137
- 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 };
6652
+ 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 };
5138
6653
  //# sourceMappingURL=one-grid-core-angular.mjs.map