@one-grid-core/angular 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -9
- package/esm2022/lib/components/one-grid/one-grid.component.mjs +244 -11
- package/esm2022/lib/components/one-grid-body/one-grid-body.component.mjs +446 -34
- package/esm2022/lib/components/one-grid-cell-currency/one-grid-cell-currency.component.mjs +6 -5
- package/esm2022/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.mjs +3 -3
- package/esm2022/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.mjs +3 -3
- package/esm2022/lib/components/one-grid-checkbox/one-grid-checkbox.component.mjs +7 -18
- package/esm2022/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.mjs +6 -6
- package/esm2022/lib/components/one-grid-header/one-grid-header.component.mjs +221 -17
- package/esm2022/lib/components/one-grid-header-menu/one-grid-header-menu.component.mjs +137 -0
- package/esm2022/lib/components/one-grid-pagination/one-grid-pagination.component.mjs +6 -5
- package/esm2022/lib/components/one-grid-row-action/one-grid-row-action.component.mjs +4 -4
- package/esm2022/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.mjs +5 -3
- package/esm2022/lib/components/one-grid-status/one-grid-status.component.mjs +4 -4
- package/esm2022/lib/core/directives/one-cell-input-validator.directive.mjs +3 -3
- package/esm2022/lib/core/directives/one-cell-renderer-component.directive.mjs +3 -3
- package/esm2022/lib/core/directives/one-cell-text-expand.directive.mjs +3 -3
- package/esm2022/lib/core/icons/one-grid-icons.mjs +38 -0
- package/esm2022/lib/core/instants/one-grid-api.class.mjs +296 -3
- 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-filter.model.mjs +1 -1
- package/esm2022/lib/core/pipes/aria-sort.pipe.mjs +31 -0
- package/esm2022/lib/core/pipes/cell-action-button-enable.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/cell-editable.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/cell-invalid.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/cell-key.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/cell-level-padding.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/cell-selected.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/cell-state-renderer.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/column-sort-state.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/header-checkbox-checked.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/header-checkbox-disabled.pipe.mjs +3 -3
- package/esm2022/lib/core/pipes/header-checkbox-indeterminate.pipe.mjs +3 -3
- package/esm2022/lib/core/services/og-column.service.mjs +153 -30
- package/esm2022/lib/core/services/one-grid-cell.service.mjs +16 -14
- package/esm2022/lib/helpers/one-cell.helper.mjs +30 -1
- package/esm2022/lib/helpers/one-column-def.helper.mjs +16 -1
- package/esm2022/lib/helpers/one-csv.helper.mjs +58 -0
- package/esm2022/lib/helpers/one-filter.helper.mjs +11 -1
- package/esm2022/lib/helpers/one-row-grouping.helper.mjs +103 -0
- package/esm2022/lib/index.mjs +3 -1
- package/esm2022/lib/one-grid.module.mjs +24 -114
- package/fesm2022/one-grid-core-angular.mjs +2328 -851
- package/fesm2022/one-grid-core-angular.mjs.map +1 -1
- package/lib/components/one-grid/one-grid.component.d.ts +101 -4
- package/lib/components/one-grid-body/one-grid-body.component.d.ts +83 -2
- package/lib/components/one-grid-cell-currency/one-grid-cell-currency.component.d.ts +1 -1
- package/lib/components/one-grid-cell-group-renderer/one-grid-cell-group-renderer.component.d.ts +1 -1
- package/lib/components/one-grid-cell-option-menu/one-grid-cell-option-menu.component.d.ts +1 -1
- package/lib/components/one-grid-checkbox/one-grid-checkbox.component.d.ts +1 -1
- package/lib/components/one-grid-checkbox-header/one-grid-checkbox-header.component.d.ts +1 -1
- package/lib/components/one-grid-header/one-grid-header.component.d.ts +69 -4
- package/lib/components/one-grid-header-menu/one-grid-header-menu.component.d.ts +52 -0
- package/lib/components/one-grid-pagination/one-grid-pagination.component.d.ts +1 -1
- package/lib/components/one-grid-row-action/one-grid-row-action.component.d.ts +1 -1
- package/lib/components/one-grid-row-drag-handle/one-grid-row-drag-handle.component.d.ts +1 -1
- package/lib/components/one-grid-status/one-grid-status.component.d.ts +1 -1
- package/lib/core/directives/one-cell-input-validator.directive.d.ts +1 -1
- package/lib/core/directives/one-cell-renderer-component.directive.d.ts +1 -1
- package/lib/core/directives/one-cell-text-expand.directive.d.ts +1 -1
- package/lib/core/icons/one-grid-icons.d.ts +35 -0
- package/lib/core/instants/one-grid-api.class.d.ts +115 -1
- package/lib/core/models/one-column-def-types.model.d.ts +77 -2
- package/lib/core/models/one-event-types.model.d.ts +19 -0
- package/lib/core/models/one-filter.model.d.ts +8 -1
- package/lib/core/pipes/aria-sort.pipe.d.ts +15 -0
- package/lib/core/pipes/cell-action-button-enable.pipe.d.ts +1 -1
- package/lib/core/pipes/cell-editable.pipe.d.ts +1 -1
- package/lib/core/pipes/cell-invalid.pipe.d.ts +1 -1
- package/lib/core/pipes/cell-key.pipe.d.ts +1 -1
- package/lib/core/pipes/cell-level-padding.pipe.d.ts +1 -1
- package/lib/core/pipes/cell-selected.pipe.d.ts +1 -1
- package/lib/core/pipes/cell-state-renderer.pipe.d.ts +1 -1
- package/lib/core/pipes/column-sort-state.pipe.d.ts +1 -1
- package/lib/core/pipes/header-checkbox-checked.pipe.d.ts +1 -1
- package/lib/core/pipes/header-checkbox-disabled.pipe.d.ts +1 -1
- package/lib/core/pipes/header-checkbox-indeterminate.pipe.d.ts +1 -1
- package/lib/core/services/og-column.service.d.ts +46 -2
- package/lib/core/services/one-grid-cell.service.d.ts +0 -3
- package/lib/helpers/one-cell.helper.d.ts +17 -0
- package/lib/helpers/one-column-def.helper.d.ts +9 -0
- package/lib/helpers/one-csv.helper.d.ts +29 -0
- package/lib/helpers/one-row-grouping.helper.d.ts +35 -0
- package/lib/index.d.ts +2 -0
- package/lib/one-grid.module.d.ts +15 -37
- package/package.json +1 -1
- package/scss/_one-grid.scss +127 -0
- package/esm2022/lib/core/pipes/cell-action-button-visible.pipe.mjs +0 -17
- package/esm2022/lib/core/pipes/cell-field-value-empty.pipe.mjs +0 -26
- package/esm2022/lib/core/pipes/cell-field-value.pipe.mjs +0 -25
- package/esm2022/lib/core/pipes/cell-field.pipe.mjs +0 -20
- package/esm2022/lib/core/pipes/cell-number-value.pipe.mjs +0 -19
- package/esm2022/lib/core/pipes/cell-state-group-renderer.pipe.mjs +0 -21
- package/esm2022/lib/core/pipes/cell-value-getter.pipe.mjs +0 -23
- package/lib/core/pipes/cell-action-button-visible.pipe.d.ts +0 -9
- package/lib/core/pipes/cell-field-value-empty.pipe.d.ts +0 -7
- package/lib/core/pipes/cell-field-value.pipe.d.ts +0 -7
- package/lib/core/pipes/cell-field.pipe.d.ts +0 -7
- package/lib/core/pipes/cell-number-value.pipe.d.ts +0 -7
- package/lib/core/pipes/cell-state-group-renderer.pipe.d.ts +0 -9
- package/lib/core/pipes/cell-value-getter.pipe.d.ts +0 -9
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { v4 } from 'uuid';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { Injectable,
|
|
3
|
+
import { Injectable, inject, NgZone, Pipe, Input, Component, EventEmitter, HostListener, ViewChildren, Output, ViewChild, ChangeDetectionStrategy, Directive, NgModule } from '@angular/core';
|
|
4
4
|
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
|
|
5
5
|
import _ from 'lodash';
|
|
6
|
-
import { filter, map } from 'rxjs/operators';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
|
|
6
|
+
import { filter, map, debounceTime, take } from 'rxjs/operators';
|
|
7
|
+
import { NgClass, NgStyle, NgTemplateOutlet, DecimalPipe } from '@angular/common';
|
|
8
|
+
import * as i2 from 'lucide-angular';
|
|
9
|
+
import { GripVertical, ChevronRight, ChevronLeft, ChevronLast, ChevronFirst, ArrowUpAZ, ArrowDownZA, ArrowDownUp, LucideIconProvider, LUCIDE_ICONS, LucideAngularModule } from 'lucide-angular';
|
|
10
|
+
import * as i5 from '@angular/cdk/scrolling';
|
|
11
|
+
import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
13
12
|
import * as i3 from '@angular/forms';
|
|
14
|
-
import { FormsModule
|
|
13
|
+
import { FormsModule } from '@angular/forms';
|
|
15
14
|
|
|
16
15
|
/** Width used when a column defines neither `width` nor `minWidth`. */
|
|
17
16
|
const ONE_DEFAULT_COLUMN_WIDTH = 150;
|
|
@@ -65,6 +64,182 @@ function resolveOneColumDefParams(prop, params) {
|
|
|
65
64
|
function resolveOneValueGetterParams(prop, params) {
|
|
66
65
|
return typeof prop === 'function' ? prop(params) : prop;
|
|
67
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Run a column's `valueGetter` for one row.
|
|
69
|
+
*
|
|
70
|
+
* Was `CellValueGetterPipe`, which existed only to call this resolver from a
|
|
71
|
+
* service. The pipe added a module declaration and an indirection for a
|
|
72
|
+
* template use that never existed.
|
|
73
|
+
*/
|
|
74
|
+
function resolveCellValueGetter(data, node, column, context) {
|
|
75
|
+
return resolveOneValueGetterParams(column?.valueGetter, {
|
|
76
|
+
data,
|
|
77
|
+
rowIndex: node?.rowIndex,
|
|
78
|
+
node,
|
|
79
|
+
context,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The columns an export can say anything about: those that read a row —
|
|
85
|
+
* through a field or a valueGetter. Action columns have neither, and the
|
|
86
|
+
* row-number column reads state the data does not hold; both are skipped.
|
|
87
|
+
*/
|
|
88
|
+
function exportableColumns(columns) {
|
|
89
|
+
return (columns ?? []).filter(column => (column.field && column.field !== '_oneRowNumber') || column.valueGetter);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Serialize rows to RFC 4180 CSV.
|
|
93
|
+
*
|
|
94
|
+
* Each cell goes through the same value pipeline the grid renders with:
|
|
95
|
+
* `valueGetter` when declared, the field otherwise, then `valueFormatter` —
|
|
96
|
+
* so the file says what the screen says. Null and undefined export as empty
|
|
97
|
+
* cells, before the formatter, exactly as the grid leaves them to the
|
|
98
|
+
* placeholder.
|
|
99
|
+
*/
|
|
100
|
+
function buildCsv(nodes, columns, context = null, options = {}) {
|
|
101
|
+
const separator = options.columnSeparator ?? ',';
|
|
102
|
+
const exportable = exportableColumns(columns);
|
|
103
|
+
const lines = [];
|
|
104
|
+
if (options.includeHeaders ?? true) {
|
|
105
|
+
lines.push(exportable.map(column => escapeCsvCell(column.headerName ?? column.field ?? '', separator)).join(separator));
|
|
106
|
+
}
|
|
107
|
+
nodes?.forEach((node, rowIndex) => {
|
|
108
|
+
const cells = exportable.map(column => {
|
|
109
|
+
let value = column.valueGetter
|
|
110
|
+
? column.valueGetter({ data: node.data, rowIndex, node, context })
|
|
111
|
+
: node.data?.[column.field];
|
|
112
|
+
if (value === null || value === undefined)
|
|
113
|
+
return '';
|
|
114
|
+
if (column.valueFormatter) {
|
|
115
|
+
value = column.valueFormatter({
|
|
116
|
+
value,
|
|
117
|
+
data: node.data,
|
|
118
|
+
node,
|
|
119
|
+
rowIndex,
|
|
120
|
+
field: column.field,
|
|
121
|
+
columnDef: column,
|
|
122
|
+
context,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return escapeCsvCell(value, separator);
|
|
126
|
+
});
|
|
127
|
+
lines.push(cells.join(separator));
|
|
128
|
+
});
|
|
129
|
+
// CRLF is what RFC 4180 asks for, and what spreadsheet apps expect.
|
|
130
|
+
return lines.join('\r\n');
|
|
131
|
+
}
|
|
132
|
+
/** Quote a cell when it holds the separator, a quote, or a line break. */
|
|
133
|
+
function escapeCsvCell(value, separator) {
|
|
134
|
+
const text = `${value}`;
|
|
135
|
+
if (text.includes(separator) || text.includes('"') || text.includes('\n') || text.includes('\r')) {
|
|
136
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
137
|
+
}
|
|
138
|
+
return text;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Marks a synthetic group row, so anything can tell it from real data. */
|
|
142
|
+
const ONE_GROUP_FLAG = '__oneGroup';
|
|
143
|
+
/** Stable id for a group row: its value path. Survives every rebuild. */
|
|
144
|
+
const ONE_GROUP_ID_FIELD = '__oneGroupId';
|
|
145
|
+
/** The label the auto group column shows for a group row. */
|
|
146
|
+
const ONE_GROUP_VALUE_FIELD = '__oneGroupValue';
|
|
147
|
+
/** Where a group row keeps its children, for the tree pipeline to recurse. */
|
|
148
|
+
const ONE_GROUP_CHILDREN_FIELD = '__oneGroupChildren';
|
|
149
|
+
/** What a group of rows with no value in the grouped field is labelled. */
|
|
150
|
+
const ONE_GROUP_BLANK_LABEL = '(Blank)';
|
|
151
|
+
/** The `rowGroup` fields of a column set, in definition order. */
|
|
152
|
+
function rowGroupFields(columns) {
|
|
153
|
+
return (columns ?? [])
|
|
154
|
+
.filter(column => column.rowGroup && column.field)
|
|
155
|
+
.map(column => column.field);
|
|
156
|
+
}
|
|
157
|
+
/** The `aggFunc` columns of a column set. */
|
|
158
|
+
function aggColumns(columns) {
|
|
159
|
+
return (columns ?? [])
|
|
160
|
+
.filter(column => column.aggFunc && column.field)
|
|
161
|
+
.map(column => ({ field: column.field, aggFunc: column.aggFunc }));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fold flat rows into a tree of synthetic group rows, one level per group
|
|
165
|
+
* field, in first-appearance order. Each group row carries:
|
|
166
|
+
*
|
|
167
|
+
* - the marker, id and label fields above;
|
|
168
|
+
* - its own grouped field's value, so sorting and filtering read it naturally;
|
|
169
|
+
* - one aggregated value per `aggFunc` column, computed over the group's
|
|
170
|
+
* leaves (not its child groups), stored under the column's own field so the
|
|
171
|
+
* ordinary cell pipeline renders it.
|
|
172
|
+
*
|
|
173
|
+
* Pure: the input rows are referenced, never mutated.
|
|
174
|
+
*/
|
|
175
|
+
function buildGroupedRows(rows, groupFields, aggregations = []) {
|
|
176
|
+
if (!groupFields?.length)
|
|
177
|
+
return [...(rows ?? [])];
|
|
178
|
+
return groupLevel(rows ?? [], groupFields, 0, aggregations, 'og-group');
|
|
179
|
+
}
|
|
180
|
+
function groupLevel(rows, groupFields, level, aggregations, idPrefix) {
|
|
181
|
+
const field = groupFields[level];
|
|
182
|
+
// Insertion-ordered buckets, keyed by the value's string form. The raw
|
|
183
|
+
// value is kept as well, so the group row exposes the real value under the
|
|
184
|
+
// grouped field, not its stringification.
|
|
185
|
+
const buckets = new Map();
|
|
186
|
+
for (const row of rows) {
|
|
187
|
+
const value = row?.[field];
|
|
188
|
+
const key = value === null || value === undefined || value === '' ? ONE_GROUP_BLANK_LABEL : String(value);
|
|
189
|
+
const bucket = buckets.get(key);
|
|
190
|
+
if (bucket) {
|
|
191
|
+
bucket.rows.push(row);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
buckets.set(key, { value, rows: [row] });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return [...buckets.entries()].map(([key, bucket]) => {
|
|
198
|
+
const id = `${idPrefix}:${key}`;
|
|
199
|
+
const children = level + 1 < groupFields.length
|
|
200
|
+
? groupLevel(bucket.rows, groupFields, level + 1, aggregations, id)
|
|
201
|
+
: bucket.rows;
|
|
202
|
+
const groupRow = {
|
|
203
|
+
[ONE_GROUP_FLAG]: true,
|
|
204
|
+
[ONE_GROUP_ID_FIELD]: id,
|
|
205
|
+
[ONE_GROUP_VALUE_FIELD]: key,
|
|
206
|
+
[field]: bucket.value,
|
|
207
|
+
[ONE_GROUP_CHILDREN_FIELD]: children,
|
|
208
|
+
};
|
|
209
|
+
// Aggregates always read the original leaves, so a two-level group's
|
|
210
|
+
// parent is not averaging averages.
|
|
211
|
+
for (const aggregation of aggregations) {
|
|
212
|
+
groupRow[aggregation.field] = aggregate(bucket.rows, aggregation.field, aggregation.aggFunc);
|
|
213
|
+
}
|
|
214
|
+
return groupRow;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/** Apply one aggregation over a set of leaf rows. */
|
|
218
|
+
function aggregate(rows, field, aggFunc) {
|
|
219
|
+
const values = rows
|
|
220
|
+
.map(row => row?.[field])
|
|
221
|
+
.filter(value => value !== null && value !== undefined);
|
|
222
|
+
if (typeof aggFunc === 'function') {
|
|
223
|
+
return aggFunc({ values, rows: rows, field });
|
|
224
|
+
}
|
|
225
|
+
switch (aggFunc) {
|
|
226
|
+
case 'count': return values.length;
|
|
227
|
+
case 'first': return values.length ? values[0] : null;
|
|
228
|
+
case 'last': return values.length ? values[values.length - 1] : null;
|
|
229
|
+
}
|
|
230
|
+
// The numeric aggregates ignore what does not parse, so one stray string in
|
|
231
|
+
// a column does not turn the whole group's total into NaN.
|
|
232
|
+
const numbers = values.map(Number).filter(Number.isFinite);
|
|
233
|
+
if (!numbers.length)
|
|
234
|
+
return null;
|
|
235
|
+
switch (aggFunc) {
|
|
236
|
+
case 'sum': return numbers.reduce((total, value) => total + value, 0);
|
|
237
|
+
case 'min': return Math.min(...numbers);
|
|
238
|
+
case 'max': return Math.max(...numbers);
|
|
239
|
+
case 'avg': return numbers.reduce((total, value) => total + value, 0) / numbers.length;
|
|
240
|
+
default: return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
68
243
|
|
|
69
244
|
/**
|
|
70
245
|
* One Grid Validators
|
|
@@ -371,13 +546,23 @@ function matchesAdvancedFilters(node, filters) {
|
|
|
371
546
|
const val = (node.data[filter.field] ?? '').toString().toLowerCase();
|
|
372
547
|
const results = filter.conditions.map(condition => {
|
|
373
548
|
const cmp = (condition.value ?? '').toString().toLowerCase();
|
|
549
|
+
const cmpTo = (condition.valueTo ?? '').toString().toLowerCase();
|
|
374
550
|
switch (condition.operator) {
|
|
375
551
|
case 'equals': return val === cmp;
|
|
552
|
+
case 'notEqual': return val !== cmp;
|
|
376
553
|
case 'contains': return val.includes(cmp);
|
|
554
|
+
case 'notContains': return !val.includes(cmp);
|
|
377
555
|
case 'startsWith': return val.startsWith(cmp);
|
|
378
556
|
case 'endsWith': return val.endsWith(cmp);
|
|
379
557
|
case 'greaterThan': return compare(val, cmp) > 0;
|
|
558
|
+
case 'greaterThanOrEqual': return compare(val, cmp) >= 0;
|
|
380
559
|
case 'lessThan': return compare(val, cmp) < 0;
|
|
560
|
+
case 'lessThanOrEqual': return compare(val, cmp) <= 0;
|
|
561
|
+
case 'inRange': return compare(val, cmp) >= 0 && compare(val, cmpTo) <= 0;
|
|
562
|
+
// `val` has already been through `?? ''`, so null and undefined land
|
|
563
|
+
// here as the empty string. 0 stringifies to '0' and does not.
|
|
564
|
+
case 'blank': return val === '';
|
|
565
|
+
case 'notBlank': return val !== '';
|
|
381
566
|
default: return true; // Unknown operator = ignore
|
|
382
567
|
}
|
|
383
568
|
});
|
|
@@ -474,6 +659,35 @@ function resolveCellClass(column, row) {
|
|
|
474
659
|
? column.cellClass(row)
|
|
475
660
|
: column.cellClass;
|
|
476
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* The cell's raw field value, with blank treated as absent.
|
|
664
|
+
*
|
|
665
|
+
* Was `CellFieldValuePipe`. It lived as an Angular pipe but was only ever
|
|
666
|
+
* instantiated inside a service — a utility wearing a `@Pipe` decorator, which
|
|
667
|
+
* also cost it a module declaration for a template use that never existed.
|
|
668
|
+
*/
|
|
669
|
+
function readCellValue(data, field) {
|
|
670
|
+
const value = getFieldValue(data, field);
|
|
671
|
+
if (value === null || value === undefined)
|
|
672
|
+
return null;
|
|
673
|
+
if (typeof value === 'string' && value.trim() === '')
|
|
674
|
+
return null;
|
|
675
|
+
return value;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Whether a cell has nothing to show.
|
|
679
|
+
*
|
|
680
|
+
* Was `CellFieldValueEmptyPipe`. `field` is optional on `OneColumnDef` — an
|
|
681
|
+
* action-only column legitimately has none — and reading it unguarded used to
|
|
682
|
+
* throw while the grid built cell state, taking the whole grid down. No field
|
|
683
|
+
* means nothing to read, which is empty by definition.
|
|
684
|
+
*/
|
|
685
|
+
function isCellEmpty(data, field) {
|
|
686
|
+
if (!data || !field)
|
|
687
|
+
return true;
|
|
688
|
+
const value = getFieldValue(data, field);
|
|
689
|
+
return value === null || value === undefined || value === '';
|
|
690
|
+
}
|
|
477
691
|
|
|
478
692
|
class OgColumnService {
|
|
479
693
|
constructor() {
|
|
@@ -483,6 +697,14 @@ class OgColumnService {
|
|
|
483
697
|
* callbacks do. Set during init; null in a bare service.
|
|
484
698
|
*/
|
|
485
699
|
this.context = null;
|
|
700
|
+
/**
|
|
701
|
+
* Every column, hidden ones included, in display order. `columnDefs$` below
|
|
702
|
+
* carries only the visible subset — it is what the header and body render
|
|
703
|
+
* from, so hiding a column means leaving it out of the emission, and every
|
|
704
|
+
* consumer keeps working with no notion of visibility at all. This list is
|
|
705
|
+
* what remembers a hidden column's place, width and pin for when it returns.
|
|
706
|
+
*/
|
|
707
|
+
this.allColumnDefs = [];
|
|
486
708
|
this.columnDefs$ = new BehaviorSubject([]);
|
|
487
709
|
this.groupColumnDefs$ = new BehaviorSubject([]);
|
|
488
710
|
}
|
|
@@ -510,9 +732,20 @@ class OgColumnService {
|
|
|
510
732
|
};
|
|
511
733
|
return this.resolveHeaderPresentation(column);
|
|
512
734
|
});
|
|
735
|
+
this.commit(columns);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Re-band, re-measure and publish a new column list.
|
|
739
|
+
*
|
|
740
|
+
* Every mutation funnels here so the master list and the visible emission
|
|
741
|
+
* cannot drift apart.
|
|
742
|
+
*/
|
|
743
|
+
commit(columns) {
|
|
513
744
|
const ordered = this.applyPinning(columns);
|
|
514
|
-
this.
|
|
515
|
-
|
|
745
|
+
this.allColumnDefs = ordered;
|
|
746
|
+
const visible = ordered.filter(column => !column.hide);
|
|
747
|
+
this.recalculateGroups(visible);
|
|
748
|
+
this.columnDefs$.next(visible);
|
|
516
749
|
}
|
|
517
750
|
/**
|
|
518
751
|
* Move pinned columns to their edge and measure how far in each one sits.
|
|
@@ -536,11 +769,14 @@ class OgColumnService {
|
|
|
536
769
|
return columns;
|
|
537
770
|
}
|
|
538
771
|
const center = columns.filter(column => column.pinned !== 'left' && column.pinned !== 'right');
|
|
772
|
+
// Hidden columns keep their place in the band but take up no space, so
|
|
773
|
+
// they are skipped when measuring offsets and never carry the divider.
|
|
539
774
|
let offset = 0;
|
|
540
775
|
for (const column of left) {
|
|
541
776
|
column._pinnedOffset = offset;
|
|
542
777
|
column._pinnedEdge = false;
|
|
543
|
-
|
|
778
|
+
if (!column.hide)
|
|
779
|
+
offset += resolveColumnWidth(column);
|
|
544
780
|
}
|
|
545
781
|
// Right-pinned columns are measured from the right edge inwards, so the
|
|
546
782
|
// last declared one sits flush and the earlier ones stack outside it.
|
|
@@ -548,46 +784,146 @@ class OgColumnService {
|
|
|
548
784
|
for (let index = right.length - 1; index >= 0; index--) {
|
|
549
785
|
right[index]._pinnedOffset = offset;
|
|
550
786
|
right[index]._pinnedEdge = false;
|
|
551
|
-
|
|
787
|
+
if (!right[index].hide)
|
|
788
|
+
offset += resolveColumnWidth(right[index]);
|
|
552
789
|
}
|
|
553
790
|
for (const column of center) {
|
|
554
791
|
column._pinnedOffset = null;
|
|
555
792
|
column._pinnedEdge = false;
|
|
556
793
|
}
|
|
557
|
-
// Only the innermost column of each band carries the divider, so
|
|
558
|
-
// pinned columns do not each cast a shadow onto the next.
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
|
|
794
|
+
// Only the innermost visible column of each band carries the divider, so
|
|
795
|
+
// stacked pinned columns do not each cast a shadow onto the next.
|
|
796
|
+
const leftVisible = left.filter(column => !column.hide);
|
|
797
|
+
const rightVisible = right.filter(column => !column.hide);
|
|
798
|
+
if (leftVisible.length)
|
|
799
|
+
leftVisible[leftVisible.length - 1]._pinnedEdge = true;
|
|
800
|
+
if (rightVisible.length)
|
|
801
|
+
rightVisible[0]._pinnedEdge = true;
|
|
563
802
|
return [...left, ...center, ...right];
|
|
564
803
|
}
|
|
565
|
-
/** Total width of the columns frozen against the given edge. */
|
|
804
|
+
/** Total width of the visible columns frozen against the given edge. */
|
|
566
805
|
getPinnedWidth(side) {
|
|
567
806
|
return this.columnDefs$.getValue()
|
|
568
807
|
.filter(column => column.pinned === side)
|
|
569
808
|
.reduce((total, column) => total + resolveColumnWidth(column), 0);
|
|
570
809
|
}
|
|
571
|
-
// Update single column
|
|
810
|
+
// Update single column. Works on the master list, so a hidden column can be
|
|
811
|
+
// patched — including `hide` itself, which is how visibility toggles.
|
|
572
812
|
updateColumnDef(id, changes) {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
813
|
+
const updatedColumns = this.allColumnDefs.map(col => col.id === id ? { ...col, ...changes } : col);
|
|
814
|
+
// Re-pinned (inside commit), not just re-grouped: resizing a pinned column
|
|
815
|
+
// changes where the ones inside it have to sit, and this is the path a
|
|
816
|
+
// column resize takes.
|
|
817
|
+
this.commit(updatedColumns);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Reorder columns. The indexes are *visible* positions — they come from the
|
|
821
|
+
* header, which only renders visible columns — so they are mapped onto the
|
|
822
|
+
* master list before splicing. A hidden column between the two keeps its
|
|
823
|
+
* place relative to its neighbours.
|
|
824
|
+
*/
|
|
583
825
|
reorderColumnDefs(fromIndex, toIndex) {
|
|
584
|
-
const columns = _.clone(this.
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
826
|
+
const columns = _.clone(this.allColumnDefs);
|
|
827
|
+
const visible = columns.filter(column => !column.hide);
|
|
828
|
+
const moved = visible[fromIndex];
|
|
829
|
+
if (!moved)
|
|
830
|
+
return;
|
|
831
|
+
columns.splice(columns.indexOf(moved), 1);
|
|
832
|
+
// Land before the visible column now at the target index — the same splice
|
|
833
|
+
// the visible-only list used to do after removal — or at the end when the
|
|
834
|
+
// target is past the last visible column.
|
|
835
|
+
const visibleAfter = visible.filter(column => column !== moved);
|
|
836
|
+
const anchor = visibleAfter[toIndex];
|
|
837
|
+
columns.splice(anchor ? columns.indexOf(anchor) : columns.length, 0, moved);
|
|
838
|
+
// Dragging can drop a column between two pinned ones; re-pinning (inside
|
|
839
|
+
// commit) puts the bands back in order rather than leaving them
|
|
840
|
+
// interleaved, which would make the computed offsets meaningless.
|
|
841
|
+
this.commit(columns);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Distribute the viewport width left over by fixed columns among the flex
|
|
845
|
+
* ones, in proportion to their `flex` values and clamped to their bounds.
|
|
846
|
+
*
|
|
847
|
+
* @returns Whether any width actually changed — the caller only dispatches
|
|
848
|
+
* (and the body only re-triggers) on a real change, which is what terminates
|
|
849
|
+
* the columnChanged → flex → columnChanged loop.
|
|
850
|
+
*/
|
|
851
|
+
applyFlexWidths(availableWidth) {
|
|
852
|
+
const visible = this.columnDefs$.getValue();
|
|
853
|
+
const flexColumns = visible.filter(column => (column.flex ?? 0) > 0);
|
|
854
|
+
if (!flexColumns.length || !(availableWidth > 0))
|
|
855
|
+
return false;
|
|
856
|
+
const fixedWidth = visible
|
|
857
|
+
.filter(column => !((column.flex ?? 0) > 0))
|
|
858
|
+
.reduce((total, column) => total + resolveColumnWidth(column), 0);
|
|
859
|
+
let remaining = Math.max(availableWidth - fixedWidth, 0);
|
|
860
|
+
// Clamped columns stop flexing and release their share to the rest, so
|
|
861
|
+
// the pass repeats until the split is stable. Each pass settles at least
|
|
862
|
+
// one column; the loop is bounded by the column count.
|
|
863
|
+
const widths = new Map();
|
|
864
|
+
let unsettled = [...flexColumns];
|
|
865
|
+
while (unsettled.length) {
|
|
866
|
+
const totalFlex = unsettled.reduce((total, column) => total + (column.flex ?? 0), 0);
|
|
867
|
+
const share = remaining / totalFlex;
|
|
868
|
+
const clamped = unsettled.filter(column => {
|
|
869
|
+
const ideal = share * (column.flex ?? 0);
|
|
870
|
+
const min = Math.max(column.minWidth ?? 0, 50);
|
|
871
|
+
const max = column.maxWidth ?? Number.POSITIVE_INFINITY;
|
|
872
|
+
const width = Math.min(Math.max(ideal, min), max);
|
|
873
|
+
if (width === ideal)
|
|
874
|
+
return false;
|
|
875
|
+
widths.set(column.id, Math.round(width));
|
|
876
|
+
remaining = Math.max(remaining - width, 0);
|
|
877
|
+
return true;
|
|
878
|
+
});
|
|
879
|
+
unsettled = unsettled.filter(column => !clamped.includes(column));
|
|
880
|
+
if (!clamped.length) {
|
|
881
|
+
unsettled.forEach(column => widths.set(column.id, Math.floor(share * (column.flex ?? 0))));
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const changed = flexColumns.some(column => widths.get(column.id) !== resolveColumnWidth(column));
|
|
886
|
+
if (!changed)
|
|
887
|
+
return false;
|
|
888
|
+
this.commit(this.allColumnDefs.map(column => widths.has(column.id) ? { ...column, width: widths.get(column.id) } : column));
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* One JSON-safe entry per column — hidden ones included — in display order.
|
|
893
|
+
*/
|
|
894
|
+
getColumnStateSnapshot() {
|
|
895
|
+
return this.allColumnDefs.map(column => {
|
|
896
|
+
const entry = { id: column.id, hide: !!column.hide, pinned: column.pinned ?? null };
|
|
897
|
+
if (column.width != null)
|
|
898
|
+
entry.width = column.width;
|
|
899
|
+
return entry;
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Restore a saved snapshot: order, width, pin and visibility.
|
|
904
|
+
*
|
|
905
|
+
* Entries are applied in the order given and become the front of the new
|
|
906
|
+
* column order; columns the snapshot does not mention keep their current
|
|
907
|
+
* relative order behind them, and ids the grid no longer has are skipped —
|
|
908
|
+
* so a stale snapshot degrades to a partial restore instead of an error.
|
|
909
|
+
* Only the properties present on an entry are applied.
|
|
910
|
+
*/
|
|
911
|
+
applyColumnState(entries) {
|
|
912
|
+
const byId = new Map(this.allColumnDefs.map(column => [column.id, column]));
|
|
913
|
+
const mentioned = (entries ?? []).filter(entry => entry && byId.has(entry.id));
|
|
914
|
+
const mentionedIds = new Set(mentioned.map(entry => entry.id));
|
|
915
|
+
const restored = mentioned.map(entry => {
|
|
916
|
+
const column = { ...byId.get(entry.id) };
|
|
917
|
+
if (entry.width != null)
|
|
918
|
+
column.width = entry.width;
|
|
919
|
+
if ('pinned' in entry)
|
|
920
|
+
column.pinned = entry.pinned ?? undefined;
|
|
921
|
+
if ('hide' in entry)
|
|
922
|
+
column.hide = !!entry.hide;
|
|
923
|
+
return column;
|
|
924
|
+
});
|
|
925
|
+
const rest = this.allColumnDefs.filter(column => !mentionedIds.has(column.id));
|
|
926
|
+
this.commit([...restored, ...rest]);
|
|
591
927
|
}
|
|
592
928
|
/**
|
|
593
929
|
* Group Header Columns
|
|
@@ -633,6 +969,7 @@ class OgColumnService {
|
|
|
633
969
|
col._resolvedSortable = resolveOneCellRendererParams(col.sortable, params);
|
|
634
970
|
col._resolvedResizable = resolveOneCellRendererParams(col.resizable, params);
|
|
635
971
|
col._resolvedDragable = resolveOneCellRendererParams(col.dragable, params);
|
|
972
|
+
col._resolvedFilterable = resolveOneCellRendererParams(col.filterable, params);
|
|
636
973
|
col._resolvedSuppressHeaderMenuButton = resolveOneCellRendererParams(col.suppressHeaderMenuButton, params);
|
|
637
974
|
return col;
|
|
638
975
|
}
|
|
@@ -684,76 +1021,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
684
1021
|
type: Injectable
|
|
685
1022
|
}] });
|
|
686
1023
|
|
|
687
|
-
class CellFieldValueEmptyPipe {
|
|
688
|
-
transform(data, field) {
|
|
689
|
-
// `field` is optional on OneColumnDef — an action-only column legitimately
|
|
690
|
-
// has none. This used to call `field.split('.')` unguarded and throw a
|
|
691
|
-
// TypeError while the grid was building cell state, taking the whole grid
|
|
692
|
-
// down. No field means nothing to read, which is empty by definition.
|
|
693
|
-
if (!data || !field) {
|
|
694
|
-
return true;
|
|
695
|
-
}
|
|
696
|
-
const value = getFieldValue(data, field);
|
|
697
|
-
return value === null || value === undefined || value === '';
|
|
698
|
-
}
|
|
699
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
700
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, name: "cellFieldValueEmpty" }); }
|
|
701
|
-
}
|
|
702
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValueEmptyPipe, decorators: [{
|
|
703
|
-
type: Pipe,
|
|
704
|
-
args: [{
|
|
705
|
-
standalone: false,
|
|
706
|
-
name: 'cellFieldValueEmpty'
|
|
707
|
-
}]
|
|
708
|
-
}] });
|
|
709
|
-
|
|
710
|
-
class CellValueGetterPipe {
|
|
711
|
-
transform(data, node, column, context) {
|
|
712
|
-
return resolveOneValueGetterParams(column.valueGetter, {
|
|
713
|
-
data,
|
|
714
|
-
rowIndex: node.rowIndex,
|
|
715
|
-
node: node,
|
|
716
|
-
context: context
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellValueGetterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
720
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellValueGetterPipe, name: "cellValueGetter" }); }
|
|
721
|
-
}
|
|
722
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellValueGetterPipe, decorators: [{
|
|
723
|
-
type: Pipe,
|
|
724
|
-
args: [{
|
|
725
|
-
standalone: false,
|
|
726
|
-
name: 'cellValueGetter'
|
|
727
|
-
}]
|
|
728
|
-
}] });
|
|
729
|
-
|
|
730
|
-
class CellFieldValuePipe {
|
|
731
|
-
transform(data, field) {
|
|
732
|
-
const value = getFieldValue(data, field);
|
|
733
|
-
if (value === null || value === undefined) {
|
|
734
|
-
return null;
|
|
735
|
-
}
|
|
736
|
-
if (typeof value === 'string' && value.trim() === '') {
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
return value;
|
|
740
|
-
}
|
|
741
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
742
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValuePipe, name: "cellFieldValue" }); }
|
|
743
|
-
}
|
|
744
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldValuePipe, decorators: [{
|
|
745
|
-
type: Pipe,
|
|
746
|
-
args: [{
|
|
747
|
-
standalone: false,
|
|
748
|
-
name: 'cellFieldValue'
|
|
749
|
-
}]
|
|
750
|
-
}] });
|
|
751
|
-
|
|
752
1024
|
class OneGridCellService {
|
|
753
1025
|
constructor() {
|
|
754
|
-
this._cellFieldValueEmpty = new CellFieldValueEmptyPipe();
|
|
755
|
-
this._cellValueGetter = new CellValueGetterPipe();
|
|
756
|
-
this._cellFieldValue = new CellFieldValuePipe();
|
|
757
1026
|
this._columnDefs = [];
|
|
758
1027
|
this._api = null;
|
|
759
1028
|
// ---------------------------------------------------------------------------
|
|
@@ -848,8 +1117,8 @@ class OneGridCellService {
|
|
|
848
1117
|
*/
|
|
849
1118
|
resolveValue(node, column) {
|
|
850
1119
|
return column?.valueGetter
|
|
851
|
-
?
|
|
852
|
-
:
|
|
1120
|
+
? resolveCellValueGetter(node.data, node, column, this._context)
|
|
1121
|
+
: readCellValue(node.data, column.field);
|
|
853
1122
|
}
|
|
854
1123
|
/**
|
|
855
1124
|
* Apply the column's `valueFormatter`.
|
|
@@ -907,7 +1176,7 @@ class OneGridCellService {
|
|
|
907
1176
|
if (!field) {
|
|
908
1177
|
return { type: 'none', value: '' };
|
|
909
1178
|
}
|
|
910
|
-
const isEmpty =
|
|
1179
|
+
const isEmpty = isCellEmpty(node.data, field);
|
|
911
1180
|
const isGroup = node.group || node.leafGroup;
|
|
912
1181
|
const isAutoGroup = column?.autoGroupField;
|
|
913
1182
|
if (column && column.cellRenderer && !isGroup) {
|
|
@@ -919,7 +1188,7 @@ class OneGridCellService {
|
|
|
919
1188
|
}
|
|
920
1189
|
// 1. Cell Group Placeholder
|
|
921
1190
|
if (isEmpty && isGroup && isAutoGroup) {
|
|
922
|
-
return { type: 'placeholder', value: this.resolvePlaceholder(node, column) ??
|
|
1191
|
+
return { type: 'placeholder', value: this.resolvePlaceholder(node, column) ?? resolveCellValueGetter(node.data, node, column, this._context) ?? '' };
|
|
923
1192
|
}
|
|
924
1193
|
// 2. Cell Group Text Value
|
|
925
1194
|
if (isGroup && isAutoGroup) {
|
|
@@ -934,7 +1203,7 @@ class OneGridCellService {
|
|
|
934
1203
|
// indented by level. Excluding it dropped every leaf through to case 5 and
|
|
935
1204
|
// rendered the group column blank for the whole bottom row of a tree.
|
|
936
1205
|
if (isEmpty && !isGroup) {
|
|
937
|
-
const valueGetter =
|
|
1206
|
+
const valueGetter = resolveCellValueGetter(node.data, node, column, this._context);
|
|
938
1207
|
if (valueGetter) {
|
|
939
1208
|
return { type: 'text', value: this.formatValue(node, column, valueGetter) };
|
|
940
1209
|
}
|
|
@@ -946,7 +1215,14 @@ class OneGridCellService {
|
|
|
946
1215
|
const value = this.formatValue(node, column, this.resolveValue(node, column));
|
|
947
1216
|
return { type: 'text', value: value ?? '' };
|
|
948
1217
|
}
|
|
949
|
-
// 5.
|
|
1218
|
+
// 5. A group row outside the group column: render the value when the row
|
|
1219
|
+
// actually carries one — aggregates land in these cells, and a tree
|
|
1220
|
+
// parent's own data belongs on screen too. Blank stays blank.
|
|
1221
|
+
if (!isEmpty) {
|
|
1222
|
+
const value = this.formatValue(node, column, this.resolveValue(node, column));
|
|
1223
|
+
return { type: 'text', value: value ?? '' };
|
|
1224
|
+
}
|
|
1225
|
+
// 6. Default
|
|
950
1226
|
return { type: 'none', value: '' };
|
|
951
1227
|
}
|
|
952
1228
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -1149,6 +1425,23 @@ class OneGridApi {
|
|
|
1149
1425
|
this.treeDataChildrenField = null;
|
|
1150
1426
|
this.groupDefaultExpanded = 0;
|
|
1151
1427
|
this.masterDetail = false;
|
|
1428
|
+
// ---------------------------------------------------------------------------
|
|
1429
|
+
// Row grouping
|
|
1430
|
+
// ---------------------------------------------------------------------------
|
|
1431
|
+
this._rowGroupFields = [];
|
|
1432
|
+
this._aggColumns = [];
|
|
1433
|
+
// ---------------------------------------------------------------------------
|
|
1434
|
+
// Undo / redo of cell edits
|
|
1435
|
+
// ---------------------------------------------------------------------------
|
|
1436
|
+
this._undoLimit = 0;
|
|
1437
|
+
this._undoStack = [];
|
|
1438
|
+
this._redoStack = [];
|
|
1439
|
+
// ---------------------------------------------------------------------------
|
|
1440
|
+
// Client-side pagination
|
|
1441
|
+
// ---------------------------------------------------------------------------
|
|
1442
|
+
this._clientPagination = false;
|
|
1443
|
+
this._pageSize = 25;
|
|
1444
|
+
this._currentPage = 1;
|
|
1152
1445
|
}
|
|
1153
1446
|
/**
|
|
1154
1447
|
* Stand the grid up. Called by `OneGridComponent` from `ngOnInit`.
|
|
@@ -1171,7 +1464,13 @@ class OneGridApi {
|
|
|
1171
1464
|
this.detailRendererComponent = detailRendererComponent;
|
|
1172
1465
|
this.treeData = treeData;
|
|
1173
1466
|
this.selectionService.setSelectionMode(rowSelection);
|
|
1174
|
-
|
|
1467
|
+
// Synthetic group rows carry their own stable id — the path of group
|
|
1468
|
+
// values — which no host getRowId knows how to derive. The wrapper reads
|
|
1469
|
+
// it first and defers to the host's function (or the uuid fallback) for
|
|
1470
|
+
// real rows, so grouping keeps expansion across rebuilds even when the
|
|
1471
|
+
// host provided no id function at all.
|
|
1472
|
+
const hostGetRowId = context?.componentParent?.getRowId ?? null;
|
|
1473
|
+
this.rowService.setGetRowIdFn((params) => params?.data?.[ONE_GROUP_ID_FIELD] ?? hostGetRowId?.(params) ?? null);
|
|
1175
1474
|
this.cellService.applyColumnDefs(columnDefs, context);
|
|
1176
1475
|
this.cellService.setGridApi(this);
|
|
1177
1476
|
this.columnService.initColumnDefs(defaultColumnDef, columnDefs, groupColumnDefs, context);
|
|
@@ -1209,6 +1508,12 @@ class OneGridApi {
|
|
|
1209
1508
|
this.selectionService.deselectAll();
|
|
1210
1509
|
this.expansionService.clear();
|
|
1211
1510
|
}
|
|
1511
|
+
// Row grouping is a transform on the way in: the host keeps handing the
|
|
1512
|
+
// grid flat rows, and the tree pipeline below sees synthetic group rows
|
|
1513
|
+
// with children — the same shape tree data arrives in.
|
|
1514
|
+
if (this._rowGroupFields.length) {
|
|
1515
|
+
rowData = buildGroupedRows(rowData, this._rowGroupFields, this._aggColumns);
|
|
1516
|
+
}
|
|
1212
1517
|
const validRowIds = new Set();
|
|
1213
1518
|
this.buildRowNodes(rowData, null, 0, validRowIds);
|
|
1214
1519
|
// Re-apply the active sort. `buildRowNodes` rebuilds the row list in the
|
|
@@ -1462,7 +1767,7 @@ class OneGridApi {
|
|
|
1462
1767
|
return;
|
|
1463
1768
|
this._sortModel = sortModel;
|
|
1464
1769
|
this.applySorting();
|
|
1465
|
-
this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes });
|
|
1770
|
+
this.eventService.dispatchEvent('sortChanged', { nodes: this._rowNodes, sortModel: this._sortModel });
|
|
1466
1771
|
}
|
|
1467
1772
|
applySorting() {
|
|
1468
1773
|
if (this._sortModel.length === 0) {
|
|
@@ -1536,6 +1841,50 @@ class OneGridApi {
|
|
|
1536
1841
|
});
|
|
1537
1842
|
}
|
|
1538
1843
|
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Select or deselect one row, honouring the grid's selection mode.
|
|
1846
|
+
*
|
|
1847
|
+
* The mode rule used to live in the checkbox component, which meant the only
|
|
1848
|
+
* way to select a row was to click that checkbox — anything else reaching for
|
|
1849
|
+
* `toggleSelection` got multi-select behaviour on a single-select grid. It
|
|
1850
|
+
* lives here so every path into selection obeys the same rule.
|
|
1851
|
+
*
|
|
1852
|
+
* @returns `false` when the grid has no selection mode, so a caller can tell
|
|
1853
|
+
* a refused request from an applied one.
|
|
1854
|
+
*/
|
|
1855
|
+
toggleRowSelection(rowId, selected) {
|
|
1856
|
+
if (this._destroyed || !this._rowSelection)
|
|
1857
|
+
return false;
|
|
1858
|
+
const node = this.getRowNodeById(rowId);
|
|
1859
|
+
if (!node)
|
|
1860
|
+
return false;
|
|
1861
|
+
// Single select is "this row and no other", which means clearing first.
|
|
1862
|
+
if (this._rowSelection === 'single')
|
|
1863
|
+
this.deselectAll();
|
|
1864
|
+
this.toggleSelection(rowId, selected);
|
|
1865
|
+
node.cellStates = { ...node.cellStates };
|
|
1866
|
+
return true;
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Select every row between two, inclusive, in display order.
|
|
1870
|
+
*
|
|
1871
|
+
* What a shift-extended keyboard selection needs. Multi-select only — a
|
|
1872
|
+
* single-select grid has no meaningful range.
|
|
1873
|
+
*/
|
|
1874
|
+
selectRowRange(fromRowId, toRowId) {
|
|
1875
|
+
if (this._destroyed || this._rowSelection !== 'multiple')
|
|
1876
|
+
return false;
|
|
1877
|
+
const order = this._visibleRowNodes.length ? this._visibleRowNodes : this._rowNodes;
|
|
1878
|
+
const from = order.findIndex(node => node.rowId === fromRowId);
|
|
1879
|
+
const to = order.findIndex(node => node.rowId === toRowId);
|
|
1880
|
+
if (from === -1 || to === -1)
|
|
1881
|
+
return false;
|
|
1882
|
+
const ids = order
|
|
1883
|
+
.slice(Math.min(from, to), Math.max(from, to) + 1)
|
|
1884
|
+
.map(node => node.rowId);
|
|
1885
|
+
this.setSelectedByIds(ids, true);
|
|
1886
|
+
return true;
|
|
1887
|
+
}
|
|
1539
1888
|
toggleSelection(rowId, selected) {
|
|
1540
1889
|
const node = this.getRowNodeById(rowId);
|
|
1541
1890
|
if (node) {
|
|
@@ -1760,19 +2109,237 @@ class OneGridApi {
|
|
|
1760
2109
|
this.columnService.setColumnDefs(columnDefs);
|
|
1761
2110
|
const columnState = this.columnService.getColumnState();
|
|
1762
2111
|
this.cellService.applyColumnDefs(columnState.columns, this._context);
|
|
2112
|
+
// Cell state is derived from the columns, so replacing them wholesale
|
|
2113
|
+
// stales every node built before this ran. Tree and grouping mode hit
|
|
2114
|
+
// this on every load: the group column registers from a deferred
|
|
2115
|
+
// setColumnDefs, after the host has typically already loaded rows —
|
|
2116
|
+
// hosts used to have to defer setRowData themselves to dodge it.
|
|
2117
|
+
this.regenerateCellStates();
|
|
1763
2118
|
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
1764
2119
|
}
|
|
1765
2120
|
updateColumn(id, changes) {
|
|
2121
|
+
const before = this.visibleColumnFields();
|
|
1766
2122
|
this.columnService.updateColumnDef(id, changes);
|
|
1767
2123
|
const columnState = this.columnService.getColumnState();
|
|
1768
2124
|
this.cellService.applyColumnDefs(columnState.columns, this._context);
|
|
2125
|
+
// Only when the set of visible columns changed (a hide, a show): a cell
|
|
2126
|
+
// state can appear or disappear then. Width patches — this method runs
|
|
2127
|
+
// per mousemove during a resize — skip the O(rows × columns) pass.
|
|
2128
|
+
if (this.visibleColumnFields() !== before)
|
|
2129
|
+
this.regenerateCellStates();
|
|
1769
2130
|
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
1770
2131
|
}
|
|
2132
|
+
/** The visible fields as one comparable key. */
|
|
2133
|
+
visibleColumnFields() {
|
|
2134
|
+
return this.columnService.getColumnState().columns?.map(column => column.field).join('|') ?? '';
|
|
2135
|
+
}
|
|
2136
|
+
/** Recompute every node's cell states against the current columns. */
|
|
2137
|
+
regenerateCellStates() {
|
|
2138
|
+
const refresh = (node) => node.setCellStates(this.cellService.generateCellStates(node));
|
|
2139
|
+
this._rowNodes.forEach(refresh);
|
|
2140
|
+
this._pinnedRowNodes.top.forEach(refresh);
|
|
2141
|
+
this._pinnedRowNodes.bottom.forEach(refresh);
|
|
2142
|
+
}
|
|
1771
2143
|
reorderColumnDefs(fromIndex, toIndex) {
|
|
1772
2144
|
this.columnService.reorderColumnDefs(fromIndex, toIndex);
|
|
1773
2145
|
const columnState = this.columnService.getColumnState();
|
|
1774
2146
|
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
1775
2147
|
}
|
|
2148
|
+
/** The fields the rows are grouped by, in nesting order. Empty when off. */
|
|
2149
|
+
get rowGroupFields() {
|
|
2150
|
+
return this._rowGroupFields;
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Turn row grouping on. Called by `OneGridComponent` when column
|
|
2154
|
+
* definitions declare `rowGroup`; the grid then behaves as a tree over
|
|
2155
|
+
* synthetic group rows, and `setRowData` / `updateRowData` keep taking the
|
|
2156
|
+
* flat rows they always took.
|
|
2157
|
+
*/
|
|
2158
|
+
setRowGrouping(groupFields, aggregations = []) {
|
|
2159
|
+
this._rowGroupFields = groupFields ?? [];
|
|
2160
|
+
this._aggColumns = aggregations ?? [];
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Start recording committed cell edits for undo/redo. Called by
|
|
2164
|
+
* `OneGridComponent` from `[undoRedoCellEditing]`; the stack keeps the last
|
|
2165
|
+
* `limit` edits. Programmatic `setDataValue` writes are not recorded — only
|
|
2166
|
+
* commits, the ones that raise `cellValueChanged` without a source.
|
|
2167
|
+
*/
|
|
2168
|
+
enableUndoRedoCellEditing(limit = 10) {
|
|
2169
|
+
if (this._undoLimit > 0)
|
|
2170
|
+
return;
|
|
2171
|
+
this._undoLimit = Math.max(Math.floor(limit), 1);
|
|
2172
|
+
this.eventService.addEventListener('cellValueChanged', (event) => {
|
|
2173
|
+
if (event.source)
|
|
2174
|
+
return;
|
|
2175
|
+
this._undoStack.push({ rowId: event.node.rowId, field: event.field, oldValue: event.oldValue, newValue: event.newValue });
|
|
2176
|
+
if (this._undoStack.length > this._undoLimit)
|
|
2177
|
+
this._undoStack.shift();
|
|
2178
|
+
// A new edit forks history; what was undone can no longer be redone.
|
|
2179
|
+
this._redoStack = [];
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
/** Revert the most recent committed edit. Returns whether one was undone. */
|
|
2183
|
+
undoCellEditing() {
|
|
2184
|
+
return this.replayEdit(this._undoStack, this._redoStack, 'undo');
|
|
2185
|
+
}
|
|
2186
|
+
/** Re-apply the most recently undone edit. Returns whether one was redone. */
|
|
2187
|
+
redoCellEditing() {
|
|
2188
|
+
return this.replayEdit(this._redoStack, this._undoStack, 'redo');
|
|
2189
|
+
}
|
|
2190
|
+
getCurrentUndoSize() {
|
|
2191
|
+
return this._undoStack.length;
|
|
2192
|
+
}
|
|
2193
|
+
getCurrentRedoSize() {
|
|
2194
|
+
return this._redoStack.length;
|
|
2195
|
+
}
|
|
2196
|
+
replayEdit(from, to, source) {
|
|
2197
|
+
if (this._destroyed)
|
|
2198
|
+
return false;
|
|
2199
|
+
const edit = from.pop();
|
|
2200
|
+
if (!edit)
|
|
2201
|
+
return false;
|
|
2202
|
+
const node = this.getRowNodeById(edit.rowId);
|
|
2203
|
+
// The row can be gone — a setRowData replaced the data under the stack.
|
|
2204
|
+
// The edit is dropped rather than replayed onto nothing.
|
|
2205
|
+
if (!node)
|
|
2206
|
+
return false;
|
|
2207
|
+
const value = source === 'undo' ? edit.oldValue : edit.newValue;
|
|
2208
|
+
const previous = source === 'undo' ? edit.newValue : edit.oldValue;
|
|
2209
|
+
node.data[edit.field] = value;
|
|
2210
|
+
this.updateCellStates(node);
|
|
2211
|
+
to.push(edit);
|
|
2212
|
+
const column = this.columnService.getColumnState().columns?.find(col => col.field === edit.field) ?? null;
|
|
2213
|
+
this.eventService.dispatchEvent('cellValueChanged', {
|
|
2214
|
+
node,
|
|
2215
|
+
data: node.data,
|
|
2216
|
+
column: column,
|
|
2217
|
+
field: edit.field,
|
|
2218
|
+
oldValue: previous,
|
|
2219
|
+
newValue: value,
|
|
2220
|
+
source,
|
|
2221
|
+
});
|
|
2222
|
+
return true;
|
|
2223
|
+
}
|
|
2224
|
+
/** The paging state, when the grid pages its own rows. */
|
|
2225
|
+
get paginationState() {
|
|
2226
|
+
return { enabled: this._clientPagination, pageSize: this._pageSize, currentPage: this._currentPage };
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Turn grid-owned paging on or off. Called by `OneGridComponent` from its
|
|
2230
|
+
* `clientSidePagination` input; the display slice is what changes — the
|
|
2231
|
+
* full dataset stays behind the API (selection, filters and sorting keep
|
|
2232
|
+
* operating on all of it).
|
|
2233
|
+
*/
|
|
2234
|
+
setClientSidePagination(enabled, pageSize) {
|
|
2235
|
+
this._clientPagination = enabled;
|
|
2236
|
+
if (pageSize && pageSize > 0)
|
|
2237
|
+
this._pageSize = pageSize;
|
|
2238
|
+
this._currentPage = 1;
|
|
2239
|
+
}
|
|
2240
|
+
/** Change the page size and return to the first page. */
|
|
2241
|
+
setPageSize(pageSize) {
|
|
2242
|
+
if (this._destroyed || !(pageSize > 0))
|
|
2243
|
+
return;
|
|
2244
|
+
this._pageSize = Math.floor(pageSize);
|
|
2245
|
+
this._currentPage = 1;
|
|
2246
|
+
this.applyFilters();
|
|
2247
|
+
}
|
|
2248
|
+
/** Move to a page (1-based). Clamped to at least 1; the top end clamps when the slice is taken. */
|
|
2249
|
+
setCurrentPage(page) {
|
|
2250
|
+
if (this._destroyed)
|
|
2251
|
+
return;
|
|
2252
|
+
const target = Math.max(Math.floor(page), 1);
|
|
2253
|
+
if (target === this._currentPage)
|
|
2254
|
+
return;
|
|
2255
|
+
this._currentPage = target;
|
|
2256
|
+
this.applyFilters();
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* The slice of rows the current page shows — a passthrough while paging is
|
|
2260
|
+
* off. The body routes its render list through here, after reporting the
|
|
2261
|
+
* full count, so `rowCountChanged` keeps meaning "rows after filtering"
|
|
2262
|
+
* whether or not the grid pages.
|
|
2263
|
+
*/
|
|
2264
|
+
pageNodes(nodes) {
|
|
2265
|
+
if (!this._clientPagination)
|
|
2266
|
+
return (nodes ?? []);
|
|
2267
|
+
const lastPage = Math.max(Math.ceil((nodes?.length ?? 0) / this._pageSize), 1);
|
|
2268
|
+
// A shrinking dataset (a filter, a delete) can strand the page past the
|
|
2269
|
+
// end; clamping here shows the last page instead of an empty one.
|
|
2270
|
+
if (this._currentPage > lastPage)
|
|
2271
|
+
this._currentPage = lastPage;
|
|
2272
|
+
const start = (this._currentPage - 1) * this._pageSize;
|
|
2273
|
+
return (nodes ?? []).slice(start, start + this._pageSize);
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Size the flex columns into the given width.
|
|
2277
|
+
*
|
|
2278
|
+
* Called by the grid body with its own viewport width — on first render, on
|
|
2279
|
+
* container resize and after column changes — so `columnDef.flex` works
|
|
2280
|
+
* without the host doing anything. Also callable directly with any width.
|
|
2281
|
+
* A no-op when no visible column declares `flex` or nothing would change.
|
|
2282
|
+
*/
|
|
2283
|
+
applyFlexWidths(availableWidth) {
|
|
2284
|
+
if (this._destroyed)
|
|
2285
|
+
return;
|
|
2286
|
+
if (!this.columnService.applyFlexWidths(availableWidth))
|
|
2287
|
+
return;
|
|
2288
|
+
const columnState = this.columnService.getColumnState();
|
|
2289
|
+
this.cellService.applyColumnDefs(columnState.columns, this._context);
|
|
2290
|
+
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* The current grid as CSV: visible columns in display order, visible rows
|
|
2294
|
+
* in current sort order, values through the same getter/formatter pipeline
|
|
2295
|
+
* the cells render with. Pinned rows and detail grids are not data and are
|
|
2296
|
+
* not included.
|
|
2297
|
+
*/
|
|
2298
|
+
getDataAsCsv(options = {}) {
|
|
2299
|
+
const nodes = options.onlySelected ? this.getSelectedRowNodes() : this._visibleRowNodes;
|
|
2300
|
+
return buildCsv(nodes, this.columnService.getColumnState().columns, this._context, options);
|
|
2301
|
+
}
|
|
2302
|
+
/** Build the CSV and hand it to the browser as a download. */
|
|
2303
|
+
exportDataAsCsv(options = {}) {
|
|
2304
|
+
if (typeof document === 'undefined')
|
|
2305
|
+
return;
|
|
2306
|
+
const blob = new Blob([this.getDataAsCsv(options)], { type: 'text/csv;charset=utf-8;' });
|
|
2307
|
+
const link = document.createElement('a');
|
|
2308
|
+
link.href = URL.createObjectURL(blob);
|
|
2309
|
+
link.download = options.fileName ?? 'export.csv';
|
|
2310
|
+
link.click();
|
|
2311
|
+
URL.revokeObjectURL(link.href);
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Show or hide one column. A hidden column keeps its order, width and pin,
|
|
2315
|
+
* so showing it again restores it exactly.
|
|
2316
|
+
*/
|
|
2317
|
+
setColumnVisible(id, visible) {
|
|
2318
|
+
this.updateColumn(id, { hide: !visible });
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Persistable layout state: one JSON-safe entry per column — hidden ones
|
|
2322
|
+
* included — in display order. Feed it back through `applyColumnState`.
|
|
2323
|
+
*/
|
|
2324
|
+
getColumnStateSnapshot() {
|
|
2325
|
+
return this.columnService.getColumnStateSnapshot();
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Restore a snapshot from `getColumnStateSnapshot`: order, width, pin and
|
|
2329
|
+
* visibility. Ids the grid no longer has are skipped, so a stale snapshot
|
|
2330
|
+
* degrades to a partial restore rather than an error.
|
|
2331
|
+
*/
|
|
2332
|
+
applyColumnState(entries) {
|
|
2333
|
+
if (this._destroyed)
|
|
2334
|
+
return;
|
|
2335
|
+
this.columnService.applyColumnState(entries);
|
|
2336
|
+
const columnState = this.columnService.getColumnState();
|
|
2337
|
+
this.cellService.applyColumnDefs(columnState.columns, this._context);
|
|
2338
|
+
// A restore can show columns that were hidden when the rows were built,
|
|
2339
|
+
// and their cells have no state yet.
|
|
2340
|
+
this.regenerateCellStates();
|
|
2341
|
+
this.eventService.dispatchEvent('columnChanged', columnState);
|
|
2342
|
+
}
|
|
1776
2343
|
/**
|
|
1777
2344
|
* Update checkbox selection features
|
|
1778
2345
|
*/
|
|
@@ -2359,47 +2926,84 @@ function resolveRowHeight(params, getRowHeight, rowHeight = 26) {
|
|
|
2359
2926
|
return rowHeight;
|
|
2360
2927
|
}
|
|
2361
2928
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2929
|
+
/**
|
|
2930
|
+
* The Lucide icons the grid draws, and only those.
|
|
2931
|
+
*
|
|
2932
|
+
* Every component that renders a `<lucide-icon>` picks this set itself, so the
|
|
2933
|
+
* icons travel with the component. That matters now the components are
|
|
2934
|
+
* standalone: a consumer importing `OneGridComponent` directly never touches
|
|
2935
|
+
* `OneGridModule`, and previously got a runtime error — "The 'chevron-first'
|
|
2936
|
+
* icon has not been provided by any available icon providers" — the moment the
|
|
2937
|
+
* pagination bar rendered.
|
|
2938
|
+
*
|
|
2939
|
+
* Eight icons rather than `LucideAngularModule.pick(icons)`, which registered
|
|
2940
|
+
* the entire Lucide set. Nothing else in the grid uses one, and the rest were
|
|
2941
|
+
* dead weight in every consumer's bundle.
|
|
2942
|
+
*/
|
|
2943
|
+
const ONE_GRID_ICONS = {
|
|
2944
|
+
ArrowDownUp,
|
|
2945
|
+
ArrowDownZA,
|
|
2946
|
+
ArrowUpAZ,
|
|
2947
|
+
ChevronFirst,
|
|
2948
|
+
ChevronLast,
|
|
2949
|
+
ChevronLeft,
|
|
2950
|
+
ChevronRight,
|
|
2951
|
+
GripVertical,
|
|
2952
|
+
};
|
|
2953
|
+
/**
|
|
2954
|
+
* Providers that register {@link ONE_GRID_ICONS} for one standalone component.
|
|
2955
|
+
*
|
|
2956
|
+
* `LucideAngularModule.pick()` cannot be used here: it returns a
|
|
2957
|
+
* `ModuleWithProviders`, and a standalone component's `imports` only accepts
|
|
2958
|
+
* components, directives, pipes and NgModule classes. This is the same
|
|
2959
|
+
* registration `pick` performs, expressed as providers so it can sit on the
|
|
2960
|
+
* component instead.
|
|
2961
|
+
*/
|
|
2962
|
+
const ONE_GRID_ICON_PROVIDERS = [
|
|
2963
|
+
{ provide: LUCIDE_ICONS, multi: true, useValue: new LucideIconProvider(ONE_GRID_ICONS) },
|
|
2964
|
+
];
|
|
2376
2965
|
|
|
2377
2966
|
class HeaderCheckboxCheckedPipe {
|
|
2378
2967
|
transform(rowNodes, selectionMode, state) {
|
|
2379
2968
|
return rowNodes?.length > 0 && selectionMode === 'multiple' && state === 'checked';
|
|
2380
2969
|
}
|
|
2381
2970
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2382
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, name: "headerCheckboxChecked" }); }
|
|
2971
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, isStandalone: true, name: "headerCheckboxChecked" }); }
|
|
2383
2972
|
}
|
|
2384
2973
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxCheckedPipe, decorators: [{
|
|
2385
2974
|
type: Pipe,
|
|
2386
2975
|
args: [{
|
|
2387
|
-
standalone:
|
|
2976
|
+
standalone: true,
|
|
2388
2977
|
name: 'headerCheckboxChecked'
|
|
2389
2978
|
}]
|
|
2390
2979
|
}] });
|
|
2391
2980
|
|
|
2981
|
+
class HeaderCheckboxDisabledPipe {
|
|
2982
|
+
transform(rowNodes, selectionMode, allowedEdit) {
|
|
2983
|
+
return rowNodes?.length === 0 || selectionMode !== 'multiple' || !allowedEdit;
|
|
2984
|
+
}
|
|
2985
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2986
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, isStandalone: true, name: "headerCheckboxDisabled" }); }
|
|
2987
|
+
}
|
|
2988
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxDisabledPipe, decorators: [{
|
|
2989
|
+
type: Pipe,
|
|
2990
|
+
args: [{
|
|
2991
|
+
standalone: true,
|
|
2992
|
+
name: 'headerCheckboxDisabled'
|
|
2993
|
+
}]
|
|
2994
|
+
}] });
|
|
2995
|
+
|
|
2392
2996
|
class HeaderCheckboxIndeterminatePipe {
|
|
2393
2997
|
transform(rowNodes, selectionMode, state) {
|
|
2394
2998
|
return rowNodes?.length > 0 && selectionMode === 'multiple' && state === 'indeterminate';
|
|
2395
2999
|
}
|
|
2396
3000
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2397
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, name: "headerCheckboxIndeterminate" }); }
|
|
3001
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, isStandalone: true, name: "headerCheckboxIndeterminate" }); }
|
|
2398
3002
|
}
|
|
2399
3003
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeaderCheckboxIndeterminatePipe, decorators: [{
|
|
2400
3004
|
type: Pipe,
|
|
2401
3005
|
args: [{
|
|
2402
|
-
standalone:
|
|
3006
|
+
standalone: true,
|
|
2403
3007
|
name: 'headerCheckboxIndeterminate'
|
|
2404
3008
|
}]
|
|
2405
3009
|
}] });
|
|
@@ -2457,36 +3061,200 @@ class OneGridCheckboxHeaderComponent {
|
|
|
2457
3061
|
}
|
|
2458
3062
|
}
|
|
2459
3063
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxHeaderComponent, deps: [{ token: OneGridEventService }, { token: OneGridSelectionService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2460
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: { api: "api", allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>", styles: [""], dependencies: [{ kind: "pipe", type:
|
|
3064
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxHeaderComponent, isStandalone: true, selector: "one-grid-checkbox-header", inputs: { api: "api", allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>", styles: [""], dependencies: [{ kind: "pipe", type: HeaderCheckboxCheckedPipe, name: "headerCheckboxChecked" }, { kind: "pipe", type: HeaderCheckboxDisabledPipe, name: "headerCheckboxDisabled" }, { kind: "pipe", type: HeaderCheckboxIndeterminatePipe, name: "headerCheckboxIndeterminate" }] }); }
|
|
2461
3065
|
}
|
|
2462
3066
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxHeaderComponent, decorators: [{
|
|
2463
3067
|
type: Component,
|
|
2464
|
-
args: [{ standalone:
|
|
3068
|
+
args: [{ standalone: true, imports: [HeaderCheckboxCheckedPipe, HeaderCheckboxDisabledPipe, HeaderCheckboxIndeterminatePipe], selector: 'one-grid-checkbox-header', template: "<div class=\"one-grid-checkbox\">\n <input\n [id]=\"api.id\"\n [type]=\"'checkbox'\"\n [class.disabled]=\"rowNodes | headerCheckboxDisabled:selectionMode:allowedEdit\"\n [checked]=\"rowNodes | headerCheckboxChecked:selectionMode:state\"\n [indeterminate]=\"rowNodes | headerCheckboxIndeterminate:selectionMode:state\"\n (change)=\"toggleSelectionAll($event)\" />\n</div>" }]
|
|
2465
3069
|
}], ctorParameters: () => [{ type: OneGridEventService }, { type: OneGridSelectionService }], propDecorators: { api: [{
|
|
2466
3070
|
type: Input
|
|
2467
3071
|
}], allowedEdit: [{
|
|
2468
3072
|
type: Input
|
|
2469
3073
|
}] } });
|
|
2470
3074
|
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
3075
|
+
/**
|
|
3076
|
+
* The grid's default column menu, opened by the header's ⋮ button when the
|
|
3077
|
+
* grid sets `[headerMenu]="true"`.
|
|
3078
|
+
*
|
|
3079
|
+
* Entries follow the column: sorting only on a sortable column and only the
|
|
3080
|
+
* transitions that make sense from the current state, pinning without the
|
|
3081
|
+
* side it is already on, hiding never for the last visible column. The menu
|
|
3082
|
+
* only reports the chosen action — the header executes it against the API,
|
|
3083
|
+
* for the same reason the cell option menu does not mutate rows itself.
|
|
3084
|
+
*
|
|
3085
|
+
* Keyboard behaviour is the menu pattern the cell option menu established:
|
|
3086
|
+
* focus moves in on open, arrows rove, Escape and outside clicks close.
|
|
3087
|
+
*/
|
|
3088
|
+
class OneGridHeaderMenuComponent {
|
|
3089
|
+
get menuLabel() {
|
|
3090
|
+
return `Column menu for ${this.column?.headerName ?? this.column?.field ?? 'this column'}`;
|
|
2478
3091
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
3092
|
+
constructor(elementRef) {
|
|
3093
|
+
this.elementRef = elementRef;
|
|
3094
|
+
this.column = null;
|
|
3095
|
+
/** The column's current sort, so the menu offers only the other states. */
|
|
3096
|
+
this.sortState = null;
|
|
3097
|
+
/** False when this is the last visible column, which must stay visible. */
|
|
3098
|
+
this.canHide = true;
|
|
3099
|
+
this.menuAction = new EventEmitter();
|
|
3100
|
+
this.menuClose = new EventEmitter();
|
|
3101
|
+
this.entries = [];
|
|
3102
|
+
this.activeIndex = 0;
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Built once on open rather than in a getter: the entries feed the roving
|
|
3106
|
+
* tabindex, and a list that re-derives mid-interaction would move focus.
|
|
3107
|
+
*/
|
|
3108
|
+
ngOnInit() {
|
|
3109
|
+
const entries = [];
|
|
3110
|
+
const column = this.column;
|
|
3111
|
+
if (column?._resolvedSortable && column.field) {
|
|
3112
|
+
if (this.sortState !== 'asc')
|
|
3113
|
+
entries.push({ action: 'sortAsc', label: 'Sort ascending' });
|
|
3114
|
+
if (this.sortState !== 'desc')
|
|
3115
|
+
entries.push({ action: 'sortDesc', label: 'Sort descending' });
|
|
3116
|
+
if (this.sortState)
|
|
3117
|
+
entries.push({ action: 'clearSort', label: 'Clear sort' });
|
|
3118
|
+
}
|
|
3119
|
+
if (column?.pinned !== 'left')
|
|
3120
|
+
entries.push({ action: 'pinLeft', label: 'Pin left' });
|
|
3121
|
+
if (column?.pinned !== 'right')
|
|
3122
|
+
entries.push({ action: 'pinRight', label: 'Pin right' });
|
|
3123
|
+
if (column?.pinned)
|
|
3124
|
+
entries.push({ action: 'unpin', label: 'Unpin' });
|
|
3125
|
+
if (this.canHide)
|
|
3126
|
+
entries.push({ action: 'hide', label: 'Hide column' });
|
|
3127
|
+
this.entries = entries;
|
|
3128
|
+
}
|
|
3129
|
+
ngAfterViewInit() {
|
|
3130
|
+
this.focusItem(0);
|
|
3131
|
+
}
|
|
3132
|
+
onEntrySelected(entry) {
|
|
3133
|
+
this.menuAction.emit(entry.action);
|
|
3134
|
+
this.menuClose.emit();
|
|
3135
|
+
}
|
|
3136
|
+
onItemKeydown(event, index, entry) {
|
|
3137
|
+
switch (event.key) {
|
|
3138
|
+
case 'ArrowDown':
|
|
3139
|
+
event.preventDefault();
|
|
3140
|
+
this.focusItem((index + 1) % this.entries.length);
|
|
3141
|
+
return;
|
|
3142
|
+
case 'ArrowUp':
|
|
3143
|
+
event.preventDefault();
|
|
3144
|
+
this.focusItem((index - 1 + this.entries.length) % this.entries.length);
|
|
3145
|
+
return;
|
|
3146
|
+
case 'Home':
|
|
3147
|
+
event.preventDefault();
|
|
3148
|
+
this.focusItem(0);
|
|
3149
|
+
return;
|
|
3150
|
+
case 'End':
|
|
3151
|
+
event.preventDefault();
|
|
3152
|
+
this.focusItem(this.entries.length - 1);
|
|
3153
|
+
return;
|
|
3154
|
+
case 'Enter':
|
|
3155
|
+
case ' ':
|
|
3156
|
+
event.preventDefault();
|
|
3157
|
+
this.onEntrySelected(entry);
|
|
3158
|
+
return;
|
|
3159
|
+
default:
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
focusItem(index) {
|
|
3164
|
+
if (!this.entries.length)
|
|
3165
|
+
return;
|
|
3166
|
+
this.activeIndex = index;
|
|
3167
|
+
this.items?.get(index)?.nativeElement?.focus();
|
|
3168
|
+
}
|
|
3169
|
+
onDocumentClick(event) {
|
|
3170
|
+
if (!this.elementRef.nativeElement.contains(event.target)) {
|
|
3171
|
+
this.menuClose.emit();
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
onEscape() {
|
|
3175
|
+
this.menuClose.emit();
|
|
3176
|
+
}
|
|
3177
|
+
onScroll() {
|
|
3178
|
+
this.menuClose.emit();
|
|
3179
|
+
}
|
|
3180
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3181
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderMenuComponent, isStandalone: true, selector: "one-grid-header-menu", inputs: { column: "column", sortState: "sortState", canHide: "canHide" }, outputs: { menuAction: "menuAction", menuClose: "menuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<div class=\"one-header-menu\" role=\"menu\" [attr.aria-label]=\"menuLabel\">\n @for (entry of entries; let index = $index; track entry.action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"one-header-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onEntrySelected(entry)\"\n (keydown)=\"onItemKeydown($event, index, entry)\">\n <span>{{ entry.label }}</span>\n </button>\n }\n</div>\n" }); }
|
|
3182
|
+
}
|
|
3183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderMenuComponent, decorators: [{
|
|
3184
|
+
type: Component,
|
|
3185
|
+
args: [{ selector: 'one-grid-header-menu', standalone: true, template: "<div class=\"one-header-menu\" role=\"menu\" [attr.aria-label]=\"menuLabel\">\n @for (entry of entries; let index = $index; track entry.action) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"one-header-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onEntrySelected(entry)\"\n (keydown)=\"onItemKeydown($event, index, entry)\">\n <span>{{ entry.label }}</span>\n </button>\n }\n</div>\n" }]
|
|
3186
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
|
|
3187
|
+
type: Input
|
|
3188
|
+
}], sortState: [{
|
|
3189
|
+
type: Input
|
|
3190
|
+
}], canHide: [{
|
|
3191
|
+
type: Input
|
|
3192
|
+
}], menuAction: [{
|
|
3193
|
+
type: Output
|
|
3194
|
+
}], menuClose: [{
|
|
3195
|
+
type: Output
|
|
3196
|
+
}], items: [{
|
|
3197
|
+
type: ViewChildren,
|
|
3198
|
+
args: ['item']
|
|
3199
|
+
}], onDocumentClick: [{
|
|
3200
|
+
type: HostListener,
|
|
3201
|
+
args: ['document:click', ['$event']]
|
|
3202
|
+
}], onEscape: [{
|
|
3203
|
+
type: HostListener,
|
|
3204
|
+
args: ['document:keydown.escape']
|
|
3205
|
+
}], onScroll: [{
|
|
3206
|
+
type: HostListener,
|
|
3207
|
+
args: ['document:scroll']
|
|
3208
|
+
}] } });
|
|
3209
|
+
|
|
3210
|
+
class ColumnSortStatePipe {
|
|
3211
|
+
// `readonly`, because this is fed straight from `api.sortModel`, which is
|
|
3212
|
+
// now a readonly view rather than the mutable array it used to hand out.
|
|
3213
|
+
transform(field, sortModel) {
|
|
3214
|
+
if (!sortModel || !field)
|
|
3215
|
+
return null;
|
|
3216
|
+
return sortModel.find(s => s.colId === field)?.sort ?? null;
|
|
3217
|
+
}
|
|
3218
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3219
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, isStandalone: true, name: "columnSortState" }); }
|
|
3220
|
+
}
|
|
3221
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ColumnSortStatePipe, decorators: [{
|
|
3222
|
+
type: Pipe,
|
|
2484
3223
|
args: [{
|
|
2485
|
-
standalone:
|
|
3224
|
+
standalone: true,
|
|
2486
3225
|
name: 'columnSortState'
|
|
2487
3226
|
}]
|
|
2488
3227
|
}] });
|
|
2489
3228
|
|
|
3229
|
+
/**
|
|
3230
|
+
* Maps the grid's sort state onto the values `aria-sort` accepts.
|
|
3231
|
+
*
|
|
3232
|
+
* The attribute takes `ascending` / `descending` / `none`, not the `asc` /
|
|
3233
|
+
* `desc` the sort model uses. `none` is only correct on a column that *can* be
|
|
3234
|
+
* sorted — on one that cannot, the attribute is omitted entirely, because
|
|
3235
|
+
* "not sorted" and "not sortable" are different statements.
|
|
3236
|
+
*/
|
|
3237
|
+
class AriaSortPipe {
|
|
3238
|
+
transform(sort, sortable) {
|
|
3239
|
+
if (!sortable)
|
|
3240
|
+
return null;
|
|
3241
|
+
if (sort === 'asc')
|
|
3242
|
+
return 'ascending';
|
|
3243
|
+
if (sort === 'desc')
|
|
3244
|
+
return 'descending';
|
|
3245
|
+
return 'none';
|
|
3246
|
+
}
|
|
3247
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3248
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, isStandalone: true, name: "ariaSort" }); }
|
|
3249
|
+
}
|
|
3250
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AriaSortPipe, decorators: [{
|
|
3251
|
+
type: Pipe,
|
|
3252
|
+
args: [{
|
|
3253
|
+
standalone: true,
|
|
3254
|
+
name: 'ariaSort',
|
|
3255
|
+
}]
|
|
3256
|
+
}] });
|
|
3257
|
+
|
|
2490
3258
|
class OneGridHeaderComponent {
|
|
2491
3259
|
/**
|
|
2492
3260
|
* Horizontal offset of the body, mirrored here so the header tracks it.
|
|
@@ -2502,9 +3270,10 @@ class OneGridHeaderComponent {
|
|
|
2502
3270
|
get scrollLeft() {
|
|
2503
3271
|
return this._scrollLeft;
|
|
2504
3272
|
}
|
|
2505
|
-
constructor(eventService, cdr) {
|
|
3273
|
+
constructor(eventService, cdr, elementRef) {
|
|
2506
3274
|
this.eventService = eventService;
|
|
2507
3275
|
this.cdr = cdr;
|
|
3276
|
+
this.elementRef = elementRef;
|
|
2508
3277
|
this._scrollLeft = 0;
|
|
2509
3278
|
this.allowedEdit = false;
|
|
2510
3279
|
this.enableGroupColumnDefs = false;
|
|
@@ -2513,11 +3282,51 @@ class OneGridHeaderComponent {
|
|
|
2513
3282
|
this.sortModel = [];
|
|
2514
3283
|
this.subscriptions = new Subscription();
|
|
2515
3284
|
this.draggedIndex = null;
|
|
3285
|
+
/** Where the drag started, for reporting the completed move as one event. */
|
|
3286
|
+
this.dragSourceIndex = null;
|
|
2516
3287
|
// Track resize state
|
|
2517
3288
|
this.resizing = false;
|
|
2518
3289
|
this.resizeColumnIndex = null;
|
|
2519
3290
|
this.startX = 0;
|
|
2520
3291
|
this.startWidth = 0;
|
|
3292
|
+
/**
|
|
3293
|
+
* Render a filter input under each column whose `filterable` resolves true.
|
|
3294
|
+
*
|
|
3295
|
+
* The inputs build a contains-filter per column and hand it to
|
|
3296
|
+
* `api.setFilterModel`, debounced. Fields without a filter input are left
|
|
3297
|
+
* alone: a programmatic filter on a non-filterable column survives typing
|
|
3298
|
+
* here, while the filterable fields belong to the row — last writer wins.
|
|
3299
|
+
*/
|
|
3300
|
+
this.filterRow = false;
|
|
3301
|
+
/** Current filter text per field. */
|
|
3302
|
+
this.filterTexts = {};
|
|
3303
|
+
this.filterInput$ = new Subject();
|
|
3304
|
+
/**
|
|
3305
|
+
* Minimum a column can be resized to, by pointer or keyboard.
|
|
3306
|
+
*
|
|
3307
|
+
* Exposed to the template for `aria-valuemin`, so what the resizer announces
|
|
3308
|
+
* and what it enforces cannot drift apart.
|
|
3309
|
+
*/
|
|
3310
|
+
this.MIN_COLUMN_WIDTH = 50;
|
|
3311
|
+
/**
|
|
3312
|
+
* The header's ⋮ button was a div with an empty handler — it rendered,
|
|
3313
|
+
* looked interactive, and did nothing at all. Making it keyboard-reachable
|
|
3314
|
+
* without giving it a purpose would only have added a dead tab stop per
|
|
3315
|
+
* column, so it now reports the press the same way every row action does and
|
|
3316
|
+
* the host decides what a column menu means.
|
|
3317
|
+
*/
|
|
3318
|
+
this.headerMenuClick = new EventEmitter();
|
|
3319
|
+
/**
|
|
3320
|
+
* Render the grid's default column menu behind the ⋮ button.
|
|
3321
|
+
*
|
|
3322
|
+
* Opt-in: hosts that bound `(headerMenuClick)` to open their own menu keep
|
|
3323
|
+
* exactly the behaviour they had — the intent still emits either way.
|
|
3324
|
+
*/
|
|
3325
|
+
this.headerMenu = false;
|
|
3326
|
+
/** The column whose default menu is open, or null. */
|
|
3327
|
+
this.openMenuColumn = null;
|
|
3328
|
+
/** Distance from the header's right edge to the open column's, in px. */
|
|
3329
|
+
this.menuOffsetRight = 0;
|
|
2521
3330
|
// Arrow function to preserve 'this' context
|
|
2522
3331
|
this.onResizeMove = (event) => {
|
|
2523
3332
|
if (!this.resizing || this.resizeColumnIndex === null)
|
|
@@ -2525,11 +3334,21 @@ class OneGridHeaderComponent {
|
|
|
2525
3334
|
// Calculate how far the mouse has moved
|
|
2526
3335
|
const deltaX = event.clientX - this.startX;
|
|
2527
3336
|
// Calculate new width (with minimum of 50px)
|
|
2528
|
-
const newWidth = Math.max(
|
|
2529
|
-
// Update the column width
|
|
2530
|
-
|
|
3337
|
+
const newWidth = Math.max(this.MIN_COLUMN_WIDTH, this.startWidth + deltaX);
|
|
3338
|
+
// Update the column width. A hand-resized column stops flexing, or the
|
|
3339
|
+
// next reflow would overwrite the width the user is dragging out.
|
|
3340
|
+
this.api.updateColumn(this.columnDefs[this.resizeColumnIndex].id, { width: newWidth, flex: undefined });
|
|
2531
3341
|
};
|
|
2532
3342
|
this.onResizeEnd = () => {
|
|
3343
|
+
// One event per completed gesture, not one per mousemove — and none at all
|
|
3344
|
+
// for a click on the handle that never moved.
|
|
3345
|
+
if (this.resizeColumnIndex !== null) {
|
|
3346
|
+
const column = this.columnDefs[this.resizeColumnIndex];
|
|
3347
|
+
const newWidth = column?.width ?? this.startWidth;
|
|
3348
|
+
if (column && newWidth !== this.startWidth) {
|
|
3349
|
+
this.eventService.dispatchEvent('columnResized', { columnId: column.id, newWidth });
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
2533
3352
|
// Clean up
|
|
2534
3353
|
this.resizing = false;
|
|
2535
3354
|
this.resizeColumnIndex = null;
|
|
@@ -2539,7 +3358,30 @@ class OneGridHeaderComponent {
|
|
|
2539
3358
|
document.removeEventListener('mouseup', this.onResizeEnd);
|
|
2540
3359
|
};
|
|
2541
3360
|
}
|
|
3361
|
+
/** Debounce for the filter row, in ms. */
|
|
3362
|
+
static { this.FILTER_DEBOUNCE = 200; }
|
|
3363
|
+
onFilterInput(col, event) {
|
|
3364
|
+
this.filterTexts[col.field] = event.target.value;
|
|
3365
|
+
this.filterInput$.next();
|
|
3366
|
+
}
|
|
3367
|
+
applyFilterRow() {
|
|
3368
|
+
const managed = new Set(this.columnDefs
|
|
3369
|
+
.filter(column => column._resolvedFilterable && column.field)
|
|
3370
|
+
.map(column => column.field));
|
|
3371
|
+
const preserved = this.api.getFilterModel().filter(entry => !managed.has(entry.field));
|
|
3372
|
+
const own = Object.entries(this.filterTexts)
|
|
3373
|
+
.filter(([field, text]) => managed.has(field) && text.trim() !== '')
|
|
3374
|
+
.map(([field, text]) => ({
|
|
3375
|
+
field,
|
|
3376
|
+
logicOperator: 'AND',
|
|
3377
|
+
conditions: [{ operator: 'contains', value: text.trim() }],
|
|
3378
|
+
}));
|
|
3379
|
+
this.api.setFilterModel([...preserved, ...own]);
|
|
3380
|
+
}
|
|
2542
3381
|
ngOnInit() {
|
|
3382
|
+
this.subscriptions.add(this.filterInput$
|
|
3383
|
+
.pipe(debounceTime(OneGridHeaderComponent.FILTER_DEBOUNCE))
|
|
3384
|
+
.subscribe(() => this.applyFilterRow()));
|
|
2543
3385
|
// Listen Column Changed Event
|
|
2544
3386
|
this.subscriptions.add(this.eventService.addEventListener('columnChanged', (payload) => {
|
|
2545
3387
|
const { groups, columns } = payload;
|
|
@@ -2562,7 +3404,100 @@ class OneGridHeaderComponent {
|
|
|
2562
3404
|
this.headerViewport.nativeElement.scrollLeft = this._scrollLeft;
|
|
2563
3405
|
}
|
|
2564
3406
|
}
|
|
2565
|
-
onHeaderMenuOptionClick(columnDef) {
|
|
3407
|
+
onHeaderMenuOptionClick(columnDef, event) {
|
|
3408
|
+
this.headerMenuClick.emit(columnDef);
|
|
3409
|
+
if (!this.headerMenu)
|
|
3410
|
+
return;
|
|
3411
|
+
if (this.openMenuColumn?.id === columnDef.id) {
|
|
3412
|
+
this.openMenuColumn = null;
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
// Right-aligned under the opening column. Measured because the menu
|
|
3416
|
+
// cannot render inside the column — every box in the header clips — so
|
|
3417
|
+
// it sits outside and has to be told where the column is.
|
|
3418
|
+
const columnElement = event?.target?.closest('.one-grid-header-column');
|
|
3419
|
+
const hostElement = this.elementRef.nativeElement;
|
|
3420
|
+
this.menuOffsetRight = columnElement
|
|
3421
|
+
? Math.max(hostElement.getBoundingClientRect().right - columnElement.getBoundingClientRect().right, 0)
|
|
3422
|
+
: 0;
|
|
3423
|
+
this.openMenuColumn = columnDef;
|
|
3424
|
+
}
|
|
3425
|
+
/** The open column's current sort, so the menu offers the other states. */
|
|
3426
|
+
menuSortState(col) {
|
|
3427
|
+
return this.api.sortModel.find(entry => entry.colId === col.field)?.sort ?? null;
|
|
3428
|
+
}
|
|
3429
|
+
onHeaderMenuAction(action, col) {
|
|
3430
|
+
switch (action) {
|
|
3431
|
+
case 'sortAsc':
|
|
3432
|
+
case 'sortDesc': {
|
|
3433
|
+
// Mirrors a sort-button click: a single-column model. The header's own
|
|
3434
|
+
// cycle state follows, so the sort button keeps cycling from here.
|
|
3435
|
+
this.sortModel = [{ colId: col.field, sort: action === 'sortAsc' ? 'asc' : 'desc' }];
|
|
3436
|
+
this.api.onSortChanged(this.sortModel);
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
case 'clearSort':
|
|
3440
|
+
this.sortModel = [];
|
|
3441
|
+
this.api.onSortChanged(this.sortModel);
|
|
3442
|
+
return;
|
|
3443
|
+
case 'pinLeft':
|
|
3444
|
+
this.api.updateColumn(col.id, { pinned: 'left' });
|
|
3445
|
+
return;
|
|
3446
|
+
case 'pinRight':
|
|
3447
|
+
this.api.updateColumn(col.id, { pinned: 'right' });
|
|
3448
|
+
return;
|
|
3449
|
+
case 'unpin':
|
|
3450
|
+
this.api.updateColumn(col.id, { pinned: undefined });
|
|
3451
|
+
return;
|
|
3452
|
+
case 'hide':
|
|
3453
|
+
this.api.setColumnVisible(col.id, false);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
closeHeaderMenu() {
|
|
3458
|
+
this.openMenuColumn = null;
|
|
3459
|
+
}
|
|
3460
|
+
/**
|
|
3461
|
+
* Resize from the keyboard.
|
|
3462
|
+
*
|
|
3463
|
+
* The pointer path is a drag, and WCAG 2.5.7 requires a single-pointer
|
|
3464
|
+
* alternative that is not one. Arrows step, shift steps coarser, and
|
|
3465
|
+
* Home/End go to the column's own bounds.
|
|
3466
|
+
*/
|
|
3467
|
+
onResizeKeydown(event, columnIndex) {
|
|
3468
|
+
const column = this.columnDefs[columnIndex];
|
|
3469
|
+
if (!column)
|
|
3470
|
+
return;
|
|
3471
|
+
const step = event.shiftKey ? 50 : 10;
|
|
3472
|
+
const current = column.width ?? resolveColumnWidth(column);
|
|
3473
|
+
let width;
|
|
3474
|
+
switch (event.key) {
|
|
3475
|
+
case 'ArrowLeft':
|
|
3476
|
+
width = current - step;
|
|
3477
|
+
break;
|
|
3478
|
+
case 'ArrowRight':
|
|
3479
|
+
width = current + step;
|
|
3480
|
+
break;
|
|
3481
|
+
case 'Home':
|
|
3482
|
+
width = column.minWidth ?? this.MIN_COLUMN_WIDTH;
|
|
3483
|
+
break;
|
|
3484
|
+
case 'End':
|
|
3485
|
+
width = column.maxWidth ?? current + step;
|
|
3486
|
+
break;
|
|
3487
|
+
default: return;
|
|
3488
|
+
}
|
|
3489
|
+
event.preventDefault();
|
|
3490
|
+
const min = Math.max(column.minWidth ?? 0, this.MIN_COLUMN_WIDTH);
|
|
3491
|
+
const max = column.maxWidth ?? Number.POSITIVE_INFINITY;
|
|
3492
|
+
const newWidth = Math.min(Math.max(width, min), max);
|
|
3493
|
+
// Home at the minimum, End at the maximum: already clamped there, nothing
|
|
3494
|
+
// changed, nothing to announce.
|
|
3495
|
+
if (newWidth === current)
|
|
3496
|
+
return;
|
|
3497
|
+
// A hand-resized column stops flexing — otherwise the next reflow would
|
|
3498
|
+
// overwrite the width the user just chose.
|
|
3499
|
+
this.api.updateColumn(column.id, { width: newWidth, flex: undefined });
|
|
3500
|
+
this.eventService.dispatchEvent('columnResized', { columnId: column.id, newWidth });
|
|
2566
3501
|
}
|
|
2567
3502
|
onHeaderMenuSortClick(colId) {
|
|
2568
3503
|
const currentSort = this.sortModel.find(sort => sort.colId === colId)?.sort ?? null;
|
|
@@ -2602,6 +3537,7 @@ class OneGridHeaderComponent {
|
|
|
2602
3537
|
// Track the dragged column index
|
|
2603
3538
|
onDragColumnStart(event, index) {
|
|
2604
3539
|
this.draggedIndex = index;
|
|
3540
|
+
this.dragSourceIndex = index;
|
|
2605
3541
|
// Optional: Set drag image/effect
|
|
2606
3542
|
if (event.dataTransfer) {
|
|
2607
3543
|
event.dataTransfer.effectAllowed = 'move';
|
|
@@ -2619,11 +3555,27 @@ class OneGridHeaderComponent {
|
|
|
2619
3555
|
this.draggedIndex = targetIndex;
|
|
2620
3556
|
}
|
|
2621
3557
|
onDragColumnEnd(event) {
|
|
2622
|
-
this.
|
|
3558
|
+
this.finishColumnDrag();
|
|
2623
3559
|
}
|
|
2624
3560
|
onDropColumn(event) {
|
|
2625
3561
|
event.preventDefault();
|
|
3562
|
+
this.finishColumnDrag();
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3565
|
+
* The reorder itself already happened, step by step, during dragover — this
|
|
3566
|
+
* only reports the completed move. Both `drop` and `dragend` funnel here and
|
|
3567
|
+
* both can fire for one gesture; nulling the indexes makes the second call a
|
|
3568
|
+
* no-op, so one gesture reports once.
|
|
3569
|
+
*/
|
|
3570
|
+
finishColumnDrag() {
|
|
3571
|
+
if (this.draggedIndex !== null && this.dragSourceIndex !== null && this.draggedIndex !== this.dragSourceIndex) {
|
|
3572
|
+
const column = this.columnDefs[this.draggedIndex];
|
|
3573
|
+
if (column) {
|
|
3574
|
+
this.eventService.dispatchEvent('columnMoved', { columnId: column.id, toIndex: this.draggedIndex });
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
2626
3577
|
this.draggedIndex = null;
|
|
3578
|
+
this.dragSourceIndex = null;
|
|
2627
3579
|
}
|
|
2628
3580
|
ngOnDestroy() {
|
|
2629
3581
|
// This completed an unused Subject and left the real subscription running.
|
|
@@ -2636,13 +3588,22 @@ class OneGridHeaderComponent {
|
|
|
2636
3588
|
this.onResizeEnd();
|
|
2637
3589
|
}
|
|
2638
3590
|
}
|
|
2639
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, deps: [{ token: OneGridEventService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2640
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: { scrollLeft: "scrollLeft", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", api: "api" }, viewQueries: [{ propertyName: "headerViewport", first: true, predicate: ["headerViewport"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"one-grid-header\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\">\n <div class=\"one-header-row\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n </div>\n</div>\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <div class=\"og-resize-handle\" (mousedown)=\"onResizeStart($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </div>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <div class=\"one-grid-header-menu-button\" (click)=\"onHeaderMenuOptionClick(col)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </div>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2$1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: ["api", "allowedEdit"] }, { kind: "pipe", type: ColumnSortStatePipe, name: "columnSortState" }] }); }
|
|
3591
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, deps: [{ token: OneGridEventService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3592
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridHeaderComponent, isStandalone: true, selector: "one-grid-header", inputs: { scrollLeft: "scrollLeft", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", api: "api", filterRow: "filterRow", headerMenu: "headerMenu" }, outputs: { headerMenuClick: "headerMenuClick" }, host: { attributes: { "role": "presentation" } }, providers: [ONE_GRID_ICON_PROVIDERS], viewQueries: [{ propertyName: "headerViewport", first: true, predicate: ["headerViewport"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"one-grid-header\" role=\"rowgroup\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" role=\"presentation\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\" role=\"presentation\">\n <div class=\"one-header-row\" role=\"row\" aria-rowindex=\"1\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n\n <!-- Filter Row (grid opt-in). Lives inside the header viewport so it\n scrolls with the columns and its pinned cells stick the same way. -->\n @if (filterRow) {\n <div class=\"one-filter-row\" role=\"row\" aria-rowindex=\"2\">\n @for (col of columnDefs; track col.id) {\n <div class=\"one-filter-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\">\n @if (col._resolvedFilterable && col.field) {\n <input class=\"one-filter-input\"\n type=\"text\"\n [attr.aria-label]=\"'Filter ' + (col.headerName || col.field)\"\n [value]=\"filterTexts[col.field] ?? ''\"\n (input)=\"onFilterInput(col, $event)\" />\n }\n </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- The default column menu (grid opt-in). A sibling of the header block\n because everything inside it clips with overflow: hidden; the host\n positions it just below, right-aligned to the opening column. -->\n@if (headerMenu && openMenuColumn) {\n <one-grid-header-menu\n [style.right.px]=\"menuOffsetRight\"\n [column]=\"openMenuColumn\"\n [sortState]=\"menuSortState(openMenuColumn)\"\n [canHide]=\"columnDefs.length > 1\"\n (menuAction)=\"onHeaderMenuAction($event, openMenuColumn)\"\n (menuClose)=\"closeHeaderMenu()\" />\n}\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.aria-sort]=\"col.field | columnSortState:api.sortModel | ariaSort:col._resolvedSortable\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <!-- `separator` is the ARIA window-splitter pattern, which is\n what a column resizer is. Focusable and arrow-operable\n because WCAG 2.5.7 requires everything achievable by\n dragging to have a non-dragging alternative. -->\n <div\n class=\"og-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'Resize ' + (col.headerName || col.field)\"\n [attr.aria-valuenow]=\"col.width\"\n [attr.aria-valuemin]=\"col.minWidth || MIN_COLUMN_WIDTH\"\n [attr.aria-valuemax]=\"col.maxWidth || null\"\n (mousedown)=\"onResizeStart($event, colIndex)\"\n (keydown)=\"onResizeKeydown($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <!-- A button, not a div with a click handler: sorting was\n pointer-only. The current state is not repeated here \u2014\n it is on the columnheader's `aria-sort`, and saying it\n twice makes a screen reader read it twice. -->\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Sort by ' + (col.headerName || col.field)\"\n [title]=\"'Sort by ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </button>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Options for ' + (col.headerName || col.field)\"\n [attr.aria-haspopup]=\"headerMenu ? 'menu' : null\"\n [attr.aria-expanded]=\"headerMenu ? openMenuColumn?.id === col.id : null\"\n [title]=\"'Options for ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuOptionClick(col, $event)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}one-grid-header-menu{position:absolute;top:100%;z-index:1000}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: OneGridCheckboxHeaderComponent, selector: "one-grid-checkbox-header", inputs: ["api", "allowedEdit"] }, { kind: "component", type: OneGridHeaderMenuComponent, selector: "one-grid-header-menu", inputs: ["column", "sortState", "canHide"], outputs: ["menuAction", "menuClose"] }, { kind: "pipe", type: ColumnSortStatePipe, name: "columnSortState" }, { kind: "pipe", type: AriaSortPipe, name: "ariaSort" }] }); }
|
|
2641
3593
|
}
|
|
2642
3594
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridHeaderComponent, decorators: [{
|
|
2643
3595
|
type: Component,
|
|
2644
|
-
args: [{ standalone:
|
|
2645
|
-
|
|
3596
|
+
args: [{ standalone: true, imports: [
|
|
3597
|
+
NgClass,
|
|
3598
|
+
NgStyle,
|
|
3599
|
+
NgTemplateOutlet,
|
|
3600
|
+
LucideAngularModule,
|
|
3601
|
+
OneGridCheckboxHeaderComponent,
|
|
3602
|
+
OneGridHeaderMenuComponent,
|
|
3603
|
+
ColumnSortStatePipe,
|
|
3604
|
+
AriaSortPipe,
|
|
3605
|
+
], selector: 'one-grid-header', host: { 'role': 'presentation' }, providers: [ONE_GRID_ICON_PROVIDERS], template: "<div class=\"one-grid-header\" role=\"rowgroup\">\n @if (enableGroupColumnDefs) {\n <div class=\"one-group-header-row\" role=\"presentation\" [style.transform]=\"'translateX(-' + scrollLeft + 'px)'\">\n <ng-container *ngTemplateOutlet=\"groupColumns; context: { groups: groupColumnDefs }\"></ng-container>\n </div>\n }\n <!-- The column row is scrolled rather than translated. `position: sticky`\n resolves against the nearest scrollport, and a transformed ancestor is\n not one \u2014 pinned headers could never hold their place inside it. The\n group row above keeps its transform: groups span contiguous columns,\n which pinning reorders, so the two features do not combine. -->\n <div #headerViewport class=\"one-grid-header-viewport\" role=\"presentation\">\n <div class=\"one-header-row\" role=\"row\" aria-rowindex=\"1\">\n <ng-container *ngTemplateOutlet=\"headerColumns; context: { columns: columnDefs }\"></ng-container>\n </div>\n\n <!-- Filter Row (grid opt-in). Lives inside the header viewport so it\n scrolls with the columns and its pinned cells stick the same way. -->\n @if (filterRow) {\n <div class=\"one-filter-row\" role=\"row\" aria-rowindex=\"2\">\n @for (col of columnDefs; track col.id) {\n <div class=\"one-filter-cell\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\">\n @if (col._resolvedFilterable && col.field) {\n <input class=\"one-filter-input\"\n type=\"text\"\n [attr.aria-label]=\"'Filter ' + (col.headerName || col.field)\"\n [value]=\"filterTexts[col.field] ?? ''\"\n (input)=\"onFilterInput(col, $event)\" />\n }\n </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- The default column menu (grid opt-in). A sibling of the header block\n because everything inside it clips with overflow: hidden; the host\n positions it just below, right-aligned to the opening column. -->\n@if (headerMenu && openMenuColumn) {\n <one-grid-header-menu\n [style.right.px]=\"menuOffsetRight\"\n [column]=\"openMenuColumn\"\n [sortState]=\"menuSortState(openMenuColumn)\"\n [canHide]=\"columnDefs.length > 1\"\n (menuAction)=\"onHeaderMenuAction($event, openMenuColumn)\"\n (menuClose)=\"closeHeaderMenu()\" />\n}\n\n<!-- Group Header Cell Template -->\n<ng-template #groupColumns let-groups=\"groups\">\n @for(group of groups; track group.id) {\n <div class=\"one-group-header-cell\"\n [style.width.px]=\"group.width\"\n [ngClass]=\"group._resolvedGroupClass\"\n [ngStyle]=\"group._resolvedGroupStyle\">\n <div class=\"one-group-header-cell-text\">\n {{ group.headerName }}\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Header Cell Template -->\n<ng-template #headerColumns let-columns=\"columns\">\n @for (col of columns; let colIndex = $index; track col.id) {\n <div class=\"one-grid-header-column\"\n role=\"columnheader\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.aria-sort]=\"col.field | columnSortState:api.sortModel | ariaSort:col._resolvedSortable\"\n [attr.data-index]=\"colIndex\"\n [class.one-column-pinned-left]=\"col.pinned === 'left'\"\n [class.one-column-pinned-right]=\"col.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [class.one-column-pinned-edge]=\"col._pinnedEdge\"\n [style.left.px]=\"col.pinned === 'left' ? col._pinnedOffset : null\"\n [style.right.px]=\"col.pinned === 'right' ? col._pinnedOffset : null\"\n [style.width.px]=\"col.width\"\n [style.min-width.px]=\"col.minWidth\"\n [style.max-width.px]=\"col.maxWidth\"\n [draggable]=\"col._resolvedDragable\"\n (dragstart)=\"onDragColumnStart($event, colIndex)\"\n (dragover)=\"onDragColumnOver($event, colIndex)\"\n (dragend)=\"onDragColumnEnd($event)\"\n (drop)=\"onDropColumn($event)\">\n <div class=\"one-grid-header-cell\"\n [ngClass]=\"col._resolvedHeaderClass\"\n [ngStyle]=\"col._resolvedHeaderStyle\"\n >\n <span class=\"one-grid-header-cell-group\">\n @if (col?.headerCheckboxSelection) {\n <one-grid-checkbox-header\n [api]=\"api\"\n [allowedEdit]=\"allowedEdit\"\n />\n }\n\n {{ col?.headerName ?? '' }}\n </span>\n\n <!-- Og Resize Handler -->\n @if (col._resolvedResizable) {\n <!-- `separator` is the ARIA window-splitter pattern, which is\n what a column resizer is. Focusable and arrow-operable\n because WCAG 2.5.7 requires everything achievable by\n dragging to have a non-dragging alternative. -->\n <div\n class=\"og-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'Resize ' + (col.headerName || col.field)\"\n [attr.aria-valuenow]=\"col.width\"\n [attr.aria-valuemin]=\"col.minWidth || MIN_COLUMN_WIDTH\"\n [attr.aria-valuemax]=\"col.maxWidth || null\"\n (mousedown)=\"onResizeStart($event, colIndex)\"\n (keydown)=\"onResizeKeydown($event, colIndex)\">\n <i class=\"bi bi-grip-vertical\"></i>\n </div>\n }\n\n <div class=\"one-grid-header-cell-group\">\n\n <!-- Sort Menu Button -->\n @if (col._resolvedSortable) {\n <!-- A button, not a div with a click handler: sorting was\n pointer-only. The current state is not repeated here \u2014\n it is on the columnheader's `aria-sort`, and saying it\n twice makes a screen reader read it twice. -->\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Sort by ' + (col.headerName || col.field)\"\n [title]=\"'Sort by ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuSortClick(col.field)\">\n\n @if ((col.field | columnSortState:api.sortModel) === 'asc') {\n <lucide-icon name=\"arrow-up-a-z\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else if ((col.field | columnSortState:api.sortModel) === 'desc') {\n <lucide-icon name=\"arrow-down-z-a\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n @else {\n <lucide-icon name=\"arrow-down-up\" [size]=\"16\" [strokeWidth]=\"2\"></lucide-icon>\n }\n </button>\n }\n\n <!-- Og Option Menu Button -->\n @if (col && !col._resolvedSuppressHeaderMenuButton) {\n <button\n type=\"button\"\n class=\"one-grid-header-menu-button\"\n [attr.aria-label]=\"'Options for ' + (col.headerName || col.field)\"\n [attr.aria-haspopup]=\"headerMenu ? 'menu' : null\"\n [attr.aria-expanded]=\"headerMenu ? openMenuColumn?.id === col.id : null\"\n [title]=\"'Options for ' + (col.headerName || col.field)\"\n (click)=\"onHeaderMenuOptionClick(col, $event)\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}one-grid-header-menu{position:absolute;top:100%;z-index:1000}\n"] }]
|
|
3606
|
+
}], ctorParameters: () => [{ type: OneGridEventService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { headerViewport: [{
|
|
2646
3607
|
type: ViewChild,
|
|
2647
3608
|
args: ['headerViewport', { static: true }]
|
|
2648
3609
|
}], scrollLeft: [{
|
|
@@ -2653,6 +3614,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2653
3614
|
type: Input
|
|
2654
3615
|
}], api: [{
|
|
2655
3616
|
type: Input
|
|
3617
|
+
}], filterRow: [{
|
|
3618
|
+
type: Input
|
|
3619
|
+
}], headerMenuClick: [{
|
|
3620
|
+
type: Output
|
|
3621
|
+
}], headerMenu: [{
|
|
3622
|
+
type: Input
|
|
2656
3623
|
}] } });
|
|
2657
3624
|
|
|
2658
3625
|
class CellEditablePipe {
|
|
@@ -2673,13 +3640,13 @@ class CellEditablePipe {
|
|
|
2673
3640
|
return false;
|
|
2674
3641
|
}
|
|
2675
3642
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2676
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, name: "cellEditable" }); }
|
|
3643
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, isStandalone: true, name: "cellEditable" }); }
|
|
2677
3644
|
}
|
|
2678
3645
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellEditablePipe, decorators: [{
|
|
2679
3646
|
type: Pipe,
|
|
2680
3647
|
args: [{
|
|
2681
3648
|
name: 'cellEditable',
|
|
2682
|
-
standalone:
|
|
3649
|
+
standalone: true
|
|
2683
3650
|
}]
|
|
2684
3651
|
}] });
|
|
2685
3652
|
|
|
@@ -2693,29 +3660,18 @@ class OneGridCheckboxComponent {
|
|
|
2693
3660
|
onToggleSelection(event) {
|
|
2694
3661
|
const isChecked = event.target.checked;
|
|
2695
3662
|
const { rowId, rowIndex, data } = this.node;
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
this.node.setSelected(true);
|
|
2701
|
-
}
|
|
2702
|
-
this.api.toggleSelection(rowId, isChecked);
|
|
2703
|
-
}
|
|
2704
|
-
else {
|
|
2705
|
-
// Multi-select mode
|
|
2706
|
-
this.selectionService.toggleSelection(rowId, isChecked);
|
|
2707
|
-
this.node.setSelected(isChecked);
|
|
2708
|
-
}
|
|
2709
|
-
this.node.cellStates = { ...this.node.cellStates };
|
|
2710
|
-
this.eventService.dispatchEvent('selectionChanged', { rowIndex, selected: isChecked, data, node: this.node });
|
|
3663
|
+
// The mode rule (single clears the rest, multiple toggles) lives on the api
|
|
3664
|
+
// now, so pressing Space on a focused row and clicking this checkbox cannot
|
|
3665
|
+
// drift apart. It dispatches `selectionChanged` itself.
|
|
3666
|
+
this.api.toggleRowSelection(rowId, isChecked);
|
|
2711
3667
|
this.onRowSelection.emit({ rowIndex, selected: isChecked, data, node: this.node });
|
|
2712
3668
|
}
|
|
2713
3669
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxComponent, deps: [{ token: OneGridSelectionService }, { token: OneGridEventService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2714
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: { node: "node", allowedEdit: "allowedEdit", api: "api" }, outputs: { onRowSelection: "onRowSelection" }, ngImport: i0, template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>", styles: [""] }); }
|
|
3670
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridCheckboxComponent, isStandalone: true, selector: "one-grid-checkbox", inputs: { node: "node", allowedEdit: "allowedEdit", api: "api" }, outputs: { onRowSelection: "onRowSelection" }, ngImport: i0, template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>", styles: [""] }); }
|
|
2715
3671
|
}
|
|
2716
3672
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCheckboxComponent, decorators: [{
|
|
2717
3673
|
type: Component,
|
|
2718
|
-
args: [{ standalone:
|
|
3674
|
+
args: [{ standalone: true, selector: 'one-grid-checkbox', template: "\n<div class=\"one-grid-checkbox\">\n <input type=\"checkbox\"\n [name]=\"node.rowId\"\n [class.disabled]=\"!(allowedEdit)\"\n [checked]=\"node.selected\"\n (change)=\"onToggleSelection($event)\"/>\n</div>" }]
|
|
2719
3675
|
}], ctorParameters: () => [{ type: OneGridSelectionService }, { type: OneGridEventService }], propDecorators: { node: [{
|
|
2720
3676
|
type: Input
|
|
2721
3677
|
}], allowedEdit: [{
|
|
@@ -2726,75 +3682,228 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2726
3682
|
type: Output
|
|
2727
3683
|
}] } });
|
|
2728
3684
|
|
|
2729
|
-
class
|
|
2730
|
-
constructor(
|
|
2731
|
-
this.cellService = cellService;
|
|
2732
|
-
this.allowedEdit = false;
|
|
3685
|
+
class OneGridCellGroupRendererComponent {
|
|
3686
|
+
constructor() {
|
|
2733
3687
|
this.masterDetail = false;
|
|
2734
|
-
this.
|
|
2735
|
-
this.gridType = null;
|
|
2736
|
-
this.context = null;
|
|
2737
|
-
/**
|
|
2738
|
-
* Bind Event to Parent Component
|
|
2739
|
-
*/
|
|
2740
|
-
this.onRowGotoDetail = new EventEmitter();
|
|
2741
|
-
this.onRowAddNew = new EventEmitter();
|
|
2742
|
-
this.onRowView = new EventEmitter();
|
|
2743
|
-
this.onRowLocation = new EventEmitter();
|
|
2744
|
-
this.onRowDocument = new EventEmitter();
|
|
2745
|
-
this.onRowCopy = new EventEmitter();
|
|
2746
|
-
this.onRowDelete = new EventEmitter();
|
|
2747
|
-
this.onRowGotoList = new EventEmitter();
|
|
2748
|
-
this.onRowEdit = new EventEmitter();
|
|
2749
|
-
this.onRowPrint = new EventEmitter();
|
|
2750
|
-
this.onRowHistory = new EventEmitter();
|
|
2751
|
-
this.onRowOption = new EventEmitter();
|
|
2752
|
-
this.onRowLanguage = new EventEmitter();
|
|
2753
|
-
this.onRowCellOption = new EventEmitter();
|
|
2754
|
-
/**
|
|
2755
|
-
* Accessible names for the action buttons.
|
|
2756
|
-
*
|
|
2757
|
-
* Every one of them is an icon with no text, and none carried a label — a
|
|
2758
|
-
* screen reader announced "button" and nothing more, fourteen times per row.
|
|
2759
|
-
* These are also the `title`, so the icons are identifiable by pointer too.
|
|
2760
|
-
*/
|
|
2761
|
-
this.labels = {
|
|
2762
|
-
gotoDetail: 'Open detail',
|
|
2763
|
-
addNew: 'Add new row',
|
|
2764
|
-
view: 'View row',
|
|
2765
|
-
location: 'Show location',
|
|
2766
|
-
document: 'Open document',
|
|
2767
|
-
copy: 'Copy row',
|
|
2768
|
-
delete: 'Delete row',
|
|
2769
|
-
gotoList: 'Open list',
|
|
2770
|
-
edit: 'Edit row',
|
|
2771
|
-
print: 'Print row',
|
|
2772
|
-
history: 'View history',
|
|
2773
|
-
option: 'Row options',
|
|
2774
|
-
language: 'Change language',
|
|
2775
|
-
cellOption: 'Cell options',
|
|
2776
|
-
};
|
|
3688
|
+
this.expansionChanged = new EventEmitter();
|
|
2777
3689
|
}
|
|
2778
3690
|
/**
|
|
2779
|
-
*
|
|
2780
|
-
*
|
|
2781
|
-
*
|
|
2782
|
-
* any function — so a predicate asking to hide the buttons showed them, and
|
|
2783
|
-
* one asking to show them showed them too. Resolved here instead, with the
|
|
2784
|
-
* same params every other row-scoped predicate gets.
|
|
3691
|
+
* Presentational only: report the click and let the body, which holds the
|
|
3692
|
+
* API, perform the toggle. The click is stopped from bubbling so it is not
|
|
3693
|
+
* also read as a cell click by the cell underneath.
|
|
2785
3694
|
*/
|
|
2786
|
-
|
|
2787
|
-
|
|
3695
|
+
invokeChangedClick(event) {
|
|
3696
|
+
event.stopPropagation();
|
|
3697
|
+
this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
|
|
3698
|
+
}
|
|
3699
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3700
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellGroupRendererComponent, isStandalone: true, selector: "one-grid-cell-group-renderer", inputs: { node: "node", masterDetail: "masterDetail", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent" }, outputs: { expansionChanged: "expansionChanged" }, ngImport: i0, template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n", styles: [""] }); }
|
|
3701
|
+
}
|
|
3702
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellGroupRendererComponent, decorators: [{
|
|
3703
|
+
type: Component,
|
|
3704
|
+
args: [{ standalone: true, selector: 'one-grid-cell-group-renderer', template: "@if (node.expandable) {\n <span class=\"cell-group-renderer\" [style.padding-left.px]=\"6\">\n @if (node.children?.length > 0 || masterDetail) {\n <!-- A real button, not a div with a click handler: this is a\n disclosure control, so it needs to be focusable, operable with\n Enter and Space, and to expose its state via aria-expanded.\n mousedown is stopped so pressing the expander does not also\n begin a cell range selection on the cell underneath. -->\n <button\n type=\"button\"\n class=\"expansion-button\"\n [attr.aria-expanded]=\"node.expanded\"\n [attr.aria-label]=\"node.expanded ? 'Collapse row' : 'Expand row'\"\n (mousedown)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\"\n (click)=\"invokeChangedClick($event)\">\n <i [class.node-expanded]=\"node.expanded\" class=\"bi bi-chevron-right expansion-icon\"></i>\n </button>\n }\n </span>\n}\n" }]
|
|
3705
|
+
}], propDecorators: { node: [{
|
|
3706
|
+
type: Input
|
|
3707
|
+
}], masterDetail: [{
|
|
3708
|
+
type: Input
|
|
3709
|
+
}], detailCellRendererParams: [{
|
|
3710
|
+
type: Input
|
|
3711
|
+
}], detailRendererComponent: [{
|
|
3712
|
+
type: Input
|
|
3713
|
+
}], expansionChanged: [{
|
|
3714
|
+
type: Output
|
|
3715
|
+
}] } });
|
|
3716
|
+
|
|
3717
|
+
class OneGridCellOptionMenuComponent {
|
|
3718
|
+
get menuLabel() {
|
|
3719
|
+
return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
|
|
3720
|
+
}
|
|
3721
|
+
constructor(elementRef) {
|
|
3722
|
+
this.elementRef = elementRef;
|
|
3723
|
+
this.column = null;
|
|
3724
|
+
this.node = null;
|
|
3725
|
+
this.options = [];
|
|
3726
|
+
this.cellOptionMenuSelect = new EventEmitter();
|
|
3727
|
+
this.cellOptionMenuClose = new EventEmitter();
|
|
3728
|
+
/**
|
|
3729
|
+
* Roving tabindex: one item is tabbable and the arrows move which. Standard
|
|
3730
|
+
* for a menu — a list of separately tabbable buttons makes a keyboard user
|
|
3731
|
+
* tab through every option to get past the menu.
|
|
3732
|
+
*/
|
|
3733
|
+
this.activeIndex = 0;
|
|
2788
3734
|
}
|
|
2789
3735
|
/**
|
|
2790
|
-
*
|
|
3736
|
+
* Move focus into the menu once it exists.
|
|
2791
3737
|
*
|
|
2792
|
-
*
|
|
2793
|
-
*
|
|
2794
|
-
* through the shared builder now, so `disable` sees exactly what `editable`
|
|
2795
|
-
* and `hideActionButton` see.
|
|
3738
|
+
* Opening a menu and leaving focus on the trigger strands a keyboard user:
|
|
3739
|
+
* the menu is on screen and nothing they press reaches it.
|
|
2796
3740
|
*/
|
|
2797
|
-
|
|
3741
|
+
ngAfterViewInit() {
|
|
3742
|
+
this.focusItem(0);
|
|
3743
|
+
}
|
|
3744
|
+
onItemKeydown(event, index, option) {
|
|
3745
|
+
switch (event.key) {
|
|
3746
|
+
case 'ArrowDown':
|
|
3747
|
+
event.preventDefault();
|
|
3748
|
+
this.focusItem((index + 1) % this.options.length);
|
|
3749
|
+
return;
|
|
3750
|
+
case 'ArrowUp':
|
|
3751
|
+
event.preventDefault();
|
|
3752
|
+
this.focusItem((index - 1 + this.options.length) % this.options.length);
|
|
3753
|
+
return;
|
|
3754
|
+
case 'Home':
|
|
3755
|
+
event.preventDefault();
|
|
3756
|
+
this.focusItem(0);
|
|
3757
|
+
return;
|
|
3758
|
+
case 'End':
|
|
3759
|
+
event.preventDefault();
|
|
3760
|
+
this.focusItem(this.options.length - 1);
|
|
3761
|
+
return;
|
|
3762
|
+
case 'Enter':
|
|
3763
|
+
case ' ':
|
|
3764
|
+
// A <button> already activates on both, but only once the default runs.
|
|
3765
|
+
// Handling it here keeps the menu's behaviour independent of that.
|
|
3766
|
+
event.preventDefault();
|
|
3767
|
+
this.onOptionSelected(option);
|
|
3768
|
+
return;
|
|
3769
|
+
default:
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
focusItem(index) {
|
|
3774
|
+
if (!this.options?.length)
|
|
3775
|
+
return;
|
|
3776
|
+
this.activeIndex = index;
|
|
3777
|
+
this.items?.get(index)?.nativeElement?.focus();
|
|
3778
|
+
}
|
|
3779
|
+
// Click outside
|
|
3780
|
+
onDocumentClick(event) {
|
|
3781
|
+
const target = event.target;
|
|
3782
|
+
if (!this.elementRef.nativeElement.contains(target)) {
|
|
3783
|
+
this.close();
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
// Escape key
|
|
3787
|
+
onEscape() {
|
|
3788
|
+
this.close();
|
|
3789
|
+
}
|
|
3790
|
+
// Scroll closes menu (prevents misalignment)
|
|
3791
|
+
onScroll() {
|
|
3792
|
+
this.close();
|
|
3793
|
+
}
|
|
3794
|
+
// Option selected
|
|
3795
|
+
onOptionSelected(option) {
|
|
3796
|
+
this.cellOptionMenuSelect.emit({
|
|
3797
|
+
column: this.column,
|
|
3798
|
+
node: this.node,
|
|
3799
|
+
option: option
|
|
3800
|
+
});
|
|
3801
|
+
this.close();
|
|
3802
|
+
}
|
|
3803
|
+
// Single close method
|
|
3804
|
+
close() {
|
|
3805
|
+
this.node.cellOptions[this.column.field] = false;
|
|
3806
|
+
this.cellOptionMenuClose.emit();
|
|
3807
|
+
}
|
|
3808
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3809
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellOptionMenuComponent, isStandalone: true, selector: "one-grid-cell-option-menu", inputs: { column: "column", node: "node", options: "options" }, outputs: { cellOptionMenuSelect: "cellOptionMenuSelect", cellOptionMenuClose: "cellOptionMenuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n", styles: [""] }); }
|
|
3810
|
+
}
|
|
3811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
|
|
3812
|
+
type: Component,
|
|
3813
|
+
args: [{ selector: 'one-grid-cell-option-menu', standalone: true, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n" }]
|
|
3814
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
|
|
3815
|
+
type: Input
|
|
3816
|
+
}], node: [{
|
|
3817
|
+
type: Input
|
|
3818
|
+
}], options: [{
|
|
3819
|
+
type: Input
|
|
3820
|
+
}], cellOptionMenuSelect: [{
|
|
3821
|
+
type: Output
|
|
3822
|
+
}], cellOptionMenuClose: [{
|
|
3823
|
+
type: Output
|
|
3824
|
+
}], items: [{
|
|
3825
|
+
type: ViewChildren,
|
|
3826
|
+
args: ['item']
|
|
3827
|
+
}], onDocumentClick: [{
|
|
3828
|
+
type: HostListener,
|
|
3829
|
+
args: ['document:click', ['$event']]
|
|
3830
|
+
}], onEscape: [{
|
|
3831
|
+
type: HostListener,
|
|
3832
|
+
args: ['document:keydown.escape']
|
|
3833
|
+
}], onScroll: [{
|
|
3834
|
+
type: HostListener,
|
|
3835
|
+
args: ['document:scroll']
|
|
3836
|
+
}] } });
|
|
3837
|
+
|
|
3838
|
+
class OneGridRowActionComponent {
|
|
3839
|
+
constructor(cellService) {
|
|
3840
|
+
this.cellService = cellService;
|
|
3841
|
+
this.allowedEdit = false;
|
|
3842
|
+
this.masterDetail = false;
|
|
3843
|
+
this.masterNode = null;
|
|
3844
|
+
this.gridType = null;
|
|
3845
|
+
this.context = null;
|
|
3846
|
+
/**
|
|
3847
|
+
* Bind Event to Parent Component
|
|
3848
|
+
*/
|
|
3849
|
+
this.onRowGotoDetail = new EventEmitter();
|
|
3850
|
+
this.onRowAddNew = new EventEmitter();
|
|
3851
|
+
this.onRowView = new EventEmitter();
|
|
3852
|
+
this.onRowLocation = new EventEmitter();
|
|
3853
|
+
this.onRowDocument = new EventEmitter();
|
|
3854
|
+
this.onRowCopy = new EventEmitter();
|
|
3855
|
+
this.onRowDelete = new EventEmitter();
|
|
3856
|
+
this.onRowGotoList = new EventEmitter();
|
|
3857
|
+
this.onRowEdit = new EventEmitter();
|
|
3858
|
+
this.onRowPrint = new EventEmitter();
|
|
3859
|
+
this.onRowHistory = new EventEmitter();
|
|
3860
|
+
this.onRowOption = new EventEmitter();
|
|
3861
|
+
this.onRowLanguage = new EventEmitter();
|
|
3862
|
+
this.onRowCellOption = new EventEmitter();
|
|
3863
|
+
/**
|
|
3864
|
+
* Accessible names for the action buttons.
|
|
3865
|
+
*
|
|
3866
|
+
* Every one of them is an icon with no text, and none carried a label — a
|
|
3867
|
+
* screen reader announced "button" and nothing more, fourteen times per row.
|
|
3868
|
+
* These are also the `title`, so the icons are identifiable by pointer too.
|
|
3869
|
+
*/
|
|
3870
|
+
this.labels = {
|
|
3871
|
+
gotoDetail: 'Open detail',
|
|
3872
|
+
addNew: 'Add new row',
|
|
3873
|
+
view: 'View row',
|
|
3874
|
+
location: 'Show location',
|
|
3875
|
+
document: 'Open document',
|
|
3876
|
+
copy: 'Copy row',
|
|
3877
|
+
delete: 'Delete row',
|
|
3878
|
+
gotoList: 'Open list',
|
|
3879
|
+
edit: 'Edit row',
|
|
3880
|
+
print: 'Print row',
|
|
3881
|
+
history: 'View history',
|
|
3882
|
+
option: 'Row options',
|
|
3883
|
+
language: 'Change language',
|
|
3884
|
+
cellOption: 'Cell options',
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Whether this column's action buttons are hidden for this row.
|
|
3889
|
+
*
|
|
3890
|
+
* The templates read `column?.hideActionButton` directly, which is truthy for
|
|
3891
|
+
* any function — so a predicate asking to hide the buttons showed them, and
|
|
3892
|
+
* one asking to show them showed them too. Resolved here instead, with the
|
|
3893
|
+
* same params every other row-scoped predicate gets.
|
|
3894
|
+
*/
|
|
3895
|
+
get hideActionButton() {
|
|
3896
|
+
return this.cellService.hideActionButtonFn(this.column, this.node);
|
|
3897
|
+
}
|
|
3898
|
+
/**
|
|
3899
|
+
* Whether this column's action buttons are disabled for this row.
|
|
3900
|
+
*
|
|
3901
|
+
* `disable` used to be resolved by `CellDisableResolverPipe`, which built its
|
|
3902
|
+
* own params — a different set from every other row-scoped predicate. It goes
|
|
3903
|
+
* through the shared builder now, so `disable` sees exactly what `editable`
|
|
3904
|
+
* and `hideActionButton` see.
|
|
3905
|
+
*/
|
|
3906
|
+
get actionDisabled() {
|
|
2798
3907
|
return this.cellService.disableFn(this.column, this.node) || !this.allowedEdit;
|
|
2799
3908
|
}
|
|
2800
3909
|
ngOnInit() {
|
|
@@ -2907,11 +4016,11 @@ class OneGridRowActionComponent {
|
|
|
2907
4016
|
this.onRowCellOption.emit(data);
|
|
2908
4017
|
}
|
|
2909
4018
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, deps: [{ token: OneGridCellService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2910
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: { column: "column", node: "node", allowedEdit: "allowedEdit", masterDetail: "masterDetail", masterNode: "masterNode", cellRendererParams: "cellRendererParams", gridType: "gridType", context: "context" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption" }, ngImport: i0, template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}", styles: [""], dependencies: [{ kind: "directive", type:
|
|
4019
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridRowActionComponent, isStandalone: true, selector: "one-grid-row-action", inputs: { column: "column", node: "node", allowedEdit: "allowedEdit", masterDetail: "masterDetail", masterNode: "masterNode", cellRendererParams: "cellRendererParams", gridType: "gridType", context: "context" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption" }, ngImport: i0, template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
|
|
2911
4020
|
}
|
|
2912
4021
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowActionComponent, decorators: [{
|
|
2913
4022
|
type: Component,
|
|
2914
|
-
args: [{ standalone:
|
|
4023
|
+
args: [{ standalone: true, imports: [NgClass], selector: 'one-grid-row-action', template: "@if ((cellRendererParams && node?.leaf && !column?.autoGroupField && !(node.group && node.leafGroup)) || (column?.rowActionLevels?.includes(node.level) && column?.autoGroupField)) {\n <div class=\"btn-action-container\">\n\n <!-- Expansion Button Status -->\n @if ((cellRendererParams?.detailActiveButtonStatus == 1 && !(detailActiveExcludeLevels?.has(node.level)))) {\n <!-- A button, not an <a> with a click handler: it has no href, so\n it was neither focusable nor operable from the keyboard. -->\n <button\n type=\"button\"\n class=\"btn-action btn-action-plain me-2\"\n [attr.aria-expanded]=\"node.detailActive\"\n [attr.aria-label]=\"node.detailActive ? 'Collapse row' : 'Expand row'\"\n [title]=\"node.detailActive ? 'Collapse' : 'Expand'\"\n (click)=\"invokeRowToggleDetailActiveExpand(node)\">\n\n <i class=\"bi\" [ngClass]=\"{ 'bi-chevron-down': node.detailActive, 'bi-chevron-right': !node.detailActive }\"></i>\n </button>\n }\n\n <!-- Goto Detail Button Status -->\n @if ((cellRendererParams?.gotoDetailButtonStatus == 1 && !(gotoDetailExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.gotoDetail\"\n [title]=\"labels.gotoDetail\"\n (click)=\"invokeRowActionGotoDetail(node, column)\">\n\n <i class=\"bi bi-list-task\"></i>\n </button>\n }\n\n <!-- Add New Button Status -->\n @if ((cellRendererParams?.addNewButtonStatus == 1 && !(addNewExcludeLevels?.has(node.level))) && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.addNew\"\n [title]=\"labels.addNew\"\n (click)=\"invokeRowActionAddNew(node, column)\">\n\n <i class=\"bi bi-plus\"></i>\n </button>\n }\n\n <!-- View Button Status -->\n @if (cellRendererParams?.viewButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.view\"\n [title]=\"labels.view\"\n (click)=\"invokeRowActionView(node, column)\">\n\n <i class=\"bi bi-eye\"></i>\n </button>\n }\n @if (cellRendererParams?.locationButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.location\"\n [title]=\"labels.location\"\n (click)=\"invokeRowActionLocation(node, column)\">\n\n <i class=\"bi bi-geo-alt\"></i>\n </button>\n }\n\n <!-- Document Button Status -->\n @if (cellRendererParams?.documentButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.document\"\n [title]=\"labels.document\"\n (click)=\"invokeRowActionDocument(node, column)\">\n\n <i class=\"bi bi-file-earmark-text\"></i>\n </button>\n }\n\n <!-- Copy Button Status -->\n @if (cellRendererParams?.copyButtonStatus == 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [attr.aria-label]=\"labels.copy\"\n [title]=\"labels.copy\"\n (click)=\"invokeRowActionCopy(node, column)\">\n\n <i class=\"bi bi-files\"></i>\n </button>\n }\n\n\n <!-- Delete Button Status -->\n @if (cellRendererParams?.deleteButtonStatus == 1 && (!hideActionButton || allowedEdit)) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-danger\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.delete\"\n [title]=\"labels.delete\"\n (click)=\"invokeRowActionDelete(node, column)\">\n\n <i class=\"bi bi-trash3\"></i>\n </button>\n }\n\n <!-- Goto List Button Status -->\n @if (cellRendererParams?.gotoListButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-success\"\n [attr.aria-label]=\"labels.gotoList\"\n [title]=\"labels.gotoList\"\n (click)=\"invokeRowActionGotoList(node, column)\">\n\n <i class=\"bi bi-list-ul\"></i>\n </button>\n }\n\n <!-- Edit Button Status -->\n @if (cellRendererParams?.editButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.edit\"\n [title]=\"labels.edit\"\n (click)=\"invokeRowActionEdit(node, column)\">\n\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n\n <!-- Print Button Status -->\n @if (cellRendererParams?.printButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.print\"\n [title]=\"labels.print\"\n (click)=\"invokeRowActionPrint(node, column)\">\n\n <i class=\"bi bi-printer\"></i>\n </button>\n }\n\n <!-- History Button Status -->\n @if (cellRendererParams?.historyButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.history\"\n [title]=\"labels.history\"\n (click)=\"invokeRowActionHistory(node, column)\">\n\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n\n <!-- Option Button Status -->\n @if (cellRendererParams?.optionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.option\"\n [title]=\"labels.option\"\n (click)=\"invokeRowActionOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n\n <!-- Language Button Status -->\n @if (cellRendererParams?.languageButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.language\"\n [title]=\"labels.language\"\n (click)=\"invokeRowActionLanguage(node, column)\">\n\n <i class=\"bi bi-translate\"></i>\n </button>\n }\n\n <!-- Cell Optoion Button Status (Context Menu) -->\n @if (cellRendererParams?.cellOptionButtonStatus === 1) {\n <button\n type=\"button\"\n class=\"btn-action btn-action-primary border-0 text-body\"\n [style.background]=\"'transparent'\"\n [class.disabled]=\"actionDisabled\"\n [disabled]=\"actionDisabled\"\n [attr.aria-label]=\"labels.cellOption\"\n [title]=\"labels.cellOption\"\n (click)=\"invokeRowActionCellOption(node, column)\">\n\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n }\n </div>\n}" }]
|
|
2915
4024
|
}], ctorParameters: () => [{ type: OneGridCellService }], propDecorators: { column: [{
|
|
2916
4025
|
type: Input,
|
|
2917
4026
|
args: [{ required: true }]
|
|
@@ -2961,54 +4070,128 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2961
4070
|
type: Output
|
|
2962
4071
|
}] } });
|
|
2963
4072
|
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
4073
|
+
/** Why the handle is disabled, in words a user can act on. */
|
|
4074
|
+
const SUPPRESSED_TEXT = {
|
|
4075
|
+
sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
|
|
4076
|
+
filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
|
|
4077
|
+
treeData: 'Rows cannot be reordered in a tree grid.',
|
|
4078
|
+
};
|
|
4079
|
+
/**
|
|
4080
|
+
* The grab handle for one row.
|
|
4081
|
+
*
|
|
4082
|
+
* A real `<button>`, not a styled div: it has to be focusable and operable from
|
|
4083
|
+
* the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
|
|
4084
|
+
* to have a non-dragging alternative. Space picks the row up, the arrows move
|
|
4085
|
+
* it, space drops it and escape puts it back.
|
|
4086
|
+
*
|
|
4087
|
+
* When reordering is unavailable the handle is disabled and says why, rather
|
|
4088
|
+
* than disappearing — a control that vanishes when the user sorts looks like a
|
|
4089
|
+
* bug, and one that silently does nothing is worse.
|
|
4090
|
+
*/
|
|
4091
|
+
class OneGridRowDragHandleComponent {
|
|
4092
|
+
constructor(dragService) {
|
|
4093
|
+
this.dragService = dragService;
|
|
4094
|
+
/** Non-null when the grid's order is not its own; see the model docs. */
|
|
4095
|
+
this.suppressedReason = null;
|
|
4096
|
+
this.dragEnd = new EventEmitter();
|
|
2968
4097
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
* API, perform the toggle. The click is stopped from bubbling so it is not
|
|
2972
|
-
* also read as a cell click by the cell underneath.
|
|
2973
|
-
*/
|
|
2974
|
-
invokeChangedClick(event) {
|
|
2975
|
-
event.stopPropagation();
|
|
2976
|
-
this.expansionChanged.emit({ rowIndex: this.node.rowIndex, data: this.node.data, node: this.node });
|
|
4098
|
+
get disabled() {
|
|
4099
|
+
return this.suppressedReason !== null;
|
|
2977
4100
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
}], propDecorators: { node: [{
|
|
2985
|
-
type: Input
|
|
2986
|
-
}], masterDetail: [{
|
|
2987
|
-
type: Input
|
|
2988
|
-
}], detailCellRendererParams: [{
|
|
2989
|
-
type: Input
|
|
2990
|
-
}], detailRendererComponent: [{
|
|
2991
|
-
type: Input
|
|
2992
|
-
}], expansionChanged: [{
|
|
2993
|
-
type: Output
|
|
2994
|
-
}] } });
|
|
2995
|
-
|
|
2996
|
-
class OneCellInputValidatorDirective {
|
|
2997
|
-
constructor(el) {
|
|
2998
|
-
this.el = el;
|
|
2999
|
-
this.regex = '';
|
|
3000
|
-
this.MAX_CODE_LENGTH = 36;
|
|
3001
|
-
this.KHMER_CHARACTER_RANGE = /[\u1780-\u17FF]/;
|
|
4101
|
+
get label() {
|
|
4102
|
+
if (this.suppressedReason)
|
|
4103
|
+
return SUPPRESSED_TEXT[this.suppressedReason];
|
|
4104
|
+
return this.grabbed
|
|
4105
|
+
? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
|
|
4106
|
+
: 'Drag to reorder row, or press space to move it with the arrow keys.';
|
|
3002
4107
|
}
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
4108
|
+
/** True while this specific row is the one being moved. */
|
|
4109
|
+
get grabbed() {
|
|
4110
|
+
return this.dragService.state?.rowId === this.node.rowId;
|
|
4111
|
+
}
|
|
4112
|
+
onPointerDown(event) {
|
|
4113
|
+
if (this.disabled)
|
|
4114
|
+
return;
|
|
4115
|
+
// The body starts a cell range selection on mousedown; the handle is not
|
|
4116
|
+
// part of that, and a press here must not also select.
|
|
4117
|
+
event.stopPropagation();
|
|
4118
|
+
this.dragService.startPointerDrag(event, this.node, { onEnd: (result) => this.emit(result) });
|
|
4119
|
+
}
|
|
4120
|
+
onKeyDown(event) {
|
|
4121
|
+
if (this.disabled)
|
|
4122
|
+
return;
|
|
4123
|
+
switch (event.key) {
|
|
4124
|
+
case ' ':
|
|
4125
|
+
case 'Enter':
|
|
3009
4126
|
event.preventDefault();
|
|
4127
|
+
this.dragService.toggleKeyboardDrag(this.node, { onEnd: (result) => this.emit(result) });
|
|
3010
4128
|
return;
|
|
3011
|
-
case '
|
|
4129
|
+
case 'ArrowUp':
|
|
4130
|
+
if (!this.grabbed)
|
|
4131
|
+
return;
|
|
4132
|
+
event.preventDefault();
|
|
4133
|
+
this.dragService.moveKeyboard(-1);
|
|
4134
|
+
return;
|
|
4135
|
+
case 'ArrowDown':
|
|
4136
|
+
if (!this.grabbed)
|
|
4137
|
+
return;
|
|
4138
|
+
event.preventDefault();
|
|
4139
|
+
this.dragService.moveKeyboard(1);
|
|
4140
|
+
return;
|
|
4141
|
+
case 'Escape':
|
|
4142
|
+
if (!this.grabbed)
|
|
4143
|
+
return;
|
|
4144
|
+
event.preventDefault();
|
|
4145
|
+
this.dragService.cancel();
|
|
4146
|
+
return;
|
|
4147
|
+
default:
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
/**
|
|
4152
|
+
* A row that is picked up and dropped where it started produces no event.
|
|
4153
|
+
* Blur while grabbed would otherwise strand the row mid-move, so it cancels.
|
|
4154
|
+
*/
|
|
4155
|
+
onBlur() {
|
|
4156
|
+
if (this.grabbed && this.dragService.state?.source === 'keyboard') {
|
|
4157
|
+
this.dragService.cancel();
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
emit(result) {
|
|
4161
|
+
if (result)
|
|
4162
|
+
this.dragEnd.emit(result);
|
|
4163
|
+
}
|
|
4164
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, deps: [{ token: OneGridRowDragService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4165
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridRowDragHandleComponent, isStandalone: true, selector: "one-grid-row-drag-handle", inputs: { node: "node", suppressedReason: "suppressedReason" }, outputs: { dragEnd: "dragEnd" }, providers: [ONE_GRID_ICON_PROVIDERS], ngImport: i0, template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4166
|
+
}
|
|
4167
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridRowDragHandleComponent, decorators: [{
|
|
4168
|
+
type: Component,
|
|
4169
|
+
args: [{ standalone: true, imports: [LucideAngularModule], selector: 'one-grid-row-drag-handle', changeDetection: ChangeDetectionStrategy.OnPush, providers: [ONE_GRID_ICON_PROVIDERS], template: "<button\n type=\"button\"\n class=\"one-row-drag-handle\"\n [class.one-row-drag-handle-grabbed]=\"grabbed\"\n [disabled]=\"disabled\"\n [attr.aria-label]=\"label\"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [attr.aria-pressed]=\"disabled ? null : grabbed\"\n [title]=\"label\"\n (pointerdown)=\"onPointerDown($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"onBlur()\"\n (click)=\"$event.stopPropagation()\"\n (dblclick)=\"$event.stopPropagation()\">\n <lucide-icon name=\"grip-vertical\" [size]=\"14\" [strokeWidth]=\"2\"></lucide-icon>\n</button>\n", styles: [":host{display:inline-flex;align-items:center}\n"] }]
|
|
4170
|
+
}], ctorParameters: () => [{ type: OneGridRowDragService }], propDecorators: { node: [{
|
|
4171
|
+
type: Input,
|
|
4172
|
+
args: [{ required: true }]
|
|
4173
|
+
}], suppressedReason: [{
|
|
4174
|
+
type: Input
|
|
4175
|
+
}], dragEnd: [{
|
|
4176
|
+
type: Output
|
|
4177
|
+
}] } });
|
|
4178
|
+
|
|
4179
|
+
class OneCellInputValidatorDirective {
|
|
4180
|
+
constructor(el) {
|
|
4181
|
+
this.el = el;
|
|
4182
|
+
this.regex = '';
|
|
4183
|
+
this.MAX_CODE_LENGTH = 36;
|
|
4184
|
+
this.KHMER_CHARACTER_RANGE = /[\u1780-\u17FF]/;
|
|
4185
|
+
}
|
|
4186
|
+
onKeyPress(event) {
|
|
4187
|
+
let isValid = false;
|
|
4188
|
+
const char = event.key;
|
|
4189
|
+
const inputElement = this.el.nativeElement;
|
|
4190
|
+
switch (this.validationType) {
|
|
4191
|
+
case 'disabled':
|
|
4192
|
+
event.preventDefault();
|
|
4193
|
+
return;
|
|
4194
|
+
case 'integer':
|
|
3012
4195
|
isValid = /^\d+$/.test(char);
|
|
3013
4196
|
break;
|
|
3014
4197
|
case 'code':
|
|
@@ -3042,12 +4225,12 @@ class OneCellInputValidatorDirective {
|
|
|
3042
4225
|
}
|
|
3043
4226
|
}
|
|
3044
4227
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellInputValidatorDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
3045
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: { validationType: "validationType", regex: "regex" }, host: { listeners: { "keypress": "onKeyPress($event)", "keydown": "onKeyDown($event)" } }, ngImport: i0 }); }
|
|
4228
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellInputValidatorDirective, isStandalone: true, selector: "[oneCellInputValidator]", inputs: { validationType: "validationType", regex: "regex" }, host: { listeners: { "keypress": "onKeyPress($event)", "keydown": "onKeyDown($event)" } }, ngImport: i0 }); }
|
|
3046
4229
|
}
|
|
3047
4230
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellInputValidatorDirective, decorators: [{
|
|
3048
4231
|
type: Directive,
|
|
3049
4232
|
args: [{
|
|
3050
|
-
standalone:
|
|
4233
|
+
standalone: true,
|
|
3051
4234
|
selector: '[oneCellInputValidator]'
|
|
3052
4235
|
}]
|
|
3053
4236
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { validationType: [{
|
|
@@ -3064,408 +4247,146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3064
4247
|
|
|
3065
4248
|
class OneCellRendererComponentDirective {
|
|
3066
4249
|
constructor(vcr) {
|
|
3067
|
-
this.vcr = vcr;
|
|
3068
|
-
this.initialized = false;
|
|
3069
|
-
}
|
|
3070
|
-
ngOnInit() {
|
|
3071
|
-
this.initialized = true;
|
|
3072
|
-
this.createOrUpdateComponent(true);
|
|
3073
|
-
}
|
|
3074
|
-
ngOnChanges(changes) {
|
|
3075
|
-
if (!this.initialized)
|
|
3076
|
-
return;
|
|
3077
|
-
const componentChanged = changes['cellRendererComponent'];
|
|
3078
|
-
const paramsChanged = changes['params'];
|
|
3079
|
-
if (componentChanged) {
|
|
3080
|
-
this.destroyComponent();
|
|
3081
|
-
this.createOrUpdateComponent(true);
|
|
3082
|
-
}
|
|
3083
|
-
else if (paramsChanged) {
|
|
3084
|
-
this.updateComponentInputs();
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
createOrUpdateComponent(forceCreate = false) {
|
|
3088
|
-
if (!this.cellRendererComponent)
|
|
3089
|
-
return;
|
|
3090
|
-
if (forceCreate || !this.compRef) {
|
|
3091
|
-
this.vcr.clear();
|
|
3092
|
-
this.compRef = this.vcr.createComponent(this.cellRendererComponent);
|
|
3093
|
-
}
|
|
3094
|
-
this.updateComponentInputs();
|
|
3095
|
-
}
|
|
3096
|
-
updateComponentInputs() {
|
|
3097
|
-
if (!this.compRef?.instance)
|
|
3098
|
-
return;
|
|
3099
|
-
const instance = this.compRef.instance;
|
|
3100
|
-
if (typeof instance.oneInit === 'function') {
|
|
3101
|
-
// The body supplies `value` already, and it honours the column's
|
|
3102
|
-
// valueGetter. Only derive it here when nothing did — this used to
|
|
3103
|
-
// overwrite unconditionally with the raw field, so a column with both a
|
|
3104
|
-
// valueGetter and a component renderer showed the underlying field
|
|
3105
|
-
// instead of the value every other part of the grid displays.
|
|
3106
|
-
const newParams = { ...this.params };
|
|
3107
|
-
if (newParams.value === undefined) {
|
|
3108
|
-
newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
|
|
3109
|
-
}
|
|
3110
|
-
instance.oneInit(newParams);
|
|
3111
|
-
}
|
|
3112
|
-
else {
|
|
3113
|
-
Object.assign(instance, this.params);
|
|
3114
|
-
}
|
|
3115
|
-
// Optional: call change detector if needed
|
|
3116
|
-
// if (this.compRef.changeDetectorRef) {
|
|
3117
|
-
// this.compRef.changeDetectorRef.detectChanges();
|
|
3118
|
-
// }
|
|
3119
|
-
}
|
|
3120
|
-
destroyComponent() {
|
|
3121
|
-
if (this.compRef) {
|
|
3122
|
-
this.compRef.destroy();
|
|
3123
|
-
this.compRef = undefined;
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
ngOnDestroy() {
|
|
3127
|
-
this.destroyComponent();
|
|
3128
|
-
}
|
|
3129
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
3130
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: { cellRendererComponent: "cellRendererComponent", params: "params" }, usesOnChanges: true, ngImport: i0 }); }
|
|
3131
|
-
}
|
|
3132
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, decorators: [{
|
|
3133
|
-
type: Directive,
|
|
3134
|
-
args: [{
|
|
3135
|
-
standalone: false,
|
|
3136
|
-
selector: '[oneCellRendererComponent]'
|
|
3137
|
-
}]
|
|
3138
|
-
}], ctorParameters: () => [{ type: i0.ViewContainerRef }], propDecorators: { cellRendererComponent: [{
|
|
3139
|
-
type: Input,
|
|
3140
|
-
args: [{ required: true }]
|
|
3141
|
-
}], params: [{
|
|
3142
|
-
type: Input,
|
|
3143
|
-
args: [{ required: true }]
|
|
3144
|
-
}] } });
|
|
3145
|
-
|
|
3146
|
-
/**
|
|
3147
|
-
* Reveals a truncated cell in full while the pointer rests on it.
|
|
3148
|
-
*
|
|
3149
|
-
* Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
|
|
3150
|
-
* used to be applied as inline styles here, one of which was
|
|
3151
|
-
* `background: var(--ins-card-bg)` — a token from the application this library
|
|
3152
|
-
* was extracted from, which no consumer defines. The expanded cell therefore
|
|
3153
|
-
* had a transparent background and its text overlapped the next column. The
|
|
3154
|
-
* inline rules also set `z-index` on a statically positioned element, where it
|
|
3155
|
-
* does nothing.
|
|
3156
|
-
*
|
|
3157
|
-
* Overflow is measured on hover rather than after every view check. The old
|
|
3158
|
-
* version measured on init and on every text change, and reading `scrollWidth`
|
|
3159
|
-
* forces a synchronous layout — several hundred of them per render for a
|
|
3160
|
-
* normal viewport, to answer a question only the hovered cell ever asks.
|
|
3161
|
-
*/
|
|
3162
|
-
class OneCellTextExpandDirective {
|
|
3163
|
-
static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
|
|
3164
|
-
static { this.HOVER_DELAY_MS = 800; }
|
|
3165
|
-
constructor(cell, renderer) {
|
|
3166
|
-
this.cell = cell;
|
|
3167
|
-
this.renderer = renderer;
|
|
3168
|
-
}
|
|
3169
|
-
onMouseEnter() {
|
|
3170
|
-
const element = this.cell.nativeElement;
|
|
3171
|
-
// Measured here, so the layout read happens once for the cell being
|
|
3172
|
-
// hovered instead of for every cell on screen.
|
|
3173
|
-
if (element.scrollWidth <= element.clientWidth)
|
|
3174
|
-
return;
|
|
3175
|
-
this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
|
|
3176
|
-
}
|
|
3177
|
-
onMouseLeave() {
|
|
3178
|
-
this.collapse();
|
|
3179
|
-
}
|
|
3180
|
-
ngOnDestroy() {
|
|
3181
|
-
this.collapse();
|
|
3182
|
-
}
|
|
3183
|
-
expand() {
|
|
3184
|
-
this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
|
|
3185
|
-
}
|
|
3186
|
-
collapse() {
|
|
3187
|
-
clearTimeout(this.timer);
|
|
3188
|
-
this.timer = undefined;
|
|
3189
|
-
this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
|
|
3190
|
-
}
|
|
3191
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
3192
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]", host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 }); }
|
|
3193
|
-
}
|
|
3194
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
|
|
3195
|
-
type: Directive,
|
|
3196
|
-
args: [{
|
|
3197
|
-
standalone: false,
|
|
3198
|
-
selector: '[oneCellTextExpand]'
|
|
3199
|
-
}]
|
|
3200
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
|
|
3201
|
-
type: HostListener,
|
|
3202
|
-
args: ['mouseenter']
|
|
3203
|
-
}], onMouseLeave: [{
|
|
3204
|
-
type: HostListener,
|
|
3205
|
-
args: ['mouseleave']
|
|
3206
|
-
}] } });
|
|
3207
|
-
|
|
3208
|
-
class OneGridCellOptionMenuComponent {
|
|
3209
|
-
get menuLabel() {
|
|
3210
|
-
return `Options for ${this.column?.headerName ?? this.column?.field ?? 'this cell'}`;
|
|
3211
|
-
}
|
|
3212
|
-
constructor(elementRef) {
|
|
3213
|
-
this.elementRef = elementRef;
|
|
3214
|
-
this.column = null;
|
|
3215
|
-
this.node = null;
|
|
3216
|
-
this.options = [];
|
|
3217
|
-
this.cellOptionMenuSelect = new EventEmitter();
|
|
3218
|
-
this.cellOptionMenuClose = new EventEmitter();
|
|
3219
|
-
/**
|
|
3220
|
-
* Roving tabindex: one item is tabbable and the arrows move which. Standard
|
|
3221
|
-
* for a menu — a list of separately tabbable buttons makes a keyboard user
|
|
3222
|
-
* tab through every option to get past the menu.
|
|
3223
|
-
*/
|
|
3224
|
-
this.activeIndex = 0;
|
|
3225
|
-
}
|
|
3226
|
-
/**
|
|
3227
|
-
* Move focus into the menu once it exists.
|
|
3228
|
-
*
|
|
3229
|
-
* Opening a menu and leaving focus on the trigger strands a keyboard user:
|
|
3230
|
-
* the menu is on screen and nothing they press reaches it.
|
|
3231
|
-
*/
|
|
3232
|
-
ngAfterViewInit() {
|
|
3233
|
-
this.focusItem(0);
|
|
3234
|
-
}
|
|
3235
|
-
onItemKeydown(event, index, option) {
|
|
3236
|
-
switch (event.key) {
|
|
3237
|
-
case 'ArrowDown':
|
|
3238
|
-
event.preventDefault();
|
|
3239
|
-
this.focusItem((index + 1) % this.options.length);
|
|
3240
|
-
return;
|
|
3241
|
-
case 'ArrowUp':
|
|
3242
|
-
event.preventDefault();
|
|
3243
|
-
this.focusItem((index - 1 + this.options.length) % this.options.length);
|
|
3244
|
-
return;
|
|
3245
|
-
case 'Home':
|
|
3246
|
-
event.preventDefault();
|
|
3247
|
-
this.focusItem(0);
|
|
3248
|
-
return;
|
|
3249
|
-
case 'End':
|
|
3250
|
-
event.preventDefault();
|
|
3251
|
-
this.focusItem(this.options.length - 1);
|
|
3252
|
-
return;
|
|
3253
|
-
case 'Enter':
|
|
3254
|
-
case ' ':
|
|
3255
|
-
// A <button> already activates on both, but only once the default runs.
|
|
3256
|
-
// Handling it here keeps the menu's behaviour independent of that.
|
|
3257
|
-
event.preventDefault();
|
|
3258
|
-
this.onOptionSelected(option);
|
|
3259
|
-
return;
|
|
3260
|
-
default:
|
|
3261
|
-
return;
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
focusItem(index) {
|
|
3265
|
-
if (!this.options?.length)
|
|
3266
|
-
return;
|
|
3267
|
-
this.activeIndex = index;
|
|
3268
|
-
this.items?.get(index)?.nativeElement?.focus();
|
|
3269
|
-
}
|
|
3270
|
-
// Click outside
|
|
3271
|
-
onDocumentClick(event) {
|
|
3272
|
-
const target = event.target;
|
|
3273
|
-
if (!this.elementRef.nativeElement.contains(target)) {
|
|
3274
|
-
this.close();
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
// Escape key
|
|
3278
|
-
onEscape() {
|
|
3279
|
-
this.close();
|
|
3280
|
-
}
|
|
3281
|
-
// Scroll closes menu (prevents misalignment)
|
|
3282
|
-
onScroll() {
|
|
3283
|
-
this.close();
|
|
3284
|
-
}
|
|
3285
|
-
// Option selected
|
|
3286
|
-
onOptionSelected(option) {
|
|
3287
|
-
this.cellOptionMenuSelect.emit({
|
|
3288
|
-
column: this.column,
|
|
3289
|
-
node: this.node,
|
|
3290
|
-
option: option
|
|
3291
|
-
});
|
|
3292
|
-
this.close();
|
|
3293
|
-
}
|
|
3294
|
-
// Single close method
|
|
3295
|
-
close() {
|
|
3296
|
-
this.node.cellOptions[this.column.field] = false;
|
|
3297
|
-
this.cellOptionMenuClose.emit();
|
|
3298
|
-
}
|
|
3299
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3300
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: { column: "column", node: "node", options: "options" }, outputs: { cellOptionMenuSelect: "cellOptionMenuSelect", cellOptionMenuClose: "cellOptionMenuClose" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()", "document:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "items", predicate: ["item"], descendants: true }], ngImport: i0, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n", styles: [""] }); }
|
|
3301
|
-
}
|
|
3302
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellOptionMenuComponent, decorators: [{
|
|
3303
|
-
type: Component,
|
|
3304
|
-
args: [{ selector: 'one-grid-cell-option-menu', standalone: false, template: "<!-- A menu, so it is announced as one and operable from the keyboard: the items\n were `<div (click)>` with no role, which meant the menu could be opened by\n mouse and then not used at all without one. -->\n<div class=\"cell-option-menu\" role=\"menu\" #menuEl [attr.aria-label]=\"menuLabel\">\n @for (opt of options; let index = $index; track opt.value) {\n <button\n type=\"button\"\n role=\"menuitem\"\n class=\"cell-option-menu-item\"\n #item\n [tabindex]=\"index === activeIndex ? 0 : -1\"\n (click)=\"onOptionSelected(opt)\"\n (keydown)=\"onItemKeydown($event, index, opt)\">\n <span>{{ opt.text }}</span>\n </button>\n }\n</div>\n" }]
|
|
3305
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
|
|
3306
|
-
type: Input
|
|
3307
|
-
}], node: [{
|
|
3308
|
-
type: Input
|
|
3309
|
-
}], options: [{
|
|
3310
|
-
type: Input
|
|
3311
|
-
}], cellOptionMenuSelect: [{
|
|
3312
|
-
type: Output
|
|
3313
|
-
}], cellOptionMenuClose: [{
|
|
3314
|
-
type: Output
|
|
3315
|
-
}], items: [{
|
|
3316
|
-
type: ViewChildren,
|
|
3317
|
-
args: ['item']
|
|
3318
|
-
}], onDocumentClick: [{
|
|
3319
|
-
type: HostListener,
|
|
3320
|
-
args: ['document:click', ['$event']]
|
|
3321
|
-
}], onEscape: [{
|
|
3322
|
-
type: HostListener,
|
|
3323
|
-
args: ['document:keydown.escape']
|
|
3324
|
-
}], onScroll: [{
|
|
3325
|
-
type: HostListener,
|
|
3326
|
-
args: ['document:scroll']
|
|
3327
|
-
}] } });
|
|
3328
|
-
|
|
3329
|
-
/** Why the handle is disabled, in words a user can act on. */
|
|
3330
|
-
const SUPPRESSED_TEXT = {
|
|
3331
|
-
sorted: 'Rows cannot be reordered while a sort is applied. Clear the sort to reorder.',
|
|
3332
|
-
filtered: 'Rows cannot be reordered while a filter is applied. Clear the filter to reorder.',
|
|
3333
|
-
treeData: 'Rows cannot be reordered in a tree grid.',
|
|
3334
|
-
};
|
|
3335
|
-
/**
|
|
3336
|
-
* The grab handle for one row.
|
|
3337
|
-
*
|
|
3338
|
-
* A real `<button>`, not a styled div: it has to be focusable and operable from
|
|
3339
|
-
* the keyboard, because WCAG 2.5.7 requires everything achievable by dragging
|
|
3340
|
-
* to have a non-dragging alternative. Space picks the row up, the arrows move
|
|
3341
|
-
* it, space drops it and escape puts it back.
|
|
3342
|
-
*
|
|
3343
|
-
* When reordering is unavailable the handle is disabled and says why, rather
|
|
3344
|
-
* than disappearing — a control that vanishes when the user sorts looks like a
|
|
3345
|
-
* bug, and one that silently does nothing is worse.
|
|
3346
|
-
*/
|
|
3347
|
-
class OneGridRowDragHandleComponent {
|
|
3348
|
-
constructor(dragService) {
|
|
3349
|
-
this.dragService = dragService;
|
|
3350
|
-
/** Non-null when the grid's order is not its own; see the model docs. */
|
|
3351
|
-
this.suppressedReason = null;
|
|
3352
|
-
this.dragEnd = new EventEmitter();
|
|
3353
|
-
}
|
|
3354
|
-
get disabled() {
|
|
3355
|
-
return this.suppressedReason !== null;
|
|
4250
|
+
this.vcr = vcr;
|
|
4251
|
+
this.initialized = false;
|
|
3356
4252
|
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
return this.grabbed
|
|
3361
|
-
? 'Moving row. Arrow keys to move, space to drop, escape to cancel.'
|
|
3362
|
-
: 'Drag to reorder row, or press space to move it with the arrow keys.';
|
|
4253
|
+
ngOnInit() {
|
|
4254
|
+
this.initialized = true;
|
|
4255
|
+
this.createOrUpdateComponent(true);
|
|
3363
4256
|
}
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
4257
|
+
ngOnChanges(changes) {
|
|
4258
|
+
if (!this.initialized)
|
|
4259
|
+
return;
|
|
4260
|
+
const componentChanged = changes['cellRendererComponent'];
|
|
4261
|
+
const paramsChanged = changes['params'];
|
|
4262
|
+
if (componentChanged) {
|
|
4263
|
+
this.destroyComponent();
|
|
4264
|
+
this.createOrUpdateComponent(true);
|
|
4265
|
+
}
|
|
4266
|
+
else if (paramsChanged) {
|
|
4267
|
+
this.updateComponentInputs();
|
|
4268
|
+
}
|
|
3367
4269
|
}
|
|
3368
|
-
|
|
3369
|
-
if (this.
|
|
4270
|
+
createOrUpdateComponent(forceCreate = false) {
|
|
4271
|
+
if (!this.cellRendererComponent)
|
|
3370
4272
|
return;
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
4273
|
+
if (forceCreate || !this.compRef) {
|
|
4274
|
+
this.vcr.clear();
|
|
4275
|
+
this.compRef = this.vcr.createComponent(this.cellRendererComponent);
|
|
4276
|
+
}
|
|
4277
|
+
this.updateComponentInputs();
|
|
3375
4278
|
}
|
|
3376
|
-
|
|
3377
|
-
if (this.
|
|
4279
|
+
updateComponentInputs() {
|
|
4280
|
+
if (!this.compRef?.instance)
|
|
3378
4281
|
return;
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
event.preventDefault();
|
|
3395
|
-
this.dragService.moveKeyboard(1);
|
|
3396
|
-
return;
|
|
3397
|
-
case 'Escape':
|
|
3398
|
-
if (!this.grabbed)
|
|
3399
|
-
return;
|
|
3400
|
-
event.preventDefault();
|
|
3401
|
-
this.dragService.cancel();
|
|
3402
|
-
return;
|
|
3403
|
-
default:
|
|
3404
|
-
return;
|
|
4282
|
+
const instance = this.compRef.instance;
|
|
4283
|
+
if (typeof instance.oneInit === 'function') {
|
|
4284
|
+
// The body supplies `value` already, and it honours the column's
|
|
4285
|
+
// valueGetter. Only derive it here when nothing did — this used to
|
|
4286
|
+
// overwrite unconditionally with the raw field, so a column with both a
|
|
4287
|
+
// valueGetter and a component renderer showed the underlying field
|
|
4288
|
+
// instead of the value every other part of the grid displays.
|
|
4289
|
+
const newParams = { ...this.params };
|
|
4290
|
+
if (newParams.value === undefined) {
|
|
4291
|
+
newParams.value = getFieldValue(this.params.data, this.params?.columnDef?.field) ?? null;
|
|
4292
|
+
}
|
|
4293
|
+
instance.oneInit(newParams);
|
|
4294
|
+
}
|
|
4295
|
+
else {
|
|
4296
|
+
Object.assign(instance, this.params);
|
|
3405
4297
|
}
|
|
4298
|
+
// Optional: call change detector if needed
|
|
4299
|
+
// if (this.compRef.changeDetectorRef) {
|
|
4300
|
+
// this.compRef.changeDetectorRef.detectChanges();
|
|
4301
|
+
// }
|
|
3406
4302
|
}
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
onBlur() {
|
|
3412
|
-
if (this.grabbed && this.dragService.state?.source === 'keyboard') {
|
|
3413
|
-
this.dragService.cancel();
|
|
4303
|
+
destroyComponent() {
|
|
4304
|
+
if (this.compRef) {
|
|
4305
|
+
this.compRef.destroy();
|
|
4306
|
+
this.compRef = undefined;
|
|
3414
4307
|
}
|
|
3415
4308
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
this.dragEnd.emit(result);
|
|
4309
|
+
ngOnDestroy() {
|
|
4310
|
+
this.destroyComponent();
|
|
3419
4311
|
}
|
|
3420
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type:
|
|
3421
|
-
static { this.ɵ
|
|
4312
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4313
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellRendererComponentDirective, isStandalone: true, selector: "[oneCellRendererComponent]", inputs: { cellRendererComponent: "cellRendererComponent", params: "params" }, usesOnChanges: true, ngImport: i0 }); }
|
|
3422
4314
|
}
|
|
3423
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type:
|
|
3424
|
-
type:
|
|
3425
|
-
args: [{
|
|
3426
|
-
|
|
4315
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellRendererComponentDirective, decorators: [{
|
|
4316
|
+
type: Directive,
|
|
4317
|
+
args: [{
|
|
4318
|
+
standalone: true,
|
|
4319
|
+
selector: '[oneCellRendererComponent]'
|
|
4320
|
+
}]
|
|
4321
|
+
}], ctorParameters: () => [{ type: i0.ViewContainerRef }], propDecorators: { cellRendererComponent: [{
|
|
4322
|
+
type: Input,
|
|
4323
|
+
args: [{ required: true }]
|
|
4324
|
+
}], params: [{
|
|
3427
4325
|
type: Input,
|
|
3428
4326
|
args: [{ required: true }]
|
|
3429
|
-
}], suppressedReason: [{
|
|
3430
|
-
type: Input
|
|
3431
|
-
}], dragEnd: [{
|
|
3432
|
-
type: Output
|
|
3433
4327
|
}] } });
|
|
3434
4328
|
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
4329
|
+
/**
|
|
4330
|
+
* Reveals a truncated cell in full while the pointer rests on it.
|
|
4331
|
+
*
|
|
4332
|
+
* Presentation lives in the stylesheet, under `.one-cell-text-expanded`. It
|
|
4333
|
+
* used to be applied as inline styles here, one of which was
|
|
4334
|
+
* `background: var(--ins-card-bg)` — a token from the application this library
|
|
4335
|
+
* was extracted from, which no consumer defines. The expanded cell therefore
|
|
4336
|
+
* had a transparent background and its text overlapped the next column. The
|
|
4337
|
+
* inline rules also set `z-index` on a statically positioned element, where it
|
|
4338
|
+
* does nothing.
|
|
4339
|
+
*
|
|
4340
|
+
* Overflow is measured on hover rather than after every view check. The old
|
|
4341
|
+
* version measured on init and on every text change, and reading `scrollWidth`
|
|
4342
|
+
* forces a synchronous layout — several hundred of them per render for a
|
|
4343
|
+
* normal viewport, to answer a question only the hovered cell ever asks.
|
|
4344
|
+
*/
|
|
4345
|
+
class OneCellTextExpandDirective {
|
|
4346
|
+
static { this.EXPANDED_CLASS = 'one-cell-text-expanded'; }
|
|
4347
|
+
static { this.HOVER_DELAY_MS = 800; }
|
|
4348
|
+
constructor(cell, renderer) {
|
|
4349
|
+
this.cell = cell;
|
|
4350
|
+
this.renderer = renderer;
|
|
3441
4351
|
}
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
name: 'cellInvalid'
|
|
3450
|
-
}]
|
|
3451
|
-
}] });
|
|
3452
|
-
|
|
3453
|
-
class CellSelectedPipe {
|
|
3454
|
-
transform(selectedCells, rowId, field) {
|
|
3455
|
-
if (!selectedCells)
|
|
3456
|
-
return false;
|
|
3457
|
-
return selectedCells.has(getCellKey(rowId, field));
|
|
4352
|
+
onMouseEnter() {
|
|
4353
|
+
const element = this.cell.nativeElement;
|
|
4354
|
+
// Measured here, so the layout read happens once for the cell being
|
|
4355
|
+
// hovered instead of for every cell on screen.
|
|
4356
|
+
if (element.scrollWidth <= element.clientWidth)
|
|
4357
|
+
return;
|
|
4358
|
+
this.timer = setTimeout(() => this.expand(), OneCellTextExpandDirective.HOVER_DELAY_MS);
|
|
3458
4359
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
4360
|
+
onMouseLeave() {
|
|
4361
|
+
this.collapse();
|
|
4362
|
+
}
|
|
4363
|
+
ngOnDestroy() {
|
|
4364
|
+
this.collapse();
|
|
4365
|
+
}
|
|
4366
|
+
expand() {
|
|
4367
|
+
this.renderer.addClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
|
|
4368
|
+
}
|
|
4369
|
+
collapse() {
|
|
4370
|
+
clearTimeout(this.timer);
|
|
4371
|
+
this.timer = undefined;
|
|
4372
|
+
this.renderer.removeClass(this.cell.nativeElement, OneCellTextExpandDirective.EXPANDED_CLASS);
|
|
4373
|
+
}
|
|
4374
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4375
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: OneCellTextExpandDirective, isStandalone: true, selector: "[oneCellTextExpand]", host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, ngImport: i0 }); }
|
|
3461
4376
|
}
|
|
3462
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type:
|
|
3463
|
-
type:
|
|
4377
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneCellTextExpandDirective, decorators: [{
|
|
4378
|
+
type: Directive,
|
|
3464
4379
|
args: [{
|
|
3465
|
-
standalone:
|
|
3466
|
-
|
|
4380
|
+
standalone: true,
|
|
4381
|
+
selector: '[oneCellTextExpand]'
|
|
3467
4382
|
}]
|
|
3468
|
-
}] }
|
|
4383
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { onMouseEnter: [{
|
|
4384
|
+
type: HostListener,
|
|
4385
|
+
args: ['mouseenter']
|
|
4386
|
+
}], onMouseLeave: [{
|
|
4387
|
+
type: HostListener,
|
|
4388
|
+
args: ['mouseleave']
|
|
4389
|
+
}] } });
|
|
3469
4390
|
|
|
3470
4391
|
class CellActionButtonEnablePipe {
|
|
3471
4392
|
transform(params) {
|
|
@@ -3476,16 +4397,34 @@ class CellActionButtonEnablePipe {
|
|
|
3476
4397
|
return statusValues.some(status => status === 1);
|
|
3477
4398
|
}
|
|
3478
4399
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3479
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }); }
|
|
4400
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, isStandalone: true, name: "cellActionButtonEnable" }); }
|
|
3480
4401
|
}
|
|
3481
4402
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellActionButtonEnablePipe, decorators: [{
|
|
3482
4403
|
type: Pipe,
|
|
3483
4404
|
args: [{
|
|
3484
|
-
standalone:
|
|
4405
|
+
standalone: true,
|
|
3485
4406
|
name: 'cellActionButtonEnable'
|
|
3486
4407
|
}]
|
|
3487
4408
|
}] });
|
|
3488
4409
|
|
|
4410
|
+
class CellInvalidPipe {
|
|
4411
|
+
transform(cellStates, value, column, node, allowedEdit, submitted) {
|
|
4412
|
+
if (!column.validationRules?.length) {
|
|
4413
|
+
return false;
|
|
4414
|
+
}
|
|
4415
|
+
return getCellError(column, node, value, node.data) !== null && !(node.editing[column.field]) && !(column?.cellRendererParams?.validateSelectedRowOnly && !node?.selected) && allowedEdit && submitted;
|
|
4416
|
+
}
|
|
4417
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
4418
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, isStandalone: true, name: "cellInvalid" }); }
|
|
4419
|
+
}
|
|
4420
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellInvalidPipe, decorators: [{
|
|
4421
|
+
type: Pipe,
|
|
4422
|
+
args: [{
|
|
4423
|
+
standalone: true,
|
|
4424
|
+
name: 'cellInvalid'
|
|
4425
|
+
}]
|
|
4426
|
+
}] });
|
|
4427
|
+
|
|
3489
4428
|
class CellKeyPipe {
|
|
3490
4429
|
transform(rowId, field) {
|
|
3491
4430
|
if (!rowId || !field) {
|
|
@@ -3494,12 +4433,12 @@ class CellKeyPipe {
|
|
|
3494
4433
|
return getCellKey(rowId, field);
|
|
3495
4434
|
}
|
|
3496
4435
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3497
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, name: "cellKey" }); }
|
|
4436
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, isStandalone: true, name: "cellKey" }); }
|
|
3498
4437
|
}
|
|
3499
4438
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellKeyPipe, decorators: [{
|
|
3500
4439
|
type: Pipe,
|
|
3501
4440
|
args: [{
|
|
3502
|
-
standalone:
|
|
4441
|
+
standalone: true,
|
|
3503
4442
|
name: 'cellKey'
|
|
3504
4443
|
}]
|
|
3505
4444
|
}] });
|
|
@@ -3510,16 +4449,33 @@ class CellLevelPaddingPipe {
|
|
|
3510
4449
|
return level * factor;
|
|
3511
4450
|
}
|
|
3512
4451
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3513
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, name: "cellLevelPadding" }); }
|
|
4452
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, isStandalone: true, name: "cellLevelPadding" }); }
|
|
3514
4453
|
}
|
|
3515
4454
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellLevelPaddingPipe, decorators: [{
|
|
3516
4455
|
type: Pipe,
|
|
3517
4456
|
args: [{
|
|
3518
|
-
standalone:
|
|
4457
|
+
standalone: true,
|
|
3519
4458
|
name: 'cellLevelPadding'
|
|
3520
4459
|
}]
|
|
3521
4460
|
}] });
|
|
3522
4461
|
|
|
4462
|
+
class CellSelectedPipe {
|
|
4463
|
+
transform(selectedCells, rowId, field) {
|
|
4464
|
+
if (!selectedCells)
|
|
4465
|
+
return false;
|
|
4466
|
+
return selectedCells.has(getCellKey(rowId, field));
|
|
4467
|
+
}
|
|
4468
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
4469
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, isStandalone: true, name: "cellSelected" }); }
|
|
4470
|
+
}
|
|
4471
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellSelectedPipe, decorators: [{
|
|
4472
|
+
type: Pipe,
|
|
4473
|
+
args: [{
|
|
4474
|
+
standalone: true,
|
|
4475
|
+
name: 'cellSelected'
|
|
4476
|
+
}]
|
|
4477
|
+
}] });
|
|
4478
|
+
|
|
3523
4479
|
class CellStateRendererPipe {
|
|
3524
4480
|
transform(cellStates, field) {
|
|
3525
4481
|
const value = cellStates?.[field]?.value;
|
|
@@ -3532,12 +4488,12 @@ class CellStateRendererPipe {
|
|
|
3532
4488
|
return value;
|
|
3533
4489
|
}
|
|
3534
4490
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3535
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, name: "cellStateRenderer" }); }
|
|
4491
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, isStandalone: true, name: "cellStateRenderer" }); }
|
|
3536
4492
|
}
|
|
3537
4493
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateRendererPipe, decorators: [{
|
|
3538
4494
|
type: Pipe,
|
|
3539
4495
|
args: [{
|
|
3540
|
-
standalone:
|
|
4496
|
+
standalone: true,
|
|
3541
4497
|
name: 'cellStateRenderer'
|
|
3542
4498
|
}]
|
|
3543
4499
|
}] });
|
|
@@ -3554,6 +4510,12 @@ class OneGridBodyComponent {
|
|
|
3554
4510
|
this.allowedEdit = true;
|
|
3555
4511
|
this.enableGroupColumnDefs = false;
|
|
3556
4512
|
this.submitted = false;
|
|
4513
|
+
/**
|
|
4514
|
+
* `'fullRow'`: a double-click opens every editable cell of the row, Enter or
|
|
4515
|
+
* focus leaving the row commits them together, Escape closes them all
|
|
4516
|
+
* without committing. Default: one cell at a time.
|
|
4517
|
+
*/
|
|
4518
|
+
this.editType = null;
|
|
3557
4519
|
this.context = null;
|
|
3558
4520
|
/**
|
|
3559
4521
|
* Pinned rows are bound only so the component re-renders when they change;
|
|
@@ -3654,6 +4616,22 @@ class OneGridBodyComponent {
|
|
|
3654
4616
|
* MAX_BUFFER_PX keeps roughly twenty rows of overscan either side instead.
|
|
3655
4617
|
*/
|
|
3656
4618
|
this.MAX_BUFFER_PX = 600;
|
|
4619
|
+
// ---------------------------------------------------------------------------
|
|
4620
|
+
// Cell navigation
|
|
4621
|
+
//
|
|
4622
|
+
// The WAI-ARIA grid pattern: the whole grid is one tab stop, and the arrows
|
|
4623
|
+
// move a focused cell within it. Before this the only tab stop was the scroll
|
|
4624
|
+
// viewport, so a keyboard user could scroll the rows and reach nothing in
|
|
4625
|
+
// them — and a screen reader had no cursor to read a cell from.
|
|
4626
|
+
//
|
|
4627
|
+
// Focus is real DOM focus on a roving tabindex rather than
|
|
4628
|
+
// `aria-activedescendant`, because the rows are virtualised: the element a
|
|
4629
|
+
// descendant id pointed at would keep being destroyed underneath it.
|
|
4630
|
+
// ---------------------------------------------------------------------------
|
|
4631
|
+
/** The cell holding the grid's tab stop. Null until something is focused. */
|
|
4632
|
+
this.activeCell = null;
|
|
4633
|
+
/** The row a shift-extended selection is measured from. */
|
|
4634
|
+
this.selectionAnchorRowId = null;
|
|
3657
4635
|
/**
|
|
3658
4636
|
* Where to paint the drop indicator, in pixels from the top of the body.
|
|
3659
4637
|
*
|
|
@@ -3709,13 +4687,20 @@ class OneGridBodyComponent {
|
|
|
3709
4687
|
this.columnDefs = [...payload.columns];
|
|
3710
4688
|
this.cellRenderer = this.columnDefs.some(col => col.cellRendererComponent);
|
|
3711
4689
|
this.rowWidth = this.columnDefs.reduce((sum, col) => sum + resolveColumnWidth(col), 0);
|
|
4690
|
+
// A column change can alter the width left for flex columns (a hide, a
|
|
4691
|
+
// resize, new defs). Re-flexing only dispatches when a width actually
|
|
4692
|
+
// changes, so this cannot ping-pong.
|
|
4693
|
+
this.applyFlexWidths();
|
|
3712
4694
|
this.cdr.markForCheck();
|
|
3713
4695
|
}));
|
|
3714
4696
|
/**
|
|
3715
4697
|
* Listen Row Data Changed Event
|
|
3716
4698
|
*/
|
|
3717
|
-
this.subscriptions.add(this.eventService.addEventListener('rowDataChanged', ({ nodes }) => {
|
|
3718
|
-
|
|
4699
|
+
this.subscriptions.add(this.eventService.addEventListener('rowDataChanged', ({ nodes: allNodes }) => {
|
|
4700
|
+
// The full post-filter count, reported before the page slice is taken,
|
|
4701
|
+
// so the host's paging totals see everything.
|
|
4702
|
+
this.rowCountChanged.emit(allNodes?.length ?? 0);
|
|
4703
|
+
const nodes = this.api.pageNodes(allNodes);
|
|
3719
4704
|
setTimeout(() => {
|
|
3720
4705
|
this.viewport?.checkViewportSize();
|
|
3721
4706
|
this.setVirtualScroll(nodes?.length);
|
|
@@ -3755,7 +4740,7 @@ class OneGridBodyComponent {
|
|
|
3755
4740
|
* Listen Sort Changed Event
|
|
3756
4741
|
*/
|
|
3757
4742
|
this.subscriptions.add(this.eventService.addEventListener('sortChanged', ({ nodes }) => {
|
|
3758
|
-
this.nodes = nodes;
|
|
4743
|
+
this.nodes = this.api.pageNodes(nodes);
|
|
3759
4744
|
// Sorting takes ownership of the order away from the user, so the handles
|
|
3760
4745
|
// have to disable in the same frame the rows re-sort.
|
|
3761
4746
|
this.refreshRowDragSuppressed();
|
|
@@ -3769,7 +4754,7 @@ class OneGridBodyComponent {
|
|
|
3769
4754
|
* for a move. A reorder is the same nodes in a different order.
|
|
3770
4755
|
*/
|
|
3771
4756
|
this.subscriptions.add(this.eventService.addEventListener('rowOrderChanged', ({ nodes }) => {
|
|
3772
|
-
this.nodes = [...nodes];
|
|
4757
|
+
this.nodes = this.api.pageNodes([...nodes]);
|
|
3773
4758
|
this.cdr.markForCheck();
|
|
3774
4759
|
}));
|
|
3775
4760
|
/**
|
|
@@ -4087,13 +5072,28 @@ class OneGridBodyComponent {
|
|
|
4087
5072
|
}
|
|
4088
5073
|
let rowNode = this.api.getRowNodeById(node.rowId);
|
|
4089
5074
|
if (rowNode) {
|
|
4090
|
-
|
|
5075
|
+
if (this.editType === 'fullRow') {
|
|
5076
|
+
// The whole row edits together, so every cell that would accept its
|
|
5077
|
+
// own double-click opens now. The clicked one still takes focus below.
|
|
5078
|
+
for (const col of this.columnDefs) {
|
|
5079
|
+
if (!col.field)
|
|
5080
|
+
continue;
|
|
5081
|
+
if (this.cellEditablePipe.transform(rowNode.cellStates, col, rowNode, this.cellService.editableFn, this.cellService.updatableFn, this.allowedEdit)) {
|
|
5082
|
+
rowNode.editing[col.field] = true;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
else {
|
|
5087
|
+
rowNode.editing[column.field] = true;
|
|
5088
|
+
}
|
|
4091
5089
|
setTimeout(() => {
|
|
4092
|
-
const input = document.querySelector(`[data-cell-id="${getCellKey(node.rowId, column.field)}"] input`);
|
|
5090
|
+
const input = document.querySelector(`[data-cell-id="${getCellKey(node.rowId, column.field)}"] input, [data-cell-id="${getCellKey(node.rowId, column.field)}"] select`);
|
|
4093
5091
|
if (input) {
|
|
4094
5092
|
input.focus();
|
|
4095
5093
|
const value = input.value;
|
|
4096
|
-
|
|
5094
|
+
// A select reports type 'select-one', so only a text input gets its
|
|
5095
|
+
// caret placed.
|
|
5096
|
+
if (value && ["text"].includes(input.type) && 'setSelectionRange' in input) {
|
|
4097
5097
|
input.setSelectionRange(value.length, value.length);
|
|
4098
5098
|
}
|
|
4099
5099
|
}
|
|
@@ -4103,19 +5103,72 @@ class OneGridBodyComponent {
|
|
|
4103
5103
|
/**
|
|
4104
5104
|
* Save Cell Edit Mode
|
|
4105
5105
|
*/
|
|
4106
|
-
stopEdit(node, column, event) {
|
|
5106
|
+
stopEdit(node, column, event) {
|
|
5107
|
+
if (this.editType === 'fullRow') {
|
|
5108
|
+
this.stopRowEditFromEvent(node, event);
|
|
5109
|
+
return;
|
|
5110
|
+
}
|
|
5111
|
+
let rowNode = this.api.getRowNodeById(node.rowId);
|
|
5112
|
+
// The editing guard makes a second commit for one edit a no-op: a select
|
|
5113
|
+
// commits on (change), which closes the editor, and the (blur) that can
|
|
5114
|
+
// follow must not commit again.
|
|
5115
|
+
if (rowNode && rowNode.editing[column.field]) {
|
|
5116
|
+
event.preventDefault();
|
|
5117
|
+
event.stopPropagation();
|
|
5118
|
+
const value = event.target.value ?? '';
|
|
5119
|
+
const oldValue = rowNode.data[column.field];
|
|
5120
|
+
this.setCellValueByRowId(node.rowId, column, value);
|
|
5121
|
+
this.finishEdit(rowNode, column, oldValue);
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
/**
|
|
5125
|
+
* Save a `select` editor's choice.
|
|
5126
|
+
*
|
|
5127
|
+
* Separate from `stopEdit` because the value is not the element's string:
|
|
5128
|
+
* the chosen index maps back into `selectOptions`, so the committed value
|
|
5129
|
+
* keeps whatever type the option declared.
|
|
5130
|
+
*/
|
|
5131
|
+
stopEditSelect(node, column, event) {
|
|
5132
|
+
if (this.editType === 'fullRow') {
|
|
5133
|
+
// (change) is not a commit in full-row mode — the choice sits in the
|
|
5134
|
+
// select until the row commits and reads every editor at once. Only a
|
|
5135
|
+
// blur that actually leaves the row closes it.
|
|
5136
|
+
if (event.type === 'blur')
|
|
5137
|
+
this.stopRowEditFromEvent(node, event);
|
|
5138
|
+
return;
|
|
5139
|
+
}
|
|
4107
5140
|
let rowNode = this.api.getRowNodeById(node.rowId);
|
|
4108
|
-
if (rowNode) {
|
|
4109
|
-
event.preventDefault();
|
|
5141
|
+
if (rowNode && rowNode.editing[column.field]) {
|
|
4110
5142
|
event.stopPropagation();
|
|
4111
|
-
const
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
5143
|
+
const options = column?.cellRendererParams?.selectOptions ?? [];
|
|
5144
|
+
const option = options[event.target.selectedIndex];
|
|
5145
|
+
if (!option)
|
|
5146
|
+
return;
|
|
5147
|
+
const oldValue = rowNode.data[column.field];
|
|
5148
|
+
rowNode.data[column.field] = option.value;
|
|
5149
|
+
this.finishEdit(rowNode, column, oldValue);
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
/** Close the editor and announce the commit. Shared by both editor kinds. */
|
|
5153
|
+
finishEdit(rowNode, column, oldValue) {
|
|
5154
|
+
const newValue = rowNode.data[column.field];
|
|
5155
|
+
rowNode.cellStates = this.cellService.generateCellStates(rowNode);
|
|
5156
|
+
rowNode.editing[column.field] = false;
|
|
5157
|
+
this.eventService.dispatchEvent('cellStateChanged', {
|
|
5158
|
+
data: rowNode.data,
|
|
5159
|
+
node: rowNode,
|
|
5160
|
+
column: column
|
|
5161
|
+
});
|
|
5162
|
+
// Compared after normalisation ('' -> null, number coercion), so a blur
|
|
5163
|
+
// that leaves the value as it was does not announce an edit.
|
|
5164
|
+
if (newValue !== oldValue) {
|
|
5165
|
+
this.eventService.dispatchEvent('cellValueChanged', {
|
|
4117
5166
|
node: rowNode,
|
|
4118
|
-
|
|
5167
|
+
data: rowNode.data,
|
|
5168
|
+
column: column,
|
|
5169
|
+
field: column.field,
|
|
5170
|
+
oldValue,
|
|
5171
|
+
newValue,
|
|
4119
5172
|
});
|
|
4120
5173
|
}
|
|
4121
5174
|
}
|
|
@@ -4125,9 +5178,63 @@ class OneGridBodyComponent {
|
|
|
4125
5178
|
cancelEdit(node, column) {
|
|
4126
5179
|
let rowNode = this.api.getRowNodeById(node.rowId);
|
|
4127
5180
|
if (rowNode) {
|
|
5181
|
+
if (this.editType === 'fullRow') {
|
|
5182
|
+
// Escape abandons the whole row: every editor closes, nothing commits.
|
|
5183
|
+
Object.keys(rowNode.editing).forEach(field => rowNode.editing[field] = false);
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
4128
5186
|
rowNode.editing[column.field] = false;
|
|
4129
5187
|
}
|
|
4130
5188
|
}
|
|
5189
|
+
/**
|
|
5190
|
+
* Full-row commit, routed from any one editor's Enter or blur.
|
|
5191
|
+
*
|
|
5192
|
+
* A blur that lands on another editor in the same row is the user tabbing
|
|
5193
|
+
* between fields, not leaving the row — the editors stay open and their
|
|
5194
|
+
* typed values stay in the DOM until the row commits.
|
|
5195
|
+
*/
|
|
5196
|
+
stopRowEditFromEvent(node, event) {
|
|
5197
|
+
if (event.type === 'blur') {
|
|
5198
|
+
const next = event.relatedTarget;
|
|
5199
|
+
if (next?.matches?.('input.one-cell-input, select.one-cell-input')
|
|
5200
|
+
&& next.closest?.(`[data-cell-id^="${node.rowId}--"]`)) {
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
event.preventDefault?.();
|
|
5205
|
+
event.stopPropagation?.();
|
|
5206
|
+
this.stopRowEdit(node);
|
|
5207
|
+
}
|
|
5208
|
+
/** Commit every open editor of one row, reading each from the DOM. */
|
|
5209
|
+
stopRowEdit(node) {
|
|
5210
|
+
const rowNode = this.api.getRowNodeById(node.rowId);
|
|
5211
|
+
if (!rowNode)
|
|
5212
|
+
return;
|
|
5213
|
+
for (const column of this.columnDefs) {
|
|
5214
|
+
const field = column.field;
|
|
5215
|
+
if (!field || !rowNode.editing[field])
|
|
5216
|
+
continue;
|
|
5217
|
+
const key = getCellKey(rowNode.rowId, field);
|
|
5218
|
+
const editor = document.querySelector(`[data-cell-id="${key}"] input, [data-cell-id="${key}"] select`);
|
|
5219
|
+
const oldValue = rowNode.data[field];
|
|
5220
|
+
if (editor?.tagName === 'SELECT') {
|
|
5221
|
+
const options = column.cellRendererParams?.selectOptions ?? [];
|
|
5222
|
+
const option = options[editor.selectedIndex];
|
|
5223
|
+
if (option)
|
|
5224
|
+
rowNode.data[field] = option.value;
|
|
5225
|
+
}
|
|
5226
|
+
else if (editor) {
|
|
5227
|
+
const raw = editor.value ?? '';
|
|
5228
|
+
rowNode.data[field] = raw === ''
|
|
5229
|
+
? null
|
|
5230
|
+
: column.cellRendererParams?.inputType === 'number' ? Number(raw) : raw;
|
|
5231
|
+
}
|
|
5232
|
+
// finishEdit closes the editor, refreshes the cell state and raises
|
|
5233
|
+
// cellValueChanged when the value moved — one event per changed cell,
|
|
5234
|
+
// the same contract as single-cell editing.
|
|
5235
|
+
this.finishEdit(rowNode, column, oldValue);
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
4131
5238
|
/**
|
|
4132
5239
|
* Set Cell Value
|
|
4133
5240
|
*/
|
|
@@ -4173,6 +5280,24 @@ class OneGridBodyComponent {
|
|
|
4173
5280
|
},
|
|
4174
5281
|
});
|
|
4175
5282
|
this.refreshRowDragSuppressed();
|
|
5283
|
+
// The CDK inserts its own content wrapper between the viewport and the
|
|
5284
|
+
// rows. It has no template of ours to annotate, and left generic it breaks
|
|
5285
|
+
// the `grid > row` ownership the roles depend on.
|
|
5286
|
+
const wrapper = element.querySelector('.cdk-virtual-scroll-content-wrapper');
|
|
5287
|
+
wrapper?.setAttribute('role', 'presentation');
|
|
5288
|
+
// Flex columns size against this viewport, now and whenever it resizes.
|
|
5289
|
+
// applyFlexWidths is a no-op without flex columns and dispatches only on a
|
|
5290
|
+
// real change, so grids that never use flex pay nothing here.
|
|
5291
|
+
this.applyFlexWidths();
|
|
5292
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
5293
|
+
this.flexResizeObserver = new ResizeObserver(() => this.applyFlexWidths());
|
|
5294
|
+
this.flexResizeObserver.observe(element);
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
applyFlexWidths() {
|
|
5298
|
+
const width = this.viewport?.elementRef?.nativeElement?.clientWidth ?? 0;
|
|
5299
|
+
if (width > 0)
|
|
5300
|
+
this.api.applyFlexWidths(width);
|
|
4176
5301
|
}
|
|
4177
5302
|
/**
|
|
4178
5303
|
* The params every per-cell callback receives.
|
|
@@ -4196,6 +5321,226 @@ class OneGridBodyComponent {
|
|
|
4196
5321
|
rendererParams(node, column) {
|
|
4197
5322
|
return { ...(column?.cellRendererParams ?? {}), ...this.cellParams(node, column) };
|
|
4198
5323
|
}
|
|
5324
|
+
/**
|
|
5325
|
+
* `aria-rowindex` is 1-based over the whole grid, not over what is rendered.
|
|
5326
|
+
*
|
|
5327
|
+
* That is the point of it: the body virtualises, so the DOM holds a sliding
|
|
5328
|
+
* window of rows and the index is how a screen reader knows where the window
|
|
5329
|
+
* sits. Row 1 is the header, then any pinned-top rows, then the data.
|
|
5330
|
+
*/
|
|
5331
|
+
dataRowAriaIndex(node) {
|
|
5332
|
+
return 2 + this.pinnedTopNodes.length + node.rowIndex;
|
|
5333
|
+
}
|
|
5334
|
+
/** Pinned rows bracket the data: top ones follow the header, bottom ones end the grid. */
|
|
5335
|
+
pinnedRowAriaIndex(node, side) {
|
|
5336
|
+
const index = (side === 'top' ? this.pinnedTopNodes : this.pinnedBottomNodes).indexOf(node);
|
|
5337
|
+
return side === 'top'
|
|
5338
|
+
? 2 + index
|
|
5339
|
+
: 2 + this.pinnedTopNodes.length + this.nodes.length + index;
|
|
5340
|
+
}
|
|
5341
|
+
/**
|
|
5342
|
+
* Whether rows can be selected at all.
|
|
5343
|
+
*
|
|
5344
|
+
* `aria-selected="false"` on every row of a grid nobody can select announces
|
|
5345
|
+
* a selection affordance that does not exist, so the attribute is omitted
|
|
5346
|
+
* rather than set false.
|
|
5347
|
+
*/
|
|
5348
|
+
get rowSelectionEnabled() {
|
|
5349
|
+
return !!this.api?.rowSelection;
|
|
5350
|
+
}
|
|
5351
|
+
isActiveCell(node, column) {
|
|
5352
|
+
if (this.activeCell) {
|
|
5353
|
+
return this.activeCell.rowId === node.rowId && this.activeCell.field === column.field;
|
|
5354
|
+
}
|
|
5355
|
+
// Nothing focused yet, so the first cell holds the tab stop. Without this
|
|
5356
|
+
// fallback a grid nobody has clicked has *no* tabbable cell, and a keyboard
|
|
5357
|
+
// user cannot get into it at all — the roving tabindex has to start
|
|
5358
|
+
// somewhere.
|
|
5359
|
+
return node === this.nodes[0] && column === this.navigableColumns()[0];
|
|
5360
|
+
}
|
|
5361
|
+
/**
|
|
5362
|
+
* Adopt whatever the user focused, however they got there — click, tab, or
|
|
5363
|
+
* the arrow keys below. Keeping this in one place is what stops the roving
|
|
5364
|
+
* tabindex drifting away from the element that actually has focus.
|
|
5365
|
+
*/
|
|
5366
|
+
onCellFocus(node, column) {
|
|
5367
|
+
if (this.isActiveCell(node, column))
|
|
5368
|
+
return;
|
|
5369
|
+
this.activeCell = { rowId: node.rowId, field: column.field };
|
|
5370
|
+
this.cdr.markForCheck();
|
|
5371
|
+
}
|
|
5372
|
+
onGridKeydown(event) {
|
|
5373
|
+
// Undo/redo of committed edits, anywhere in the grid except inside an
|
|
5374
|
+
// open editor — a text input's Ctrl+Z is the browser's, and it operates
|
|
5375
|
+
// on the uncommitted text.
|
|
5376
|
+
if ((event.ctrlKey || event.metaKey) && ['z', 'y'].includes(event.key.toLowerCase())) {
|
|
5377
|
+
const tag = event.target?.tagName?.toLowerCase();
|
|
5378
|
+
if (tag === 'input' || tag === 'select' || tag === 'textarea')
|
|
5379
|
+
return;
|
|
5380
|
+
event.preventDefault();
|
|
5381
|
+
const redo = event.key.toLowerCase() === 'y' || event.shiftKey;
|
|
5382
|
+
const applied = redo ? this.api.redoCellEditing() : this.api.undoCellEditing();
|
|
5383
|
+
if (applied)
|
|
5384
|
+
this.cdr.markForCheck();
|
|
5385
|
+
return;
|
|
5386
|
+
}
|
|
5387
|
+
const columns = this.navigableColumns();
|
|
5388
|
+
if (!this.nodes.length || !columns.length)
|
|
5389
|
+
return;
|
|
5390
|
+
// Navigate only when a cell itself holds focus. Anything focusable *inside*
|
|
5391
|
+
// a cell owns its own keys — a text editor's caret, the row drag handle's
|
|
5392
|
+
// reorder keys, the option menu's arrows — and all of them bubble to here.
|
|
5393
|
+
// Checking the target rather than blocklisting tag names is what keeps this
|
|
5394
|
+
// correct as more controls are added to cells.
|
|
5395
|
+
const target = event.target;
|
|
5396
|
+
if (!target?.matches?.('[role="gridcell"]'))
|
|
5397
|
+
return;
|
|
5398
|
+
const current = this.activeCellPosition(columns);
|
|
5399
|
+
let row = current.row;
|
|
5400
|
+
let col = current.col;
|
|
5401
|
+
switch (event.key) {
|
|
5402
|
+
case 'ArrowDown':
|
|
5403
|
+
row += 1;
|
|
5404
|
+
break;
|
|
5405
|
+
case 'ArrowUp':
|
|
5406
|
+
row -= 1;
|
|
5407
|
+
break;
|
|
5408
|
+
case 'ArrowRight':
|
|
5409
|
+
col += 1;
|
|
5410
|
+
break;
|
|
5411
|
+
case 'ArrowLeft':
|
|
5412
|
+
col -= 1;
|
|
5413
|
+
break;
|
|
5414
|
+
// Selection. Space is the grid pattern's toggle, and it has to be caught
|
|
5415
|
+
// here or the browser scrolls the page with it.
|
|
5416
|
+
case ' ': {
|
|
5417
|
+
const node = this.nodes[current.row];
|
|
5418
|
+
if (node) {
|
|
5419
|
+
event.preventDefault();
|
|
5420
|
+
this.toggleRowSelection(node);
|
|
5421
|
+
}
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
case 'a':
|
|
5425
|
+
case 'A':
|
|
5426
|
+
if (!event.ctrlKey && !event.metaKey)
|
|
5427
|
+
return;
|
|
5428
|
+
event.preventDefault();
|
|
5429
|
+
this.api.selectAll();
|
|
5430
|
+
this.cdr.markForCheck();
|
|
5431
|
+
return;
|
|
5432
|
+
case 'Home':
|
|
5433
|
+
col = 0;
|
|
5434
|
+
if (event.ctrlKey || event.metaKey)
|
|
5435
|
+
row = 0;
|
|
5436
|
+
break;
|
|
5437
|
+
case 'End':
|
|
5438
|
+
col = columns.length - 1;
|
|
5439
|
+
if (event.ctrlKey || event.metaKey)
|
|
5440
|
+
row = this.nodes.length - 1;
|
|
5441
|
+
break;
|
|
5442
|
+
// A page is what the viewport can show, which is the only definition that
|
|
5443
|
+
// matches what the user sees move.
|
|
5444
|
+
case 'PageDown':
|
|
5445
|
+
row += this.rowsPerPage();
|
|
5446
|
+
break;
|
|
5447
|
+
case 'PageUp':
|
|
5448
|
+
row -= this.rowsPerPage();
|
|
5449
|
+
break;
|
|
5450
|
+
case 'Enter':
|
|
5451
|
+
case 'F2': {
|
|
5452
|
+
const node = this.nodes[current.row];
|
|
5453
|
+
const column = columns[current.col];
|
|
5454
|
+
if (node && column) {
|
|
5455
|
+
event.preventDefault();
|
|
5456
|
+
this.startEdit({ target: event.target }, node, column);
|
|
5457
|
+
}
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
default: return;
|
|
5461
|
+
}
|
|
5462
|
+
event.preventDefault();
|
|
5463
|
+
const targetRow = Math.min(Math.max(row, 0), this.nodes.length - 1);
|
|
5464
|
+
this.focusCell(targetRow, Math.min(Math.max(col, 0), columns.length - 1));
|
|
5465
|
+
// Shift with a vertical move extends the selection, anchored where the
|
|
5466
|
+
// extension began — the same gesture a file list uses.
|
|
5467
|
+
if (event.shiftKey && targetRow !== current.row) {
|
|
5468
|
+
this.extendSelectionTo(current.row, targetRow);
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
toggleRowSelection(node) {
|
|
5472
|
+
if (!this.api.toggleRowSelection(node.rowId, !node.selected))
|
|
5473
|
+
return;
|
|
5474
|
+
// Where a subsequent shift-extension counts from.
|
|
5475
|
+
this.selectionAnchorRowId = node.rowId;
|
|
5476
|
+
this.cdr.markForCheck();
|
|
5477
|
+
}
|
|
5478
|
+
extendSelectionTo(fromRow, toRow) {
|
|
5479
|
+
const target = this.nodes[toRow];
|
|
5480
|
+
if (!target)
|
|
5481
|
+
return;
|
|
5482
|
+
// With nothing selected yet the row the extension started on becomes the
|
|
5483
|
+
// anchor, so shift+arrow selects both ends rather than only the far one.
|
|
5484
|
+
const anchor = this.selectionAnchorRowId ?? this.nodes[fromRow]?.rowId;
|
|
5485
|
+
if (!anchor)
|
|
5486
|
+
return;
|
|
5487
|
+
this.selectionAnchorRowId = anchor;
|
|
5488
|
+
this.api.selectRowRange(anchor, target.rowId);
|
|
5489
|
+
this.cdr.markForCheck();
|
|
5490
|
+
}
|
|
5491
|
+
/**
|
|
5492
|
+
* Columns a cell cursor can land on.
|
|
5493
|
+
*
|
|
5494
|
+
* A column with no field renders no cell state and is keyed by field, so it
|
|
5495
|
+
* cannot be addressed — an action-only column is a legitimate example.
|
|
5496
|
+
*/
|
|
5497
|
+
navigableColumns() {
|
|
5498
|
+
return this.columnDefs.filter(column => !!column?.field);
|
|
5499
|
+
}
|
|
5500
|
+
activeCellPosition(columns) {
|
|
5501
|
+
if (!this.activeCell)
|
|
5502
|
+
return { row: 0, col: 0 };
|
|
5503
|
+
const row = this.nodes.findIndex(node => node.rowId === this.activeCell.rowId);
|
|
5504
|
+
const col = columns.findIndex(column => column.field === this.activeCell.field);
|
|
5505
|
+
return { row: row < 0 ? 0 : row, col: col < 0 ? 0 : col };
|
|
5506
|
+
}
|
|
5507
|
+
rowsPerPage() {
|
|
5508
|
+
const height = this.viewport?.elementRef?.nativeElement?.clientHeight ?? 0;
|
|
5509
|
+
return Math.max(1, Math.floor(height / this.itemSize));
|
|
5510
|
+
}
|
|
5511
|
+
/**
|
|
5512
|
+
* Move the cursor, scrolling the row into view first when it is outside the
|
|
5513
|
+
* rendered window.
|
|
5514
|
+
*
|
|
5515
|
+
* The scroll is the reason focusing is deferred: a virtualised row that is
|
|
5516
|
+
* not on screen has no element to focus until the viewport has rendered it.
|
|
5517
|
+
*/
|
|
5518
|
+
focusCell(rowIndex, colIndex) {
|
|
5519
|
+
const node = this.nodes[rowIndex];
|
|
5520
|
+
const column = this.navigableColumns()[colIndex];
|
|
5521
|
+
if (!node || !column)
|
|
5522
|
+
return;
|
|
5523
|
+
this.activeCell = { rowId: node.rowId, field: column.field };
|
|
5524
|
+
this.cdr.markForCheck();
|
|
5525
|
+
const range = this.viewport?.getRenderedRange();
|
|
5526
|
+
const rendered = range && rowIndex >= range.start && rowIndex < range.end;
|
|
5527
|
+
if (rendered) {
|
|
5528
|
+
setTimeout(() => this.focusCellElement(node, column));
|
|
5529
|
+
return;
|
|
5530
|
+
}
|
|
5531
|
+
this.viewport?.scrollToIndex(rowIndex);
|
|
5532
|
+
// Scrolling is not enough: the CDK renders on its own schedule, so the row
|
|
5533
|
+
// has no element to focus until it has. Waiting for the rendered range to
|
|
5534
|
+
// actually contain the row is the only reliable signal — a fixed delay
|
|
5535
|
+
// races the viewport and silently drops focus when it loses.
|
|
5536
|
+
this.subscriptions.add(this.viewport?.renderedRangeStream
|
|
5537
|
+
.pipe(filter(next => rowIndex >= next.start && rowIndex < next.end), take(1))
|
|
5538
|
+
.subscribe(() => setTimeout(() => this.focusCellElement(node, column))));
|
|
5539
|
+
}
|
|
5540
|
+
focusCellElement(node, column) {
|
|
5541
|
+
const key = getCellKey(node.rowId, column.field);
|
|
5542
|
+
this.bodyRef?.nativeElement?.querySelector(`[data-cell-id="${key}"]`)?.focus();
|
|
5543
|
+
}
|
|
4199
5544
|
/** Is this the row currently being moved? */
|
|
4200
5545
|
isRowDragging(node) {
|
|
4201
5546
|
return this.dragState?.rowId === node.rowId;
|
|
@@ -4221,14 +5566,35 @@ class OneGridBodyComponent {
|
|
|
4221
5566
|
this.cdr.markForCheck();
|
|
4222
5567
|
}
|
|
4223
5568
|
ngOnDestroy() {
|
|
5569
|
+
this.flexResizeObserver?.disconnect();
|
|
4224
5570
|
this.subscriptions.unsubscribe();
|
|
4225
5571
|
}
|
|
4226
5572
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, deps: [{ token: OgColumnService }, { token: OneGridCellService }, { token: OneGridEventService }, { token: OneGridRowDragService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4227
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridBodyComponent, selector: "one-grid-body", inputs: { rowData: "rowData", rowNumber: "rowNumber", api: "api", pagination: "pagination", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", submitted: "submitted", context: "context", getRowId: "getRowId", gridCopy$: "gridCopy$", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailRowMaxHeight: "detailRowMaxHeight", detailCellRendererParams: "detailCellRendererParams", detailGridTemplate: "detailGridTemplate", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowCopy: "onRowCopy", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", expansionChanged: "expansionChanged", bodyScroll: "bodyScroll", cellOptionSelect: "cellOptionSelect", rowDragEnd: "rowDragEnd" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:mouseup": "onGlobalMouseUp()" } }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodyRef"], descendants: true, static: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "pinnedViewports", predicate: ["pinnedViewport"], descendants: true }], ngImport: i0, template: "<div #bodyRef class=\"one-grid-body\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n tabindex=\"0\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\" [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\">\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i6.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i6.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i6.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: ["node", "allowedEdit", "api"], outputs: ["onRowSelection"] }, { kind: "component", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: ["column", "node", "allowedEdit", "masterDetail", "masterNode", "cellRendererParams", "gridType", "context"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption"] }, { kind: "component", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: ["node", "masterDetail", "detailCellRendererParams", "detailRendererComponent"], outputs: ["expansionChanged"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "directive", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: ["cellRendererComponent", "params"] }, { kind: "directive", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]" }, { kind: "component", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: ["column", "node", "options"], outputs: ["cellOptionMenuSelect", "cellOptionMenuClose"] }, { kind: "component", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: ["node", "suppressedReason"], outputs: ["dragEnd"] }, { kind: "pipe", type: CellEditablePipe, name: "cellEditable" }, { kind: "pipe", type: CellInvalidPipe, name: "cellInvalid" }, { kind: "pipe", type: CellSelectedPipe, name: "cellSelected" }, { kind: "pipe", type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }, { kind: "pipe", type: CellKeyPipe, name: "cellKey" }, { kind: "pipe", type: CellLevelPaddingPipe, name: "cellLevelPadding" }, { kind: "pipe", type: CellStateRendererPipe, name: "cellStateRenderer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5573
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridBodyComponent, isStandalone: true, selector: "one-grid-body", inputs: { rowData: "rowData", rowNumber: "rowNumber", api: "api", pagination: "pagination", allowedEdit: "allowedEdit", enableGroupColumnDefs: "enableGroupColumnDefs", submitted: "submitted", editType: "editType", context: "context", getRowId: "getRowId", gridCopy$: "gridCopy$", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailRowMaxHeight: "detailRowMaxHeight", detailCellRendererParams: "detailCellRendererParams", detailGridTemplate: "detailGridTemplate", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded" }, outputs: { onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowCopy: "onRowCopy", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowCellOption: "onRowCellOption", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", expansionChanged: "expansionChanged", bodyScroll: "bodyScroll", cellOptionSelect: "cellOptionSelect", rowDragEnd: "rowDragEnd" }, host: { attributes: { "role": "presentation" }, listeners: { "document:click": "onDocumentClick($event)", "document:mouseup": "onGlobalMouseUp()" } }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodyRef"], descendants: true, static: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "pinnedViewports", predicate: ["pinnedViewport"], descendants: true }], ngImport: i0, template: "<div #bodyRef class=\"one-grid-body\" role=\"presentation\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes, side: 'top' }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n role=\"rowgroup\"\n [attr.tabindex]=\"nodes?.length ? null : 0\"\n (keydown)=\"onGridKeydown($event)\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"dataRowAriaIndex(node)\"\n [attr.aria-selected]=\"rowSelectionEnabled ? node.selected : null\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col, colIndex: $index }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes, side: 'bottom' }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\" let-side=\"side\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\"\n role=\"row\"\n [attr.aria-rowindex]=\"pinnedRowAriaIndex(node, side)\"\n [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\" let-colIndex=\"colIndex\">\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.tabindex]=\"isActiveCell(node, column) ? 0 : -1\"\n [class.one-cell-active]=\"isActiveCell(node, column)\"\n (focus)=\"onCellFocus(node, column)\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n @if (column?.cellRendererParams?.inputType === 'select') {\n <!-- Cell Editing Select. Commits on change \u2014 picking an option\n is the whole edit \u2014 with blur as the keyboard fallback. -->\n <select class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n (change)=\"stopEditSelect(node, column, $event)\"\n (blur)=\"stopEditSelect(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\">\n @for (option of column?.cellRendererParams?.selectOptions ?? []; track $index) {\n <option [selected]=\"option.value === node.data[column.field]\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i5.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i5.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i5.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: OneGridCheckboxComponent, selector: "one-grid-checkbox", inputs: ["node", "allowedEdit", "api"], outputs: ["onRowSelection"] }, { kind: "component", type: OneGridCellGroupRendererComponent, selector: "one-grid-cell-group-renderer", inputs: ["node", "masterDetail", "detailCellRendererParams", "detailRendererComponent"], outputs: ["expansionChanged"] }, { kind: "component", type: OneGridCellOptionMenuComponent, selector: "one-grid-cell-option-menu", inputs: ["column", "node", "options"], outputs: ["cellOptionMenuSelect", "cellOptionMenuClose"] }, { kind: "component", type: OneGridRowActionComponent, selector: "one-grid-row-action", inputs: ["column", "node", "allowedEdit", "masterDetail", "masterNode", "cellRendererParams", "gridType", "context"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption"] }, { kind: "component", type: OneGridRowDragHandleComponent, selector: "one-grid-row-drag-handle", inputs: ["node", "suppressedReason"], outputs: ["dragEnd"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }, { kind: "directive", type: OneCellRendererComponentDirective, selector: "[oneCellRendererComponent]", inputs: ["cellRendererComponent", "params"] }, { kind: "directive", type: OneCellTextExpandDirective, selector: "[oneCellTextExpand]" }, { kind: "pipe", type: CellActionButtonEnablePipe, name: "cellActionButtonEnable" }, { kind: "pipe", type: CellEditablePipe, name: "cellEditable" }, { kind: "pipe", type: CellInvalidPipe, name: "cellInvalid" }, { kind: "pipe", type: CellKeyPipe, name: "cellKey" }, { kind: "pipe", type: CellLevelPaddingPipe, name: "cellLevelPadding" }, { kind: "pipe", type: CellSelectedPipe, name: "cellSelected" }, { kind: "pipe", type: CellStateRendererPipe, name: "cellStateRenderer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4228
5574
|
}
|
|
4229
5575
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridBodyComponent, decorators: [{
|
|
4230
5576
|
type: Component,
|
|
4231
|
-
args: [{ standalone: false, selector: 'one-grid-body', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bodyRef class=\"one-grid-body\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n tabindex=\"0\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\" [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\">\n <div\n class=\"one-column\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n" }]
|
|
5577
|
+
args: [{ standalone: true, imports: [
|
|
5578
|
+
NgClass,
|
|
5579
|
+
NgStyle,
|
|
5580
|
+
NgTemplateOutlet,
|
|
5581
|
+
ScrollingModule,
|
|
5582
|
+
OneGridCheckboxComponent,
|
|
5583
|
+
OneGridCellGroupRendererComponent,
|
|
5584
|
+
OneGridCellOptionMenuComponent,
|
|
5585
|
+
OneGridRowActionComponent,
|
|
5586
|
+
OneGridRowDragHandleComponent,
|
|
5587
|
+
OneCellInputValidatorDirective,
|
|
5588
|
+
OneCellRendererComponentDirective,
|
|
5589
|
+
OneCellTextExpandDirective,
|
|
5590
|
+
CellActionButtonEnablePipe,
|
|
5591
|
+
CellEditablePipe,
|
|
5592
|
+
CellInvalidPipe,
|
|
5593
|
+
CellKeyPipe,
|
|
5594
|
+
CellLevelPaddingPipe,
|
|
5595
|
+
CellSelectedPipe,
|
|
5596
|
+
CellStateRendererPipe,
|
|
5597
|
+
], selector: 'one-grid-body', host: { 'role': 'presentation' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bodyRef class=\"one-grid-body\" role=\"presentation\">\n\n <!-- Pinned rows live outside the scroll viewport so they never scroll\n vertically, and mirror its scrollLeft so the columns stay aligned. -->\n @if (pinnedTopNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-top\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedTopNodes, side: 'top' }\"></ng-container>\n </div>\n }\n\n <!-- tabindex makes the scroll container reachable, so a keyboard user can\n scroll the rows with the arrow keys, Page Up/Down and Home/End. Without\n it the body is scrollable by pointer only (WCAG 2.1.1). -->\n <cdk-virtual-scroll-viewport\n class=\"one-grid-body-viewport\"\n role=\"rowgroup\"\n [attr.tabindex]=\"nodes?.length ? null : 0\"\n (keydown)=\"onGridKeydown($event)\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n (scroll)=\"onBodyScroll($event)\">\n\n <ng-container *cdkVirtualFor=\"let node of nodes; trackBy: trackByRowId;\">\n \n <div class=\"one-row\"\n role=\"row\"\n [attr.aria-rowindex]=\"dataRowAriaIndex(node)\"\n [attr.aria-selected]=\"rowSelectionEnabled ? node.selected : null\"\n [attr.data-row-id]=\"node.rowId\"\n [class.one-row-selected]=\"node.selected\"\n [class.one-row-dragging]=\"isRowDragging(node)\">\n @for (col of columnDefs; track col.field;) {\n <ng-container [ngTemplateOutlet]=\"cellTemplate\" [ngTemplateOutletContext]=\"{ node, rowIndex: node.rowIndex, column: col, colIndex: $index }\"></ng-container>\n }\n </div>\n\n <!-- Custom Detail Component -->\n @if(node.leaf && node.detailActive && node?.detailRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"node?.detailRendererComponent\"\n [params]=\"{ data: node.data, rowIndex: node.rowIndex, node: node, context: context }\">\n </ng-container>\n }\n\n <!-- Master Detail Grid (template provided by the parent one-grid to avoid a compile-time import cycle) -->\n @if (masterDetail && node.expanded && node?.detailRowData) {\n <div class=\"one-row-detail-container\" [attr.data-row-id]=\"node.rowId + '-detail'\">\n <ng-container\n [ngTemplateOutlet]=\"detailGridTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node, node: node }\">\n </ng-container>\n </div>\n }\n </ng-container>\n @if (!nodes?.length) {\n <div style=\"width: max-content; border-top: 1px solid transparent;\">\n <div [style.width.px]=\"rowWidth\"></div>\n </div>\n }\n </cdk-virtual-scroll-viewport>\n\n <!-- Drop indicator.\n A line at the insertion point rather than a gap opened between rows:\n the viewport is virtualised at a fixed itemSize, so shifting rows apart\n would desynchronise the scroller from its own geometry. It is also the\n clearer signal \u2014 the row lands exactly where the line is.\n Positioned against the body, with the viewport's scroll subtracted, so\n it stays put while the content moves under it during auto-scroll. -->\n @if (dropIndicatorTop !== null) {\n <div class=\"one-row-drop-indicator\" aria-hidden=\"true\"\n [style.top.px]=\"dropIndicatorTop\"\n [style.width.px]=\"dropIndicatorWidth\"></div>\n }\n\n <!-- Keyboard reordering has no drag image to watch, so it narrates instead.\n Assertive because the message is the only feedback that the row moved. -->\n <div class=\"one-grid-visually-hidden\" role=\"status\" aria-live=\"assertive\" aria-atomic=\"true\">{{ dragAnnouncement }}</div>\n @if (!nodes?.length) {\n <span class=\"one-grid-empty-message\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">No Rows to Show</span>\n }\n\n @if (pinnedBottomNodes.length) {\n <div #pinnedViewport class=\"one-pinned-rows one-pinned-rows-bottom\" role=\"rowgroup\">\n <ng-container [ngTemplateOutlet]=\"pinnedRows\" [ngTemplateOutletContext]=\"{ nodes: pinnedBottomNodes, side: 'bottom' }\"></ng-container>\n </div>\n }\n</div>\n\n<!-- Pinned rows are display-only: they render exactly what a data cell renders,\n but carry none of its interactions. A totals row should not open an editor\n on double-click or start a range selection on drag. -->\n<ng-template #pinnedRows let-nodes=\"nodes\" let-side=\"side\">\n @for (node of nodes; track node.rowId) {\n <div class=\"one-row one-row-pinned\"\n role=\"row\"\n [attr.aria-rowindex]=\"pinnedRowAriaIndex(node, side)\"\n [attr.data-row-id]=\"node.rowId\">\n @for (column of columnDefs; track column.field) {\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"$index + 1\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\">\n\n @if (column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px', position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\">\n <div class=\"one-cell-text\">{{ (node.cellStates | cellStateRenderer:column.field) }}</div>\n </div>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n\n<!-- Master Column Template -->\n<ng-template #cellTemplate let-node=\"node\" let-rowIndex=\"rowIndex\" let-column=\"column\" let-colIndex=\"colIndex\">\n <div\n class=\"one-column\"\n role=\"gridcell\"\n [attr.aria-colindex]=\"colIndex + 1\"\n [attr.tabindex]=\"isActiveCell(node, column) ? 0 : -1\"\n [class.one-cell-active]=\"isActiveCell(node, column)\"\n (focus)=\"onCellFocus(node, column)\"\n [attr.data-cell-id]=\"node.rowId | cellKey:column.field\"\n [class.one-column-pinned-left]=\"column.pinned === 'left'\"\n [class.one-column-pinned-right]=\"column.pinned === 'right'\"\n [class.one-column-pinned-edge]=\"column._pinnedEdge\"\n [style.left.px]=\"column.pinned === 'left' ? column._pinnedOffset : null\"\n [style.right.px]=\"column.pinned === 'right' ? column._pinnedOffset : null\"\n [style.width.px]=\"column?.width\"\n [style.min-width.px]=\"column?.minWidth\"\n [style.max-width.px]=\"column?.maxWidth\"\n [class.one-column-border-top]=\"masterDetail && nodes?.length > 0 && nodes[node.rowIndex - 1]?.expanded\"\n [class.one-cell-selected]=\"selectedCells | cellSelected:node.rowId:column.field\"\n [class.one-cell-invalid]=\"node.cellStates | cellInvalid:node.data[column.field]:column:node:allowedEdit:submitted\"\n (dblclick)=\"startEdit($event, node, column)\"\n (mousedown)=\"startSelection(node.rowId, column.field)\"\n (mouseenter)=\"extendSelection(node.rowId, column.field)\"\n (mouseup)=\"endSelection()\">\n\n <!-- One Grid Cell Option -->\n @if (node.cellOptions[column.field]) {\n <one-grid-cell-option-menu\n #contextOptionMenuRef\n [node]=\"node\"\n [column]=\"column\"\n [options]=\"column.cellOptionMenu\"\n (cellOptionMenuSelect)=\"cellOptionMenuSelected($event)\"\n (cellOptionMenuClose)=\"cellOptionMenuClosed()\"/>\n }\n\n <!-- Grid Cell Template -->\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else if (node.editing[column.field]) {\n @if (column?.cellRendererParams?.inputType === 'select') {\n <!-- Cell Editing Select. Commits on change \u2014 picking an option\n is the whole edit \u2014 with blur as the keyboard fallback. -->\n <select class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n (change)=\"stopEditSelect(node, column, $event)\"\n (blur)=\"stopEditSelect(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\">\n @for (option of column?.cellRendererParams?.selectOptions ?? []; track $index) {\n <option [selected]=\"option.value === node.data[column.field]\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <!-- Cell Editing Input -->\n <input class=\"one-cell-input\"\n name=\"{{ node.rowId + '' + column.field }}\"\n oneCellInputValidator\n [validationType]=\"column?.cellRendererParams?.inputType === 'number' ? 'number' : 'none'\"\n [placeholder]=\"column?.cellRendererParams?.placeholder\"\n [type]=\"column?.cellRendererParams?.inputType === 'number' ? 'text' : column?.cellRendererParams?.inputType\"\n [value]=\"node?.data[column?.field]\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(node, column, $event)\"\n (keydown.enter)=\"stopEdit(node, column, $event)\"\n (keydown.esc)=\"cancelEdit(node, column)\"\n />\n }\n }\n @else if (column?.cellRenderer) {\n <div\n [ngStyle]=\"{ width: column.width + 'px' , position: 'relative' }\"\n [innerHTML]=\"node.cellStates | cellStateRenderer:column.field\"\n [class.editable-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n </div>\n }\n @else {\n <div class=\"one-cell\"\n [class.align-content-left]=\"column?.alignContent === 'left'\"\n [class.align-content-center]=\"column?.alignContent === 'center'\"\n [class.align-content-right]=\"column?.alignContent === 'right'\"\n [ngClass]=\"colService.resolveCellClass(column, cellParams(node, column))\"\n [ngStyle]=\"colService.resolveCellStyle(column, cellParams(node, column))\"\n [style.padding-left.px]=\"column.autoGroupField && node.level | cellLevelPadding:24\"\n [class.one-cell-allowed-edit-active]=\"node.cellStates | cellEditable:column:node:cellService.editableFn:cellService.updatableFn:allowedEdit\">\n\n <!-- Row Drag Handle -->\n @if (cellService.rowDragFn(column, node)) {\n <one-grid-row-drag-handle\n [node]=\"node\"\n [suppressedReason]=\"rowDragSuppressed\"\n (dragEnd)=\"onRowDragEnd($event)\"\n />\n }\n\n <!-- Cell Checkbox Selection -->\n @if (column?.checkboxSelection) {\n <one-grid-checkbox\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [api]=\"api\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n />\n }\n\n <!-- Expand & Collapse Icon -->\n @if (((node?.group || detailRendererComponent) && column?.autoGroupField) || (masterDetail && node?.expandable && gridType === 'master' && column?.masterGroupField)) {\n <one-grid-cell-group-renderer\n [node]=\"node\"\n [masterDetail]=\"masterDetail\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n />\n }\n\n @if(column?.cellRendererComponent) {\n <ng-container\n oneCellRendererComponent\n [cellRendererComponent]=\"column?.cellRendererComponent\"\n [params]=\"rendererParams(node, column)\">\n </ng-container>\n }\n @else {\n <!-- Cell Value OR Placeholder -->\n <div oneCellTextExpand class=\"one-cell-text\" [class.one-cell-placeholder]=\"node.cellStates[column.field]?.type === 'placeholder'\">\n {{ (node.cellStates | cellStateRenderer:column.field) }}\n </div>\n }\n\n <!-- Cell Action Buttons -->\n @if (column.cellRendererParams | cellActionButtonEnable) {\n <one-grid-row-action\n [ngStyle]=\"{ marginLeft: 'auto' }\"\n [column]=\"column\"\n [node]=\"node\"\n [allowedEdit]=\"allowedEdit\"\n [masterNode]=\"masterNode\"\n [masterDetail]=\"masterDetail\"\n [gridType]=\"gridType\"\n [context]=\"context\"\n [cellRendererParams]=\"column?.cellRendererParams\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n />\n }\n </div>\n }\n </div>\n</ng-template>\n" }]
|
|
4232
5598
|
}], ctorParameters: () => [{ type: OgColumnService }, { type: OneGridCellService }, { type: OneGridEventService }, { type: OneGridRowDragService }, { type: i0.ChangeDetectorRef }], propDecorators: { bodyRef: [{
|
|
4233
5599
|
type: ViewChild,
|
|
4234
5600
|
args: ['bodyRef', { static: true }]
|
|
@@ -4247,6 +5613,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4247
5613
|
type: Input
|
|
4248
5614
|
}], submitted: [{
|
|
4249
5615
|
type: Input
|
|
5616
|
+
}], editType: [{
|
|
5617
|
+
type: Input
|
|
4250
5618
|
}], context: [{
|
|
4251
5619
|
type: Input
|
|
4252
5620
|
}], getRowId: [{
|
|
@@ -4412,11 +5780,11 @@ class OneGridPaginationComponent {
|
|
|
4412
5780
|
}
|
|
4413
5781
|
}
|
|
4414
5782
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4415
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: { paginate: "paginate" }, outputs: { goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, ngImport: i0, template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n <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: "
|
|
5783
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridPaginationComponent, isStandalone: true, selector: "one-grid-pagination", inputs: { paginate: "paginate" }, outputs: { goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [ONE_GRID_ICON_PROVIDERS], ngImport: i0, template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n <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: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] }); }
|
|
4416
5784
|
}
|
|
4417
5785
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridPaginationComponent, decorators: [{
|
|
4418
5786
|
type: Component,
|
|
4419
|
-
args: [{ standalone:
|
|
5787
|
+
args: [{ standalone: true, imports: [LucideAngularModule], selector: 'one-grid-pagination', providers: [ONE_GRID_ICON_PROVIDERS], template: "<!-- A navigation landmark, so the bar is reachable as one thing rather than as\n four unlabelled icon buttons floating under the grid. -->\n<div class=\"one-grid-pagination\" role=\"navigation\" aria-label=\"Pagination\">\n <div class=\"one-pagination-row\">\n @if (paginate?.firstRowIndex > 0 && paginate?.lastRowIndex > 0 && paginate?.total > 0) {\n <div class=\"one-pagination-columns-group\">\n <div class=\"one-pagination-column\">\n <span>{{ paginate?.firstRowIndex }}</span>\n <span>-</span>\n <span>{{ paginate?.lastRowIndex }}</span>\n <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" }]
|
|
4420
5788
|
}], propDecorators: { paginate: [{
|
|
4421
5789
|
type: Input
|
|
4422
5790
|
}], goToFirst: [{
|
|
@@ -4432,16 +5800,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4432
5800
|
}] } });
|
|
4433
5801
|
|
|
4434
5802
|
class OneGridComponent {
|
|
4435
|
-
|
|
5803
|
+
/**
|
|
5804
|
+
* Recompute the bar's numbers from the filtered row count and the API's
|
|
5805
|
+
* paging state. Deferred a tick for the same reason the aria counts are:
|
|
5806
|
+
* the count arrives while the first change detection pass is still running.
|
|
5807
|
+
*/
|
|
5808
|
+
refreshClientPagination(total) {
|
|
5809
|
+
const { pageSize, currentPage } = this.api.paginationState;
|
|
5810
|
+
const lastPage = Math.max(Math.ceil(total / pageSize), 1);
|
|
5811
|
+
const page = Math.min(currentPage, lastPage);
|
|
5812
|
+
setTimeout(() => {
|
|
5813
|
+
this.internalPaginate = {
|
|
5814
|
+
total,
|
|
5815
|
+
currentPage: page,
|
|
5816
|
+
lastPage,
|
|
5817
|
+
perPage: pageSize,
|
|
5818
|
+
firstRowIndex: total === 0 ? 0 : (page - 1) * pageSize + 1,
|
|
5819
|
+
lastRowIndex: Math.min(page * pageSize, total),
|
|
5820
|
+
pageSizeOptions: this.paginate?.pageSizeOptions,
|
|
5821
|
+
};
|
|
5822
|
+
this.cdr.markForCheck();
|
|
5823
|
+
});
|
|
5824
|
+
}
|
|
5825
|
+
constructor(api, eventService, elementRef, cdr) {
|
|
4436
5826
|
this.api = api;
|
|
4437
5827
|
this.eventService = eventService;
|
|
4438
5828
|
this.elementRef = elementRef;
|
|
5829
|
+
this.cdr = cdr;
|
|
4439
5830
|
this.rowNumber = false;
|
|
4440
5831
|
this.allowedEdit = true;
|
|
4441
5832
|
this.submitted = false;
|
|
4442
5833
|
this.context = null;
|
|
4443
5834
|
this.rowData = [];
|
|
4444
5835
|
this.rowSelection = null;
|
|
5836
|
+
/**
|
|
5837
|
+
* The grid's accessible name.
|
|
5838
|
+
*
|
|
5839
|
+
* A `role="grid"` with no name is announced as an unlabelled grid, which
|
|
5840
|
+
* tells a screen-reader user nothing about what they have landed in. Set it
|
|
5841
|
+
* to whatever the grid is showing — "Employees", "Order lines".
|
|
5842
|
+
*/
|
|
5843
|
+
this.gridLabel = 'Data grid';
|
|
4445
5844
|
this.enableGroupColumnDefs = false;
|
|
4446
5845
|
this.groupColumnDefs = [];
|
|
4447
5846
|
this.defaultColumnDef = {};
|
|
@@ -4476,6 +5875,41 @@ class OneGridComponent {
|
|
|
4476
5875
|
* pointer is in flight — and only when the position actually changed.
|
|
4477
5876
|
*/
|
|
4478
5877
|
this.rowDragEnd = new EventEmitter();
|
|
5878
|
+
/**
|
|
5879
|
+
* The header's ⋮ button was pressed for this column.
|
|
5880
|
+
*
|
|
5881
|
+
* The grid has no column menu of its own — this reports the intent so the
|
|
5882
|
+
* host can show whatever a column menu means in its app. Set
|
|
5883
|
+
* `columnDef.suppressHeaderMenuButton` to hide the button entirely.
|
|
5884
|
+
*/
|
|
5885
|
+
this.headerMenuClick = new EventEmitter();
|
|
5886
|
+
/**
|
|
5887
|
+
* Render the grid's own column menu behind the header ⋮ button: sort, pin,
|
|
5888
|
+
* hide. Opt-in — a host that built its menu on `(headerMenuClick)` keeps
|
|
5889
|
+
* exactly what it had, and the intent still emits either way.
|
|
5890
|
+
*/
|
|
5891
|
+
this.headerMenu = false;
|
|
5892
|
+
/**
|
|
5893
|
+
* Render a filter input under every column whose `filterable` resolves
|
|
5894
|
+
* true. The inputs feed `api.setFilterModel` with contains-conditions,
|
|
5895
|
+
* debounced; filters on fields without an input are left untouched.
|
|
5896
|
+
*/
|
|
5897
|
+
this.filterRow = false;
|
|
5898
|
+
/**
|
|
5899
|
+
* Record committed cell edits so `api.undoCellEditing` /
|
|
5900
|
+
* `api.redoCellEditing` (and Ctrl+Z / Ctrl+Y in the grid) can walk them.
|
|
5901
|
+
* Programmatic `setDataValue` writes are not recorded.
|
|
5902
|
+
*/
|
|
5903
|
+
this.undoRedoCellEditing = false;
|
|
5904
|
+
/** How many edits the undo stack keeps. */
|
|
5905
|
+
this.undoRedoCellEditingLimit = 10;
|
|
5906
|
+
/**
|
|
5907
|
+
* `'fullRow'` opens every editable cell of the row on a double-click and
|
|
5908
|
+
* commits them together — Enter anywhere in the row, or focus leaving the
|
|
5909
|
+
* row. Escape closes the whole row without committing. Default: cells edit
|
|
5910
|
+
* one at a time.
|
|
5911
|
+
*/
|
|
5912
|
+
this.editType = null;
|
|
4479
5913
|
this.onGridReady = new EventEmitter();
|
|
4480
5914
|
this.onRowGotoDetail = new EventEmitter();
|
|
4481
5915
|
this.onRowAddNew = new EventEmitter();
|
|
@@ -4495,6 +5929,22 @@ class OneGridComponent {
|
|
|
4495
5929
|
this.rowCountChanged = new EventEmitter();
|
|
4496
5930
|
this.selectionChanged = new EventEmitter();
|
|
4497
5931
|
this.expansionChanged = new EventEmitter();
|
|
5932
|
+
/**
|
|
5933
|
+
* The sort model changed — a header click or `api.onSortChanged`. Carries
|
|
5934
|
+
* the re-sorted nodes and the model that produced them; `api.sortModel`
|
|
5935
|
+
* stays readable between emissions.
|
|
5936
|
+
*/
|
|
5937
|
+
this.sortChanged = new EventEmitter();
|
|
5938
|
+
/**
|
|
5939
|
+
* An inline edit was committed with a value different from the one it
|
|
5940
|
+
* started with. Programmatic `node.setDataValue` does not emit here — the
|
|
5941
|
+
* caller already holds both values.
|
|
5942
|
+
*/
|
|
5943
|
+
this.cellValueChanged = new EventEmitter();
|
|
5944
|
+
/** A header drag finished somewhere other than where it started. */
|
|
5945
|
+
this.columnMoved = new EventEmitter();
|
|
5946
|
+
/** A resize gesture finished — one event per gesture, not per pixel. */
|
|
5947
|
+
this.columnResized = new EventEmitter();
|
|
4498
5948
|
// For context menu option
|
|
4499
5949
|
this.cellOptionSelect = new EventEmitter();
|
|
4500
5950
|
this.onRowCellOption = new EventEmitter();
|
|
@@ -4502,17 +5952,42 @@ class OneGridComponent {
|
|
|
4502
5952
|
* Bind One Grid Pagination Feature
|
|
4503
5953
|
*/
|
|
4504
5954
|
this.pagination = false;
|
|
5955
|
+
/**
|
|
5956
|
+
* Let the grid page its own rows. The bar renders, the grid slices the
|
|
5957
|
+
* filtered rows to the current page, and the navigation buttons work
|
|
5958
|
+
* without the host handling anything — `[paginate]` is ignored in this
|
|
5959
|
+
* mode (the grid owns those numbers) except for `pageSizeOptions`. The
|
|
5960
|
+
* navigation outputs still emit, as notifications rather than intents.
|
|
5961
|
+
* Selection, filtering and sorting keep operating on the whole dataset.
|
|
5962
|
+
*/
|
|
5963
|
+
this.clientSidePagination = false;
|
|
5964
|
+
/** Rows per page for `clientSidePagination`. */
|
|
5965
|
+
this.pageSize = 25;
|
|
4505
5966
|
this.goToFirst = new EventEmitter();
|
|
4506
5967
|
this.goToPrevious = new EventEmitter();
|
|
4507
5968
|
this.goToNext = new EventEmitter();
|
|
4508
5969
|
this.goToLast = new EventEmitter();
|
|
4509
5970
|
this.pageSizeChange = new EventEmitter();
|
|
5971
|
+
/** The paging numbers the bar shows when the grid pages its own rows. */
|
|
5972
|
+
this.internalPaginate = {
|
|
5973
|
+
total: 0, currentPage: 1, lastPage: 1, perPage: 25, firstRowIndex: 0, lastRowIndex: 0,
|
|
5974
|
+
};
|
|
4510
5975
|
/**
|
|
4511
5976
|
* One Grid Properties
|
|
4512
5977
|
*/
|
|
4513
5978
|
this.bodyScrollLeft = 0;
|
|
4514
5979
|
this._oneRowNumberColumnDef = null;
|
|
4515
5980
|
this.subscriptions = new Subscription();
|
|
5981
|
+
/**
|
|
5982
|
+
* Totals for `aria-rowcount` / `aria-colcount`.
|
|
5983
|
+
*
|
|
5984
|
+
* Held as fields rather than computed in the template: they are read on every
|
|
5985
|
+
* change detection pass and only actually change when the data or the columns
|
|
5986
|
+
* do. `-1` is the ARIA value for "unknown", which is the honest answer before
|
|
5987
|
+
* the first row arrives.
|
|
5988
|
+
*/
|
|
5989
|
+
this.ariaRowCount = -1;
|
|
5990
|
+
this.ariaColCount = -1;
|
|
4516
5991
|
}
|
|
4517
5992
|
ngOnChanges(changes) {
|
|
4518
5993
|
if (changes['masterNode'] && changes['masterNode'].isFirstChange) {
|
|
@@ -4556,6 +6031,35 @@ class OneGridComponent {
|
|
|
4556
6031
|
maxWidth: 200
|
|
4557
6032
|
};
|
|
4558
6033
|
}
|
|
6034
|
+
/**
|
|
6035
|
+
* Row grouping: column defs declaring `rowGroup` turn the grid into a
|
|
6036
|
+
* tree over synthetic group rows. Configured before `api.init` because
|
|
6037
|
+
* everything downstream — the treeData flag the API captures, the group
|
|
6038
|
+
* column the tree block below prepends — has to see the tree shape.
|
|
6039
|
+
*/
|
|
6040
|
+
const rowGroupColumnFields = this.treeData || this.masterDetail
|
|
6041
|
+
? []
|
|
6042
|
+
: rowGroupFields(this.columnDefs);
|
|
6043
|
+
if (rowGroupColumnFields.length) {
|
|
6044
|
+
this.treeData = true;
|
|
6045
|
+
this.treeDataChildrenField = ONE_GROUP_CHILDREN_FIELD;
|
|
6046
|
+
// The group column: host-tuned through autoGroupColumnDef, but its
|
|
6047
|
+
// field is always the synthetic label — that is where a group row
|
|
6048
|
+
// carries its display value.
|
|
6049
|
+
this.autoGroupColumnDef = {
|
|
6050
|
+
headerName: 'Group',
|
|
6051
|
+
minWidth: 200,
|
|
6052
|
+
...this.autoGroupColumnDef,
|
|
6053
|
+
field: ONE_GROUP_VALUE_FIELD,
|
|
6054
|
+
// The marker the tree machinery keys on: the expander renders on this
|
|
6055
|
+
// column and group rows print their label here.
|
|
6056
|
+
autoGroupField: true,
|
|
6057
|
+
};
|
|
6058
|
+
// A grouped-by column's value is on the group row; showing the flat
|
|
6059
|
+
// column too repeats it down every leaf. Hidden, not removed — the
|
|
6060
|
+
// host can bring it back with api.setColumnVisible.
|
|
6061
|
+
this.columnDefs = this.columnDefs.map(column => column.rowGroup ? { ...column, hide: true } : column);
|
|
6062
|
+
}
|
|
4559
6063
|
/**
|
|
4560
6064
|
* Initialize One Grid API
|
|
4561
6065
|
*/
|
|
@@ -4571,6 +6075,15 @@ class OneGridComponent {
|
|
|
4571
6075
|
detailRendererComponent: this.detailRendererComponent,
|
|
4572
6076
|
treeData: this.treeData,
|
|
4573
6077
|
});
|
|
6078
|
+
if (rowGroupColumnFields.length) {
|
|
6079
|
+
this.api.setRowGrouping(rowGroupColumnFields, aggColumns(this.columnDefs));
|
|
6080
|
+
}
|
|
6081
|
+
if (this.undoRedoCellEditing) {
|
|
6082
|
+
this.api.enableUndoRedoCellEditing(this.undoRedoCellEditingLimit);
|
|
6083
|
+
}
|
|
6084
|
+
if (this.clientSidePagination) {
|
|
6085
|
+
this.api.setClientSidePagination(true, this.paginate?.perPage ?? this.pageSize);
|
|
6086
|
+
}
|
|
4574
6087
|
/**
|
|
4575
6088
|
* Setup Master Detail Configuration
|
|
4576
6089
|
*/
|
|
@@ -4593,9 +6106,28 @@ class OneGridComponent {
|
|
|
4593
6106
|
setTimeout(() => this.api.setColumnDefs(columnDefs));
|
|
4594
6107
|
}
|
|
4595
6108
|
// Listen Selection Changed Event
|
|
6109
|
+
// Columns can be replaced at runtime through `api.setColumnDefs`, and
|
|
6110
|
+
// `aria-colcount` has to follow.
|
|
6111
|
+
this.subscriptions.add(this.eventService.addEventListener('columnChanged', () => {
|
|
6112
|
+
this.ariaColCount = this.api?.getColumnState()?.columns?.length ?? -1;
|
|
6113
|
+
}));
|
|
4596
6114
|
this.subscriptions.add(this.eventService.addEventListener('selectionChanged', (payload) => {
|
|
4597
6115
|
this.selectionChanged.emit(payload);
|
|
4598
6116
|
}));
|
|
6117
|
+
// The internal events a host can act on, surfaced as outputs. Same
|
|
6118
|
+
// forwarding pattern as selectionChanged above.
|
|
6119
|
+
this.subscriptions.add(this.eventService.addEventListener('sortChanged', (payload) => {
|
|
6120
|
+
this.sortChanged.emit(payload);
|
|
6121
|
+
}));
|
|
6122
|
+
this.subscriptions.add(this.eventService.addEventListener('cellValueChanged', (payload) => {
|
|
6123
|
+
this.cellValueChanged.emit(payload);
|
|
6124
|
+
}));
|
|
6125
|
+
this.subscriptions.add(this.eventService.addEventListener('columnMoved', (payload) => {
|
|
6126
|
+
this.columnMoved.emit(payload);
|
|
6127
|
+
}));
|
|
6128
|
+
this.subscriptions.add(this.eventService.addEventListener('columnResized', (payload) => {
|
|
6129
|
+
this.columnResized.emit(payload);
|
|
6130
|
+
}));
|
|
4599
6131
|
}
|
|
4600
6132
|
ngAfterViewInit() {
|
|
4601
6133
|
// The header follows the body through (bodyScroll) -> [scrollLeft]. A second
|
|
@@ -4687,8 +6219,34 @@ class OneGridComponent {
|
|
|
4687
6219
|
this.onShiftDoubleClick.emit(event);
|
|
4688
6220
|
}
|
|
4689
6221
|
invokeRowCountChanged(length) {
|
|
6222
|
+
this.refreshAriaCounts(length);
|
|
6223
|
+
if (this.clientSidePagination)
|
|
6224
|
+
this.refreshClientPagination(length);
|
|
4690
6225
|
this.rowCountChanged.emit(length);
|
|
4691
6226
|
}
|
|
6227
|
+
/**
|
|
6228
|
+
* Recompute the totals a screen reader is told about.
|
|
6229
|
+
*
|
|
6230
|
+
* `aria-rowcount` counts every row the grid represents, not the handful the
|
|
6231
|
+
* virtual scroller currently has in the DOM — the header row, the pinned rows
|
|
6232
|
+
* either side, and all the data rows.
|
|
6233
|
+
*/
|
|
6234
|
+
refreshAriaCounts(dataRows) {
|
|
6235
|
+
const pinned = (this.pinnedTopRowData?.length ?? 0) + (this.pinnedBottomRowData?.length ?? 0);
|
|
6236
|
+
const rows = 1 + pinned + dataRows;
|
|
6237
|
+
const cols = this.api?.getColumnState()?.columns?.length ?? -1;
|
|
6238
|
+
if (rows === this.ariaRowCount && cols === this.ariaColCount)
|
|
6239
|
+
return;
|
|
6240
|
+
// Deferred a tick. The body emits its row count while the first change
|
|
6241
|
+
// detection pass is still running, after this component's own attributes
|
|
6242
|
+
// were checked — assigning here directly is the textbook NG0100. The body
|
|
6243
|
+
// defers its node list for the same reason.
|
|
6244
|
+
setTimeout(() => {
|
|
6245
|
+
this.ariaRowCount = rows;
|
|
6246
|
+
this.ariaColCount = cols;
|
|
6247
|
+
this.cdr.markForCheck();
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
4692
6250
|
cellOptionMenuSelected(event) {
|
|
4693
6251
|
this.cellOptionSelect.emit(event);
|
|
4694
6252
|
}
|
|
@@ -4696,18 +6254,28 @@ class OneGridComponent {
|
|
|
4696
6254
|
* Bing Pagination Events to Parent Component
|
|
4697
6255
|
*/
|
|
4698
6256
|
invokeGoToFirst() {
|
|
6257
|
+
if (this.clientSidePagination)
|
|
6258
|
+
this.api.setCurrentPage(1);
|
|
4699
6259
|
this.goToFirst.emit();
|
|
4700
6260
|
}
|
|
4701
6261
|
invokeGoToPrevious() {
|
|
6262
|
+
if (this.clientSidePagination)
|
|
6263
|
+
this.api.setCurrentPage(this.api.paginationState.currentPage - 1);
|
|
4702
6264
|
this.goToPrevious.emit();
|
|
4703
6265
|
}
|
|
4704
6266
|
invokeGoToNext() {
|
|
6267
|
+
if (this.clientSidePagination)
|
|
6268
|
+
this.api.setCurrentPage(this.api.paginationState.currentPage + 1);
|
|
4705
6269
|
this.goToNext.emit();
|
|
4706
6270
|
}
|
|
4707
6271
|
invokeGoToLast() {
|
|
6272
|
+
if (this.clientSidePagination)
|
|
6273
|
+
this.api.setCurrentPage(this.internalPaginate.lastPage ?? 1);
|
|
4708
6274
|
this.goToLast.emit();
|
|
4709
6275
|
}
|
|
4710
6276
|
invokePageSizeChange(pageSize) {
|
|
6277
|
+
if (this.clientSidePagination)
|
|
6278
|
+
this.api.setPageSize(pageSize);
|
|
4711
6279
|
this.pageSizeChange.emit(pageSize);
|
|
4712
6280
|
}
|
|
4713
6281
|
invokeRowDragEnd(event) {
|
|
@@ -4724,8 +6292,8 @@ class OneGridComponent {
|
|
|
4724
6292
|
this.api.destroy();
|
|
4725
6293
|
this.subscriptions.unsubscribe();
|
|
4726
6294
|
}
|
|
4727
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, deps: [{ token: OneGridApi }, { token: OneGridEventService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4728
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridComponent, selector: "one-grid", inputs: { rowNumber: "rowNumber", allowedEdit: "allowedEdit", submitted: "submitted", context: "context", rowData: "rowData", rowSelection: "rowSelection", enableGroupColumnDefs: "enableGroupColumnDefs", groupColumnDefs: "groupColumnDefs", defaultColumnDef: "defaultColumnDef", columnDefs: "columnDefs", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded", pagination: "pagination", paginate: "paginate" }, outputs: { rowDragEnd: "rowDragEnd", onGridReady: "onGridReady", onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", selectionChanged: "selectionChanged", expansionChanged: "expansionChanged", cellOptionSelect: "cellOptionSelect", onRowCellOption: "onRowCellOption", goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [
|
|
6295
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, deps: [{ token: OneGridApi }, { token: OneGridEventService }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6296
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridComponent, isStandalone: true, selector: "one-grid", inputs: { rowNumber: "rowNumber", allowedEdit: "allowedEdit", submitted: "submitted", context: "context", rowData: "rowData", rowSelection: "rowSelection", gridLabel: "gridLabel", enableGroupColumnDefs: "enableGroupColumnDefs", groupColumnDefs: "groupColumnDefs", defaultColumnDef: "defaultColumnDef", columnDefs: "columnDefs", pinnedTopRowData: "pinnedTopRowData", pinnedBottomRowData: "pinnedBottomRowData", gridType: "gridType", masterDetail: "masterDetail", masterNode: "masterNode", detailCellRendererParams: "detailCellRendererParams", detailRendererComponent: "detailRendererComponent", treeData: "treeData", treeDataChildrenField: "treeDataChildrenField", autoGroupColumnDef: "autoGroupColumnDef", groupDefaultExpanded: "groupDefaultExpanded", headerMenu: "headerMenu", filterRow: "filterRow", undoRedoCellEditing: "undoRedoCellEditing", undoRedoCellEditingLimit: "undoRedoCellEditingLimit", editType: "editType", pagination: "pagination", paginate: "paginate", clientSidePagination: "clientSidePagination", pageSize: "pageSize" }, outputs: { rowDragEnd: "rowDragEnd", headerMenuClick: "headerMenuClick", onGridReady: "onGridReady", onRowGotoDetail: "onRowGotoDetail", onRowAddNew: "onRowAddNew", onRowView: "onRowView", onRowLocation: "onRowLocation", onRowDocument: "onRowDocument", onRowCopy: "onRowCopy", onRowDelete: "onRowDelete", onRowGotoList: "onRowGotoList", onRowEdit: "onRowEdit", onRowPrint: "onRowPrint", onRowHistory: "onRowHistory", onRowOption: "onRowOption", onRowLanguage: "onRowLanguage", onRowSelection: "onRowSelection", onShiftDoubleClick: "onShiftDoubleClick", rowCountChanged: "rowCountChanged", selectionChanged: "selectionChanged", expansionChanged: "expansionChanged", sortChanged: "sortChanged", cellValueChanged: "cellValueChanged", columnMoved: "columnMoved", columnResized: "columnResized", cellOptionSelect: "cellOptionSelect", onRowCellOption: "onRowCellOption", goToFirst: "goToFirst", goToPrevious: "goToPrevious", goToNext: "goToNext", goToLast: "goToLast", pageSizeChange: "pageSizeChange" }, providers: [
|
|
4729
6297
|
OneGridEventService,
|
|
4730
6298
|
OneGridApi,
|
|
4731
6299
|
OgColumnService,
|
|
@@ -4734,11 +6302,16 @@ class OneGridComponent {
|
|
|
4734
6302
|
OneGridRowService,
|
|
4735
6303
|
OneGridCellService,
|
|
4736
6304
|
OneGridRowDragService
|
|
4737
|
-
], viewQueries: [{ propertyName: "ogRef", first: true, predicate: ["ogRef"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<div #ogRef
|
|
6305
|
+
], viewQueries: [{ propertyName: "ogRef", first: true, predicate: ["ogRef"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<!-- role=\"grid\" is what turns this from a pile of divs into something a screen\n reader can navigate. The counts are the *total* \u2014 `aria-rowcount` includes\n the header row and any pinned rows, and both stay correct while the body is\n virtualised and most rows are not in the DOM, which is exactly what these\n attributes exist to communicate. -->\n<div #ogRef\n class=\"one-grid\"\n role=\"grid\"\n [attr.aria-label]=\"gridLabel\"\n [attr.aria-rowcount]=\"ariaRowCount\"\n [attr.aria-colcount]=\"ariaColCount\"\n [attr.aria-multiselectable]=\"rowSelection === 'multiple' ? true : null\"\n (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n [headerMenu]=\"headerMenu\"\n [filterRow]=\"filterRow\"\n (headerMenuClick)=\"headerMenuClick.emit($event)\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [editType]=\"editType\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination || clientSidePagination) {\n <one-grid-pagination\n [paginate]=\"clientSidePagination ? internalPaginate : paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>", styles: [""], dependencies: [{ kind: "component", type: OneGridComponent, selector: "one-grid", inputs: ["rowNumber", "allowedEdit", "submitted", "context", "rowData", "rowSelection", "gridLabel", "enableGroupColumnDefs", "groupColumnDefs", "defaultColumnDef", "columnDefs", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailCellRendererParams", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded", "headerMenu", "filterRow", "undoRedoCellEditing", "undoRedoCellEditingLimit", "editType", "pagination", "paginate", "clientSidePagination", "pageSize"], outputs: ["rowDragEnd", "headerMenuClick", "onGridReady", "onRowGotoDetail", "onRowAddNew", "onRowView", "onRowLocation", "onRowDocument", "onRowCopy", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "selectionChanged", "expansionChanged", "sortChanged", "cellValueChanged", "columnMoved", "columnResized", "cellOptionSelect", "onRowCellOption", "goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }, { kind: "component", type: OneGridHeaderComponent, selector: "one-grid-header", inputs: ["scrollLeft", "allowedEdit", "enableGroupColumnDefs", "api", "filterRow", "headerMenu"], outputs: ["headerMenuClick"] }, { kind: "component", type: OneGridBodyComponent, selector: "one-grid-body", inputs: ["rowData", "rowNumber", "api", "pagination", "allowedEdit", "enableGroupColumnDefs", "submitted", "editType", "context", "getRowId", "gridCopy$", "pinnedTopRowData", "pinnedBottomRowData", "gridType", "masterDetail", "masterNode", "detailRowMaxHeight", "detailCellRendererParams", "detailGridTemplate", "detailRendererComponent", "treeData", "treeDataChildrenField", "autoGroupColumnDef", "groupDefaultExpanded"], outputs: ["onRowGotoDetail", "onRowAddNew", "onRowView", "onRowCopy", "onRowLocation", "onRowDocument", "onRowDelete", "onRowGotoList", "onRowEdit", "onRowPrint", "onRowHistory", "onRowOption", "onRowLanguage", "onRowCellOption", "onRowSelection", "onShiftDoubleClick", "rowCountChanged", "expansionChanged", "bodyScroll", "cellOptionSelect", "rowDragEnd"] }, { kind: "component", type: OneGridPaginationComponent, selector: "one-grid-pagination", inputs: ["paginate"], outputs: ["goToFirst", "goToPrevious", "goToNext", "goToLast", "pageSizeChange"] }] }); }
|
|
4738
6306
|
}
|
|
4739
6307
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridComponent, decorators: [{
|
|
4740
6308
|
type: Component,
|
|
4741
|
-
args: [{ standalone:
|
|
6309
|
+
args: [{ standalone: true, imports: [
|
|
6310
|
+
NgTemplateOutlet,
|
|
6311
|
+
OneGridHeaderComponent,
|
|
6312
|
+
OneGridBodyComponent,
|
|
6313
|
+
OneGridPaginationComponent,
|
|
6314
|
+
], selector: 'one-grid', providers: [
|
|
4742
6315
|
OneGridEventService,
|
|
4743
6316
|
OneGridApi,
|
|
4744
6317
|
OgColumnService,
|
|
@@ -4747,8 +6320,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4747
6320
|
OneGridRowService,
|
|
4748
6321
|
OneGridCellService,
|
|
4749
6322
|
OneGridRowDragService
|
|
4750
|
-
], template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<div #ogRef
|
|
4751
|
-
}], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }], propDecorators: { ogRef: [{
|
|
6323
|
+
], template: "<!-- No tabindex here. The tab stop belongs on the body viewport, which is the\n element that actually scrolls; a focusable wrapper around a scrollable\n child is reachable but cannot be scrolled with the keyboard. The copy\n handler is unaffected: a copy event fired at the focused viewport bubbles\n up to here. -->\n<!-- role=\"grid\" is what turns this from a pile of divs into something a screen\n reader can navigate. The counts are the *total* \u2014 `aria-rowcount` includes\n the header row and any pinned rows, and both stay correct while the body is\n virtualised and most rows are not in the DOM, which is exactly what these\n attributes exist to communicate. -->\n<div #ogRef\n class=\"one-grid\"\n role=\"grid\"\n [attr.aria-label]=\"gridLabel\"\n [attr.aria-rowcount]=\"ariaRowCount\"\n [attr.aria-colcount]=\"ariaColCount\"\n [attr.aria-multiselectable]=\"rowSelection === 'multiple' ? true : null\"\n (copy)=\"onGridCopy($event)\">\n \n <!-- Headers -->\n <one-grid-header\n [api]=\"api\"\n [enableGroupColumnDefs]=\"enableGroupColumnDefs\"\n [allowedEdit]=\"allowedEdit\"\n [scrollLeft]=\"bodyScrollLeft\"\n [headerMenu]=\"headerMenu\"\n [filterRow]=\"filterRow\"\n (headerMenuClick)=\"headerMenuClick.emit($event)\"\n />\n\n <!-- Body -->\n <one-grid-body\n [gridCopy$]=\"gridCopy$\"\n [rowData]=\"rowData\"\n [pinnedTopRowData]=\"pinnedTopRowData\"\n [pinnedBottomRowData]=\"pinnedBottomRowData\"\n [api]=\"api\"\n [rowNumber]=\"rowNumber\"\n [pagination]=\"pagination\"\n [allowedEdit]=\"allowedEdit\"\n [editType]=\"editType\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n [treeData]=\"treeData\"\n [gridType]=\"gridType\"\n [masterDetail]=\"masterDetail\"\n [masterNode]=\"masterNode\"\n [detailCellRendererParams]=\"detailCellRendererParams\"\n [detailRendererComponent]=\"detailRendererComponent\"\n [detailGridTemplate]=\"detailGridTpl\"\n [treeDataChildrenField]=\"treeDataChildrenField\"\n [autoGroupColumnDef]=\"autoGroupColumnDef\"\n [groupDefaultExpanded]=\"groupDefaultExpanded\"\n (rowDragEnd)=\"invokeRowDragEnd($event)\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onRowSelection)=\"invokeRowActionSelection($event)\"\n (onShiftDoubleClick)=\"invokeShiftDoubleClick($event)\"\n (rowCountChanged)=\"invokeRowCountChanged($event)\"\n (cellOptionSelect)=\"cellOptionMenuSelected($event)\"\n (bodyScroll)=\"onBodyScroll($event)\"\n />\n \n <!-- Pagination -->\n @if (pagination || clientSidePagination) {\n <one-grid-pagination\n [paginate]=\"clientSidePagination ? internalPaginate : paginate\"\n (goToFirst)=\"invokeGoToFirst()\"\n (goToLast)=\"invokeGoToLast()\"\n (goToPrevious)=\"invokeGoToPrevious()\"\n (goToNext)=\"invokeGoToNext()\"\n (pageSizeChange)=\"invokePageSizeChange($event)\"\n />\n }\n\n <!-- Master Detail Grid template, rendered inside one-grid-body via ngTemplateOutlet.\n Declared here (self-reference) so one-grid-body's template does not need to\n import one-grid, which would create an unsupported compile-time cycle. -->\n <ng-template #detailGridTpl let-node=\"node\">\n <one-grid\n [style.height.px]=\"node.detailRowHeight\"\n [gridType]=\"'detail'\"\n [masterNode]=\"node\"\n [masterDetail]=\"masterDetail\"\n [defaultColumnDef]=\"detailCellRendererParams.detailGridOptions.defaultColumnDef\"\n [columnDefs]=\"detailCellRendererParams.detailGridOptions.columnDefs\"\n [rowData]=\"node?.detailRowData\"\n [rowNumber]=\"detailCellRendererParams.detailGridOptions?.rowNumber\"\n [allowedEdit]=\"allowedEdit\"\n [submitted]=\"submitted\"\n [context]=\"context\"\n (expansionChanged)=\"invokeExpansionChangedClick($event)\"\n (onRowGotoDetail)=\"invokeRowActionGotoDetail($event)\"\n (onRowAddNew)=\"invokeRowActionAddNew($event)\"\n (onRowView)=\"invokeRowActionView($event)\"\n (onRowLocation)=\"invokeRowActionLocation($event)\"\n (onRowDocument)=\"invokeRowActionDocument($event)\"\n (onRowCopy)=\"invokeRowActionCopy($event)\"\n (onRowDelete)=\"invokeRowActionDelete($event)\"\n (onRowGotoList)=\"invokeRowActionGotoList($event)\"\n (onRowEdit)=\"invokeRowActionEdit($event)\"\n (onRowPrint)=\"invokeRowActionPrint($event)\"\n (onRowHistory)=\"invokeRowActionHistory($event)\"\n (onRowOption)=\"invokeRowActionOption($event)\"\n (onRowLanguage)=\"invokeRowActionLanguage($event)\"\n (onRowCellOption)=\"invokeRowActionCellOption($event)\"\n (onGridReady)=\"onDetailGridReady($event)\"\n />\n </ng-template>\n</div>" }]
|
|
6324
|
+
}], ctorParameters: () => [{ type: OneGridApi }, { type: OneGridEventService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { ogRef: [{
|
|
4752
6325
|
type: ViewChild,
|
|
4753
6326
|
args: ['ogRef', { static: true }]
|
|
4754
6327
|
}], rowNumber: [{
|
|
@@ -4763,6 +6336,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4763
6336
|
type: Input
|
|
4764
6337
|
}], rowSelection: [{
|
|
4765
6338
|
type: Input
|
|
6339
|
+
}], gridLabel: [{
|
|
6340
|
+
type: Input
|
|
4766
6341
|
}], enableGroupColumnDefs: [{
|
|
4767
6342
|
type: Input
|
|
4768
6343
|
}], groupColumnDefs: [{
|
|
@@ -4795,6 +6370,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4795
6370
|
type: Input
|
|
4796
6371
|
}], rowDragEnd: [{
|
|
4797
6372
|
type: Output
|
|
6373
|
+
}], headerMenuClick: [{
|
|
6374
|
+
type: Output
|
|
6375
|
+
}], headerMenu: [{
|
|
6376
|
+
type: Input
|
|
6377
|
+
}], filterRow: [{
|
|
6378
|
+
type: Input
|
|
6379
|
+
}], undoRedoCellEditing: [{
|
|
6380
|
+
type: Input
|
|
6381
|
+
}], undoRedoCellEditingLimit: [{
|
|
6382
|
+
type: Input
|
|
6383
|
+
}], editType: [{
|
|
6384
|
+
type: Input
|
|
4798
6385
|
}], onGridReady: [{
|
|
4799
6386
|
type: Output
|
|
4800
6387
|
}], onRowGotoDetail: [{
|
|
@@ -4833,6 +6420,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4833
6420
|
type: Output
|
|
4834
6421
|
}], expansionChanged: [{
|
|
4835
6422
|
type: Output
|
|
6423
|
+
}], sortChanged: [{
|
|
6424
|
+
type: Output
|
|
6425
|
+
}], cellValueChanged: [{
|
|
6426
|
+
type: Output
|
|
6427
|
+
}], columnMoved: [{
|
|
6428
|
+
type: Output
|
|
6429
|
+
}], columnResized: [{
|
|
6430
|
+
type: Output
|
|
4836
6431
|
}], cellOptionSelect: [{
|
|
4837
6432
|
type: Output
|
|
4838
6433
|
}], onRowCellOption: [{
|
|
@@ -4841,6 +6436,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4841
6436
|
type: Input
|
|
4842
6437
|
}], paginate: [{
|
|
4843
6438
|
type: Input
|
|
6439
|
+
}], clientSidePagination: [{
|
|
6440
|
+
type: Input
|
|
6441
|
+
}], pageSize: [{
|
|
6442
|
+
type: Input
|
|
4844
6443
|
}], goToFirst: [{
|
|
4845
6444
|
type: Output
|
|
4846
6445
|
}], goToPrevious: [{
|
|
@@ -4878,11 +6477,11 @@ class OneGridStatusComponent {
|
|
|
4878
6477
|
return labels[type][status === 1 ? 1 : 0];
|
|
4879
6478
|
}
|
|
4880
6479
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4881
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridStatusComponent, selector: "one-grid-status", ngImport: i0, template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n", styles: [""], dependencies: [{ kind: "directive", type:
|
|
6480
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: OneGridStatusComponent, isStandalone: true, selector: "one-grid-status", ngImport: i0, template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
|
|
4882
6481
|
}
|
|
4883
6482
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridStatusComponent, decorators: [{
|
|
4884
6483
|
type: Component,
|
|
4885
|
-
args: [{ standalone:
|
|
6484
|
+
args: [{ standalone: true, imports: [NgClass], selector: 'one-grid-status', template: "<span class=\"one-cell-status\"\n [style.width.px]=\"params.columnDef?.width\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\"\n >\n <span class=\"one-cell-status-body\" [ngClass]=\"label === 1 ? 'one-success-bg' : 'one-warning-bg'\">\n {{ value }}\n </span>\n</span>\n" }]
|
|
4886
6485
|
}] });
|
|
4887
6486
|
|
|
4888
6487
|
class OneGridCellCurrencyComponent {
|
|
@@ -4957,172 +6556,50 @@ class OneGridCellCurrencyComponent {
|
|
|
4957
6556
|
this.unsubscribe?.();
|
|
4958
6557
|
}
|
|
4959
6558
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, deps: [{ token: OneGridCellService }, { token: OneGridEventService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4960
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellCurrencyComponent, selector: "app-one-grid-cell-currency", inputs: { allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"], dependencies: [{ kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }
|
|
6559
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: OneGridCellCurrencyComponent, isStandalone: true, selector: "app-one-grid-cell-currency", inputs: { allowedEdit: "allowedEdit" }, ngImport: i0, template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"], dependencies: [{ kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: OneCellInputValidatorDirective, selector: "[oneCellInputValidator]", inputs: ["validationType", "regex"] }] }); }
|
|
4961
6560
|
}
|
|
4962
6561
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridCellCurrencyComponent, decorators: [{
|
|
4963
6562
|
type: Component,
|
|
4964
|
-
args: [{ standalone:
|
|
6563
|
+
args: [{ standalone: true, imports: [DecimalPipe, FormsModule, OneCellInputValidatorDirective], selector: 'app-one-grid-cell-currency', template: "<div class=\"cell-wrapper\"\n [class.editable]=\"isEditable\"\n [attr.data-cell-id]=\"getCellField(params.node?.rowIndex, params.columnDef?.field)\"\n [style.min-width.px]=\"params.columnDef?.minWidth\"\n [style.max-width.px]=\"params.columnDef?.maxWidth\">\n\n @if (params.node?.editing?.[params.columnDef?.field]) {\n <input class=\"one-cell-input\"\n name=\"{{ params.node?.rowIndex }}_{{ params.columnDef?.field }}\"\n oneCellInputValidator\n validationType=\"number\"\n [placeholder]=\"params.columnDef?.cellRendererParams?.placeholder || ''\"\n type=\"text\"\n [(ngModel)]=\"value\"\n [maxLength]=\"250\"\n (blur)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.enter)=\"stopEdit(params.node, params.columnDef, $event)\"\n (keydown.esc)=\"cancelEdit(params.node, params.columnDef)\" />\n }\n @else {\n <div class=\"d-flex justify-content-between px-2 pt-1\"\n [class.one-cell-allowed-edit-active]=\"isEditable\"\n (dblclick)=\"startEdit(params.node, params.columnDef)\">\n <span class=\"currency-code\" style=\"font-weight: bold;\">{{ this.params?.data?.currency?.currencyCode || 'USD' }}</span>\n <span class=\"amount-value\">\n {{ result | number:(this.params?.field === 'amount' ? '1.3-3' : '1.2-2') }}\n </span>\n </div>\n }\n</div>\n", styles: [".one-cell-input{position:absolute;top:0;left:0;width:calc(100% - 1px);height:100%;padding:0 6px;background-color:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754));border-radius:var(--one-grid-action-btn-radius, 4px);z-index:5}.one-cell-input:focus{background:var(--one-grid-input-bg, var(--bs-input-bg, #fff));border:1px solid var(--one-grid-success, var(--bs-success, #198754))}:host{display:block;width:100%;height:100%;position:relative}.cell-wrapper{position:relative;width:100%;height:100%}.cell-wrapper.editable:before{content:\"\";position:absolute;top:4px;right:4px;width:4px;height:4px;background-color:var(--one-grid-success, var(--bs-success, #198754));border-radius:50%;z-index:1;opacity:.5;transition:opacity .2s ease}.cell-wrapper.editable:hover:before{opacity:1}\n"] }]
|
|
4965
6564
|
}], ctorParameters: () => [{ type: OneGridCellService }, { type: OneGridEventService }], propDecorators: { allowedEdit: [{
|
|
4966
6565
|
type: Input
|
|
4967
6566
|
}] } });
|
|
4968
6567
|
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
}]
|
|
4982
|
-
}] });
|
|
4983
|
-
|
|
4984
|
-
class CellNumberValuePipe {
|
|
4985
|
-
transform(value) {
|
|
4986
|
-
if (value === null || value === undefined)
|
|
4987
|
-
return undefined;
|
|
4988
|
-
return Number(value);
|
|
4989
|
-
}
|
|
4990
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
4991
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, name: "cellNumberValue" }); }
|
|
4992
|
-
}
|
|
4993
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellNumberValuePipe, decorators: [{
|
|
4994
|
-
type: Pipe,
|
|
4995
|
-
args: [{
|
|
4996
|
-
standalone: false,
|
|
4997
|
-
name: 'cellNumberValue'
|
|
4998
|
-
}]
|
|
4999
|
-
}] });
|
|
5000
|
-
|
|
5001
|
-
class CellFieldPipe {
|
|
5002
|
-
transform(rowIndex, field) {
|
|
5003
|
-
if (rowIndex === null || rowIndex === undefined || !field)
|
|
5004
|
-
return '';
|
|
5005
|
-
return getCellField(rowIndex, field);
|
|
5006
|
-
}
|
|
5007
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
5008
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, name: "cellField" }); }
|
|
5009
|
-
}
|
|
5010
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellFieldPipe, decorators: [{
|
|
5011
|
-
type: Pipe,
|
|
5012
|
-
args: [{
|
|
5013
|
-
standalone: false,
|
|
5014
|
-
name: 'cellField'
|
|
5015
|
-
}]
|
|
5016
|
-
}] });
|
|
5017
|
-
|
|
5018
|
-
class CellStateGroupRendererPipe {
|
|
5019
|
-
constructor() {
|
|
5020
|
-
this._cellFieldValue = new CellFieldValuePipe();
|
|
5021
|
-
}
|
|
5022
|
-
transform(value, data, column) {
|
|
5023
|
-
return value ? this._cellFieldValue.transform(data, column.field) : column?.cellRendererParams?.placeholder ?? '';
|
|
5024
|
-
}
|
|
5025
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
5026
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, name: "cellStateGroupRenderer" }); }
|
|
5027
|
-
}
|
|
5028
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CellStateGroupRendererPipe, decorators: [{
|
|
5029
|
-
type: Pipe,
|
|
5030
|
-
args: [{
|
|
5031
|
-
standalone: false,
|
|
5032
|
-
name: 'cellStateGroupRenderer'
|
|
5033
|
-
}]
|
|
5034
|
-
}] });
|
|
5035
|
-
|
|
6568
|
+
/**
|
|
6569
|
+
* Compatibility shim.
|
|
6570
|
+
*
|
|
6571
|
+
* Every component, directive and pipe in the library is standalone, so this
|
|
6572
|
+
* module declares nothing — it exists so `imports: [OneGridModule]` keeps
|
|
6573
|
+
* working for anyone already on it. New code can import `OneGridComponent`
|
|
6574
|
+
* directly instead.
|
|
6575
|
+
*
|
|
6576
|
+
* Icons no longer need registering here: each component that draws one
|
|
6577
|
+
* provides the eight the grid uses, so they travel with the component whichever
|
|
6578
|
+
* way it is imported.
|
|
6579
|
+
*/
|
|
5036
6580
|
class OneGridModule {
|
|
5037
6581
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
5038
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule,
|
|
5039
|
-
OneGridHeaderComponent,
|
|
5040
|
-
OneGridBodyComponent,
|
|
5041
|
-
OneGridCheckboxComponent,
|
|
5042
|
-
OneGridCheckboxHeaderComponent,
|
|
5043
|
-
OneGridRowActionComponent,
|
|
5044
|
-
OneGridPaginationComponent,
|
|
6582
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [OneGridComponent,
|
|
5045
6583
|
OneGridStatusComponent,
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
CellEditablePipe,
|
|
5052
|
-
CellInvalidPipe,
|
|
5053
|
-
CellSelectedPipe,
|
|
5054
|
-
CellActionButtonEnablePipe,
|
|
5055
|
-
CellNumberValuePipe,
|
|
5056
|
-
CellFieldPipe,
|
|
5057
|
-
CellKeyPipe,
|
|
5058
|
-
CellLevelPaddingPipe,
|
|
5059
|
-
ColumnSortStatePipe,
|
|
5060
|
-
HeaderCheckboxDisabledPipe,
|
|
5061
|
-
HeaderCheckboxCheckedPipe,
|
|
5062
|
-
HeaderCheckboxIndeterminatePipe,
|
|
5063
|
-
CellStateRendererPipe,
|
|
5064
|
-
CellStateGroupRendererPipe,
|
|
5065
|
-
OneCellInputValidatorDirective,
|
|
5066
|
-
OneCellRendererComponentDirective,
|
|
5067
|
-
OneCellTextExpandDirective,
|
|
5068
|
-
OneGridCellCurrencyComponent,
|
|
5069
|
-
OneGridCellOptionMenuComponent,
|
|
5070
|
-
OneGridRowDragHandleComponent], imports: [CommonModule,
|
|
5071
|
-
FormsModule,
|
|
5072
|
-
ScrollingModule,
|
|
5073
|
-
ReactiveFormsModule, i2$1.LucideAngularModule], exports: [OneGridComponent] }); }
|
|
5074
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [CommonModule,
|
|
5075
|
-
FormsModule,
|
|
5076
|
-
ScrollingModule,
|
|
5077
|
-
ReactiveFormsModule,
|
|
5078
|
-
LucideAngularModule.pick(icons)] }); }
|
|
6584
|
+
OneGridCellCurrencyComponent], exports: [OneGridComponent,
|
|
6585
|
+
OneGridStatusComponent,
|
|
6586
|
+
OneGridCellCurrencyComponent] }); }
|
|
6587
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, imports: [OneGridComponent,
|
|
6588
|
+
OneGridCellCurrencyComponent] }); }
|
|
5079
6589
|
}
|
|
5080
6590
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: OneGridModule, decorators: [{
|
|
5081
6591
|
type: NgModule,
|
|
5082
6592
|
args: [{
|
|
5083
|
-
|
|
6593
|
+
imports: [
|
|
5084
6594
|
OneGridComponent,
|
|
5085
|
-
OneGridHeaderComponent,
|
|
5086
|
-
OneGridBodyComponent,
|
|
5087
|
-
OneGridCheckboxComponent,
|
|
5088
|
-
OneGridCheckboxHeaderComponent,
|
|
5089
|
-
OneGridRowActionComponent,
|
|
5090
|
-
OneGridPaginationComponent,
|
|
5091
6595
|
OneGridStatusComponent,
|
|
5092
|
-
OneGridCellGroupRendererComponent,
|
|
5093
|
-
CellFieldValueEmptyPipe,
|
|
5094
|
-
CellValueGetterPipe,
|
|
5095
|
-
CellFieldValuePipe,
|
|
5096
|
-
CellActionButtonVisiblePipe,
|
|
5097
|
-
CellEditablePipe,
|
|
5098
|
-
CellInvalidPipe,
|
|
5099
|
-
CellSelectedPipe,
|
|
5100
|
-
CellActionButtonEnablePipe,
|
|
5101
|
-
CellNumberValuePipe,
|
|
5102
|
-
CellFieldPipe,
|
|
5103
|
-
CellKeyPipe,
|
|
5104
|
-
CellLevelPaddingPipe,
|
|
5105
|
-
ColumnSortStatePipe,
|
|
5106
|
-
HeaderCheckboxDisabledPipe,
|
|
5107
|
-
HeaderCheckboxCheckedPipe,
|
|
5108
|
-
HeaderCheckboxIndeterminatePipe,
|
|
5109
|
-
CellStateRendererPipe,
|
|
5110
|
-
CellStateGroupRendererPipe,
|
|
5111
|
-
OneCellInputValidatorDirective,
|
|
5112
|
-
OneCellRendererComponentDirective,
|
|
5113
|
-
OneCellTextExpandDirective,
|
|
5114
6596
|
OneGridCellCurrencyComponent,
|
|
5115
|
-
OneGridCellOptionMenuComponent,
|
|
5116
|
-
OneGridRowDragHandleComponent
|
|
5117
6597
|
],
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
ReactiveFormsModule,
|
|
5123
|
-
LucideAngularModule.pick(icons),
|
|
6598
|
+
exports: [
|
|
6599
|
+
OneGridComponent,
|
|
6600
|
+
OneGridStatusComponent,
|
|
6601
|
+
OneGridCellCurrencyComponent,
|
|
5124
6602
|
],
|
|
5125
|
-
exports: [OneGridComponent]
|
|
5126
6603
|
}]
|
|
5127
6604
|
}] });
|
|
5128
6605
|
|
|
@@ -5134,5 +6611,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
5134
6611
|
* Generated bundle index. Do not edit.
|
|
5135
6612
|
*/
|
|
5136
6613
|
|
|
5137
|
-
export { ONE_DEFAULT_COLUMN_WIDTH, OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridRowDragService, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, applyDefaultColumnDef, getCellError, resolveColumnWidth, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams, resolveRowHeight };
|
|
6614
|
+
export { ONE_DEFAULT_COLUMN_WIDTH, ONE_GROUP_BLANK_LABEL, ONE_GROUP_CHILDREN_FIELD, ONE_GROUP_FLAG, ONE_GROUP_ID_FIELD, ONE_GROUP_VALUE_FIELD, OneGridApi, OneGridCellCurrencyComponent, OneGridCellService, OneGridComponent, OneGridEventService, OneGridExpansionService, OneGridModule, OneGridRowDragService, OneGridSelectionService, OneGridStatusComponent, OneGridTreeDataService, OnePageSizeOption, OneRowNode, ValidationRules, Validators, aggColumns, aggregate, applyDefaultColumnDef, buildCsv, buildGroupedRows, exportableColumns, getCellError, resolveCellValueGetter, resolveColumnWidth, resolveOneCellRendererParams, resolveOneColumDefParams, resolveOneValueGetterParams, resolveRowHeight, rowGroupFields };
|
|
5138
6615
|
//# sourceMappingURL=one-grid-core-angular.mjs.map
|