@one-grid-core/angular 0.1.0-beta.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +123 -20
- 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 +1964 -421
- 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 +52 -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,101 @@ 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
|
+
}
|
|
526
875
|
/**
|
|
527
876
|
* Generate New Node State Based on Column Field
|
|
528
877
|
*/
|
|
529
878
|
generateCellState(node, column) {
|
|
530
|
-
|
|
531
|
-
|
|
879
|
+
// This was `field !== undefined || field !== '' || field !== null`, which is
|
|
880
|
+
// true for every possible value including all three it meant to exclude —
|
|
881
|
+
// `||` where `&&` was intended. It gated the four branches below, so none
|
|
882
|
+
// of them could ever fall through to the default. Reduced to the check it
|
|
883
|
+
// was reaching for: a column with no field has no value to render.
|
|
884
|
+
const field = column?.field;
|
|
885
|
+
if (!field) {
|
|
886
|
+
return { type: 'none', value: '' };
|
|
887
|
+
}
|
|
888
|
+
const isEmpty = this._cellFieldValueEmpty.transform(node.data, field);
|
|
532
889
|
const isGroup = node.group || node.leafGroup;
|
|
533
890
|
const isAutoGroup = column?.autoGroupField;
|
|
534
891
|
if (column && column.cellRenderer && !isGroup) {
|
|
535
|
-
|
|
536
|
-
|
|
892
|
+
// The renderer used to be handed `{ node, columnDef }` and nothing else,
|
|
893
|
+
// so the obvious way to write one — `params.value` — produced the string
|
|
894
|
+
// "undefined", and `params.data.x` threw. It now gets exactly what every
|
|
895
|
+
// other row-scoped hook gets.
|
|
896
|
+
return { type: 'html', value: column.cellRenderer(this.cellParams(column, node)) };
|
|
537
897
|
}
|
|
538
898
|
// 1. Cell Group Placeholder
|
|
539
|
-
if (
|
|
899
|
+
if (isEmpty && isGroup && isAutoGroup) {
|
|
540
900
|
return { type: 'placeholder', value: column?.cellRendererParams?.placeholder ?? this._cellValueGetter.transform(node.data, node, column, this._context) ?? '' };
|
|
541
901
|
}
|
|
542
902
|
// 2. Cell Group Text Value
|
|
543
|
-
if (
|
|
544
|
-
const value =
|
|
903
|
+
if (isGroup && isAutoGroup) {
|
|
904
|
+
const value = this.formatValue(node, column, this.resolveValue(node, column));
|
|
545
905
|
return { type: 'text', value: value ?? '' };
|
|
546
906
|
}
|
|
547
907
|
// 3. Cell Placeholder
|
|
548
|
-
|
|
908
|
+
//
|
|
909
|
+
// The auto group column is deliberately NOT excluded here. Cases 1 and 2
|
|
910
|
+
// above already claimed the group rows, so what reaches this point is a
|
|
911
|
+
// leaf — and a leaf on the auto group column has to render its label too,
|
|
912
|
+
// indented by level. Excluding it dropped every leaf through to case 5 and
|
|
913
|
+
// rendered the group column blank for the whole bottom row of a tree.
|
|
914
|
+
if (isEmpty && !isGroup) {
|
|
549
915
|
const valueGetter = this._cellValueGetter.transform(node.data, node, column, this._context);
|
|
550
916
|
if (valueGetter) {
|
|
551
|
-
return { type: 'text', value: valueGetter };
|
|
917
|
+
return { type: 'text', value: this.formatValue(node, column, valueGetter) };
|
|
552
918
|
}
|
|
553
919
|
const placeholder = column?.cellRendererParams?.placeholder ?? '';
|
|
554
920
|
return { type: 'placeholder', value: placeholder };
|
|
555
921
|
}
|
|
556
|
-
// 4. Cell Text Value
|
|
557
|
-
if (
|
|
558
|
-
const value =
|
|
922
|
+
// 4. Cell Text Value (leaves, including the auto group column — see above)
|
|
923
|
+
if (!isGroup) {
|
|
924
|
+
const value = this.formatValue(node, column, this.resolveValue(node, column));
|
|
559
925
|
return { type: 'text', value: value ?? '' };
|
|
560
926
|
}
|
|
561
927
|
// 5. Default
|
|
@@ -637,13 +1003,26 @@ class OneGridSelectionService {
|
|
|
637
1003
|
toggleAllSelection(allIds, selected) {
|
|
638
1004
|
selected ? this._selectedIds = new Set(allIds) : this._selectedIds.clear();
|
|
639
1005
|
}
|
|
640
|
-
|
|
1006
|
+
/**
|
|
1007
|
+
* Tri-state for the header checkbox, over the rows currently present.
|
|
1008
|
+
*
|
|
1009
|
+
* This compared `_selectedIds.size` with `allIds.length` and never checked
|
|
1010
|
+
* that the selected ids were among the present ones. Selecting two rows and
|
|
1011
|
+
* then swapping in two different rows reported `checked` with nothing
|
|
1012
|
+
* actually selected, because the two counts happened to match. Counting the
|
|
1013
|
+
* intersection is the only thing that answers the question being asked.
|
|
1014
|
+
*/
|
|
641
1015
|
getSelectAllState(allIds) {
|
|
642
|
-
if (
|
|
1016
|
+
if (!allIds?.length)
|
|
1017
|
+
return 'unchecked';
|
|
1018
|
+
let selected = 0;
|
|
1019
|
+
for (const rowId of allIds) {
|
|
1020
|
+
if (this._selectedIds.has(rowId))
|
|
1021
|
+
selected++;
|
|
1022
|
+
}
|
|
1023
|
+
if (selected === 0)
|
|
643
1024
|
return 'unchecked';
|
|
644
|
-
|
|
645
|
-
return 'checked';
|
|
646
|
-
return 'indeterminate';
|
|
1025
|
+
return selected === allIds.length ? 'checked' : 'indeterminate';
|
|
647
1026
|
}
|
|
648
1027
|
selectAll(rowIds) {
|
|
649
1028
|
this._selectedIds = new Set(rowIds);
|
|
@@ -697,6 +1076,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
697
1076
|
}] });
|
|
698
1077
|
|
|
699
1078
|
class OneGridApi {
|
|
1079
|
+
/** Unique per grid instance, assigned during `init`. */
|
|
1080
|
+
get id() {
|
|
1081
|
+
return this._id;
|
|
1082
|
+
}
|
|
1083
|
+
/** The `[context]` the grid was given. */
|
|
1084
|
+
get context() {
|
|
1085
|
+
return this._context;
|
|
1086
|
+
}
|
|
1087
|
+
/** Selection mode, captured during `init`. Changing the input later has no effect. */
|
|
1088
|
+
get rowSelection() {
|
|
1089
|
+
return this._rowSelection;
|
|
1090
|
+
}
|
|
1091
|
+
/** Which role this instance plays: simple, master, detail or tree. */
|
|
1092
|
+
get apiType() {
|
|
1093
|
+
return this._apiType;
|
|
1094
|
+
}
|
|
1095
|
+
/** Every node the grid holds, in flattened order. */
|
|
1096
|
+
get rowNodes() {
|
|
1097
|
+
return this._rowNodes;
|
|
1098
|
+
}
|
|
1099
|
+
/** The sort model currently applied. Change it with `onSortChanged`. */
|
|
1100
|
+
get sortModel() {
|
|
1101
|
+
return this._sortModel;
|
|
1102
|
+
}
|
|
700
1103
|
constructor(columnService, rowService, cellService, eventService, selectionService, expansionService) {
|
|
701
1104
|
this.columnService = columnService;
|
|
702
1105
|
this.rowService = rowService;
|
|
@@ -704,17 +1107,18 @@ class OneGridApi {
|
|
|
704
1107
|
this.eventService = eventService;
|
|
705
1108
|
this.selectionService = selectionService;
|
|
706
1109
|
this.expansionService = expansionService;
|
|
707
|
-
this.
|
|
708
|
-
this.
|
|
709
|
-
this.
|
|
710
|
-
this.
|
|
711
|
-
this.
|
|
1110
|
+
this._context = null;
|
|
1111
|
+
this._rowSelection = null;
|
|
1112
|
+
this._apiType = null;
|
|
1113
|
+
this._rowNodes = [];
|
|
1114
|
+
this._sortModel = [];
|
|
712
1115
|
this.rowHeight = 26;
|
|
713
1116
|
this.headerRowHeight = 32;
|
|
714
1117
|
this._currentIndex = 0;
|
|
715
1118
|
this._quickFilterValue = null;
|
|
716
1119
|
this._activeFilters = [];
|
|
717
1120
|
this._visibleRowNodes = [];
|
|
1121
|
+
this._pinnedRowNodes = { top: [], bottom: [] };
|
|
718
1122
|
this._originalRowNodes = new Map();
|
|
719
1123
|
this._nodeCleanupMap = new Map(); // Cleanup registry (prevents memory leaks)
|
|
720
1124
|
this._destroyed = false;
|
|
@@ -724,63 +1128,83 @@ class OneGridApi {
|
|
|
724
1128
|
this.groupDefaultExpanded = 0;
|
|
725
1129
|
this.masterDetail = false;
|
|
726
1130
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1131
|
+
/**
|
|
1132
|
+
* Stand the grid up. Called by `OneGridComponent` from `ngOnInit`.
|
|
1133
|
+
*
|
|
1134
|
+
* Takes a single `OneGridInitOptions` rather than the ten positional
|
|
1135
|
+
* arguments it used to, so a call site reads as the configuration it is and
|
|
1136
|
+
* a new option no longer means editing every caller.
|
|
1137
|
+
*
|
|
1138
|
+
* @internal Applications obtain a ready-made API from `(onGridReady)`; they
|
|
1139
|
+
* should never call this themselves.
|
|
1140
|
+
*/
|
|
1141
|
+
init(options) {
|
|
1142
|
+
const { gridType, columnDefs, defaultColumnDef = {}, groupColumnDefs = [], context = null, rowSelection = null, masterDetail = false, detailCellRendererParams = null, detailRendererComponent = null, treeData = false, } = options;
|
|
1143
|
+
this._id = v4();
|
|
1144
|
+
this._apiType = gridType;
|
|
1145
|
+
this._rowSelection = rowSelection;
|
|
1146
|
+
this._context = context;
|
|
732
1147
|
this.masterDetail = masterDetail;
|
|
733
1148
|
this.detailCellRendererParams = detailCellRendererParams;
|
|
734
1149
|
this.detailRendererComponent = detailRendererComponent;
|
|
735
1150
|
this.treeData = treeData;
|
|
736
|
-
this.selectionService.setSelectionMode(
|
|
737
|
-
this.rowService.setGetRowIdFn(
|
|
1151
|
+
this.selectionService.setSelectionMode(rowSelection);
|
|
1152
|
+
this.rowService.setGetRowIdFn(context?.componentParent?.getRowId ?? null);
|
|
738
1153
|
this.cellService.applyColumnDefs(columnDefs, context);
|
|
739
|
-
this.
|
|
1154
|
+
this.cellService.setGridApi(this);
|
|
1155
|
+
this.columnService.initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs, context);
|
|
740
1156
|
const columnState = this.columnService.getColumnState();
|
|
741
1157
|
setTimeout(() => this.eventService.dispatchEvent('columnChanged', columnState));
|
|
742
1158
|
}
|
|
743
1159
|
/**
|
|
744
|
-
*
|
|
1160
|
+
* Replace every row, discarding selection and expansion.
|
|
1161
|
+
*
|
|
1162
|
+
* Use this when the rows represent something new. To swap the data while
|
|
1163
|
+
* keeping what the user has selected and opened, use `updateRowData`.
|
|
745
1164
|
*/
|
|
746
1165
|
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
|
-
}
|
|
1166
|
+
this.rebuildRows(rowData, true);
|
|
761
1167
|
}
|
|
762
1168
|
/**
|
|
763
|
-
*
|
|
1169
|
+
* Replace every row, keeping selection and expansion.
|
|
1170
|
+
*
|
|
1171
|
+
* State is keyed by row id, so this only preserves anything if the grid has a
|
|
1172
|
+
* `context.componentParent.getRowId` — otherwise each rebuild mints new ids
|
|
1173
|
+
* and there is nothing left to match against.
|
|
764
1174
|
*/
|
|
765
1175
|
updateRowData(rowData) {
|
|
1176
|
+
this.rebuildRows(rowData, false);
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* The one row-loading path. `setRowData` and `updateRowData` differed only in
|
|
1180
|
+
* whether they cleared the state services first, so everything else — build,
|
|
1181
|
+
* filter, prune — lives here once.
|
|
1182
|
+
*/
|
|
1183
|
+
rebuildRows(rowData, resetState) {
|
|
1184
|
+
if (this._destroyed)
|
|
1185
|
+
return;
|
|
1186
|
+
if (resetState) {
|
|
1187
|
+
this.selectionService.deselectAll();
|
|
1188
|
+
this.expansionService.clear();
|
|
1189
|
+
}
|
|
766
1190
|
const validRowIds = new Set();
|
|
767
|
-
// Build New Row Nodes
|
|
768
1191
|
this.buildRowNodes(rowData, null, 0, validRowIds);
|
|
769
|
-
//
|
|
1192
|
+
// Re-apply the active sort. `buildRowNodes` rebuilds the row list in the
|
|
1193
|
+
// order the data arrived, so without this a data update silently returned
|
|
1194
|
+
// the rows to that order while `sortModel` — and therefore the header's
|
|
1195
|
+
// sort arrow — still said they were sorted. Sorting before filtering
|
|
1196
|
+
// because filtering only sets visibility on whatever order this leaves.
|
|
1197
|
+
if (this._sortModel.length > 0)
|
|
1198
|
+
this.applySorting();
|
|
770
1199
|
this.applyFilters();
|
|
771
|
-
|
|
772
|
-
for (const rowId of this._originalRowNodes.keys()) {
|
|
773
|
-
if (!validRowIds.has(rowId)) {
|
|
774
|
-
this._originalRowNodes.delete(rowId);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
1200
|
+
this.pruneRemovedNodes(validRowIds);
|
|
777
1201
|
}
|
|
778
1202
|
/**
|
|
779
1203
|
* Recursive Build Row Nodes (Optimized)
|
|
780
1204
|
*/
|
|
781
1205
|
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
1206
|
if (parent === null) {
|
|
783
|
-
this.
|
|
1207
|
+
this._rowNodes = [];
|
|
784
1208
|
this._currentIndex = 0;
|
|
785
1209
|
}
|
|
786
1210
|
if (!rowData?.length)
|
|
@@ -806,7 +1230,11 @@ class OneGridApi {
|
|
|
806
1230
|
}
|
|
807
1231
|
}
|
|
808
1232
|
const childrenExist = children && children.length >= 0;
|
|
809
|
-
|
|
1233
|
+
// Coerced, because `childrenExist` is `children && children.length >= 0`
|
|
1234
|
+
// — which is `null` for a row with no children field, not `false`. These
|
|
1235
|
+
// are declared `boolean`, and assigning null made every consumer compare
|
|
1236
|
+
// truthiness instead of the value.
|
|
1237
|
+
node.group = this.masterDetail ? false : !!childrenExist;
|
|
810
1238
|
node.leaf = this.masterDetail ? true : !node.group;
|
|
811
1239
|
node.leafGroup = this.masterDetail ? false : (node.group && !childrenExist);
|
|
812
1240
|
node.expandable = this.masterDetail ? true : (node.group && !node.leafGroup);
|
|
@@ -814,11 +1242,11 @@ class OneGridApi {
|
|
|
814
1242
|
node.setExpanded(expandedIds.has(rowId) ? true : collapsedIds.has(rowId) ? false : defaultExpanded);
|
|
815
1243
|
node.setSelected(selectedIds.has(rowId));
|
|
816
1244
|
node.setCellStates(this.cellService.generateCellStates(node));
|
|
817
|
-
if (this.masterDetail && this.
|
|
818
|
-
this.
|
|
1245
|
+
if (this.masterDetail && this._apiType === 'master') {
|
|
1246
|
+
this._getDetailRowData({ data, node: node, successCallback: (detailRowData) => node.setDetailRowData(detailRowData) });
|
|
819
1247
|
}
|
|
820
1248
|
this._currentIndex++;
|
|
821
|
-
this.
|
|
1249
|
+
this._rowNodes.push(node);
|
|
822
1250
|
nodes.push(node);
|
|
823
1251
|
node.children = this.treeData && childrenExist ? this.buildRowNodes(children || [], node, level + 1, validRowIds, expandedIds, collapsedIds, selectedIds) : [];
|
|
824
1252
|
}
|
|
@@ -830,19 +1258,37 @@ class OneGridApi {
|
|
|
830
1258
|
applyFilters() {
|
|
831
1259
|
const filters = this._activeFilters;
|
|
832
1260
|
const quick = this._quickFilterValue?.toLowerCase() ?? '';
|
|
833
|
-
|
|
1261
|
+
// The fields the grid actually shows. Without them the quick filter falls
|
|
1262
|
+
// back to every property on the row, which lets a user match on data that
|
|
1263
|
+
// has no column and never appears on screen.
|
|
1264
|
+
const fields = this.quickFilterFields();
|
|
1265
|
+
this._rowNodes.forEach(node => filterTreeNode(node, filters, quick, fields));
|
|
834
1266
|
this.refreshVisibleNodes();
|
|
835
1267
|
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Which fields the quick filter searches: every column that has one.
|
|
1270
|
+
*
|
|
1271
|
+
* Empty while the columns are still being registered, in which case the
|
|
1272
|
+
* helper falls back to searching the row — the same behaviour as before, and
|
|
1273
|
+
* the only sensible answer when the grid does not yet know its columns.
|
|
1274
|
+
*/
|
|
1275
|
+
quickFilterFields() {
|
|
1276
|
+
return this.columnService
|
|
1277
|
+
.getColumnState()
|
|
1278
|
+
.columns
|
|
1279
|
+
.map(column => column.field)
|
|
1280
|
+
.filter((field) => !!field && field !== '_oneRowNumber');
|
|
1281
|
+
}
|
|
836
1282
|
/**
|
|
837
1283
|
* Update Row Nodes Display
|
|
838
1284
|
*/
|
|
839
|
-
refreshVisibleNodes() {
|
|
1285
|
+
refreshVisibleNodes(dispatch = true) {
|
|
840
1286
|
if (this._destroyed)
|
|
841
1287
|
return;
|
|
842
1288
|
const visibleNodes = [];
|
|
843
1289
|
let rowNumber = 1;
|
|
844
1290
|
const visibilityCache = new Map();
|
|
845
|
-
for (const node of this.
|
|
1291
|
+
for (const node of this._rowNodes) {
|
|
846
1292
|
const parentVisible = node.parent ? visibilityCache.get(node.parent.rowId) : true;
|
|
847
1293
|
const isVisible = node.visible && parentVisible && (!node.parent || node.parent.expanded);
|
|
848
1294
|
visibilityCache.set(node.rowId, isVisible);
|
|
@@ -856,7 +1302,132 @@ class OneGridApi {
|
|
|
856
1302
|
}
|
|
857
1303
|
}
|
|
858
1304
|
this._visibleRowNodes = [...visibleNodes];
|
|
859
|
-
|
|
1305
|
+
if (dispatch)
|
|
1306
|
+
this.eventService.dispatchEvent('rowDataChanged', { nodes: visibleNodes });
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Replace the rows anchored to one edge of the grid.
|
|
1310
|
+
*
|
|
1311
|
+
* Pinned rows are deliberately kept out of `rowNodes` and
|
|
1312
|
+
* `_originalRowNodes`: they are not data. Sorting, filtering, selection,
|
|
1313
|
+
* expansion, pagination and the visible row count all ignore them, which is
|
|
1314
|
+
* the whole point of a summary row. They render with the same column
|
|
1315
|
+
* definitions, so renderers and value getters work as they do everywhere
|
|
1316
|
+
* else.
|
|
1317
|
+
*/
|
|
1318
|
+
setPinnedRowData(side, rowData) {
|
|
1319
|
+
if (this._destroyed)
|
|
1320
|
+
return;
|
|
1321
|
+
for (const node of this._pinnedRowNodes[side])
|
|
1322
|
+
node.destroy();
|
|
1323
|
+
this._pinnedRowNodes[side] = (rowData ?? []).map((data, index) => {
|
|
1324
|
+
// Ids are namespaced by side and position. They never collide with a data
|
|
1325
|
+
// row's id, and they are stable across rebuilds, which keeps the rendered
|
|
1326
|
+
// `data-cell-id` attributes stable too.
|
|
1327
|
+
const node = new OneRowNode(`one-pinned-${side}-${index}`, data, index);
|
|
1328
|
+
node.level = 0;
|
|
1329
|
+
node.leaf = true;
|
|
1330
|
+
node.visible = true;
|
|
1331
|
+
node.setCellStates(this.cellService.generateCellStates(node));
|
|
1332
|
+
return node;
|
|
1333
|
+
});
|
|
1334
|
+
this.eventService.dispatchEvent('pinnedRowDataChanged', {
|
|
1335
|
+
side,
|
|
1336
|
+
nodes: this._pinnedRowNodes[side],
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
/** The rows anchored to one edge. Read-only; change them with `setPinnedRowData`. */
|
|
1340
|
+
getPinnedRowNodes(side) {
|
|
1341
|
+
return this._pinnedRowNodes[side];
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Row reordering
|
|
1345
|
+
* --------------------------------------------------------------------------
|
|
1346
|
+
*
|
|
1347
|
+
* `_originalRowNodes` is the source of truth for row order: its insertion
|
|
1348
|
+
* order is what `applySorting` falls back to when the sort model empties, and
|
|
1349
|
+
* what a rebuild walks. `_rowNodes` is only the current display order. A move
|
|
1350
|
+
* that touched one and not the other would either be undone by the next sort
|
|
1351
|
+
* change or invisible until one happened, so both are rewritten together.
|
|
1352
|
+
*/
|
|
1353
|
+
/**
|
|
1354
|
+
* Whether the displayed order is the data order, and so whether a manual move
|
|
1355
|
+
* would survive.
|
|
1356
|
+
*
|
|
1357
|
+
* @returns the reason reordering is unavailable, or `null` when it is fine.
|
|
1358
|
+
*/
|
|
1359
|
+
getRowDragSuppressedReason() {
|
|
1360
|
+
if (this.treeData)
|
|
1361
|
+
return 'treeData';
|
|
1362
|
+
if (this._sortModel.length > 0)
|
|
1363
|
+
return 'sorted';
|
|
1364
|
+
if (isFilterActive(this._activeFilters, this._quickFilterValue ?? ''))
|
|
1365
|
+
return 'filtered';
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
/** Convenience inverse of {@link getRowDragSuppressedReason}. */
|
|
1369
|
+
canReorderRows() {
|
|
1370
|
+
return this.getRowDragSuppressedReason() === null;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Move a row from one position to another.
|
|
1374
|
+
*
|
|
1375
|
+
* Indices are positions in the grid's row order, the same order
|
|
1376
|
+
* `getAllRowNodes()` returns. `targetIndex` is where the row ends up *after*
|
|
1377
|
+
* it has been lifted out of `sourceIndex` — dragging row 0 to the bottom of a
|
|
1378
|
+
* 5-row grid is `moveRow(0, 4)`, not `moveRow(0, 5)`.
|
|
1379
|
+
*
|
|
1380
|
+
* @returns `true` if the rows moved. `false` when the move was rejected —
|
|
1381
|
+
* out-of-range indices, a no-op, or an order the grid does not own
|
|
1382
|
+
* (see {@link getRowDragSuppressedReason}) — so a caller can tell a
|
|
1383
|
+
* refused move from an applied one.
|
|
1384
|
+
*/
|
|
1385
|
+
moveRow(sourceIndex, targetIndex) {
|
|
1386
|
+
if (this._destroyed)
|
|
1387
|
+
return false;
|
|
1388
|
+
if (!this.canReorderRows())
|
|
1389
|
+
return false;
|
|
1390
|
+
const order = [...this._originalRowNodes.values()];
|
|
1391
|
+
if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex))
|
|
1392
|
+
return false;
|
|
1393
|
+
if (sourceIndex < 0 || sourceIndex >= order.length)
|
|
1394
|
+
return false;
|
|
1395
|
+
if (targetIndex < 0 || targetIndex >= order.length)
|
|
1396
|
+
return false;
|
|
1397
|
+
if (sourceIndex === targetIndex)
|
|
1398
|
+
return false;
|
|
1399
|
+
const [moved] = order.splice(sourceIndex, 1);
|
|
1400
|
+
order.splice(targetIndex, 0, moved);
|
|
1401
|
+
// Rebuilt rather than mutated: a Map preserves insertion order, and there
|
|
1402
|
+
// is no way to reposition a key within one.
|
|
1403
|
+
this._originalRowNodes = new Map(order.map(node => [node.rowId, node]));
|
|
1404
|
+
this._rowNodes = order;
|
|
1405
|
+
this.reindexRows();
|
|
1406
|
+
this.eventService.dispatchEvent('rowOrderChanged', { nodes: this._visibleRowNodes, node: moved, fromIndex: sourceIndex, toIndex: targetIndex });
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Move a row identified by id. Convenient when the caller holds a node rather
|
|
1411
|
+
* than a position — the index it was built with goes stale after any move.
|
|
1412
|
+
*/
|
|
1413
|
+
moveRowById(rowId, targetIndex) {
|
|
1414
|
+
const sourceIndex = [...this._originalRowNodes.values()].findIndex(node => node.rowId === rowId);
|
|
1415
|
+
return sourceIndex === -1 ? false : this.moveRow(sourceIndex, targetIndex);
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Re-stamp `rowIndex` from the current order.
|
|
1419
|
+
*
|
|
1420
|
+
* Row index is denormalised onto every node, and half the grid reads it —
|
|
1421
|
+
* cell params, the range selection, the detail lookup. Leaving it stale after
|
|
1422
|
+
* a move puts every row below the drop point one out.
|
|
1423
|
+
*/
|
|
1424
|
+
reindexRows() {
|
|
1425
|
+
this._rowNodes.forEach((node, index) => node.setRowIndex(index));
|
|
1426
|
+
// Silent: row numbers and the visible list have to be rebuilt, but the
|
|
1427
|
+
// `rowDataChanged` that normally follows defers a frame and re-seeds detail
|
|
1428
|
+
// rows. A keyboard drag fires one of these per arrow key, and `moveRow`
|
|
1429
|
+
// announces itself through `rowOrderChanged` instead.
|
|
1430
|
+
this.refreshVisibleNodes(false);
|
|
860
1431
|
}
|
|
861
1432
|
getVisibleRowNodes() {
|
|
862
1433
|
return this._visibleRowNodes;
|
|
@@ -865,17 +1436,19 @@ class OneGridApi {
|
|
|
865
1436
|
* Sorting Model Feature
|
|
866
1437
|
*/
|
|
867
1438
|
onSortChanged(sortModel) {
|
|
868
|
-
this.
|
|
1439
|
+
if (this._destroyed)
|
|
1440
|
+
return;
|
|
1441
|
+
this._sortModel = sortModel;
|
|
869
1442
|
this.applySorting();
|
|
870
|
-
this.eventService.dispatchEvent('sortChanged', { nodes: this.
|
|
1443
|
+
this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes });
|
|
871
1444
|
}
|
|
872
1445
|
applySorting() {
|
|
873
|
-
if (this.
|
|
874
|
-
this.
|
|
1446
|
+
if (this._sortModel.length === 0) {
|
|
1447
|
+
this._rowNodes = [...this._originalRowNodes.values()];
|
|
875
1448
|
return;
|
|
876
1449
|
}
|
|
877
|
-
this.
|
|
878
|
-
for (const sortItem of this.
|
|
1450
|
+
this._rowNodes = [...this._originalRowNodes.values()].sort((a, b) => {
|
|
1451
|
+
for (const sortItem of this._sortModel) {
|
|
879
1452
|
const { colId, sort } = sortItem;
|
|
880
1453
|
const valA = getFieldValue(a.data, colId);
|
|
881
1454
|
const valB = getFieldValue(b.data, colId);
|
|
@@ -888,11 +1461,17 @@ class OneGridApi {
|
|
|
888
1461
|
});
|
|
889
1462
|
}
|
|
890
1463
|
compareValues(valA, valB, sortDirection) {
|
|
891
|
-
|
|
1464
|
+
// `undefined` has to be treated as empty alongside `null`: getFieldValue
|
|
1465
|
+
// returns undefined for a missing field or an unresolved dotted path, and
|
|
1466
|
+
// comparing undefined with < / > yields false both ways, which made such
|
|
1467
|
+
// rows settle in whatever order the sort happened to encounter them.
|
|
1468
|
+
const aEmpty = valA === null || valA === undefined;
|
|
1469
|
+
const bEmpty = valB === null || valB === undefined;
|
|
1470
|
+
if (aEmpty && bEmpty)
|
|
892
1471
|
return 0;
|
|
893
|
-
if (
|
|
1472
|
+
if (aEmpty)
|
|
894
1473
|
return sortDirection === 'asc' ? 1 : -1;
|
|
895
|
-
if (
|
|
1474
|
+
if (bEmpty)
|
|
896
1475
|
return sortDirection === 'asc' ? -1 : 1;
|
|
897
1476
|
if (typeof valA === 'string' && typeof valB === 'string') {
|
|
898
1477
|
const comparison = valA.localeCompare(valB, undefined, { numeric: true });
|
|
@@ -947,7 +1526,7 @@ class OneGridApi {
|
|
|
947
1526
|
const node = this.getRowNodeById(rowId);
|
|
948
1527
|
if (node) {
|
|
949
1528
|
if (this.selectionService.getSelectionMode() === 'single') {
|
|
950
|
-
this.
|
|
1529
|
+
this._rowNodes.forEach(rowNode => rowNode.setSelected(rowNode.rowId === node.rowId));
|
|
951
1530
|
this._originalRowNodes.forEach(rowNode => rowNode.setSelected(rowNode.rowId === node.rowId));
|
|
952
1531
|
}
|
|
953
1532
|
else {
|
|
@@ -972,13 +1551,13 @@ class OneGridApi {
|
|
|
972
1551
|
* Get & Set Functions
|
|
973
1552
|
*/
|
|
974
1553
|
getAllRowNodes() {
|
|
975
|
-
return this.
|
|
1554
|
+
return this._rowNodes?.length > 0 ? [...this._rowNodes] : [];
|
|
976
1555
|
}
|
|
977
1556
|
getRowDataSelected() {
|
|
978
|
-
return this.
|
|
1557
|
+
return this._rowNodes.filter(node => node.selected).map(node => ({ ...node.data }));
|
|
979
1558
|
}
|
|
980
1559
|
getSelectedRowNodes() {
|
|
981
|
-
const nodes = this.
|
|
1560
|
+
const nodes = this._rowNodes.filter(node => node.selected);
|
|
982
1561
|
return nodes && nodes.length > 0 ? [...nodes] : [];
|
|
983
1562
|
}
|
|
984
1563
|
/**
|
|
@@ -988,6 +1567,8 @@ class OneGridApi {
|
|
|
988
1567
|
return this._activeFilters;
|
|
989
1568
|
}
|
|
990
1569
|
setFilterModel(model) {
|
|
1570
|
+
if (this._destroyed)
|
|
1571
|
+
return;
|
|
991
1572
|
this._activeFilters = model;
|
|
992
1573
|
this.applyFilters();
|
|
993
1574
|
}
|
|
@@ -998,76 +1579,69 @@ class OneGridApi {
|
|
|
998
1579
|
* Quick filter
|
|
999
1580
|
*/
|
|
1000
1581
|
setQuickFilter(value) {
|
|
1582
|
+
if (this._destroyed)
|
|
1583
|
+
return;
|
|
1001
1584
|
this._quickFilterValue = (value || '').toLowerCase();
|
|
1002
1585
|
this.applyFilters();
|
|
1003
1586
|
}
|
|
1004
1587
|
/**
|
|
1005
|
-
*
|
|
1588
|
+
* Run the column validators over one row.
|
|
1589
|
+
*
|
|
1590
|
+
* Rules run in order and stop at the first failure, so a field appears at
|
|
1591
|
+
* most once. Fields that passed are absent rather than mapped to `null`.
|
|
1592
|
+
*
|
|
1593
|
+
* The row type comes from the grid's own `T` — this used to declare its own
|
|
1594
|
+
* `<T>`, which shadowed the class parameter and let an `OneGridApi<Employee>`
|
|
1595
|
+
* happily validate an unrelated shape against employee columns.
|
|
1006
1596
|
*/
|
|
1007
1597
|
validateRow(row, columns) {
|
|
1008
1598
|
const errors = {};
|
|
1009
|
-
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
for (const rule of
|
|
1015
|
-
const error = rule(row[
|
|
1599
|
+
for (const column of columns) {
|
|
1600
|
+
// A column can only contribute an error if it has both rules and a field
|
|
1601
|
+
// to key them under.
|
|
1602
|
+
if (!column.field || !column.validationRules?.length)
|
|
1603
|
+
continue;
|
|
1604
|
+
for (const rule of column.validationRules) {
|
|
1605
|
+
const error = rule(row[column.field], row);
|
|
1016
1606
|
if (error) {
|
|
1017
|
-
errors[
|
|
1018
|
-
break; //
|
|
1607
|
+
errors[column.field] = error;
|
|
1608
|
+
break; // first failure per column wins
|
|
1019
1609
|
}
|
|
1020
1610
|
}
|
|
1021
|
-
}
|
|
1611
|
+
}
|
|
1022
1612
|
return errors;
|
|
1023
1613
|
}
|
|
1024
1614
|
/**
|
|
1025
|
-
*
|
|
1615
|
+
* Run the column validators over a whole dataset.
|
|
1616
|
+
*
|
|
1617
|
+
* Delegates to `validateRow` so the two can never disagree about what counts
|
|
1618
|
+
* as invalid — they previously carried separate copies of the same loop.
|
|
1026
1619
|
*/
|
|
1027
1620
|
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);
|
|
1621
|
+
const allErrors = rowData.map(row => this.validateRow(row, columns));
|
|
1622
|
+
const hasErrors = allErrors.some(errors => Object.keys(errors).length > 0);
|
|
1044
1623
|
return { errors: allErrors, valid: !hasErrors, invalid: hasErrors };
|
|
1045
1624
|
}
|
|
1046
|
-
/**
|
|
1625
|
+
/**
|
|
1626
|
+
* Traverse every node, depth-first, once each.
|
|
1627
|
+
*
|
|
1628
|
+
* `rowNodes` is already the flattened tree — `buildRowNodes` pushes every
|
|
1629
|
+
* node into it as it descends — so walking it is the whole traversal. The
|
|
1630
|
+
* previous implementation walked that flat list *and* recursed into
|
|
1631
|
+
* `children`, visiting every nested node twice and every depth-3 node three
|
|
1632
|
+
* times. `index` is the node's position in the flattened order, matching
|
|
1633
|
+
* `node.rowIndex`, rather than its position among its siblings.
|
|
1634
|
+
*/
|
|
1047
1635
|
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);
|
|
1636
|
+
this._rowNodes.forEach((node, index) => callback(node, index));
|
|
1056
1637
|
}
|
|
1057
|
-
/** Traverse only leaf nodes */
|
|
1638
|
+
/** Traverse only leaf nodes, once each, in flattened order. */
|
|
1058
1639
|
forEachLeafNode(callback) {
|
|
1059
1640
|
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);
|
|
1641
|
+
for (const node of this._rowNodes) {
|
|
1642
|
+
if (node.leaf)
|
|
1643
|
+
callback(node, leafIndex++);
|
|
1644
|
+
}
|
|
1071
1645
|
}
|
|
1072
1646
|
getRowNodeById(rowId) {
|
|
1073
1647
|
return this._originalRowNodes.get(rowId);
|
|
@@ -1081,8 +1655,25 @@ class OneGridApi {
|
|
|
1081
1655
|
setGroupDefaultExpanded(defaultExpanded) {
|
|
1082
1656
|
this.groupDefaultExpanded = defaultExpanded;
|
|
1083
1657
|
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Expansion state lives in two places, and they answer different questions.
|
|
1660
|
+
*
|
|
1661
|
+
* `OneGridExpansionService` records only *deliberate* changes — a click, or
|
|
1662
|
+
* one of the methods below. It is an override layer: `buildRowNodes` consults
|
|
1663
|
+
* it first and falls back to `groupDefaultExpanded` for every row the user
|
|
1664
|
+
* has not touched. That is what lets a later `groupDefaultExpanded` change
|
|
1665
|
+
* take effect, and what lets a collapse survive a rebuild.
|
|
1666
|
+
*
|
|
1667
|
+
* `node.expanded` is the resolved truth for a row, defaults included. Public
|
|
1668
|
+
* reads report that; the override sets stay internal.
|
|
1669
|
+
*
|
|
1670
|
+
* The invariant the setters below maintain: `expanded` is only ever true for
|
|
1671
|
+
* a node that can expand.
|
|
1672
|
+
*/
|
|
1084
1673
|
toggleExpandAll(expanded) {
|
|
1085
1674
|
this.forEachNode(node => {
|
|
1675
|
+
if (!node.expandable)
|
|
1676
|
+
return;
|
|
1086
1677
|
node.setExpanded(expanded);
|
|
1087
1678
|
this.expansionService.toggleExpanded(node.rowId, expanded);
|
|
1088
1679
|
});
|
|
@@ -1090,7 +1681,7 @@ class OneGridApi {
|
|
|
1090
1681
|
}
|
|
1091
1682
|
setExpandedById(rowId, expanded) {
|
|
1092
1683
|
const node = this._originalRowNodes.get(rowId);
|
|
1093
|
-
if (node) {
|
|
1684
|
+
if (node?.expandable) {
|
|
1094
1685
|
node.setExpanded(expanded);
|
|
1095
1686
|
this.expansionService.toggleExpanded(rowId, expanded);
|
|
1096
1687
|
}
|
|
@@ -1100,7 +1691,7 @@ class OneGridApi {
|
|
|
1100
1691
|
if (rowIds && rowIds.length > 0) {
|
|
1101
1692
|
for (let rowId of rowIds) {
|
|
1102
1693
|
const node = this._originalRowNodes.get(rowId);
|
|
1103
|
-
if (node) {
|
|
1694
|
+
if (node?.expandable) {
|
|
1104
1695
|
node.setExpanded(expanded);
|
|
1105
1696
|
this.expansionService.toggleExpanded(rowId, expanded);
|
|
1106
1697
|
}
|
|
@@ -1108,20 +1699,32 @@ class OneGridApi {
|
|
|
1108
1699
|
this.refreshVisibleNodes();
|
|
1109
1700
|
}
|
|
1110
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Every currently expanded row, in tree order.
|
|
1704
|
+
*
|
|
1705
|
+
* Derived from the nodes rather than read off the override record, so rows
|
|
1706
|
+
* opened by `groupDefaultExpanded` are included — previously this returned
|
|
1707
|
+
* only rows someone had explicitly toggled, and came back empty on first load
|
|
1708
|
+
* while the first level was visibly open.
|
|
1709
|
+
*/
|
|
1111
1710
|
getExpandedByIds() {
|
|
1112
|
-
return
|
|
1711
|
+
return this._rowNodes
|
|
1712
|
+
.filter(node => node.expandable && node.expanded)
|
|
1713
|
+
.map(node => node.rowId);
|
|
1113
1714
|
}
|
|
1114
1715
|
/**
|
|
1115
1716
|
* Master Detail Feature
|
|
1116
1717
|
*/
|
|
1117
1718
|
setDetailRowData(node, detailData) {
|
|
1719
|
+
if (this._destroyed)
|
|
1720
|
+
return;
|
|
1118
1721
|
const rowNode = this.getRowNodeById(node.rowId);
|
|
1119
1722
|
if (rowNode) {
|
|
1120
1723
|
rowNode.setDetailRowData(detailData);
|
|
1121
1724
|
}
|
|
1122
1725
|
}
|
|
1123
1726
|
setGetDetailRowData(getDetailRowData) {
|
|
1124
|
-
this.
|
|
1727
|
+
this._getDetailRowData = getDetailRowData;
|
|
1125
1728
|
}
|
|
1126
1729
|
/**
|
|
1127
1730
|
* Columns State
|
|
@@ -1130,15 +1733,17 @@ class OneGridApi {
|
|
|
1130
1733
|
return this.columnService.getColumnState();
|
|
1131
1734
|
}
|
|
1132
1735
|
setColumnDefs(columnDefs) {
|
|
1736
|
+
if (this._destroyed)
|
|
1737
|
+
return;
|
|
1133
1738
|
this.columnService.setColumnDefs(columnDefs);
|
|
1134
1739
|
const columnState = this.columnService.getColumnState();
|
|
1135
|
-
this.cellService.applyColumnDefs(columnState.columns, this.
|
|
1740
|
+
this.cellService.applyColumnDefs(columnState.columns, this._context);
|
|
1136
1741
|
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
1137
1742
|
}
|
|
1138
1743
|
updateColumn(id, changes) {
|
|
1139
1744
|
this.columnService.updateColumnDef(id, changes);
|
|
1140
1745
|
const columnState = this.columnService.getColumnState();
|
|
1141
|
-
this.cellService.applyColumnDefs(columnState.columns, this.
|
|
1746
|
+
this.cellService.applyColumnDefs(columnState.columns, this._context);
|
|
1142
1747
|
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
1143
1748
|
}
|
|
1144
1749
|
reorderColumnDefs(fromIndex, toIndex) {
|
|
@@ -1160,11 +1765,12 @@ class OneGridApi {
|
|
|
1160
1765
|
collect(node);
|
|
1161
1766
|
this.setSelectedByIds(Array.from(rowIds), selected);
|
|
1162
1767
|
}
|
|
1768
|
+
/**
|
|
1769
|
+
* @deprecated Identical to `updateCellStates`. Kept as an alias so existing
|
|
1770
|
+
* callers keep working; prefer `updateCellStates`, which says what it does.
|
|
1771
|
+
*/
|
|
1163
1772
|
updateNodeStates(node) {
|
|
1164
|
-
|
|
1165
|
-
if (rowNode) {
|
|
1166
|
-
rowNode.cellStates = this.cellService.generateCellStates(rowNode);
|
|
1167
|
-
}
|
|
1773
|
+
this.updateCellStates(node);
|
|
1168
1774
|
}
|
|
1169
1775
|
updateCellState(node, column) {
|
|
1170
1776
|
const rowNode = this.getRowNodeById(node.rowId);
|
|
@@ -1172,34 +1778,42 @@ class OneGridApi {
|
|
|
1172
1778
|
rowNode.cellStates[column.field] = this.cellService.generateCellState(rowNode, column);
|
|
1173
1779
|
}
|
|
1174
1780
|
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Recompute every cell of one row and repaint it.
|
|
1783
|
+
*
|
|
1784
|
+
* The repaint is the point: this is what a caller reaches for after mutating
|
|
1785
|
+
* a row in place, and it used to regenerate the state and stop there. The
|
|
1786
|
+
* body renders with OnPush, so nothing marked it dirty and the new state sat
|
|
1787
|
+
* unread until some unrelated event happened to trigger change detection —
|
|
1788
|
+
* which is why callers ended up nudging the grid with a synthetic DOM event.
|
|
1789
|
+
*/
|
|
1175
1790
|
updateCellStates(node) {
|
|
1791
|
+
if (this._destroyed)
|
|
1792
|
+
return;
|
|
1176
1793
|
const rowNode = this.getRowNodeById(node.rowId);
|
|
1177
|
-
if (rowNode)
|
|
1178
|
-
|
|
1179
|
-
|
|
1794
|
+
if (!rowNode)
|
|
1795
|
+
return;
|
|
1796
|
+
rowNode.cellStates = this.cellService.generateCellStates(rowNode);
|
|
1797
|
+
this.eventService.dispatchEvent('cellStateChanged', { data: rowNode.data, node: rowNode });
|
|
1180
1798
|
}
|
|
1181
1799
|
/**
|
|
1182
1800
|
* Handler that Reacts to Detail Changes
|
|
1183
1801
|
*/
|
|
1802
|
+
/**
|
|
1803
|
+
* Keep a master row's reserved height in step with its detail rows.
|
|
1804
|
+
*
|
|
1805
|
+
* Two listeners used to be registered here and thrown away: an empty
|
|
1806
|
+
* `rowDataChanged` handler whose unsubscribe was never kept, and an
|
|
1807
|
+
* `expandedChanged` handler whose body was entirely commented out. Both are
|
|
1808
|
+
* gone — a listener with no behaviour is a leak with no upside.
|
|
1809
|
+
*/
|
|
1184
1810
|
subscribeToDetailChanges(node) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const unsubDetail = node.on('detailRowDataChanged', ({ node, detailRowData, previousDetailRowData }) => {
|
|
1189
|
-
if (this.apiType === 'master') {
|
|
1190
|
-
this._resolvedDetailRowHeight(node);
|
|
1811
|
+
const unsubscribeDetail = node.on('detailRowDataChanged', ({ node }) => {
|
|
1812
|
+
if (this._apiType === 'master') {
|
|
1813
|
+
this.resolveDetailRowHeight(node);
|
|
1191
1814
|
}
|
|
1192
1815
|
});
|
|
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]);
|
|
1816
|
+
this.registerNodeCleanup(node.rowId, [unsubscribeDetail]);
|
|
1203
1817
|
}
|
|
1204
1818
|
registerNodeCleanup(rowId, unsubs) {
|
|
1205
1819
|
// Merge with any existing cleanups for this rowId
|
|
@@ -1211,7 +1825,14 @@ class OneGridApi {
|
|
|
1211
1825
|
cleanups?.forEach(fn => fn());
|
|
1212
1826
|
this._nodeCleanupMap.delete(rowId);
|
|
1213
1827
|
}
|
|
1214
|
-
|
|
1828
|
+
/**
|
|
1829
|
+
* Drop nodes that are no longer in the data, releasing what they hold.
|
|
1830
|
+
*
|
|
1831
|
+
* This existed but was never called: `setRowData` and `updateRowData` each
|
|
1832
|
+
* deleted straight out of `_originalRowNodes`, so a removed node kept its
|
|
1833
|
+
* listeners and its entry in `_nodeCleanupMap` for the lifetime of the grid.
|
|
1834
|
+
* Both now come through here, which unsubscribes and clears the node first.
|
|
1835
|
+
*/
|
|
1215
1836
|
pruneRemovedNodes(validRowIds) {
|
|
1216
1837
|
for (const [rowId, node] of this._originalRowNodes) {
|
|
1217
1838
|
if (!validRowIds.has(rowId)) {
|
|
@@ -1222,58 +1843,427 @@ class OneGridApi {
|
|
|
1222
1843
|
}
|
|
1223
1844
|
}
|
|
1224
1845
|
/**
|
|
1225
|
-
*
|
|
1846
|
+
* Height a master row must reserve for its detail grid:
|
|
1847
|
+
* rowHeight x clamp(rows, minRows, maxRows) + headerHeight.
|
|
1226
1848
|
*/
|
|
1227
|
-
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1849
|
+
resolveDetailRowHeight(node) {
|
|
1850
|
+
const heightOptions = this.detailCellRendererParams?.detailGridOptions?.detailRowHeightOptions;
|
|
1851
|
+
// Reachable whenever masterDetail is on but detailCellRendererParams is
|
|
1852
|
+
// incomplete; without the guard this threw from inside an event listener.
|
|
1853
|
+
if (!heightOptions)
|
|
1854
|
+
return;
|
|
1855
|
+
const minRows = heightOptions.minRows;
|
|
1856
|
+
const maxRows = heightOptions.maxRows;
|
|
1230
1857
|
const minHeight = (this.rowHeight * minRows) + this.headerRowHeight;
|
|
1231
1858
|
const maxHeight = (this.rowHeight * maxRows) + this.headerRowHeight;
|
|
1232
1859
|
const rows = node.detailRowData && node.detailRowData.length ? node.detailRowData.length : 0;
|
|
1233
1860
|
node.detailRowHeight = rows <= minRows ? minHeight : rows >= maxRows ? maxHeight : (this.rowHeight * rows) + this.headerRowHeight;
|
|
1234
1861
|
}
|
|
1235
1862
|
/**
|
|
1236
|
-
*
|
|
1863
|
+
* Release everything the grid holds. Called by `OneGridComponent.ngOnDestroy`.
|
|
1864
|
+
*
|
|
1865
|
+
* The services are deliberately *not* nulled out any more. They are injected,
|
|
1866
|
+
* Angular owns their lifetime, and nulling them only guaranteed a
|
|
1867
|
+
* `TypeError` for any callback that arrived after teardown — a detail-row
|
|
1868
|
+
* fetch resolving late, or the body's deferred update. `_destroyed` makes
|
|
1869
|
+
* those calls no-ops instead, which is what a torn-down object should do.
|
|
1237
1870
|
*/
|
|
1238
1871
|
destroy() {
|
|
1239
1872
|
if (this._destroyed)
|
|
1240
1873
|
return;
|
|
1241
1874
|
this._destroyed = true;
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1875
|
+
// Unsubscribe every per-node listener before dropping the nodes.
|
|
1876
|
+
for (const rowId of Array.from(this._nodeCleanupMap.keys())) {
|
|
1877
|
+
this.cleanupNode(rowId);
|
|
1878
|
+
}
|
|
1879
|
+
// `destroy` drops the tree links and the listeners; the API no longer has
|
|
1880
|
+
// to reach in and unpick each node itself.
|
|
1881
|
+
this._originalRowNodes.forEach(node => node.destroy());
|
|
1882
|
+
this._rowNodes.forEach(node => node.destroy());
|
|
1883
|
+
for (const side of ['top', 'bottom']) {
|
|
1884
|
+
this._pinnedRowNodes[side].forEach(node => node.destroy());
|
|
1885
|
+
this._pinnedRowNodes[side] = [];
|
|
1886
|
+
}
|
|
1887
|
+
this._rowNodes = [];
|
|
1888
|
+
this._visibleRowNodes = [];
|
|
1247
1889
|
this._originalRowNodes.clear();
|
|
1248
|
-
this.
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1890
|
+
this._nodeCleanupMap.clear();
|
|
1891
|
+
this._currentIndex = 0;
|
|
1892
|
+
// Release the back-reference the cell service holds, so a torn-down api is
|
|
1893
|
+
// not kept alive by the service that outlives this call.
|
|
1894
|
+
this.cellService.setGridApi(null);
|
|
1895
|
+
this.selectionService.deselectAll();
|
|
1896
|
+
this.expansionService.clear();
|
|
1897
|
+
this._activeFilters = [];
|
|
1898
|
+
this._quickFilterValue = null;
|
|
1899
|
+
this._sortModel = [];
|
|
1900
|
+
this._context = null;
|
|
1901
|
+
this.masterDetail = false;
|
|
1902
|
+
this.treeData = false;
|
|
1903
|
+
this.treeDataChildrenField = null;
|
|
1904
|
+
this.groupDefaultExpanded = 0;
|
|
1905
|
+
this.detailCellRendererParams = null;
|
|
1906
|
+
this.detailRendererComponent = null;
|
|
1907
|
+
this._getDetailRowData = null;
|
|
1908
|
+
}
|
|
1909
|
+
/** Whether `destroy` has run. Late callbacks check this before doing work. */
|
|
1910
|
+
get destroyed() {
|
|
1911
|
+
return this._destroyed;
|
|
1912
|
+
}
|
|
1913
|
+
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 }); }
|
|
1914
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi }); }
|
|
1915
|
+
}
|
|
1916
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridApi, decorators: [{
|
|
1917
|
+
type: Injectable
|
|
1918
|
+
}], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridRowService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridSelectionService }, { type: OneGridExpansionService }] });
|
|
1919
|
+
|
|
1920
|
+
/** Pointer distance before a press becomes a drag. */
|
|
1921
|
+
const DRAG_THRESHOLD_PX = 4;
|
|
1922
|
+
/** How close to an edge auto-scroll kicks in, and how fast it goes. */
|
|
1923
|
+
const EDGE_ZONE_PX = 36;
|
|
1924
|
+
const EDGE_SPEED_PX = 12;
|
|
1925
|
+
/**
|
|
1926
|
+
* Owns one row-drag gesture at a time.
|
|
1927
|
+
*
|
|
1928
|
+
* Built on pointer events rather than HTML5 drag-and-drop or the CDK's
|
|
1929
|
+
* drag-drop:
|
|
1930
|
+
*
|
|
1931
|
+
* - HTML5 DnD does not fire for touch at all, and its drag image cannot be
|
|
1932
|
+
* styled to match the grid.
|
|
1933
|
+
* - `cdkDropList` assumes every item it manages is in the DOM. Inside a virtual
|
|
1934
|
+
* scroll viewport rows unmount as they leave the buffer, so the list it
|
|
1935
|
+
* thinks it is sorting is only ever the visible window.
|
|
1936
|
+
* - Pointer events cover mouse, touch and pen in one path, and pointer capture
|
|
1937
|
+
* keeps the gesture alive when the finger leaves the row it started on.
|
|
1938
|
+
*
|
|
1939
|
+
* Because rows are a fixed `itemSize`, the drop target is arithmetic on the
|
|
1940
|
+
* pointer's Y rather than a hit test against row elements — which is also why
|
|
1941
|
+
* it stays correct for rows that are scrolled out and not rendered.
|
|
1942
|
+
*/
|
|
1943
|
+
class OneGridRowDragService {
|
|
1944
|
+
constructor() {
|
|
1945
|
+
this.zone = inject(NgZone);
|
|
1946
|
+
/** Live drag, for the body to render the handle state and the indicator. */
|
|
1947
|
+
this.state$ = new BehaviorSubject(null);
|
|
1948
|
+
/** Screen-reader narration for the keyboard path. */
|
|
1949
|
+
this.announcement$ = new BehaviorSubject('');
|
|
1950
|
+
this.context = null;
|
|
1951
|
+
this.pointerId = null;
|
|
1952
|
+
this.handle = null;
|
|
1953
|
+
this.startY = 0;
|
|
1954
|
+
this.armed = false;
|
|
1955
|
+
this.lastClientY = 0;
|
|
1956
|
+
this.scrollFrame = null;
|
|
1957
|
+
/** A move already applied during this drag, so a cancel can undo it. */
|
|
1958
|
+
this.appliedFrom = null;
|
|
1959
|
+
this.handlePointerMove = (event) => {
|
|
1960
|
+
if (this.pointerId === null || event.pointerId !== this.pointerId)
|
|
1961
|
+
return;
|
|
1962
|
+
if (!this.armed) {
|
|
1963
|
+
if (Math.abs(event.clientY - this.startY) < DRAG_THRESHOLD_PX)
|
|
1964
|
+
return;
|
|
1965
|
+
this.armed = true;
|
|
1966
|
+
this.publish({ rowId: this.nodeAt(this.appliedFrom)?.rowId ?? '', fromIndex: this.appliedFrom, insertAt: this.appliedFrom, source: 'pointer' });
|
|
1967
|
+
}
|
|
1968
|
+
// Held so the auto-scroll loop can recompute the target as the viewport
|
|
1969
|
+
// moves under a stationary finger.
|
|
1970
|
+
this.lastClientY = event.clientY;
|
|
1971
|
+
// Touch scrolling would otherwise pan the viewport out from under us.
|
|
1972
|
+
event.preventDefault();
|
|
1973
|
+
this.updateFromPointer();
|
|
1974
|
+
this.updateAutoScroll();
|
|
1975
|
+
};
|
|
1976
|
+
this.handlePointerUp = (event) => {
|
|
1977
|
+
if (this.pointerId === null || event.pointerId !== this.pointerId)
|
|
1978
|
+
return;
|
|
1979
|
+
const dragged = this.armed;
|
|
1980
|
+
this.teardownPointer();
|
|
1981
|
+
if (!dragged) {
|
|
1982
|
+
this.reset();
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
this.commit('pointer');
|
|
1986
|
+
};
|
|
1987
|
+
this.handlePointerCancel = () => {
|
|
1988
|
+
this.teardownPointer();
|
|
1989
|
+
this.cancel();
|
|
1990
|
+
};
|
|
1991
|
+
/** Escape aborts a pointer drag, the same as it does a keyboard one. */
|
|
1992
|
+
this.handlePointerKeydown = (event) => {
|
|
1993
|
+
if (event.key !== 'Escape')
|
|
1994
|
+
return;
|
|
1995
|
+
event.preventDefault();
|
|
1996
|
+
this.teardownPointer();
|
|
1997
|
+
this.cancel();
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
attach(context) {
|
|
2001
|
+
this.context = context;
|
|
2002
|
+
}
|
|
2003
|
+
get state() {
|
|
2004
|
+
return this.state$.value;
|
|
2005
|
+
}
|
|
2006
|
+
get dragging() {
|
|
2007
|
+
return this.state$.value !== null;
|
|
2008
|
+
}
|
|
2009
|
+
// -------------------------------------------------------------------------
|
|
2010
|
+
// Pointer
|
|
2011
|
+
// -------------------------------------------------------------------------
|
|
2012
|
+
/**
|
|
2013
|
+
* Begin a pointer drag. Nothing visible happens until the pointer has moved
|
|
2014
|
+
* {@link DRAG_THRESHOLD_PX}, so a click on the handle — which is also the
|
|
2015
|
+
* keyboard affordance — is not swallowed by a zero-distance drag.
|
|
2016
|
+
*/
|
|
2017
|
+
startPointerDrag(event, node, handlers = {}) {
|
|
2018
|
+
if (!this.context || this.dragging || this.pointerId !== null)
|
|
2019
|
+
return;
|
|
2020
|
+
// Secondary buttons open context menus; they are not a drag.
|
|
2021
|
+
if (event.button !== 0)
|
|
2022
|
+
return;
|
|
2023
|
+
const index = this.indexOf(node);
|
|
2024
|
+
if (index === -1)
|
|
2025
|
+
return;
|
|
2026
|
+
this.pointerId = event.pointerId;
|
|
2027
|
+
this.handle = event.target;
|
|
2028
|
+
this.startY = event.clientY;
|
|
2029
|
+
this.lastClientY = event.clientY;
|
|
2030
|
+
this.armed = false;
|
|
2031
|
+
this.appliedFrom = index;
|
|
2032
|
+
this.onEnd = handlers.onEnd;
|
|
2033
|
+
this.handle.setPointerCapture?.(event.pointerId);
|
|
2034
|
+
// Outside Angular: pointermove fires at the display's refresh rate and
|
|
2035
|
+
// every one of them would otherwise run change detection over the grid.
|
|
2036
|
+
// The few that change the drop target re-enter through `publish`.
|
|
2037
|
+
this.zone.runOutsideAngular(() => {
|
|
2038
|
+
document.addEventListener('pointermove', this.handlePointerMove, { passive: false });
|
|
2039
|
+
document.addEventListener('pointerup', this.handlePointerUp);
|
|
2040
|
+
document.addEventListener('pointercancel', this.handlePointerCancel);
|
|
2041
|
+
document.addEventListener('keydown', this.handlePointerKeydown);
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Turn the pointer's Y into an insertion boundary.
|
|
2046
|
+
*
|
|
2047
|
+
* Boundaries, not rows: the value is where the row would be *inserted*, so
|
|
2048
|
+
* 0 is above the first row and `length` is below the last. Rounding rather
|
|
2049
|
+
* than flooring puts the split at the middle of each row, which is what
|
|
2050
|
+
* makes the indicator flip when you cross the halfway point.
|
|
2051
|
+
*/
|
|
2052
|
+
updateFromPointer() {
|
|
2053
|
+
const current = this.state$.value;
|
|
2054
|
+
if (!this.context || !current)
|
|
2055
|
+
return;
|
|
2056
|
+
const rect = this.context.viewport.getBoundingClientRect();
|
|
2057
|
+
const offset = this.lastClientY - rect.top + this.context.viewport.scrollTop;
|
|
2058
|
+
const insertAt = this.clamp(Math.round(offset / this.context.itemSize), 0, this.context.nodes().length);
|
|
2059
|
+
if (insertAt === current.insertAt)
|
|
2060
|
+
return;
|
|
2061
|
+
this.publish({ ...current, insertAt });
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Scroll when the pointer sits near an edge, so rows outside the viewport
|
|
2065
|
+
* are reachable without letting go. Runs on rAF while the pointer is parked
|
|
2066
|
+
* in the zone, and re-derives the target each frame because the content
|
|
2067
|
+
* moves while the pointer does not.
|
|
2068
|
+
*/
|
|
2069
|
+
updateAutoScroll() {
|
|
2070
|
+
if (!this.context)
|
|
2071
|
+
return;
|
|
2072
|
+
const rect = this.context.viewport.getBoundingClientRect();
|
|
2073
|
+
const distanceToTop = this.lastClientY - rect.top;
|
|
2074
|
+
const distanceToBottom = rect.bottom - this.lastClientY;
|
|
2075
|
+
let delta = 0;
|
|
2076
|
+
if (distanceToTop < EDGE_ZONE_PX)
|
|
2077
|
+
delta = -EDGE_SPEED_PX;
|
|
2078
|
+
else if (distanceToBottom < EDGE_ZONE_PX)
|
|
2079
|
+
delta = EDGE_SPEED_PX;
|
|
2080
|
+
if (delta === 0) {
|
|
2081
|
+
this.stopAutoScroll();
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
if (this.scrollFrame !== null)
|
|
2085
|
+
return;
|
|
2086
|
+
const step = () => {
|
|
2087
|
+
if (!this.context || !this.dragging) {
|
|
2088
|
+
this.stopAutoScroll();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
const viewport = this.context.viewport;
|
|
2092
|
+
const before = viewport.scrollTop;
|
|
2093
|
+
viewport.scrollTop = before + delta;
|
|
2094
|
+
// Hit the end — nothing more to reveal, so stop burning frames.
|
|
2095
|
+
if (viewport.scrollTop === before) {
|
|
2096
|
+
this.stopAutoScroll();
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
this.updateFromPointer();
|
|
2100
|
+
this.scrollFrame = requestAnimationFrame(step);
|
|
2101
|
+
};
|
|
2102
|
+
this.scrollFrame = requestAnimationFrame(step);
|
|
2103
|
+
}
|
|
2104
|
+
stopAutoScroll() {
|
|
2105
|
+
if (this.scrollFrame === null)
|
|
2106
|
+
return;
|
|
2107
|
+
cancelAnimationFrame(this.scrollFrame);
|
|
2108
|
+
this.scrollFrame = null;
|
|
2109
|
+
}
|
|
2110
|
+
teardownPointer() {
|
|
2111
|
+
this.stopAutoScroll();
|
|
2112
|
+
if (this.handle && this.pointerId !== null) {
|
|
2113
|
+
this.handle.releasePointerCapture?.(this.pointerId);
|
|
2114
|
+
}
|
|
2115
|
+
document.removeEventListener('pointermove', this.handlePointerMove);
|
|
2116
|
+
document.removeEventListener('pointerup', this.handlePointerUp);
|
|
2117
|
+
document.removeEventListener('pointercancel', this.handlePointerCancel);
|
|
2118
|
+
document.removeEventListener('keydown', this.handlePointerKeydown);
|
|
2119
|
+
this.pointerId = null;
|
|
2120
|
+
this.handle = null;
|
|
2121
|
+
this.armed = false;
|
|
2122
|
+
}
|
|
2123
|
+
// -------------------------------------------------------------------------
|
|
2124
|
+
// Keyboard
|
|
2125
|
+
//
|
|
2126
|
+
// WCAG 2.5.7 Dragging Movements: anything achievable by dragging needs a
|
|
2127
|
+
// single-pointer, non-dragging alternative. Arrow-key moves are that, and
|
|
2128
|
+
// they are also simply how a keyboard user reorders a list.
|
|
2129
|
+
// -------------------------------------------------------------------------
|
|
2130
|
+
/** Space/Enter on the handle: pick the row up, or put it down. */
|
|
2131
|
+
toggleKeyboardDrag(node, handlers = {}) {
|
|
2132
|
+
if (!this.context)
|
|
2133
|
+
return;
|
|
2134
|
+
if (this.state$.value?.source === 'keyboard') {
|
|
2135
|
+
this.commit('keyboard');
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (this.dragging)
|
|
2139
|
+
return;
|
|
2140
|
+
const index = this.indexOf(node);
|
|
2141
|
+
if (index === -1)
|
|
2142
|
+
return;
|
|
2143
|
+
this.appliedFrom = index;
|
|
2144
|
+
this.onEnd = handlers.onEnd;
|
|
2145
|
+
this.publish({ rowId: node.rowId, fromIndex: index, insertAt: index, source: 'keyboard' });
|
|
2146
|
+
this.announce(`${this.describe(node, index)} grabbed. Use the arrow keys to move it, space to drop, escape to cancel.`);
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Arrow keys move the row a position at a time, applying as they go.
|
|
2150
|
+
*
|
|
2151
|
+
* Applied rather than previewed because the row has to be seen to move: a
|
|
2152
|
+
* keyboard user gets no drag image, and a preview line alone tells someone
|
|
2153
|
+
* relying on a screen reader nothing about where the row now sits. Escape
|
|
2154
|
+
* still restores the original position.
|
|
2155
|
+
*/
|
|
2156
|
+
moveKeyboard(delta) {
|
|
2157
|
+
const current = this.state$.value;
|
|
2158
|
+
if (!this.context || current?.source !== 'keyboard')
|
|
2159
|
+
return false;
|
|
2160
|
+
const nodes = this.context.nodes();
|
|
2161
|
+
const from = current.insertAt;
|
|
2162
|
+
const to = this.clamp(from + delta, 0, nodes.length - 1);
|
|
2163
|
+
if (to === from) {
|
|
2164
|
+
this.announce(to === 0 ? 'Already at the top.' : 'Already at the bottom.');
|
|
2165
|
+
return true;
|
|
2166
|
+
}
|
|
2167
|
+
if (!this.context.move(from, to))
|
|
2168
|
+
return true;
|
|
2169
|
+
this.publish({ ...current, insertAt: to });
|
|
2170
|
+
const node = this.nodeAt(to);
|
|
2171
|
+
this.announce(node ? `Moved to position ${to + 1} of ${nodes.length}.` : '');
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
// -------------------------------------------------------------------------
|
|
2175
|
+
// Commit / cancel
|
|
2176
|
+
// -------------------------------------------------------------------------
|
|
2177
|
+
commit(source) {
|
|
2178
|
+
const current = this.state$.value;
|
|
2179
|
+
if (!this.context || !current) {
|
|
2180
|
+
this.reset();
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
const nodes = this.context.nodes();
|
|
2184
|
+
// Pointer drags apply once, at the end. A boundary index becomes a row
|
|
2185
|
+
// index by dropping one when the row travelled downwards, since lifting
|
|
2186
|
+
// it out first shifts everything below it up.
|
|
2187
|
+
if (source === 'pointer') {
|
|
2188
|
+
const from = current.fromIndex;
|
|
2189
|
+
const to = this.clamp(current.insertAt > from ? current.insertAt - 1 : current.insertAt, 0, nodes.length - 1);
|
|
2190
|
+
const node = this.nodeAt(from);
|
|
2191
|
+
if (node && to !== from && this.context.move(from, to)) {
|
|
2192
|
+
this.finish({ node, fromIndex: from, toIndex: to, source });
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
this.reset();
|
|
2196
|
+
return;
|
|
1252
2197
|
}
|
|
1253
|
-
this
|
|
1254
|
-
|
|
1255
|
-
|
|
2198
|
+
// Keyboard moves were applied as they happened; this only confirms.
|
|
2199
|
+
const node = this.nodeAt(current.insertAt);
|
|
2200
|
+
const moved = node && current.insertAt !== current.fromIndex;
|
|
2201
|
+
this.announce(moved ? `Dropped at position ${current.insertAt + 1} of ${nodes.length}.` : 'Cancelled. Row returned to its original position.');
|
|
2202
|
+
if (moved) {
|
|
2203
|
+
this.finish({ node: node, fromIndex: current.fromIndex, toIndex: current.insertAt, source });
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
this.reset();
|
|
2207
|
+
}
|
|
2208
|
+
/** Abort, restoring anything a keyboard drag already applied. */
|
|
2209
|
+
cancel() {
|
|
2210
|
+
const current = this.state$.value;
|
|
2211
|
+
if (current?.source === 'keyboard' && this.context && current.insertAt !== current.fromIndex) {
|
|
2212
|
+
this.context.move(current.insertAt, current.fromIndex);
|
|
2213
|
+
this.announce('Cancelled. Row returned to its original position.');
|
|
2214
|
+
}
|
|
2215
|
+
else if (current) {
|
|
2216
|
+
this.announce('Cancelled.');
|
|
2217
|
+
}
|
|
2218
|
+
this.reset();
|
|
2219
|
+
}
|
|
2220
|
+
finish(event) {
|
|
2221
|
+
const done = this.onEnd;
|
|
2222
|
+
this.reset();
|
|
2223
|
+
this.zone.run(() => done?.(event));
|
|
2224
|
+
}
|
|
2225
|
+
reset() {
|
|
2226
|
+
this.stopAutoScroll();
|
|
2227
|
+
this.appliedFrom = null;
|
|
2228
|
+
this.onEnd = undefined;
|
|
2229
|
+
if (this.state$.value !== null) {
|
|
2230
|
+
this.zone.run(() => this.state$.next(null));
|
|
1256
2231
|
}
|
|
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
2232
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
2233
|
+
// -------------------------------------------------------------------------
|
|
2234
|
+
// Helpers
|
|
2235
|
+
// -------------------------------------------------------------------------
|
|
2236
|
+
publish(state) {
|
|
2237
|
+
this.zone.run(() => this.state$.next(state));
|
|
2238
|
+
}
|
|
2239
|
+
announce(message) {
|
|
2240
|
+
this.zone.run(() => this.announcement$.next(message));
|
|
2241
|
+
}
|
|
2242
|
+
describe(node, index) {
|
|
2243
|
+
return `Row ${index + 1} of ${this.context?.nodes().length ?? 0},`;
|
|
2244
|
+
}
|
|
2245
|
+
indexOf(node) {
|
|
2246
|
+
return this.context?.nodes().findIndex(candidate => candidate.rowId === node.rowId) ?? -1;
|
|
2247
|
+
}
|
|
2248
|
+
nodeAt(index) {
|
|
2249
|
+
return this.context?.nodes()[index] ?? null;
|
|
2250
|
+
}
|
|
2251
|
+
clamp(value, min, max) {
|
|
2252
|
+
return Math.min(Math.max(value, min), max);
|
|
2253
|
+
}
|
|
2254
|
+
ngOnDestroy() {
|
|
2255
|
+
this.teardownPointer();
|
|
2256
|
+
this.state$.next(null);
|
|
2257
|
+
this.state$.complete();
|
|
2258
|
+
this.announcement$.complete();
|
|
2259
|
+
this.context = null;
|
|
2260
|
+
}
|
|
2261
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2262
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService }); }
|
|
1273
2263
|
}
|
|
1274
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type:
|
|
2264
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragService, decorators: [{
|
|
1275
2265
|
type: Injectable
|
|
1276
|
-
}]
|
|
2266
|
+
}] });
|
|
1277
2267
|
|
|
1278
2268
|
class OneGridTreeDataService {
|
|
1279
2269
|
create(tree, parentId, newNode) {
|
|
@@ -1334,6 +2324,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1334
2324
|
|
|
1335
2325
|
const OnePageSizeOption = [25, 50, 100, 250];
|
|
1336
2326
|
|
|
2327
|
+
function resolveRowHeight(params, getRowHeight, rowHeight = 26) {
|
|
2328
|
+
if (getRowHeight) {
|
|
2329
|
+
const result = getRowHeight(params);
|
|
2330
|
+
// fallback behavior like AG Grid
|
|
2331
|
+
if (result == null) {
|
|
2332
|
+
return rowHeight;
|
|
2333
|
+
}
|
|
2334
|
+
// guard against invalid values
|
|
2335
|
+
return Math.max(1, result);
|
|
2336
|
+
}
|
|
2337
|
+
return rowHeight;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
1337
2340
|
class HeaderCheckboxDisabledPipe {
|
|
1338
2341
|
transform(rowNodes, selectionMode, allowedEdit) {
|
|
1339
2342
|
return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
|
|
@@ -1384,6 +2387,8 @@ class OneGridCheckboxHeaderComponent {
|
|
|
1384
2387
|
this.eventService = eventService;
|
|
1385
2388
|
this.selectionService = selectionService;
|
|
1386
2389
|
this.allowedEdit = false;
|
|
2390
|
+
/** Unsubscribe callbacks for the two grid listeners below. */
|
|
2391
|
+
this.teardown = [];
|
|
1387
2392
|
this.state = 'unchecked';
|
|
1388
2393
|
this.rowNodes = [];
|
|
1389
2394
|
}
|
|
@@ -1392,19 +2397,26 @@ class OneGridCheckboxHeaderComponent {
|
|
|
1392
2397
|
/**
|
|
1393
2398
|
* Listen Row Node Selection Changed Event
|
|
1394
2399
|
*/
|
|
1395
|
-
this.eventService.addEventListener('selectionChanged', (payload) => {
|
|
2400
|
+
this.teardown.push(this.eventService.addEventListener('selectionChanged', (payload) => {
|
|
1396
2401
|
this.rowNodes = this.api.getAllRowNodes();
|
|
1397
2402
|
const rowIds = this.rowNodes && this.rowNodes.length > 0 ? this.rowNodes.map((node) => node.rowId) : [];
|
|
1398
2403
|
this.state = this.selectionService.getSelectAllState(rowIds);
|
|
1399
|
-
});
|
|
2404
|
+
}));
|
|
1400
2405
|
/**
|
|
1401
2406
|
* Listen Row Data Changed Event
|
|
1402
2407
|
*/
|
|
1403
|
-
this.eventService.addEventListener('rowDataChanged', (payload) => {
|
|
2408
|
+
this.teardown.push(this.eventService.addEventListener('rowDataChanged', (payload) => {
|
|
1404
2409
|
this.rowNodes = this.api.getAllRowNodes();
|
|
1405
2410
|
const rowIds = this.rowNodes && this.rowNodes.length > 0 ? this.rowNodes.map((node) => node.rowId) : [];
|
|
1406
2411
|
this.state = this.selectionService.getSelectAllState(rowIds);
|
|
1407
|
-
});
|
|
2412
|
+
}));
|
|
2413
|
+
}
|
|
2414
|
+
ngOnDestroy() {
|
|
2415
|
+
// Both listeners were registered and never released. The component-scoped
|
|
2416
|
+
// event service happened to clean up after them; relying on that left the
|
|
2417
|
+
// component's own lifecycle incomplete.
|
|
2418
|
+
this.teardown.forEach(unsubscribe => unsubscribe());
|
|
2419
|
+
this.teardown.length = 0;
|
|
1408
2420
|
}
|
|
1409
2421
|
/**
|
|
1410
2422
|
* Toggle Selection Action
|
|
@@ -1435,6 +2447,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1435
2447
|
}] } });
|
|
1436
2448
|
|
|
1437
2449
|
class ColumnSortStatePipe {
|
|
2450
|
+
// `readonly`, because this is fed straight from `api.sortModel`, which is
|
|
2451
|
+
// now a readonly view rather than the mutable array it used to hand out.
|
|
1438
2452
|
transform(field, sortModel) {
|
|
1439
2453
|
if (!sortModel || !field)
|
|
1440
2454
|
return null;
|
|
@@ -1452,16 +2466,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1452
2466
|
}] });
|
|
1453
2467
|
|
|
1454
2468
|
class OneGridHeaderComponent {
|
|
2469
|
+
/**
|
|
2470
|
+
* Horizontal offset of the body, mirrored here so the header tracks it.
|
|
2471
|
+
*
|
|
2472
|
+
* Applied to the viewport's `scrollLeft` rather than as a transform on the
|
|
2473
|
+
* row: sticky positioning needs a real scrollport above it. The group row,
|
|
2474
|
+
* which is a sibling of that viewport, still reads this for its transform.
|
|
2475
|
+
*/
|
|
2476
|
+
set scrollLeft(value) {
|
|
2477
|
+
this._scrollLeft = value ?? 0;
|
|
2478
|
+
this.applyScrollLeft();
|
|
2479
|
+
}
|
|
2480
|
+
get scrollLeft() {
|
|
2481
|
+
return this._scrollLeft;
|
|
2482
|
+
}
|
|
1455
2483
|
constructor(eventService, cdr) {
|
|
1456
2484
|
this.eventService = eventService;
|
|
1457
2485
|
this.cdr = cdr;
|
|
1458
|
-
this.
|
|
2486
|
+
this._scrollLeft = 0;
|
|
1459
2487
|
this.allowedEdit = false;
|
|
1460
2488
|
this.enableGroupColumnDefs = false;
|
|
1461
2489
|
this.groupColumnDefs = [];
|
|
1462
2490
|
this.columnDefs = [];
|
|
1463
2491
|
this.sortModel = [];
|
|
1464
|
-
this.destroy$ = new Subject();
|
|
1465
2492
|
this.subscriptions = new Subscription();
|
|
1466
2493
|
this.draggedIndex = null;
|
|
1467
2494
|
// Track resize state
|
|
@@ -1503,6 +2530,16 @@ class OneGridHeaderComponent {
|
|
|
1503
2530
|
// this.cdr.markForCheck();
|
|
1504
2531
|
}));
|
|
1505
2532
|
}
|
|
2533
|
+
ngAfterViewInit() {
|
|
2534
|
+
// The input setter can run before the view exists, so whatever arrived
|
|
2535
|
+
// first is applied once here.
|
|
2536
|
+
this.applyScrollLeft();
|
|
2537
|
+
}
|
|
2538
|
+
applyScrollLeft() {
|
|
2539
|
+
if (this.headerViewport) {
|
|
2540
|
+
this.headerViewport.nativeElement.scrollLeft = this._scrollLeft;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
1506
2543
|
onHeaderMenuOptionClick(columnDef) {
|
|
1507
2544
|
}
|
|
1508
2545
|
onHeaderMenuSortClick(colId) {
|
|
@@ -1528,7 +2565,12 @@ class OneGridHeaderComponent {
|
|
|
1528
2565
|
this.resizing = true;
|
|
1529
2566
|
this.resizeColumnIndex = columnIndex;
|
|
1530
2567
|
this.startX = event.clientX;
|
|
1531
|
-
|
|
2568
|
+
// Measure what is on screen rather than trusting `col.width`, which is
|
|
2569
|
+
// optional: a column sized purely by minWidth had `undefined` here, and
|
|
2570
|
+
// `Math.max(50, undefined + deltaX)` is NaN — the column collapsed on the
|
|
2571
|
+
// first mouse move and could not be recovered.
|
|
2572
|
+
const columnElement = event.target?.closest('.one-grid-header-column');
|
|
2573
|
+
this.startWidth = columnElement?.offsetWidth || resolveColumnWidth(this.columnDefs[columnIndex]);
|
|
1532
2574
|
// Add document-level listeners for drag and release
|
|
1533
2575
|
document.addEventListener('mousemove', this.onResizeMove);
|
|
1534
2576
|
document.addEventListener('mouseup', this.onResizeEnd);
|
|
@@ -1561,19 +2603,27 @@ class OneGridHeaderComponent {
|
|
|
1561
2603
|
event.preventDefault();
|
|
1562
2604
|
this.draggedIndex = null;
|
|
1563
2605
|
}
|
|
1564
|
-
/**
|
|
1565
|
-
* Clear All Subscriptions
|
|
1566
|
-
*/
|
|
1567
2606
|
ngOnDestroy() {
|
|
1568
|
-
|
|
2607
|
+
// This completed an unused Subject and left the real subscription running.
|
|
2608
|
+
// It survived only because OneGridEventService is component-scoped and
|
|
2609
|
+
// tears its own listeners down — the header was relying on that by accident.
|
|
2610
|
+
this.subscriptions.unsubscribe();
|
|
2611
|
+
// A resize in flight owns two listeners on `document`. Without this they
|
|
2612
|
+
// outlive the component, and their closures keep it alive with them.
|
|
2613
|
+
if (this.resizing) {
|
|
2614
|
+
this.onResizeEnd();
|
|
2615
|
+
}
|
|
1569
2616
|
}
|
|
1570
2617
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, deps: [{ token: OneGridEventService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
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\"
|
|
2618
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: { scrollLeft: "scrollLeft", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", api: "api" }, viewQueries: [{ propertyName: "headerViewport", first: true, predicate: ["headerViewport"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"one-grid-header\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n </div>\n</div>\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <div class=\"og-resize-handle\" (mousedown)=\"onResizeStart($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </div>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuOptionClick(col)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </div>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: ["api", "allowedEdit"] }, { kind: "pipe", type: ColumnSortStatePipe, name: "columnSortState" }] }); }
|
|
1572
2619
|
}
|
|
1573
2620
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, decorators: [{
|
|
1574
2621
|
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: {
|
|
2622
|
+
args: [{ standalone: false, selector: 'one-grid-header', template: "<div class=\"one-grid-header\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n </div>\n</div>\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <div class=\"og-resize-handle\" (mousedown)=\"onResizeStart($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </div>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuOptionClick(col)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </div>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n" }]
|
|
2623
|
+
}], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }], propDecorators: { headerViewport: [{
|
|
2624
|
+
type: ViewChild,
|
|
2625
|
+
args: ['headerViewport', { static: true }]
|
|
2626
|
+
}], scrollLeft: [{
|
|
1577
2627
|
type: Input
|
|
1578
2628
|
}], allowedEdit: [{
|
|
1579
2629
|
type: Input
|
|
@@ -1654,31 +2704,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1654
2704
|
type: Output
|
|
1655
2705
|
}] } });
|
|
1656
2706
|
|
|
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
2707
|
class OneGridRowActionComponent {
|
|
1681
|
-
constructor() {
|
|
2708
|
+
constructor(cellService) {
|
|
2709
|
+
this.cellService = cellService;
|
|
1682
2710
|
this.allowedEdit = false;
|
|
1683
2711
|
this.masterDetail = false;
|
|
1684
2712
|
this.masterNode = null;
|
|
@@ -1701,6 +2729,51 @@ class OneGridRowActionComponent {
|
|
|
1701
2729
|
this.onRowOption = new EventEmitter();
|
|
1702
2730
|
this.onRowLanguage = new EventEmitter();
|
|
1703
2731
|
this.onRowCellOption = new EventEmitter();
|
|
2732
|
+
/**
|
|
2733
|
+
* Accessible names for the action buttons.
|
|
2734
|
+
*
|
|
2735
|
+
* Every one of them is an icon with no text, and none carried a label — a
|
|
2736
|
+
* screen reader announced "button" and nothing more, fourteen times per row.
|
|
2737
|
+
* These are also the `title`, so the icons are identifiable by pointer too.
|
|
2738
|
+
*/
|
|
2739
|
+
this.labels = {
|
|
2740
|
+
gotoDetail: 'Open detail',
|
|
2741
|
+
addNew: 'Add new row',
|
|
2742
|
+
view: 'View row',
|
|
2743
|
+
location: 'Show location',
|
|
2744
|
+
document: 'Open document',
|
|
2745
|
+
copy: 'Copy row',
|
|
2746
|
+
delete: 'Delete row',
|
|
2747
|
+
gotoList: 'Open list',
|
|
2748
|
+
edit: 'Edit row',
|
|
2749
|
+
print: 'Print row',
|
|
2750
|
+
history: 'View history',
|
|
2751
|
+
option: 'Row options',
|
|
2752
|
+
language: 'Change language',
|
|
2753
|
+
cellOption: 'Cell options',
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Whether this column's action buttons are hidden for this row.
|
|
2758
|
+
*
|
|
2759
|
+
* The templates read `column?.hideActionButton` directly, which is truthy for
|
|
2760
|
+
* any function — so a predicate asking to hide the buttons showed them, and
|
|
2761
|
+
* one asking to show them showed them too. Resolved here instead, with the
|
|
2762
|
+
* same params every other row-scoped predicate gets.
|
|
2763
|
+
*/
|
|
2764
|
+
get hideActionButton() {
|
|
2765
|
+
return this.cellService.hideActionButtonFn(this.column, this.node);
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* Whether this column's action buttons are disabled for this row.
|
|
2769
|
+
*
|
|
2770
|
+
* `disable` used to be resolved by `CellDisableResolverPipe`, which built its
|
|
2771
|
+
* own params — a different set from every other row-scoped predicate. It goes
|
|
2772
|
+
* through the shared builder now, so `disable` sees exactly what `editable`
|
|
2773
|
+
* and `hideActionButton` see.
|
|
2774
|
+
*/
|
|
2775
|
+
get actionDisabled() {
|
|
2776
|
+
return this.cellService.disableFn(this.column, this.node) || !this.allowedEdit;
|
|
1704
2777
|
}
|
|
1705
2778
|
ngOnInit() {
|
|
1706
2779
|
this.detailActiveExcludeLevels = new Set(this.column.cellRendererParams?.detailActiveExcludeLevels ?? []);
|
|
@@ -1811,13 +2884,13 @@ class OneGridRowActionComponent {
|
|
|
1811
2884
|
}
|
|
1812
2885
|
this.onRowCellOption.emit(data);
|
|
1813
2886
|
}
|
|
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 </
|
|
2887
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, deps: [{ token: OneGridCellService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2888
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: { column: "column", node: "node", allowedEdit: "allowedEdit", masterDetail: "masterDetail", masterNode: "masterNode", cellRendererParams: "cellRendererParams", gridType: "gridType", context: "context" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption" }, ngImport: i0, template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
|
|
1816
2889
|
}
|
|
1817
2890
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, decorators: [{
|
|
1818
2891
|
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: [{
|
|
2892
|
+
args: [{ standalone: false, selector: 'one-grid-row-action', template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}" }]
|
|
2893
|
+
}], ctorParameters: () => [{ type: OneGridCellService }], propDecorators: { column: [{
|
|
1821
2894
|
type: Input,
|
|
1822
2895
|
args: [{ required: true }]
|
|
1823
2896
|
}], node: [{
|
|
@@ -1871,18 +2944,21 @@ class OneGridCellGroupRendererComponent {
|
|
|
1871
2944
|
this.masterDetail = false;
|
|
1872
2945
|
this.expansionChanged = new EventEmitter();
|
|
1873
2946
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2947
|
+
/**
|
|
2948
|
+
* Presentational only: report the click and let the body, which holds the
|
|
2949
|
+
* API, perform the toggle. The click is stopped from bubbling so it is not
|
|
2950
|
+
* also read as a cell click by the cell underneath.
|
|
2951
|
+
*/
|
|
2952
|
+
invokeChangedClick(event) {
|
|
2953
|
+
event.stopPropagation();
|
|
1878
2954
|
this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
|
|
1879
2955
|
}
|
|
1880
2956
|
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\"
|
|
2957
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: { node: "node", masterDetail: "masterDetail", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent" }, outputs: { expansionChanged: "expansionChanged" }, ngImport: i0, template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n", styles: [""] }); }
|
|
1882
2958
|
}
|
|
1883
2959
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
|
|
1884
2960
|
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\"
|
|
2961
|
+
args: [{ standalone: false, selector: 'one-grid-cell-group-renderer', template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n" }]
|
|
1886
2962
|
}], propDecorators: { node: [{
|
|
1887
2963
|
type: Input
|
|
1888
2964
|
}], masterDetail: [{
|
|
@@ -2000,8 +3076,15 @@ class OneCellRendererComponentDirective {
|
|
|
2000
3076
|
return;
|
|
2001
3077
|
const instance = this.compRef.instance;
|
|
2002
3078
|
if (typeof instance.oneInit === 'function') {
|
|
3079
|
+
// The body supplies `value` already, and it honours the column's
|
|
3080
|
+
// valueGetter. Only derive it here when nothing did — this used to
|
|
3081
|
+
// overwrite unconditionally with the raw field, so a column with both a
|
|
3082
|
+
// valueGetter and a component renderer showed the underlying field
|
|
3083
|
+
// instead of the value every other part of the grid displays.
|
|
2003
3084
|
const newParams = { ...this.params };
|
|
2004
|
-
newParams.value
|
|
3085
|
+
if (newParams.value === undefined) {
|
|
3086
|
+
newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
|
|
3087
|
+
}
|
|
2005
3088
|
instance.oneInit(newParams);
|
|
2006
3089
|
}
|
|
2007
3090
|
else {
|
|
@@ -2038,68 +3121,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2038
3121
|
args: [{ required: true }]
|
|
2039
3122
|
}] } });
|
|
2040
3123
|
|
|
3124
|
+
/**
|
|
3125
|
+
* Reveals a truncated cell in full while the pointer rests on it.
|
|
3126
|
+
*
|
|
3127
|
+
* Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
|
|
3128
|
+
* used to be applied as inline styles here, one of which was
|
|
3129
|
+
* `background: var(--ins-card-bg)` — a token from the application this library
|
|
3130
|
+
* was extracted from, which no consumer defines. The expanded cell therefore
|
|
3131
|
+
* had a transparent background and its text overlapped the next column. The
|
|
3132
|
+
* inline rules also set `z-index` on a statically positioned element, where it
|
|
3133
|
+
* does nothing.
|
|
3134
|
+
*
|
|
3135
|
+
* Overflow is measured on hover rather than after every view check. The old
|
|
3136
|
+
* version measured on init and on every text change, and reading `scrollWidth`
|
|
3137
|
+
* forces a synchronous layout — several hundred of them per render for a
|
|
3138
|
+
* normal viewport, to answer a question only the hovered cell ever asks.
|
|
3139
|
+
*/
|
|
2041
3140
|
class OneCellTextExpandDirective {
|
|
3141
|
+
static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
|
|
3142
|
+
static { this.HOVER_DELAY_MS = 800; }
|
|
2042
3143
|
constructor(cell, renderer) {
|
|
2043
3144
|
this.cell = cell;
|
|
2044
3145
|
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
3146
|
}
|
|
2071
3147
|
onMouseEnter() {
|
|
2072
|
-
|
|
3148
|
+
const element = this.cell.nativeElement;
|
|
3149
|
+
// Measured here, so the layout read happens once for the cell being
|
|
3150
|
+
// hovered instead of for every cell on screen.
|
|
3151
|
+
if (element.scrollWidth <= element.clientWidth)
|
|
2073
3152
|
return;
|
|
2074
|
-
this.timer = setTimeout(() => this.expand(),
|
|
3153
|
+
this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
|
|
2075
3154
|
}
|
|
2076
3155
|
onMouseLeave() {
|
|
2077
|
-
|
|
3156
|
+
this.collapse();
|
|
3157
|
+
}
|
|
3158
|
+
ngOnDestroy() {
|
|
2078
3159
|
this.collapse();
|
|
2079
3160
|
}
|
|
2080
3161
|
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');
|
|
3162
|
+
this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
|
|
2090
3163
|
}
|
|
2091
3164
|
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');
|
|
3165
|
+
clearTimeout(this.timer);
|
|
3166
|
+
this.timer = undefined;
|
|
3167
|
+
this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
|
|
2100
3168
|
}
|
|
2101
3169
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2102
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]", host: { listeners: { "
|
|
3170
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]", host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 }); }
|
|
2103
3171
|
}
|
|
2104
3172
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
|
|
2105
3173
|
type: Directive,
|
|
@@ -2107,10 +3175,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2107
3175
|
standalone: false,
|
|
2108
3176
|
selector: '[oneCellTextExpand]'
|
|
2109
3177
|
}]
|
|
2110
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: {
|
|
2111
|
-
type: HostListener,
|
|
2112
|
-
args: ['window:resize']
|
|
2113
|
-
}], onMouseEnter: [{
|
|
3178
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
|
|
2114
3179
|
type: HostListener,
|
|
2115
3180
|
args: ['mouseenter']
|
|
2116
3181
|
}], onMouseLeave: [{
|
|
@@ -2119,6 +3184,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2119
3184
|
}] } });
|
|
2120
3185
|
|
|
2121
3186
|
class OneGridCellOptionMenuComponent {
|
|
3187
|
+
get menuLabel() {
|
|
3188
|
+
return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
|
|
3189
|
+
}
|
|
2122
3190
|
constructor(elementRef) {
|
|
2123
3191
|
this.elementRef = elementRef;
|
|
2124
3192
|
this.column = null;
|
|
@@ -2126,6 +3194,56 @@ class OneGridCellOptionMenuComponent {
|
|
|
2126
3194
|
this.options = [];
|
|
2127
3195
|
this.cellOptionMenuSelect = new EventEmitter();
|
|
2128
3196
|
this.cellOptionMenuClose = new EventEmitter();
|
|
3197
|
+
/**
|
|
3198
|
+
* Roving tabindex: one item is tabbable and the arrows move which. Standard
|
|
3199
|
+
* for a menu — a list of separately tabbable buttons makes a keyboard user
|
|
3200
|
+
* tab through every option to get past the menu.
|
|
3201
|
+
*/
|
|
3202
|
+
this.activeIndex = 0;
|
|
3203
|
+
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Move focus into the menu once it exists.
|
|
3206
|
+
*
|
|
3207
|
+
* Opening a menu and leaving focus on the trigger strands a keyboard user:
|
|
3208
|
+
* the menu is on screen and nothing they press reaches it.
|
|
3209
|
+
*/
|
|
3210
|
+
ngAfterViewInit() {
|
|
3211
|
+
this.focusItem(0);
|
|
3212
|
+
}
|
|
3213
|
+
onItemKeydown(event, index, option) {
|
|
3214
|
+
switch (event.key) {
|
|
3215
|
+
case 'ArrowDown':
|
|
3216
|
+
event.preventDefault();
|
|
3217
|
+
this.focusItem((index + 1) % this.options.length);
|
|
3218
|
+
return;
|
|
3219
|
+
case 'ArrowUp':
|
|
3220
|
+
event.preventDefault();
|
|
3221
|
+
this.focusItem((index - 1 + this.options.length) % this.options.length);
|
|
3222
|
+
return;
|
|
3223
|
+
case 'Home':
|
|
3224
|
+
event.preventDefault();
|
|
3225
|
+
this.focusItem(0);
|
|
3226
|
+
return;
|
|
3227
|
+
case 'End':
|
|
3228
|
+
event.preventDefault();
|
|
3229
|
+
this.focusItem(this.options.length - 1);
|
|
3230
|
+
return;
|
|
3231
|
+
case 'Enter':
|
|
3232
|
+
case ' ':
|
|
3233
|
+
// A <button> already activates on both, but only once the default runs.
|
|
3234
|
+
// Handling it here keeps the menu's behaviour independent of that.
|
|
3235
|
+
event.preventDefault();
|
|
3236
|
+
this.onOptionSelected(option);
|
|
3237
|
+
return;
|
|
3238
|
+
default:
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
focusItem(index) {
|
|
3243
|
+
if (!this.options?.length)
|
|
3244
|
+
return;
|
|
3245
|
+
this.activeIndex = index;
|
|
3246
|
+
this.items?.get(index)?.nativeElement?.focus();
|
|
2129
3247
|
}
|
|
2130
3248
|
// Click outside
|
|
2131
3249
|
onDocumentClick(event) {
|
|
@@ -2157,11 +3275,11 @@ class OneGridCellOptionMenuComponent {
|
|
|
2157
3275
|
this.cellOptionMenuClose.emit();
|
|
2158
3276
|
}
|
|
2159
3277
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
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 <
|
|
3278
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: { column: "column", node: "node", options: "options" }, outputs: { cellOptionMenuSelect: "cellOptionMenuSelect", cellOptionMenuClose: "cellOptionMenuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n", styles: [""] }); }
|
|
2161
3279
|
}
|
|
2162
3280
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
|
|
2163
3281
|
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 <
|
|
3282
|
+
args: [{ selector: 'one-grid-cell-option-menu', standalone: false, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n" }]
|
|
2165
3283
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
|
|
2166
3284
|
type: Input
|
|
2167
3285
|
}], node: [{
|
|
@@ -2172,6 +3290,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2172
3290
|
type: Output
|
|
2173
3291
|
}], cellOptionMenuClose: [{
|
|
2174
3292
|
type: Output
|
|
3293
|
+
}], items: [{
|
|
3294
|
+
type: ViewChildren,
|
|
3295
|
+
args: ['item']
|
|
2175
3296
|
}], onDocumentClick: [{
|
|
2176
3297
|
type: HostListener,
|
|
2177
3298
|
args: ['document:click', ['$event']]
|
|
@@ -2183,6 +3304,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2183
3304
|
args: ['document:scroll']
|
|
2184
3305
|
}] } });
|
|
2185
3306
|
|
|
3307
|
+
/** Why the handle is disabled, in words a user can act on. */
|
|
3308
|
+
const SUPPRESSED_TEXT = {
|
|
3309
|
+
sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
|
|
3310
|
+
filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
|
|
3311
|
+
treeData: 'Rows cannot be reordered in a tree grid.',
|
|
3312
|
+
};
|
|
3313
|
+
/**
|
|
3314
|
+
* The grab handle for one row.
|
|
3315
|
+
*
|
|
3316
|
+
* A real `<button>`, not a styled div: it has to be focusable and operable from
|
|
3317
|
+
* the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
|
|
3318
|
+
* to have a non-dragging alternative. Space picks the row up, the arrows move
|
|
3319
|
+
* it, space drops it and escape puts it back.
|
|
3320
|
+
*
|
|
3321
|
+
* When reordering is unavailable the handle is disabled and says why, rather
|
|
3322
|
+
* than disappearing — a control that vanishes when the user sorts looks like a
|
|
3323
|
+
* bug, and one that silently does nothing is worse.
|
|
3324
|
+
*/
|
|
3325
|
+
class OneGridRowDragHandleComponent {
|
|
3326
|
+
constructor(dragService) {
|
|
3327
|
+
this.dragService = dragService;
|
|
3328
|
+
/** Non-null when the grid's order is not its own; see the model docs. */
|
|
3329
|
+
this.suppressedReason = null;
|
|
3330
|
+
this.dragEnd = new EventEmitter();
|
|
3331
|
+
}
|
|
3332
|
+
get disabled() {
|
|
3333
|
+
return this.suppressedReason !== null;
|
|
3334
|
+
}
|
|
3335
|
+
get label() {
|
|
3336
|
+
if (this.suppressedReason)
|
|
3337
|
+
return SUPPRESSED_TEXT[this.suppressedReason];
|
|
3338
|
+
return this.grabbed
|
|
3339
|
+
? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
|
|
3340
|
+
: 'Drag to reorder row, or press space to move it with the arrow keys.';
|
|
3341
|
+
}
|
|
3342
|
+
/** True while this specific row is the one being moved. */
|
|
3343
|
+
get grabbed() {
|
|
3344
|
+
return this.dragService.state?.rowId === this.node.rowId;
|
|
3345
|
+
}
|
|
3346
|
+
onPointerDown(event) {
|
|
3347
|
+
if (this.disabled)
|
|
3348
|
+
return;
|
|
3349
|
+
// The body starts a cell range selection on mousedown; the handle is not
|
|
3350
|
+
// part of that, and a press here must not also select.
|
|
3351
|
+
event.stopPropagation();
|
|
3352
|
+
this.dragService.startPointerDrag(event, this.node, { onEnd: (result) => this.emit(result) });
|
|
3353
|
+
}
|
|
3354
|
+
onKeyDown(event) {
|
|
3355
|
+
if (this.disabled)
|
|
3356
|
+
return;
|
|
3357
|
+
switch (event.key) {
|
|
3358
|
+
case ' ':
|
|
3359
|
+
case 'Enter':
|
|
3360
|
+
event.preventDefault();
|
|
3361
|
+
this.dragService.toggleKeyboardDrag(this.node, { onEnd: (result) => this.emit(result) });
|
|
3362
|
+
return;
|
|
3363
|
+
case 'ArrowUp':
|
|
3364
|
+
if (!this.grabbed)
|
|
3365
|
+
return;
|
|
3366
|
+
event.preventDefault();
|
|
3367
|
+
this.dragService.moveKeyboard(-1);
|
|
3368
|
+
return;
|
|
3369
|
+
case 'ArrowDown':
|
|
3370
|
+
if (!this.grabbed)
|
|
3371
|
+
return;
|
|
3372
|
+
event.preventDefault();
|
|
3373
|
+
this.dragService.moveKeyboard(1);
|
|
3374
|
+
return;
|
|
3375
|
+
case 'Escape':
|
|
3376
|
+
if (!this.grabbed)
|
|
3377
|
+
return;
|
|
3378
|
+
event.preventDefault();
|
|
3379
|
+
this.dragService.cancel();
|
|
3380
|
+
return;
|
|
3381
|
+
default:
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* A row that is picked up and dropped where it started produces no event.
|
|
3387
|
+
* Blur while grabbed would otherwise strand the row mid-move, so it cancels.
|
|
3388
|
+
*/
|
|
3389
|
+
onBlur() {
|
|
3390
|
+
if (this.grabbed && this.dragService.state?.source === 'keyboard') {
|
|
3391
|
+
this.dragService.cancel();
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
emit(result) {
|
|
3395
|
+
if (result)
|
|
3396
|
+
this.dragEnd.emit(result);
|
|
3397
|
+
}
|
|
3398
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, deps: [{ token: OneGridRowDragService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3399
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: { node: "node", suppressedReason: "suppressedReason" }, outputs: { dragEnd: "dragEnd" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"], dependencies: [{ kind: "component", type: i2$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3400
|
+
}
|
|
3401
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, decorators: [{
|
|
3402
|
+
type: Component,
|
|
3403
|
+
args: [{ standalone: false, selector: 'one-grid-row-drag-handle', changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"] }]
|
|
3404
|
+
}], ctorParameters: () => [{ type: OneGridRowDragService }], propDecorators: { node: [{
|
|
3405
|
+
type: Input,
|
|
3406
|
+
args: [{ required: true }]
|
|
3407
|
+
}], suppressedReason: [{
|
|
3408
|
+
type: Input
|
|
3409
|
+
}], dragEnd: [{
|
|
3410
|
+
type: Output
|
|
3411
|
+
}] } });
|
|
3412
|
+
|
|
2186
3413
|
class CellInvalidPipe {
|
|
2187
3414
|
transform(cellStates, value, column, node, allowedEdit, submitted) {
|
|
2188
3415
|
if (!column.validationRules?.length) {
|
|
@@ -2294,10 +3521,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2294
3521
|
}] });
|
|
2295
3522
|
|
|
2296
3523
|
class OneGridBodyComponent {
|
|
2297
|
-
constructor(colService, cellService, eventService, cdr) {
|
|
3524
|
+
constructor(colService, cellService, eventService, rowDragService, cdr) {
|
|
2298
3525
|
this.colService = colService;
|
|
2299
3526
|
this.cellService = cellService;
|
|
2300
3527
|
this.eventService = eventService;
|
|
3528
|
+
this.rowDragService = rowDragService;
|
|
2301
3529
|
this.cdr = cdr;
|
|
2302
3530
|
this.rowData = [];
|
|
2303
3531
|
this.pagination = false;
|
|
@@ -2305,6 +3533,14 @@ class OneGridBodyComponent {
|
|
|
2305
3533
|
this.enableGroupColumnDefs = false;
|
|
2306
3534
|
this.submitted = false;
|
|
2307
3535
|
this.context = null;
|
|
3536
|
+
/**
|
|
3537
|
+
* Pinned rows are bound only so the component re-renders when they change;
|
|
3538
|
+
* the nodes themselves come from the API, which owns them.
|
|
3539
|
+
*/
|
|
3540
|
+
this.pinnedTopRowData = [];
|
|
3541
|
+
this.pinnedBottomRowData = [];
|
|
3542
|
+
this.pinnedTopNodes = [];
|
|
3543
|
+
this.pinnedBottomNodes = [];
|
|
2308
3544
|
this.columnDefs = [];
|
|
2309
3545
|
this.cellRenderer = false;
|
|
2310
3546
|
/**
|
|
@@ -2345,11 +3581,19 @@ class OneGridBodyComponent {
|
|
|
2345
3581
|
this.expansionChanged = new EventEmitter();
|
|
2346
3582
|
this.bodyScroll = new EventEmitter();
|
|
2347
3583
|
this.cellOptionSelect = new EventEmitter();
|
|
3584
|
+
this.rowDragEnd = new EventEmitter();
|
|
2348
3585
|
/**
|
|
2349
3586
|
* Selection Range Variables
|
|
2350
3587
|
*/
|
|
2351
3588
|
this.rowWidth = 0;
|
|
2352
3589
|
this.nodes = [];
|
|
3590
|
+
/**
|
|
3591
|
+
* Row drag state, mirrored from the service so the template can read it
|
|
3592
|
+
* synchronously. `null` whenever no drag is in flight.
|
|
3593
|
+
*/
|
|
3594
|
+
this.dragState = null;
|
|
3595
|
+
this.dragAnnouncement = '';
|
|
3596
|
+
this.rowDragSuppressed = null;
|
|
2353
3597
|
this.selectedCells = new Set();
|
|
2354
3598
|
this.isMouseDown = false;
|
|
2355
3599
|
this.startCell = null;
|
|
@@ -2377,6 +3621,40 @@ class OneGridBodyComponent {
|
|
|
2377
3621
|
this.itemSize = 30;
|
|
2378
3622
|
this.minBufferPx = 200;
|
|
2379
3623
|
this.maxBufferPx = 400;
|
|
3624
|
+
/**
|
|
3625
|
+
* Virtual Scroll Properties
|
|
3626
|
+
*
|
|
3627
|
+
* The buffer scales with the row count so small grids render in one pass, but
|
|
3628
|
+
* it is capped: `itemSize * items * 1.5` is 1.5x the *total* content height,
|
|
3629
|
+
* and CdkFixedSizeVirtualScroll expands its rendered range until the buffer is
|
|
3630
|
+
* satisfied. Uncapped, that makes the viewport materialise every row and
|
|
3631
|
+
* virtual scrolling stops doing anything at all (100k rows locked the tab).
|
|
3632
|
+
* MAX_BUFFER_PX keeps roughly twenty rows of overscan either side instead.
|
|
3633
|
+
*/
|
|
3634
|
+
this.MAX_BUFFER_PX = 600;
|
|
3635
|
+
/**
|
|
3636
|
+
* Where to paint the drop indicator, in pixels from the top of the body.
|
|
3637
|
+
*
|
|
3638
|
+
* `insertAt` is a boundary, so the line sits at row-height times the number
|
|
3639
|
+
* of rows above it — in content space. The indicator is a sibling of the
|
|
3640
|
+
* viewport rather than a child, because anything inside it either scrolls
|
|
3641
|
+
* away with the transformed content wrapper or ignores the scroll entirely,
|
|
3642
|
+
* so the viewport's own scroll offset and its position within the body are
|
|
3643
|
+
* both subtracted back out here.
|
|
3644
|
+
*
|
|
3645
|
+
* A field rather than a getter: it is read from the template on every change
|
|
3646
|
+
* detection pass, and it only actually moves when the drag state or the
|
|
3647
|
+
* scroll position does.
|
|
3648
|
+
*/
|
|
3649
|
+
this.dropIndicatorTop = null;
|
|
3650
|
+
/**
|
|
3651
|
+
* How wide the line is drawn. Rows are `width: fit-content`, so they are
|
|
3652
|
+
* often narrower than the viewport — a full-bleed line would hang past the
|
|
3653
|
+
* last column into empty space and read as part of the frame rather than the
|
|
3654
|
+
* rows. This is the visible extent of the row content, which also shrinks
|
|
3655
|
+
* correctly as the grid is scrolled sideways.
|
|
3656
|
+
*/
|
|
3657
|
+
this.dropIndicatorWidth = 0;
|
|
2380
3658
|
}
|
|
2381
3659
|
ngOnInit() {
|
|
2382
3660
|
/**
|
|
@@ -2385,13 +3663,30 @@ class OneGridBodyComponent {
|
|
|
2385
3663
|
this.subscriptions.add(this.gridCopy$.subscribe((event) => {
|
|
2386
3664
|
this.onGridCopy(event);
|
|
2387
3665
|
}));
|
|
3666
|
+
/**
|
|
3667
|
+
* Row drag
|
|
3668
|
+
*
|
|
3669
|
+
* The service owns the gesture; the body only renders it and lends it the
|
|
3670
|
+
* viewport, the row height and a way to apply a move. The suppression
|
|
3671
|
+
* reason is re-read on every event that could change the order, so the
|
|
3672
|
+
* handle disables itself the moment a sort or filter is applied.
|
|
3673
|
+
*/
|
|
3674
|
+
this.subscriptions.add(this.rowDragService.state$.subscribe((state) => {
|
|
3675
|
+
this.dragState = state;
|
|
3676
|
+
this.updateDropIndicator();
|
|
3677
|
+
this.cdr.markForCheck();
|
|
3678
|
+
}));
|
|
3679
|
+
this.subscriptions.add(this.rowDragService.announcement$.subscribe((message) => {
|
|
3680
|
+
this.dragAnnouncement = message;
|
|
3681
|
+
this.cdr.markForCheck();
|
|
3682
|
+
}));
|
|
2388
3683
|
/**
|
|
2389
3684
|
* Listen Column Changed Event
|
|
2390
3685
|
*/
|
|
2391
3686
|
this.subscriptions.add(this.eventService.addEventListener('columnChanged', (payload) => {
|
|
2392
3687
|
this.columnDefs = [...payload.columns];
|
|
2393
3688
|
this.cellRenderer = this.columnDefs.some(col => col.cellRendererComponent);
|
|
2394
|
-
this.rowWidth = this.columnDefs.reduce((sum, col) => sum + (col
|
|
3689
|
+
this.rowWidth = this.columnDefs.reduce((sum, col) => sum + resolveColumnWidth(col), 0);
|
|
2395
3690
|
this.cdr.markForCheck();
|
|
2396
3691
|
}));
|
|
2397
3692
|
/**
|
|
@@ -2407,7 +3702,9 @@ class OneGridBodyComponent {
|
|
|
2407
3702
|
if (node.leaf && this.detailRendererComponent) {
|
|
2408
3703
|
node.detailRendererComponent = this.detailRendererComponent;
|
|
2409
3704
|
}
|
|
2410
|
-
|
|
3705
|
+
// Detail rows can still be in flight when a row is expanded, and
|
|
3706
|
+
// spreading null throws. Nothing to re-seed until they arrive.
|
|
3707
|
+
if (node.expanded && node.detailRowData) {
|
|
2411
3708
|
this.api.setDetailRowData(node, [...node.detailRowData]);
|
|
2412
3709
|
}
|
|
2413
3710
|
return node;
|
|
@@ -2416,14 +3713,50 @@ class OneGridBodyComponent {
|
|
|
2416
3713
|
else {
|
|
2417
3714
|
this.nodes = nodes;
|
|
2418
3715
|
}
|
|
3716
|
+
this.refreshRowDragSuppressed();
|
|
2419
3717
|
this.cdr.markForCheck();
|
|
2420
3718
|
});
|
|
2421
3719
|
}));
|
|
3720
|
+
/**
|
|
3721
|
+
* Listen Pinned Row Data Changed Event
|
|
3722
|
+
*/
|
|
3723
|
+
this.subscriptions.add(this.eventService.addEventListener('pinnedRowDataChanged', ({ side, nodes }) => {
|
|
3724
|
+
if (side === 'top') {
|
|
3725
|
+
this.pinnedTopNodes = nodes;
|
|
3726
|
+
}
|
|
3727
|
+
else {
|
|
3728
|
+
this.pinnedBottomNodes = nodes;
|
|
3729
|
+
}
|
|
3730
|
+
this.cdr.markForCheck();
|
|
3731
|
+
}));
|
|
2422
3732
|
/**
|
|
2423
3733
|
* Listen Sort Changed Event
|
|
2424
3734
|
*/
|
|
2425
3735
|
this.subscriptions.add(this.eventService.addEventListener('sortChanged', ({ nodes }) => {
|
|
2426
3736
|
this.nodes = nodes;
|
|
3737
|
+
// Sorting takes ownership of the order away from the user, so the handles
|
|
3738
|
+
// have to disable in the same frame the rows re-sort.
|
|
3739
|
+
this.refreshRowDragSuppressed();
|
|
3740
|
+
this.cdr.markForCheck();
|
|
3741
|
+
}));
|
|
3742
|
+
/**
|
|
3743
|
+
* Listen Row Order Changed Event
|
|
3744
|
+
*
|
|
3745
|
+
* Deliberately not folded into `rowDataChanged`: that path defers a frame
|
|
3746
|
+
* and re-seeds detail rows, which is right for a data load and far too much
|
|
3747
|
+
* for a move. A reorder is the same nodes in a different order.
|
|
3748
|
+
*/
|
|
3749
|
+
this.subscriptions.add(this.eventService.addEventListener('rowOrderChanged', ({ nodes }) => {
|
|
3750
|
+
this.nodes = [...nodes];
|
|
3751
|
+
this.cdr.markForCheck();
|
|
3752
|
+
}));
|
|
3753
|
+
/**
|
|
3754
|
+
* Listen Cell State Changed Event
|
|
3755
|
+
*
|
|
3756
|
+
* The body renders OnPush, so regenerating a row's cell state is invisible
|
|
3757
|
+
* until something marks the body dirty. This is that something.
|
|
3758
|
+
*/
|
|
3759
|
+
this.subscriptions.add(this.eventService.addEventListener('cellStateChanged', () => {
|
|
2427
3760
|
this.cdr.markForCheck();
|
|
2428
3761
|
}));
|
|
2429
3762
|
/**
|
|
@@ -2442,9 +3775,33 @@ class OneGridBodyComponent {
|
|
|
2442
3775
|
const scrollLeft = event.target.scrollLeft;
|
|
2443
3776
|
if (scrollLeft !== this.lastScrollLeft) {
|
|
2444
3777
|
this.lastScrollLeft = scrollLeft;
|
|
3778
|
+
this.syncPinnedRows(scrollLeft);
|
|
2445
3779
|
this.bodyScroll.emit(scrollLeft);
|
|
3780
|
+
// The indicator is clipped to the row content, which moves sideways too.
|
|
3781
|
+
if (this.dragState) {
|
|
3782
|
+
this.updateDropIndicator();
|
|
3783
|
+
this.cdr.markForCheck();
|
|
3784
|
+
}
|
|
2446
3785
|
return;
|
|
2447
3786
|
}
|
|
3787
|
+
// Vertical: the drop indicator is anchored to the body, not the content, so
|
|
3788
|
+
// it has to be repositioned as the rows move beneath it — which they do on
|
|
3789
|
+
// their own during auto-scroll, with the pointer standing still.
|
|
3790
|
+
if (this.dragState) {
|
|
3791
|
+
this.updateDropIndicator();
|
|
3792
|
+
this.cdr.markForCheck();
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Keep the pinned rows in step with the body's horizontal position.
|
|
3797
|
+
*
|
|
3798
|
+
* Assigned rather than transformed, for the same reason as the header: sticky
|
|
3799
|
+
* positioning for pinned *columns* has to resolve against a real scrollport,
|
|
3800
|
+
* and a transformed ancestor is not one. Pinned rows and pinned columns
|
|
3801
|
+
* therefore compose — a frozen corner cell stays frozen on both axes.
|
|
3802
|
+
*/
|
|
3803
|
+
syncPinnedRows(scrollLeft) {
|
|
3804
|
+
this.pinnedViewports?.forEach(viewport => (viewport.nativeElement.scrollLeft = scrollLeft));
|
|
2448
3805
|
}
|
|
2449
3806
|
/**
|
|
2450
3807
|
* Track Rendering Row Node By Row ID
|
|
@@ -2624,14 +3981,56 @@ class OneGridBodyComponent {
|
|
|
2624
3981
|
invokeRowActionLanguage(event) {
|
|
2625
3982
|
this.onRowLanguage.emit(event);
|
|
2626
3983
|
}
|
|
3984
|
+
/**
|
|
3985
|
+
* Open the cell option menu, and tell the host it was asked for.
|
|
3986
|
+
*
|
|
3987
|
+
* The opening is the part that was missing. Everything else existed — the
|
|
3988
|
+
* trigger button behind `cellRendererParams.cellOptionButtonStatus`, the menu
|
|
3989
|
+
* component, the `cellOptionMenu` column property, the selection event wired
|
|
3990
|
+
* through to the grid's output — but nothing ever set the flag the template
|
|
3991
|
+
* renders on, so the button did nothing at all.
|
|
3992
|
+
*
|
|
3993
|
+
* Done here rather than in the row-action component because this is the
|
|
3994
|
+
* component whose template renders the menu, and so the one that has to be
|
|
3995
|
+
* marked for check.
|
|
3996
|
+
*/
|
|
2627
3997
|
invokeRowActionCellOption(event) {
|
|
3998
|
+
const field = event.column?.field;
|
|
3999
|
+
// Nothing to open when the column declared no options; the event still
|
|
4000
|
+
// goes to the host, which may be driving something of its own.
|
|
4001
|
+
if (field && event.node && event.column?.cellOptionMenu?.length) {
|
|
4002
|
+
event.node.cellOptions[field] = true;
|
|
4003
|
+
this.cdr.markForCheck();
|
|
4004
|
+
}
|
|
2628
4005
|
this.onRowCellOption.emit(event);
|
|
2629
4006
|
}
|
|
4007
|
+
/**
|
|
4008
|
+
* The menu closed itself — on select, on Escape, or on a click outside. It
|
|
4009
|
+
* has already cleared its own flag; this is what repaints without it.
|
|
4010
|
+
*/
|
|
4011
|
+
cellOptionMenuClosed() {
|
|
4012
|
+
this.cdr.markForCheck();
|
|
4013
|
+
}
|
|
2630
4014
|
invokeRowActionSelection(event) {
|
|
2631
4015
|
this.onRowSelection.emit(event);
|
|
2632
4016
|
}
|
|
4017
|
+
/**
|
|
4018
|
+
* Toggle a group / master row, then report the result.
|
|
4019
|
+
*
|
|
4020
|
+
* The group renderer is presentational: it says "the expander was clicked"
|
|
4021
|
+
* and nothing more. Performing the state change belongs here, where the API
|
|
4022
|
+
* is available. Previously this method only re-emitted, so the expander
|
|
4023
|
+
* rendered, animated on hover and did nothing at all — every consumer had to
|
|
4024
|
+
* discover for itself that `expansionChanged` was a request rather than the
|
|
4025
|
+
* notification its past-tense name promises.
|
|
4026
|
+
*
|
|
4027
|
+
* The event is re-emitted *after* the toggle, so `event.node.expanded` is the
|
|
4028
|
+
* new state, which is what a past-tense event should carry.
|
|
4029
|
+
*/
|
|
2633
4030
|
invokeExpansionChangedClick(event) {
|
|
2634
|
-
this.
|
|
4031
|
+
const node = this.api.getRowNodeById(event.node.rowId) ?? event.node;
|
|
4032
|
+
this.api.setExpandedById(node.rowId, !node.expanded);
|
|
4033
|
+
this.expansionChanged.emit({ ...event, node, api: this.api });
|
|
2635
4034
|
}
|
|
2636
4035
|
cellOptionMenuSelected(event) {
|
|
2637
4036
|
this.cellOptionSelect.emit(event);
|
|
@@ -2721,26 +4120,94 @@ class OneGridBodyComponent {
|
|
|
2721
4120
|
}
|
|
2722
4121
|
}
|
|
2723
4122
|
}
|
|
2724
|
-
/**
|
|
2725
|
-
* Virtual Scroll Properties
|
|
2726
|
-
*/
|
|
2727
4123
|
setVirtualScroll(items = 0) {
|
|
2728
|
-
this.minBufferPx = this.itemSize * items * 1.5;
|
|
4124
|
+
this.minBufferPx = Math.min(this.itemSize * items * 1.5, this.MAX_BUFFER_PX);
|
|
2729
4125
|
this.maxBufferPx = this.minBufferPx * 2;
|
|
2730
4126
|
}
|
|
2731
4127
|
/**
|
|
2732
4128
|
* Unsubscribe All Subscriptions
|
|
2733
4129
|
*/
|
|
4130
|
+
/**
|
|
4131
|
+
* Hand the drag service what it needs to run a gesture.
|
|
4132
|
+
*
|
|
4133
|
+
* Deferred to here because the viewport element does not exist until the view
|
|
4134
|
+
* is up, and `nodes` is passed as a getter rather than a value — a drag
|
|
4135
|
+
* mutates the order underneath it, so a snapshot taken at attach time would
|
|
4136
|
+
* go stale on the first move.
|
|
4137
|
+
*/
|
|
4138
|
+
ngAfterViewInit() {
|
|
4139
|
+
const element = this.viewport?.elementRef?.nativeElement;
|
|
4140
|
+
if (!element)
|
|
4141
|
+
return;
|
|
4142
|
+
this.rowDragService.attach({
|
|
4143
|
+
viewport: element,
|
|
4144
|
+
itemSize: this.itemSize,
|
|
4145
|
+
nodes: () => this.nodes,
|
|
4146
|
+
move: (fromIndex, toIndex) => {
|
|
4147
|
+
const moved = this.api.moveRow(fromIndex, toIndex);
|
|
4148
|
+
if (moved)
|
|
4149
|
+
this.cdr.markForCheck();
|
|
4150
|
+
return moved;
|
|
4151
|
+
},
|
|
4152
|
+
});
|
|
4153
|
+
this.refreshRowDragSuppressed();
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* The params every per-cell callback receives.
|
|
4157
|
+
*
|
|
4158
|
+
* Built here rather than inline in the template so the whole documented
|
|
4159
|
+
* shape is passed — `value`, `field`, `rowIndex`, `api` and `context` were
|
|
4160
|
+
* all declared on `OneCellRendererParams` and none of them were being sent,
|
|
4161
|
+
* so a `cellClass` reading `params.value` silently got `undefined`.
|
|
4162
|
+
*/
|
|
4163
|
+
cellParams(node, column) {
|
|
4164
|
+
return this.cellService.cellParams(column, node);
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Params for a component cell renderer.
|
|
4168
|
+
*
|
|
4169
|
+
* The cell params, plus whatever the column declared in
|
|
4170
|
+
* `cellRendererParams` — a field that exists precisely so a renderer can be
|
|
4171
|
+
* configured per column, and that nothing had ever forwarded to it. Column
|
|
4172
|
+
* config goes first so the grid's own fields win a name collision.
|
|
4173
|
+
*/
|
|
4174
|
+
rendererParams(node, column) {
|
|
4175
|
+
return { ...(column?.cellRendererParams ?? {}), ...this.cellParams(node, column) };
|
|
4176
|
+
}
|
|
4177
|
+
/** Is this the row currently being moved? */
|
|
4178
|
+
isRowDragging(node) {
|
|
4179
|
+
return this.dragState?.rowId === node.rowId;
|
|
4180
|
+
}
|
|
4181
|
+
updateDropIndicator() {
|
|
4182
|
+
const state = this.dragState;
|
|
4183
|
+
const element = this.viewport?.elementRef?.nativeElement;
|
|
4184
|
+
if (state?.source !== 'pointer' || !element) {
|
|
4185
|
+
this.dropIndicatorTop = null;
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
this.dropIndicatorTop = (state.insertAt * this.itemSize) - element.scrollTop + element.offsetTop;
|
|
4189
|
+
this.dropIndicatorWidth = Math.max(Math.min(this.rowWidth - element.scrollLeft, element.clientWidth), 0);
|
|
4190
|
+
}
|
|
4191
|
+
onRowDragEnd(event) {
|
|
4192
|
+
this.rowDragEnd.emit(event);
|
|
4193
|
+
}
|
|
4194
|
+
refreshRowDragSuppressed() {
|
|
4195
|
+
const reason = this.api?.getRowDragSuppressedReason?.() ?? null;
|
|
4196
|
+
if (reason === this.rowDragSuppressed)
|
|
4197
|
+
return;
|
|
4198
|
+
this.rowDragSuppressed = reason;
|
|
4199
|
+
this.cdr.markForCheck();
|
|
4200
|
+
}
|
|
2734
4201
|
ngOnDestroy() {
|
|
2735
4202
|
this.subscriptions.unsubscribe();
|
|
2736
4203
|
}
|
|
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 }); }
|
|
4204
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, deps: [{ token: OgColumnService }, { token: OneGridCellService }, { token: OneGridEventService }, { token: OneGridRowDragService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4205
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridBodyComponent, selector: "one-grid-body", inputs: { rowData: "rowData", rowNumber: "rowNumber", api: "api", pagination: "pagination", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", submitted: "submitted", context: "context", getRowId: "getRowId", gridCopy$: "gridCopy$", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailRowMaxHeight: "detailRowMaxHeight", detailCellRendererParams: "detailCellRendererParams", detailGridTemplate: "detailGridTemplate", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowCopy: "onRowCopy", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", expansionChanged: "expansionChanged", bodyScroll: "bodyScroll", cellOptionSelect: "cellOptionSelect", rowDragEnd: "rowDragEnd" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:mouseup": "onGlobalMouseUp()" } }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodyRef"], descendants: true, static: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "pinnedViewports", predicate: ["pinnedViewport"], descendants: true }], ngImport: i0, template: "<div #bodyRef class=\"one-grid-body\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n tabindex=\"0\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\" [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\">\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i6.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i6.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i6.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: ["node", "allowedEdit", "api"], outputs: ["onRowSelection"] }, { kind: "component", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: ["column", "node", "allowedEdit", "masterDetail", "masterNode", "cellRendererParams", "gridType", "context"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption"] }, { kind: "component", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: ["node", "masterDetail", "detailCellRendererParams", "detailRendererComponent"], outputs: ["expansionChanged"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "directive", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: ["cellRendererComponent", "params"] }, { kind: "directive", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]" }, { kind: "component", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: ["column", "node", "options"], outputs: ["cellOptionMenuSelect", "cellOptionMenuClose"] }, { kind: "component", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: ["node", "suppressedReason"], outputs: ["dragEnd"] }, { kind: "pipe", type: CellEditablePipe, name: "cellEditable" }, { kind: "pipe", type: CellInvalidPipe, name: "cellInvalid" }, { kind: "pipe", type: CellSelectedPipe, name: "cellSelected" }, { kind: "pipe", type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }, { kind: "pipe", type: CellKeyPipe, name: "cellKey" }, { kind: "pipe", type: CellLevelPaddingPipe, name: "cellLevelPadding" }, { kind: "pipe", type: CellStateRendererPipe, name: "cellStateRenderer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2739
4206
|
}
|
|
2740
4207
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, decorators: [{
|
|
2741
4208
|
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: [{
|
|
4209
|
+
args: [{ standalone: false, selector: 'one-grid-body', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bodyRef class=\"one-grid-body\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n tabindex=\"0\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\" [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\">\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n" }]
|
|
4210
|
+
}], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridRowDragService }, { type: i0.ChangeDetectorRef }], propDecorators: { bodyRef: [{
|
|
2744
4211
|
type: ViewChild,
|
|
2745
4212
|
args: ['bodyRef', { static: true }]
|
|
2746
4213
|
}], rowData: [{
|
|
@@ -2765,6 +4232,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2765
4232
|
}], gridCopy$: [{
|
|
2766
4233
|
type: Input,
|
|
2767
4234
|
args: [{ required: true }]
|
|
4235
|
+
}], pinnedTopRowData: [{
|
|
4236
|
+
type: Input
|
|
4237
|
+
}], pinnedBottomRowData: [{
|
|
4238
|
+
type: Input
|
|
4239
|
+
}], pinnedViewports: [{
|
|
4240
|
+
type: ViewChildren,
|
|
4241
|
+
args: ['pinnedViewport']
|
|
2768
4242
|
}], viewport: [{
|
|
2769
4243
|
type: ViewChild,
|
|
2770
4244
|
args: [CdkVirtualScrollViewport]
|
|
@@ -2830,6 +4304,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2830
4304
|
type: Output
|
|
2831
4305
|
}], cellOptionSelect: [{
|
|
2832
4306
|
type: Output
|
|
4307
|
+
}], rowDragEnd: [{
|
|
4308
|
+
type: Output
|
|
2833
4309
|
}], onDocumentClick: [{
|
|
2834
4310
|
type: HostListener,
|
|
2835
4311
|
args: ['document:click', ['$event']]
|
|
@@ -2853,31 +4329,56 @@ class OneGridPaginationComponent {
|
|
|
2853
4329
|
this.goToNext = new EventEmitter();
|
|
2854
4330
|
this.goToLast = new EventEmitter();
|
|
2855
4331
|
this.pageSizeChange = new EventEmitter();
|
|
2856
|
-
this.pageSize = OnePageSizeOption[0];
|
|
2857
|
-
this.pageSizeOptions = OnePageSizeOption;
|
|
2858
4332
|
}
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
4333
|
+
/**
|
|
4334
|
+
* The page size the bar shows.
|
|
4335
|
+
*
|
|
4336
|
+
* Derived from the input rather than held locally. The bar is a display of
|
|
4337
|
+
* the host's paging state — it does not own it — and a local copy meant a
|
|
4338
|
+
* host that supplied `perPage: 50` still saw "25 rows" selected.
|
|
4339
|
+
*/
|
|
4340
|
+
get pageSize() {
|
|
4341
|
+
return this.paginate?.perPage ?? OnePageSizeOption[0];
|
|
4342
|
+
}
|
|
4343
|
+
/**
|
|
4344
|
+
* The size options offered.
|
|
4345
|
+
*
|
|
4346
|
+
* Also derived. These used to be copied out of the input in
|
|
4347
|
+
* `ngAfterViewInit` — after the view had already been checked with the
|
|
4348
|
+
* defaults — so any grid supplying its own options threw `NG0100` in dev
|
|
4349
|
+
* mode, and later changes to the list were ignored entirely.
|
|
4350
|
+
*/
|
|
4351
|
+
get pageSizeOptions() {
|
|
4352
|
+
return this.paginate?.pageSizeOptions?.length ? this.paginate.pageSizeOptions : OnePageSizeOption;
|
|
4353
|
+
}
|
|
4354
|
+
/** Whether there is a page before the current one. */
|
|
4355
|
+
get hasPrevious() {
|
|
4356
|
+
return this.pageCount > 1 && (this.paginate?.currentPage ?? 1) > 1;
|
|
4357
|
+
}
|
|
4358
|
+
/** Whether there is a page after the current one. */
|
|
4359
|
+
get hasNext() {
|
|
4360
|
+
return this.pageCount > 1 && (this.paginate?.currentPage ?? 1) < (this.paginate?.lastPage ?? 1);
|
|
4361
|
+
}
|
|
4362
|
+
get pageCount() {
|
|
4363
|
+
return this.paginate?.lastPage ?? 1;
|
|
2863
4364
|
}
|
|
2864
4365
|
goToFirstPage() {
|
|
2865
|
-
if (!
|
|
4366
|
+
if (!this.hasPrevious)
|
|
2866
4367
|
return;
|
|
2867
4368
|
this.goToFirst.emit(this.paginate?.currentPage);
|
|
2868
4369
|
}
|
|
2869
4370
|
goToPreviousPage() {
|
|
2870
|
-
if (!
|
|
4371
|
+
if (!this.hasPrevious)
|
|
2871
4372
|
return;
|
|
2872
4373
|
this.goToPrevious.emit(this.paginate?.currentPage);
|
|
2873
4374
|
}
|
|
2874
4375
|
goToNextPage() {
|
|
2875
|
-
if (!
|
|
4376
|
+
if (!this.hasNext)
|
|
2876
4377
|
return;
|
|
2877
4378
|
this.goToNext.emit(this.paginate?.currentPage);
|
|
2878
4379
|
}
|
|
2879
4380
|
goToLastPage() {
|
|
2880
|
-
if (!
|
|
4381
|
+
if (!this.hasNext)
|
|
2881
4382
|
return;
|
|
2882
4383
|
this.goToLast.emit(this.paginate?.currentPage);
|
|
2883
4384
|
}
|
|
@@ -2885,16 +4386,15 @@ class OneGridPaginationComponent {
|
|
|
2885
4386
|
const select = event.target;
|
|
2886
4387
|
const newSize = Number(select.value);
|
|
2887
4388
|
if (newSize !== this.pageSize) {
|
|
2888
|
-
this.
|
|
2889
|
-
this.pageSizeChange.emit(this.pageSize);
|
|
4389
|
+
this.pageSizeChange.emit(newSize);
|
|
2890
4390
|
}
|
|
2891
4391
|
}
|
|
2892
4392
|
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
|
|
4393
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: { paginate: "paginate" }, outputs: { goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, ngImport: i0, template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n <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
4394
|
}
|
|
2895
4395
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, decorators: [{
|
|
2896
4396
|
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
|
|
4397
|
+
args: [{ standalone: false, selector: 'one-grid-pagination', template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n <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
4398
|
}], propDecorators: { paginate: [{
|
|
2899
4399
|
type: Input
|
|
2900
4400
|
}], goToFirst: [{
|
|
@@ -2924,6 +4424,14 @@ class OneGridComponent {
|
|
|
2924
4424
|
this.groupColumnDefs = [];
|
|
2925
4425
|
this.defaultColumnDef = {};
|
|
2926
4426
|
this.columnDefs = [];
|
|
4427
|
+
/**
|
|
4428
|
+
* Rows anchored above the scrolling area — a header summary, or an entry line.
|
|
4429
|
+
* They keep column alignment and scroll sideways with the body, but are not
|
|
4430
|
+
* data: sorting, filtering, selection and the row count all pass them by.
|
|
4431
|
+
*/
|
|
4432
|
+
this.pinnedTopRowData = [];
|
|
4433
|
+
/** Rows anchored below the scrolling area, typically a totals row. */
|
|
4434
|
+
this.pinnedBottomRowData = [];
|
|
2927
4435
|
// RXJS Properties
|
|
2928
4436
|
this.gridCopy$ = new Subject();
|
|
2929
4437
|
/**
|
|
@@ -2941,6 +4449,11 @@ class OneGridComponent {
|
|
|
2941
4449
|
/**
|
|
2942
4450
|
* Bind Row Action Events & Data to Parent
|
|
2943
4451
|
*/
|
|
4452
|
+
/**
|
|
4453
|
+
* A row finished moving. Fires once per completed reorder — not while the
|
|
4454
|
+
* pointer is in flight — and only when the position actually changed.
|
|
4455
|
+
*/
|
|
4456
|
+
this.rowDragEnd = new EventEmitter();
|
|
2944
4457
|
this.onGridReady = new EventEmitter();
|
|
2945
4458
|
this.onRowGotoDetail = new EventEmitter();
|
|
2946
4459
|
this.onRowAddNew = new EventEmitter();
|
|
@@ -2988,6 +4501,15 @@ class OneGridComponent {
|
|
|
2988
4501
|
if (changes["rowData"] && !changes["rowData"]?.firstChange) {
|
|
2989
4502
|
this.treeData || this.masterDetail ? this.api.updateRowData(this.rowData) : this.api.setRowData(this.rowData);
|
|
2990
4503
|
}
|
|
4504
|
+
// Only later changes. The first one arrives before ngOnInit has run
|
|
4505
|
+
// api.init, so the column definitions are not registered yet and the body
|
|
4506
|
+
// has not subscribed — the initial load happens in ngAfterViewInit instead.
|
|
4507
|
+
if (changes["pinnedTopRowData"] && !changes["pinnedTopRowData"].firstChange) {
|
|
4508
|
+
this.api.setPinnedRowData('top', this.pinnedTopRowData);
|
|
4509
|
+
}
|
|
4510
|
+
if (changes["pinnedBottomRowData"] && !changes["pinnedBottomRowData"].firstChange) {
|
|
4511
|
+
this.api.setPinnedRowData('bottom', this.pinnedBottomRowData);
|
|
4512
|
+
}
|
|
2991
4513
|
}
|
|
2992
4514
|
ngOnInit() {
|
|
2993
4515
|
/**
|
|
@@ -3015,7 +4537,18 @@ class OneGridComponent {
|
|
|
3015
4537
|
/**
|
|
3016
4538
|
* Initialize One Grid API
|
|
3017
4539
|
*/
|
|
3018
|
-
this.api.init(
|
|
4540
|
+
this.api.init({
|
|
4541
|
+
gridType: this.gridType,
|
|
4542
|
+
columnDefs: this.columnDefs,
|
|
4543
|
+
defaultColumnDef: this.defaultColumnDef,
|
|
4544
|
+
groupColumnDefs: this.groupColumnDefs,
|
|
4545
|
+
context: this.context,
|
|
4546
|
+
rowSelection: this.rowSelection,
|
|
4547
|
+
masterDetail: this.masterDetail,
|
|
4548
|
+
detailCellRendererParams: this.detailCellRendererParams,
|
|
4549
|
+
detailRendererComponent: this.detailRendererComponent,
|
|
4550
|
+
treeData: this.treeData,
|
|
4551
|
+
});
|
|
3019
4552
|
/**
|
|
3020
4553
|
* Setup Master Detail Configuration
|
|
3021
4554
|
*/
|
|
@@ -3043,14 +4576,20 @@ class OneGridComponent {
|
|
|
3043
4576
|
}));
|
|
3044
4577
|
}
|
|
3045
4578
|
ngAfterViewInit() {
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
//
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
4579
|
+
// The header follows the body through (bodyScroll) -> [scrollLeft]. A second
|
|
4580
|
+
// subscription used to live here that queried '.one-grid-header-column' — a
|
|
4581
|
+
// header *cell*, not the viewport — and assigned scrollLeft to it, which
|
|
4582
|
+
// does nothing. It has been removed rather than repaired: the binding path
|
|
4583
|
+
// is the one that works, and it now drives the pinned columns too.
|
|
4584
|
+
// Pinned rows, now that the columns are registered and the body is
|
|
4585
|
+
// listening. Unlike rowData these are not loaded through (onGridReady) —
|
|
4586
|
+
// binding them is the whole contract — so the grid loads them itself.
|
|
4587
|
+
if (this.pinnedTopRowData?.length) {
|
|
4588
|
+
this.api.setPinnedRowData('top', this.pinnedTopRowData);
|
|
4589
|
+
}
|
|
4590
|
+
if (this.pinnedBottomRowData?.length) {
|
|
4591
|
+
this.api.setPinnedRowData('bottom', this.pinnedBottomRowData);
|
|
4592
|
+
}
|
|
3054
4593
|
/**
|
|
3055
4594
|
* Emit Grid Ready Event
|
|
3056
4595
|
*/
|
|
@@ -3149,6 +4688,9 @@ class OneGridComponent {
|
|
|
3149
4688
|
invokePageSizeChange(pageSize) {
|
|
3150
4689
|
this.pageSizeChange.emit(pageSize);
|
|
3151
4690
|
}
|
|
4691
|
+
invokeRowDragEnd(event) {
|
|
4692
|
+
this.rowDragEnd.emit(event);
|
|
4693
|
+
}
|
|
3152
4694
|
invokeExpansionChangedClick(event) {
|
|
3153
4695
|
this.expansionChanged.emit(event);
|
|
3154
4696
|
}
|
|
@@ -3161,15 +4703,16 @@ class OneGridComponent {
|
|
|
3161
4703
|
this.subscriptions.unsubscribe();
|
|
3162
4704
|
}
|
|
3163
4705
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, deps: [{ token: OneGridApi }, { token: OneGridEventService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
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: [
|
|
4706
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridComponent, selector: "one-grid", inputs: { rowNumber: "rowNumber", allowedEdit: "allowedEdit", submitted: "submitted", context: "context", rowData: "rowData", rowSelection: "rowSelection", enableGroupColumnDefs: "enableGroupColumnDefs", groupColumnDefs: "groupColumnDefs", defaultColumnDef: "defaultColumnDef", columnDefs: "columnDefs", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded", pagination: "pagination", paginate: "paginate" }, outputs: { rowDragEnd: "rowDragEnd", onGridReady: "onGridReady", onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", selectionChanged: "selectionChanged", expansionChanged: "expansionChanged", cellOptionSelect: "cellOptionSelect", onRowCellOption: "onRowCellOption", goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [
|
|
3165
4707
|
OneGridEventService,
|
|
3166
4708
|
OneGridApi,
|
|
3167
4709
|
OgColumnService,
|
|
3168
4710
|
OneGridSelectionService,
|
|
3169
4711
|
OneGridExpansionService,
|
|
3170
4712
|
OneGridRowService,
|
|
3171
|
-
OneGridCellService
|
|
3172
|
-
|
|
4713
|
+
OneGridCellService,
|
|
4714
|
+
OneGridRowDragService
|
|
4715
|
+
], viewQueries: [{ propertyName: "ogRef", first: true, predicate: ["ogRef"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<div #ogRef class=\"one-grid\" (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination) {\n <one-grid-pagination\n [paginate]=\"paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>", styles: [""], dependencies: [{ kind: "component", type: OneGridComponent, selector: "one-grid", inputs: ["rowNumber", "allowedEdit", "submitted", "context", "rowData", "rowSelection", "enableGroupColumnDefs", "groupColumnDefs", "defaultColumnDef", "columnDefs", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailCellRendererParams", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded", "pagination", "paginate"], outputs: ["rowDragEnd", "onGridReady", "onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "selectionChanged", "expansionChanged", "cellOptionSelect", "onRowCellOption", "goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }, { kind: "component", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: ["scrollLeft", "allowedEdit", "enableGroupColumnDefs", "api"] }, { kind: "component", type: OneGridBodyComponent, selector: "one-grid-body", inputs: ["rowData", "rowNumber", "api", "pagination", "allowedEdit", "enableGroupColumnDefs", "submitted", "context", "getRowId", "gridCopy$", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailRowMaxHeight", "detailCellRendererParams", "detailGridTemplate", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowCopy", "onRowLocation", "onRowDocument", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "expansionChanged", "bodyScroll", "cellOptionSelect", "rowDragEnd"] }, { kind: "component", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: ["paginate"], outputs: ["goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }] }); }
|
|
3173
4716
|
}
|
|
3174
4717
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, decorators: [{
|
|
3175
4718
|
type: Component,
|
|
@@ -3180,8 +4723,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3180
4723
|
OneGridSelectionService,
|
|
3181
4724
|
OneGridExpansionService,
|
|
3182
4725
|
OneGridRowService,
|
|
3183
|
-
OneGridCellService
|
|
3184
|
-
|
|
4726
|
+
OneGridCellService,
|
|
4727
|
+
OneGridRowDragService
|
|
4728
|
+
], template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<div #ogRef class=\"one-grid\" (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination) {\n <one-grid-pagination\n [paginate]=\"paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>" }]
|
|
3185
4729
|
}], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }], propDecorators: { ogRef: [{
|
|
3186
4730
|
type: ViewChild,
|
|
3187
4731
|
args: ['ogRef', { static: true }]
|
|
@@ -3205,6 +4749,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3205
4749
|
type: Input
|
|
3206
4750
|
}], columnDefs: [{
|
|
3207
4751
|
type: Input
|
|
4752
|
+
}], pinnedTopRowData: [{
|
|
4753
|
+
type: Input
|
|
4754
|
+
}], pinnedBottomRowData: [{
|
|
4755
|
+
type: Input
|
|
3208
4756
|
}], gridType: [{
|
|
3209
4757
|
type: Input
|
|
3210
4758
|
}], masterDetail: [{
|
|
@@ -3223,6 +4771,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3223
4771
|
type: Input
|
|
3224
4772
|
}], groupDefaultExpanded: [{
|
|
3225
4773
|
type: Input
|
|
4774
|
+
}], rowDragEnd: [{
|
|
4775
|
+
type: Output
|
|
3226
4776
|
}], onGridReady: [{
|
|
3227
4777
|
type: Output
|
|
3228
4778
|
}], onRowGotoDetail: [{
|
|
@@ -3385,7 +4935,7 @@ class OneGridCellCurrencyComponent {
|
|
|
3385
4935
|
this.unsubscribe?.();
|
|
3386
4936
|
}
|
|
3387
4937
|
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
|
|
4938
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellCurrencyComponent, selector: "app-one-grid-cell-currency", inputs: { allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"], dependencies: [{ kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }] }); }
|
|
3389
4939
|
}
|
|
3390
4940
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, decorators: [{
|
|
3391
4941
|
type: Component,
|
|
@@ -3481,7 +5031,6 @@ class OneGridModule {
|
|
|
3481
5031
|
CellSelectedPipe,
|
|
3482
5032
|
CellActionButtonEnablePipe,
|
|
3483
5033
|
CellNumberValuePipe,
|
|
3484
|
-
CellDisableResolverPipe,
|
|
3485
5034
|
CellFieldPipe,
|
|
3486
5035
|
CellKeyPipe,
|
|
3487
5036
|
CellLevelPaddingPipe,
|
|
@@ -3495,17 +5044,14 @@ class OneGridModule {
|
|
|
3495
5044
|
OneCellRendererComponentDirective,
|
|
3496
5045
|
OneCellTextExpandDirective,
|
|
3497
5046
|
OneGridCellCurrencyComponent,
|
|
3498
|
-
OneGridCellOptionMenuComponent
|
|
5047
|
+
OneGridCellOptionMenuComponent,
|
|
5048
|
+
OneGridRowDragHandleComponent], imports: [CommonModule,
|
|
3499
5049
|
FormsModule,
|
|
3500
5050
|
ScrollingModule,
|
|
3501
|
-
|
|
3502
|
-
NgbHighlight,
|
|
3503
|
-
ReactiveFormsModule,
|
|
3504
|
-
CdkDragPlaceholder, i3.LucideAngularModule], exports: [OneGridComponent] }); }
|
|
5051
|
+
ReactiveFormsModule, i2$1.LucideAngularModule], exports: [OneGridComponent] }); }
|
|
3505
5052
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [CommonModule,
|
|
3506
5053
|
FormsModule,
|
|
3507
5054
|
ScrollingModule,
|
|
3508
|
-
NgbHighlight,
|
|
3509
5055
|
ReactiveFormsModule,
|
|
3510
5056
|
LucideAngularModule.pick(icons)] }); }
|
|
3511
5057
|
}
|
|
@@ -3531,7 +5077,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3531
5077
|
CellSelectedPipe,
|
|
3532
5078
|
CellActionButtonEnablePipe,
|
|
3533
5079
|
CellNumberValuePipe,
|
|
3534
|
-
CellDisableResolverPipe,
|
|
3535
5080
|
CellFieldPipe,
|
|
3536
5081
|
CellKeyPipe,
|
|
3537
5082
|
CellLevelPaddingPipe,
|
|
@@ -3545,16 +5090,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3545
5090
|
OneCellRendererComponentDirective,
|
|
3546
5091
|
OneCellTextExpandDirective,
|
|
3547
5092
|
OneGridCellCurrencyComponent,
|
|
3548
|
-
OneGridCellOptionMenuComponent
|
|
5093
|
+
OneGridCellOptionMenuComponent,
|
|
5094
|
+
OneGridRowDragHandleComponent
|
|
3549
5095
|
],
|
|
3550
5096
|
imports: [
|
|
3551
5097
|
CommonModule,
|
|
3552
5098
|
FormsModule,
|
|
3553
5099
|
ScrollingModule,
|
|
3554
|
-
NgbTypeahead,
|
|
3555
|
-
NgbHighlight,
|
|
3556
5100
|
ReactiveFormsModule,
|
|
3557
|
-
CdkDragPlaceholder,
|
|
3558
5101
|
LucideAngularModule.pick(icons),
|
|
3559
5102
|
],
|
|
3560
5103
|
exports: [OneGridComponent]
|
|
@@ -3569,5 +5112,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3569
5112
|
* Generated bundle index. Do not edit.
|
|
3570
5113
|
*/
|
|
3571
5114
|
|
|
3572
|
-
export { OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, applyDefaultColumnDef, getCellError, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams };
|
|
5115
|
+
export { ONE_DEFAULT_COLUMN_WIDTH, OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridRowDragService, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, applyDefaultColumnDef, getCellError, resolveColumnWidth, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams, resolveRowHeight };
|
|
3573
5116
|
//# sourceMappingURL=one-grid-core-angular.mjs.map
|