@one-grid-core/angular 0.1.0-beta.6 → 0.2.1

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 (66) hide show
  1. package/README.md +49 -4
  2. package/esm2022/lib/components/one-grid/one-grid.component.mjs +67 -16
  3. package/esm2022/lib/components/one-grid-body/one-grid-body.component.mjs +282 -30
  4. package/esm2022/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.mjs +10 -7
  5. package/esm2022/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.mjs +60 -4
  6. package/esm2022/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.mjs +14 -5
  7. package/esm2022/lib/components/one-grid-header/one-grid-header.component.mjs +50 -13
  8. package/esm2022/lib/components/one-grid-pagination/one-grid-pagination.component.mjs +39 -15
  9. package/esm2022/lib/components/one-grid-row-action/one-grid-row-action.component.mjs +54 -8
  10. package/esm2022/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.mjs +110 -0
  11. package/esm2022/lib/core/directives/one-cell-renderer-component.directive.mjs +9 -2
  12. package/esm2022/lib/core/directives/one-cell-text-expand.directive.mjs +33 -51
  13. package/esm2022/lib/core/instants/one-grid-api.class.mjs +429 -166
  14. package/esm2022/lib/core/instants/one-row-node.class.mjs +103 -37
  15. package/esm2022/lib/core/models/one-column-def-types.model.mjs +1 -1
  16. package/esm2022/lib/core/models/one-event-types.model.mjs +1 -1
  17. package/esm2022/lib/core/models/one-grid-types.model.mjs +1 -1
  18. package/esm2022/lib/core/models/one-row-drag.model.mjs +2 -0
  19. package/esm2022/lib/core/models/one-row.model.mjs +1 -1
  20. package/esm2022/lib/core/models/one-tree-data.model.mjs +1 -1
  21. package/esm2022/lib/core/models/one-validation.model.mjs +1 -1
  22. package/esm2022/lib/core/pipes/cell-field-value-empty.pipe.mjs +8 -3
  23. package/esm2022/lib/core/pipes/column-sort-state.pipe.mjs +3 -1
  24. package/esm2022/lib/core/services/og-column.service.mjs +120 -13
  25. package/esm2022/lib/core/services/one-grid-cell.service.mjs +147 -22
  26. package/esm2022/lib/core/services/one-grid-row-drag.service.mjs +351 -0
  27. package/esm2022/lib/core/services/one-grid-selection.service.mjs +19 -6
  28. package/esm2022/lib/helpers/one-column-def.helper.mjs +27 -2
  29. package/esm2022/lib/helpers/one-filter.helper.mjs +71 -12
  30. package/esm2022/lib/helpers/one-validation.helper.mjs +7 -2
  31. package/esm2022/lib/index.mjs +6 -1
  32. package/esm2022/lib/one-grid.module.mjs +7 -16
  33. package/fesm2022/one-grid-core-angular.mjs +1988 -423
  34. package/fesm2022/one-grid-core-angular.mjs.map +1 -1
  35. package/lib/components/one-grid/one-grid.component.d.ts +16 -1
  36. package/lib/components/one-grid-body/one-grid-body.component.d.ts +132 -5
  37. package/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.d.ts +6 -1
  38. package/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.d.ts +19 -2
  39. package/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.d.ts +5 -2
  40. package/lib/components/one-grid-header/one-grid-header.component.d.ts +15 -7
  41. package/lib/components/one-grid-pagination/one-grid-pagination.component.d.ts +24 -5
  42. package/lib/components/one-grid-row-action/one-grid-row-action.component.d.ts +44 -0
  43. package/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.d.ts +39 -0
  44. package/lib/core/directives/one-cell-text-expand.directive.d.ts +21 -9
  45. package/lib/core/instants/one-grid-api.class.d.ts +212 -26
  46. package/lib/core/instants/one-row-node.class.d.ts +67 -14
  47. package/lib/core/models/one-column-def-types.model.d.ts +89 -15
  48. package/lib/core/models/one-event-types.model.d.ts +24 -1
  49. package/lib/core/models/one-grid-types.model.d.ts +36 -1
  50. package/lib/core/models/one-row-drag.model.d.ts +44 -0
  51. package/lib/core/models/one-row.model.d.ts +0 -5
  52. package/lib/core/models/one-tree-data.model.d.ts +13 -0
  53. package/lib/core/models/one-validation.model.d.ts +12 -2
  54. package/lib/core/pipes/column-sort-state.pipe.d.ts +1 -1
  55. package/lib/core/services/og-column.service.d.ts +44 -3
  56. package/lib/core/services/one-grid-cell.service.d.ts +68 -4
  57. package/lib/core/services/one-grid-row-drag.service.d.ts +113 -0
  58. package/lib/core/services/one-grid-selection.service.d.ts +9 -0
  59. package/lib/helpers/one-column-def.helper.d.ts +19 -1
  60. package/lib/helpers/one-filter.helper.d.ts +29 -4
  61. package/lib/index.d.ts +5 -0
  62. package/lib/one-grid.module.d.ts +17 -19
  63. package/package.json +1 -2
  64. package/scss/_one-grid.scss +389 -17
  65. package/esm2022/lib/core/pipes/cell-disable-resolver.pipe.mjs +0 -26
  66. package/lib/core/pipes/cell-disable-resolver.pipe.d.ts +0 -9
@@ -1,20 +1,35 @@
1
1
  import { v4 } from 'uuid';
2
2
  import * as i0 from '@angular/core';
3
- import { Injectable, Pipe, Input, Component, EventEmitter, Output, HostListener, Directive, ViewChild, ChangeDetectionStrategy, NgModule } from '@angular/core';
4
- import { BehaviorSubject, Subject, Subscription, fromEvent } from 'rxjs';
3
+ import { Injectable, Pipe, inject, NgZone, Input, Component, ViewChild, EventEmitter, Output, HostListener, Directive, ViewChildren, ChangeDetectionStrategy, NgModule } from '@angular/core';
4
+ import { BehaviorSubject, Subject, Subscription } from 'rxjs';
5
5
  import _ from 'lodash';
6
6
  import { filter, map } from 'rxjs/operators';
7
7
  import * as i2 from '@angular/common';
8
8
  import { CommonModule } from '@angular/common';
9
- import * as i3 from 'lucide-angular';
9
+ import * as i2$1 from 'lucide-angular';
10
10
  import { LucideAngularModule, icons } from 'lucide-angular';
11
- import * as i5 from '@angular/cdk/scrolling';
11
+ import * as i6 from '@angular/cdk/scrolling';
12
12
  import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
13
- import * as i3$1 from '@angular/forms';
13
+ import * as i3 from '@angular/forms';
14
14
  import { FormsModule, ReactiveFormsModule } from '@angular/forms';
15
- import { NgbTypeahead, NgbHighlight } from '@ng-bootstrap/ng-bootstrap';
16
- import { CdkDragPlaceholder } from '@angular/cdk/drag-drop';
17
15
 
16
+ /** Width used when a column defines neither `width` nor `minWidth`. */
17
+ const ONE_DEFAULT_COLUMN_WIDTH = 150;
18
+ /**
19
+ * The rendered width of a column, clamped to its bounds.
20
+ *
21
+ * `width`, `minWidth` and `maxWidth` are all optional, and the previous inline
22
+ * expression — repeated in two places — fell back to `minWidth` when `width`
23
+ * was absent. With neither set that produced `undefined`, and summing it gave
24
+ * a `NaN` row width, so the row collapsed. This is total: every combination of
25
+ * present and absent bounds resolves to a usable number.
26
+ */
27
+ function resolveColumnWidth(column) {
28
+ const min = column?.minWidth ?? 0;
29
+ const max = column?.maxWidth ?? Number.POSITIVE_INFINITY;
30
+ const width = column?.width ?? column?.minWidth ?? ONE_DEFAULT_COLUMN_WIDTH;
31
+ return Math.min(Math.max(width, min), max);
32
+ }
18
33
  /**
19
34
  * Apply Default Column Definitions
20
35
  */
@@ -27,7 +42,15 @@ function applyDefaultColumnDef(columnDefs, defaultColumnDef) {
27
42
  /**
28
43
  * One Column Params Resolver (Cell Renderer)
29
44
  */
30
- function resolveOneCellRendererParams(prop, params) {
45
+ function resolveOneCellRendererParams(prop, params,
46
+ /**
47
+ * What an unset property means: `false` for a capability you opt into,
48
+ * `true` for a restriction you opt out of. `!!prop` could not tell the two
49
+ * apart, which left `updatable` — a restriction — defaulting to "deny".
50
+ */
51
+ whenUnset = false) {
52
+ if (prop === undefined || prop === null)
53
+ return whenUnset;
31
54
  return typeof prop === 'function' ? prop(params) : !!prop;
32
55
  }
33
56
  /**
@@ -117,7 +140,12 @@ function getCellError(column, node, value, row) {
117
140
  if (!column?.validationRules || column?.validationRules?.length === 0)
118
141
  return null;
119
142
  if (column?.autoGroupField) {
120
- if (column.editableLevels.includes(node.level)) {
143
+ // `editableLevels` is optional, and this read was unguarded — a tree grid
144
+ // with validation rules on its group column threw on the first cell it
145
+ // rendered. Undeclared means no level is validated, matching the property's
146
+ // meaning elsewhere: it names the levels that are editable, and a level not
147
+ // named is not one of them.
148
+ if (column.editableLevels?.includes(node.level)) {
121
149
  for (const ruleFn of column?.validationRules) {
122
150
  const error = ruleFn(value, row);
123
151
  if (error)
@@ -138,35 +166,77 @@ function getCellError(column, node, value, row) {
138
166
  }
139
167
  }
140
168
 
169
+ /**
170
+ * The grid's wrapper around one row of your data.
171
+ *
172
+ * A node owns three kinds of state: its identity and position, the structural
173
+ * facts the grid derived while building the tree, and the per-cell UI state.
174
+ * `data` is your object, held by reference and mutated in place when a cell is
175
+ * committed — the node never copies it.
176
+ *
177
+ * @typeParam T - the row type
178
+ * @typeParam D - the detail row type, for master/detail grids
179
+ */
141
180
  class OneRowNode {
142
181
  constructor(rowId, data, rowIndex) {
143
- this.rowId = null;
144
- this.rowIndex = null;
145
- this.data = null;
146
- this.level = null;
182
+ /** Depth in the tree. 0 at the root; always 0 outside tree data. */
183
+ this.level = 0;
184
+ // ---------------------------------------------------------------------------
185
+ // Structure, derived by the grid while building rows
186
+ // ---------------------------------------------------------------------------
187
+ /** Has no children of its own. */
147
188
  this.leaf = false;
189
+ /** Is a group, but an empty one. */
148
190
  this.leafGroup = false;
191
+ /** Has children. */
192
+ this.group = false;
193
+ /** Can be opened — a non-empty group, or any row in a master/detail grid. */
194
+ this.expandable = false;
195
+ /** Survives the active filters. Recomputed on every filter pass. */
149
196
  this.visible = false;
150
197
  this.selected = false;
151
- this.expandable = false;
152
198
  this.expanded = false;
153
- this.group = false;
154
- this.submitted = false;
155
199
  this.parent = null;
156
200
  this.children = null;
201
+ // ---------------------------------------------------------------------------
202
+ // Per-cell UI state, keyed by column field
203
+ // ---------------------------------------------------------------------------
204
+ /** Which cells currently have an open editor. */
157
205
  this.editing = {};
206
+ /** Rendered value and kind for each cell, computed when the row is built. */
158
207
  this.cellStates = {};
159
208
  this.cellOptions = {};
209
+ // ---------------------------------------------------------------------------
210
+ // Master / detail
211
+ // ---------------------------------------------------------------------------
212
+ /** Detail rows for this master row. `null` until they have been supplied. */
160
213
  this.detailRowData = null;
214
+ /** Whether the custom detail renderer is showing. Toggled by the row action. */
161
215
  this.detailActive = false;
216
+ /** Height reserved for the detail area, computed from the detail row count. */
162
217
  this.detailRowHeight = null;
218
+ /**
219
+ * Listeners per event. Typed as a payload-agnostic callback because the map
220
+ * is heterogeneous; `on` and `emit` restore the per-event type at the edges.
221
+ */
163
222
  this.listeners = new Map();
164
223
  this.rowId = rowId;
165
224
  this.data = data;
166
225
  this.rowIndex = rowIndex;
167
226
  }
227
+ // ---------------------------------------------------------------------------
228
+ // Mutators
229
+ // ---------------------------------------------------------------------------
230
+ /**
231
+ * Write one field of the row and announce it.
232
+ *
233
+ * `null` is allowed because that is what the grid itself stores for a cleared
234
+ * cell; anything else must match the field's declared type.
235
+ */
168
236
  setDataValue(field, value) {
237
+ const previousData = { ...this.data };
169
238
  this.data[field] = value;
239
+ this.emit('rowDataChanged', { node: this, data: this.data, previousData });
170
240
  }
171
241
  setSelected(selected) {
172
242
  this.selected = selected;
@@ -177,54 +247,78 @@ class OneRowNode {
177
247
  setCellStates(cellStates) {
178
248
  this.cellStates = cellStates;
179
249
  }
250
+ /** @deprecated Read `node.selected`. */
180
251
  isSelected() {
181
252
  return this.selected;
182
253
  }
254
+ /** @deprecated Read `node.expanded`. */
183
255
  isExpanded() {
184
256
  return this.expanded;
185
257
  }
258
+ /** Supply or replace this master row's detail rows. */
259
+ setDetailRowData(detailRowData) {
260
+ const previousDetailRowData = this.detailRowData;
261
+ this.detailRowData = detailRowData;
262
+ this.emit('detailRowDataChanged', { node: this, detailRowData, previousDetailRowData });
263
+ }
264
+ setExpanded(expanded) {
265
+ if (this.expanded === expanded)
266
+ return; // no event for a no-op
267
+ this.expanded = expanded;
268
+ this.emit('expandedChanged', { node: this, expanded });
269
+ }
270
+ // ---------------------------------------------------------------------------
271
+ // Events
272
+ // ---------------------------------------------------------------------------
186
273
  /**
187
- * Event System
274
+ * Subscribe to one of this node's events.
275
+ *
276
+ * @returns an unsubscribe function. Call it, or `destroy()`, to release the
277
+ * listener — a node outlives any single render.
188
278
  */
189
279
  on(event, listener) {
190
- if (!this.listeners.has(event)) {
191
- this.listeners.set(event, new Set());
280
+ let listeners = this.listeners.get(event);
281
+ if (!listeners) {
282
+ listeners = new Set();
283
+ this.listeners.set(event, listeners);
192
284
  }
193
- this.listeners.get(event).add(listener);
194
- // Return unsubscribe fn
285
+ listeners.add(listener);
195
286
  return () => this.off(event, listener);
196
287
  }
197
288
  off(event, listener) {
198
289
  this.listeners.get(event)?.delete(listener);
199
290
  }
291
+ /**
292
+ * Notify listeners. Private on purpose: every event this class publishes is
293
+ * raised by the mutator that caused it, and a forged `expandedChanged` would
294
+ * leave the node's flag and the grid's view of it disagreeing.
295
+ */
200
296
  emit(event, payload) {
201
- this.listeners.get(event)?.forEach(cb => cb(payload));
297
+ const listeners = this.listeners.get(event);
298
+ if (!listeners?.size)
299
+ return;
300
+ // Snapshot: a listener is allowed to unsubscribe itself, or another, while
301
+ // the event is being delivered.
302
+ for (const listener of Array.from(listeners)) {
303
+ listener(payload);
304
+ }
202
305
  }
203
306
  /**
204
- * Mutators that fire events
205
- */
206
- setDetailRowData(detailRowData) {
207
- const previousDetailRowData = this.detailRowData;
208
- this.detailRowData = detailRowData;
209
- this.emit('detailRowDataChanged', {
210
- node: this,
211
- detailRowData,
212
- previousDetailRowData,
213
- });
214
- this.emit('childrenChanged', {
215
- node: this,
216
- children: this.children,
217
- source: 'masterDetail',
218
- });
219
- }
220
- setExpanded(expanded) {
221
- if (this.expanded === expanded)
222
- return; // skip if no change
223
- this.expanded = expanded;
224
- this.emit('expandedChanged', { node: this, expanded });
225
- }
307
+ * Release everything this node holds. Called by the grid when the row leaves
308
+ * the data or the grid is torn down.
309
+ *
310
+ * The tree links matter as much as the listeners: a parent holding children
311
+ * that hold the parent keeps the whole subtree — and its data — reachable.
312
+ */
226
313
  destroy() {
227
- this.listeners.clear(); // prevent memory leaks
314
+ this.listeners.clear();
315
+ this.parent = null;
316
+ this.children = null;
317
+ this.detailRowData = null;
318
+ this.detailRendererComponent = undefined;
319
+ this.editing = {};
320
+ this.cellStates = {};
321
+ this.cellOptions = {};
228
322
  }
229
323
  }
230
324
 
@@ -233,21 +327,40 @@ class OneRowNode {
233
327
  *
234
328
  * Rules:
235
329
  * - A node is visible if it matches the filters or has a matching descendant.
236
- * - A node is expanded if it has a matching descendant.
330
+ * - While a filter is active, a node is expanded iff it has a matching
331
+ * descendant, so the hit is revealed.
332
+ *
333
+ * Overwriting `expanded` is only correct *while filtering*. This runs from
334
+ * `applyFilters`, which every `setRowData` / `updateRowData` calls, so doing it
335
+ * unconditionally force-expanded every parent on each data update: it made
336
+ * `groupDefaultExpanded` inert for tree data and collapsed expanded
337
+ * master-detail rows (they have no children, so `childMatch` is false).
237
338
  *
238
339
  * @returns True if the current node or any descendant matches.
239
340
  */
240
- function filterTreeNode(node, filters, quick) {
341
+ function filterTreeNode(node, filters, quick, fields) {
342
+ const filtering = isFilterActive(filters, quick);
241
343
  // Check whether the current node matches all active filters.
242
- const selfMatch = matchesAdvancedFilters(node, filters) && matchesQuickFilter(node, quick);
344
+ const selfMatch = matchesAdvancedFilters(node, filters) && matchesQuickFilter(node, quick, fields);
243
345
  // Recursively determine whether any child matches.
244
- const childMatch = node.children?.some(child => filterTreeNode(child, filters, quick)) ?? false;
346
+ const childMatch = node.children?.some(child => filterTreeNode(child, filters, quick, fields)) ?? false;
245
347
  // Keep the node visible when it or any descendant matches.
246
348
  node.visible = selfMatch || childMatch;
247
- // Expand parents to reveal matching descendants.
248
- node.expanded = childMatch;
349
+ if (filtering) {
350
+ // Expand parents to reveal matching descendants.
351
+ node.expanded = childMatch;
352
+ }
249
353
  return node.visible;
250
354
  }
355
+ /**
356
+ * Whether anything is actually filtering right now. A model entry with no
357
+ * conditions matches everything, so it does not count as active.
358
+ */
359
+ function isFilterActive(filters, quick) {
360
+ if (quick)
361
+ return true;
362
+ return !!filters?.some(filter => filter?.conditions?.length > 0);
363
+ }
251
364
  /**
252
365
  * Apply advanced per-column filters
253
366
  */
@@ -263,8 +376,8 @@ function matchesAdvancedFilters(node, filters) {
263
376
  case 'contains': return val.includes(cmp);
264
377
  case 'startsWith': return val.startsWith(cmp);
265
378
  case 'endsWith': return val.endsWith(cmp);
266
- case 'greaterThan': return +val > +cmp;
267
- case 'lessThan': return +val < +cmp;
379
+ case 'greaterThan': return compare(val, cmp) > 0;
380
+ case 'lessThan': return compare(val, cmp) < 0;
268
381
  default: return true; // Unknown operator = ignore
269
382
  }
270
383
  });
@@ -272,12 +385,52 @@ function matchesAdvancedFilters(node, filters) {
272
385
  });
273
386
  }
274
387
  /**
275
- * Apply quick filter across all fields
388
+ * Order two already-lowercased cell values.
389
+ *
390
+ * Numeric when both sides parse as numbers, lexicographic otherwise. This used
391
+ * to be `+val > +cmp` unconditionally, so anything non-numeric compared NaN
392
+ * against NaN — always false. A date column filtered with `greaterThan` matched
393
+ * nothing at all rather than reporting a problem, and ISO dates happen to sort
394
+ * correctly as strings, which is what the fallback gives them.
395
+ */
396
+ function compare(val, cmp) {
397
+ const left = Number(val);
398
+ const right = Number(cmp);
399
+ const numeric = val !== '' && cmp !== '' && !Number.isNaN(left) && !Number.isNaN(right);
400
+ if (numeric)
401
+ return left === right ? 0 : left < right ? -1 : 1;
402
+ return val === cmp ? 0 : val < cmp ? -1 : 1;
403
+ }
404
+ /**
405
+ * Apply the quick filter to one row.
406
+ *
407
+ * Searches the fields the grid actually shows, when it is told what they are.
408
+ * It used to walk `Object.values(node.data)`, which had three consequences, all
409
+ * of them wrong:
410
+ *
411
+ * - a property with no column was searchable, so a user could match rows on
412
+ * data the grid never displayed;
413
+ * - a nested object stringified to `[object Object]`, so typing `object`
414
+ * matched every row holding one;
415
+ * - in a tree, `children` did the same, so most parents matched most queries.
416
+ *
417
+ * Non-primitive values are skipped regardless, so the `[object Object]` haystack
418
+ * cannot come back through a declared column either.
276
419
  */
277
- function matchesQuickFilter(node, quick) {
420
+ function matchesQuickFilter(node, quick, fields) {
278
421
  if (!quick)
279
422
  return true;
280
- return Object.values(node.data).some(value => (value ?? '').toString().toLowerCase().includes(quick));
423
+ const data = node.data;
424
+ const values = fields?.length ? fields.map(field => data?.[field]) : Object.values(data ?? {});
425
+ return values.some(value => {
426
+ if (value === null || value === undefined)
427
+ return false;
428
+ // Objects, arrays and functions have no useful text form. Skipping them is
429
+ // what keeps `[object Object]` out of the search.
430
+ if (typeof value === 'object' || typeof value === 'function')
431
+ return false;
432
+ return value.toString().toLowerCase().includes(quick);
433
+ });
281
434
  }
282
435
 
283
436
  /**
@@ -325,11 +478,17 @@ function resolveCellClass(column, row) {
325
478
  class OgColumnService {
326
479
  constructor() {
327
480
  this.defaultColumnDef = null;
481
+ /**
482
+ * The grid's `context`, so header callbacks receive the same one cell
483
+ * callbacks do. Set during init; null in a bare service.
484
+ */
485
+ this.context = null;
328
486
  this.columnDefs$ = new BehaviorSubject([]);
329
487
  this.groupColumnDefs$ = new BehaviorSubject([]);
330
488
  }
331
- initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs) {
489
+ initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs, context = null) {
332
490
  this.defaultColumnDef = defaultColumnDef;
491
+ this.context = context;
333
492
  this.setGroupColumnDefs(groupColumnDefs);
334
493
  this.setColumnDefs(columnDefs);
335
494
  }
@@ -339,7 +498,7 @@ class OgColumnService {
339
498
  return { columns, groups };
340
499
  }
341
500
  setGroupColumnDefs(groupColumnDefs) {
342
- const groups = groupColumnDefs.map((group) => this.resolveGroupPresentation(group));
501
+ const groups = groupColumnDefs.map((group) => this.resolveGroupPresentation(group, { context: this.context }));
343
502
  this.groupColumnDefs$.next(groups);
344
503
  }
345
504
  setColumnDefs(columnDefs) {
@@ -349,24 +508,86 @@ class OgColumnService {
349
508
  ...col,
350
509
  id: col?.field ? col.field : _.uniqueId('col_'),
351
510
  };
352
- return this.resolveCellPresentation(column);
511
+ return this.resolveHeaderPresentation(column);
353
512
  });
354
- this.recalculateGroups(columns);
355
- this.columnDefs$.next(columns);
513
+ const ordered = this.applyPinning(columns);
514
+ this.recalculateGroups(ordered);
515
+ this.columnDefs$.next(ordered);
516
+ }
517
+ /**
518
+ * Move pinned columns to their edge and measure how far in each one sits.
519
+ *
520
+ * Display order becomes left-pinned, unpinned, right-pinned, with declared
521
+ * order preserved inside each band. `_pinnedOffset` is the distance from that
522
+ * column's own edge — the sum of the widths of the pinned columns outside it
523
+ * — which is what the sticky `left` / `right` binding needs.
524
+ *
525
+ * With nothing pinned this returns the same array, in the same order, so a
526
+ * grid that does not use the feature is unaffected.
527
+ */
528
+ applyPinning(columns) {
529
+ const left = columns.filter(column => column.pinned === 'left');
530
+ const right = columns.filter(column => column.pinned === 'right');
531
+ if (!left.length && !right.length) {
532
+ for (const column of columns) {
533
+ column._pinnedOffset = null;
534
+ column._pinnedEdge = false;
535
+ }
536
+ return columns;
537
+ }
538
+ const center = columns.filter(column => column.pinned !== 'left' && column.pinned !== 'right');
539
+ let offset = 0;
540
+ for (const column of left) {
541
+ column._pinnedOffset = offset;
542
+ column._pinnedEdge = false;
543
+ offset += resolveColumnWidth(column);
544
+ }
545
+ // Right-pinned columns are measured from the right edge inwards, so the
546
+ // last declared one sits flush and the earlier ones stack outside it.
547
+ offset = 0;
548
+ for (let index = right.length - 1; index >= 0; index--) {
549
+ right[index]._pinnedOffset = offset;
550
+ right[index]._pinnedEdge = false;
551
+ offset += resolveColumnWidth(right[index]);
552
+ }
553
+ for (const column of center) {
554
+ column._pinnedOffset = null;
555
+ column._pinnedEdge = false;
556
+ }
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;
563
+ return [...left, ...center, ...right];
564
+ }
565
+ /** Total width of the columns frozen against the given edge. */
566
+ getPinnedWidth(side) {
567
+ return this.columnDefs$.getValue()
568
+ .filter(column => column.pinned === side)
569
+ .reduce((total, column) => total + resolveColumnWidth(column), 0);
356
570
  }
357
571
  // Update single column
358
572
  updateColumnDef(id, changes) {
359
573
  const columns = this.getColumnState().columns;
360
574
  const updatedColumns = columns.map(col => col.id === id ? { ...col, ...changes } : col);
361
- this.recalculateGroups(updatedColumns);
362
- this.columnDefs$.next(updatedColumns);
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);
363
581
  }
364
582
  // Reorder columns
365
583
  reorderColumnDefs(fromIndex, toIndex) {
366
584
  const columns = _.clone(this.getColumnState().columns);
367
585
  const [moved] = columns.splice(fromIndex, 1);
368
586
  columns.splice(toIndex, 0, moved);
369
- this.columnDefs$.next(columns);
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));
370
591
  }
371
592
  /**
372
593
  * Group Header Columns
@@ -375,7 +596,7 @@ class OgColumnService {
375
596
  if (this.groupColumnDefs$.getValue().length > 0) {
376
597
  const groups = this.groupColumnDefs$.value.map(group => ({
377
598
  ...group,
378
- width: columnDefs.filter(c => c.groupId === group.id).reduce((sum, col) => sum + (col.width ? col.width > col.maxWidth ? col.maxWidth : col.width < col.minWidth ? col.minWidth : col.width : col.minWidth), 0)
599
+ width: columnDefs.filter(c => c.groupId === group.id).reduce((sum, col) => sum + resolveColumnWidth(col), 0)
379
600
  }));
380
601
  this.groupColumnDefs$.next(groups);
381
602
  }
@@ -385,13 +606,51 @@ class OgColumnService {
385
606
  col._resolvedGroupStyle = typeof col.groupStyle === 'function' ? col.groupStyle(params) : col.groupStyle;
386
607
  return col;
387
608
  }
388
- resolveCellPresentation(col, params) {
609
+ /**
610
+ * Resolve a column's header presentation. Once per column, when the columns
611
+ * are set — a header is one cell for the whole column, so there is no row to
612
+ * resolve it against and nothing to redo per render.
613
+ *
614
+ * This used to resolve the cell hooks too, from the same call, with no params
615
+ * at all. Two bugs came out of that: any callback touching `params` threw a
616
+ * TypeError at init, and the header's resolved value was then overwritten by
617
+ * every cell that rendered, since the body re-ran the same method per cell
618
+ * with row params. In dev mode the disagreement surfaced as NG0100.
619
+ */
620
+ resolveHeaderPresentation(col) {
621
+ const params = {
622
+ columnDef: col,
623
+ field: col.field,
624
+ context: this.context,
625
+ };
389
626
  col._resolvedHeaderClass = typeof col.headerClass === 'function' ? col.headerClass(params) : col.headerClass;
390
627
  col._resolvedHeaderStyle = typeof col.headerStyle === 'function' ? col.headerStyle(params) : col.headerStyle;
391
- col._resolvedCellClass = typeof col.cellClass === 'function' ? col.cellClass(params) : col.cellClass;
392
- col._resolvedCellStyle = typeof col.cellStyle === 'function' ? col.cellStyle(params) : col.cellStyle;
628
+ // The header's own predicates. Declared as `boolean | ((params) => boolean)`
629
+ // and read straight off the column in the template, where a function is
630
+ // simply truthy — so `sortable: () => false` rendered a sort button that a
631
+ // consumer had explicitly asked not to have. Resolved here to a real
632
+ // boolean, once, because none of them depends on a row.
633
+ col._resolvedSortable = resolveOneCellRendererParams(col.sortable, params);
634
+ col._resolvedResizable = resolveOneCellRendererParams(col.resizable, params);
635
+ col._resolvedDragable = resolveOneCellRendererParams(col.dragable, params);
636
+ col._resolvedSuppressHeaderMenuButton = resolveOneCellRendererParams(col.suppressHeaderMenuButton, params);
393
637
  return col;
394
638
  }
639
+ /**
640
+ * Resolve one cell's class, for one row.
641
+ *
642
+ * Pure: the result is returned rather than written back onto the column def,
643
+ * which every row of that column shares. Writing to it worked only because
644
+ * the template resolved and read in the same expression — one render order
645
+ * away from every row showing the last row's class.
646
+ */
647
+ resolveCellClass(col, params) {
648
+ return typeof col?.cellClass === 'function' ? col.cellClass(params) : col?.cellClass;
649
+ }
650
+ /** Resolve one cell's inline style, for one row. Pure, as above. */
651
+ resolveCellStyle(col, params) {
652
+ return typeof col?.cellStyle === 'function' ? col.cellStyle(params) : col?.cellStyle;
653
+ }
395
654
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OgColumnService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
396
655
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OgColumnService }); }
397
656
  }
@@ -427,10 +686,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
427
686
 
428
687
  class CellFieldValueEmptyPipe {
429
688
  transform(data, field) {
430
- if (!data) {
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) {
431
694
  return true;
432
695
  }
433
- const value = field.split('.').reduce((obj, key) => obj?.[key], data);
696
+ const value = getFieldValue(data, field);
434
697
  return value === null || value === undefined || value === '';
435
698
  }
436
699
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
@@ -492,19 +755,69 @@ class OneGridCellService {
492
755
  this._cellValueGetter = new CellValueGetterPipe();
493
756
  this._cellFieldValue = new CellFieldValuePipe();
494
757
  this._columnDefs = [];
758
+ this._api = null;
759
+ // ---------------------------------------------------------------------------
760
+ // Row-scoped column predicates
761
+ //
762
+ // Every one of these resolves the same way and receives the same params, via
763
+ // `cellParams`. They used to each build their own subset inline, which is how
764
+ // `value`, `field` and `api` came to be missing from all of them.
765
+ // ---------------------------------------------------------------------------
766
+ /** Whether this cell may be edited at all. A capability: unset means no. */
767
+ this.editableFn = (column, node) => resolveOneCellRendererParams(column?.editable, this.cellParams(column, node));
495
768
  /**
496
- * One Column Def Params Resolver
769
+ * The second edit gate, ANDed with `editable`.
770
+ *
771
+ * A restriction, so unset means "no restriction". It used to resolve through
772
+ * `!!undefined` to `false`, closing the gate on every column that did not name
773
+ * it — so `editable: true` on its own made nothing editable.
497
774
  */
498
- this.editableFn = (column, node) => resolveOneCellRendererParams(column.editable, { node, data: node.data, columnDef: column, rowIndex: node.rowIndex, context: this._context });
499
- this.updatableFn = (column, node) => resolveOneCellRendererParams(column.updatable, { node, data: node.data, columnDef: column, rowIndex: node.rowIndex, context: this._context });
500
- this.disableFn = (column, node) => resolveOneCellRendererParams(column.disable, { node, data: node.data, columnDef: column, rowIndex: node.rowIndex, context: this._context });
501
- this.dragableFn = (column, node) => resolveOneCellRendererParams(column.dragable, { node, data: node.data, columnDef: column, rowIndex: node.rowIndex, context: this._context });
502
- this.valueGetterFn = (column, node) => resolveOneValueGetterParams(column.valueGetter, { data: node.data, rowIndex: node.rowIndex, node: node, context: this._context });
775
+ this.updatableFn = (column, node) => resolveOneCellRendererParams(column?.updatable, this.cellParams(column, node), true);
776
+ /** Disables this column's row action buttons. Not an edit gate. */
777
+ this.disableFn = (column, node) => resolveOneCellRendererParams(column?.disable, this.cellParams(column, node));
778
+ /** Hides this column's row action buttons entirely. */
779
+ this.hideActionButtonFn = (column, node) => resolveOneCellRendererParams(column?.hideActionButton, this.cellParams(column, node));
780
+ /** Whether this cell carries a row drag handle. */
781
+ this.rowDragFn = (column, node) => resolveOneCellRendererParams(column?.rowDrag, this.cellParams(column, node));
782
+ this.valueGetterFn = (column, node) => resolveOneValueGetterParams(column?.valueGetter, { data: node.data, rowIndex: node.rowIndex, node, context: this._context });
503
783
  }
504
784
  applyColumnDefs(columnDefs, context) {
505
785
  this._columnDefs = columnDefs;
506
786
  this._context = context;
507
787
  }
788
+ /**
789
+ * Back-reference to the grid api, so predicate params can carry it.
790
+ *
791
+ * Pushed in rather than injected: `OneGridApi` already depends on this
792
+ * service, and injecting it back would close a DI cycle.
793
+ */
794
+ setGridApi(api) {
795
+ this._api = api;
796
+ }
797
+ /**
798
+ * The params every row-scoped column callback receives.
799
+ *
800
+ * One builder, deliberately: `editable`, `updatable`, `disable`, `rowDrag`,
801
+ * `hideActionButton`, `cellClass`, `cellStyle`, `cellRenderer` and
802
+ * `valueFormatter` are all "decide something about this cell", and a consumer
803
+ * should not have to remember which of them happens to be handed `value` or
804
+ * `field`. Each used to be called with a slightly different subset.
805
+ */
806
+ cellParams(column, node) {
807
+ return {
808
+ // Column config first, so the grid's own fields win a name collision: a
809
+ // stale `value` left in `cellRendererParams` must not shadow this cell's.
810
+ ...(column?.cellRendererParams ?? {}),
811
+ value: this.resolveValue(node, column),
812
+ data: node.data,
813
+ node,
814
+ rowIndex: node.rowIndex,
815
+ field: column?.field,
816
+ columnDef: column,
817
+ api: this._api ?? undefined,
818
+ context: this._context,
819
+ };
820
+ }
508
821
  /**
509
822
  * Generate New Node States
510
823
  */
@@ -514,48 +827,123 @@ class OneGridCellService {
514
827
  const newStates = {};
515
828
  for (const column of this._columnDefs) {
516
829
  const field = column.field;
830
+ // Cell state is keyed by field, so a column without one contributes
831
+ // nothing. Skipping it keeps `undefined` out of the state object; such a
832
+ // column still renders its action buttons, which do not read cell state.
833
+ if (!field)
834
+ continue;
517
835
  if (field !== '_oneRowNumber') {
518
836
  newStates[field] = this.generateCellState(node, column) ?? node.cellStates[field] ?? { type: 'none', value: '' };
519
837
  }
520
838
  else {
521
- newStates[field] = node.cellStates[column.field];
839
+ newStates[field] = node.cellStates[field];
522
840
  }
523
841
  }
524
842
  return newStates;
525
843
  }
844
+ /**
845
+ * The cell's value: the `valueGetter`'s output where the column declares one,
846
+ * the raw field otherwise. This is the value, not the display text — see
847
+ * {@link formatValue}.
848
+ */
849
+ resolveValue(node, column) {
850
+ return column?.valueGetter
851
+ ? this._cellValueGetter.transform(node.data, node, column, this._context)
852
+ : this._cellFieldValue.transform(node.data, column.field);
853
+ }
854
+ /**
855
+ * Apply the column's `valueFormatter`.
856
+ *
857
+ * `valueFormatter` was declared on the column definition and called from
858
+ * nowhere — a consumer could write one, get no error, and see nothing happen.
859
+ * It runs on displayed text only, after the `valueGetter`, and is deliberately
860
+ * skipped for placeholders: an empty cell already has its own mechanism in
861
+ * `cellRendererParams.placeholder`, and two features competing to fill the
862
+ * same gap is worse than one.
863
+ *
864
+ * Note that sorting and filtering read the underlying row data, not this —
865
+ * which is exactly why formatting belongs here rather than in the getter.
866
+ */
867
+ formatValue(node, column, value) {
868
+ if (typeof column?.valueFormatter !== 'function')
869
+ return value;
870
+ // `value` is passed explicitly rather than left to `cellParams`: the empty
871
+ // -cell branch resolves it through the getter before deciding whether there
872
+ // is anything to format, and the formatter must see that same value.
873
+ return column.valueFormatter({ ...this.cellParams(column, node), value });
874
+ }
875
+ /**
876
+ * The placeholder for an empty cell, or `undefined` when this row's tree
877
+ * depth may not be edited.
878
+ *
879
+ * `cellRendererParams.placeholder` is a prompt, not data: it renders at half
880
+ * opacity where the value would go, which reads as "click here and fill this
881
+ * in". On the auto group column `editableLevels` decides which depths accept
882
+ * an edit at all, so a depth outside that list was advertising an edit the
883
+ * grid then refuses — the cell offers no input and the prompt never clears.
884
+ *
885
+ * The suppression is scoped to columns that actually declare `editableLevels`.
886
+ * The list is opt-in, and a column without one has no per-level rules rather
887
+ * than no editable levels, so plain grids and untouched tree columns keep
888
+ * showing their placeholder exactly as before.
889
+ */
890
+ resolvePlaceholder(node, column) {
891
+ const placeholder = column?.cellRendererParams?.placeholder;
892
+ const editableLevels = column?.editableLevels;
893
+ if (!column?.autoGroupField || !Array.isArray(editableLevels))
894
+ return placeholder;
895
+ return editableLevels.includes(node?.level) ? placeholder : undefined;
896
+ }
526
897
  /**
527
898
  * Generate New Node State Based on Column Field
528
899
  */
529
900
  generateCellState(node, column) {
530
- const isField = column.field !== undefined || column.field !== '' || column.field !== null;
531
- const isEmpty = this._cellFieldValueEmpty.transform(node.data, column.field);
901
+ // This was `field !== undefined || field !== '' || field !== null`, which is
902
+ // true for every possible value including all three it meant to exclude —
903
+ // `||` where `&&` was intended. It gated the four branches below, so none
904
+ // of them could ever fall through to the default. Reduced to the check it
905
+ // was reaching for: a column with no field has no value to render.
906
+ const field = column?.field;
907
+ if (!field) {
908
+ return { type: 'none', value: '' };
909
+ }
910
+ const isEmpty = this._cellFieldValueEmpty.transform(node.data, field);
532
911
  const isGroup = node.group || node.leafGroup;
533
912
  const isAutoGroup = column?.autoGroupField;
534
913
  if (column && column.cellRenderer && !isGroup) {
535
- const value = column.cellRenderer({ node, columnDef: column });
536
- return { type: 'html', value: value };
914
+ // The renderer used to be handed `{ node, columnDef }` and nothing else,
915
+ // so the obvious way to write one — `params.value` — produced the string
916
+ // "undefined", and `params.data.x` threw. It now gets exactly what every
917
+ // other row-scoped hook gets.
918
+ return { type: 'html', value: column.cellRenderer(this.cellParams(column, node)) };
537
919
  }
538
920
  // 1. Cell Group Placeholder
539
- if (isField && isEmpty && isGroup && isAutoGroup) {
540
- return { type: 'placeholder', value: column?.cellRendererParams?.placeholder ?? this._cellValueGetter.transform(node.data, node, column, this._context) ?? '' };
921
+ if (isEmpty && isGroup && isAutoGroup) {
922
+ return { type: 'placeholder', value: this.resolvePlaceholder(node, column) ?? this._cellValueGetter.transform(node.data, node, column, this._context) ?? '' };
541
923
  }
542
924
  // 2. Cell Group Text Value
543
- if (isField && isGroup && isAutoGroup) {
544
- const value = column?.valueGetter ? this._cellValueGetter.transform(node.data, node, column, this._context) : this._cellFieldValue.transform(node.data, column.field);
925
+ if (isGroup && isAutoGroup) {
926
+ const value = this.formatValue(node, column, this.resolveValue(node, column));
545
927
  return { type: 'text', value: value ?? '' };
546
928
  }
547
929
  // 3. Cell Placeholder
548
- if (isField && isEmpty && !isGroup && !isAutoGroup) {
930
+ //
931
+ // The auto group column is deliberately NOT excluded here. Cases 1 and 2
932
+ // above already claimed the group rows, so what reaches this point is a
933
+ // leaf — and a leaf on the auto group column has to render its label too,
934
+ // indented by level. Excluding it dropped every leaf through to case 5 and
935
+ // rendered the group column blank for the whole bottom row of a tree.
936
+ if (isEmpty && !isGroup) {
549
937
  const valueGetter = this._cellValueGetter.transform(node.data, node, column, this._context);
550
938
  if (valueGetter) {
551
- return { type: 'text', value: valueGetter };
939
+ return { type: 'text', value: this.formatValue(node, column, valueGetter) };
552
940
  }
553
- const placeholder = column?.cellRendererParams?.placeholder ?? '';
941
+ const placeholder = this.resolvePlaceholder(node, column) ?? '';
554
942
  return { type: 'placeholder', value: placeholder };
555
943
  }
556
- // 4. Cell Text Value
557
- if (isField && !(isGroup || isAutoGroup)) {
558
- const value = column?.valueGetter ? this._cellValueGetter.transform(node.data, node, column, this._context) : this._cellFieldValue.transform(node.data, column.field);
944
+ // 4. Cell Text Value (leaves, including the auto group column — see above)
945
+ if (!isGroup) {
946
+ const value = this.formatValue(node, column, this.resolveValue(node, column));
559
947
  return { type: 'text', value: value ?? '' };
560
948
  }
561
949
  // 5. Default
@@ -637,13 +1025,26 @@ class OneGridSelectionService {
637
1025
  toggleAllSelection(allIds, selected) {
638
1026
  selected ? this._selectedIds = new Set(allIds) : this._selectedIds.clear();
639
1027
  }
640
- // Header Checkbox State
1028
+ /**
1029
+ * Tri-state for the header checkbox, over the rows currently present.
1030
+ *
1031
+ * This compared `_selectedIds.size` with `allIds.length` and never checked
1032
+ * that the selected ids were among the present ones. Selecting two rows and
1033
+ * then swapping in two different rows reported `checked` with nothing
1034
+ * actually selected, because the two counts happened to match. Counting the
1035
+ * intersection is the only thing that answers the question being asked.
1036
+ */
641
1037
  getSelectAllState(allIds) {
642
- if (this._selectedIds.size === 0)
1038
+ if (!allIds?.length)
643
1039
  return 'unchecked';
644
- if (this._selectedIds.size === allIds.length)
645
- return 'checked';
646
- return 'indeterminate';
1040
+ let selected = 0;
1041
+ for (const rowId of allIds) {
1042
+ if (this._selectedIds.has(rowId))
1043
+ selected++;
1044
+ }
1045
+ if (selected === 0)
1046
+ return 'unchecked';
1047
+ return selected === allIds.length ? 'checked' : 'indeterminate';
647
1048
  }
648
1049
  selectAll(rowIds) {
649
1050
  this._selectedIds = new Set(rowIds);
@@ -697,6 +1098,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
697
1098
  }] });
698
1099
 
699
1100
  class OneGridApi {
1101
+ /** Unique per grid instance, assigned during `init`. */
1102
+ get id() {
1103
+ return this._id;
1104
+ }
1105
+ /** The `[context]` the grid was given. */
1106
+ get context() {
1107
+ return this._context;
1108
+ }
1109
+ /** Selection mode, captured during `init`. Changing the input later has no effect. */
1110
+ get rowSelection() {
1111
+ return this._rowSelection;
1112
+ }
1113
+ /** Which role this instance plays: simple, master, detail or tree. */
1114
+ get apiType() {
1115
+ return this._apiType;
1116
+ }
1117
+ /** Every node the grid holds, in flattened order. */
1118
+ get rowNodes() {
1119
+ return this._rowNodes;
1120
+ }
1121
+ /** The sort model currently applied. Change it with `onSortChanged`. */
1122
+ get sortModel() {
1123
+ return this._sortModel;
1124
+ }
700
1125
  constructor(columnService, rowService, cellService, eventService, selectionService, expansionService) {
701
1126
  this.columnService = columnService;
702
1127
  this.rowService = rowService;
@@ -704,17 +1129,18 @@ class OneGridApi {
704
1129
  this.eventService = eventService;
705
1130
  this.selectionService = selectionService;
706
1131
  this.expansionService = expansionService;
707
- this.context = null;
708
- this.rowSelection = null;
709
- this.apiType = null;
710
- this.rowNodes = [];
711
- this.sortModel = [];
1132
+ this._context = null;
1133
+ this._rowSelection = null;
1134
+ this._apiType = null;
1135
+ this._rowNodes = [];
1136
+ this._sortModel = [];
712
1137
  this.rowHeight = 26;
713
1138
  this.headerRowHeight = 32;
714
1139
  this._currentIndex = 0;
715
1140
  this._quickFilterValue = null;
716
1141
  this._activeFilters = [];
717
1142
  this._visibleRowNodes = [];
1143
+ this._pinnedRowNodes = { top: [], bottom: [] };
718
1144
  this._originalRowNodes = new Map();
719
1145
  this._nodeCleanupMap = new Map(); // Cleanup registry (prevents memory leaks)
720
1146
  this._destroyed = false;
@@ -724,63 +1150,83 @@ class OneGridApi {
724
1150
  this.groupDefaultExpanded = 0;
725
1151
  this.masterDetail = false;
726
1152
  }
727
- init(gridApiType, rowSelection, columnDefs, defaultColumnDef, context, masterDetail, detailCellRendererParams, detailRendererComponent, treeData, groupColumnDefs) {
728
- this.id = v4();
729
- this.apiType = gridApiType;
730
- this.rowSelection = rowSelection;
731
- this.context = context;
1153
+ /**
1154
+ * Stand the grid up. Called by `OneGridComponent` from `ngOnInit`.
1155
+ *
1156
+ * Takes a single `OneGridInitOptions` rather than the ten positional
1157
+ * arguments it used to, so a call site reads as the configuration it is and
1158
+ * a new option no longer means editing every caller.
1159
+ *
1160
+ * @internal Applications obtain a ready-made API from `(onGridReady)`; they
1161
+ * should never call this themselves.
1162
+ */
1163
+ init(options) {
1164
+ const { gridType, columnDefs, defaultColumnDef = {}, groupColumnDefs = [], context = null, rowSelection = null, masterDetail = false, detailCellRendererParams = null, detailRendererComponent = null, treeData = false, } = options;
1165
+ this._id = v4();
1166
+ this._apiType = gridType;
1167
+ this._rowSelection = rowSelection;
1168
+ this._context = context;
732
1169
  this.masterDetail = masterDetail;
733
1170
  this.detailCellRendererParams = detailCellRendererParams;
734
1171
  this.detailRendererComponent = detailRendererComponent;
735
1172
  this.treeData = treeData;
736
- this.selectionService.setSelectionMode(this.rowSelection);
737
- this.rowService.setGetRowIdFn(this.context?.componentParent?.getRowId ?? null);
1173
+ this.selectionService.setSelectionMode(rowSelection);
1174
+ this.rowService.setGetRowIdFn(context?.componentParent?.getRowId ?? null);
738
1175
  this.cellService.applyColumnDefs(columnDefs, context);
739
- this.columnService.initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs);
1176
+ this.cellService.setGridApi(this);
1177
+ this.columnService.initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs, context);
740
1178
  const columnState = this.columnService.getColumnState();
741
1179
  setTimeout(() => this.eventService.dispatchEvent('columnChanged', columnState));
742
1180
  }
743
1181
  /**
744
- * Create Row Node & Clear All State IDS
1182
+ * Replace every row, discarding selection and expansion.
1183
+ *
1184
+ * Use this when the rows represent something new. To swap the data while
1185
+ * keeping what the user has selected and opened, use `updateRowData`.
745
1186
  */
746
1187
  setRowData(rowData) {
747
- const validRowIds = new Set();
748
- // Clear All State IDS
749
- this.selectionService.deselectAll();
750
- this.expansionService.clear();
751
- // Build New Row Nodes
752
- this.buildRowNodes(rowData, null, 0, validRowIds);
753
- // Apply Filter
754
- this.applyFilters();
755
- // Remove invalid nodes from map
756
- for (const rowId of this._originalRowNodes.keys()) {
757
- if (!validRowIds.has(rowId)) {
758
- this._originalRowNodes.delete(rowId);
759
- }
760
- }
1188
+ this.rebuildRows(rowData, true);
761
1189
  }
762
1190
  /**
763
- * Create Row Nodes & Not Clear State IDS
1191
+ * Replace every row, keeping selection and expansion.
1192
+ *
1193
+ * State is keyed by row id, so this only preserves anything if the grid has a
1194
+ * `context.componentParent.getRowId` — otherwise each rebuild mints new ids
1195
+ * and there is nothing left to match against.
764
1196
  */
765
1197
  updateRowData(rowData) {
1198
+ this.rebuildRows(rowData, false);
1199
+ }
1200
+ /**
1201
+ * The one row-loading path. `setRowData` and `updateRowData` differed only in
1202
+ * whether they cleared the state services first, so everything else — build,
1203
+ * filter, prune — lives here once.
1204
+ */
1205
+ rebuildRows(rowData, resetState) {
1206
+ if (this._destroyed)
1207
+ return;
1208
+ if (resetState) {
1209
+ this.selectionService.deselectAll();
1210
+ this.expansionService.clear();
1211
+ }
766
1212
  const validRowIds = new Set();
767
- // Build New Row Nodes
768
1213
  this.buildRowNodes(rowData, null, 0, validRowIds);
769
- // Apply Filter
1214
+ // Re-apply the active sort. `buildRowNodes` rebuilds the row list in the
1215
+ // order the data arrived, so without this a data update silently returned
1216
+ // the rows to that order while `sortModel` — and therefore the header's
1217
+ // sort arrow — still said they were sorted. Sorting before filtering
1218
+ // because filtering only sets visibility on whatever order this leaves.
1219
+ if (this._sortModel.length > 0)
1220
+ this.applySorting();
770
1221
  this.applyFilters();
771
- // Remove invalid nodes from map
772
- for (const rowId of this._originalRowNodes.keys()) {
773
- if (!validRowIds.has(rowId)) {
774
- this._originalRowNodes.delete(rowId);
775
- }
776
- }
1222
+ this.pruneRemovedNodes(validRowIds);
777
1223
  }
778
1224
  /**
779
1225
  * Recursive Build Row Nodes (Optimized)
780
1226
  */
781
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())) {
782
1228
  if (parent === null) {
783
- this.rowNodes = [];
1229
+ this._rowNodes = [];
784
1230
  this._currentIndex = 0;
785
1231
  }
786
1232
  if (!rowData?.length)
@@ -806,7 +1252,11 @@ class OneGridApi {
806
1252
  }
807
1253
  }
808
1254
  const childrenExist = children && children.length >= 0;
809
- node.group = this.masterDetail ? false : childrenExist;
1255
+ // Coerced, because `childrenExist` is `children && children.length >= 0`
1256
+ // — which is `null` for a row with no children field, not `false`. These
1257
+ // are declared `boolean`, and assigning null made every consumer compare
1258
+ // truthiness instead of the value.
1259
+ node.group = this.masterDetail ? false : !!childrenExist;
810
1260
  node.leaf = this.masterDetail ? true : !node.group;
811
1261
  node.leafGroup = this.masterDetail ? false : (node.group && !childrenExist);
812
1262
  node.expandable = this.masterDetail ? true : (node.group && !node.leafGroup);
@@ -814,11 +1264,11 @@ class OneGridApi {
814
1264
  node.setExpanded(expandedIds.has(rowId) ? true : collapsedIds.has(rowId) ? false : defaultExpanded);
815
1265
  node.setSelected(selectedIds.has(rowId));
816
1266
  node.setCellStates(this.cellService.generateCellStates(node));
817
- if (this.masterDetail && this.apiType === 'master') {
818
- this.getDetailRowData({ data, node: node, successCallback: (detailRowData) => node.setDetailRowData(detailRowData) });
1267
+ if (this.masterDetail && this._apiType === 'master') {
1268
+ this._getDetailRowData({ data, node: node, successCallback: (detailRowData) => node.setDetailRowData(detailRowData) });
819
1269
  }
820
1270
  this._currentIndex++;
821
- this.rowNodes.push(node);
1271
+ this._rowNodes.push(node);
822
1272
  nodes.push(node);
823
1273
  node.children = this.treeData && childrenExist ? this.buildRowNodes(children || [], node, level + 1, validRowIds, expandedIds, collapsedIds, selectedIds) : [];
824
1274
  }
@@ -830,19 +1280,37 @@ class OneGridApi {
830
1280
  applyFilters() {
831
1281
  const filters = this._activeFilters;
832
1282
  const quick = this._quickFilterValue?.toLowerCase() ?? '';
833
- this.rowNodes.forEach(node => filterTreeNode(node, filters, quick));
1283
+ // The fields the grid actually shows. Without them the quick filter falls
1284
+ // back to every property on the row, which lets a user match on data that
1285
+ // has no column and never appears on screen.
1286
+ const fields = this.quickFilterFields();
1287
+ this._rowNodes.forEach(node => filterTreeNode(node, filters, quick, fields));
834
1288
  this.refreshVisibleNodes();
835
1289
  }
1290
+ /**
1291
+ * Which fields the quick filter searches: every column that has one.
1292
+ *
1293
+ * Empty while the columns are still being registered, in which case the
1294
+ * helper falls back to searching the row — the same behaviour as before, and
1295
+ * the only sensible answer when the grid does not yet know its columns.
1296
+ */
1297
+ quickFilterFields() {
1298
+ return this.columnService
1299
+ .getColumnState()
1300
+ .columns
1301
+ .map(column => column.field)
1302
+ .filter((field) => !!field && field !== '_oneRowNumber');
1303
+ }
836
1304
  /**
837
1305
  * Update Row Nodes Display
838
1306
  */
839
- refreshVisibleNodes() {
1307
+ refreshVisibleNodes(dispatch = true) {
840
1308
  if (this._destroyed)
841
1309
  return;
842
1310
  const visibleNodes = [];
843
1311
  let rowNumber = 1;
844
1312
  const visibilityCache = new Map();
845
- for (const node of this.rowNodes) {
1313
+ for (const node of this._rowNodes) {
846
1314
  const parentVisible = node.parent ? visibilityCache.get(node.parent.rowId) : true;
847
1315
  const isVisible = node.visible && parentVisible && (!node.parent || node.parent.expanded);
848
1316
  visibilityCache.set(node.rowId, isVisible);
@@ -856,7 +1324,132 @@ class OneGridApi {
856
1324
  }
857
1325
  }
858
1326
  this._visibleRowNodes = [...visibleNodes];
859
- this.eventService.dispatchEvent('rowDataChanged', { nodes: visibleNodes });
1327
+ if (dispatch)
1328
+ this.eventService.dispatchEvent('rowDataChanged', { nodes: visibleNodes });
1329
+ }
1330
+ /**
1331
+ * Replace the rows anchored to one edge of the grid.
1332
+ *
1333
+ * Pinned rows are deliberately kept out of `rowNodes` and
1334
+ * `_originalRowNodes`: they are not data. Sorting, filtering, selection,
1335
+ * expansion, pagination and the visible row count all ignore them, which is
1336
+ * the whole point of a summary row. They render with the same column
1337
+ * definitions, so renderers and value getters work as they do everywhere
1338
+ * else.
1339
+ */
1340
+ setPinnedRowData(side, rowData) {
1341
+ if (this._destroyed)
1342
+ return;
1343
+ for (const node of this._pinnedRowNodes[side])
1344
+ node.destroy();
1345
+ this._pinnedRowNodes[side] = (rowData ?? []).map((data, index) => {
1346
+ // Ids are namespaced by side and position. They never collide with a data
1347
+ // row's id, and they are stable across rebuilds, which keeps the rendered
1348
+ // `data-cell-id` attributes stable too.
1349
+ const node = new OneRowNode(`one-pinned-${side}-${index}`, data, index);
1350
+ node.level = 0;
1351
+ node.leaf = true;
1352
+ node.visible = true;
1353
+ node.setCellStates(this.cellService.generateCellStates(node));
1354
+ return node;
1355
+ });
1356
+ this.eventService.dispatchEvent('pinnedRowDataChanged', {
1357
+ side,
1358
+ nodes: this._pinnedRowNodes[side],
1359
+ });
1360
+ }
1361
+ /** The rows anchored to one edge. Read-only; change them with `setPinnedRowData`. */
1362
+ getPinnedRowNodes(side) {
1363
+ return this._pinnedRowNodes[side];
1364
+ }
1365
+ /**
1366
+ * Row reordering
1367
+ * --------------------------------------------------------------------------
1368
+ *
1369
+ * `_originalRowNodes` is the source of truth for row order: its insertion
1370
+ * order is what `applySorting` falls back to when the sort model empties, and
1371
+ * what a rebuild walks. `_rowNodes` is only the current display order. A move
1372
+ * that touched one and not the other would either be undone by the next sort
1373
+ * change or invisible until one happened, so both are rewritten together.
1374
+ */
1375
+ /**
1376
+ * Whether the displayed order is the data order, and so whether a manual move
1377
+ * would survive.
1378
+ *
1379
+ * @returns the reason reordering is unavailable, or `null` when it is fine.
1380
+ */
1381
+ getRowDragSuppressedReason() {
1382
+ if (this.treeData)
1383
+ return 'treeData';
1384
+ if (this._sortModel.length > 0)
1385
+ return 'sorted';
1386
+ if (isFilterActive(this._activeFilters, this._quickFilterValue ?? ''))
1387
+ return 'filtered';
1388
+ return null;
1389
+ }
1390
+ /** Convenience inverse of {@link getRowDragSuppressedReason}. */
1391
+ canReorderRows() {
1392
+ return this.getRowDragSuppressedReason() === null;
1393
+ }
1394
+ /**
1395
+ * Move a row from one position to another.
1396
+ *
1397
+ * Indices are positions in the grid's row order, the same order
1398
+ * `getAllRowNodes()` returns. `targetIndex` is where the row ends up *after*
1399
+ * it has been lifted out of `sourceIndex` — dragging row 0 to the bottom of a
1400
+ * 5-row grid is `moveRow(0, 4)`, not `moveRow(0, 5)`.
1401
+ *
1402
+ * @returns `true` if the rows moved. `false` when the move was rejected —
1403
+ * out-of-range indices, a no-op, or an order the grid does not own
1404
+ * (see {@link getRowDragSuppressedReason}) — so a caller can tell a
1405
+ * refused move from an applied one.
1406
+ */
1407
+ moveRow(sourceIndex, targetIndex) {
1408
+ if (this._destroyed)
1409
+ return false;
1410
+ if (!this.canReorderRows())
1411
+ return false;
1412
+ const order = [...this._originalRowNodes.values()];
1413
+ if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex))
1414
+ return false;
1415
+ if (sourceIndex < 0 || sourceIndex >= order.length)
1416
+ return false;
1417
+ if (targetIndex < 0 || targetIndex >= order.length)
1418
+ return false;
1419
+ if (sourceIndex === targetIndex)
1420
+ return false;
1421
+ const [moved] = order.splice(sourceIndex, 1);
1422
+ order.splice(targetIndex, 0, moved);
1423
+ // Rebuilt rather than mutated: a Map preserves insertion order, and there
1424
+ // is no way to reposition a key within one.
1425
+ this._originalRowNodes = new Map(order.map(node => [node.rowId, node]));
1426
+ this._rowNodes = order;
1427
+ this.reindexRows();
1428
+ this.eventService.dispatchEvent('rowOrderChanged', { nodes: this._visibleRowNodes, node: moved, fromIndex: sourceIndex, toIndex: targetIndex });
1429
+ return true;
1430
+ }
1431
+ /**
1432
+ * Move a row identified by id. Convenient when the caller holds a node rather
1433
+ * than a position — the index it was built with goes stale after any move.
1434
+ */
1435
+ moveRowById(rowId, targetIndex) {
1436
+ const sourceIndex = [...this._originalRowNodes.values()].findIndex(node => node.rowId === rowId);
1437
+ return sourceIndex === -1 ? false : this.moveRow(sourceIndex, targetIndex);
1438
+ }
1439
+ /**
1440
+ * Re-stamp `rowIndex` from the current order.
1441
+ *
1442
+ * Row index is denormalised onto every node, and half the grid reads it —
1443
+ * cell params, the range selection, the detail lookup. Leaving it stale after
1444
+ * a move puts every row below the drop point one out.
1445
+ */
1446
+ reindexRows() {
1447
+ this._rowNodes.forEach((node, index) => node.setRowIndex(index));
1448
+ // Silent: row numbers and the visible list have to be rebuilt, but the
1449
+ // `rowDataChanged` that normally follows defers a frame and re-seeds detail
1450
+ // rows. A keyboard drag fires one of these per arrow key, and `moveRow`
1451
+ // announces itself through `rowOrderChanged` instead.
1452
+ this.refreshVisibleNodes(false);
860
1453
  }
861
1454
  getVisibleRowNodes() {
862
1455
  return this._visibleRowNodes;
@@ -865,17 +1458,19 @@ class OneGridApi {
865
1458
  * Sorting Model Feature
866
1459
  */
867
1460
  onSortChanged(sortModel) {
868
- this.sortModel = sortModel;
1461
+ if (this._destroyed)
1462
+ return;
1463
+ this._sortModel = sortModel;
869
1464
  this.applySorting();
870
- this.eventService.dispatchEvent('sortChanged', { nodes: this.rowNodes });
1465
+ this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes });
871
1466
  }
872
1467
  applySorting() {
873
- if (this.sortModel.length === 0) {
874
- this.rowNodes = [...this._originalRowNodes.values()];
1468
+ if (this._sortModel.length === 0) {
1469
+ this._rowNodes = [...this._originalRowNodes.values()];
875
1470
  return;
876
1471
  }
877
- this.rowNodes = [...this._originalRowNodes.values()].sort((a, b) => {
878
- for (const sortItem of this.sortModel) {
1472
+ this._rowNodes = [...this._originalRowNodes.values()].sort((a, b) => {
1473
+ for (const sortItem of this._sortModel) {
879
1474
  const { colId, sort } = sortItem;
880
1475
  const valA = getFieldValue(a.data, colId);
881
1476
  const valB = getFieldValue(b.data, colId);
@@ -888,11 +1483,17 @@ class OneGridApi {
888
1483
  });
889
1484
  }
890
1485
  compareValues(valA, valB, sortDirection) {
891
- if (valA === null && valB === null)
1486
+ // `undefined` has to be treated as empty alongside `null`: getFieldValue
1487
+ // returns undefined for a missing field or an unresolved dotted path, and
1488
+ // comparing undefined with < / > yields false both ways, which made such
1489
+ // rows settle in whatever order the sort happened to encounter them.
1490
+ const aEmpty = valA === null || valA === undefined;
1491
+ const bEmpty = valB === null || valB === undefined;
1492
+ if (aEmpty && bEmpty)
892
1493
  return 0;
893
- if (valA === null)
1494
+ if (aEmpty)
894
1495
  return sortDirection === 'asc' ? 1 : -1;
895
- if (valB === null)
1496
+ if (bEmpty)
896
1497
  return sortDirection === 'asc' ? -1 : 1;
897
1498
  if (typeof valA === 'string' && typeof valB === 'string') {
898
1499
  const comparison = valA.localeCompare(valB, undefined, { numeric: true });
@@ -947,7 +1548,7 @@ class OneGridApi {
947
1548
  const node = this.getRowNodeById(rowId);
948
1549
  if (node) {
949
1550
  if (this.selectionService.getSelectionMode() === 'single') {
950
- this.rowNodes.forEach(rowNode => rowNode.setSelected(rowNode.rowId === node.rowId));
1551
+ this._rowNodes.forEach(rowNode => rowNode.setSelected(rowNode.rowId === node.rowId));
951
1552
  this._originalRowNodes.forEach(rowNode => rowNode.setSelected(rowNode.rowId === node.rowId));
952
1553
  }
953
1554
  else {
@@ -972,13 +1573,13 @@ class OneGridApi {
972
1573
  * Get & Set Functions
973
1574
  */
974
1575
  getAllRowNodes() {
975
- return this.rowNodes?.length > 0 ? [...this.rowNodes] : [];
1576
+ return this._rowNodes?.length > 0 ? [...this._rowNodes] : [];
976
1577
  }
977
1578
  getRowDataSelected() {
978
- return this.rowNodes.filter(node => node.selected).map(node => ({ ...node.data }));
1579
+ return this._rowNodes.filter(node => node.selected).map(node => ({ ...node.data }));
979
1580
  }
980
1581
  getSelectedRowNodes() {
981
- const nodes = this.rowNodes.filter(node => node.selected);
1582
+ const nodes = this._rowNodes.filter(node => node.selected);
982
1583
  return nodes && nodes.length > 0 ? [...nodes] : [];
983
1584
  }
984
1585
  /**
@@ -988,6 +1589,8 @@ class OneGridApi {
988
1589
  return this._activeFilters;
989
1590
  }
990
1591
  setFilterModel(model) {
1592
+ if (this._destroyed)
1593
+ return;
991
1594
  this._activeFilters = model;
992
1595
  this.applyFilters();
993
1596
  }
@@ -998,76 +1601,69 @@ class OneGridApi {
998
1601
  * Quick filter
999
1602
  */
1000
1603
  setQuickFilter(value) {
1604
+ if (this._destroyed)
1605
+ return;
1001
1606
  this._quickFilterValue = (value || '').toLowerCase();
1002
1607
  this.applyFilters();
1003
1608
  }
1004
1609
  /**
1005
- * Validation Feature
1610
+ * Run the column validators over one row.
1611
+ *
1612
+ * Rules run in order and stop at the first failure, so a field appears at
1613
+ * most once. Fields that passed are absent rather than mapped to `null`.
1614
+ *
1615
+ * The row type comes from the grid's own `T` — this used to declare its own
1616
+ * `<T>`, which shadowed the class parameter and let an `OneGridApi<Employee>`
1617
+ * happily validate an unrelated shape against employee columns.
1006
1618
  */
1007
1619
  validateRow(row, columns) {
1008
1620
  const errors = {};
1009
- columns.forEach(col => {
1010
- // Only check columns with validationRule
1011
- if (!col.validationRules || col.validationRules.length === 0)
1012
- return;
1013
- const rules = col.validationRules;
1014
- for (const rule of rules) {
1015
- const error = rule(row[col.field], row);
1621
+ for (const column of columns) {
1622
+ // A column can only contribute an error if it has both rules and a field
1623
+ // to key them under.
1624
+ if (!column.field || !column.validationRules?.length)
1625
+ continue;
1626
+ for (const rule of column.validationRules) {
1627
+ const error = rule(row[column.field], row);
1016
1628
  if (error) {
1017
- errors[col.field] = error;
1018
- break; // stop at first error for this column
1629
+ errors[column.field] = error;
1630
+ break; // first failure per column wins
1019
1631
  }
1020
1632
  }
1021
- });
1633
+ }
1022
1634
  return errors;
1023
1635
  }
1024
1636
  /**
1025
- * Validate all rows and return both errors and a flag indicating if there are any errors
1637
+ * Run the column validators over a whole dataset.
1638
+ *
1639
+ * Delegates to `validateRow` so the two can never disagree about what counts
1640
+ * as invalid — they previously carried separate copies of the same loop.
1026
1641
  */
1027
1642
  validateAllRows(rowData, columns) {
1028
- const allErrors = rowData.map(row => {
1029
- const errors = {};
1030
- columns.forEach(col => {
1031
- if (!col.validationRules || col.validationRules.length === 0)
1032
- return;
1033
- for (const rule of col.validationRules) {
1034
- const error = rule(row[col.field], row);
1035
- if (error) {
1036
- errors[col.field] = error;
1037
- break;
1038
- }
1039
- }
1040
- });
1041
- return errors;
1042
- });
1043
- const hasErrors = allErrors.some(err => Object.keys(err).length > 0);
1643
+ const allErrors = rowData.map(row => this.validateRow(row, columns));
1644
+ const hasErrors = allErrors.some(errors => Object.keys(errors).length > 0);
1044
1645
  return { errors: allErrors, valid: !hasErrors, invalid: hasErrors };
1045
1646
  }
1046
- /** Traverse all nodes */
1647
+ /**
1648
+ * Traverse every node, depth-first, once each.
1649
+ *
1650
+ * `rowNodes` is already the flattened tree — `buildRowNodes` pushes every
1651
+ * node into it as it descends — so walking it is the whole traversal. The
1652
+ * previous implementation walked that flat list *and* recursed into
1653
+ * `children`, visiting every nested node twice and every depth-3 node three
1654
+ * times. `index` is the node's position in the flattened order, matching
1655
+ * `node.rowIndex`, rather than its position among its siblings.
1656
+ */
1047
1657
  forEachNode(callback) {
1048
- const visit = (nodes) => {
1049
- nodes.forEach((node, index) => {
1050
- callback(node, index);
1051
- if (node?.children?.length)
1052
- visit(node.children);
1053
- });
1054
- };
1055
- visit(this.rowNodes);
1658
+ this._rowNodes.forEach((node, index) => callback(node, index));
1056
1659
  }
1057
- /** Traverse only leaf nodes */
1660
+ /** Traverse only leaf nodes, once each, in flattened order. */
1058
1661
  forEachLeafNode(callback) {
1059
1662
  let leafIndex = 0;
1060
- const visit = (nodes) => {
1061
- nodes.forEach(node => {
1062
- if (node.leaf) {
1063
- callback(node, leafIndex++);
1064
- }
1065
- else if (node.group && node?.children?.length) {
1066
- visit(node.children);
1067
- }
1068
- });
1069
- };
1070
- visit(this.rowNodes);
1663
+ for (const node of this._rowNodes) {
1664
+ if (node.leaf)
1665
+ callback(node, leafIndex++);
1666
+ }
1071
1667
  }
1072
1668
  getRowNodeById(rowId) {
1073
1669
  return this._originalRowNodes.get(rowId);
@@ -1081,8 +1677,25 @@ class OneGridApi {
1081
1677
  setGroupDefaultExpanded(defaultExpanded) {
1082
1678
  this.groupDefaultExpanded = defaultExpanded;
1083
1679
  }
1680
+ /**
1681
+ * Expansion state lives in two places, and they answer different questions.
1682
+ *
1683
+ * `OneGridExpansionService` records only *deliberate* changes — a click, or
1684
+ * one of the methods below. It is an override layer: `buildRowNodes` consults
1685
+ * it first and falls back to `groupDefaultExpanded` for every row the user
1686
+ * has not touched. That is what lets a later `groupDefaultExpanded` change
1687
+ * take effect, and what lets a collapse survive a rebuild.
1688
+ *
1689
+ * `node.expanded` is the resolved truth for a row, defaults included. Public
1690
+ * reads report that; the override sets stay internal.
1691
+ *
1692
+ * The invariant the setters below maintain: `expanded` is only ever true for
1693
+ * a node that can expand.
1694
+ */
1084
1695
  toggleExpandAll(expanded) {
1085
1696
  this.forEachNode(node => {
1697
+ if (!node.expandable)
1698
+ return;
1086
1699
  node.setExpanded(expanded);
1087
1700
  this.expansionService.toggleExpanded(node.rowId, expanded);
1088
1701
  });
@@ -1090,7 +1703,7 @@ class OneGridApi {
1090
1703
  }
1091
1704
  setExpandedById(rowId, expanded) {
1092
1705
  const node = this._originalRowNodes.get(rowId);
1093
- if (node) {
1706
+ if (node?.expandable) {
1094
1707
  node.setExpanded(expanded);
1095
1708
  this.expansionService.toggleExpanded(rowId, expanded);
1096
1709
  }
@@ -1100,7 +1713,7 @@ class OneGridApi {
1100
1713
  if (rowIds && rowIds.length > 0) {
1101
1714
  for (let rowId of rowIds) {
1102
1715
  const node = this._originalRowNodes.get(rowId);
1103
- if (node) {
1716
+ if (node?.expandable) {
1104
1717
  node.setExpanded(expanded);
1105
1718
  this.expansionService.toggleExpanded(rowId, expanded);
1106
1719
  }
@@ -1108,20 +1721,32 @@ class OneGridApi {
1108
1721
  this.refreshVisibleNodes();
1109
1722
  }
1110
1723
  }
1724
+ /**
1725
+ * Every currently expanded row, in tree order.
1726
+ *
1727
+ * Derived from the nodes rather than read off the override record, so rows
1728
+ * opened by `groupDefaultExpanded` are included — previously this returned
1729
+ * only rows someone had explicitly toggled, and came back empty on first load
1730
+ * while the first level was visibly open.
1731
+ */
1111
1732
  getExpandedByIds() {
1112
- return Array.from(this.expansionService.getExpandedIds());
1733
+ return this._rowNodes
1734
+ .filter(node => node.expandable && node.expanded)
1735
+ .map(node => node.rowId);
1113
1736
  }
1114
1737
  /**
1115
1738
  * Master Detail Feature
1116
1739
  */
1117
1740
  setDetailRowData(node, detailData) {
1741
+ if (this._destroyed)
1742
+ return;
1118
1743
  const rowNode = this.getRowNodeById(node.rowId);
1119
1744
  if (rowNode) {
1120
1745
  rowNode.setDetailRowData(detailData);
1121
1746
  }
1122
1747
  }
1123
1748
  setGetDetailRowData(getDetailRowData) {
1124
- this.getDetailRowData = getDetailRowData;
1749
+ this._getDetailRowData = getDetailRowData;
1125
1750
  }
1126
1751
  /**
1127
1752
  * Columns State
@@ -1130,15 +1755,17 @@ class OneGridApi {
1130
1755
  return this.columnService.getColumnState();
1131
1756
  }
1132
1757
  setColumnDefs(columnDefs) {
1758
+ if (this._destroyed)
1759
+ return;
1133
1760
  this.columnService.setColumnDefs(columnDefs);
1134
1761
  const columnState = this.columnService.getColumnState();
1135
- this.cellService.applyColumnDefs(columnState.columns, this.context);
1762
+ this.cellService.applyColumnDefs(columnState.columns, this._context);
1136
1763
  this.eventService.dispatchEvent('columnChanged', columnState);
1137
1764
  }
1138
1765
  updateColumn(id, changes) {
1139
1766
  this.columnService.updateColumnDef(id, changes);
1140
1767
  const columnState = this.columnService.getColumnState();
1141
- this.cellService.applyColumnDefs(columnState.columns, this.context);
1768
+ this.cellService.applyColumnDefs(columnState.columns, this._context);
1142
1769
  this.eventService.dispatchEvent('columnChanged', columnState);
1143
1770
  }
1144
1771
  reorderColumnDefs(fromIndex, toIndex) {
@@ -1160,11 +1787,12 @@ class OneGridApi {
1160
1787
  collect(node);
1161
1788
  this.setSelectedByIds(Array.from(rowIds), selected);
1162
1789
  }
1790
+ /**
1791
+ * @deprecated Identical to `updateCellStates`. Kept as an alias so existing
1792
+ * callers keep working; prefer `updateCellStates`, which says what it does.
1793
+ */
1163
1794
  updateNodeStates(node) {
1164
- const rowNode = this.getRowNodeById(node.rowId);
1165
- if (rowNode) {
1166
- rowNode.cellStates = this.cellService.generateCellStates(rowNode);
1167
- }
1795
+ this.updateCellStates(node);
1168
1796
  }
1169
1797
  updateCellState(node, column) {
1170
1798
  const rowNode = this.getRowNodeById(node.rowId);
@@ -1172,34 +1800,42 @@ class OneGridApi {
1172
1800
  rowNode.cellStates[column.field] = this.cellService.generateCellState(rowNode, column);
1173
1801
  }
1174
1802
  }
1803
+ /**
1804
+ * Recompute every cell of one row and repaint it.
1805
+ *
1806
+ * The repaint is the point: this is what a caller reaches for after mutating
1807
+ * a row in place, and it used to regenerate the state and stop there. The
1808
+ * body renders with OnPush, so nothing marked it dirty and the new state sat
1809
+ * unread until some unrelated event happened to trigger change detection —
1810
+ * which is why callers ended up nudging the grid with a synthetic DOM event.
1811
+ */
1175
1812
  updateCellStates(node) {
1813
+ if (this._destroyed)
1814
+ return;
1176
1815
  const rowNode = this.getRowNodeById(node.rowId);
1177
- if (rowNode) {
1178
- rowNode.cellStates = this.cellService.generateCellStates(rowNode);
1179
- }
1816
+ if (!rowNode)
1817
+ return;
1818
+ rowNode.cellStates = this.cellService.generateCellStates(rowNode);
1819
+ this.eventService.dispatchEvent('cellStateChanged', { data: rowNode.data, node: rowNode });
1180
1820
  }
1181
1821
  /**
1182
1822
  * Handler that Reacts to Detail Changes
1183
1823
  */
1824
+ /**
1825
+ * Keep a master row's reserved height in step with its detail rows.
1826
+ *
1827
+ * Two listeners used to be registered here and thrown away: an empty
1828
+ * `rowDataChanged` handler whose unsubscribe was never kept, and an
1829
+ * `expandedChanged` handler whose body was entirely commented out. Both are
1830
+ * gone — a listener with no behaviour is a leak with no upside.
1831
+ */
1184
1832
  subscribeToDetailChanges(node) {
1185
- const unsubRowData = node.on('rowDataChanged', ({ node, data, previousData }) => {
1186
- });
1187
- // When detail data arrives
1188
- const unsubDetail = node.on('detailRowDataChanged', ({ node, detailRowData, previousDetailRowData }) => {
1189
- if (this.apiType === 'master') {
1190
- this._resolvedDetailRowHeight(node);
1833
+ const unsubscribeDetail = node.on('detailRowDataChanged', ({ node }) => {
1834
+ if (this._apiType === 'master') {
1835
+ this.resolveDetailRowHeight(node);
1191
1836
  }
1192
1837
  });
1193
- // When row expands
1194
- const unsubExpand = node.on('expandedChanged', ({ node, expanded }) => {
1195
- // if (!expanded) return;
1196
- // // Only fetch if detail data not already loaded
1197
- // if (!node.detailRowData) {
1198
- // this.getDetailRowData({ data: node.data, node, successCallback: (detailRowData) => node.setDetailRowData(detailRowData) });
1199
- // }
1200
- });
1201
- // // Cleanup when node is removed from grid
1202
- this.registerNodeCleanup(node.rowId, [unsubDetail, unsubExpand]);
1838
+ this.registerNodeCleanup(node.rowId, [unsubscribeDetail]);
1203
1839
  }
1204
1840
  registerNodeCleanup(rowId, unsubs) {
1205
1841
  // Merge with any existing cleanups for this rowId
@@ -1211,7 +1847,14 @@ class OneGridApi {
1211
1847
  cleanups?.forEach(fn => fn());
1212
1848
  this._nodeCleanupMap.delete(rowId);
1213
1849
  }
1214
- // Call this when rows are removed
1850
+ /**
1851
+ * Drop nodes that are no longer in the data, releasing what they hold.
1852
+ *
1853
+ * This existed but was never called: `setRowData` and `updateRowData` each
1854
+ * deleted straight out of `_originalRowNodes`, so a removed node kept its
1855
+ * listeners and its entry in `_nodeCleanupMap` for the lifetime of the grid.
1856
+ * Both now come through here, which unsubscribes and clears the node first.
1857
+ */
1215
1858
  pruneRemovedNodes(validRowIds) {
1216
1859
  for (const [rowId, node] of this._originalRowNodes) {
1217
1860
  if (!validRowIds.has(rowId)) {
@@ -1222,58 +1865,427 @@ class OneGridApi {
1222
1865
  }
1223
1866
  }
1224
1867
  /**
1225
- * Resolve Detail Row Height
1868
+ * Height a master row must reserve for its detail grid:
1869
+ * rowHeight x clamp(rows, minRows, maxRows) + headerHeight.
1226
1870
  */
1227
- _resolvedDetailRowHeight(node) {
1228
- const minRows = this.detailCellRendererParams.detailGridOptions.detailRowHeightOptions.minRows;
1229
- const maxRows = this.detailCellRendererParams.detailGridOptions.detailRowHeightOptions.maxRows;
1871
+ resolveDetailRowHeight(node) {
1872
+ const heightOptions = this.detailCellRendererParams?.detailGridOptions?.detailRowHeightOptions;
1873
+ // Reachable whenever masterDetail is on but detailCellRendererParams is
1874
+ // incomplete; without the guard this threw from inside an event listener.
1875
+ if (!heightOptions)
1876
+ return;
1877
+ const minRows = heightOptions.minRows;
1878
+ const maxRows = heightOptions.maxRows;
1230
1879
  const minHeight = (this.rowHeight * minRows) + this.headerRowHeight;
1231
1880
  const maxHeight = (this.rowHeight * maxRows) + this.headerRowHeight;
1232
1881
  const rows = node.detailRowData && node.detailRowData.length ? node.detailRowData.length : 0;
1233
1882
  node.detailRowHeight = rows <= minRows ? minHeight : rows >= maxRows ? maxHeight : (this.rowHeight * rows) + this.headerRowHeight;
1234
1883
  }
1235
1884
  /**
1236
- * On Destroy
1885
+ * Release everything the grid holds. Called by `OneGridComponent.ngOnDestroy`.
1886
+ *
1887
+ * The services are deliberately *not* nulled out any more. They are injected,
1888
+ * Angular owns their lifetime, and nulling them only guaranteed a
1889
+ * `TypeError` for any callback that arrived after teardown — a detail-row
1890
+ * fetch resolving late, or the body's deferred update. `_destroyed` makes
1891
+ * those calls no-ops instead, which is what a torn-down object should do.
1237
1892
  */
1238
1893
  destroy() {
1239
1894
  if (this._destroyed)
1240
1895
  return;
1241
1896
  this._destroyed = true;
1242
- this.rowNodes.forEach(node => {
1243
- node.children = [];
1244
- node.parent = null;
1245
- });
1246
- this.rowNodes = [];
1897
+ // Unsubscribe every per-node listener before dropping the nodes.
1898
+ for (const rowId of Array.from(this._nodeCleanupMap.keys())) {
1899
+ this.cleanupNode(rowId);
1900
+ }
1901
+ // `destroy` drops the tree links and the listeners; the API no longer has
1902
+ // to reach in and unpick each node itself.
1903
+ this._originalRowNodes.forEach(node => node.destroy());
1904
+ this._rowNodes.forEach(node => node.destroy());
1905
+ for (const side of ['top', 'bottom']) {
1906
+ this._pinnedRowNodes[side].forEach(node => node.destroy());
1907
+ this._pinnedRowNodes[side] = [];
1908
+ }
1909
+ this._rowNodes = [];
1910
+ this._visibleRowNodes = [];
1247
1911
  this._originalRowNodes.clear();
1248
- this._currentIndex = null;
1249
- // Clear services
1250
- if (this.selectionService) {
1251
- this.selectionService.deselectAll();
1912
+ this._nodeCleanupMap.clear();
1913
+ this._currentIndex = 0;
1914
+ // Release the back-reference the cell service holds, so a torn-down api is
1915
+ // not kept alive by the service that outlives this call.
1916
+ this.cellService.setGridApi(null);
1917
+ this.selectionService.deselectAll();
1918
+ this.expansionService.clear();
1919
+ this._activeFilters = [];
1920
+ this._quickFilterValue = null;
1921
+ this._sortModel = [];
1922
+ this._context = null;
1923
+ this.masterDetail = false;
1924
+ this.treeData = false;
1925
+ this.treeDataChildrenField = null;
1926
+ this.groupDefaultExpanded = 0;
1927
+ this.detailCellRendererParams = null;
1928
+ this.detailRendererComponent = null;
1929
+ this._getDetailRowData = null;
1930
+ }
1931
+ /** Whether `destroy` has run. Late callbacks check this before doing work. */
1932
+ get destroyed() {
1933
+ return this._destroyed;
1934
+ }
1935
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi, deps: [{ token: OgColumnService }, { token: OneGridRowService }, { token: OneGridCellService }, { token: OneGridEventService }, { token: OneGridSelectionService }, { token: OneGridExpansionService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1936
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi }); }
1937
+ }
1938
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi, decorators: [{
1939
+ type: Injectable
1940
+ }], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridRowService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridSelectionService }, { type: OneGridExpansionService }] });
1941
+
1942
+ /** Pointer distance before a press becomes a drag. */
1943
+ const DRAG_THRESHOLD_PX = 4;
1944
+ /** How close to an edge auto-scroll kicks in, and how fast it goes. */
1945
+ const EDGE_ZONE_PX = 36;
1946
+ const EDGE_SPEED_PX = 12;
1947
+ /**
1948
+ * Owns one row-drag gesture at a time.
1949
+ *
1950
+ * Built on pointer events rather than HTML5 drag-and-drop or the CDK's
1951
+ * drag-drop:
1952
+ *
1953
+ * - HTML5 DnD does not fire for touch at all, and its drag image cannot be
1954
+ * styled to match the grid.
1955
+ * - `cdkDropList` assumes every item it manages is in the DOM. Inside a virtual
1956
+ * scroll viewport rows unmount as they leave the buffer, so the list it
1957
+ * thinks it is sorting is only ever the visible window.
1958
+ * - Pointer events cover mouse, touch and pen in one path, and pointer capture
1959
+ * keeps the gesture alive when the finger leaves the row it started on.
1960
+ *
1961
+ * Because rows are a fixed `itemSize`, the drop target is arithmetic on the
1962
+ * pointer's Y rather than a hit test against row elements — which is also why
1963
+ * it stays correct for rows that are scrolled out and not rendered.
1964
+ */
1965
+ class OneGridRowDragService {
1966
+ constructor() {
1967
+ this.zone = inject(NgZone);
1968
+ /** Live drag, for the body to render the handle state and the indicator. */
1969
+ this.state$ = new BehaviorSubject(null);
1970
+ /** Screen-reader narration for the keyboard path. */
1971
+ this.announcement$ = new BehaviorSubject('');
1972
+ this.context = null;
1973
+ this.pointerId = null;
1974
+ this.handle = null;
1975
+ this.startY = 0;
1976
+ this.armed = false;
1977
+ this.lastClientY = 0;
1978
+ this.scrollFrame = null;
1979
+ /** A move already applied during this drag, so a cancel can undo it. */
1980
+ this.appliedFrom = null;
1981
+ this.handlePointerMove = (event) => {
1982
+ if (this.pointerId === null || event.pointerId !== this.pointerId)
1983
+ return;
1984
+ if (!this.armed) {
1985
+ if (Math.abs(event.clientY - this.startY) < DRAG_THRESHOLD_PX)
1986
+ return;
1987
+ this.armed = true;
1988
+ this.publish({ rowId: this.nodeAt(this.appliedFrom)?.rowId ?? '', fromIndex: this.appliedFrom, insertAt: this.appliedFrom, source: 'pointer' });
1989
+ }
1990
+ // Held so the auto-scroll loop can recompute the target as the viewport
1991
+ // moves under a stationary finger.
1992
+ this.lastClientY = event.clientY;
1993
+ // Touch scrolling would otherwise pan the viewport out from under us.
1994
+ event.preventDefault();
1995
+ this.updateFromPointer();
1996
+ this.updateAutoScroll();
1997
+ };
1998
+ this.handlePointerUp = (event) => {
1999
+ if (this.pointerId === null || event.pointerId !== this.pointerId)
2000
+ return;
2001
+ const dragged = this.armed;
2002
+ this.teardownPointer();
2003
+ if (!dragged) {
2004
+ this.reset();
2005
+ return;
2006
+ }
2007
+ this.commit('pointer');
2008
+ };
2009
+ this.handlePointerCancel = () => {
2010
+ this.teardownPointer();
2011
+ this.cancel();
2012
+ };
2013
+ /** Escape aborts a pointer drag, the same as it does a keyboard one. */
2014
+ this.handlePointerKeydown = (event) => {
2015
+ if (event.key !== 'Escape')
2016
+ return;
2017
+ event.preventDefault();
2018
+ this.teardownPointer();
2019
+ this.cancel();
2020
+ };
2021
+ }
2022
+ attach(context) {
2023
+ this.context = context;
2024
+ }
2025
+ get state() {
2026
+ return this.state$.value;
2027
+ }
2028
+ get dragging() {
2029
+ return this.state$.value !== null;
2030
+ }
2031
+ // -------------------------------------------------------------------------
2032
+ // Pointer
2033
+ // -------------------------------------------------------------------------
2034
+ /**
2035
+ * Begin a pointer drag. Nothing visible happens until the pointer has moved
2036
+ * {@link DRAG_THRESHOLD_PX}, so a click on the handle — which is also the
2037
+ * keyboard affordance — is not swallowed by a zero-distance drag.
2038
+ */
2039
+ startPointerDrag(event, node, handlers = {}) {
2040
+ if (!this.context || this.dragging || this.pointerId !== null)
2041
+ return;
2042
+ // Secondary buttons open context menus; they are not a drag.
2043
+ if (event.button !== 0)
2044
+ return;
2045
+ const index = this.indexOf(node);
2046
+ if (index === -1)
2047
+ return;
2048
+ this.pointerId = event.pointerId;
2049
+ this.handle = event.target;
2050
+ this.startY = event.clientY;
2051
+ this.lastClientY = event.clientY;
2052
+ this.armed = false;
2053
+ this.appliedFrom = index;
2054
+ this.onEnd = handlers.onEnd;
2055
+ this.handle.setPointerCapture?.(event.pointerId);
2056
+ // Outside Angular: pointermove fires at the display's refresh rate and
2057
+ // every one of them would otherwise run change detection over the grid.
2058
+ // The few that change the drop target re-enter through `publish`.
2059
+ this.zone.runOutsideAngular(() => {
2060
+ document.addEventListener('pointermove', this.handlePointerMove, { passive: false });
2061
+ document.addEventListener('pointerup', this.handlePointerUp);
2062
+ document.addEventListener('pointercancel', this.handlePointerCancel);
2063
+ document.addEventListener('keydown', this.handlePointerKeydown);
2064
+ });
2065
+ }
2066
+ /**
2067
+ * Turn the pointer's Y into an insertion boundary.
2068
+ *
2069
+ * Boundaries, not rows: the value is where the row would be *inserted*, so
2070
+ * 0 is above the first row and `length` is below the last. Rounding rather
2071
+ * than flooring puts the split at the middle of each row, which is what
2072
+ * makes the indicator flip when you cross the halfway point.
2073
+ */
2074
+ updateFromPointer() {
2075
+ const current = this.state$.value;
2076
+ if (!this.context || !current)
2077
+ return;
2078
+ const rect = this.context.viewport.getBoundingClientRect();
2079
+ const offset = this.lastClientY - rect.top + this.context.viewport.scrollTop;
2080
+ const insertAt = this.clamp(Math.round(offset / this.context.itemSize), 0, this.context.nodes().length);
2081
+ if (insertAt === current.insertAt)
2082
+ return;
2083
+ this.publish({ ...current, insertAt });
2084
+ }
2085
+ /**
2086
+ * Scroll when the pointer sits near an edge, so rows outside the viewport
2087
+ * are reachable without letting go. Runs on rAF while the pointer is parked
2088
+ * in the zone, and re-derives the target each frame because the content
2089
+ * moves while the pointer does not.
2090
+ */
2091
+ updateAutoScroll() {
2092
+ if (!this.context)
2093
+ return;
2094
+ const rect = this.context.viewport.getBoundingClientRect();
2095
+ const distanceToTop = this.lastClientY - rect.top;
2096
+ const distanceToBottom = rect.bottom - this.lastClientY;
2097
+ let delta = 0;
2098
+ if (distanceToTop < EDGE_ZONE_PX)
2099
+ delta = -EDGE_SPEED_PX;
2100
+ else if (distanceToBottom < EDGE_ZONE_PX)
2101
+ delta = EDGE_SPEED_PX;
2102
+ if (delta === 0) {
2103
+ this.stopAutoScroll();
2104
+ return;
2105
+ }
2106
+ if (this.scrollFrame !== null)
2107
+ return;
2108
+ const step = () => {
2109
+ if (!this.context || !this.dragging) {
2110
+ this.stopAutoScroll();
2111
+ return;
2112
+ }
2113
+ const viewport = this.context.viewport;
2114
+ const before = viewport.scrollTop;
2115
+ viewport.scrollTop = before + delta;
2116
+ // Hit the end — nothing more to reveal, so stop burning frames.
2117
+ if (viewport.scrollTop === before) {
2118
+ this.stopAutoScroll();
2119
+ return;
2120
+ }
2121
+ this.updateFromPointer();
2122
+ this.scrollFrame = requestAnimationFrame(step);
2123
+ };
2124
+ this.scrollFrame = requestAnimationFrame(step);
2125
+ }
2126
+ stopAutoScroll() {
2127
+ if (this.scrollFrame === null)
2128
+ return;
2129
+ cancelAnimationFrame(this.scrollFrame);
2130
+ this.scrollFrame = null;
2131
+ }
2132
+ teardownPointer() {
2133
+ this.stopAutoScroll();
2134
+ if (this.handle && this.pointerId !== null) {
2135
+ this.handle.releasePointerCapture?.(this.pointerId);
2136
+ }
2137
+ document.removeEventListener('pointermove', this.handlePointerMove);
2138
+ document.removeEventListener('pointerup', this.handlePointerUp);
2139
+ document.removeEventListener('pointercancel', this.handlePointerCancel);
2140
+ document.removeEventListener('keydown', this.handlePointerKeydown);
2141
+ this.pointerId = null;
2142
+ this.handle = null;
2143
+ this.armed = false;
2144
+ }
2145
+ // -------------------------------------------------------------------------
2146
+ // Keyboard
2147
+ //
2148
+ // WCAG 2.5.7 Dragging Movements: anything achievable by dragging needs a
2149
+ // single-pointer, non-dragging alternative. Arrow-key moves are that, and
2150
+ // they are also simply how a keyboard user reorders a list.
2151
+ // -------------------------------------------------------------------------
2152
+ /** Space/Enter on the handle: pick the row up, or put it down. */
2153
+ toggleKeyboardDrag(node, handlers = {}) {
2154
+ if (!this.context)
2155
+ return;
2156
+ if (this.state$.value?.source === 'keyboard') {
2157
+ this.commit('keyboard');
2158
+ return;
2159
+ }
2160
+ if (this.dragging)
2161
+ return;
2162
+ const index = this.indexOf(node);
2163
+ if (index === -1)
2164
+ return;
2165
+ this.appliedFrom = index;
2166
+ this.onEnd = handlers.onEnd;
2167
+ this.publish({ rowId: node.rowId, fromIndex: index, insertAt: index, source: 'keyboard' });
2168
+ this.announce(`${this.describe(node, index)} grabbed. Use the arrow keys to move it, space to drop, escape to cancel.`);
2169
+ }
2170
+ /**
2171
+ * Arrow keys move the row a position at a time, applying as they go.
2172
+ *
2173
+ * Applied rather than previewed because the row has to be seen to move: a
2174
+ * keyboard user gets no drag image, and a preview line alone tells someone
2175
+ * relying on a screen reader nothing about where the row now sits. Escape
2176
+ * still restores the original position.
2177
+ */
2178
+ moveKeyboard(delta) {
2179
+ const current = this.state$.value;
2180
+ if (!this.context || current?.source !== 'keyboard')
2181
+ return false;
2182
+ const nodes = this.context.nodes();
2183
+ const from = current.insertAt;
2184
+ const to = this.clamp(from + delta, 0, nodes.length - 1);
2185
+ if (to === from) {
2186
+ this.announce(to === 0 ? 'Already at the top.' : 'Already at the bottom.');
2187
+ return true;
2188
+ }
2189
+ if (!this.context.move(from, to))
2190
+ return true;
2191
+ this.publish({ ...current, insertAt: to });
2192
+ const node = this.nodeAt(to);
2193
+ this.announce(node ? `Moved to position ${to + 1} of ${nodes.length}.` : '');
2194
+ return true;
2195
+ }
2196
+ // -------------------------------------------------------------------------
2197
+ // Commit / cancel
2198
+ // -------------------------------------------------------------------------
2199
+ commit(source) {
2200
+ const current = this.state$.value;
2201
+ if (!this.context || !current) {
2202
+ this.reset();
2203
+ return;
2204
+ }
2205
+ const nodes = this.context.nodes();
2206
+ // Pointer drags apply once, at the end. A boundary index becomes a row
2207
+ // index by dropping one when the row travelled downwards, since lifting
2208
+ // it out first shifts everything below it up.
2209
+ if (source === 'pointer') {
2210
+ const from = current.fromIndex;
2211
+ const to = this.clamp(current.insertAt > from ? current.insertAt - 1 : current.insertAt, 0, nodes.length - 1);
2212
+ const node = this.nodeAt(from);
2213
+ if (node && to !== from && this.context.move(from, to)) {
2214
+ this.finish({ node, fromIndex: from, toIndex: to, source });
2215
+ return;
2216
+ }
2217
+ this.reset();
2218
+ return;
1252
2219
  }
1253
- this.selectionService = null;
1254
- if (this.expansionService) {
1255
- this.expansionService.clear();
2220
+ // Keyboard moves were applied as they happened; this only confirms.
2221
+ const node = this.nodeAt(current.insertAt);
2222
+ const moved = node && current.insertAt !== current.fromIndex;
2223
+ this.announce(moved ? `Dropped at position ${current.insertAt + 1} of ${nodes.length}.` : 'Cancelled. Row returned to its original position.');
2224
+ if (moved) {
2225
+ this.finish({ node: node, fromIndex: current.fromIndex, toIndex: current.insertAt, source });
2226
+ return;
2227
+ }
2228
+ this.reset();
2229
+ }
2230
+ /** Abort, restoring anything a keyboard drag already applied. */
2231
+ cancel() {
2232
+ const current = this.state$.value;
2233
+ if (current?.source === 'keyboard' && this.context && current.insertAt !== current.fromIndex) {
2234
+ this.context.move(current.insertAt, current.fromIndex);
2235
+ this.announce('Cancelled. Row returned to its original position.');
2236
+ }
2237
+ else if (current) {
2238
+ this.announce('Cancelled.');
2239
+ }
2240
+ this.reset();
2241
+ }
2242
+ finish(event) {
2243
+ const done = this.onEnd;
2244
+ this.reset();
2245
+ this.zone.run(() => done?.(event));
2246
+ }
2247
+ reset() {
2248
+ this.stopAutoScroll();
2249
+ this.appliedFrom = null;
2250
+ this.onEnd = undefined;
2251
+ if (this.state$.value !== null) {
2252
+ this.zone.run(() => this.state$.next(null));
1256
2253
  }
1257
- this.expansionService = null;
1258
- this.eventService = null;
1259
- this.rowService = null;
1260
- this.cellService = null;
1261
- // Clear filters, sorting, context
1262
- this._activeFilters = [];
1263
- this._quickFilterValue = null;
1264
- this.sortModel = [];
1265
- // Clear master/detail/treeData
1266
- this.masterDetail = false;
1267
- this.treeData = false;
1268
- this.treeDataChildrenField = '';
1269
- this.groupDefaultExpanded = 0;
1270
2254
  }
1271
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi, deps: [{ token: OgColumnService }, { token: OneGridRowService }, { token: OneGridCellService }, { token: OneGridEventService }, { token: OneGridSelectionService }, { token: OneGridExpansionService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1272
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi }); }
2255
+ // -------------------------------------------------------------------------
2256
+ // Helpers
2257
+ // -------------------------------------------------------------------------
2258
+ publish(state) {
2259
+ this.zone.run(() => this.state$.next(state));
2260
+ }
2261
+ announce(message) {
2262
+ this.zone.run(() => this.announcement$.next(message));
2263
+ }
2264
+ describe(node, index) {
2265
+ return `Row ${index + 1} of ${this.context?.nodes().length ?? 0},`;
2266
+ }
2267
+ indexOf(node) {
2268
+ return this.context?.nodes().findIndex(candidate => candidate.rowId === node.rowId) ?? -1;
2269
+ }
2270
+ nodeAt(index) {
2271
+ return this.context?.nodes()[index] ?? null;
2272
+ }
2273
+ clamp(value, min, max) {
2274
+ return Math.min(Math.max(value, min), max);
2275
+ }
2276
+ ngOnDestroy() {
2277
+ this.teardownPointer();
2278
+ this.state$.next(null);
2279
+ this.state$.complete();
2280
+ this.announcement$.complete();
2281
+ this.context = null;
2282
+ }
2283
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2284
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService }); }
1273
2285
  }
1274
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi, decorators: [{
2286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService, decorators: [{
1275
2287
  type: Injectable
1276
- }], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridRowService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridSelectionService }, { type: OneGridExpansionService }] });
2288
+ }] });
1277
2289
 
1278
2290
  class OneGridTreeDataService {
1279
2291
  create(tree, parentId, newNode) {
@@ -1334,6 +2346,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1334
2346
 
1335
2347
  const OnePageSizeOption = [25, 50, 100, 250];
1336
2348
 
2349
+ function resolveRowHeight(params, getRowHeight, rowHeight = 26) {
2350
+ if (getRowHeight) {
2351
+ const result = getRowHeight(params);
2352
+ // fallback behavior like AG Grid
2353
+ if (result == null) {
2354
+ return rowHeight;
2355
+ }
2356
+ // guard against invalid values
2357
+ return Math.max(1, result);
2358
+ }
2359
+ return rowHeight;
2360
+ }
2361
+
1337
2362
  class HeaderCheckboxDisabledPipe {
1338
2363
  transform(rowNodes, selectionMode, allowedEdit) {
1339
2364
  return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
@@ -1384,6 +2409,8 @@ class OneGridCheckboxHeaderComponent {
1384
2409
  this.eventService = eventService;
1385
2410
  this.selectionService = selectionService;
1386
2411
  this.allowedEdit = false;
2412
+ /** Unsubscribe callbacks for the two grid listeners below. */
2413
+ this.teardown = [];
1387
2414
  this.state = 'unchecked';
1388
2415
  this.rowNodes = [];
1389
2416
  }
@@ -1392,19 +2419,26 @@ class OneGridCheckboxHeaderComponent {
1392
2419
  /**
1393
2420
  * Listen Row Node Selection Changed Event
1394
2421
  */
1395
- this.eventService.addEventListener('selectionChanged', (payload) => {
2422
+ this.teardown.push(this.eventService.addEventListener('selectionChanged', (payload) => {
1396
2423
  this.rowNodes = this.api.getAllRowNodes();
1397
2424
  const rowIds = this.rowNodes && this.rowNodes.length > 0 ? this.rowNodes.map((node) => node.rowId) : [];
1398
2425
  this.state = this.selectionService.getSelectAllState(rowIds);
1399
- });
2426
+ }));
1400
2427
  /**
1401
2428
  * Listen Row Data Changed Event
1402
2429
  */
1403
- this.eventService.addEventListener('rowDataChanged', (payload) => {
2430
+ this.teardown.push(this.eventService.addEventListener('rowDataChanged', (payload) => {
1404
2431
  this.rowNodes = this.api.getAllRowNodes();
1405
2432
  const rowIds = this.rowNodes && this.rowNodes.length > 0 ? this.rowNodes.map((node) => node.rowId) : [];
1406
2433
  this.state = this.selectionService.getSelectAllState(rowIds);
1407
- });
2434
+ }));
2435
+ }
2436
+ ngOnDestroy() {
2437
+ // Both listeners were registered and never released. The component-scoped
2438
+ // event service happened to clean up after them; relying on that left the
2439
+ // component's own lifecycle incomplete.
2440
+ this.teardown.forEach(unsubscribe => unsubscribe());
2441
+ this.teardown.length = 0;
1408
2442
  }
1409
2443
  /**
1410
2444
  * Toggle Selection Action
@@ -1435,6 +2469,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1435
2469
  }] } });
1436
2470
 
1437
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.
1438
2474
  transform(field, sortModel) {
1439
2475
  if (!sortModel || !field)
1440
2476
  return null;
@@ -1452,16 +2488,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1452
2488
  }] });
1453
2489
 
1454
2490
  class OneGridHeaderComponent {
2491
+ /**
2492
+ * Horizontal offset of the body, mirrored here so the header tracks it.
2493
+ *
2494
+ * Applied to the viewport's `scrollLeft` rather than as a transform on the
2495
+ * row: sticky positioning needs a real scrollport above it. The group row,
2496
+ * which is a sibling of that viewport, still reads this for its transform.
2497
+ */
2498
+ set scrollLeft(value) {
2499
+ this._scrollLeft = value ?? 0;
2500
+ this.applyScrollLeft();
2501
+ }
2502
+ get scrollLeft() {
2503
+ return this._scrollLeft;
2504
+ }
1455
2505
  constructor(eventService, cdr) {
1456
2506
  this.eventService = eventService;
1457
2507
  this.cdr = cdr;
1458
- this.scrollLeft = 0;
2508
+ this._scrollLeft = 0;
1459
2509
  this.allowedEdit = false;
1460
2510
  this.enableGroupColumnDefs = false;
1461
2511
  this.groupColumnDefs = [];
1462
2512
  this.columnDefs = [];
1463
2513
  this.sortModel = [];
1464
- this.destroy$ = new Subject();
1465
2514
  this.subscriptions = new Subscription();
1466
2515
  this.draggedIndex = null;
1467
2516
  // Track resize state
@@ -1503,6 +2552,16 @@ class OneGridHeaderComponent {
1503
2552
  // this.cdr.markForCheck();
1504
2553
  }));
1505
2554
  }
2555
+ ngAfterViewInit() {
2556
+ // The input setter can run before the view exists, so whatever arrived
2557
+ // first is applied once here.
2558
+ this.applyScrollLeft();
2559
+ }
2560
+ applyScrollLeft() {
2561
+ if (this.headerViewport) {
2562
+ this.headerViewport.nativeElement.scrollLeft = this._scrollLeft;
2563
+ }
2564
+ }
1506
2565
  onHeaderMenuOptionClick(columnDef) {
1507
2566
  }
1508
2567
  onHeaderMenuSortClick(colId) {
@@ -1528,7 +2587,12 @@ class OneGridHeaderComponent {
1528
2587
  this.resizing = true;
1529
2588
  this.resizeColumnIndex = columnIndex;
1530
2589
  this.startX = event.clientX;
1531
- this.startWidth = this.columnDefs[columnIndex].width;
2590
+ // Measure what is on screen rather than trusting `col.width`, which is
2591
+ // optional: a column sized purely by minWidth had `undefined` here, and
2592
+ // `Math.max(50, undefined + deltaX)` is NaN — the column collapsed on the
2593
+ // first mouse move and could not be recovered.
2594
+ const columnElement = event.target?.closest('.one-grid-header-column');
2595
+ this.startWidth = columnElement?.offsetWidth || resolveColumnWidth(this.columnDefs[columnIndex]);
1532
2596
  // Add document-level listeners for drag and release
1533
2597
  document.addEventListener('mousemove', this.onResizeMove);
1534
2598
  document.addEventListener('mouseup', this.onResizeEnd);
@@ -1561,19 +2625,27 @@ class OneGridHeaderComponent {
1561
2625
  event.preventDefault();
1562
2626
  this.draggedIndex = null;
1563
2627
  }
1564
- /**
1565
- * Clear All Subscriptions
1566
- */
1567
2628
  ngOnDestroy() {
1568
- this.destroy$.complete();
2629
+ // This completed an unused Subject and left the real subscription running.
2630
+ // It survived only because OneGridEventService is component-scoped and
2631
+ // tears its own listeners down — the header was relying on that by accident.
2632
+ this.subscriptions.unsubscribe();
2633
+ // A resize in flight owns two listeners on `document`. Without this they
2634
+ // outlive the component, and their closures keep it alive with them.
2635
+ if (this.resizing) {
2636
+ this.onResizeEnd();
2637
+ }
1569
2638
  }
1570
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 }); }
1571
- 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" }, 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 <div class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\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 [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col?.dragable\"\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?.resizable) {\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?.sortable) {\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.suppressHeaderMenuButton) {\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: i3.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" }] }); }
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" }] }); }
1572
2641
  }
1573
2642
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, decorators: [{
1574
2643
  type: Component,
1575
- 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 <div class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\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 [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col?.dragable\"\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?.resizable) {\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?.sortable) {\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.suppressHeaderMenuButton) {\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" }]
1576
- }], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }], propDecorators: { scrollLeft: [{
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: [{
2646
+ type: ViewChild,
2647
+ args: ['headerViewport', { static: true }]
2648
+ }], scrollLeft: [{
1577
2649
  type: Input
1578
2650
  }], allowedEdit: [{
1579
2651
  type: Input
@@ -1654,31 +2726,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1654
2726
  type: Output
1655
2727
  }] } });
1656
2728
 
1657
- class CellDisableResolverPipe {
1658
- transform(node, column, context, allowedEdit) {
1659
- if (!column || !node)
1660
- return false;
1661
- return resolveOneCellRendererParams(column.disable, {
1662
- node,
1663
- data: node.data,
1664
- columnDef: column,
1665
- rowIndex: node.rowIndex,
1666
- context
1667
- }) || !allowedEdit;
1668
- }
1669
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellDisableResolverPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
1670
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellDisableResolverPipe, name: "cellDisableResolver" }); }
1671
- }
1672
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellDisableResolverPipe, decorators: [{
1673
- type: Pipe,
1674
- args: [{
1675
- standalone: false,
1676
- name: 'cellDisableResolver'
1677
- }]
1678
- }] });
1679
-
1680
2729
  class OneGridRowActionComponent {
1681
- constructor() {
2730
+ constructor(cellService) {
2731
+ this.cellService = cellService;
1682
2732
  this.allowedEdit = false;
1683
2733
  this.masterDetail = false;
1684
2734
  this.masterNode = null;
@@ -1701,6 +2751,51 @@ class OneGridRowActionComponent {
1701
2751
  this.onRowOption = new EventEmitter();
1702
2752
  this.onRowLanguage = new EventEmitter();
1703
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
+ };
2777
+ }
2778
+ /**
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.
2785
+ */
2786
+ get hideActionButton() {
2787
+ return this.cellService.hideActionButtonFn(this.column, this.node);
2788
+ }
2789
+ /**
2790
+ * Whether this column's action buttons are disabled for this row.
2791
+ *
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.
2796
+ */
2797
+ get actionDisabled() {
2798
+ return this.cellService.disableFn(this.column, this.node) || !this.allowedEdit;
1704
2799
  }
1705
2800
  ngOnInit() {
1706
2801
  this.detailActiveExcludeLevels = new Set(this.column.cellRendererParams?.detailActiveExcludeLevels ?? []);
@@ -1811,13 +2906,13 @@ class OneGridRowActionComponent {
1811
2906
  }
1812
2907
  this.onRowCellOption.emit(data);
1813
2908
  }
1814
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1815
- 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\n class=\"me-2\"\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 </a>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!column?.hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\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))) && (!column?.hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"!allowedEdit\"\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 (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 (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 (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 (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 && (!column?.hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"!allowedEdit\"\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 (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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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"] }, { kind: "pipe", type: CellDisableResolverPipe, name: "cellDisableResolver" }] }); }
2909
+ 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"] }] }); }
1816
2911
  }
1817
2912
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, decorators: [{
1818
2913
  type: Component,
1819
- 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\n class=\"me-2\"\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 </a>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!column?.hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\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))) && (!column?.hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"!allowedEdit\"\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 (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 (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 (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 (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 && (!column?.hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"!allowedEdit\"\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 (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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\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]=\"node | cellDisableResolver:column:context:allowedEdit\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}" }]
1820
- }], propDecorators: { column: [{
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}" }]
2915
+ }], ctorParameters: () => [{ type: OneGridCellService }], propDecorators: { column: [{
1821
2916
  type: Input,
1822
2917
  args: [{ required: true }]
1823
2918
  }], node: [{
@@ -1871,18 +2966,21 @@ class OneGridCellGroupRendererComponent {
1871
2966
  this.masterDetail = false;
1872
2967
  this.expansionChanged = new EventEmitter();
1873
2968
  }
1874
- invokeChangedClick() {
1875
- // if (this.masterDetail && !this.node.expanded) {
1876
- // this.detailCellRendererParams.getDetailRowData({ data: this.node.data, node: this.node, successCallback: (rows) => { this.node.setDetailRowData(rows) }});
1877
- // }
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();
1878
2976
  this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
1879
2977
  }
1880
2978
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1881
- 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\" (click)=\"invokeChangedClick()\">\n @if (node.children?.length > 0 || masterDetail) {\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n }\n </span>\n}\n", styles: [""] }); }
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: [""] }); }
1882
2980
  }
1883
2981
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
1884
2982
  type: Component,
1885
- args: [{ standalone: false, selector: 'one-grid-cell-group-renderer', template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\" (click)=\"invokeChangedClick()\">\n @if (node.children?.length > 0 || masterDetail) {\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n }\n </span>\n}\n" }]
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" }]
1886
2984
  }], propDecorators: { node: [{
1887
2985
  type: Input
1888
2986
  }], masterDetail: [{
@@ -2000,8 +3098,15 @@ class OneCellRendererComponentDirective {
2000
3098
  return;
2001
3099
  const instance = this.compRef.instance;
2002
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.
2003
3106
  const newParams = { ...this.params };
2004
- newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
3107
+ if (newParams.value === undefined) {
3108
+ newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
3109
+ }
2005
3110
  instance.oneInit(newParams);
2006
3111
  }
2007
3112
  else {
@@ -2038,68 +3143,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2038
3143
  args: [{ required: true }]
2039
3144
  }] } });
2040
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
+ */
2041
3162
  class OneCellTextExpandDirective {
3163
+ static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
3164
+ static { this.HOVER_DELAY_MS = 800; }
2042
3165
  constructor(cell, renderer) {
2043
3166
  this.cell = cell;
2044
3167
  this.renderer = renderer;
2045
- this.overflowing = false;
2046
- this.lastText = null;
2047
- }
2048
- ngAfterViewInit() {
2049
- this.checkOverflow();
2050
- this.lastText = this.cell.nativeElement.textContent;
2051
- }
2052
- ngAfterViewChecked() {
2053
- const currentText = this.cell.nativeElement.textContent;
2054
- if (this.lastText !== currentText) {
2055
- this.lastText = currentText;
2056
- this.checkOverflow();
2057
- this.collapse();
2058
- }
2059
- }
2060
- ngOnDestroy() {
2061
- clearTimeout(this.timer);
2062
- this.collapse();
2063
- }
2064
- checkOverflow() {
2065
- const cell = this.cell.nativeElement;
2066
- this.overflowing = cell.scrollWidth > cell.clientWidth;
2067
- }
2068
- onResize() {
2069
- this.checkOverflow();
2070
3168
  }
2071
3169
  onMouseEnter() {
2072
- if (!this.overflowing)
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)
2073
3174
  return;
2074
- this.timer = setTimeout(() => this.expand(), 800);
3175
+ this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
2075
3176
  }
2076
3177
  onMouseLeave() {
2077
- clearTimeout(this.timer);
3178
+ this.collapse();
3179
+ }
3180
+ ngOnDestroy() {
2078
3181
  this.collapse();
2079
3182
  }
2080
3183
  expand() {
2081
- const cell = this.cell.nativeElement;
2082
- this.renderer.setStyle(cell, 'overflow', 'visible');
2083
- this.renderer.setStyle(cell, 'text-overflow', 'unset');
2084
- this.renderer.setStyle(cell, 'z-index', '1000');
2085
- this.renderer.setStyle(cell, 'background', 'var(--ins-card-bg)');
2086
- this.renderer.setStyle(cell, 'white-space', 'nowrap');
2087
- this.renderer.setStyle(cell, 'transition', 'width 0.3s ease');
2088
- this.renderer.setStyle(cell, 'padding', '4px 8px');
2089
- this.renderer.setStyle(cell, 'border-radius', '4px');
3184
+ this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
2090
3185
  }
2091
3186
  collapse() {
2092
- const cell = this.cell.nativeElement;
2093
- this.renderer.setStyle(cell, 'white-space', 'nowrap');
2094
- this.renderer.setStyle(cell, 'overflow', 'hidden');
2095
- this.renderer.setStyle(cell, 'text-overflow', 'ellipsis');
2096
- this.renderer.removeStyle(cell, 'z-index');
2097
- this.renderer.removeStyle(cell, 'background');
2098
- this.renderer.removeStyle(cell, 'padding');
2099
- this.renderer.removeStyle(cell, 'border-radius');
3187
+ clearTimeout(this.timer);
3188
+ this.timer = undefined;
3189
+ this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
2100
3190
  }
2101
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 }); }
2102
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]", host: { listeners: { "window:resize": "onResize()", "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 }); }
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 }); }
2103
3193
  }
2104
3194
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
2105
3195
  type: Directive,
@@ -2107,10 +3197,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2107
3197
  standalone: false,
2108
3198
  selector: '[oneCellTextExpand]'
2109
3199
  }]
2110
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onResize: [{
2111
- type: HostListener,
2112
- args: ['window:resize']
2113
- }], onMouseEnter: [{
3200
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
2114
3201
  type: HostListener,
2115
3202
  args: ['mouseenter']
2116
3203
  }], onMouseLeave: [{
@@ -2119,6 +3206,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2119
3206
  }] } });
2120
3207
 
2121
3208
  class OneGridCellOptionMenuComponent {
3209
+ get menuLabel() {
3210
+ return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
3211
+ }
2122
3212
  constructor(elementRef) {
2123
3213
  this.elementRef = elementRef;
2124
3214
  this.column = null;
@@ -2126,6 +3216,56 @@ class OneGridCellOptionMenuComponent {
2126
3216
  this.options = [];
2127
3217
  this.cellOptionMenuSelect = new EventEmitter();
2128
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();
2129
3269
  }
2130
3270
  // Click outside
2131
3271
  onDocumentClick(event) {
@@ -2157,11 +3297,11 @@ class OneGridCellOptionMenuComponent {
2157
3297
  this.cellOptionMenuClose.emit();
2158
3298
  }
2159
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 }); }
2160
- 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()" } }, ngImport: i0, template: "<div class=\"cell-option-menu\" #menuEl>\n @for (opt of options; track opt.value) {\n <div class=\"cell-option-menu-item\" (click)=\"onOptionSelected(opt)\">\n <!-- <span [style.padding-right.px]=\"8\"><lucide-icon name=\"check\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon></span> -->\n <span>{{ opt.text }}</span> \n </div>\n }\n</div>", styles: [""] }); }
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: [""] }); }
2161
3301
  }
2162
3302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
2163
3303
  type: Component,
2164
- args: [{ selector: 'one-grid-cell-option-menu', standalone: false, template: "<div class=\"cell-option-menu\" #menuEl>\n @for (opt of options; track opt.value) {\n <div class=\"cell-option-menu-item\" (click)=\"onOptionSelected(opt)\">\n <!-- <span [style.padding-right.px]=\"8\"><lucide-icon name=\"check\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon></span> -->\n <span>{{ opt.text }}</span> \n </div>\n }\n</div>" }]
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" }]
2165
3305
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
2166
3306
  type: Input
2167
3307
  }], node: [{
@@ -2172,6 +3312,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2172
3312
  type: Output
2173
3313
  }], cellOptionMenuClose: [{
2174
3314
  type: Output
3315
+ }], items: [{
3316
+ type: ViewChildren,
3317
+ args: ['item']
2175
3318
  }], onDocumentClick: [{
2176
3319
  type: HostListener,
2177
3320
  args: ['document:click', ['$event']]
@@ -2183,6 +3326,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2183
3326
  args: ['document:scroll']
2184
3327
  }] } });
2185
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;
3356
+ }
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.';
3363
+ }
3364
+ /** True while this specific row is the one being moved. */
3365
+ get grabbed() {
3366
+ return this.dragService.state?.rowId === this.node.rowId;
3367
+ }
3368
+ onPointerDown(event) {
3369
+ if (this.disabled)
3370
+ 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) });
3375
+ }
3376
+ onKeyDown(event) {
3377
+ if (this.disabled)
3378
+ 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;
3405
+ }
3406
+ }
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();
3414
+ }
3415
+ }
3416
+ emit(result) {
3417
+ if (result)
3418
+ this.dragEnd.emit(result);
3419
+ }
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 }); }
3422
+ }
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: [{
3427
+ type: Input,
3428
+ args: [{ required: true }]
3429
+ }], suppressedReason: [{
3430
+ type: Input
3431
+ }], dragEnd: [{
3432
+ type: Output
3433
+ }] } });
3434
+
2186
3435
  class CellInvalidPipe {
2187
3436
  transform(cellStates, value, column, node, allowedEdit, submitted) {
2188
3437
  if (!column.validationRules?.length) {
@@ -2294,10 +3543,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2294
3543
  }] });
2295
3544
 
2296
3545
  class OneGridBodyComponent {
2297
- constructor(colService, cellService, eventService, cdr) {
3546
+ constructor(colService, cellService, eventService, rowDragService, cdr) {
2298
3547
  this.colService = colService;
2299
3548
  this.cellService = cellService;
2300
3549
  this.eventService = eventService;
3550
+ this.rowDragService = rowDragService;
2301
3551
  this.cdr = cdr;
2302
3552
  this.rowData = [];
2303
3553
  this.pagination = false;
@@ -2305,6 +3555,14 @@ class OneGridBodyComponent {
2305
3555
  this.enableGroupColumnDefs = false;
2306
3556
  this.submitted = false;
2307
3557
  this.context = null;
3558
+ /**
3559
+ * Pinned rows are bound only so the component re-renders when they change;
3560
+ * the nodes themselves come from the API, which owns them.
3561
+ */
3562
+ this.pinnedTopRowData = [];
3563
+ this.pinnedBottomRowData = [];
3564
+ this.pinnedTopNodes = [];
3565
+ this.pinnedBottomNodes = [];
2308
3566
  this.columnDefs = [];
2309
3567
  this.cellRenderer = false;
2310
3568
  /**
@@ -2345,11 +3603,19 @@ class OneGridBodyComponent {
2345
3603
  this.expansionChanged = new EventEmitter();
2346
3604
  this.bodyScroll = new EventEmitter();
2347
3605
  this.cellOptionSelect = new EventEmitter();
3606
+ this.rowDragEnd = new EventEmitter();
2348
3607
  /**
2349
3608
  * Selection Range Variables
2350
3609
  */
2351
3610
  this.rowWidth = 0;
2352
3611
  this.nodes = [];
3612
+ /**
3613
+ * Row drag state, mirrored from the service so the template can read it
3614
+ * synchronously. `null` whenever no drag is in flight.
3615
+ */
3616
+ this.dragState = null;
3617
+ this.dragAnnouncement = '';
3618
+ this.rowDragSuppressed = null;
2353
3619
  this.selectedCells = new Set();
2354
3620
  this.isMouseDown = false;
2355
3621
  this.startCell = null;
@@ -2377,6 +3643,40 @@ class OneGridBodyComponent {
2377
3643
  this.itemSize = 30;
2378
3644
  this.minBufferPx = 200;
2379
3645
  this.maxBufferPx = 400;
3646
+ /**
3647
+ * Virtual Scroll Properties
3648
+ *
3649
+ * The buffer scales with the row count so small grids render in one pass, but
3650
+ * it is capped: `itemSize * items * 1.5` is 1.5x the *total* content height,
3651
+ * and CdkFixedSizeVirtualScroll expands its rendered range until the buffer is
3652
+ * satisfied. Uncapped, that makes the viewport materialise every row and
3653
+ * virtual scrolling stops doing anything at all (100k rows locked the tab).
3654
+ * MAX_BUFFER_PX keeps roughly twenty rows of overscan either side instead.
3655
+ */
3656
+ this.MAX_BUFFER_PX = 600;
3657
+ /**
3658
+ * Where to paint the drop indicator, in pixels from the top of the body.
3659
+ *
3660
+ * `insertAt` is a boundary, so the line sits at row-height times the number
3661
+ * of rows above it — in content space. The indicator is a sibling of the
3662
+ * viewport rather than a child, because anything inside it either scrolls
3663
+ * away with the transformed content wrapper or ignores the scroll entirely,
3664
+ * so the viewport's own scroll offset and its position within the body are
3665
+ * both subtracted back out here.
3666
+ *
3667
+ * A field rather than a getter: it is read from the template on every change
3668
+ * detection pass, and it only actually moves when the drag state or the
3669
+ * scroll position does.
3670
+ */
3671
+ this.dropIndicatorTop = null;
3672
+ /**
3673
+ * How wide the line is drawn. Rows are `width: fit-content`, so they are
3674
+ * often narrower than the viewport — a full-bleed line would hang past the
3675
+ * last column into empty space and read as part of the frame rather than the
3676
+ * rows. This is the visible extent of the row content, which also shrinks
3677
+ * correctly as the grid is scrolled sideways.
3678
+ */
3679
+ this.dropIndicatorWidth = 0;
2380
3680
  }
2381
3681
  ngOnInit() {
2382
3682
  /**
@@ -2385,13 +3685,30 @@ class OneGridBodyComponent {
2385
3685
  this.subscriptions.add(this.gridCopy$.subscribe((event) => {
2386
3686
  this.onGridCopy(event);
2387
3687
  }));
3688
+ /**
3689
+ * Row drag
3690
+ *
3691
+ * The service owns the gesture; the body only renders it and lends it the
3692
+ * viewport, the row height and a way to apply a move. The suppression
3693
+ * reason is re-read on every event that could change the order, so the
3694
+ * handle disables itself the moment a sort or filter is applied.
3695
+ */
3696
+ this.subscriptions.add(this.rowDragService.state$.subscribe((state) => {
3697
+ this.dragState = state;
3698
+ this.updateDropIndicator();
3699
+ this.cdr.markForCheck();
3700
+ }));
3701
+ this.subscriptions.add(this.rowDragService.announcement$.subscribe((message) => {
3702
+ this.dragAnnouncement = message;
3703
+ this.cdr.markForCheck();
3704
+ }));
2388
3705
  /**
2389
3706
  * Listen Column Changed Event
2390
3707
  */
2391
3708
  this.subscriptions.add(this.eventService.addEventListener('columnChanged', (payload) => {
2392
3709
  this.columnDefs = [...payload.columns];
2393
3710
  this.cellRenderer = this.columnDefs.some(col => col.cellRendererComponent);
2394
- this.rowWidth = this.columnDefs.reduce((sum, col) => sum + (col.width ? col.width > col.maxWidth ? col.maxWidth : col.width < col.minWidth ? col.minWidth : col.width : col.minWidth), 0);
3711
+ this.rowWidth = this.columnDefs.reduce((sum, col) => sum + resolveColumnWidth(col), 0);
2395
3712
  this.cdr.markForCheck();
2396
3713
  }));
2397
3714
  /**
@@ -2407,7 +3724,9 @@ class OneGridBodyComponent {
2407
3724
  if (node.leaf && this.detailRendererComponent) {
2408
3725
  node.detailRendererComponent = this.detailRendererComponent;
2409
3726
  }
2410
- if (node.expanded) {
3727
+ // Detail rows can still be in flight when a row is expanded, and
3728
+ // spreading null throws. Nothing to re-seed until they arrive.
3729
+ if (node.expanded && node.detailRowData) {
2411
3730
  this.api.setDetailRowData(node, [...node.detailRowData]);
2412
3731
  }
2413
3732
  return node;
@@ -2416,14 +3735,50 @@ class OneGridBodyComponent {
2416
3735
  else {
2417
3736
  this.nodes = nodes;
2418
3737
  }
3738
+ this.refreshRowDragSuppressed();
2419
3739
  this.cdr.markForCheck();
2420
3740
  });
2421
3741
  }));
3742
+ /**
3743
+ * Listen Pinned Row Data Changed Event
3744
+ */
3745
+ this.subscriptions.add(this.eventService.addEventListener('pinnedRowDataChanged', ({ side, nodes }) => {
3746
+ if (side === 'top') {
3747
+ this.pinnedTopNodes = nodes;
3748
+ }
3749
+ else {
3750
+ this.pinnedBottomNodes = nodes;
3751
+ }
3752
+ this.cdr.markForCheck();
3753
+ }));
2422
3754
  /**
2423
3755
  * Listen Sort Changed Event
2424
3756
  */
2425
3757
  this.subscriptions.add(this.eventService.addEventListener('sortChanged', ({ nodes }) => {
2426
3758
  this.nodes = nodes;
3759
+ // Sorting takes ownership of the order away from the user, so the handles
3760
+ // have to disable in the same frame the rows re-sort.
3761
+ this.refreshRowDragSuppressed();
3762
+ this.cdr.markForCheck();
3763
+ }));
3764
+ /**
3765
+ * Listen Row Order Changed Event
3766
+ *
3767
+ * Deliberately not folded into `rowDataChanged`: that path defers a frame
3768
+ * and re-seeds detail rows, which is right for a data load and far too much
3769
+ * for a move. A reorder is the same nodes in a different order.
3770
+ */
3771
+ this.subscriptions.add(this.eventService.addEventListener('rowOrderChanged', ({ nodes }) => {
3772
+ this.nodes = [...nodes];
3773
+ this.cdr.markForCheck();
3774
+ }));
3775
+ /**
3776
+ * Listen Cell State Changed Event
3777
+ *
3778
+ * The body renders OnPush, so regenerating a row's cell state is invisible
3779
+ * until something marks the body dirty. This is that something.
3780
+ */
3781
+ this.subscriptions.add(this.eventService.addEventListener('cellStateChanged', () => {
2427
3782
  this.cdr.markForCheck();
2428
3783
  }));
2429
3784
  /**
@@ -2442,9 +3797,33 @@ class OneGridBodyComponent {
2442
3797
  const scrollLeft = event.target.scrollLeft;
2443
3798
  if (scrollLeft !== this.lastScrollLeft) {
2444
3799
  this.lastScrollLeft = scrollLeft;
3800
+ this.syncPinnedRows(scrollLeft);
2445
3801
  this.bodyScroll.emit(scrollLeft);
3802
+ // The indicator is clipped to the row content, which moves sideways too.
3803
+ if (this.dragState) {
3804
+ this.updateDropIndicator();
3805
+ this.cdr.markForCheck();
3806
+ }
2446
3807
  return;
2447
3808
  }
3809
+ // Vertical: the drop indicator is anchored to the body, not the content, so
3810
+ // it has to be repositioned as the rows move beneath it — which they do on
3811
+ // their own during auto-scroll, with the pointer standing still.
3812
+ if (this.dragState) {
3813
+ this.updateDropIndicator();
3814
+ this.cdr.markForCheck();
3815
+ }
3816
+ }
3817
+ /**
3818
+ * Keep the pinned rows in step with the body's horizontal position.
3819
+ *
3820
+ * Assigned rather than transformed, for the same reason as the header: sticky
3821
+ * positioning for pinned *columns* has to resolve against a real scrollport,
3822
+ * and a transformed ancestor is not one. Pinned rows and pinned columns
3823
+ * therefore compose — a frozen corner cell stays frozen on both axes.
3824
+ */
3825
+ syncPinnedRows(scrollLeft) {
3826
+ this.pinnedViewports?.forEach(viewport => (viewport.nativeElement.scrollLeft = scrollLeft));
2448
3827
  }
2449
3828
  /**
2450
3829
  * Track Rendering Row Node By Row ID
@@ -2624,14 +4003,56 @@ class OneGridBodyComponent {
2624
4003
  invokeRowActionLanguage(event) {
2625
4004
  this.onRowLanguage.emit(event);
2626
4005
  }
4006
+ /**
4007
+ * Open the cell option menu, and tell the host it was asked for.
4008
+ *
4009
+ * The opening is the part that was missing. Everything else existed — the
4010
+ * trigger button behind `cellRendererParams.cellOptionButtonStatus`, the menu
4011
+ * component, the `cellOptionMenu` column property, the selection event wired
4012
+ * through to the grid's output — but nothing ever set the flag the template
4013
+ * renders on, so the button did nothing at all.
4014
+ *
4015
+ * Done here rather than in the row-action component because this is the
4016
+ * component whose template renders the menu, and so the one that has to be
4017
+ * marked for check.
4018
+ */
2627
4019
  invokeRowActionCellOption(event) {
4020
+ const field = event.column?.field;
4021
+ // Nothing to open when the column declared no options; the event still
4022
+ // goes to the host, which may be driving something of its own.
4023
+ if (field && event.node && event.column?.cellOptionMenu?.length) {
4024
+ event.node.cellOptions[field] = true;
4025
+ this.cdr.markForCheck();
4026
+ }
2628
4027
  this.onRowCellOption.emit(event);
2629
4028
  }
4029
+ /**
4030
+ * The menu closed itself — on select, on Escape, or on a click outside. It
4031
+ * has already cleared its own flag; this is what repaints without it.
4032
+ */
4033
+ cellOptionMenuClosed() {
4034
+ this.cdr.markForCheck();
4035
+ }
2630
4036
  invokeRowActionSelection(event) {
2631
4037
  this.onRowSelection.emit(event);
2632
4038
  }
4039
+ /**
4040
+ * Toggle a group / master row, then report the result.
4041
+ *
4042
+ * The group renderer is presentational: it says "the expander was clicked"
4043
+ * and nothing more. Performing the state change belongs here, where the API
4044
+ * is available. Previously this method only re-emitted, so the expander
4045
+ * rendered, animated on hover and did nothing at all — every consumer had to
4046
+ * discover for itself that `expansionChanged` was a request rather than the
4047
+ * notification its past-tense name promises.
4048
+ *
4049
+ * The event is re-emitted *after* the toggle, so `event.node.expanded` is the
4050
+ * new state, which is what a past-tense event should carry.
4051
+ */
2633
4052
  invokeExpansionChangedClick(event) {
2634
- this.expansionChanged.emit(event);
4053
+ const node = this.api.getRowNodeById(event.node.rowId) ?? event.node;
4054
+ this.api.setExpandedById(node.rowId, !node.expanded);
4055
+ this.expansionChanged.emit({ ...event, node, api: this.api });
2635
4056
  }
2636
4057
  cellOptionMenuSelected(event) {
2637
4058
  this.cellOptionSelect.emit(event);
@@ -2721,26 +4142,94 @@ class OneGridBodyComponent {
2721
4142
  }
2722
4143
  }
2723
4144
  }
2724
- /**
2725
- * Virtual Scroll Properties
2726
- */
2727
4145
  setVirtualScroll(items = 0) {
2728
- this.minBufferPx = this.itemSize * items * 1.5;
4146
+ this.minBufferPx = Math.min(this.itemSize * items * 1.5, this.MAX_BUFFER_PX);
2729
4147
  this.maxBufferPx = this.minBufferPx * 2;
2730
4148
  }
2731
4149
  /**
2732
4150
  * Unsubscribe All Subscriptions
2733
4151
  */
4152
+ /**
4153
+ * Hand the drag service what it needs to run a gesture.
4154
+ *
4155
+ * Deferred to here because the viewport element does not exist until the view
4156
+ * is up, and `nodes` is passed as a getter rather than a value — a drag
4157
+ * mutates the order underneath it, so a snapshot taken at attach time would
4158
+ * go stale on the first move.
4159
+ */
4160
+ ngAfterViewInit() {
4161
+ const element = this.viewport?.elementRef?.nativeElement;
4162
+ if (!element)
4163
+ return;
4164
+ this.rowDragService.attach({
4165
+ viewport: element,
4166
+ itemSize: this.itemSize,
4167
+ nodes: () => this.nodes,
4168
+ move: (fromIndex, toIndex) => {
4169
+ const moved = this.api.moveRow(fromIndex, toIndex);
4170
+ if (moved)
4171
+ this.cdr.markForCheck();
4172
+ return moved;
4173
+ },
4174
+ });
4175
+ this.refreshRowDragSuppressed();
4176
+ }
4177
+ /**
4178
+ * The params every per-cell callback receives.
4179
+ *
4180
+ * Built here rather than inline in the template so the whole documented
4181
+ * shape is passed — `value`, `field`, `rowIndex`, `api` and `context` were
4182
+ * all declared on `OneCellRendererParams` and none of them were being sent,
4183
+ * so a `cellClass` reading `params.value` silently got `undefined`.
4184
+ */
4185
+ cellParams(node, column) {
4186
+ return this.cellService.cellParams(column, node);
4187
+ }
4188
+ /**
4189
+ * Params for a component cell renderer.
4190
+ *
4191
+ * The cell params, plus whatever the column declared in
4192
+ * `cellRendererParams` — a field that exists precisely so a renderer can be
4193
+ * configured per column, and that nothing had ever forwarded to it. Column
4194
+ * config goes first so the grid's own fields win a name collision.
4195
+ */
4196
+ rendererParams(node, column) {
4197
+ return { ...(column?.cellRendererParams ?? {}), ...this.cellParams(node, column) };
4198
+ }
4199
+ /** Is this the row currently being moved? */
4200
+ isRowDragging(node) {
4201
+ return this.dragState?.rowId === node.rowId;
4202
+ }
4203
+ updateDropIndicator() {
4204
+ const state = this.dragState;
4205
+ const element = this.viewport?.elementRef?.nativeElement;
4206
+ if (state?.source !== 'pointer' || !element) {
4207
+ this.dropIndicatorTop = null;
4208
+ return;
4209
+ }
4210
+ this.dropIndicatorTop = (state.insertAt * this.itemSize) - element.scrollTop + element.offsetTop;
4211
+ this.dropIndicatorWidth = Math.max(Math.min(this.rowWidth - element.scrollLeft, element.clientWidth), 0);
4212
+ }
4213
+ onRowDragEnd(event) {
4214
+ this.rowDragEnd.emit(event);
4215
+ }
4216
+ refreshRowDragSuppressed() {
4217
+ const reason = this.api?.getRowDragSuppressedReason?.() ?? null;
4218
+ if (reason === this.rowDragSuppressed)
4219
+ return;
4220
+ this.rowDragSuppressed = reason;
4221
+ this.cdr.markForCheck();
4222
+ }
2734
4223
  ngOnDestroy() {
2735
4224
  this.subscriptions.unsubscribe();
2736
4225
  }
2737
- 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: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
2738
- 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$", 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" }, 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 }], ngImport: i0, template: "<div #bodyRef class=\"one-grid-body\">\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\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\" [attr.data-row-id]=\"node.rowId\" [class.one-row-selected]=\"node.selected\">\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 @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</div>\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 [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 }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"{ data: node.data, field: column.field, rowIndex: node.rowIndex, columnDef: column, node: node, context: context }\">\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.resolveCellPresentation(column, { node, data: node.data, columnDef: column })._resolvedCellClass\"\n [ngStyle]=\"colService.resolveCellPresentation(column, { node, data: node.data, columnDef: column })._resolvedCellStyle\"\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 <!-- 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]=\"{ data: node.data, field: column.field, rowIndex: node.rowIndex, columnDef: column, node: node, context: context }\">\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: 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: 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: "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 }); }
4226
+ 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 }); }
2739
4228
  }
2740
4229
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, decorators: [{
2741
4230
  type: Component,
2742
- args: [{ standalone: false, selector: 'one-grid-body', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bodyRef class=\"one-grid-body\">\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\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\" [attr.data-row-id]=\"node.rowId\" [class.one-row-selected]=\"node.selected\">\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 @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</div>\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 [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 }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"{ data: node.data, field: column.field, rowIndex: node.rowIndex, columnDef: column, node: node, context: context }\">\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.resolveCellPresentation(column, { node, data: node.data, columnDef: column })._resolvedCellClass\"\n [ngStyle]=\"colService.resolveCellPresentation(column, { node, data: node.data, columnDef: column })._resolvedCellStyle\"\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 <!-- 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]=\"{ data: node.data, field: column.field, rowIndex: node.rowIndex, columnDef: column, node: node, context: context }\">\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" }]
2743
- }], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: i0.ChangeDetectorRef }], propDecorators: { bodyRef: [{
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" }]
4232
+ }], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridRowDragService }, { type: i0.ChangeDetectorRef }], propDecorators: { bodyRef: [{
2744
4233
  type: ViewChild,
2745
4234
  args: ['bodyRef', { static: true }]
2746
4235
  }], rowData: [{
@@ -2765,6 +4254,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2765
4254
  }], gridCopy$: [{
2766
4255
  type: Input,
2767
4256
  args: [{ required: true }]
4257
+ }], pinnedTopRowData: [{
4258
+ type: Input
4259
+ }], pinnedBottomRowData: [{
4260
+ type: Input
4261
+ }], pinnedViewports: [{
4262
+ type: ViewChildren,
4263
+ args: ['pinnedViewport']
2768
4264
  }], viewport: [{
2769
4265
  type: ViewChild,
2770
4266
  args: [CdkVirtualScrollViewport]
@@ -2830,6 +4326,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2830
4326
  type: Output
2831
4327
  }], cellOptionSelect: [{
2832
4328
  type: Output
4329
+ }], rowDragEnd: [{
4330
+ type: Output
2833
4331
  }], onDocumentClick: [{
2834
4332
  type: HostListener,
2835
4333
  args: ['document:click', ['$event']]
@@ -2853,31 +4351,56 @@ class OneGridPaginationComponent {
2853
4351
  this.goToNext = new EventEmitter();
2854
4352
  this.goToLast = new EventEmitter();
2855
4353
  this.pageSizeChange = new EventEmitter();
2856
- this.pageSize = OnePageSizeOption[0];
2857
- this.pageSizeOptions = OnePageSizeOption;
2858
4354
  }
2859
- ngAfterViewInit() {
2860
- if (this.paginate?.pageSizeOptions) {
2861
- this.pageSizeOptions = this.paginate.pageSizeOptions;
2862
- }
4355
+ /**
4356
+ * The page size the bar shows.
4357
+ *
4358
+ * Derived from the input rather than held locally. The bar is a display of
4359
+ * the host's paging state — it does not own it — and a local copy meant a
4360
+ * host that supplied `perPage: 50` still saw "25 rows" selected.
4361
+ */
4362
+ get pageSize() {
4363
+ return this.paginate?.perPage ?? OnePageSizeOption[0];
4364
+ }
4365
+ /**
4366
+ * The size options offered.
4367
+ *
4368
+ * Also derived. These used to be copied out of the input in
4369
+ * `ngAfterViewInit` — after the view had already been checked with the
4370
+ * defaults — so any grid supplying its own options threw `NG0100` in dev
4371
+ * mode, and later changes to the list were ignored entirely.
4372
+ */
4373
+ get pageSizeOptions() {
4374
+ return this.paginate?.pageSizeOptions?.length ? this.paginate.pageSizeOptions : OnePageSizeOption;
4375
+ }
4376
+ /** Whether there is a page before the current one. */
4377
+ get hasPrevious() {
4378
+ return this.pageCount > 1 && (this.paginate?.currentPage ?? 1) > 1;
4379
+ }
4380
+ /** Whether there is a page after the current one. */
4381
+ get hasNext() {
4382
+ return this.pageCount > 1 && (this.paginate?.currentPage ?? 1) < (this.paginate?.lastPage ?? 1);
4383
+ }
4384
+ get pageCount() {
4385
+ return this.paginate?.lastPage ?? 1;
2863
4386
  }
2864
4387
  goToFirstPage() {
2865
- if (!(this.paginate?.perPage < this.paginate?.total && this.paginate?.currentPage > 1))
4388
+ if (!this.hasPrevious)
2866
4389
  return;
2867
4390
  this.goToFirst.emit(this.paginate?.currentPage);
2868
4391
  }
2869
4392
  goToPreviousPage() {
2870
- if (!(this.paginate?.perPage < this.paginate?.total && this.paginate?.currentPage > 1))
4393
+ if (!this.hasPrevious)
2871
4394
  return;
2872
4395
  this.goToPrevious.emit(this.paginate?.currentPage);
2873
4396
  }
2874
4397
  goToNextPage() {
2875
- if (!(this.paginate?.perPage < this.paginate?.total && this.paginate?.currentPage < this.paginate?.lastPage))
4398
+ if (!this.hasNext)
2876
4399
  return;
2877
4400
  this.goToNext.emit(this.paginate?.currentPage);
2878
4401
  }
2879
4402
  goToLastPage() {
2880
- if (!(this.paginate?.perPage < this.paginate?.total && this.paginate?.currentPage < this.paginate?.lastPage))
4403
+ if (!this.hasNext)
2881
4404
  return;
2882
4405
  this.goToLast.emit(this.paginate?.currentPage);
2883
4406
  }
@@ -2885,16 +4408,15 @@ class OneGridPaginationComponent {
2885
4408
  const select = event.target;
2886
4409
  const newSize = Number(select.value);
2887
4410
  if (newSize !== this.pageSize) {
2888
- this.pageSize = newSize;
2889
- this.pageSizeChange.emit(this.pageSize);
4411
+ this.pageSizeChange.emit(newSize);
2890
4412
  }
2891
4413
  }
2892
4414
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2893
- 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: "<div class=\"one-grid-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 <button \n class=\"one-pagination-btn\"\n (click)=\"goToFirstPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage > 1)\">\n <lucide-icon name=\"chevron-first\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n class=\"one-pagination-btn\"\n (click)=\"goToPreviousPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage > 1)\">\n <lucide-icon name=\"chevron-left\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <div class=\"one-pagination-column\">\n &nbsp;{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }}&nbsp;\n </div>\n <div class=\"one-pagination-column\">\n <button\n class=\"one-pagination-btn\"\n (click)=\"goToNextPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage < paginate?.lastPage)\">\n <lucide-icon name=\"chevron-right\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n class=\"one-pagination-btn\"\n (click)=\"goToLastPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage < paginate?.lastPage)\">\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 <select name=\"row-per-page\" [value]=\"pageSize\" (change)=\"onPageSizeChange($event)\">\n @for (size of pageSizeOptions; track $index) {\n <option [value]=\"size\">{{ size }} rows</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n</div>", styles: [""], dependencies: [{ kind: "directive", type: i3$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: i3.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] }); }
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"] }] }); }
2894
4416
  }
2895
4417
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, decorators: [{
2896
4418
  type: Component,
2897
- args: [{ standalone: false, selector: 'one-grid-pagination', template: "<div class=\"one-grid-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 <button \n class=\"one-pagination-btn\"\n (click)=\"goToFirstPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage > 1)\">\n <lucide-icon name=\"chevron-first\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n class=\"one-pagination-btn\"\n (click)=\"goToPreviousPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage > 1)\">\n <lucide-icon name=\"chevron-left\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n </div>\n <div class=\"one-pagination-column\">\n &nbsp;{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }}&nbsp;\n </div>\n <div class=\"one-pagination-column\">\n <button\n class=\"one-pagination-btn\"\n (click)=\"goToNextPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage < paginate?.lastPage)\">\n <lucide-icon name=\"chevron-right\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n </button>\n <button\n class=\"one-pagination-btn\"\n (click)=\"goToLastPage()\"\n [class.disabled]=\"!(paginate?.perPage < paginate?.total && paginate?.currentPage < paginate?.lastPage)\">\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 <select name=\"row-per-page\" [value]=\"pageSize\" (change)=\"onPageSizeChange($event)\">\n @for (size of pageSizeOptions; track $index) {\n <option [value]=\"size\">{{ size }} rows</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n</div>" }]
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" }]
2898
4420
  }], propDecorators: { paginate: [{
2899
4421
  type: Input
2900
4422
  }], goToFirst: [{
@@ -2924,6 +4446,14 @@ class OneGridComponent {
2924
4446
  this.groupColumnDefs = [];
2925
4447
  this.defaultColumnDef = {};
2926
4448
  this.columnDefs = [];
4449
+ /**
4450
+ * Rows anchored above the scrolling area — a header summary, or an entry line.
4451
+ * They keep column alignment and scroll sideways with the body, but are not
4452
+ * data: sorting, filtering, selection and the row count all pass them by.
4453
+ */
4454
+ this.pinnedTopRowData = [];
4455
+ /** Rows anchored below the scrolling area, typically a totals row. */
4456
+ this.pinnedBottomRowData = [];
2927
4457
  // RXJS Properties
2928
4458
  this.gridCopy$ = new Subject();
2929
4459
  /**
@@ -2941,6 +4471,11 @@ class OneGridComponent {
2941
4471
  /**
2942
4472
  * Bind Row Action Events & Data to Parent
2943
4473
  */
4474
+ /**
4475
+ * A row finished moving. Fires once per completed reorder — not while the
4476
+ * pointer is in flight — and only when the position actually changed.
4477
+ */
4478
+ this.rowDragEnd = new EventEmitter();
2944
4479
  this.onGridReady = new EventEmitter();
2945
4480
  this.onRowGotoDetail = new EventEmitter();
2946
4481
  this.onRowAddNew = new EventEmitter();
@@ -2988,6 +4523,15 @@ class OneGridComponent {
2988
4523
  if (changes["rowData"] && !changes["rowData"]?.firstChange) {
2989
4524
  this.treeData || this.masterDetail ? this.api.updateRowData(this.rowData) : this.api.setRowData(this.rowData);
2990
4525
  }
4526
+ // Only later changes. The first one arrives before ngOnInit has run
4527
+ // api.init, so the column definitions are not registered yet and the body
4528
+ // has not subscribed — the initial load happens in ngAfterViewInit instead.
4529
+ if (changes["pinnedTopRowData"] && !changes["pinnedTopRowData"].firstChange) {
4530
+ this.api.setPinnedRowData('top', this.pinnedTopRowData);
4531
+ }
4532
+ if (changes["pinnedBottomRowData"] && !changes["pinnedBottomRowData"].firstChange) {
4533
+ this.api.setPinnedRowData('bottom', this.pinnedBottomRowData);
4534
+ }
2991
4535
  }
2992
4536
  ngOnInit() {
2993
4537
  /**
@@ -3015,7 +4559,18 @@ class OneGridComponent {
3015
4559
  /**
3016
4560
  * Initialize One Grid API
3017
4561
  */
3018
- this.api.init(this.gridType, this.rowSelection, this.columnDefs, this.defaultColumnDef, this.context, this.masterDetail, this.detailCellRendererParams, this.detailRendererComponent, this.treeData, this.groupColumnDefs);
4562
+ this.api.init({
4563
+ gridType: this.gridType,
4564
+ columnDefs: this.columnDefs,
4565
+ defaultColumnDef: this.defaultColumnDef,
4566
+ groupColumnDefs: this.groupColumnDefs,
4567
+ context: this.context,
4568
+ rowSelection: this.rowSelection,
4569
+ masterDetail: this.masterDetail,
4570
+ detailCellRendererParams: this.detailCellRendererParams,
4571
+ detailRendererComponent: this.detailRendererComponent,
4572
+ treeData: this.treeData,
4573
+ });
3019
4574
  /**
3020
4575
  * Setup Master Detail Configuration
3021
4576
  */
@@ -3043,14 +4598,20 @@ class OneGridComponent {
3043
4598
  }));
3044
4599
  }
3045
4600
  ngAfterViewInit() {
3046
- const headerViewport = this.elementRef.nativeElement.querySelector('.one-grid-header-column');
3047
- const bodyViewport = this.elementRef.nativeElement.querySelector('.one-grid-body-viewport');
3048
- // Listen Scroll Event Changed
3049
- this.subscriptions.add(fromEvent(bodyViewport, 'scroll').subscribe(() => {
3050
- if (headerViewport) {
3051
- headerViewport.scrollLeft = bodyViewport.scrollLeft;
3052
- }
3053
- }));
4601
+ // The header follows the body through (bodyScroll) -> [scrollLeft]. A second
4602
+ // subscription used to live here that queried '.one-grid-header-column' — a
4603
+ // header *cell*, not the viewport — and assigned scrollLeft to it, which
4604
+ // does nothing. It has been removed rather than repaired: the binding path
4605
+ // is the one that works, and it now drives the pinned columns too.
4606
+ // Pinned rows, now that the columns are registered and the body is
4607
+ // listening. Unlike rowData these are not loaded through (onGridReady) —
4608
+ // binding them is the whole contract — so the grid loads them itself.
4609
+ if (this.pinnedTopRowData?.length) {
4610
+ this.api.setPinnedRowData('top', this.pinnedTopRowData);
4611
+ }
4612
+ if (this.pinnedBottomRowData?.length) {
4613
+ this.api.setPinnedRowData('bottom', this.pinnedBottomRowData);
4614
+ }
3054
4615
  /**
3055
4616
  * Emit Grid Ready Event
3056
4617
  */
@@ -3149,6 +4710,9 @@ class OneGridComponent {
3149
4710
  invokePageSizeChange(pageSize) {
3150
4711
  this.pageSizeChange.emit(pageSize);
3151
4712
  }
4713
+ invokeRowDragEnd(event) {
4714
+ this.rowDragEnd.emit(event);
4715
+ }
3152
4716
  invokeExpansionChangedClick(event) {
3153
4717
  this.expansionChanged.emit(event);
3154
4718
  }
@@ -3161,15 +4725,16 @@ class OneGridComponent {
3161
4725
  this.subscriptions.unsubscribe();
3162
4726
  }
3163
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 }); }
3164
- 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", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded", pagination: "pagination", paginate: "paginate" }, outputs: { 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: [
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: [
3165
4729
  OneGridEventService,
3166
4730
  OneGridApi,
3167
4731
  OgColumnService,
3168
4732
  OneGridSelectionService,
3169
4733
  OneGridExpansionService,
3170
4734
  OneGridRowService,
3171
- OneGridCellService
3172
- ], viewQueries: [{ propertyName: "ogRef", first: true, predicate: ["ogRef"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #ogRef class=\"one-grid\" (copy)=\"onGridCopy($event)\" tabindex=\"0\">\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 [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 (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", "gridType", "masterDetail", "masterNode", "detailCellRendererParams", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded", "pagination", "paginate"], outputs: ["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$", "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"] }, { kind: "component", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: ["paginate"], outputs: ["goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }] }); }
4735
+ OneGridCellService,
4736
+ 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"] }] }); }
3173
4738
  }
3174
4739
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, decorators: [{
3175
4740
  type: Component,
@@ -3180,8 +4745,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3180
4745
  OneGridSelectionService,
3181
4746
  OneGridExpansionService,
3182
4747
  OneGridRowService,
3183
- OneGridCellService
3184
- ], template: "<div #ogRef class=\"one-grid\" (copy)=\"onGridCopy($event)\" tabindex=\"0\">\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 [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 (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>" }]
4748
+ OneGridCellService,
4749
+ 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>" }]
3185
4751
  }], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }], propDecorators: { ogRef: [{
3186
4752
  type: ViewChild,
3187
4753
  args: ['ogRef', { static: true }]
@@ -3205,6 +4771,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3205
4771
  type: Input
3206
4772
  }], columnDefs: [{
3207
4773
  type: Input
4774
+ }], pinnedTopRowData: [{
4775
+ type: Input
4776
+ }], pinnedBottomRowData: [{
4777
+ type: Input
3208
4778
  }], gridType: [{
3209
4779
  type: Input
3210
4780
  }], masterDetail: [{
@@ -3223,6 +4793,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3223
4793
  type: Input
3224
4794
  }], groupDefaultExpanded: [{
3225
4795
  type: Input
4796
+ }], rowDragEnd: [{
4797
+ type: Output
3226
4798
  }], onGridReady: [{
3227
4799
  type: Output
3228
4800
  }], onRowGotoDetail: [{
@@ -3385,7 +4957,7 @@ class OneGridCellCurrencyComponent {
3385
4957
  this.unsubscribe?.();
3386
4958
  }
3387
4959
  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 }); }
3388
- 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$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.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" }] }); }
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" }] }); }
3389
4961
  }
3390
4962
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, decorators: [{
3391
4963
  type: Component,
@@ -3481,7 +5053,6 @@ class OneGridModule {
3481
5053
  CellSelectedPipe,
3482
5054
  CellActionButtonEnablePipe,
3483
5055
  CellNumberValuePipe,
3484
- CellDisableResolverPipe,
3485
5056
  CellFieldPipe,
3486
5057
  CellKeyPipe,
3487
5058
  CellLevelPaddingPipe,
@@ -3495,17 +5066,14 @@ class OneGridModule {
3495
5066
  OneCellRendererComponentDirective,
3496
5067
  OneCellTextExpandDirective,
3497
5068
  OneGridCellCurrencyComponent,
3498
- OneGridCellOptionMenuComponent], imports: [CommonModule,
5069
+ OneGridCellOptionMenuComponent,
5070
+ OneGridRowDragHandleComponent], imports: [CommonModule,
3499
5071
  FormsModule,
3500
5072
  ScrollingModule,
3501
- NgbTypeahead,
3502
- NgbHighlight,
3503
- ReactiveFormsModule,
3504
- CdkDragPlaceholder, i3.LucideAngularModule], exports: [OneGridComponent] }); }
5073
+ ReactiveFormsModule, i2$1.LucideAngularModule], exports: [OneGridComponent] }); }
3505
5074
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [CommonModule,
3506
5075
  FormsModule,
3507
5076
  ScrollingModule,
3508
- NgbHighlight,
3509
5077
  ReactiveFormsModule,
3510
5078
  LucideAngularModule.pick(icons)] }); }
3511
5079
  }
@@ -3531,7 +5099,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3531
5099
  CellSelectedPipe,
3532
5100
  CellActionButtonEnablePipe,
3533
5101
  CellNumberValuePipe,
3534
- CellDisableResolverPipe,
3535
5102
  CellFieldPipe,
3536
5103
  CellKeyPipe,
3537
5104
  CellLevelPaddingPipe,
@@ -3545,16 +5112,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3545
5112
  OneCellRendererComponentDirective,
3546
5113
  OneCellTextExpandDirective,
3547
5114
  OneGridCellCurrencyComponent,
3548
- OneGridCellOptionMenuComponent
5115
+ OneGridCellOptionMenuComponent,
5116
+ OneGridRowDragHandleComponent
3549
5117
  ],
3550
5118
  imports: [
3551
5119
  CommonModule,
3552
5120
  FormsModule,
3553
5121
  ScrollingModule,
3554
- NgbTypeahead,
3555
- NgbHighlight,
3556
5122
  ReactiveFormsModule,
3557
- CdkDragPlaceholder,
3558
5123
  LucideAngularModule.pick(icons),
3559
5124
  ],
3560
5125
  exports: [OneGridComponent]
@@ -3569,5 +5134,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3569
5134
  * Generated bundle index. Do not edit.
3570
5135
  */
3571
5136
 
3572
- export { OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, applyDefaultColumnDef, getCellError, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams };
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 };
3573
5138
  //# sourceMappingURL=one-grid-core-angular.mjs.map