@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.
- package/README.md +49 -4
- package/esm2022/lib/components/one-grid/one-grid.component.mjs +67 -16
- package/esm2022/lib/components/one-grid-body/one-grid-body.component.mjs +282 -30
- package/esm2022/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.mjs +10 -7
- package/esm2022/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.mjs +60 -4
- package/esm2022/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.mjs +14 -5
- package/esm2022/lib/components/one-grid-header/one-grid-header.component.mjs +50 -13
- package/esm2022/lib/components/one-grid-pagination/one-grid-pagination.component.mjs +39 -15
- package/esm2022/lib/components/one-grid-row-action/one-grid-row-action.component.mjs +54 -8
- package/esm2022/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.mjs +110 -0
- package/esm2022/lib/core/directives/one-cell-renderer-component.directive.mjs +9 -2
- package/esm2022/lib/core/directives/one-cell-text-expand.directive.mjs +33 -51
- package/esm2022/lib/core/instants/one-grid-api.class.mjs +429 -166
- package/esm2022/lib/core/instants/one-row-node.class.mjs +103 -37
- package/esm2022/lib/core/models/one-column-def-types.model.mjs +1 -1
- package/esm2022/lib/core/models/one-event-types.model.mjs +1 -1
- package/esm2022/lib/core/models/one-grid-types.model.mjs +1 -1
- package/esm2022/lib/core/models/one-row-drag.model.mjs +2 -0
- package/esm2022/lib/core/models/one-row.model.mjs +1 -1
- package/esm2022/lib/core/models/one-tree-data.model.mjs +1 -1
- package/esm2022/lib/core/models/one-validation.model.mjs +1 -1
- package/esm2022/lib/core/pipes/cell-field-value-empty.pipe.mjs +8 -3
- package/esm2022/lib/core/pipes/column-sort-state.pipe.mjs +3 -1
- package/esm2022/lib/core/services/og-column.service.mjs +120 -13
- package/esm2022/lib/core/services/one-grid-cell.service.mjs +147 -22
- package/esm2022/lib/core/services/one-grid-row-drag.service.mjs +351 -0
- package/esm2022/lib/core/services/one-grid-selection.service.mjs +19 -6
- package/esm2022/lib/helpers/one-column-def.helper.mjs +27 -2
- package/esm2022/lib/helpers/one-filter.helper.mjs +71 -12
- package/esm2022/lib/helpers/one-validation.helper.mjs +7 -2
- package/esm2022/lib/index.mjs +6 -1
- package/esm2022/lib/one-grid.module.mjs +7 -16
- package/fesm2022/one-grid-core-angular.mjs +1988 -423
- package/fesm2022/one-grid-core-angular.mjs.map +1 -1
- package/lib/components/one-grid/one-grid.component.d.ts +16 -1
- package/lib/components/one-grid-body/one-grid-body.component.d.ts +132 -5
- package/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.d.ts +6 -1
- package/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.d.ts +19 -2
- package/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.d.ts +5 -2
- package/lib/components/one-grid-header/one-grid-header.component.d.ts +15 -7
- package/lib/components/one-grid-pagination/one-grid-pagination.component.d.ts +24 -5
- package/lib/components/one-grid-row-action/one-grid-row-action.component.d.ts +44 -0
- package/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.d.ts +39 -0
- package/lib/core/directives/one-cell-text-expand.directive.d.ts +21 -9
- package/lib/core/instants/one-grid-api.class.d.ts +212 -26
- package/lib/core/instants/one-row-node.class.d.ts +67 -14
- package/lib/core/models/one-column-def-types.model.d.ts +89 -15
- package/lib/core/models/one-event-types.model.d.ts +24 -1
- package/lib/core/models/one-grid-types.model.d.ts +36 -1
- package/lib/core/models/one-row-drag.model.d.ts +44 -0
- package/lib/core/models/one-row.model.d.ts +0 -5
- package/lib/core/models/one-tree-data.model.d.ts +13 -0
- package/lib/core/models/one-validation.model.d.ts +12 -2
- package/lib/core/pipes/column-sort-state.pipe.d.ts +1 -1
- package/lib/core/services/og-column.service.d.ts +44 -3
- package/lib/core/services/one-grid-cell.service.d.ts +68 -4
- package/lib/core/services/one-grid-row-drag.service.d.ts +113 -0
- package/lib/core/services/one-grid-selection.service.d.ts +9 -0
- package/lib/helpers/one-column-def.helper.d.ts +19 -1
- package/lib/helpers/one-filter.helper.d.ts +29 -4
- package/lib/index.d.ts +5 -0
- package/lib/one-grid.module.d.ts +17 -19
- package/package.json +1 -2
- package/scss/_one-grid.scss +389 -17
- package/esm2022/lib/core/pipes/cell-disable-resolver.pipe.mjs +0 -26
- 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,
|
|
4
|
-
import { BehaviorSubject, Subject, Subscription
|
|
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
|
|
9
|
+
import * as i2$1 from 'lucide-angular';
|
|
10
10
|
import { LucideAngularModule, icons } from 'lucide-angular';
|
|
11
|
-
import * as
|
|
11
|
+
import * as i6 from '@angular/cdk/scrolling';
|
|
12
12
|
import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
|
|
13
|
-
import * as i3
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
this.
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
191
|
-
|
|
280
|
+
let listeners = this.listeners.get(event);
|
|
281
|
+
if (!listeners) {
|
|
282
|
+
listeners = new Set();
|
|
283
|
+
this.listeners.set(event, listeners);
|
|
192
284
|
}
|
|
193
|
-
|
|
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)
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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();
|
|
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
|
-
* -
|
|
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
|
-
|
|
248
|
-
|
|
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
|
|
267
|
-
case 'lessThan': return
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
511
|
+
return this.resolveHeaderPresentation(column);
|
|
353
512
|
});
|
|
354
|
-
this.
|
|
355
|
-
this.
|
|
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
|
-
|
|
362
|
-
this
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
*
|
|
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.
|
|
499
|
-
|
|
500
|
-
this.disableFn = (column, node) => resolveOneCellRendererParams(column
|
|
501
|
-
|
|
502
|
-
this.
|
|
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[
|
|
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
|
-
|
|
531
|
-
|
|
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
|
-
|
|
536
|
-
|
|
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 (
|
|
540
|
-
return { type: 'placeholder', value: column
|
|
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 (
|
|
544
|
-
const value =
|
|
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
|
-
|
|
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
|
|
941
|
+
const placeholder = this.resolvePlaceholder(node, column) ?? '';
|
|
554
942
|
return { type: 'placeholder', value: placeholder };
|
|
555
943
|
}
|
|
556
|
-
// 4. Cell Text Value
|
|
557
|
-
if (
|
|
558
|
-
const value =
|
|
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
|
-
|
|
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 (
|
|
1038
|
+
if (!allIds?.length)
|
|
643
1039
|
return 'unchecked';
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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.
|
|
708
|
-
this.
|
|
709
|
-
this.
|
|
710
|
-
this.
|
|
711
|
-
this.
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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(
|
|
737
|
-
this.rowService.setGetRowIdFn(
|
|
1173
|
+
this.selectionService.setSelectionMode(rowSelection);
|
|
1174
|
+
this.rowService.setGetRowIdFn(context?.componentParent?.getRowId ?? null);
|
|
738
1175
|
this.cellService.applyColumnDefs(columnDefs, context);
|
|
739
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
818
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1461
|
+
if (this._destroyed)
|
|
1462
|
+
return;
|
|
1463
|
+
this._sortModel = sortModel;
|
|
869
1464
|
this.applySorting();
|
|
870
|
-
this.eventService.dispatchEvent('sortChanged', { nodes: this.
|
|
1465
|
+
this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes });
|
|
871
1466
|
}
|
|
872
1467
|
applySorting() {
|
|
873
|
-
if (this.
|
|
874
|
-
this.
|
|
1468
|
+
if (this._sortModel.length === 0) {
|
|
1469
|
+
this._rowNodes = [...this._originalRowNodes.values()];
|
|
875
1470
|
return;
|
|
876
1471
|
}
|
|
877
|
-
this.
|
|
878
|
-
for (const sortItem of this.
|
|
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
|
-
|
|
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 (
|
|
1494
|
+
if (aEmpty)
|
|
894
1495
|
return sortDirection === 'asc' ? 1 : -1;
|
|
895
|
-
if (
|
|
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.
|
|
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.
|
|
1576
|
+
return this._rowNodes?.length > 0 ? [...this._rowNodes] : [];
|
|
976
1577
|
}
|
|
977
1578
|
getRowDataSelected() {
|
|
978
|
-
return this.
|
|
1579
|
+
return this._rowNodes.filter(node => node.selected).map(node => ({ ...node.data }));
|
|
979
1580
|
}
|
|
980
1581
|
getSelectedRowNodes() {
|
|
981
|
-
const nodes = this.
|
|
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
|
-
*
|
|
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
|
-
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
for (const rule of
|
|
1015
|
-
const error = rule(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[
|
|
1018
|
-
break; //
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1868
|
+
* Height a master row must reserve for its detail grid:
|
|
1869
|
+
* rowHeight x clamp(rows, minRows, maxRows) + headerHeight.
|
|
1226
1870
|
*/
|
|
1227
|
-
|
|
1228
|
-
const
|
|
1229
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
|
|
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.
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
1272
|
-
|
|
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:
|
|
2286
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService, decorators: [{
|
|
1275
2287
|
type: Injectable
|
|
1276
|
-
}]
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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\"
|
|
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\"
|
|
1576
|
-
}], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }], propDecorators: {
|
|
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 </
|
|
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 </
|
|
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
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
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\"
|
|
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\"
|
|
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
|
|
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
|
-
|
|
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(),
|
|
3175
|
+
this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
|
|
2075
3176
|
}
|
|
2076
3177
|
onMouseLeave() {
|
|
2077
|
-
|
|
3178
|
+
this.collapse();
|
|
3179
|
+
}
|
|
3180
|
+
ngOnDestroy() {
|
|
2078
3181
|
this.collapse();
|
|
2079
3182
|
}
|
|
2080
3183
|
expand() {
|
|
2081
|
-
|
|
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
|
-
|
|
2093
|
-
this.
|
|
2094
|
-
this.renderer.
|
|
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: { "
|
|
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: {
|
|
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 <
|
|
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 <
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
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 (!
|
|
4388
|
+
if (!this.hasPrevious)
|
|
2866
4389
|
return;
|
|
2867
4390
|
this.goToFirst.emit(this.paginate?.currentPage);
|
|
2868
4391
|
}
|
|
2869
4392
|
goToPreviousPage() {
|
|
2870
|
-
if (!
|
|
4393
|
+
if (!this.hasPrevious)
|
|
2871
4394
|
return;
|
|
2872
4395
|
this.goToPrevious.emit(this.paginate?.currentPage);
|
|
2873
4396
|
}
|
|
2874
4397
|
goToNextPage() {
|
|
2875
|
-
if (!
|
|
4398
|
+
if (!this.hasNext)
|
|
2876
4399
|
return;
|
|
2877
4400
|
this.goToNext.emit(this.paginate?.currentPage);
|
|
2878
4401
|
}
|
|
2879
4402
|
goToLastPage() {
|
|
2880
|
-
if (!
|
|
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.
|
|
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 <span>/</span> \n <span>{{ paginate?.total }}</span>\n </div>\n </div>\n }\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n
|
|
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 <span>/</span> \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 <span class=\"one-grid-visually-hidden\">Page </span>{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }} \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 <span>/</span> \n <span>{{ paginate?.total }}</span>\n </div>\n </div>\n }\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n
|
|
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 <span>/</span> \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 <span class=\"one-grid-visually-hidden\">Page </span>{{ paginate?.currentPage }}<span>/</span>{{ paginate?.lastPage }} \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(
|
|
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
|
-
|
|
3047
|
-
|
|
3048
|
-
//
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
5069
|
+
OneGridCellOptionMenuComponent,
|
|
5070
|
+
OneGridRowDragHandleComponent], imports: [CommonModule,
|
|
3499
5071
|
FormsModule,
|
|
3500
5072
|
ScrollingModule,
|
|
3501
|
-
|
|
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
|