eru-grid 0.0.54 → 0.0.56
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/fesm2022/eru-grid.mjs +281 -63
- package/fesm2022/eru-grid.mjs.map +1 -1
- package/package.json +1 -1
- package/types/eru-grid.d.ts +138 -29
package/fesm2022/eru-grid.mjs
CHANGED
|
@@ -194,9 +194,23 @@ const SELF_COLOURED_DATATYPES = new Set(['status', 'priority', 'tag', 'progress'
|
|
|
194
194
|
*/
|
|
195
195
|
const ACTION_COLUMN_MIN_WIDTH = 56;
|
|
196
196
|
const SEEDED_FIELD_KEYS = [
|
|
197
|
-
'dynamic_number', 'display_number_as', 'symbol_field', 'date_format',
|
|
197
|
+
'dynamic_number', 'display_number_as', 'symbol_field', 'date_format',
|
|
198
198
|
'storage_name', 'folder_name', 'default_upload',
|
|
199
199
|
];
|
|
200
|
+
/**
|
|
201
|
+
* Keys the model seeds only until the design panel overrides them EXPLICITLY,
|
|
202
|
+
* tracked by a companion flag rather than by "does the column hold a value".
|
|
203
|
+
*
|
|
204
|
+
* `label` is the case that forced this: it is in SEEDED_FIELD_KEYS' spirit —
|
|
205
|
+
* the model's label is the default and a grid may shorten it — but a column
|
|
206
|
+
* never arrives without one, so the seeded test ("column already has a value,
|
|
207
|
+
* leave it alone") was true from the first render and the model's label never
|
|
208
|
+
* reached a mapped column. A query-derived column kept showing its raw result
|
|
209
|
+
* key as the heading.
|
|
210
|
+
*/
|
|
211
|
+
const EXPLICITLY_OVERRIDDEN_FIELD_KEYS = {
|
|
212
|
+
label: 'label_overridden',
|
|
213
|
+
};
|
|
200
214
|
/**
|
|
201
215
|
* Datatype aliases the data model emits that mean an existing DataTypes value.
|
|
202
216
|
*
|
|
@@ -1149,6 +1163,30 @@ function parseCellDate(value, pattern, keepTime = false) {
|
|
|
1149
1163
|
* the interactive cell's job, and a composite that tried to host those would be
|
|
1150
1164
|
* a layout engine. A column needing that is a `page` cell.
|
|
1151
1165
|
*/
|
|
1166
|
+
/**
|
|
1167
|
+
* A status field with its `open_status` / `close_status` lists flattened into
|
|
1168
|
+
* the `options` array the status control actually reads for its colours.
|
|
1169
|
+
*
|
|
1170
|
+
* Shared rather than done at the call site: the status cell has always built
|
|
1171
|
+
* this, and a composite part handed the raw field instead rendered every pill
|
|
1172
|
+
* in the neutral fallback — the colour lives on the option, and without the
|
|
1173
|
+
* flattened list there is no option to match the value against.
|
|
1174
|
+
*/
|
|
1175
|
+
function toStatusOptionsField(field) {
|
|
1176
|
+
if (!field || normalizeDatatype(field.datatype) !== 'status')
|
|
1177
|
+
return null;
|
|
1178
|
+
const combined = [...(field.open_status || []), ...(field.close_status || [])];
|
|
1179
|
+
return {
|
|
1180
|
+
...field,
|
|
1181
|
+
options: combined.map((status) => ({
|
|
1182
|
+
name: status?.name,
|
|
1183
|
+
color: status?.color || '',
|
|
1184
|
+
df: status?.df || false,
|
|
1185
|
+
})),
|
|
1186
|
+
open_status: field.open_status || [],
|
|
1187
|
+
close_status: field.close_status || [],
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1152
1190
|
function formatCellValue(value, field) {
|
|
1153
1191
|
if (value === null || value === undefined || value === '')
|
|
1154
1192
|
return '';
|
|
@@ -4077,33 +4115,34 @@ class EruGridStore {
|
|
|
4077
4115
|
}
|
|
4078
4116
|
return this.rows();
|
|
4079
4117
|
}, ...(ngDevMode ? [{ debugName: "displayData" }] : []));
|
|
4118
|
+
// The frozen grand total is the row the transform appended (or prepended) to
|
|
4119
|
+
// the pivot data, so it is only there when the transform actually produced
|
|
4120
|
+
// one. It skips totalling an empty result, so `data` can be [] with the
|
|
4121
|
+
// grand-total config still on; indexing it then yields undefined and the
|
|
4122
|
+
// frozen-row template renders a row that has no `_rowKey`. Read the row
|
|
4123
|
+
// positionally but keep it only when it really is the grand total.
|
|
4124
|
+
frozenGrandTotalRow = computed(() => {
|
|
4125
|
+
if (!this.isPivotMode() || !this.pivotResult())
|
|
4126
|
+
return null;
|
|
4127
|
+
const config = this.configuration().config;
|
|
4128
|
+
if (!config?.enableGrandTotal || !config?.freezeGrandTotal)
|
|
4129
|
+
return null;
|
|
4130
|
+
const data = this.pivotResult().data;
|
|
4131
|
+
const row = config?.grandTotalPosition === 'before' ? data[0] : data[data.length - 1];
|
|
4132
|
+
return row?._isGrandTotal ? row : null;
|
|
4133
|
+
}, ...(ngDevMode ? [{ debugName: "frozenGrandTotalRow" }] : []));
|
|
4080
4134
|
// Type-safe accessors for specific modes
|
|
4081
4135
|
pivotGrandTotalData = computed(() => {
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
if (this.configuration().config?.enableGrandTotal) {
|
|
4085
|
-
if (this.configuration().config?.freezeGrandTotal) {
|
|
4086
|
-
if (this.configuration().config?.grandTotalPosition === 'before') {
|
|
4087
|
-
pd = [this.pivotResult().data[0]];
|
|
4088
|
-
}
|
|
4089
|
-
else {
|
|
4090
|
-
pd = [this.pivotResult().data[this.pivotResult().data.length - 1]];
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
}
|
|
4095
|
-
return pd;
|
|
4136
|
+
const row = this.frozenGrandTotalRow();
|
|
4137
|
+
return row ? [row] : [];
|
|
4096
4138
|
}, ...(ngDevMode ? [{ debugName: "pivotGrandTotalData" }] : []));
|
|
4097
4139
|
// Type-safe accessors for specific modes
|
|
4098
4140
|
pivotDisplayData = computed(() => {
|
|
4099
4141
|
if (this.isPivotMode() && this.pivotResult()) {
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
else {
|
|
4105
|
-
return this.pivotResult().data;
|
|
4106
|
-
}
|
|
4142
|
+
// Only drop the leading row when it is the frozen grand total that the
|
|
4143
|
+
// header renders separately, never a real data row.
|
|
4144
|
+
if (this.frozenGrandTotalRow() && this.configuration().config?.grandTotalPosition === 'before') {
|
|
4145
|
+
return this.pivotResult().data.slice(1);
|
|
4107
4146
|
}
|
|
4108
4147
|
return this.pivotResult().data;
|
|
4109
4148
|
}
|
|
@@ -4502,6 +4541,56 @@ class EruGridStore {
|
|
|
4502
4541
|
const rows = this.rows().map(row => row.entity_id === rowId ? { ...row, ...updates } : row);
|
|
4503
4542
|
this._rows.set(rows);
|
|
4504
4543
|
}
|
|
4544
|
+
/**
|
|
4545
|
+
* Merge field values into the record with this id, wherever the grid is
|
|
4546
|
+
* holding it — the flat row list AND the per-group buckets a board fills.
|
|
4547
|
+
*
|
|
4548
|
+
* For a consumer that edits a record somewhere else on the page and needs the
|
|
4549
|
+
* grid to agree without refetching. `updateRow` is not enough on two counts:
|
|
4550
|
+
* it only looks at `_rows`, so a grouped/board grid keeps the stale copy, and
|
|
4551
|
+
* it replaces top-level row keys rather than merging into `entity_data`,
|
|
4552
|
+
* where an entity-backed row keeps its fields.
|
|
4553
|
+
*
|
|
4554
|
+
* Unknown ids are ignored, so a caller can announce a change blindly.
|
|
4555
|
+
*/
|
|
4556
|
+
patchRecord(entityId, fields) {
|
|
4557
|
+
if (!entityId || !fields)
|
|
4558
|
+
return;
|
|
4559
|
+
const entries = Object.entries(fields);
|
|
4560
|
+
if (entries.length === 0)
|
|
4561
|
+
return;
|
|
4562
|
+
const patch = (row) => {
|
|
4563
|
+
if (!row || row.entity_id !== entityId)
|
|
4564
|
+
return row;
|
|
4565
|
+
const next = { ...row };
|
|
4566
|
+
if (next.entity_data && typeof next.entity_data === 'object') {
|
|
4567
|
+
next.entity_data = { ...next.entity_data, ...fields };
|
|
4568
|
+
}
|
|
4569
|
+
// An array-sourced row carries its fields at the top level instead. Only
|
|
4570
|
+
// keys the row already has are touched, so this never invents columns.
|
|
4571
|
+
for (const [key, value] of entries) {
|
|
4572
|
+
if (key !== 'entity_data' && Object.prototype.hasOwnProperty.call(next, key)) {
|
|
4573
|
+
next[key] = value;
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
return next;
|
|
4577
|
+
};
|
|
4578
|
+
const holdsRecord = (rows) => rows.some(row => row?.entity_id === entityId);
|
|
4579
|
+
if (holdsRecord(this._rows())) {
|
|
4580
|
+
this._rows.set(this._rows().map(patch));
|
|
4581
|
+
}
|
|
4582
|
+
this._groupRows.update(current => {
|
|
4583
|
+
let touched = false;
|
|
4584
|
+
const next = new Map(current);
|
|
4585
|
+
current.forEach((rows, groupId) => {
|
|
4586
|
+
if (!holdsRecord(rows))
|
|
4587
|
+
return;
|
|
4588
|
+
next.set(groupId, rows.map(patch));
|
|
4589
|
+
touched = true;
|
|
4590
|
+
});
|
|
4591
|
+
return touched ? next : current;
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4505
4594
|
// Selection methods
|
|
4506
4595
|
selectRow(rowId) {
|
|
4507
4596
|
const selectedRowIds = new Set(this.selectedRowIds());
|
|
@@ -11100,11 +11189,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
11100
11189
|
* Several of the row's own columns rendered as one cell — a name over an email,
|
|
11101
11190
|
* an amount over a date.
|
|
11102
11191
|
*
|
|
11103
|
-
* View-only
|
|
11104
|
-
* field's rules (`formatCellValue`), so a currency part keeps its symbol and
|
|
11105
|
-
* decimals and a date part keeps its pattern; the composite itself decides only
|
|
11106
|
-
* arrangement and emphasis. Editing stays on the underlying columns, which are
|
|
11192
|
+
* View-only by design: editing stays on the underlying columns, which are
|
|
11107
11193
|
* usually hidden once composed.
|
|
11194
|
+
*
|
|
11195
|
+
* A part whose datatype has a visual form of its own — a rating, a progress
|
|
11196
|
+
* bar, a status pill, tags, a priority, people, a checkbox — is drawn with that
|
|
11197
|
+
* control rather than as text, since "4" and "75" tell a reader far less than
|
|
11198
|
+
* four stars and a bar. Everything else is formatted by its OWN field's rules
|
|
11199
|
+
* (`formatCellValue`), so a currency part keeps its symbol and decimals and a
|
|
11200
|
+
* date part keeps its pattern — wrapping those in a control would render the
|
|
11201
|
+
* same string in a heavier box. The composite itself decides only arrangement
|
|
11202
|
+
* and emphasis.
|
|
11108
11203
|
*/
|
|
11109
11204
|
class CompositeComponent {
|
|
11110
11205
|
row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : []));
|
|
@@ -11121,6 +11216,25 @@ class CompositeComponent {
|
|
|
11121
11216
|
}, ...(ngDevMode ? [{ debugName: "labelPosition" }] : []));
|
|
11122
11217
|
primaryCss = computed(() => cellTextStyleToCss(this.column()?.cell_style), ...(ngDevMode ? [{ debugName: "primaryCss" }] : []));
|
|
11123
11218
|
secondaryCss = computed(() => cellTextStyleToCss(this.column()?.cell_style_secondary ?? DEFAULT_SECONDARY_STYLE), ...(ngDevMode ? [{ debugName: "secondaryCss" }] : []));
|
|
11219
|
+
/**
|
|
11220
|
+
* The third part and any beyond it.
|
|
11221
|
+
*
|
|
11222
|
+
* Falls back to the secondary style when unset, so every composite authored
|
|
11223
|
+
* before this existed keeps the look it had — three parts sharing one style
|
|
11224
|
+
* is precisely what this is for, not what it breaks.
|
|
11225
|
+
*/
|
|
11226
|
+
tertiaryCss = computed(() => cellTextStyleToCss(this.column()?.cell_style_tertiary
|
|
11227
|
+
?? this.column()?.cell_style_secondary
|
|
11228
|
+
?? DEFAULT_SECONDARY_STYLE), ...(ngDevMode ? [{ debugName: "tertiaryCss" }] : []));
|
|
11229
|
+
/**
|
|
11230
|
+
* Primary, secondary, then tertiary for the rest — keyed on the part's
|
|
11231
|
+
* configured slot, never on where it ended up after empty parts were dropped.
|
|
11232
|
+
*/
|
|
11233
|
+
cssForSlot(slot) {
|
|
11234
|
+
if (slot === 0)
|
|
11235
|
+
return this.primaryCss();
|
|
11236
|
+
return slot === 1 ? this.secondaryCss() : this.tertiaryCss();
|
|
11237
|
+
}
|
|
11124
11238
|
/**
|
|
11125
11239
|
* Falls back to the composite column itself when `composite_fields` is unset
|
|
11126
11240
|
* or names nothing that exists, so a column switched to `composite` before it
|
|
@@ -11135,7 +11249,8 @@ class CompositeComponent {
|
|
|
11135
11249
|
const row = this.row();
|
|
11136
11250
|
const labels = column.composite_labels || [];
|
|
11137
11251
|
const parts = names.map((name, index) => {
|
|
11138
|
-
const field = byName.get(name);
|
|
11252
|
+
const field = byName.get(name) ?? null;
|
|
11253
|
+
const value = readRowValue(row, name);
|
|
11139
11254
|
// Blank means no label for THIS part, never the field's own label as a
|
|
11140
11255
|
// fallback: a composite is usually built from columns whose labels are
|
|
11141
11256
|
// what the header already says, so falling back printed "Upload Date"
|
|
@@ -11144,15 +11259,98 @@ class CompositeComponent {
|
|
|
11144
11259
|
return {
|
|
11145
11260
|
name,
|
|
11146
11261
|
label: String(labels[index] ?? '').trim(),
|
|
11147
|
-
text: formatCellValue(
|
|
11262
|
+
text: formatCellValue(value, CompositeComponent.withRowSymbol(field, row)),
|
|
11263
|
+
field,
|
|
11264
|
+
value,
|
|
11265
|
+
numeric: CompositeComponent.isNumeric(field),
|
|
11266
|
+
render: CompositeComponent.selfDrawnDatatype(field),
|
|
11267
|
+
slot: index,
|
|
11148
11268
|
};
|
|
11149
11269
|
});
|
|
11150
|
-
|
|
11270
|
+
// A drawn part counts as populated even with an empty formatted string: an
|
|
11271
|
+
// unrated row still shows its empty stars, and a 0% bar is a real reading.
|
|
11272
|
+
const populated = parts.filter(part => part.text !== '' || part.render !== '');
|
|
11151
11273
|
if (populated.length > 0)
|
|
11152
11274
|
return populated;
|
|
11153
|
-
const own = formatCellValue(readRowValue(row, column.name), column);
|
|
11154
|
-
return own === '' ? [] : [{
|
|
11275
|
+
const own = formatCellValue(readRowValue(row, column.name), CompositeComponent.withRowSymbol(column, row));
|
|
11276
|
+
return own === '' ? [] : [{
|
|
11277
|
+
name: column.name,
|
|
11278
|
+
label: column.label,
|
|
11279
|
+
text: own,
|
|
11280
|
+
numeric: CompositeComponent.isNumeric(column),
|
|
11281
|
+
field: column,
|
|
11282
|
+
value: readRowValue(row, column.name),
|
|
11283
|
+
render: '',
|
|
11284
|
+
slot: 0,
|
|
11285
|
+
}];
|
|
11155
11286
|
}, ...(ngDevMode ? [{ debugName: "parts" }] : []));
|
|
11287
|
+
/**
|
|
11288
|
+
* Datatypes drawn with their own control instead of as text.
|
|
11289
|
+
*
|
|
11290
|
+
* The test is "does this type carry meaning a string cannot" — stars, a bar,
|
|
11291
|
+
* a coloured pill, chips, avatars, a tick. A currency or a date is already
|
|
11292
|
+
* fully said by `formatCellValue`, so it stays text.
|
|
11293
|
+
*/
|
|
11294
|
+
static SELF_DRAWN = new Set([
|
|
11295
|
+
'rating', 'progress', 'status', 'tag', 'priority', 'people', 'checkbox',
|
|
11296
|
+
]);
|
|
11297
|
+
/**
|
|
11298
|
+
* The field with this ROW's currency symbol resolved onto it.
|
|
11299
|
+
*
|
|
11300
|
+
* `formatCellValue` only knows the column's static `symbol`, but a
|
|
11301
|
+
* multi-currency column names a sibling column in `symbol_field` and takes
|
|
11302
|
+
* the symbol from each record — which is how the currency CELL renders it
|
|
11303
|
+
* (it is handed the row for exactly this). Without it a composed currency
|
|
11304
|
+
* part lost its symbol and read as a bare number.
|
|
11305
|
+
*/
|
|
11306
|
+
static withRowSymbol(field, row) {
|
|
11307
|
+
const symbolField = String(field?.symbol_field || '').trim();
|
|
11308
|
+
if (!field || !symbolField)
|
|
11309
|
+
return field;
|
|
11310
|
+
const raw = readRowValue(row, symbolField);
|
|
11311
|
+
const symbol = raw === null || raw === undefined ? '' : String(raw).trim();
|
|
11312
|
+
return symbol ? { ...field, symbol } : field;
|
|
11313
|
+
}
|
|
11314
|
+
/** Figures are right-aligned by their own cell, so a composed one is too. */
|
|
11315
|
+
static isNumeric(field) {
|
|
11316
|
+
const datatype = effectiveValueDatatype(field ?? undefined);
|
|
11317
|
+
return datatype === 'currency' || datatype === 'number';
|
|
11318
|
+
}
|
|
11319
|
+
static selfDrawnDatatype(field) {
|
|
11320
|
+
const datatype = normalizeDatatype(field?.datatype ?? '');
|
|
11321
|
+
return CompositeComponent.SELF_DRAWN.has(datatype) ? datatype : '';
|
|
11322
|
+
}
|
|
11323
|
+
/**
|
|
11324
|
+
* A status part needs its option colours flattened the same way the status
|
|
11325
|
+
* CELL does, or every pill renders in the neutral fallback.
|
|
11326
|
+
*/
|
|
11327
|
+
statusConfig(field) {
|
|
11328
|
+
return toStatusOptionsField(field);
|
|
11329
|
+
}
|
|
11330
|
+
/** Rating takes a shape of its own rather than the raw Field. */
|
|
11331
|
+
ratingConfig(field) {
|
|
11332
|
+
const config = field;
|
|
11333
|
+
return {
|
|
11334
|
+
startValue: config?.start_value || 1,
|
|
11335
|
+
endValue: config?.end_value || 5,
|
|
11336
|
+
emojiValue: config?.emoji_value || 'star',
|
|
11337
|
+
};
|
|
11338
|
+
}
|
|
11339
|
+
/** Controls want their own types; a row value can arrive as anything. */
|
|
11340
|
+
asNumber(value) {
|
|
11341
|
+
const n = Number(value);
|
|
11342
|
+
return Number.isFinite(n) ? n : 0;
|
|
11343
|
+
}
|
|
11344
|
+
asText(value) {
|
|
11345
|
+
return value === null || value === undefined ? null : String(value);
|
|
11346
|
+
}
|
|
11347
|
+
asList(value) {
|
|
11348
|
+
if (Array.isArray(value))
|
|
11349
|
+
return value.map(v => String(v));
|
|
11350
|
+
if (value === null || value === undefined || value === '')
|
|
11351
|
+
return null;
|
|
11352
|
+
return String(value).split(',').map(v => v.trim()).filter(Boolean);
|
|
11353
|
+
}
|
|
11156
11354
|
onDrilldown(event, part) {
|
|
11157
11355
|
if (!this.isDrillable())
|
|
11158
11356
|
return;
|
|
@@ -11160,11 +11358,20 @@ class CompositeComponent {
|
|
|
11160
11358
|
this.drilldownClick.emit(part.text);
|
|
11161
11359
|
}
|
|
11162
11360
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CompositeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11163
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CompositeComponent, isStandalone: true, selector: "eru-composite", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drilldownClick: "drilldownClick" }, ngImport: i0, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"
|
|
11361
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CompositeComponent, isStandalone: true, selector: "eru-composite", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drilldownClick: "drilldownClick" }, ngImport: i0, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n @if (part.render) {\n <!-- A datatype that draws itself. Rendered read-only with pointer events\n off (see .eru-composite__control): a composite is a view, so a rating\n here must not take a click that would edit the underlying column. The\n secondary slot scales the control down so the primary still leads. -->\n <span class=\"eru-composite__control\" [ngClass]=\"'eru-composite__control--' + part.render\"\n [class.eru-composite__control--secondary]=\"part.slot > 0\">\n @switch (part.render) {\n @case ('rating') {\n <eru-rating [value]=\"asNumber(part.value)\" [config]=\"ratingConfig(part.field)\" [isEditable]=\"false\"></eru-rating>\n }\n @case ('progress') {\n <eru-progress [value]=\"asNumber(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-progress>\n }\n @case ('status') {\n <eru-status [value]=\"asText(part.value)\" [config]=\"statusConfig(part.field)\" [isEditable]=\"false\"></eru-status>\n }\n @case ('tag') {\n <eru-tag [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-tag>\n }\n @case ('priority') {\n <eru-priority [value]=\"asText(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-priority>\n }\n @case ('people') {\n <eru-people [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-people>\n }\n @case ('checkbox') {\n <eru-checkbox [value]=\"part.value\" [config]=\"part.field\" [isEditable]=\"false\"></eru-checkbox>\n }\n }\n </span>\n } @else {\n <span class=\"eru-composite__part\" [class.eru-composite__part--secondary]=\"part.slot > 0\"\n [class.eru-composite__part--numeric]=\"part.numeric\"\n [class.eru-composite__part--drillable]=\"isDrillable() && part.slot === 0\" [ngStyle]=\"cssForSlot(part.slot)\"\n [title]=\"part.text\" (click)=\"onDrilldown($event, part)\">{{ part.text }}</span>\n }\n @if (labelPosition() === 'right' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n </span>\n @if (inline() && !last && separator()) {\n <span class=\"eru-composite__sep\" [ngStyle]=\"secondaryCss()\">{{ separator() }}</span>\n }\n }\n</div>\n", styles: [".eru-composite{display:flex;flex-direction:column;justify-content:center;gap:3px;min-width:0;width:100%;height:100%;line-height:1.3;box-sizing:border-box;padding:0 var(--grid-cell-inset-x, 8px)}.eru-composite--inline{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:4px}.eru-composite__part{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.eru-composite__part--drillable{color:var(--grid-primary);cursor:pointer;text-decoration:underline}.eru-composite__sep{flex:0 0 auto}.eru-composite__part--secondary{line-height:1.15}.eru-composite__slot{display:contents}.eru-composite--labelled .eru-composite__slot{display:inline-flex;align-items:baseline;gap:4px;min-width:0}.eru-composite__label{flex:0 0 auto;white-space:nowrap;opacity:.75}.eru-composite__control{display:inline-flex;align-items:center;justify-content:flex-start;min-width:0;pointer-events:none}.eru-composite__control--progress{display:flex;width:100%}.eru-composite__control--progress>eru-progress{display:block;flex:1 1 auto;width:100%;min-width:0}.eru-composite--inline .eru-composite__control--progress{width:auto;min-width:72px}.eru-composite__control>*{margin-left:0;margin-right:0}.eru-composite__control--status .status-display,.eru-composite__control--status .status-dot-display{margin:0}.eru-composite__control--tag .tag-display,.eru-composite__control--priority .priority-display{padding:0}.eru-composite__control--progress .progress-view-container{padding:0;max-height:none}.eru-composite__control--secondary{transform:scale(.78);transform-origin:left center;margin-right:-22%}.eru-composite:not(.eru-composite--inline) .eru-composite__part--numeric{text-align:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: RatingComponent, selector: "eru-rating", inputs: ["value", "config", "isEditable", "isActive"], outputs: ["valueChange", "editModeChange"] }, { kind: "component", type: ProgressComponent, selector: "eru-progress", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: StatusComponent, selector: "eru-status", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: TagComponent, selector: "eru-tag", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange", "newTagAdded"] }, { kind: "component", type: PriorityComponent, selector: "eru-priority", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: PeopleComponent, selector: "eru-people", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "eruGridStore", "personCardTemplate"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: CheckboxComponent, selector: "eru-checkbox", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "label"], outputs: ["valueChange", "change", "blur", "focus", "drilldownClick", "editModeChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
11164
11362
|
}
|
|
11165
11363
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CompositeComponent, decorators: [{
|
|
11166
11364
|
type: Component,
|
|
11167
|
-
args: [{ selector: 'eru-composite', standalone: true, imports: [
|
|
11365
|
+
args: [{ selector: 'eru-composite', standalone: true, imports: [
|
|
11366
|
+
CommonModule,
|
|
11367
|
+
RatingComponent,
|
|
11368
|
+
ProgressComponent,
|
|
11369
|
+
StatusComponent,
|
|
11370
|
+
TagComponent,
|
|
11371
|
+
PriorityComponent,
|
|
11372
|
+
PeopleComponent,
|
|
11373
|
+
CheckboxComponent,
|
|
11374
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n @if (part.render) {\n <!-- A datatype that draws itself. Rendered read-only with pointer events\n off (see .eru-composite__control): a composite is a view, so a rating\n here must not take a click that would edit the underlying column. The\n secondary slot scales the control down so the primary still leads. -->\n <span class=\"eru-composite__control\" [ngClass]=\"'eru-composite__control--' + part.render\"\n [class.eru-composite__control--secondary]=\"part.slot > 0\">\n @switch (part.render) {\n @case ('rating') {\n <eru-rating [value]=\"asNumber(part.value)\" [config]=\"ratingConfig(part.field)\" [isEditable]=\"false\"></eru-rating>\n }\n @case ('progress') {\n <eru-progress [value]=\"asNumber(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-progress>\n }\n @case ('status') {\n <eru-status [value]=\"asText(part.value)\" [config]=\"statusConfig(part.field)\" [isEditable]=\"false\"></eru-status>\n }\n @case ('tag') {\n <eru-tag [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-tag>\n }\n @case ('priority') {\n <eru-priority [value]=\"asText(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-priority>\n }\n @case ('people') {\n <eru-people [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-people>\n }\n @case ('checkbox') {\n <eru-checkbox [value]=\"part.value\" [config]=\"part.field\" [isEditable]=\"false\"></eru-checkbox>\n }\n }\n </span>\n } @else {\n <span class=\"eru-composite__part\" [class.eru-composite__part--secondary]=\"part.slot > 0\"\n [class.eru-composite__part--numeric]=\"part.numeric\"\n [class.eru-composite__part--drillable]=\"isDrillable() && part.slot === 0\" [ngStyle]=\"cssForSlot(part.slot)\"\n [title]=\"part.text\" (click)=\"onDrilldown($event, part)\">{{ part.text }}</span>\n }\n @if (labelPosition() === 'right' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n </span>\n @if (inline() && !last && separator()) {\n <span class=\"eru-composite__sep\" [ngStyle]=\"secondaryCss()\">{{ separator() }}</span>\n }\n }\n</div>\n", styles: [".eru-composite{display:flex;flex-direction:column;justify-content:center;gap:3px;min-width:0;width:100%;height:100%;line-height:1.3;box-sizing:border-box;padding:0 var(--grid-cell-inset-x, 8px)}.eru-composite--inline{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:4px}.eru-composite__part{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.eru-composite__part--drillable{color:var(--grid-primary);cursor:pointer;text-decoration:underline}.eru-composite__sep{flex:0 0 auto}.eru-composite__part--secondary{line-height:1.15}.eru-composite__slot{display:contents}.eru-composite--labelled .eru-composite__slot{display:inline-flex;align-items:baseline;gap:4px;min-width:0}.eru-composite__label{flex:0 0 auto;white-space:nowrap;opacity:.75}.eru-composite__control{display:inline-flex;align-items:center;justify-content:flex-start;min-width:0;pointer-events:none}.eru-composite__control--progress{display:flex;width:100%}.eru-composite__control--progress>eru-progress{display:block;flex:1 1 auto;width:100%;min-width:0}.eru-composite--inline .eru-composite__control--progress{width:auto;min-width:72px}.eru-composite__control>*{margin-left:0;margin-right:0}.eru-composite__control--status .status-display,.eru-composite__control--status .status-dot-display{margin:0}.eru-composite__control--tag .tag-display,.eru-composite__control--priority .priority-display{padding:0}.eru-composite__control--progress .progress-view-container{padding:0;max-height:none}.eru-composite__control--secondary{transform:scale(.78);transform-origin:left center;margin-right:-22%}.eru-composite:not(.eru-composite--inline) .eru-composite__part--numeric{text-align:right}\n"] }]
|
|
11168
11375
|
}], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], drilldownClick: [{ type: i0.Output, args: ["drilldownClick"] }] } });
|
|
11169
11376
|
const DEFAULT_SECONDARY_STYLE = {
|
|
11170
11377
|
font_size: 11,
|
|
@@ -11616,32 +11823,9 @@ class DataCellComponent {
|
|
|
11616
11823
|
onSelectDrilldown(value) {
|
|
11617
11824
|
this.handleDrilldown(value);
|
|
11618
11825
|
}
|
|
11619
|
-
// Status component handlers
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
if (!config || config.datatype !== 'status') {
|
|
11623
|
-
return null;
|
|
11624
|
-
}
|
|
11625
|
-
// Combine open and close status
|
|
11626
|
-
const combinedStatus = [
|
|
11627
|
-
...(config.open_status || []),
|
|
11628
|
-
...(config.close_status || [])
|
|
11629
|
-
];
|
|
11630
|
-
// Transform to Field format with options array
|
|
11631
|
-
// Each option should have 'name' and 'color' properties
|
|
11632
|
-
const options = combinedStatus.map((status) => ({
|
|
11633
|
-
name: status.name,
|
|
11634
|
-
color: status.color || '',
|
|
11635
|
-
df: status.df || false
|
|
11636
|
-
}));
|
|
11637
|
-
// Return Field object with combined options and preserved open/close arrays
|
|
11638
|
-
return {
|
|
11639
|
-
...config,
|
|
11640
|
-
options: options,
|
|
11641
|
-
open_status: config.open_status || [],
|
|
11642
|
-
close_status: config.close_status || []
|
|
11643
|
-
};
|
|
11644
|
-
}, ...(ngDevMode ? [{ debugName: "getStatusConfig" }] : []));
|
|
11826
|
+
// Status component handlers. The flattening lives in the model so a composed
|
|
11827
|
+
// status part builds the same config from the same definition.
|
|
11828
|
+
getStatusConfig = computed(() => toStatusOptionsField(this.columnCellConfiguration()), ...(ngDevMode ? [{ debugName: "getStatusConfig" }] : []));
|
|
11645
11829
|
onStatusBlur(value) {
|
|
11646
11830
|
this.currentValue.set(value);
|
|
11647
11831
|
this.eruGridStore().setActiveCell(null);
|
|
@@ -13337,6 +13521,14 @@ const COMPOSITE_FIELDS = [
|
|
|
13337
13521
|
showWhen: f => f.composite_label_position === 'left' || f.composite_label_position === 'right',
|
|
13338
13522
|
},
|
|
13339
13523
|
{ key: 'cell_style_secondary', label: 'Secondary value style', control: 'text_style' },
|
|
13524
|
+
{
|
|
13525
|
+
// Only once a third part exists: on a two-part composite the control would
|
|
13526
|
+
// style nothing, and the panel already runs long.
|
|
13527
|
+
key: 'cell_style_tertiary',
|
|
13528
|
+
label: 'Third value style',
|
|
13529
|
+
control: 'text_style',
|
|
13530
|
+
showWhen: f => (f.composite_fields || []).filter(Boolean).length > 2,
|
|
13531
|
+
},
|
|
13340
13532
|
];
|
|
13341
13533
|
const PAGE_CELL_FIELDS = [
|
|
13342
13534
|
{ key: 'cell_page_id', label: 'Cell page', control: 'text' },
|
|
@@ -13756,9 +13948,13 @@ class ColumnDesignPanelComponent {
|
|
|
13756
13948
|
* drift apart: a key that is inherited is always locked, and vice versa.
|
|
13757
13949
|
*
|
|
13758
13950
|
* SEEDED_FIELD_KEYS are the exception the resolver makes too: the model seeds
|
|
13759
|
-
* them, this grid may then override them, so they stay editable here.
|
|
13951
|
+
* them, this grid may then override them, so they stay editable here. So are
|
|
13952
|
+
* EXPLICITLY_OVERRIDDEN_FIELD_KEYS, seeded the same way but tracked by a flag
|
|
13953
|
+
* — `label` among them, which must stay typeable for the flag to ever be set.
|
|
13760
13954
|
*/
|
|
13761
|
-
static INHERITED_KEYS = new Set(INHERITED_FIELD_KEYS.filter(k => !SEEDED_FIELD_KEYS.includes(k) &&
|
|
13955
|
+
static INHERITED_KEYS = new Set(INHERITED_FIELD_KEYS.filter(k => !SEEDED_FIELD_KEYS.includes(k) &&
|
|
13956
|
+
!EXPLICITLY_OVERRIDDEN_FIELD_KEYS[k] &&
|
|
13957
|
+
k !== 'datatype'));
|
|
13762
13958
|
isInherited(key) {
|
|
13763
13959
|
return this.isEntityMapped() && ColumnDesignPanelComponent.INHERITED_KEYS.has(key);
|
|
13764
13960
|
}
|
|
@@ -14045,6 +14241,20 @@ class ColumnDesignPanelComponent {
|
|
|
14045
14241
|
this.gridStore.updateColumnMeta(name, { [key]: value });
|
|
14046
14242
|
}
|
|
14047
14243
|
onText(key, value) {
|
|
14244
|
+
// A typed heading is the author's, and stops the mapped field's label from
|
|
14245
|
+
// seeding it on the next resolve; clearing the box hands the column back to
|
|
14246
|
+
// the data model rather than pinning it to an empty header.
|
|
14247
|
+
const flag = EXPLICITLY_OVERRIDDEN_FIELD_KEYS[key];
|
|
14248
|
+
if (flag) {
|
|
14249
|
+
const name = this.gridStore.selectedDesignColumn();
|
|
14250
|
+
if (!name)
|
|
14251
|
+
return;
|
|
14252
|
+
const typed = String(value ?? '').trim();
|
|
14253
|
+
this.gridStore.updateColumnMeta(name, typed
|
|
14254
|
+
? { [key]: value, [flag]: true }
|
|
14255
|
+
: { [key]: null, [flag]: null });
|
|
14256
|
+
return;
|
|
14257
|
+
}
|
|
14048
14258
|
this.patch(key, value);
|
|
14049
14259
|
}
|
|
14050
14260
|
onNumber(key, value) {
|
|
@@ -15789,7 +15999,7 @@ class EruGridComponent {
|
|
|
15789
15999
|
}
|
|
15790
16000
|
}
|
|
15791
16001
|
trackByPivotRowFn(index, pivotRow) {
|
|
15792
|
-
return pivotRow
|
|
16002
|
+
return pivotRow?._rowKey || `pivot-row-${index}`;
|
|
15793
16003
|
}
|
|
15794
16004
|
trackByRowFn(index, row) {
|
|
15795
16005
|
// Use both entity_id and index to ensure uniqueness even with duplicate entity_ids
|
|
@@ -16121,6 +16331,11 @@ class EruGridComponent {
|
|
|
16121
16331
|
* including an explicit `false` on a checkbox — wins from then on. Without the
|
|
16122
16332
|
* column in hand there is no way to tell "not set" from "set to the opposite",
|
|
16123
16333
|
* so an override was overwritten on every resolve.
|
|
16334
|
+
*
|
|
16335
|
+
* EXPLICITLY_OVERRIDDEN_FIELD_KEYS work the same way but ask a companion flag
|
|
16336
|
+
* instead, for keys a column always holds a value for — `label`, whose
|
|
16337
|
+
* "already set" was true from the first render, so the model's label never
|
|
16338
|
+
* won and a mapped column kept its raw result key as the heading.
|
|
16124
16339
|
*/
|
|
16125
16340
|
buildMappedColumnPatch(meta, col) {
|
|
16126
16341
|
const patch = {};
|
|
@@ -16132,6 +16347,9 @@ class EruGridComponent {
|
|
|
16132
16347
|
continue;
|
|
16133
16348
|
if (seeded.has(k) && !isUnset(col?.[k]))
|
|
16134
16349
|
continue;
|
|
16350
|
+
const overrideFlag = EXPLICITLY_OVERRIDDEN_FIELD_KEYS[k];
|
|
16351
|
+
if (overrideFlag && col?.[overrideFlag])
|
|
16352
|
+
continue;
|
|
16135
16353
|
patch[k] = v;
|
|
16136
16354
|
}
|
|
16137
16355
|
// setColumns() stores the canonical datatype ('r_status' -> 'status'), so a
|
|
@@ -17033,5 +17251,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
17033
17251
|
* Generated bundle index. Do not edit.
|
|
17034
17252
|
*/
|
|
17035
17253
|
|
|
17036
|
-
export { ACTION_COLUMN_MIN_WIDTH, AttachmentComponent, CELL_RULE_OPERATORS, CheckboxComponent, ChipListComponent, ColumnConstraintsService, CompositeComponent, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, GRID_COLOR_TOKENS, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, NumericInputDirective, PRESENTATION_DATATYPES, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SEEDED_FIELD_KEYS, SELF_COLOURED_DATATYPES, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, abbreviateNumber, abbreviationScaleFor, cellRuleBarPercent, cellRuleMatches, cellRuleToCss, cellStatAlias, cellTextStyleToCss, collectColumnStatRequests, columnCellRules, composeColorValue, effectiveValueDatatype, evaluateRowCondition, formatCellValue, formatDateTimeWithPattern, formatDateWithPattern, formatNumberValue, hasOwnCellRules, matchCellRule, matchColorRange, normalizeCellRender, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseCellDate, parseCellDateTime, parseColorValue, parseDateWithPattern, readRowValue, resolveColumnRules, resolveRowValue, resolveStatValue, rowSelectionKey, statusPillColors, tagPillColors, toDateOnlyString, toDateTimeString };
|
|
17254
|
+
export { ACTION_COLUMN_MIN_WIDTH, AttachmentComponent, CELL_RULE_OPERATORS, CheckboxComponent, ChipListComponent, ColumnConstraintsService, CompositeComponent, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EXPLICITLY_OVERRIDDEN_FIELD_KEYS, EmailComponent, EruGridComponent, EruGridService, EruGridStore, GRID_COLOR_TOKENS, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, NumericInputDirective, PRESENTATION_DATATYPES, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SEEDED_FIELD_KEYS, SELF_COLOURED_DATATYPES, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, abbreviateNumber, abbreviationScaleFor, cellRuleBarPercent, cellRuleMatches, cellRuleToCss, cellStatAlias, cellTextStyleToCss, collectColumnStatRequests, columnCellRules, composeColorValue, effectiveValueDatatype, evaluateRowCondition, formatCellValue, formatDateTimeWithPattern, formatDateWithPattern, formatNumberValue, hasOwnCellRules, matchCellRule, matchColorRange, normalizeCellRender, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseCellDate, parseCellDateTime, parseColorValue, parseDateWithPattern, readRowValue, resolveColumnRules, resolveRowValue, resolveStatValue, rowSelectionKey, statusPillColors, tagPillColors, toDateOnlyString, toDateTimeString, toStatusOptionsField };
|
|
17037
17255
|
//# sourceMappingURL=eru-grid.mjs.map
|