eru-grid 0.0.39 → 0.0.41

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.
@@ -15,7 +15,7 @@ import { MatSliderModule } from '@angular/material/slider';
15
15
  import * as i2$1 from '@angular/material/icon';
16
16
  import { MatIconModule } from '@angular/material/icon';
17
17
  import * as i2$4 from '@angular/common';
18
- import { DatePipe, CommonModule } from '@angular/common';
18
+ import { CommonModule, DatePipe } from '@angular/common';
19
19
  import * as i4$2 from '@angular/material/datepicker';
20
20
  import { MatDatepickerModule } from '@angular/material/datepicker';
21
21
  import * as i1$1 from '@angular/material/core';
@@ -39,6 +39,296 @@ import { MatCardModule } from '@angular/material/card';
39
39
  import * as i7 from '@angular/material/menu';
40
40
  import { MatMenuModule } from '@angular/material/menu';
41
41
 
42
+ // Fields that a preset enforces. When preset !== 'custom', the library ignores
43
+ // any user-provided value for these fields and applies the preset's default.
44
+ // Users who need to tweak any of these must switch preset to 'custom' first.
45
+ const PRESET_MANAGED_FIELDS = [
46
+ 'showColumnLines',
47
+ 'showRowLines',
48
+ 'headerRowHeight',
49
+ 'dataRowHeight',
50
+ ];
51
+ // Per-preset enforced defaults for the preset-managed fields. Keep the
52
+ // surfaces (divider colors, header bg, radius, shadow, density) in SCSS —
53
+ // these are purely config-level values that pair with the SCSS DNA.
54
+ const PRESET_CONFIG_DEFAULTS = {
55
+ default: { showColumnLines: false, showRowLines: true, headerRowHeight: 40, dataRowHeight: 36 },
56
+ modern: { showColumnLines: false, showRowLines: true, headerRowHeight: 52, dataRowHeight: 52 },
57
+ compact: { showColumnLines: false, showRowLines: true, headerRowHeight: 28, dataRowHeight: 24 },
58
+ bold: { showColumnLines: true, showRowLines: true, headerRowHeight: 46, dataRowHeight: 38 },
59
+ financial: { showColumnLines: false, showRowLines: true, headerRowHeight: 40, dataRowHeight: 34 },
60
+ elevated: { showColumnLines: false, showRowLines: true, headerRowHeight: 50, dataRowHeight: 46 },
61
+ };
62
+ /**
63
+ * Field attributes a mapped column inherits from the data model.
64
+ *
65
+ * Single source of truth for two things that must agree: what
66
+ * `buildMappedColumnPatch()` merges onto the column on every load, and what the
67
+ * column design panel renders read-only. If a key is inherited but not locked,
68
+ * a user's edit is silently discarded on reload; if it is locked but not
69
+ * inherited, the control is dead. Keep both behaviours driven from this list.
70
+ *
71
+ * The rule: anything the data model authors belongs here; anything that is a
72
+ * property of *this view* of the field does not. Deliberately excluded —
73
+ * field_size, grid_index layout, set by column resize/reorder
74
+ * tool_tip per-grid header help text
75
+ * is_hidden hiding a column in one grid should not require
76
+ * hiding the field in the data model
77
+ * dateTimeFormat legacy alias; the model writes `date_format`
78
+ */
79
+ const INHERITED_FIELD_KEYS = [
80
+ // Identity and presentation
81
+ 'datatype', 'label', 'description', 'default',
82
+ // Integrity: a rollup or locked field must not be editable anywhere
83
+ 'mandatory', 'editable',
84
+ // Option sources (dpef = dependent/cascading parent field)
85
+ 'options', 'option_type', 'entity_name', 'field_name', 'api_name', 'api_field',
86
+ 'df_fields', 'dpef', 'def',
87
+ // Choice colours
88
+ 'open_status', 'close_status', 'color_ranges',
89
+ // Numeric formatting and bounds
90
+ 'decimal', 'symbol', 'symbol_field', 'seperator', 'dynamic_number',
91
+ 'display_number_as', 'num_val', 'num_val_check',
92
+ // Temporal
93
+ 'date_format', 'allow_days',
94
+ // Text
95
+ 'data_length', 'data_length_check', 'rich_text',
96
+ // Checkbox stored representation
97
+ 'value_true', 'value_false',
98
+ // Website
99
+ 'is_hyp', 'hypl_nm',
100
+ // Scales
101
+ 'start_value', 'end_value', 'emoji_value', 'is_perc',
102
+ // Attachment constraints
103
+ 'max_files', 'allowed_file_types', 'max_file_size',
104
+ // Phone
105
+ 'default_country',
106
+ // Cardinality and picker behaviour
107
+ 'multiple', 'searchable', 'max_avatars', 'people_card', 'show_people_card',
108
+ // Governance — authored in the data model, never overridden per grid
109
+ 'is_pii', 'is_ephi', 'is_pf', 'to_encrypt', 'is_unique', 'unq_sa',
110
+ 'system_validate', 'ssv_api', 'can_group', 'default_group',
111
+ ];
112
+ /**
113
+ * Datatype aliases the data model emits that mean an existing DataTypes value.
114
+ *
115
+ * `r_status` is what the backend sends for a record-status field (e.g. a
116
+ * system `Record Status` column); it behaves exactly like `status` — same
117
+ * open/close lists, same cell, same design-panel controls. Normalising here
118
+ * keeps that as one line instead of an extra `case` in every switch that
119
+ * handles status.
120
+ */
121
+ const DATATYPE_ALIASES = {
122
+ r_status: 'status',
123
+ };
124
+ /** Resolve a data-model datatype to the canonical DataTypes value. */
125
+ function normalizeDatatype(datatype) {
126
+ const key = String(datatype ?? '');
127
+ return DATATYPE_ALIASES[key] ?? key;
128
+ }
129
+ /** Short month names, for the `MMM` token. */
130
+ const MONTH_SHORT_NAMES = [
131
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
132
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
133
+ ];
134
+ /**
135
+ * The date formats offered to users, in every layer.
136
+ *
137
+ * One list so eru-studio, eru-grid and the processo data model present the same
138
+ * choices — previously studio offered eight, while the grid's panel and processo
139
+ * offered three, so a format could be picked on a page and had no equivalent on
140
+ * a field.
141
+ *
142
+ * Values are in token form: `dd` day, `MM` month number, `MMM` month name,
143
+ * `yyyy` year. Casing is significant — lowercase `mm` means minutes in a
144
+ * datetime — so values are normalised rather than lowercased on read.
145
+ */
146
+ const DATE_FORMATS = [
147
+ { value: 'dd-MM-yyyy', label: 'DD-MM-YYYY' },
148
+ { value: 'MM-dd-yyyy', label: 'MM-DD-YYYY' },
149
+ { value: 'yyyy-MM-dd', label: 'YYYY-MM-DD' },
150
+ { value: 'dd/MM/yyyy', label: 'DD/MM/YYYY' },
151
+ { value: 'MM/dd/yyyy', label: 'MM/DD/YYYY' },
152
+ { value: 'yyyy/MM/dd', label: 'YYYY/MM/DD' },
153
+ { value: 'dd.MM.yyyy', label: 'DD.MM.YYYY' },
154
+ { value: 'MM.dd.yyyy', label: 'MM.DD.YYYY' },
155
+ // Month as a word, which reads unambiguously across locales — 02-Aug-2026
156
+ // cannot be misread the way 02-08-2026 can.
157
+ { value: 'dd-MMM-yyyy', label: 'DD-MMM-YYYY (02-Aug-2026)' },
158
+ { value: 'MMM-dd-yyyy', label: 'MMM-DD-YYYY (Aug-02-2026)' },
159
+ { value: 'dd MMM yyyy', label: 'DD MMM YYYY (02 Aug 2026)' },
160
+ { value: 'MMM dd, yyyy', label: 'MMM DD, YYYY (Aug 02, 2026)' },
161
+ ];
162
+ /** The same orderings with a 24-hour time part, for datetime fields. */
163
+ const DATETIME_FORMATS = DATE_FORMATS.map(f => ({
164
+ value: `${f.value} hh:mm:ss`,
165
+ label: `${f.label.split(' (')[0]} HH:MM:SS`,
166
+ }));
167
+ /**
168
+ * Normalise a date format to token casing, so a value saved in any casing
169
+ * matches the canonical option. The data model uppercases on save
170
+ * (`DD-MMM-YYYY`) and older UIs stored lowercase.
171
+ *
172
+ * `mmm` is handled before `mm`, or the longer token would be half-consumed and
173
+ * `dd-mmm-yyyy` would come out as `dd-MMm-yyyy`.
174
+ */
175
+ function normalizeDateFormat(format) {
176
+ if (!format)
177
+ return '';
178
+ return String(format)
179
+ .toLowerCase()
180
+ .replace(/mmm/g, '\u0000')
181
+ .replace(/mm/g, 'MM')
182
+ .replace(/\u0000/g, 'MMM');
183
+ }
184
+ /**
185
+ * Normalise a datetime format. Only the date half is token-cased — `mm` in the
186
+ * time half is minutes and must stay lowercase, which is why this cannot just
187
+ * reuse normalizeDateFormat over the whole string. The date half is everything
188
+ * before the time, located by the `hh` token rather than by whitespace, since a
189
+ * date pattern may itself contain a space (`dd MMM yyyy`).
190
+ */
191
+ function normalizeDateTimeFormat(format) {
192
+ if (!format)
193
+ return '';
194
+ const raw = String(format).trim();
195
+ const timeAt = raw.toLowerCase().indexOf('hh');
196
+ if (timeAt === -1)
197
+ return normalizeDateFormat(raw);
198
+ const datePart = raw.slice(0, timeAt).trimEnd();
199
+ const timePart = raw.slice(timeAt).toLowerCase();
200
+ return `${normalizeDateFormat(datePart)} ${timePart}`;
201
+ }
202
+ /**
203
+ * Render a date with a token pattern (`dd`, `MM`, `MMM`, `yyyy`).
204
+ *
205
+ * Shared by both libraries so a date reads identically on a page and in a grid.
206
+ * The alternation lists longer tokens first, so `MMM` is matched before `MM`
207
+ * and `yyyy` is never partly consumed — the bug that a chain of string
208
+ * replacements invites.
209
+ */
210
+ function formatDateWithPattern(date, pattern) {
211
+ if (!date || isNaN(date.getTime()))
212
+ return '';
213
+ const pad = (n) => (n < 10 ? `0${n}` : String(n));
214
+ return normalizeDateFormat(pattern).replace(/yyyy|MMM|MM|dd/g, token => {
215
+ switch (token) {
216
+ case 'yyyy': return String(date.getFullYear());
217
+ case 'MMM': return MONTH_SHORT_NAMES[date.getMonth()];
218
+ case 'MM': return pad(date.getMonth() + 1);
219
+ case 'dd': return pad(date.getDate());
220
+ default: return token;
221
+ }
222
+ });
223
+ }
224
+ /**
225
+ * Parse text written in a token pattern back to a Date, or null.
226
+ *
227
+ * Built as a regex from the pattern rather than by splitting on a separator:
228
+ * a month name is not numeric, and patterns may use a space or a comma, so
229
+ * splitting cannot recover the field order on its own.
230
+ */
231
+ function parseDateWithPattern(text, pattern) {
232
+ if (!text || !pattern)
233
+ return null;
234
+ const order = [];
235
+ // Escape literals first — tokens are alphanumeric, so escaping cannot touch
236
+ // them — then swap each token for its capture group.
237
+ const source = normalizeDateFormat(pattern)
238
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
239
+ .replace(/yyyy|MMM|MM|dd/g, token => {
240
+ order.push(token);
241
+ if (token === 'yyyy')
242
+ return '(\\d{4})';
243
+ if (token === 'MMM')
244
+ return '([A-Za-z]{3})';
245
+ return '(\\d{1,2})';
246
+ });
247
+ const match = new RegExp(`^\\s*${source}\\s*$`).exec(String(text).trim());
248
+ if (!match)
249
+ return null;
250
+ let year = NaN, month = NaN, day = NaN;
251
+ order.forEach((token, i) => {
252
+ const raw = match[i + 1];
253
+ if (token === 'yyyy')
254
+ year = Number(raw);
255
+ else if (token === 'dd')
256
+ day = Number(raw);
257
+ else if (token === 'MM')
258
+ month = Number(raw) - 1;
259
+ else {
260
+ const idx = MONTH_SHORT_NAMES.findIndex(m => m.toLowerCase() === raw.toLowerCase());
261
+ month = idx;
262
+ }
263
+ });
264
+ if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day))
265
+ return null;
266
+ if (month < 0 || month > 11)
267
+ return null;
268
+ const date = new Date(year, month, day);
269
+ // Reject values the calendar rolled over (e.g. 31 Feb).
270
+ return (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day)
271
+ ? date
272
+ : null;
273
+ }
274
+ /**
275
+ * Resolve a numeric value against a column's `color_ranges`, returning the
276
+ * first matching band or null.
277
+ *
278
+ * Shared by the progress, number and currency cells so a value falling in the
279
+ * same band paints the same way whichever datatype renders it. A blank or
280
+ * absent bound is open-ended — data-model bands are authored that way ("80 and
281
+ * above"), and coercing a missing bound to 0 would stop such a band matching.
282
+ * Bands are checked in order, so the author controls precedence.
283
+ */
284
+ function matchColorRange(value, ranges) {
285
+ if (!Array.isArray(ranges) || ranges.length === 0)
286
+ return null;
287
+ const v = Number(value);
288
+ if (value === null || value === undefined || value === '' || isNaN(v))
289
+ return null;
290
+ const bound = (raw) => {
291
+ if (raw === null || raw === undefined || raw === '')
292
+ return null;
293
+ const n = Number(raw);
294
+ return isNaN(n) ? null : n;
295
+ };
296
+ const match = ranges.find((r) => {
297
+ const from = bound(r?.from);
298
+ const to = bound(r?.to);
299
+ if (from !== null && v < from)
300
+ return false;
301
+ if (to !== null && v > to)
302
+ return false;
303
+ return true;
304
+ });
305
+ return match || null;
306
+ }
307
+ const DATA_TYPES = [
308
+ 'number',
309
+ 'textbox',
310
+ 'textarea',
311
+ 'currency',
312
+ 'date',
313
+ 'datetime',
314
+ 'duration',
315
+ 'time',
316
+ 'dropdown_single_select',
317
+ 'dropdown_multi_select',
318
+ 'location',
319
+ 'email',
320
+ 'people',
321
+ 'checkbox',
322
+ 'phone',
323
+ 'priority',
324
+ 'status',
325
+ 'progress',
326
+ 'attachment',
327
+ 'tag',
328
+ 'rating',
329
+ 'website',
330
+ ];
331
+
42
332
  class GridConfigService {
43
333
  /**
44
334
  * Detects if pivot mode should be automatically enabled based on configuration
@@ -2394,6 +2684,9 @@ class ColumnConstraintsService {
2394
2684
  // Sensible defaults for each datatype
2395
2685
  DATATYPE_DEFAULTS = {
2396
2686
  textbox: { minWidth: 80, maxWidth: 400 },
2687
+ // Wider ceiling than textbox: a textarea holds multi-line prose, so it
2688
+ // benefits from the extra room when the user widens the column.
2689
+ textarea: { minWidth: 120, maxWidth: 500 },
2397
2690
  email: { minWidth: 120, maxWidth: 300 },
2398
2691
  location: { minWidth: 100, maxWidth: 250 },
2399
2692
  number: { minWidth: 60, maxWidth: 150 },
@@ -2401,6 +2694,7 @@ class ColumnConstraintsService {
2401
2694
  date: { minWidth: 100, maxWidth: 180 },
2402
2695
  datetime: { minWidth: 140, maxWidth: 220 },
2403
2696
  duration: { minWidth: 100, maxWidth: 180 },
2697
+ time: { minWidth: 90, maxWidth: 140 },
2404
2698
  dropdown_single_select: { minWidth: 80, maxWidth: 200 },
2405
2699
  dropdown_multi_select: { minWidth: 100, maxWidth: 250 },
2406
2700
  checkbox: { minWidth: 40, maxWidth: 100 },
@@ -2559,6 +2853,18 @@ class EruGridStore {
2559
2853
  // excel-download icon is clicked; the consumer's effect drains the queue,
2560
2854
  // performs its own HTTP fetch, and removes the request when done.
2561
2855
  _excelDownloadRequest = signal([], ...(ngDevMode ? [{ debugName: "_excelDownloadRequest" }] : []));
2856
+ // Server-side validation notices, drained by the consumer (see the interface).
2857
+ _serverValidationNotices = signal([], ...(ngDevMode ? [{ debugName: "_serverValidationNotices" }] : []));
2858
+ serverValidationNotices = this._serverValidationNotices.asReadonly();
2859
+ setServerValidationNotice(notice) {
2860
+ this._serverValidationNotices.update(list => [
2861
+ ...list,
2862
+ { ...notice, id: `ssv-${Date.now()}-${list.length}`, timestamp: Date.now() },
2863
+ ]);
2864
+ }
2865
+ removeServerValidationNotice(id) {
2866
+ this._serverValidationNotices.update(list => list.filter(n => n.id !== id));
2867
+ }
2562
2868
  // Column currently selected for editing in the design panel (by name)
2563
2869
  _selectedDesignColumn = signal(null, ...(ngDevMode ? [{ debugName: "_selectedDesignColumn" }] : []));
2564
2870
  selectedDesignColumn = this._selectedDesignColumn.asReadonly();
@@ -2695,6 +3001,10 @@ class EruGridStore {
2695
3001
  const config = this.configuration();
2696
3002
  const normalizedColumns = columns.map(column => ({
2697
3003
  ...column,
3004
+ // Resolve data-model datatype aliases (r_status -> status) once here, so
3005
+ // every downstream switch, validator and design-panel lookup sees the
3006
+ // canonical value.
3007
+ datatype: normalizeDatatype(column.datatype),
2698
3008
  field_size: this.columnConstraintsService.getDefaultFieldSize(column, config)
2699
3009
  }));
2700
3010
  // Sort columns by grid_index to ensure proper order in table mode
@@ -3790,31 +4100,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
3790
4100
  }], ctorParameters: () => [{ type: EruGridStore }, { type: ThemeService }, { type: ColumnConstraintsService }] });
3791
4101
 
3792
4102
  const CELL_VALIDATORS = new InjectionToken('CELL_VALIDATORS');
4103
+ /**
4104
+ * `mandatory` is the name the data model and eru-studio use; `required` is the
4105
+ * legacy alias kept on `Field`. Read through this so a column configured either
4106
+ * way validates the same, rather than silently skipping the check.
4107
+ */
4108
+ function isMandatory(config) {
4109
+ return !!(config?.mandatory ?? config?.required);
4110
+ }
4111
+ /**
4112
+ * Length rule from the data model: `data_length` is the bound and
4113
+ * `data_length_check` picks how to apply it. EXACT constrains the length to
4114
+ * exactly N — it is a length rule, not a value rule. An absent or unparseable
4115
+ * bound means no check, so a field with only one of the two configured is left
4116
+ * alone rather than validated against NaN.
4117
+ */
4118
+ function checkLength(value, config) {
4119
+ const bound = Number(config?.data_length);
4120
+ if (!Number.isFinite(bound) || bound <= 0)
4121
+ return { isValid: true };
4122
+ const mode = String(config?.data_length_check || 'MAX').toUpperCase();
4123
+ const len = value.length;
4124
+ const label = config?.label ?? 'Value';
4125
+ if (mode === 'MIN' && len < bound) {
4126
+ return { isValid: false, error: `${label} must be at least ${bound} characters` };
4127
+ }
4128
+ if (mode === 'EXACT' && len !== bound) {
4129
+ return { isValid: false, error: `${label} must be exactly ${bound} characters` };
4130
+ }
4131
+ if (mode === 'MAX' && len > bound) {
4132
+ return { isValid: false, error: `${label} cannot exceed ${bound} characters` };
4133
+ }
4134
+ return { isValid: true };
4135
+ }
4136
+ /**
4137
+ * Numeric bound from the data model: `num_val` is the limit and
4138
+ * `num_val_check` says whether it is a floor or a ceiling.
4139
+ */
4140
+ function checkNumericBound(value, config) {
4141
+ const bound = Number(config?.num_val);
4142
+ if (!Number.isFinite(bound))
4143
+ return { isValid: true };
4144
+ const mode = String(config?.num_val_check || '').toUpperCase();
4145
+ const label = config?.label ?? 'Value';
4146
+ if (mode === 'MIN' && value < bound) {
4147
+ return { isValid: false, error: `${label} must be at least ${bound}` };
4148
+ }
4149
+ if (mode === 'MAX' && value > bound) {
4150
+ return { isValid: false, error: `${label} cannot exceed ${bound}` };
4151
+ }
4152
+ return { isValid: true };
4153
+ }
3793
4154
  const cellValidators = {
3794
4155
  textbox: (value, config) => {
3795
- if (config.mandatory && !value) {
4156
+ if (isMandatory(config) && !value) {
3796
4157
  return { isValid: false, error: `${config.label} is required` };
3797
4158
  }
3798
4159
  if (value?.length > 0) {
3799
- const length = Number(config.data_length);
3800
- // Add length validation logic
4160
+ return checkLength(value, config);
3801
4161
  }
3802
4162
  return { isValid: true };
3803
4163
  },
3804
4164
  textarea: (value, config) => {
3805
- if (config.mandatory && !value) {
4165
+ if (isMandatory(config) && !value) {
3806
4166
  return { isValid: false, error: `${config.label} is required` };
3807
4167
  }
3808
4168
  if (value?.length > 0) {
3809
- const maxLength = config.maxLength || config.data_length;
3810
- if (maxLength && value.length > maxLength) {
4169
+ // `maxLength` is not a data-model key; honoured first so a caller that
4170
+ // sets it directly on the column config still wins over the model bound.
4171
+ const maxLength = Number(config.maxLength);
4172
+ if (Number.isFinite(maxLength) && maxLength > 0 && value.length > maxLength) {
3811
4173
  return { isValid: false, error: `Maximum length is ${maxLength} characters` };
3812
4174
  }
4175
+ return checkLength(value, config);
3813
4176
  }
3814
4177
  return { isValid: true };
3815
4178
  },
3816
4179
  email: (value, config) => {
3817
- if (config.mandatory && !value) {
4180
+ if (isMandatory(config) && !value) {
3818
4181
  return { isValid: false, error: `${config.label} is required` };
3819
4182
  }
3820
4183
  if (value && value.length > 0) {
@@ -3826,7 +4189,7 @@ const cellValidators = {
3826
4189
  return { isValid: true };
3827
4190
  },
3828
4191
  website: (value, config) => {
3829
- if (config.mandatory && !value) {
4192
+ if (isMandatory(config) && !value) {
3830
4193
  return { isValid: false, error: `${config.label} is required` };
3831
4194
  }
3832
4195
  if (value && value.trim().length > 0) {
@@ -3839,24 +4202,26 @@ const cellValidators = {
3839
4202
  return { isValid: true };
3840
4203
  },
3841
4204
  location: (value, config) => {
3842
- if (config.mandatory && !value) {
4205
+ if (isMandatory(config) && !value) {
3843
4206
  return { isValid: false, error: `${config.label} is required` };
3844
4207
  }
3845
4208
  return { isValid: true };
3846
4209
  },
3847
4210
  number: (value, config) => {
3848
- if (config.mandatory && (value === null || value === undefined || isNaN(value))) {
4211
+ if (isMandatory(config) && (value === null || value === undefined || isNaN(value))) {
3849
4212
  return { isValid: false, error: `${config.label} is mandatory` };
3850
4213
  }
3851
- // Add number validation logic
3852
- return { isValid: true };
4214
+ if (value === null || value === undefined || isNaN(value))
4215
+ return { isValid: true };
4216
+ return checkNumericBound(value, config);
3853
4217
  },
3854
4218
  currency: (value, config) => {
3855
- if (config.mandatory && (value === null || value === undefined || isNaN(value))) {
4219
+ if (isMandatory(config) && (value === null || value === undefined || isNaN(value))) {
3856
4220
  return { isValid: false, error: `${config.label} is mandatory` };
3857
4221
  }
3858
- // Add currency validation logic
3859
- return { isValid: true };
4222
+ if (value === null || value === undefined || isNaN(value))
4223
+ return { isValid: true };
4224
+ return checkNumericBound(value, config);
3860
4225
  }
3861
4226
  };
3862
4227
 
@@ -3950,6 +4315,12 @@ class CurrencyComponent {
3950
4315
  isDrillable = input(false, ...(ngDevMode ? [{ debugName: "isDrillable" }] : []));
3951
4316
  replaceZeroValue = input(undefined, ...(ngDevMode ? [{ debugName: "replaceZeroValue" }] : []));
3952
4317
  placeholder = input('0.00', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
4318
+ /**
4319
+ * The row this cell belongs to. Needed for `symbol_field`, which names another
4320
+ * field on the SAME record supplying the currency symbol — so a multi-currency
4321
+ * column renders each row with its own symbol instead of one column-wide one.
4322
+ */
4323
+ row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : []));
3953
4324
  // Outputs
3954
4325
  valueChange = new EventEmitter();
3955
4326
  blur = new EventEmitter();
@@ -3960,6 +4331,34 @@ class CurrencyComponent {
3960
4331
  // Internal state
3961
4332
  currentValue = signal(null, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
3962
4333
  error = signal('', ...(ngDevMode ? [{ debugName: "error" }] : []));
4334
+ /**
4335
+ * Colours from the column's configured value ranges, applied over the cell's
4336
+ * normal appearance. View mode only — tinting an active input reads as a
4337
+ * validation state rather than a value. Null when no band matches, matching
4338
+ * the eru-studio currency component so a value paints the same on a page and here.
4339
+ */
4340
+ /**
4341
+ * Symbol to show. `symbol_field` wins when set — read from this row, so each
4342
+ * record can carry its own currency — otherwise the column's static `symbol`.
4343
+ * Rows may nest their values under entity_data.
4344
+ */
4345
+ resolvedSymbol = computed(() => {
4346
+ const cfg = this.config();
4347
+ const field = String(cfg?.symbol_field || '').trim();
4348
+ if (field) {
4349
+ const r = this.row();
4350
+ const fromEntityData = r?.entity_data && typeof r.entity_data === 'object'
4351
+ ? r.entity_data[field]
4352
+ : undefined;
4353
+ const raw = fromEntityData !== undefined ? fromEntityData : r?.[field];
4354
+ if (raw !== undefined && raw !== null && String(raw).trim().length > 0) {
4355
+ return String(raw).trim();
4356
+ }
4357
+ }
4358
+ return cfg?.symbol || '';
4359
+ }, ...(ngDevMode ? [{ debugName: "resolvedSymbol" }] : []));
4360
+ rangeColor = computed(() => this.isActive() ? null : matchColorRange(this.currentValue(), this.config()?.color_ranges)?.color || null, ...(ngDevMode ? [{ debugName: "rangeColor" }] : []));
4361
+ rangeBackground = computed(() => this.isActive() ? null : matchColorRange(this.currentValue(), this.config()?.color_ranges)?.background || null, ...(ngDevMode ? [{ debugName: "rangeBackground" }] : []));
3963
4362
  constructor() {
3964
4363
  // Watch for changes in the value input and update currentValue
3965
4364
  effect(() => {
@@ -3994,7 +4393,7 @@ class CurrencyComponent {
3994
4393
  }
3995
4394
  const separator = cfg?.seperator || 'thousands';
3996
4395
  const decimalPlaces = cfg?.decimal ?? 2;
3997
- const symbol = cfg?.symbol || '';
4396
+ const symbol = this.resolvedSymbol();
3998
4397
  const prefix = symbol ? symbol + ' ' : '';
3999
4398
  if (cfg?.dynamic_number) {
4000
4399
  return `${prefix}${abbreviateNumber$1(numValue, cfg?.display_number_as ?? 'mn', decimalPlaces)}`;
@@ -4095,7 +4494,7 @@ class CurrencyComponent {
4095
4494
  setTimeout(tryFocus, 50);
4096
4495
  }
4097
4496
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CurrencyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4098
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CurrencyComponent, isStandalone: true, selector: "eru-currency", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, replaceZeroValue: { classPropertyName: "replaceZeroValue", publicName: "replaceZeroValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", validationError: "validationError", editModeChange: "editModeChange" }, ngImport: i0, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"currency-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n <span matTextPrefix>{{ config()?.symbol || '$' }}</span>\n </mat-form-field>\n} @else {\n <div class=\"currency-display\" \n [class.currency-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"currency-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.currency-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-outline,.currency-form-field .mat-mdc-form-field-subscript-wrapper,.currency-form-field .mat-mdc-form-field-text-suffix{display:none!important}.currency-form-field .mat-mdc-form-field-wrapper,.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.currency-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.currency-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.currency-display,.currency-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.currency-display-editable{cursor:pointer!important}.currency-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4497
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CurrencyComponent, isStandalone: true, selector: "eru-currency", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, replaceZeroValue: { classPropertyName: "replaceZeroValue", publicName: "replaceZeroValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", validationError: "validationError", editModeChange: "editModeChange" }, ngImport: i0, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"currency-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n <span matTextPrefix>{{ config()?.symbol || '$' }}</span>\n </mat-form-field>\n} @else {\n <div class=\"currency-display\"\n [style.color]=\"rangeColor()\"\n [style.background-color]=\"rangeBackground()\" \n [class.currency-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"currency-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.currency-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-outline,.currency-form-field .mat-mdc-form-field-subscript-wrapper,.currency-form-field .mat-mdc-form-field-text-suffix{display:none!important}.currency-form-field .mat-mdc-form-field-wrapper,.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.currency-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.currency-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.currency-display,.currency-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.currency-display-editable{cursor:pointer!important}.currency-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4099
4498
  }
4100
4499
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CurrencyComponent, decorators: [{
4101
4500
  type: Component,
@@ -4103,8 +4502,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
4103
4502
  FormsModule,
4104
4503
  MatFormFieldModule,
4105
4504
  MatInputModule
4106
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"currency-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n <span matTextPrefix>{{ config()?.symbol || '$' }}</span>\n </mat-form-field>\n} @else {\n <div class=\"currency-display\" \n [class.currency-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"currency-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.currency-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-outline,.currency-form-field .mat-mdc-form-field-subscript-wrapper,.currency-form-field .mat-mdc-form-field-text-suffix{display:none!important}.currency-form-field .mat-mdc-form-field-wrapper,.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.currency-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.currency-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.currency-display,.currency-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.currency-display-editable{cursor:pointer!important}.currency-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"] }]
4107
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], replaceZeroValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "replaceZeroValue", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], valueChange: [{
4505
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"currency-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n <span matTextPrefix>{{ config()?.symbol || '$' }}</span>\n </mat-form-field>\n} @else {\n <div class=\"currency-display\"\n [style.color]=\"rangeColor()\"\n [style.background-color]=\"rangeBackground()\" \n [class.currency-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"currency-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.currency-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-outline,.currency-form-field .mat-mdc-form-field-subscript-wrapper,.currency-form-field .mat-mdc-form-field-text-suffix{display:none!important}.currency-form-field .mat-mdc-form-field-wrapper,.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.currency-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.currency-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.currency-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.currency-display,.currency-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.currency-display-editable{cursor:pointer!important}.currency-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"] }]
4506
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], replaceZeroValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "replaceZeroValue", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], valueChange: [{
4108
4507
  type: Output
4109
4508
  }], blur: [{
4110
4509
  type: Output
@@ -4164,6 +4563,14 @@ class NumberComponent {
4164
4563
  // Internal state
4165
4564
  currentValue = signal(null, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
4166
4565
  error = signal('', ...(ngDevMode ? [{ debugName: "error" }] : []));
4566
+ /**
4567
+ * Colours from the column's configured value ranges, applied over the cell's
4568
+ * normal appearance. View mode only — tinting an active input reads as a
4569
+ * validation state rather than a value. Null when no band matches, matching
4570
+ * the eru-studio number component so a value paints the same on a page and here.
4571
+ */
4572
+ rangeColor = computed(() => this.isActive() ? null : matchColorRange(this.currentValue(), this.config()?.color_ranges)?.color || null, ...(ngDevMode ? [{ debugName: "rangeColor" }] : []));
4573
+ rangeBackground = computed(() => this.isActive() ? null : matchColorRange(this.currentValue(), this.config()?.color_ranges)?.background || null, ...(ngDevMode ? [{ debugName: "rangeBackground" }] : []));
4167
4574
  constructor() {
4168
4575
  effect(() => {
4169
4576
  const value = this.value();
@@ -4296,7 +4703,7 @@ class NumberComponent {
4296
4703
  setTimeout(tryFocus, 50);
4297
4704
  }
4298
4705
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: NumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4299
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: NumberComponent, isStandalone: true, selector: "eru-number", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, replaceZeroValue: { classPropertyName: "replaceZeroValue", publicName: "replaceZeroValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", validationError: "validationError", editModeChange: "editModeChange" }, ngImport: i0, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"number-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n </mat-form-field>\n} @else {\n <div class=\"number-display\" \n [class.number-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"number-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.number-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-outline,.number-form-field .mat-mdc-form-field-subscript-wrapper{display:none!important}.number-form-field .mat-mdc-form-field-wrapper,.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.number-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.number-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.number-display,.number-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.number-display-editable{cursor:pointer!important}.number-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4706
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: NumberComponent, isStandalone: true, selector: "eru-number", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, replaceZeroValue: { classPropertyName: "replaceZeroValue", publicName: "replaceZeroValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", validationError: "validationError", editModeChange: "editModeChange" }, ngImport: i0, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"number-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n </mat-form-field>\n} @else {\n <div class=\"number-display\"\n [style.color]=\"rangeColor()\"\n [style.background-color]=\"rangeBackground()\" \n [class.number-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"number-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.number-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-outline,.number-form-field .mat-mdc-form-field-subscript-wrapper{display:none!important}.number-form-field .mat-mdc-form-field-wrapper,.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.number-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.number-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.number-display,.number-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.number-display-editable{cursor:pointer!important}.number-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4300
4707
  }
4301
4708
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: NumberComponent, decorators: [{
4302
4709
  type: Component,
@@ -4304,7 +4711,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
4304
4711
  FormsModule,
4305
4712
  MatFormFieldModule,
4306
4713
  MatInputModule
4307
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"number-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n </mat-form-field>\n} @else {\n <div class=\"number-display\" \n [class.number-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"number-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.number-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-outline,.number-form-field .mat-mdc-form-field-subscript-wrapper{display:none!important}.number-form-field .mat-mdc-form-field-wrapper,.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.number-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.number-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.number-display,.number-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.number-display-editable{cursor:pointer!important}.number-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"] }]
4714
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()){\n <mat-form-field appearance=\"outline\" class=\"number-form-field\">\n <input matInput \n type=\"text\" \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n [placeholder]=\"placeholder()\" \n (blur)=\"onBlur()\">\n </mat-form-field>\n} @else {\n <div class=\"number-display\"\n [style.color]=\"rangeColor()\"\n [style.background-color]=\"rangeBackground()\" \n [class.number-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"number-drillable\" (click)=\"onDrillableClick($event)\">{{formatNumberSignal()}}</span>\n } @else {\n {{formatNumberSignal()}}\n }\n </div>\n}\n", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.number-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-outline,.number-form-field .mat-mdc-form-field-subscript-wrapper{display:none!important}.number-form-field .mat-mdc-form-field-wrapper,.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.number-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.number-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.number-display{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important;text-align:right!important}.number-display,.number-display>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.number-display-editable{cursor:pointer!important}.number-drillable{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease;cursor:pointer;padding:4px}\n"] }]
4308
4715
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], replaceZeroValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "replaceZeroValue", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], valueChange: [{
4309
4716
  type: Output
4310
4717
  }], blur: [{
@@ -5313,7 +5720,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
5313
5720
 
5314
5721
  class CheckboxComponent {
5315
5722
  // Inputs
5723
+ // Not typed `boolean`: a checkbox column may store an arbitrary literal
5724
+ // (e.g. 'Y'/'N') when the field configures value_true/value_false. Taking it
5725
+ // as boolean meant a column written 'N' read as CHECKED here, because a
5726
+ // non-empty string is truthy — the same field then disagreed with the page
5727
+ // that wrote it.
5316
5728
  value = input(false, ...(ngDevMode ? [{ debugName: "value" }] : []));
5729
+ /** Column config; supplies value_true / value_false when the field uses literals. */
5730
+ config = input(null, ...(ngDevMode ? [{ debugName: "config" }] : []));
5317
5731
  isEditable = input(true, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
5318
5732
  isActive = input(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
5319
5733
  isDrillable = input(false, ...(ngDevMode ? [{ debugName: "isDrillable" }] : []));
@@ -5325,18 +5739,71 @@ class CheckboxComponent {
5325
5739
  focus = new EventEmitter();
5326
5740
  drilldownClick = new EventEmitter();
5327
5741
  editModeChange = new EventEmitter();
5328
- // Internal state
5742
+ // Internal state — the checked state shown, not the stored representation.
5329
5743
  currentValue = signal(false, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
5330
5744
  constructor() {
5331
5745
  effect(() => {
5332
- const value = this.value();
5333
- this.currentValue.set(value);
5746
+ this.currentValue.set(this.resolveCheckboxState(this.value()));
5334
5747
  });
5335
5748
  }
5749
+ toBool(v) {
5750
+ if (typeof v === 'boolean')
5751
+ return v;
5752
+ if (typeof v === 'number')
5753
+ return v !== 0;
5754
+ if (typeof v === 'string') {
5755
+ const s = v.trim().toLowerCase();
5756
+ return s === 'true' || s === '1' || s === 'yes' || s === 'on' || s === 'checked';
5757
+ }
5758
+ return !!v;
5759
+ }
5760
+ literals() {
5761
+ const cfg = this.config();
5762
+ const valueTrue = cfg?.value_true;
5763
+ const valueFalse = cfg?.value_false;
5764
+ return {
5765
+ valueTrue,
5766
+ valueFalse,
5767
+ hasTrue: valueTrue !== undefined && valueTrue !== null && String(valueTrue).length > 0,
5768
+ hasFalse: valueFalse !== undefined && valueFalse !== null && String(valueFalse).length > 0,
5769
+ };
5770
+ }
5771
+ /**
5772
+ * Stored representation -> checked state. When the field configures
5773
+ * value_true / value_false the backend may hold arbitrary literals ('Y'/'N',
5774
+ * 'Yes'/'No'); left blank, both fall through to the generic coercion. Ported
5775
+ * from the eru-studio checkbox so the two agree on what a stored value means.
5776
+ */
5777
+ resolveCheckboxState(raw) {
5778
+ const { valueTrue, valueFalse, hasTrue, hasFalse } = this.literals();
5779
+ if (!hasTrue && !hasFalse)
5780
+ return this.toBool(raw);
5781
+ if (hasTrue && String(raw) === String(valueTrue))
5782
+ return true;
5783
+ if (hasFalse && String(raw) === String(valueFalse))
5784
+ return false;
5785
+ if (hasTrue && !hasFalse)
5786
+ return false;
5787
+ if (hasFalse && !hasTrue)
5788
+ return true;
5789
+ return this.toBool(raw);
5790
+ }
5791
+ /** Inverse: checked state -> the representation to store. */
5792
+ checkboxValueFor(checked) {
5793
+ const { valueTrue, valueFalse, hasTrue, hasFalse } = this.literals();
5794
+ if (!hasTrue && !hasFalse)
5795
+ return checked;
5796
+ if (checked)
5797
+ return hasTrue ? valueTrue : true;
5798
+ return hasFalse ? valueFalse : false;
5799
+ }
5336
5800
  onValueChange(event) {
5337
5801
  this.currentValue.set(event);
5338
- this.valueChange.emit(event);
5339
- this.change.emit(event);
5802
+ // Emit the stored representation, not the checked state, so a column using
5803
+ // literals round-trips instead of being overwritten with a raw boolean.
5804
+ const stored = this.checkboxValueFor(event);
5805
+ this.valueChange.emit(stored);
5806
+ this.change.emit(stored);
5340
5807
  }
5341
5808
  onActivate() {
5342
5809
  if (this.isEditable()) {
@@ -5345,17 +5812,16 @@ class CheckboxComponent {
5345
5812
  }
5346
5813
  }
5347
5814
  onBlur() {
5348
- const value = this.currentValue();
5349
- this.blur.emit(value);
5815
+ this.blur.emit(this.checkboxValueFor(this.currentValue()));
5350
5816
  }
5351
5817
  onDrillableClick(event) {
5352
5818
  if (this.isDrillable()) {
5353
5819
  event.stopPropagation();
5354
- this.drilldownClick.emit(this.currentValue());
5820
+ this.drilldownClick.emit(this.checkboxValueFor(this.currentValue()));
5355
5821
  }
5356
5822
  }
5357
5823
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5358
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CheckboxComponent, isStandalone: true, selector: "eru-checkbox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", change: "change", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, ngImport: i0, template: "<div \n class=\"checkbox-container\"\n [class.checkbox-active]=\"isActive()\"\n [class.checkbox-display-editable]=\"isEditable() && !isActive()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable() && !isActive()) {\n <span class=\"checkbox-drillable\" (click)=\"onDrillableClick($event)\">\n <mat-checkbox \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\"\n [disabled]=\"true\">\n {{label()}}\n </mat-checkbox>\n </span>\n } @else {\n <mat-checkbox \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n [disabled]=\"!isEditable() || !isActive()\">\n {{label()}}\n </mat-checkbox>\n }\n</div>\n", styles: [":host{display:block;height:100%;width:100%}.checkbox-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:4px;cursor:default}.checkbox-container.checkbox-display-editable{cursor:pointer}.checkbox-container.checkbox-display-editable:hover{background-color:#0000000a}.checkbox-container.checkbox-active{cursor:default}.checkbox-drillable{cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5824
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CheckboxComponent, isStandalone: true, selector: "eru-checkbox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", change: "change", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, ngImport: i0, template: "<div \n class=\"checkbox-container\"\n [class.checkbox-active]=\"isActive()\"\n [class.checkbox-display-editable]=\"isEditable() && !isActive()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable() && !isActive()) {\n <span class=\"checkbox-drillable\" (click)=\"onDrillableClick($event)\">\n <mat-checkbox \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\"\n [disabled]=\"true\">\n {{label()}}\n </mat-checkbox>\n </span>\n } @else {\n <mat-checkbox \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n [disabled]=\"!isEditable() || !isActive()\">\n {{label()}}\n </mat-checkbox>\n }\n</div>\n", styles: [":host{display:block;height:100%;width:100%}.checkbox-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:4px;cursor:default}.checkbox-container.checkbox-display-editable{cursor:pointer}.checkbox-container.checkbox-display-editable:hover{background-color:#0000000a}.checkbox-container.checkbox-active{cursor:default}.checkbox-drillable{cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5359
5825
  }
5360
5826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CheckboxComponent, decorators: [{
5361
5827
  type: Component,
@@ -5363,7 +5829,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
5363
5829
  FormsModule,
5364
5830
  MatCheckboxModule
5365
5831
  ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div \n class=\"checkbox-container\"\n [class.checkbox-active]=\"isActive()\"\n [class.checkbox-display-editable]=\"isEditable() && !isActive()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable() && !isActive()) {\n <span class=\"checkbox-drillable\" (click)=\"onDrillableClick($event)\">\n <mat-checkbox \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\"\n [disabled]=\"true\">\n {{label()}}\n </mat-checkbox>\n </span>\n } @else {\n <mat-checkbox \n [ngModel]=\"currentValue()\" \n (ngModelChange)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n [disabled]=\"!isEditable() || !isActive()\">\n {{label()}}\n </mat-checkbox>\n }\n</div>\n", styles: [":host{display:block;height:100%;width:100%}.checkbox-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:4px;cursor:default}.checkbox-container.checkbox-display-editable{cursor:pointer}.checkbox-container.checkbox-display-editable:hover{background-color:#0000000a}.checkbox-container.checkbox-active{cursor:default}.checkbox-drillable{cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}\n"] }]
5366
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], valueChange: [{
5832
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], valueChange: [{
5367
5833
  type: Output
5368
5834
  }], change: [{
5369
5835
  type: Output
@@ -5471,7 +5937,9 @@ class DateComponent {
5471
5937
  isDrillable = input(false, ...(ngDevMode ? [{ debugName: "isDrillable" }] : []));
5472
5938
  placeholder = input('Select date', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
5473
5939
  // Column-configured display format (e.g. 'dd-mm-yyyy'); defaults to dd-mm-yyyy.
5474
- dateFormat = computed(() => (this.config()?.date_format || 'dd-mm-yyyy').toLowerCase(), ...(ngDevMode ? [{ debugName: "dateFormat" }] : []));
5940
+ // Normalised, not lowercased: `MMM` (month name) must keep its casing, and a
5941
+ // stored value may be uppercase from the data model.
5942
+ dateFormat = computed(() => normalizeDateFormat(this.config()?.date_format) || 'dd-MM-yyyy', ...(ngDevMode ? [{ debugName: "dateFormat" }] : []));
5475
5943
  allowDays = computed(() => this.config()?.allow_days ?? [], ...(ngDevMode ? [{ debugName: "allowDays" }] : []));
5476
5944
  // Restrict the picker to the configured weekdays (empty = all allowed).
5477
5945
  dayFilter = computed(() => {
@@ -5490,6 +5958,31 @@ class DateComponent {
5490
5958
  // Internal state
5491
5959
  currentValue = signal(null, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
5492
5960
  shouldOpenPicker = signal(false, ...(ngDevMode ? [{ debugName: "shouldOpenPicker" }] : []));
5961
+ /**
5962
+ * The column's configured default, resolved to a real date.
5963
+ *
5964
+ * The data model expresses it as a rule rather than a fixed value: the
5965
+ * `Current_Date` sentinel in `default`, optionally shifted by
5966
+ * `default_value_offset_days` (negative goes back). Resolving it here means a
5967
+ * cell with no stored value shows what the field defines, matching eru-studio
5968
+ * — previously the grid ignored both and showed an empty cell.
5969
+ */
5970
+ resolveDefaultDate() {
5971
+ const cfg = this.config();
5972
+ const raw = cfg?.default;
5973
+ if (typeof raw !== 'string' || !raw.trim())
5974
+ return null;
5975
+ const sentinel = raw.trim().toLowerCase();
5976
+ if (sentinel !== 'current_date' && sentinel !== 'current_datetime')
5977
+ return null;
5978
+ const today = new Date();
5979
+ const anchor = new Date(today.getFullYear(), today.getMonth(), today.getDate());
5980
+ const offset = Number(cfg?.default_value_offset_days);
5981
+ if (Number.isFinite(offset) && offset !== 0) {
5982
+ anchor.setDate(anchor.getDate() + offset);
5983
+ }
5984
+ return anchor;
5985
+ }
5493
5986
  constructor() {
5494
5987
  effect(() => {
5495
5988
  const value = this.value();
@@ -5501,7 +5994,8 @@ class DateComponent {
5501
5994
  this.currentValue.set(parsedDate);
5502
5995
  }
5503
5996
  else {
5504
- this.currentValue.set(null);
5997
+ // No stored value — fall back to the field's configured default.
5998
+ this.currentValue.set(this.resolveDefaultDate());
5505
5999
  }
5506
6000
  });
5507
6001
  // Watch for isActive changes and open picker if needed
@@ -5538,41 +6032,21 @@ class DateComponent {
5538
6032
  this.drilldownClick.emit(this.getDisplayValue());
5539
6033
  }
5540
6034
  }
6035
+ /** Render a date in the column's configured format, via the shared token
6036
+ * formatter so a page and a grid read identically. */
5541
6037
  formatDate(date) {
5542
- if (!date)
5543
- return '';
5544
- const pad = (n) => n < 10 ? '0' + n : n.toString();
5545
- const dd = pad(date.getDate());
5546
- const mm = pad(date.getMonth() + 1);
5547
- const yyyy = date.getFullYear().toString();
5548
- switch (this.dateFormat()) {
5549
- case 'mm-dd-yyyy': return `${mm}-${dd}-${yyyy}`;
5550
- case 'yyyy-mm-dd': return `${yyyy}-${mm}-${dd}`;
5551
- case 'dd-mm-yyyy':
5552
- default: return `${dd}-${mm}-${yyyy}`;
5553
- }
6038
+ return formatDateWithPattern(date, this.dateFormat());
5554
6039
  }
5555
6040
  parseDateString(dateString) {
5556
- const parts = dateString.split('-').map(p => parseInt(p, 10));
5557
- if (parts.length !== 3 || parts.some(isNaN))
5558
- return null;
5559
- // Locate the year by its 4-digit segment so values written in any of the
5560
- // supported formats parse correctly regardless of the current setting.
5561
- const yearIdx = dateString.split('-').findIndex(p => p.length === 4);
5562
- let year, month, day;
5563
- if (yearIdx === 0) { // yyyy-mm-dd
5564
- [year, month, day] = [parts[0], parts[1] - 1, parts[2]];
5565
- }
5566
- else if (this.dateFormat() === 'mm-dd-yyyy') {
5567
- [month, day, year] = [parts[0] - 1, parts[1], parts[2]];
5568
- }
5569
- else { // dd-mm-yyyy (default)
5570
- [day, month, year] = [parts[0], parts[1] - 1, parts[2]];
5571
- }
5572
- const date = new Date(year, month, day);
5573
- if (date.getDate() === day && date.getMonth() === month && date.getFullYear() === year) {
5574
- return date;
5575
- }
6041
+ // Shared token parser first, so month-name and space/comma patterns parse.
6042
+ const byPattern = parseDateWithPattern(dateString, this.dateFormat());
6043
+ if (byPattern)
6044
+ return byPattern;
6045
+ // Fall back to ISO, which is how values are stored regardless of how the
6046
+ // column chooses to display them.
6047
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(dateString).trim());
6048
+ if (iso)
6049
+ return new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));
5576
6050
  return null;
5577
6051
  }
5578
6052
  getDisplayValue() {
@@ -5852,9 +6326,14 @@ class DatetimeComponent {
5852
6326
  this.drilldownClick.emit(value);
5853
6327
  }
5854
6328
  }
5855
- // Column-configured format, e.g. 'dd-mm-yyyy hh:mm:ss'. Defaults to
5856
- // dd-mm-yyyy hh:mm:ss.
5857
- dtFormat = computed(() => (this.config()?.dateTimeFormat || 'dd-mm-yyyy hh:mm:ss').toLowerCase(), ...(ngDevMode ? [{ debugName: "dtFormat" }] : []));
6329
+ // Column-configured format, e.g. 'dd-mm-yyyy hh:mm:ss'. `date_format` is the
6330
+ // key the data model actually persists the datetime pattern under;
6331
+ // `dateTimeFormat` is the legacy alias. Lowercased because the model
6332
+ // uppercases the value on save. Defaults to dd-mm-yyyy hh:mm:ss.
6333
+ dtFormat = computed(() => {
6334
+ const cfg = this.config();
6335
+ return String(cfg?.date_format || cfg?.dateTimeFormat || 'dd-mm-yyyy hh:mm:ss').toLowerCase();
6336
+ }, ...(ngDevMode ? [{ debugName: "dtFormat" }] : []));
5858
6337
  formatDateTime(date) {
5859
6338
  if (!date)
5860
6339
  return '';
@@ -6558,14 +7037,27 @@ class ProgressComponent {
6558
7037
  // Bar colour from the column's configured value ranges (e.g. 0-20 green).
6559
7038
  // Applies in both editable and view/disabled modes. Null when no range
6560
7039
  // matches, so the bar falls back to the default theme colour.
6561
- barColor = computed(() => {
6562
- const ranges = this.config()?.color_ranges;
6563
- if (!Array.isArray(ranges) || ranges.length === 0)
6564
- return null;
6565
- const v = this.displayValue();
6566
- const match = ranges.find((r) => v >= Number(r?.from) && v <= Number(r?.to));
6567
- return match?.color || null;
6568
- }, ...(ngDevMode ? [{ debugName: "barColor" }] : []));
7040
+ barColor = computed(() => matchColorRange(this.displayValue(), this.config()?.color_ranges)?.color || null, ...(ngDevMode ? [{ debugName: "barColor" }] : []));
7041
+ /**
7042
+ * Whether to show the value as a percentage. `is_perc` is the data model's
7043
+ * flag; it only reads as a percentage on a 0-100 scale, so a column with a
7044
+ * custom scale shows the raw value against its bound instead.
7045
+ */
7046
+ showPercent = computed(() => {
7047
+ const cfg = this.config();
7048
+ const start = Number(cfg?.start_value);
7049
+ const end = Number(cfg?.end_value);
7050
+ const isDefaultScale = (!Number.isFinite(start) || start === 0)
7051
+ && (!Number.isFinite(end) || end === 100);
7052
+ return cfg?.is_perc !== false && isDefaultScale;
7053
+ }, ...(ngDevMode ? [{ debugName: "showPercent" }] : []));
7054
+ /** Label beside the bar: "45%" on a percentage scale, "3 / 5" otherwise. */
7055
+ valueLabel = computed(() => {
7056
+ if (this.showPercent())
7057
+ return `${this.displayValue()}%`;
7058
+ const end = Number(this.config()?.end_value);
7059
+ return Number.isFinite(end) ? `${this.displayValue()} / ${end}` : `${this.displayValue()}`;
7060
+ }, ...(ngDevMode ? [{ debugName: "valueLabel" }] : []));
6569
7061
  constructor() {
6570
7062
  // Sync signal-based input with internal state
6571
7063
  effect(() => {
@@ -6616,11 +7108,11 @@ class ProgressComponent {
6616
7108
  return cfg[property];
6617
7109
  }
6618
7110
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6619
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: ProgressComponent, isStandalone: true, selector: "eru-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, ngImport: i0, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"\n [style.--mat-slider-active-track-color]=\"barColor()\"\n [style.--mat-slider-disabled-active-track-color]=\"barColor()\"\n [style.--mat-slider-with-tick-marks-active-container-color]=\"barColor()\"\n [style.--mat-slider-handle-color]=\"barColor()\"\n [style.--mat-slider-focus-handle-color]=\"barColor()\"\n [style.--mat-slider-hover-handle-color]=\"barColor()\"\n [style.--mat-slider-disabled-handle-color]=\"barColor()\"\n [style.--mat-slider-ripple-color]=\"barColor()\"\n (dblclick)=\"onActivate(); $event.stopPropagation()\">\n <span class=\"progress-value\" [style.color]=\"barColor()\">{{displayValue()}}%</span>\n <mat-slider \n class=\"progress-slider\" \n [min]=\"0\" \n [max]=\"100\" \n [step]=\"1\" \n [discrete]=\"true\" \n [showTickMarks]=\"true\"\n [disabled]=\"!isActive()\">\n <input \n matSliderThumb \n [ngModel]=\"displayValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n (blur)=\"onBlur()\"\n [disabled]=\"!isActive()\">\n </mat-slider>\n</div>\n\n", styles: [":root{--mat-slider-with-overlap-handle-outline-color: var(--grid-primary, #6750a4) !important;--mat-slider-with-tick-marks-active-container-color: var(--grid-primary, #6750a4) !important;--mat-slider-handle-height: 12px;--mat-slider-handle-width: 12px;--mat-slider-disabled-active-track-color: var(--grid-primary, #6750a4);--mat-slider-active-track-color: var(--grid-primary, #6750a4)}:host{display:block;height:100%;width:100%}.progress-bar-container{width:100%;height:100%;min-height:30px;position:relative;background:#e0e0e0;border-radius:4px;cursor:pointer}.progress-bar{height:100%;background:var(--grid-primary, #6750a4);transition:width .3s;border-radius:4px}.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);pointer-events:none;z-index:1}.drillable-value{color:inherit;text-decoration:underline;cursor:pointer;pointer-events:auto}.drillable-value:hover{opacity:.8}.progress-input-container{display:flex;flex-direction:row-reverse;align-items:center;padding:4px;width:100%;box-sizing:border-box;overflow:visible;max-height:30px}.progress-input-container.disabled{cursor:pointer}.progress-input-container.disabled .progress-slider{pointer-events:none}.progress-value{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);flex:0 0 auto;min-width:35px;text-align:right;white-space:nowrap;flex-shrink:0}.progress-slider{flex:1 1 0%;min-width:0!important;max-width:100%!important;overflow:visible}.progress-slider .mdc-slider__track{left:0!important}.progress-slider .mdc-slider{padding-left:0!important;margin-left:0!important}.progress-slider .mdc-slider__value-indicator,.progress-slider .mdc-slider__value-indicator-container{display:none!important;visibility:hidden!important}.progress-slider .mdc-slider__thumb:before,.progress-slider .mdc-slider__thumb:after{display:none!important;content:none!important}.progress-slider .mdc-slider__thumb-knob:before,.progress-slider .mdc-slider__thumb-knob:after{display:none!important;content:none!important}.progress-slider [class*=value-indicator],.progress-slider [class*=tooltip]{display:none!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i2$3.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i2$3.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7111
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: ProgressComponent, isStandalone: true, selector: "eru-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, ngImport: i0, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"\n [style.--mat-slider-active-track-color]=\"barColor()\"\n [style.--mat-slider-disabled-active-track-color]=\"barColor()\"\n [style.--mat-slider-with-tick-marks-active-container-color]=\"barColor()\"\n [style.--mat-slider-handle-color]=\"barColor()\"\n [style.--mat-slider-focus-handle-color]=\"barColor()\"\n [style.--mat-slider-hover-handle-color]=\"barColor()\"\n [style.--mat-slider-disabled-handle-color]=\"barColor()\"\n [style.--mat-slider-ripple-color]=\"barColor()\"\n (dblclick)=\"onActivate(); $event.stopPropagation()\">\n <span class=\"progress-value\" [style.color]=\"barColor()\">{{valueLabel()}}</span>\n <mat-slider \n class=\"progress-slider\" \n [min]=\"0\" \n [max]=\"100\" \n [step]=\"1\" \n [discrete]=\"true\" \n [showTickMarks]=\"true\"\n [disabled]=\"!isActive()\">\n <input \n matSliderThumb \n [ngModel]=\"displayValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n (blur)=\"onBlur()\"\n [disabled]=\"!isActive()\">\n </mat-slider>\n</div>\n\n", styles: [":root{--mat-slider-with-overlap-handle-outline-color: var(--grid-primary, #6750a4) !important;--mat-slider-with-tick-marks-active-container-color: var(--grid-primary, #6750a4) !important;--mat-slider-handle-height: 12px;--mat-slider-handle-width: 12px;--mat-slider-disabled-active-track-color: var(--grid-primary, #6750a4);--mat-slider-active-track-color: var(--grid-primary, #6750a4)}:host{display:block;height:100%;width:100%}.progress-bar-container{width:100%;height:100%;min-height:30px;position:relative;background:#e0e0e0;border-radius:4px;cursor:pointer}.progress-bar{height:100%;background:var(--grid-primary, #6750a4);transition:width .3s;border-radius:4px}.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);pointer-events:none;z-index:1}.drillable-value{color:inherit;text-decoration:underline;cursor:pointer;pointer-events:auto}.drillable-value:hover{opacity:.8}.progress-input-container{display:flex;flex-direction:row-reverse;align-items:center;padding:4px;width:100%;box-sizing:border-box;overflow:visible;max-height:30px}.progress-input-container.disabled{cursor:pointer}.progress-input-container.disabled .progress-slider{pointer-events:none}.progress-value{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);flex:0 0 auto;min-width:35px;text-align:right;white-space:nowrap;flex-shrink:0}.progress-slider{flex:1 1 0%;min-width:0!important;max-width:100%!important;overflow:visible}.progress-slider .mdc-slider__track{left:0!important}.progress-slider .mdc-slider{padding-left:0!important;margin-left:0!important}.progress-slider .mdc-slider__value-indicator,.progress-slider .mdc-slider__value-indicator-container{display:none!important;visibility:hidden!important}.progress-slider .mdc-slider__thumb:before,.progress-slider .mdc-slider__thumb:after{display:none!important;content:none!important}.progress-slider .mdc-slider__thumb-knob:before,.progress-slider .mdc-slider__thumb-knob:after{display:none!important;content:none!important}.progress-slider [class*=value-indicator],.progress-slider [class*=tooltip]{display:none!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i2$3.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i2$3.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
6620
7112
  }
6621
7113
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ProgressComponent, decorators: [{
6622
7114
  type: Component,
6623
- args: [{ selector: 'eru-progress', standalone: true, imports: [FormsModule, MatSliderModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"\n [style.--mat-slider-active-track-color]=\"barColor()\"\n [style.--mat-slider-disabled-active-track-color]=\"barColor()\"\n [style.--mat-slider-with-tick-marks-active-container-color]=\"barColor()\"\n [style.--mat-slider-handle-color]=\"barColor()\"\n [style.--mat-slider-focus-handle-color]=\"barColor()\"\n [style.--mat-slider-hover-handle-color]=\"barColor()\"\n [style.--mat-slider-disabled-handle-color]=\"barColor()\"\n [style.--mat-slider-ripple-color]=\"barColor()\"\n (dblclick)=\"onActivate(); $event.stopPropagation()\">\n <span class=\"progress-value\" [style.color]=\"barColor()\">{{displayValue()}}%</span>\n <mat-slider \n class=\"progress-slider\" \n [min]=\"0\" \n [max]=\"100\" \n [step]=\"1\" \n [discrete]=\"true\" \n [showTickMarks]=\"true\"\n [disabled]=\"!isActive()\">\n <input \n matSliderThumb \n [ngModel]=\"displayValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n (blur)=\"onBlur()\"\n [disabled]=\"!isActive()\">\n </mat-slider>\n</div>\n\n", styles: [":root{--mat-slider-with-overlap-handle-outline-color: var(--grid-primary, #6750a4) !important;--mat-slider-with-tick-marks-active-container-color: var(--grid-primary, #6750a4) !important;--mat-slider-handle-height: 12px;--mat-slider-handle-width: 12px;--mat-slider-disabled-active-track-color: var(--grid-primary, #6750a4);--mat-slider-active-track-color: var(--grid-primary, #6750a4)}:host{display:block;height:100%;width:100%}.progress-bar-container{width:100%;height:100%;min-height:30px;position:relative;background:#e0e0e0;border-radius:4px;cursor:pointer}.progress-bar{height:100%;background:var(--grid-primary, #6750a4);transition:width .3s;border-radius:4px}.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);pointer-events:none;z-index:1}.drillable-value{color:inherit;text-decoration:underline;cursor:pointer;pointer-events:auto}.drillable-value:hover{opacity:.8}.progress-input-container{display:flex;flex-direction:row-reverse;align-items:center;padding:4px;width:100%;box-sizing:border-box;overflow:visible;max-height:30px}.progress-input-container.disabled{cursor:pointer}.progress-input-container.disabled .progress-slider{pointer-events:none}.progress-value{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);flex:0 0 auto;min-width:35px;text-align:right;white-space:nowrap;flex-shrink:0}.progress-slider{flex:1 1 0%;min-width:0!important;max-width:100%!important;overflow:visible}.progress-slider .mdc-slider__track{left:0!important}.progress-slider .mdc-slider{padding-left:0!important;margin-left:0!important}.progress-slider .mdc-slider__value-indicator,.progress-slider .mdc-slider__value-indicator-container{display:none!important;visibility:hidden!important}.progress-slider .mdc-slider__thumb:before,.progress-slider .mdc-slider__thumb:after{display:none!important;content:none!important}.progress-slider .mdc-slider__thumb-knob:before,.progress-slider .mdc-slider__thumb-knob:after{display:none!important;content:none!important}.progress-slider [class*=value-indicator],.progress-slider [class*=tooltip]{display:none!important}\n"] }]
7115
+ args: [{ selector: 'eru-progress', standalone: true, imports: [FormsModule, MatSliderModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"\n [style.--mat-slider-active-track-color]=\"barColor()\"\n [style.--mat-slider-disabled-active-track-color]=\"barColor()\"\n [style.--mat-slider-with-tick-marks-active-container-color]=\"barColor()\"\n [style.--mat-slider-handle-color]=\"barColor()\"\n [style.--mat-slider-focus-handle-color]=\"barColor()\"\n [style.--mat-slider-hover-handle-color]=\"barColor()\"\n [style.--mat-slider-disabled-handle-color]=\"barColor()\"\n [style.--mat-slider-ripple-color]=\"barColor()\"\n (dblclick)=\"onActivate(); $event.stopPropagation()\">\n <span class=\"progress-value\" [style.color]=\"barColor()\">{{valueLabel()}}</span>\n <mat-slider \n class=\"progress-slider\" \n [min]=\"0\" \n [max]=\"100\" \n [step]=\"1\" \n [discrete]=\"true\" \n [showTickMarks]=\"true\"\n [disabled]=\"!isActive()\">\n <input \n matSliderThumb \n [ngModel]=\"displayValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n (blur)=\"onBlur()\"\n [disabled]=\"!isActive()\">\n </mat-slider>\n</div>\n\n", styles: [":root{--mat-slider-with-overlap-handle-outline-color: var(--grid-primary, #6750a4) !important;--mat-slider-with-tick-marks-active-container-color: var(--grid-primary, #6750a4) !important;--mat-slider-handle-height: 12px;--mat-slider-handle-width: 12px;--mat-slider-disabled-active-track-color: var(--grid-primary, #6750a4);--mat-slider-active-track-color: var(--grid-primary, #6750a4)}:host{display:block;height:100%;width:100%}.progress-bar-container{width:100%;height:100%;min-height:30px;position:relative;background:#e0e0e0;border-radius:4px;cursor:pointer}.progress-bar{height:100%;background:var(--grid-primary, #6750a4);transition:width .3s;border-radius:4px}.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);pointer-events:none;z-index:1}.drillable-value{color:inherit;text-decoration:underline;cursor:pointer;pointer-events:auto}.drillable-value:hover{opacity:.8}.progress-input-container{display:flex;flex-direction:row-reverse;align-items:center;padding:4px;width:100%;box-sizing:border-box;overflow:visible;max-height:30px}.progress-input-container.disabled{cursor:pointer}.progress-input-container.disabled .progress-slider{pointer-events:none}.progress-value{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);flex:0 0 auto;min-width:35px;text-align:right;white-space:nowrap;flex-shrink:0}.progress-slider{flex:1 1 0%;min-width:0!important;max-width:100%!important;overflow:visible}.progress-slider .mdc-slider__track{left:0!important}.progress-slider .mdc-slider{padding-left:0!important;margin-left:0!important}.progress-slider .mdc-slider__value-indicator,.progress-slider .mdc-slider__value-indicator-container{display:none!important;visibility:hidden!important}.progress-slider .mdc-slider__thumb:before,.progress-slider .mdc-slider__thumb:after{display:none!important;content:none!important}.progress-slider .mdc-slider__thumb-knob:before,.progress-slider .mdc-slider__thumb-knob:after{display:none!important;content:none!important}.progress-slider [class*=value-indicator],.progress-slider [class*=tooltip]{display:none!important}\n"] }]
6624
7116
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], fieldSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSize", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], valueChange: [{
6625
7117
  type: Output
6626
7118
  }], blur: [{
@@ -6697,6 +7189,12 @@ class SelectComponent {
6697
7189
  fieldSize = input(0, ...(ngDevMode ? [{ debugName: "fieldSize" }] : []));
6698
7190
  multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
6699
7191
  eruGridStore = input(null, ...(ngDevMode ? [{ debugName: "eruGridStore" }] : []));
7192
+ /**
7193
+ * The row this cell belongs to. Needed for a dependent dropdown: `dpef` names
7194
+ * another field on the SAME record whose value scopes this list, so the list
7195
+ * differs per row and cannot be resolved from the column config alone.
7196
+ */
7197
+ row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : []));
6700
7198
  // Outputs
6701
7199
  valueChange = new EventEmitter();
6702
7200
  blur = new EventEmitter();
@@ -6762,8 +7260,9 @@ class SelectComponent {
6762
7260
  if (store) {
6763
7261
  // Access the dynamicData signal to make computed reactive
6764
7262
  const dynamicDataMap = store.dynamicData();
6765
- if (dynamicDataMap && dynamicDataMap.has(apiName)) {
6766
- const apiData = dynamicDataMap.get(apiName);
7263
+ const cacheKey = this.optionsCacheKey();
7264
+ if (dynamicDataMap && dynamicDataMap.has(cacheKey)) {
7265
+ const apiData = dynamicDataMap.get(cacheKey);
6767
7266
  if (apiData) {
6768
7267
  // Process API response data
6769
7268
  // Expected format: array of { [apiField]: value } objects, or strings.
@@ -6866,10 +7365,14 @@ class SelectComponent {
6866
7365
  }
6867
7366
  // The map key the response is stored under: entity_name for ENTITY_DATA,
6868
7367
  // api_name for API (mirrors the lookup in filteredOptions()).
6869
- const key = cfg.option_type === 'ENTITY_DATA' ? (cfg.entity_name || '') : (cfg.api_name || '');
7368
+ // A dependent list is scoped by the parent field's value, which varies per
7369
+ // row, so the key carries that value — keying on the column alone would
7370
+ // let the first row's list be reused for every other row.
7371
+ const key = this.optionsCacheKey();
6870
7372
  if (!key) {
6871
7373
  return;
6872
7374
  }
7375
+ const filter = this.dependencyFilter();
6873
7376
  // Already fetched for this column, or a fetch is already pending → reuse it.
6874
7377
  if ((cache && cache.has(key)) || store.hasDynamicDataRequest(key)) {
6875
7378
  return;
@@ -6881,7 +7384,12 @@ class SelectComponent {
6881
7384
  fieldName: cfg.field_name,
6882
7385
  apiName: cfg.api_name,
6883
7386
  apiField: cfg.api_field,
6884
- search: untracked(() => this.searchText()) || ''
7387
+ search: untracked(() => this.searchText()) || '',
7388
+ ...(Object.keys(filter).length > 0 ? { filter } : {}),
7389
+ // API sources keep the legacy single dependency.
7390
+ ...(cfg.option_type === 'API' && cfg.dpef
7391
+ ? { dpef: String(cfg.dpef), dpefValue: this.legacyDpefValue() ?? '' }
7392
+ : {})
6885
7393
  });
6886
7394
  });
6887
7395
  }
@@ -6982,6 +7490,93 @@ class SelectComponent {
6982
7490
  this.valueChange.emit([]);
6983
7491
  }
6984
7492
  }
7493
+ /**
7494
+ * Dependency pairs for a cascading dropdown, normalised.
7495
+ *
7496
+ * `def` names the field on THIS ROW supplying a value; `dpef` names the field
7497
+ * in the option-source entity to match it against. A column saved before
7498
+ * `df_fields` existed carries a single `dpef`, read as `{ def: dpef, dpef }`.
7499
+ */
7500
+ dependencyPairs = computed(() => {
7501
+ const cfg = this.config();
7502
+ // ENTITY_DATA only — an API-sourced list scopes differently and keeps the
7503
+ // legacy single-dpef passthrough.
7504
+ if (cfg?.option_type !== 'ENTITY_DATA')
7505
+ return [];
7506
+ const raw = cfg?.df_fields;
7507
+ if (Array.isArray(raw) && raw.length > 0) {
7508
+ return raw
7509
+ .map((p) => ({
7510
+ def: String(p?.def ?? '').trim(),
7511
+ dpef: String(p?.dpef ?? '').trim(),
7512
+ }))
7513
+ .filter((p) => p.def.length > 0 && p.dpef.length > 0);
7514
+ }
7515
+ const legacy = String(cfg?.dpef ?? '').trim();
7516
+ if (!legacy)
7517
+ return [];
7518
+ return [{ def: String(cfg?.def ?? '').trim() || legacy, dpef: legacy }];
7519
+ }, ...(ngDevMode ? [{ debugName: "dependencyPairs" }] : []));
7520
+ /** Legacy single-dependency value from this row, for API sources. */
7521
+ legacyDpefValue() {
7522
+ const cfg = this.config();
7523
+ const parent = String(cfg?.dpef ?? '').trim();
7524
+ if (!parent)
7525
+ return undefined;
7526
+ const r = this.row();
7527
+ if (!r)
7528
+ return undefined;
7529
+ const fromEntityData = r.entity_data && typeof r.entity_data === 'object'
7530
+ ? r.entity_data[parent]
7531
+ : undefined;
7532
+ return fromEntityData !== undefined ? fromEntityData : r[parent];
7533
+ }
7534
+ /** True when this column's options are scoped by another field on the row. */
7535
+ isDependent = computed(() => this.dependencyPairs().length > 0, ...(ngDevMode ? [{ debugName: "isDependent" }] : []));
7536
+ /**
7537
+ * Filter sent with the options request: keys are fields in the option-source
7538
+ * entity, values read from this row. A pair whose source value is absent is
7539
+ * omitted rather than sent blank, since filtering on a blank would narrow the
7540
+ * list to nothing.
7541
+ */
7542
+ dependencyFilter = computed(() => {
7543
+ const filter = {};
7544
+ const r = this.row();
7545
+ if (!r)
7546
+ return filter;
7547
+ for (const pair of this.dependencyPairs()) {
7548
+ const fromEntityData = r.entity_data && typeof r.entity_data === 'object'
7549
+ ? r.entity_data[pair.def]
7550
+ : undefined;
7551
+ const value = fromEntityData !== undefined ? fromEntityData : r[pair.def];
7552
+ if (value === null || value === undefined || value === '')
7553
+ continue;
7554
+ if (Array.isArray(value) && value.length === 0)
7555
+ continue;
7556
+ filter[pair.dpef] = value;
7557
+ }
7558
+ return filter;
7559
+ }, ...(ngDevMode ? [{ debugName: "dependencyFilter" }] : []));
7560
+ /**
7561
+ * Map key the option list is cached under. Must be identical at request time
7562
+ * and at lookup time, or the response is written under one key and read from
7563
+ * another — so both go through this.
7564
+ */
7565
+ optionsCacheKey = computed(() => {
7566
+ const cfg = this.config();
7567
+ if (!cfg)
7568
+ return '';
7569
+ const baseKey = cfg.option_type === 'ENTITY_DATA' ? (cfg.entity_name || '') : (cfg.api_name || '');
7570
+ if (!baseKey)
7571
+ return '';
7572
+ const filter = this.dependencyFilter();
7573
+ const keys = Object.keys(filter).sort();
7574
+ if (keys.length === 0)
7575
+ return baseKey;
7576
+ // Sorted so the same filter always produces the same key regardless of the
7577
+ // order the pairs were authored in.
7578
+ return `${baseKey}|${keys.map(k => `${k}=${filter[k]}`).join('&')}`;
7579
+ }, ...(ngDevMode ? [{ debugName: "optionsCacheKey" }] : []));
6985
7580
  onSearchChange(searchValue) {
6986
7581
  this.searchText.set(searchValue || '');
6987
7582
  }
@@ -7103,7 +7698,7 @@ class SelectComponent {
7103
7698
  return undefined;
7104
7699
  }
7105
7700
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7106
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: SelectComponent, isStandalone: true, selector: "eru-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, viewQueries: [{ propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true }], ngImport: i0, template: "@if(isActive()) {\n<mat-form-field [appearance]=\"getProperty('appearance') || 'outline'\" class=\"select-form-field\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"select-container\">\n <mat-select [placeholder]=\"getProperty('placeholder') || ''\" [multiple]=\"multiple()\"\n [disabled]=\"getProperty('disabled') || !isEditable()\" [required]=\"hasRequiredValidation()\"\n [value]=\"currentValue()\" [compareWith]=\"compareWith\" panelClass=\"select-panel\" class=\"select-input\"\n (selectionChange)=\"onValueChange($event.value)\" (blur)=\"onBlur($event)\" (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n\n <mat-option disabled class=\"search-option\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-form-field [appearance]=\"getProperty('appearance') || 'outline'\" class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input matInput type=\"text\" class=\"search-input\" [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n @if (multiple()) {\n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox [checked]=\"isAllSelected()\" [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n }\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n @if (multiple()) {\n <mat-option [value]=\"option.value\" (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n {{ option.label }}\n </mat-option>\n } @else {\n <mat-option [value]=\"option.value\">\n {{ option.label }}\n </mat-option>\n }\n }\n </mat-select>\n </div>\n</mat-form-field>\n} @else {\n<div class=\"select-display\" [class.select-display-editable]=\"isEditable()\" (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"select-drillable\" (click)=\"onDrillableClick($event)\">\n {{formatDisplayValue() || (getProperty('placeholder') || '')}}\n </span>\n } @else {\n {{formatDisplayValue() || (getProperty('placeholder') || '')}}\n }\n</div>\n}", styles: ["@charset \"UTF-8\";.select-form-field{width:100%}.select-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.select-container{width:100%}.select-display{width:100%;padding:8px 12px;min-height:30px;display:flex;align-items:center;cursor:default}.select-display.select-display-editable{cursor:pointer}.select-display.select-display-editable:hover{background-color:#0000000a}.select-drillable{color:#1976d2;cursor:pointer;text-decoration:underline}.select-drillable:hover{color:#1565c0}.search-option{padding:2px 4px!important;cursor:default!important;pointer-events:auto!important;width:100%!important;box-sizing:border-box!important;overflow:hidden!important}.search-option mat-pseudo-checkbox{display:none!important}.search-option .mat-mdc-option{height:auto!important;padding:2px 4px!important;min-height:auto!important}.search-option .mat-mdc-option:hover{background:transparent!important}.search-option .mdc-list-item__primary-text{width:100%!important;max-width:100%!important;box-sizing:border-box!important;padding:0!important;margin:0!important;align-items:stretch!important}.search-option .search-form-field{width:100%!important;max-width:100%!important;pointer-events:auto!important;box-sizing:border-box!important;margin:0!important}.search-option .search-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.search-option .search-form-field .mat-mdc-text-field-wrapper{padding-bottom:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.search-option .search-form-field .mat-mdc-form-field-infix{width:100%!important;max-width:100%!important;box-sizing:border-box!important;min-height:auto!important;padding:0!important}.search-option .search-form-field .mat-mdc-form-field-flex{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.search-option .search-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--grid-font-size-body, 12px)!important;padding:8px 0}.search-option .select-all-container{padding:4px 0 4px 12px!important;display:flex!important;align-items:center!important;pointer-events:auto!important}.search-option .select-all-container .select-all-text{font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;font-weight:500!important;color:var(--grid-on-surface, #1d1b20)!important}.search-option .select-all-container mat-checkbox .mdc-checkbox{display:none!important}.search-option .select-all-container mat-checkbox .mdc-form-field{padding:0!important;margin:0!important}.search-option .select-all-container mat-checkbox .mdc-label{padding:0 0 0 12px!important;margin:0!important;cursor:pointer!important}.search-option .select-all-container mat-checkbox.mat-mdc-checkbox-checked .mdc-label:before,.search-option .select-all-container mat-checkbox.mat-mdc-checkbox-indeterminate .mdc-label:before{content:\"\\2713\";font-size:16px;font-weight:700;color:var(--grid-on-surface, #1d1b20);margin-right:4px}.select-panel{min-width:200px!important}.select-input{padding-left:4px}.select-panel .search-option .mat-mdc-option,.mat-mdc-select-panel .search-option .mat-mdc-option{min-height:auto!important;padding:2px 4px!important}.select-panel .mdc-list-item--disabled,.mat-mdc-select-panel .mdc-list-item--disabled{pointer-events:auto!important;cursor:default!important}.select-panel .mdc-list-item--disabled:hover,.mat-mdc-select-panel .mdc-list-item--disabled:hover{background:transparent!important}.select-panel mat-option mat-pseudo-checkbox{display:none!important}.select-panel mat-option.mdc-list-item--selected:not(.search-option) .mdc-list-item__primary-text:before{content:\"\\2713\"!important;font-size:16px;font-weight:700;color:var(--grid-on-surface, #1d1b20);margin-right:8px;display:inline!important}mat-option .mdc-list-item__primary-text{line-height:1.5!important;white-space:normal!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7701
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: SelectComponent, isStandalone: true, selector: "eru-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, viewQueries: [{ propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true }], ngImport: i0, template: "@if(isActive()) {\n<mat-form-field [appearance]=\"getProperty('appearance') || 'outline'\" class=\"select-form-field\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"select-container\">\n <mat-select [placeholder]=\"getProperty('placeholder') || ''\" [multiple]=\"multiple()\"\n [disabled]=\"getProperty('disabled') || !isEditable()\" [required]=\"hasRequiredValidation()\"\n [value]=\"currentValue()\" [compareWith]=\"compareWith\" panelClass=\"select-panel\" class=\"select-input\"\n (selectionChange)=\"onValueChange($event.value)\" (blur)=\"onBlur($event)\" (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n\n <mat-option disabled class=\"search-option\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-form-field [appearance]=\"getProperty('appearance') || 'outline'\" class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input matInput type=\"text\" class=\"search-input\" [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n @if (multiple()) {\n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox [checked]=\"isAllSelected()\" [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n }\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n @if (multiple()) {\n <mat-option [value]=\"option.value\" (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n {{ option.label }}\n </mat-option>\n } @else {\n <mat-option [value]=\"option.value\">\n {{ option.label }}\n </mat-option>\n }\n }\n </mat-select>\n </div>\n</mat-form-field>\n} @else {\n<div class=\"select-display\" [class.select-display-editable]=\"isEditable()\" (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"select-drillable\" (click)=\"onDrillableClick($event)\">\n {{formatDisplayValue() || (getProperty('placeholder') || '')}}\n </span>\n } @else {\n {{formatDisplayValue() || (getProperty('placeholder') || '')}}\n }\n</div>\n}", styles: ["@charset \"UTF-8\";.select-form-field{width:100%}.select-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.select-container{width:100%}.select-display{width:100%;padding:8px 12px;min-height:30px;display:flex;align-items:center;cursor:default}.select-display.select-display-editable{cursor:pointer}.select-display.select-display-editable:hover{background-color:#0000000a}.select-drillable{color:#1976d2;cursor:pointer;text-decoration:underline}.select-drillable:hover{color:#1565c0}.search-option{padding:2px 4px!important;cursor:default!important;pointer-events:auto!important;width:100%!important;box-sizing:border-box!important;overflow:hidden!important}.search-option mat-pseudo-checkbox{display:none!important}.search-option .mat-mdc-option{height:auto!important;padding:2px 4px!important;min-height:auto!important}.search-option .mat-mdc-option:hover{background:transparent!important}.search-option .mdc-list-item__primary-text{width:100%!important;max-width:100%!important;box-sizing:border-box!important;padding:0!important;margin:0!important;align-items:stretch!important}.search-option .search-form-field{width:100%!important;max-width:100%!important;pointer-events:auto!important;box-sizing:border-box!important;margin:0!important}.search-option .search-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.search-option .search-form-field .mat-mdc-text-field-wrapper{padding-bottom:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.search-option .search-form-field .mat-mdc-form-field-infix{width:100%!important;max-width:100%!important;box-sizing:border-box!important;min-height:auto!important;padding:0!important}.search-option .search-form-field .mat-mdc-form-field-flex{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.search-option .search-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--grid-font-size-body, 12px)!important;padding:8px 0}.search-option .select-all-container{padding:4px 0 4px 12px!important;display:flex!important;align-items:center!important;pointer-events:auto!important}.search-option .select-all-container .select-all-text{font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;font-weight:500!important;color:var(--grid-on-surface, #1d1b20)!important}.search-option .select-all-container mat-checkbox .mdc-checkbox{display:none!important}.search-option .select-all-container mat-checkbox .mdc-form-field{padding:0!important;margin:0!important}.search-option .select-all-container mat-checkbox .mdc-label{padding:0 0 0 12px!important;margin:0!important;cursor:pointer!important}.search-option .select-all-container mat-checkbox.mat-mdc-checkbox-checked .mdc-label:before,.search-option .select-all-container mat-checkbox.mat-mdc-checkbox-indeterminate .mdc-label:before{content:\"\\2713\";font-size:16px;font-weight:700;color:var(--grid-on-surface, #1d1b20);margin-right:4px}.select-panel{min-width:200px!important}.select-input{padding-left:4px}.select-panel .search-option .mat-mdc-option,.mat-mdc-select-panel .search-option .mat-mdc-option{min-height:auto!important;padding:2px 4px!important}.select-panel .mdc-list-item--disabled,.mat-mdc-select-panel .mdc-list-item--disabled{pointer-events:auto!important;cursor:default!important}.select-panel .mdc-list-item--disabled:hover,.mat-mdc-select-panel .mdc-list-item--disabled:hover{background:transparent!important}.select-panel mat-option mat-pseudo-checkbox{display:none!important}.select-panel mat-option.mdc-list-item--selected:not(.search-option) .mdc-list-item__primary-text:before{content:\"\\2713\"!important;font-size:16px;font-weight:700;color:var(--grid-on-surface, #1d1b20);margin-right:8px;display:inline!important}mat-option .mdc-list-item__primary-text{line-height:1.5!important;white-space:normal!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7107
7702
  }
7108
7703
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SelectComponent, decorators: [{
7109
7704
  type: Component,
@@ -7118,7 +7713,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
7118
7713
  }], ctorParameters: () => [], propDecorators: { selectContainer: [{
7119
7714
  type: ViewChild,
7120
7715
  args: ['selectContainer', { static: false }]
7121
- }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], fieldSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSize", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], valueChange: [{
7716
+ }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], fieldSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSize", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], valueChange: [{
7122
7717
  type: Output
7123
7718
  }], blur: [{
7124
7719
  type: Output
@@ -7828,6 +8423,12 @@ class PeopleComponent {
7828
8423
  isDrillable = input(false, ...(ngDevMode ? [{ debugName: "isDrillable" }] : []));
7829
8424
  columnWidth = input(0, ...(ngDevMode ? [{ debugName: "columnWidth" }] : []));
7830
8425
  eruGridStore = input(null, ...(ngDevMode ? [{ debugName: "eruGridStore" }] : []));
8426
+ /**
8427
+ * Content for the person card, supplied by the consumer. The grid owns the
8428
+ * trigger, positioning and dismissal; only the consumer can render a page,
8429
+ * which is what eru-studio's card is. Without a template the trigger is inert.
8430
+ */
8431
+ personCardTemplate = input(null, ...(ngDevMode ? [{ debugName: "personCardTemplate" }] : []));
7831
8432
  // Outputs
7832
8433
  valueChange = new EventEmitter();
7833
8434
  blur = new EventEmitter();
@@ -7835,6 +8436,78 @@ class PeopleComponent {
7835
8436
  drilldownClick = new EventEmitter();
7836
8437
  editModeChange = new EventEmitter();
7837
8438
  // Internal state
8439
+ /**
8440
+ * Whether more than one person may be picked. From the column config, so a
8441
+ * field declared single-select in the data model is enforced here too. The
8442
+ * stored value stays a list either way — the flag only limits how many may be
8443
+ * chosen, so no consumer has to handle a shape change.
8444
+ */
8445
+ isMultiple = computed(() => this.config()?.multiple !== false, ...(ngDevMode ? [{ debugName: "isMultiple" }] : []));
8446
+ /** Show the filter box inside the dropdown. Configurable, as in eru-studio. */
8447
+ isSearchable = computed(() => this.config()?.searchable !== false, ...(ngDevMode ? [{ debugName: "isSearchable" }] : []));
8448
+ /** mat-select holds a list when multiple, a single id otherwise. */
8449
+ selectValue = computed(() => {
8450
+ const ids = this.currentValue();
8451
+ return this.isMultiple() ? ids : (ids.length > 0 ? ids[0] : null);
8452
+ }, ...(ngDevMode ? [{ debugName: "selectValue" }] : []));
8453
+ /** Page the field names as the card. Blank means no card. */
8454
+ cardPage = computed(() => String(this.config()?.people_card || ''), ...(ngDevMode ? [{ debugName: "cardPage" }] : []));
8455
+ /** 'none' | 'hover' | 'click' — 'none' whenever no card page is mapped. */
8456
+ cardTrigger = computed(() => {
8457
+ if (!this.cardPage())
8458
+ return 'none';
8459
+ const t = String(this.config()?.show_people_card || 'none');
8460
+ return t === 'hover' || t === 'click' ? t : 'none';
8461
+ }, ...(ngDevMode ? [{ debugName: "cardTrigger" }] : []));
8462
+ /** The card is a view-mode affordance, and needs content to show. */
8463
+ cardEnabled = computed(() => !this.isActive() && this.cardTrigger() !== 'none' && !!this.personCardTemplate(), ...(ngDevMode ? [{ debugName: "cardEnabled" }] : []));
8464
+ /** Beside the avatar first (right, then left), dropping below only when
8465
+ * neither side fits. Offsets stay small: on hover the cursor has to cross the
8466
+ * gap to reach the card, and a wide one makes that a miss. */
8467
+ cardPositions = [
8468
+ { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: 2 },
8469
+ { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -2 },
8470
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 2 },
8471
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -2 },
8472
+ ];
8473
+ /** Person whose card is open, or null. */
8474
+ openCardUserId = signal(null, ...(ngDevMode ? [{ debugName: "openCardUserId" }] : []));
8475
+ // Leaving the avatar closes on a short delay: the cursor has to cross the gap
8476
+ // to reach the card, and an immediate close makes it unreachable. Entering
8477
+ // either the avatar or the card cancels the pending close.
8478
+ static CARD_CLOSE_DELAY_MS = 220;
8479
+ closeCardTimer = null;
8480
+ onAvatarEnter(userId) {
8481
+ if (!this.cardEnabled() || this.cardTrigger() !== 'hover')
8482
+ return;
8483
+ this.cancelPendingClose();
8484
+ this.openCardUserId.set(userId);
8485
+ }
8486
+ onAvatarLeave() {
8487
+ if (!this.cardEnabled() || this.cardTrigger() !== 'hover')
8488
+ return;
8489
+ this.cancelPendingClose();
8490
+ this.closeCardTimer = setTimeout(() => {
8491
+ this.closeCardTimer = null;
8492
+ this.openCardUserId.set(null);
8493
+ }, PeopleComponent.CARD_CLOSE_DELAY_MS);
8494
+ }
8495
+ onAvatarCardClick(event, userId) {
8496
+ if (!this.cardEnabled() || this.cardTrigger() !== 'click')
8497
+ return;
8498
+ event.stopPropagation();
8499
+ this.openCardUserId.set(this.openCardUserId() === userId ? null : userId);
8500
+ }
8501
+ closeCard() {
8502
+ this.cancelPendingClose();
8503
+ this.openCardUserId.set(null);
8504
+ }
8505
+ cancelPendingClose() {
8506
+ if (this.closeCardTimer === null)
8507
+ return;
8508
+ clearTimeout(this.closeCardTimer);
8509
+ this.closeCardTimer = null;
8510
+ }
7838
8511
  currentValue = signal([], ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
7839
8512
  searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
7840
8513
  selectContainer;
@@ -7871,10 +8544,17 @@ class PeopleComponent {
7871
8544
  const apiData = dynamicDataMap.get(apiName);
7872
8545
  if (apiData && Array.isArray(apiData)) {
7873
8546
  // Process API response: { fn, ln, id } -> label = fn + ln, value = id
8547
+ // The data model names the label attribute in `api_field`; when set, that
8548
+ // user attribute is the display name, matching how eru-studio labels
8549
+ // people. Falling back to first+last keeps existing columns unchanged.
8550
+ const labelField = String(cfg?.api_field || '').trim();
7874
8551
  let processedOptions = apiData.map((item) => {
7875
8552
  const fn = item.fn || '';
7876
8553
  const ln = item.ln || '';
7877
- const fullName = `${fn} ${ln}`.trim() || String(item.id || '');
8554
+ const fromField = labelField ? item?.[labelField] : undefined;
8555
+ const fullName = (fromField !== undefined && fromField !== null && String(fromField).trim().length > 0)
8556
+ ? String(fromField).trim()
8557
+ : (`${fn} ${ln}`.trim() || String(item.id || ''));
7878
8558
  return {
7879
8559
  value: String(item.id || ''),
7880
8560
  label: fullName,
@@ -7911,6 +8591,12 @@ class PeopleComponent {
7911
8591
  // Calculate how many avatars can fit
7912
8592
  const availableWidth = columnWidth - padding;
7913
8593
  const maxAvatars = (Math.floor(availableWidth / avatarWidth) - 1);
8594
+ // An explicit max_avatars wins; otherwise the width-derived count applies,
8595
+ // which keeps the cell from overflowing a narrow column.
8596
+ const configured = Number(this.config()?.max_avatars);
8597
+ if (Number.isFinite(configured) && configured > 0) {
8598
+ return Math.max(1, Math.floor(configured));
8599
+ }
7914
8600
  // Ensure at least 1 avatar can be shown, but cap at reasonable max
7915
8601
  return Math.max(1, Math.min(maxAvatars, 10));
7916
8602
  }, ...(ngDevMode ? [{ debugName: "maxDisplayAvatars" }] : []));
@@ -8017,6 +8703,19 @@ class PeopleComponent {
8017
8703
  this.currentValue.set(value);
8018
8704
  this.valueChange.emit(value.length > 0 ? value : null);
8019
8705
  }
8706
+ /**
8707
+ * mat-select emits a list when multiple and a bare id otherwise. The stored
8708
+ * value is a list in both cases, so a single selection is re-wrapped here —
8709
+ * that way turning cardinality off never changes the value's shape for
8710
+ * consumers or for the data model.
8711
+ */
8712
+ onSelectionChange(value) {
8713
+ if (Array.isArray(value)) {
8714
+ this.onValueChange(value);
8715
+ return;
8716
+ }
8717
+ this.onValueChange(value === null || value === undefined || value === '' ? [] : [value]);
8718
+ }
8020
8719
  onSearchChange(searchValue) {
8021
8720
  this.searchText.set(searchValue || '');
8022
8721
  }
@@ -8089,19 +8788,21 @@ class PeopleComponent {
8089
8788
  return `${option?.value || ''}_${index}`;
8090
8789
  }
8091
8790
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: PeopleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8092
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: PeopleComponent, isStandalone: true, selector: "eru-people", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, viewQueries: [{ propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true }], ngImport: i0, template: "@if(isActive()) {\n<mat-form-field appearance=\"outline\" class=\"people-form-field\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"people-select-container\">\n <mat-select [multiple]=\"true\" [disabled]=\"!isEditable()\" [value]=\"currentValue()\" panelClass=\"people-select-panel\"\n (selectionChange)=\"onValueChange($event.value)\" (blur)=\"onBlur($event)\" (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n\n <mat-select-trigger>\n <div class=\"people-trigger-content\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">Select people...</span>\n }\n </div>\n </mat-select-trigger>\n\n <mat-option disabled class=\"search-option\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input matInput type=\"text\" class=\"search-input\" placeholder=\"filter options...\" [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox [checked]=\"isAllSelected()\" [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"option-content\">\n <div class=\"option-avatar\">{{ getInitials(option.label) }}</div>\n <span class=\"option-text\">{{ option.label }}</span>\n </div>\n </mat-option>\n }\n </mat-select>\n </div>\n</mat-form-field>\n} @else {\n<div class=\"people-display\" [class.people-display-editable]=\"isEditable()\" (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"people-drillable\" (click)=\"onDrillableClick($event)\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n </span>\n } @else {\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n }\n</div>\n}", styles: [".people-form-field{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:var(--grid-font-size-body, 12px)!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.people-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-container{width:100%;display:flex;align-items:center}.people-trigger-content{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;overflow:hidden;flex-wrap:nowrap}.people-display{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;height:100%;cursor:default;overflow:hidden;flex-wrap:nowrap}.people-display.people-display-editable{cursor:pointer}.people-display.people-display-editable:hover{background-color:#0000000a}.avatar-circle{width:var(--grid-avatar-size, 28px);height:var(--grid-avatar-size, 28px);min-width:var(--grid-avatar-size, 28px);min-height:var(--grid-avatar-size, 28px);max-width:var(--grid-avatar-size, 28px);max-height:var(--grid-avatar-size, 28px);border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:var(--grid-avatar-font-size, 10px);font-weight:var(--grid-avatar-font-weight, 500);border:2px solid var(--grid-surface, #fef7ff);margin-left:-8px;position:relative;z-index:1;flex-shrink:0;box-sizing:border-box}.avatar-circle:first-child{margin-left:0}.avatar-circle.count-circle{background-color:var(--grid-surface-variant, #e7e0ec);color:var(--grid-on-surface-variant, #49454f);font-size:var(--grid-avatar-font-size, 11px);font-weight:var(--grid-avatar-font-weight, 500)}.no-people-text{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px;padding:4px 8px}.people-drillable{display:flex;align-items:center;gap:4px;cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.people-drillable .avatar-circle{text-decoration:none}.people-select-panel{min-width:200px!important}.people-select-panel .search-option{height:auto!important;padding:2px 4px!important;cursor:default!important;pointer-events:auto!important;width:100%!important;box-sizing:border-box!important;overflow:hidden!important}.people-select-panel .search-option .mdc-list-item__primary-text{opacity:1!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field{width:100%!important;max-width:100%!important;pointer-events:auto;box-sizing:border-box!important;margin-bottom:8px}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-panel .search-option .search-form-field .mat-mdc-text-field-wrapper{padding-bottom:0;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-infix{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .select-all-container{padding:4px 8px;cursor:pointer;pointer-events:auto}.people-select-panel .search-option .select-all-container .select-all-text{margin-left:8px;font-size:var(--grid-font-size-body, 12px)}.people-select-panel .search-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--grid-font-size-body, 12px)!important;padding:8px 0}.people-select-panel .mat-mdc-option{padding:8px 12px;cursor:pointer;transition:background-color .2s}.people-select-panel .mat-mdc-option:hover{background-color:#0000000a}.people-select-panel .mat-mdc-option[aria-selected=true]{background-color:#6750a41a}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i2$2.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8791
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: PeopleComponent, isStandalone: true, selector: "eru-people", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null }, personCardTemplate: { classPropertyName: "personCardTemplate", publicName: "personCardTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, viewQueries: [{ propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true }], ngImport: i0, template: "@if(isActive()) {\n<mat-form-field appearance=\"outline\" class=\"people-form-field\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"people-select-container\">\n <mat-select [multiple]=\"isMultiple()\" [disabled]=\"!isEditable()\" [value]=\"selectValue()\" panelClass=\"people-select-panel\"\n (selectionChange)=\"onSelectionChange($event.value)\" (blur)=\"onBlur($event)\" (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n\n <mat-select-trigger>\n <div class=\"people-trigger-content\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">Select people...</span>\n }\n </div>\n </mat-select-trigger>\n\n @if (isSearchable() || isMultiple()) {\n <mat-option disabled class=\"search-option\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n @if (isSearchable()) {\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input matInput type=\"text\" class=\"search-input\" placeholder=\"filter options...\" [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n }\n @if (isMultiple()) {\n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox [checked]=\"isAllSelected()\" [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n }\n </mat-option>\n }\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"option-content\">\n <div class=\"option-avatar\">{{ getInitials(option.label) }}</div>\n <span class=\"option-text\">{{ option.label }}</span>\n </div>\n </mat-option>\n }\n </mat-select>\n </div>\n</mat-form-field>\n} @else {\n<div class=\"people-display\" [class.people-display-editable]=\"isEditable()\" (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"people-drillable\" (click)=\"onDrillableClick($event)\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n </span>\n } @else {\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\"\n [style.z-index]=\"currentValue().length - $index\"\n [class.avatar-card-trigger]=\"cardEnabled()\"\n cdkOverlayOrigin\n #cardOrigin=\"cdkOverlayOrigin\"\n (mouseenter)=\"onAvatarEnter(assigneeId)\"\n (mouseleave)=\"onAvatarLeave()\"\n (click)=\"onAvatarCardClick($event, assigneeId)\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n\n <!-- Card content comes from the consumer; the grid owns trigger and\n placement. Beside the avatar first, dropping below only if neither\n side fits. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"cardOrigin\"\n [cdkConnectedOverlayOpen]=\"cardEnabled() && openCardUserId() === assigneeId\"\n [cdkConnectedOverlayPositions]=\"cardPositions\"\n (overlayOutsideClick)=\"closeCard()\"\n (detach)=\"closeCard()\">\n <div class=\"people-card-overlay\"\n (mouseenter)=\"onAvatarEnter(assigneeId)\"\n (mouseleave)=\"onAvatarLeave()\">\n <ng-container\n *ngTemplateOutlet=\"personCardTemplate()!;\n context: { $implicit: assigneeId, column: config(), cardPage: cardPage() }\">\n </ng-container>\n </div>\n </ng-template>\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n }\n</div>\n}", styles: [".people-form-field{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:var(--grid-font-size-body, 12px)!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.people-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-container{width:100%;display:flex;align-items:center}.people-trigger-content{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;overflow:hidden;flex-wrap:nowrap}.people-display{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;height:100%;cursor:default;overflow:hidden;flex-wrap:nowrap}.people-display.people-display-editable{cursor:pointer}.people-display.people-display-editable:hover{background-color:#0000000a}.avatar-circle{width:var(--grid-avatar-size, 28px);height:var(--grid-avatar-size, 28px);min-width:var(--grid-avatar-size, 28px);min-height:var(--grid-avatar-size, 28px);max-width:var(--grid-avatar-size, 28px);max-height:var(--grid-avatar-size, 28px);border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:var(--grid-avatar-font-size, 10px);font-weight:var(--grid-avatar-font-weight, 500);border:2px solid var(--grid-surface, #fef7ff);margin-left:-8px;position:relative;z-index:1;flex-shrink:0;box-sizing:border-box}.avatar-circle:first-child{margin-left:0}.avatar-circle.count-circle{background-color:var(--grid-surface-variant, #e7e0ec);color:var(--grid-on-surface-variant, #49454f);font-size:var(--grid-avatar-font-size, 11px);font-weight:var(--grid-avatar-font-weight, 500)}.no-people-text{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px;padding:4px 8px}.people-drillable{display:flex;align-items:center;gap:4px;cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.people-drillable .avatar-circle{text-decoration:none}.people-select-panel{min-width:200px!important}.people-select-panel .search-option{height:auto!important;padding:2px 4px!important;cursor:default!important;pointer-events:auto!important;width:100%!important;box-sizing:border-box!important;overflow:hidden!important}.people-select-panel .search-option .mdc-list-item__primary-text{opacity:1!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field{width:100%!important;max-width:100%!important;pointer-events:auto;box-sizing:border-box!important;margin-bottom:8px}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-panel .search-option .search-form-field .mat-mdc-text-field-wrapper{padding-bottom:0;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-infix{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .select-all-container{padding:4px 8px;cursor:pointer;pointer-events:auto}.people-select-panel .search-option .select-all-container .select-all-text{margin-left:8px;font-size:var(--grid-font-size-body, 12px)}.people-select-panel .search-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--grid-font-size-body, 12px)!important;padding:8px 0}.people-select-panel .mat-mdc-option{padding:8px 12px;cursor:pointer;transition:background-color .2s}.people-select-panel .mat-mdc-option:hover{background-color:#0000000a}.people-select-panel .mat-mdc-option[aria-selected=true]{background-color:#6750a41a}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}.people-card-overlay{background:var(--eru-grid-surface, #ffffff);border:1px solid var(--eru-grid-divider, #e5e7eb);border-radius:10px;box-shadow:0 8px 24px -8px #00000040;overflow:hidden}.avatar-card-trigger{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$3.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$3.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i2$2.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8093
8792
  }
8094
8793
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: PeopleComponent, decorators: [{
8095
8794
  type: Component,
8096
8795
  args: [{ selector: 'eru-people', standalone: true, imports: [
8796
+ CommonModule,
8797
+ OverlayModule,
8097
8798
  FormsModule,
8098
8799
  MatFormFieldModule,
8099
8800
  MatInputModule,
8100
8801
  MatSelectModule,
8101
8802
  MatOptionModule,
8102
8803
  MatCheckboxModule
8103
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()) {\n<mat-form-field appearance=\"outline\" class=\"people-form-field\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"people-select-container\">\n <mat-select [multiple]=\"true\" [disabled]=\"!isEditable()\" [value]=\"currentValue()\" panelClass=\"people-select-panel\"\n (selectionChange)=\"onValueChange($event.value)\" (blur)=\"onBlur($event)\" (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n\n <mat-select-trigger>\n <div class=\"people-trigger-content\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">Select people...</span>\n }\n </div>\n </mat-select-trigger>\n\n <mat-option disabled class=\"search-option\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input matInput type=\"text\" class=\"search-input\" placeholder=\"filter options...\" [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox [checked]=\"isAllSelected()\" [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"option-content\">\n <div class=\"option-avatar\">{{ getInitials(option.label) }}</div>\n <span class=\"option-text\">{{ option.label }}</span>\n </div>\n </mat-option>\n }\n </mat-select>\n </div>\n</mat-form-field>\n} @else {\n<div class=\"people-display\" [class.people-display-editable]=\"isEditable()\" (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"people-drillable\" (click)=\"onDrillableClick($event)\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n </span>\n } @else {\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n }\n</div>\n}", styles: [".people-form-field{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:var(--grid-font-size-body, 12px)!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.people-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-container{width:100%;display:flex;align-items:center}.people-trigger-content{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;overflow:hidden;flex-wrap:nowrap}.people-display{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;height:100%;cursor:default;overflow:hidden;flex-wrap:nowrap}.people-display.people-display-editable{cursor:pointer}.people-display.people-display-editable:hover{background-color:#0000000a}.avatar-circle{width:var(--grid-avatar-size, 28px);height:var(--grid-avatar-size, 28px);min-width:var(--grid-avatar-size, 28px);min-height:var(--grid-avatar-size, 28px);max-width:var(--grid-avatar-size, 28px);max-height:var(--grid-avatar-size, 28px);border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:var(--grid-avatar-font-size, 10px);font-weight:var(--grid-avatar-font-weight, 500);border:2px solid var(--grid-surface, #fef7ff);margin-left:-8px;position:relative;z-index:1;flex-shrink:0;box-sizing:border-box}.avatar-circle:first-child{margin-left:0}.avatar-circle.count-circle{background-color:var(--grid-surface-variant, #e7e0ec);color:var(--grid-on-surface-variant, #49454f);font-size:var(--grid-avatar-font-size, 11px);font-weight:var(--grid-avatar-font-weight, 500)}.no-people-text{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px;padding:4px 8px}.people-drillable{display:flex;align-items:center;gap:4px;cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.people-drillable .avatar-circle{text-decoration:none}.people-select-panel{min-width:200px!important}.people-select-panel .search-option{height:auto!important;padding:2px 4px!important;cursor:default!important;pointer-events:auto!important;width:100%!important;box-sizing:border-box!important;overflow:hidden!important}.people-select-panel .search-option .mdc-list-item__primary-text{opacity:1!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field{width:100%!important;max-width:100%!important;pointer-events:auto;box-sizing:border-box!important;margin-bottom:8px}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-panel .search-option .search-form-field .mat-mdc-text-field-wrapper{padding-bottom:0;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-infix{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .select-all-container{padding:4px 8px;cursor:pointer;pointer-events:auto}.people-select-panel .search-option .select-all-container .select-all-text{margin-left:8px;font-size:var(--grid-font-size-body, 12px)}.people-select-panel .search-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--grid-font-size-body, 12px)!important;padding:8px 0}.people-select-panel .mat-mdc-option{padding:8px 12px;cursor:pointer;transition:background-color .2s}.people-select-panel .mat-mdc-option:hover{background-color:#0000000a}.people-select-panel .mat-mdc-option[aria-selected=true]{background-color:#6750a41a}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}\n"] }]
8104
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], valueChange: [{
8804
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()) {\n<mat-form-field appearance=\"outline\" class=\"people-form-field\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"people-select-container\">\n <mat-select [multiple]=\"isMultiple()\" [disabled]=\"!isEditable()\" [value]=\"selectValue()\" panelClass=\"people-select-panel\"\n (selectionChange)=\"onSelectionChange($event.value)\" (blur)=\"onBlur($event)\" (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n\n <mat-select-trigger>\n <div class=\"people-trigger-content\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">Select people...</span>\n }\n </div>\n </mat-select-trigger>\n\n @if (isSearchable() || isMultiple()) {\n <mat-option disabled class=\"search-option\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n @if (isSearchable()) {\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input matInput type=\"text\" class=\"search-input\" placeholder=\"filter options...\" [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\" (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n }\n @if (isMultiple()) {\n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox [checked]=\"isAllSelected()\" [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\" (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n }\n </mat-option>\n }\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"option-content\">\n <div class=\"option-avatar\">{{ getInitials(option.label) }}</div>\n <span class=\"option-text\">{{ option.label }}</span>\n </div>\n </mat-option>\n }\n </mat-select>\n </div>\n</mat-form-field>\n} @else {\n<div class=\"people-display\" [class.people-display-editable]=\"isEditable()\" (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"people-drillable\" (click)=\"onDrillableClick($event)\">\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\" [style.z-index]=\"currentValue().length - $index\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n </span>\n } @else {\n @if (isAssigneeArray(currentValue()) && currentValue().length > 0) {\n @for (assigneeId of displayAvatars(); track assigneeId) {\n <div class=\"avatar-circle\"\n [style.z-index]=\"currentValue().length - $index\"\n [class.avatar-card-trigger]=\"cardEnabled()\"\n cdkOverlayOrigin\n #cardOrigin=\"cdkOverlayOrigin\"\n (mouseenter)=\"onAvatarEnter(assigneeId)\"\n (mouseleave)=\"onAvatarLeave()\"\n (click)=\"onAvatarCardClick($event, assigneeId)\">\n {{ getInitials(getDisplayName(assigneeId)) }}\n\n <!-- Card content comes from the consumer; the grid owns trigger and\n placement. Beside the avatar first, dropping below only if neither\n side fits. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"cardOrigin\"\n [cdkConnectedOverlayOpen]=\"cardEnabled() && openCardUserId() === assigneeId\"\n [cdkConnectedOverlayPositions]=\"cardPositions\"\n (overlayOutsideClick)=\"closeCard()\"\n (detach)=\"closeCard()\">\n <div class=\"people-card-overlay\"\n (mouseenter)=\"onAvatarEnter(assigneeId)\"\n (mouseleave)=\"onAvatarLeave()\">\n <ng-container\n *ngTemplateOutlet=\"personCardTemplate()!;\n context: { $implicit: assigneeId, column: config(), cardPage: cardPage() }\">\n </ng-container>\n </div>\n </ng-template>\n </div>\n }\n @if (remainingCount() > 0) {\n <div class=\"avatar-circle count-circle\" [style.z-index]=\"1\">\n +{{ remainingCount() }}\n </div>\n }\n } @else {\n <span class=\"no-people-text\">No people assigned</span>\n }\n }\n</div>\n}", styles: [".people-form-field{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:var(--grid-font-size-body, 12px)!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.people-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-container{width:100%;display:flex;align-items:center}.people-trigger-content{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;overflow:hidden;flex-wrap:nowrap}.people-display{display:flex;align-items:center;gap:4px;padding:4px;min-height:32px;width:100%;height:100%;cursor:default;overflow:hidden;flex-wrap:nowrap}.people-display.people-display-editable{cursor:pointer}.people-display.people-display-editable:hover{background-color:#0000000a}.avatar-circle{width:var(--grid-avatar-size, 28px);height:var(--grid-avatar-size, 28px);min-width:var(--grid-avatar-size, 28px);min-height:var(--grid-avatar-size, 28px);max-width:var(--grid-avatar-size, 28px);max-height:var(--grid-avatar-size, 28px);border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:var(--grid-avatar-font-size, 10px);font-weight:var(--grid-avatar-font-weight, 500);border:2px solid var(--grid-surface, #fef7ff);margin-left:-8px;position:relative;z-index:1;flex-shrink:0;box-sizing:border-box}.avatar-circle:first-child{margin-left:0}.avatar-circle.count-circle{background-color:var(--grid-surface-variant, #e7e0ec);color:var(--grid-on-surface-variant, #49454f);font-size:var(--grid-avatar-font-size, 11px);font-weight:var(--grid-avatar-font-weight, 500)}.no-people-text{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px;padding:4px 8px}.people-drillable{display:flex;align-items:center;gap:4px;cursor:pointer;color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.people-drillable .avatar-circle{text-decoration:none}.people-select-panel{min-width:200px!important}.people-select-panel .search-option{height:auto!important;padding:2px 4px!important;cursor:default!important;pointer-events:auto!important;width:100%!important;box-sizing:border-box!important;overflow:hidden!important}.people-select-panel .search-option .mdc-list-item__primary-text{opacity:1!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field{width:100%!important;max-width:100%!important;pointer-events:auto;box-sizing:border-box!important;margin-bottom:8px}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.people-select-panel .search-option .search-form-field .mat-mdc-text-field-wrapper{padding-bottom:0;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .search-form-field .mat-mdc-form-field-infix{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.people-select-panel .search-option .select-all-container{padding:4px 8px;cursor:pointer;pointer-events:auto}.people-select-panel .search-option .select-all-container .select-all-text{margin-left:8px;font-size:var(--grid-font-size-body, 12px)}.people-select-panel .search-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--grid-font-size-body, 12px)!important;padding:8px 0}.people-select-panel .mat-mdc-option{padding:8px 12px;cursor:pointer;transition:background-color .2s}.people-select-panel .mat-mdc-option:hover{background-color:#0000000a}.people-select-panel .mat-mdc-option[aria-selected=true]{background-color:#6750a41a}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}.people-card-overlay{background:var(--eru-grid-surface, #ffffff);border:1px solid var(--eru-grid-divider, #e5e7eb);border-radius:10px;box-shadow:0 8px 24px -8px #00000040;overflow:hidden}.avatar-card-trigger{cursor:pointer}\n"] }]
8805
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], personCardTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "personCardTemplate", required: false }] }], valueChange: [{
8105
8806
  type: Output
8106
8807
  }], blur: [{
8107
8808
  type: Output
@@ -8383,6 +9084,17 @@ class AttachmentComponent {
8383
9084
  fileViewed = new EventEmitter();
8384
9085
  // Internal state
8385
9086
  currentValue = signal([], ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
9087
+ /**
9088
+ * Why the last attempted file was refused, or '' when none.
9089
+ *
9090
+ * The checks were already here but only reached console.warn, so a user
9091
+ * dropping an oversized file saw nothing happen at all. Shown in the panel,
9092
+ * matching how eru-studio's attachment reports a rejection.
9093
+ */
9094
+ rejectionMessage = signal('', ...(ngDevMode ? [{ debugName: "rejectionMessage" }] : []));
9095
+ rejectFile(reason) {
9096
+ this.rejectionMessage.set(reason);
9097
+ }
8386
9098
  isOverlayOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOverlayOpen" }] : []));
8387
9099
  isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
8388
9100
  // ViewChild for file input
@@ -8543,18 +9255,19 @@ class AttachmentComponent {
8543
9255
  // Check file type if restrictions exist
8544
9256
  if (cfg.allowedFileTypes && cfg.allowedFileTypes.length > 0) {
8545
9257
  if (!cfg.allowedFileTypes.includes(file.type)) {
8546
- console.warn(`File type ${file.type} is not allowed`);
9258
+ this.rejectFile(`${file.name}: only ${cfg.allowedFileTypes.join(', ')} allowed`);
8547
9259
  return;
8548
9260
  }
8549
9261
  }
8550
9262
  // Check file size if restriction exists
8551
9263
  if (cfg.maxFileSize && file.size > cfg.maxFileSize) {
8552
- console.warn(`File size ${file.size} exceeds maximum ${cfg.maxFileSize}`);
9264
+ const asMb = (n) => `${(n / (1024 * 1024)).toFixed(2)} MB`;
9265
+ this.rejectFile(`${file.name}: ${asMb(file.size)} exceeds the ${asMb(cfg.maxFileSize)} limit`);
8553
9266
  return;
8554
9267
  }
8555
9268
  // Check max files if restriction exists
8556
9269
  if (cfg.maxFiles && this.currentValue().length >= cfg.maxFiles) {
8557
- console.warn(`Maximum number of files (${cfg.maxFiles}) reached`);
9270
+ this.rejectFile(`At most ${cfg.maxFiles} file${cfg.maxFiles === 1 ? '' : 's'} allowed`);
8558
9271
  return;
8559
9272
  }
8560
9273
  const newFileName = file.name.replace(/_/g, '');
@@ -8636,7 +9349,7 @@ class AttachmentComponent {
8636
9349
  return file;
8637
9350
  }
8638
9351
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AttachmentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8639
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AttachmentComponent, isStandalone: true, selector: "eru-attachment", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange", fileAdded: "fileAdded", fileDeleted: "fileDeleted", fileViewed: "fileViewed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div class=\"attachment-overlay-container\">\n <div \n class=\"attachment-cell-wrapper\"\n cdkOverlayOrigin \n #attachmentTrigger=\"cdkOverlayOrigin\"\n [class.attachment-display-editable]=\"isEditable() && !isActive()\"\n [class.attachment-display-viewable]=\"!isEditable() && hasFiles()\"\n (dblclick)=\"onActivate()\">\n @if(hasFiles()) {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n } @else {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n }\n </div>\n\n <ng-template \n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"attachmentTrigger\"\n [cdkConnectedOverlayOpen]=\"isOverlayOpen() && (isActive() || !isEditable())\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayBackdropClass]=\"'attachment-backdrop'\"\n (backdropClick)=\"onOverlayBackdropClick()\"\n (detach)=\"closeOverlay()\">\n <div \n class=\"attachment-menu-overlay\" \n [style.width.px]=\"columnWidth() || config().columnWidth || 300\" \n [style.min-width.px]=\"300\"\n (click)=\"onOverlayClick($event)\">\n <div class=\"attachment-container\" (click)=\"$event.stopPropagation()\">\n @if (isEditable()) {\n <!-- Header Section -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Add or Drag files</span>\n <button\n mat-flat-button\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n class=\"attachment-upload-btn\"\n [disabled]=\"config().disabled\">\n Upload\n </button>\n </div>\n\n <!-- Drop zone -->\n <div\n class=\"attachment-drop-zone\"\n (drop)=\"onFileDrop($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (paste)=\"onPaste($event)\"\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n [class.dragging]=\"isDragging()\">\n <div class=\"attachment-drop-content\">\n <div class=\"attachment-drop-icon\">+</div>\n <div class=\"attachment-drop-text\">\n <div class=\"primary-text\">Click to upload or drag files here</div>\n <div class=\"secondary-text\">Support multiple files</div>\n </div>\n </div>\n </div>\n\n <!-- Hidden file input -->\n <input\n #fileInput\n type=\"file\"\n multiple\n (change)=\"onFileInputChange($event)\"\n style=\"display: none;\">\n } @else {\n <!-- Read-only header -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Attachments</span>\n </div>\n }\n\n <!-- File list -->\n @if (hasFiles()) {\n <div class=\"attachment-file-list\">\n @for (file of currentValue(); track $index) {\n <div class=\"attachment-file-item\">\n <div class=\"file-info\">\n <mat-icon class=\"file-status-icon done-icon\">check_circle</mat-icon>\n <span class=\"file-name\">{{ getFileName(file) }}</span>\n </div>\n <div class=\"file-actions\">\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"viewFile(file, $event)\"\n title=\"View\"\n type=\"button\">\n <mat-icon>visibility</mat-icon>\n </button>\n @if (isEditable()) {\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"deleteFile(file, $event)\"\n title=\"Delete\"\n type=\"button\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </ng-template>\n</div>\n\n", styles: [".attachment-overlay-container{width:100%;height:100%}.attachment-cell-wrapper{display:flex;align-items:center;justify-content:center;padding:4px;min-height:32px;cursor:default}.attachment-cell-wrapper.attachment-display-editable{cursor:pointer}.attachment-cell-wrapper.attachment-display-editable:hover{background-color:#0000000a}.attachment-cell-wrapper.attachment-display-viewable{cursor:pointer}.attachment-cell-wrapper.attachment-display-viewable:hover{background-color:#0000000a}.cell-attachment-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.drillable-value{display:flex;align-items:center;justify-content:center;cursor:pointer}.drillable-value svg{text-decoration:none}.drillable-value:hover{opacity:.8}.attachment-backdrop{background:transparent}.attachment-menu-overlay{background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;overflow:hidden;position:relative;z-index:1000}.attachment-container{padding:16px}.attachment-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.attachment-title{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1c1b1f)}.attachment-upload-btn{font-size:var(--grid-font-size-body, 12px)!important;min-width:50px}.attachment-drop-zone{border:2px dashed var(--grid-outline-variant, #cac4d0);border-radius:8px;padding:40px 20px;text-align:center;cursor:pointer;transition:all .3s ease;background:var(--grid-surface-variant, #e7e0ec);margin:16px 0}.attachment-drop-zone:hover{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7)}.attachment-drop-zone.dragging{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7);transform:scale(1.01)}.attachment-drop-content{display:flex;flex-direction:column;align-items:center;gap:12px}.attachment-drop-icon{font-size:48px;color:var(--grid-primary, #6750a4);font-weight:300;line-height:1}.attachment-drop-text .primary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:14px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;margin:0}.attachment-drop-text .secondary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-top:4px;display:block}.attachment-file-list{display:block;margin-top:16px;max-height:300px;overflow-y:auto}.attachment-file-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;margin-bottom:8px;background:var(--grid-surface-variant, #e7e0ec);transition:background-color .2s}.attachment-file-item:hover{background:var(--grid-surface-container, #f3edf7)}.file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.file-status-icon{font-size:18px!important;width:18px!important;height:18px!important;flex-shrink:0}.file-status-icon.done-icon{color:#22c55e}.file-name{font-size:var(--grid-font-size-body, 12px);color:var(--grid-on-surface, #1c1b1f);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-actions{display:flex;gap:8px;flex-shrink:0}.file-action-btn{width:28px!important;height:28px!important;padding:0!important;display:flex!important;align-items:center;justify-content:center}.file-action-btn .mat-icon{font-size:18px;width:18px;height:18px;color:var(--grid-on-surface-variant, #49454f)}.file-action-btn:hover .mat-icon{color:var(--grid-on-surface, #1c1b1f)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$3.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$3.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9352
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AttachmentComponent, isStandalone: true, selector: "eru-attachment", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange", fileAdded: "fileAdded", fileDeleted: "fileDeleted", fileViewed: "fileViewed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div class=\"attachment-overlay-container\">\n <div \n class=\"attachment-cell-wrapper\"\n cdkOverlayOrigin \n #attachmentTrigger=\"cdkOverlayOrigin\"\n [class.attachment-display-editable]=\"isEditable() && !isActive()\"\n [class.attachment-display-viewable]=\"!isEditable() && hasFiles()\"\n (dblclick)=\"onActivate()\">\n @if(hasFiles()) {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n } @else {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n }\n </div>\n\n <ng-template \n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"attachmentTrigger\"\n [cdkConnectedOverlayOpen]=\"isOverlayOpen() && (isActive() || !isEditable())\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayBackdropClass]=\"'attachment-backdrop'\"\n (backdropClick)=\"onOverlayBackdropClick()\"\n (detach)=\"closeOverlay()\">\n <div \n class=\"attachment-menu-overlay\" \n [style.width.px]=\"columnWidth() || config().columnWidth || 300\" \n [style.min-width.px]=\"300\"\n (click)=\"onOverlayClick($event)\">\n <div class=\"attachment-container\" (click)=\"$event.stopPropagation()\">\n @if (rejectionMessage()) {\n <div class=\"attachment-rejection\" role=\"alert\">{{ rejectionMessage() }}</div>\n }\n @if (isEditable()) {\n <!-- Header Section -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Add or Drag files</span>\n <button\n mat-flat-button\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n class=\"attachment-upload-btn\"\n [disabled]=\"config().disabled\">\n Upload\n </button>\n </div>\n\n <!-- Drop zone -->\n <div\n class=\"attachment-drop-zone\"\n (drop)=\"onFileDrop($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (paste)=\"onPaste($event)\"\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n [class.dragging]=\"isDragging()\">\n <div class=\"attachment-drop-content\">\n <div class=\"attachment-drop-icon\">+</div>\n <div class=\"attachment-drop-text\">\n <div class=\"primary-text\">Click to upload or drag files here</div>\n <div class=\"secondary-text\">Support multiple files</div>\n </div>\n </div>\n </div>\n\n <!-- Hidden file input -->\n <input\n #fileInput\n type=\"file\"\n multiple\n (change)=\"onFileInputChange($event)\"\n style=\"display: none;\">\n } @else {\n <!-- Read-only header -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Attachments</span>\n </div>\n }\n\n <!-- File list -->\n @if (hasFiles()) {\n <div class=\"attachment-file-list\">\n @for (file of currentValue(); track $index) {\n <div class=\"attachment-file-item\">\n <div class=\"file-info\">\n <mat-icon class=\"file-status-icon done-icon\">check_circle</mat-icon>\n <span class=\"file-name\">{{ getFileName(file) }}</span>\n </div>\n <div class=\"file-actions\">\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"viewFile(file, $event)\"\n title=\"View\"\n type=\"button\">\n <mat-icon>visibility</mat-icon>\n </button>\n @if (isEditable()) {\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"deleteFile(file, $event)\"\n title=\"Delete\"\n type=\"button\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </ng-template>\n</div>\n\n", styles: [".attachment-rejection{padding:8px 10px;margin-bottom:8px;border-radius:6px;background:#fef2f2;color:#b91c1c;font-size:12px;line-height:1.4}.attachment-overlay-container{width:100%;height:100%}.attachment-cell-wrapper{display:flex;align-items:center;justify-content:center;padding:4px;min-height:32px;cursor:default}.attachment-cell-wrapper.attachment-display-editable{cursor:pointer}.attachment-cell-wrapper.attachment-display-editable:hover{background-color:#0000000a}.attachment-cell-wrapper.attachment-display-viewable{cursor:pointer}.attachment-cell-wrapper.attachment-display-viewable:hover{background-color:#0000000a}.cell-attachment-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.drillable-value{display:flex;align-items:center;justify-content:center;cursor:pointer}.drillable-value svg{text-decoration:none}.drillable-value:hover{opacity:.8}.attachment-backdrop{background:transparent}.attachment-menu-overlay{background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;overflow:hidden;position:relative;z-index:1000}.attachment-container{padding:16px}.attachment-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.attachment-title{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1c1b1f)}.attachment-upload-btn{font-size:var(--grid-font-size-body, 12px)!important;min-width:50px}.attachment-drop-zone{border:2px dashed var(--grid-outline-variant, #cac4d0);border-radius:8px;padding:40px 20px;text-align:center;cursor:pointer;transition:all .3s ease;background:var(--grid-surface-variant, #e7e0ec);margin:16px 0}.attachment-drop-zone:hover{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7)}.attachment-drop-zone.dragging{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7);transform:scale(1.01)}.attachment-drop-content{display:flex;flex-direction:column;align-items:center;gap:12px}.attachment-drop-icon{font-size:48px;color:var(--grid-primary, #6750a4);font-weight:300;line-height:1}.attachment-drop-text .primary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:14px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;margin:0}.attachment-drop-text .secondary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-top:4px;display:block}.attachment-file-list{display:block;margin-top:16px;max-height:300px;overflow-y:auto}.attachment-file-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;margin-bottom:8px;background:var(--grid-surface-variant, #e7e0ec);transition:background-color .2s}.attachment-file-item:hover{background:var(--grid-surface-container, #f3edf7)}.file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.file-status-icon{font-size:18px!important;width:18px!important;height:18px!important;flex-shrink:0}.file-status-icon.done-icon{color:#22c55e}.file-name{font-size:var(--grid-font-size-body, 12px);color:var(--grid-on-surface, #1c1b1f);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-actions{display:flex;gap:8px;flex-shrink:0}.file-action-btn{width:28px!important;height:28px!important;padding:0!important;display:flex!important;align-items:center;justify-content:center}.file-action-btn .mat-icon{font-size:18px;width:18px;height:18px;color:var(--grid-on-surface-variant, #49454f)}.file-action-btn:hover .mat-icon{color:var(--grid-on-surface, #1c1b1f)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$3.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$3.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8640
9353
  }
8641
9354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AttachmentComponent, decorators: [{
8642
9355
  type: Component,
@@ -8647,7 +9360,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
8647
9360
  MatButtonModule,
8648
9361
  MatIconModule,
8649
9362
  OverlayModule
8650
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"attachment-overlay-container\">\n <div \n class=\"attachment-cell-wrapper\"\n cdkOverlayOrigin \n #attachmentTrigger=\"cdkOverlayOrigin\"\n [class.attachment-display-editable]=\"isEditable() && !isActive()\"\n [class.attachment-display-viewable]=\"!isEditable() && hasFiles()\"\n (dblclick)=\"onActivate()\">\n @if(hasFiles()) {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n } @else {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n }\n </div>\n\n <ng-template \n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"attachmentTrigger\"\n [cdkConnectedOverlayOpen]=\"isOverlayOpen() && (isActive() || !isEditable())\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayBackdropClass]=\"'attachment-backdrop'\"\n (backdropClick)=\"onOverlayBackdropClick()\"\n (detach)=\"closeOverlay()\">\n <div \n class=\"attachment-menu-overlay\" \n [style.width.px]=\"columnWidth() || config().columnWidth || 300\" \n [style.min-width.px]=\"300\"\n (click)=\"onOverlayClick($event)\">\n <div class=\"attachment-container\" (click)=\"$event.stopPropagation()\">\n @if (isEditable()) {\n <!-- Header Section -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Add or Drag files</span>\n <button\n mat-flat-button\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n class=\"attachment-upload-btn\"\n [disabled]=\"config().disabled\">\n Upload\n </button>\n </div>\n\n <!-- Drop zone -->\n <div\n class=\"attachment-drop-zone\"\n (drop)=\"onFileDrop($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (paste)=\"onPaste($event)\"\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n [class.dragging]=\"isDragging()\">\n <div class=\"attachment-drop-content\">\n <div class=\"attachment-drop-icon\">+</div>\n <div class=\"attachment-drop-text\">\n <div class=\"primary-text\">Click to upload or drag files here</div>\n <div class=\"secondary-text\">Support multiple files</div>\n </div>\n </div>\n </div>\n\n <!-- Hidden file input -->\n <input\n #fileInput\n type=\"file\"\n multiple\n (change)=\"onFileInputChange($event)\"\n style=\"display: none;\">\n } @else {\n <!-- Read-only header -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Attachments</span>\n </div>\n }\n\n <!-- File list -->\n @if (hasFiles()) {\n <div class=\"attachment-file-list\">\n @for (file of currentValue(); track $index) {\n <div class=\"attachment-file-item\">\n <div class=\"file-info\">\n <mat-icon class=\"file-status-icon done-icon\">check_circle</mat-icon>\n <span class=\"file-name\">{{ getFileName(file) }}</span>\n </div>\n <div class=\"file-actions\">\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"viewFile(file, $event)\"\n title=\"View\"\n type=\"button\">\n <mat-icon>visibility</mat-icon>\n </button>\n @if (isEditable()) {\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"deleteFile(file, $event)\"\n title=\"Delete\"\n type=\"button\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </ng-template>\n</div>\n\n", styles: [".attachment-overlay-container{width:100%;height:100%}.attachment-cell-wrapper{display:flex;align-items:center;justify-content:center;padding:4px;min-height:32px;cursor:default}.attachment-cell-wrapper.attachment-display-editable{cursor:pointer}.attachment-cell-wrapper.attachment-display-editable:hover{background-color:#0000000a}.attachment-cell-wrapper.attachment-display-viewable{cursor:pointer}.attachment-cell-wrapper.attachment-display-viewable:hover{background-color:#0000000a}.cell-attachment-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.drillable-value{display:flex;align-items:center;justify-content:center;cursor:pointer}.drillable-value svg{text-decoration:none}.drillable-value:hover{opacity:.8}.attachment-backdrop{background:transparent}.attachment-menu-overlay{background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;overflow:hidden;position:relative;z-index:1000}.attachment-container{padding:16px}.attachment-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.attachment-title{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1c1b1f)}.attachment-upload-btn{font-size:var(--grid-font-size-body, 12px)!important;min-width:50px}.attachment-drop-zone{border:2px dashed var(--grid-outline-variant, #cac4d0);border-radius:8px;padding:40px 20px;text-align:center;cursor:pointer;transition:all .3s ease;background:var(--grid-surface-variant, #e7e0ec);margin:16px 0}.attachment-drop-zone:hover{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7)}.attachment-drop-zone.dragging{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7);transform:scale(1.01)}.attachment-drop-content{display:flex;flex-direction:column;align-items:center;gap:12px}.attachment-drop-icon{font-size:48px;color:var(--grid-primary, #6750a4);font-weight:300;line-height:1}.attachment-drop-text .primary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:14px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;margin:0}.attachment-drop-text .secondary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-top:4px;display:block}.attachment-file-list{display:block;margin-top:16px;max-height:300px;overflow-y:auto}.attachment-file-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;margin-bottom:8px;background:var(--grid-surface-variant, #e7e0ec);transition:background-color .2s}.attachment-file-item:hover{background:var(--grid-surface-container, #f3edf7)}.file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.file-status-icon{font-size:18px!important;width:18px!important;height:18px!important;flex-shrink:0}.file-status-icon.done-icon{color:#22c55e}.file-name{font-size:var(--grid-font-size-body, 12px);color:var(--grid-on-surface, #1c1b1f);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-actions{display:flex;gap:8px;flex-shrink:0}.file-action-btn{width:28px!important;height:28px!important;padding:0!important;display:flex!important;align-items:center;justify-content:center}.file-action-btn .mat-icon{font-size:18px;width:18px;height:18px;color:var(--grid-on-surface-variant, #49454f)}.file-action-btn:hover .mat-icon{color:var(--grid-on-surface, #1c1b1f)}\n"] }]
9363
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"attachment-overlay-container\">\n <div \n class=\"attachment-cell-wrapper\"\n cdkOverlayOrigin \n #attachmentTrigger=\"cdkOverlayOrigin\"\n [class.attachment-display-editable]=\"isEditable() && !isActive()\"\n [class.attachment-display-viewable]=\"!isEditable() && hasFiles()\"\n (dblclick)=\"onActivate()\">\n @if(hasFiles()) {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M19 12.5C19 14.985 15.866 17 12 17C8.134 17 5 14.985 5 12.5C5 10.015 8.134 8 12 8C15.866 8 19 10.015 19 12.5Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M13.75 12.5001C13.7716 13.1394 13.4429 13.7397 12.8925 14.0657C12.3422 14.3918 11.6578 14.3918 11.1075 14.0657C10.5571 13.7397 10.2284 13.1394 10.25 12.5001C10.2284 11.8608 10.5571 11.2606 11.1075 10.9345C11.6578 10.6084 12.3422 10.6084 12.8925 10.9345C13.4429 11.2606 13.7716 11.8608 13.75 12.5001V12.5001Z\"\n stroke=\"#7C818C\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n } @else {\n <div class=\"cell-attachment-container\">\n @if (isDrillable() && !isActive()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M14.67 11.053L10.68 15.315C10.3416 15.6932 9.85986 15.9119 9.35236 15.9178C8.84487 15.9237 8.35821 15.7162 8.01104 15.346C7.24412 14.5454 7.257 13.2788 8.04004 12.494L13.399 6.763C13.9902 6.10491 14.8315 5.72677 15.7161 5.72163C16.6006 5.71649 17.4463 6.08482 18.045 6.736C19.3222 8.14736 19.3131 10.2995 18.024 11.7L12.342 17.771C11.5334 18.5827 10.4265 19.0261 9.28113 18.9971C8.13575 18.9682 7.05268 18.4695 6.28604 17.618C4.5337 15.6414 4.57705 12.6549 6.38604 10.73L11.753 5\"\n stroke=\"#363B44\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n }\n </div>\n }\n </div>\n\n <ng-template \n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"attachmentTrigger\"\n [cdkConnectedOverlayOpen]=\"isOverlayOpen() && (isActive() || !isEditable())\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayBackdropClass]=\"'attachment-backdrop'\"\n (backdropClick)=\"onOverlayBackdropClick()\"\n (detach)=\"closeOverlay()\">\n <div \n class=\"attachment-menu-overlay\" \n [style.width.px]=\"columnWidth() || config().columnWidth || 300\" \n [style.min-width.px]=\"300\"\n (click)=\"onOverlayClick($event)\">\n <div class=\"attachment-container\" (click)=\"$event.stopPropagation()\">\n @if (rejectionMessage()) {\n <div class=\"attachment-rejection\" role=\"alert\">{{ rejectionMessage() }}</div>\n }\n @if (isEditable()) {\n <!-- Header Section -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Add or Drag files</span>\n <button\n mat-flat-button\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n class=\"attachment-upload-btn\"\n [disabled]=\"config().disabled\">\n Upload\n </button>\n </div>\n\n <!-- Drop zone -->\n <div\n class=\"attachment-drop-zone\"\n (drop)=\"onFileDrop($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (paste)=\"onPaste($event)\"\n (click)=\"onUploadClick(); $event.stopPropagation()\"\n [class.dragging]=\"isDragging()\">\n <div class=\"attachment-drop-content\">\n <div class=\"attachment-drop-icon\">+</div>\n <div class=\"attachment-drop-text\">\n <div class=\"primary-text\">Click to upload or drag files here</div>\n <div class=\"secondary-text\">Support multiple files</div>\n </div>\n </div>\n </div>\n\n <!-- Hidden file input -->\n <input\n #fileInput\n type=\"file\"\n multiple\n (change)=\"onFileInputChange($event)\"\n style=\"display: none;\">\n } @else {\n <!-- Read-only header -->\n <div class=\"attachment-header\">\n <span class=\"attachment-title\">Attachments</span>\n </div>\n }\n\n <!-- File list -->\n @if (hasFiles()) {\n <div class=\"attachment-file-list\">\n @for (file of currentValue(); track $index) {\n <div class=\"attachment-file-item\">\n <div class=\"file-info\">\n <mat-icon class=\"file-status-icon done-icon\">check_circle</mat-icon>\n <span class=\"file-name\">{{ getFileName(file) }}</span>\n </div>\n <div class=\"file-actions\">\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"viewFile(file, $event)\"\n title=\"View\"\n type=\"button\">\n <mat-icon>visibility</mat-icon>\n </button>\n @if (isEditable()) {\n <button\n mat-icon-button\n class=\"file-action-btn\"\n (click)=\"deleteFile(file, $event)\"\n title=\"Delete\"\n type=\"button\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </ng-template>\n</div>\n\n", styles: [".attachment-rejection{padding:8px 10px;margin-bottom:8px;border-radius:6px;background:#fef2f2;color:#b91c1c;font-size:12px;line-height:1.4}.attachment-overlay-container{width:100%;height:100%}.attachment-cell-wrapper{display:flex;align-items:center;justify-content:center;padding:4px;min-height:32px;cursor:default}.attachment-cell-wrapper.attachment-display-editable{cursor:pointer}.attachment-cell-wrapper.attachment-display-editable:hover{background-color:#0000000a}.attachment-cell-wrapper.attachment-display-viewable{cursor:pointer}.attachment-cell-wrapper.attachment-display-viewable:hover{background-color:#0000000a}.cell-attachment-container{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.drillable-value{display:flex;align-items:center;justify-content:center;cursor:pointer}.drillable-value svg{text-decoration:none}.drillable-value:hover{opacity:.8}.attachment-backdrop{background:transparent}.attachment-menu-overlay{background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;overflow:hidden;position:relative;z-index:1000}.attachment-container{padding:16px}.attachment-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.attachment-title{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1c1b1f)}.attachment-upload-btn{font-size:var(--grid-font-size-body, 12px)!important;min-width:50px}.attachment-drop-zone{border:2px dashed var(--grid-outline-variant, #cac4d0);border-radius:8px;padding:40px 20px;text-align:center;cursor:pointer;transition:all .3s ease;background:var(--grid-surface-variant, #e7e0ec);margin:16px 0}.attachment-drop-zone:hover{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7)}.attachment-drop-zone.dragging{border-color:var(--grid-primary, #6750a4);background:var(--grid-surface-container, #f3edf7);transform:scale(1.01)}.attachment-drop-content{display:flex;flex-direction:column;align-items:center;gap:12px}.attachment-drop-icon{font-size:48px;color:var(--grid-primary, #6750a4);font-weight:300;line-height:1}.attachment-drop-text .primary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:14px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;margin:0}.attachment-drop-text .secondary-text{font-family:var(--grid-font-family, \"Poppins\");font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-top:4px;display:block}.attachment-file-list{display:block;margin-top:16px;max-height:300px;overflow-y:auto}.attachment-file-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;margin-bottom:8px;background:var(--grid-surface-variant, #e7e0ec);transition:background-color .2s}.attachment-file-item:hover{background:var(--grid-surface-container, #f3edf7)}.file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.file-status-icon{font-size:18px!important;width:18px!important;height:18px!important;flex-shrink:0}.file-status-icon.done-icon{color:#22c55e}.file-name{font-size:var(--grid-font-size-body, 12px);color:var(--grid-on-surface, #1c1b1f);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-actions{display:flex;gap:8px;flex-shrink:0}.file-action-btn{width:28px!important;height:28px!important;padding:0!important;display:flex!important;align-items:center;justify-content:center}.file-action-btn .mat-icon{font-size:18px;width:18px;height:18px;color:var(--grid-on-surface-variant, #49454f)}.file-action-btn:hover .mat-icon{color:var(--grid-on-surface, #1c1b1f)}\n"] }]
8651
9364
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], valueChange: [{
8652
9365
  type: Output
8653
9366
  }], blur: [{
@@ -8698,6 +9411,8 @@ class DataCellComponent {
8698
9411
  mode = input('', ...(ngDevMode ? [{ debugName: "mode" }] : []));
8699
9412
  isEditable = input(false, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
8700
9413
  row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : []));
9414
+ /** Person-card content, forwarded to the people cell. See EruGridComponent. */
9415
+ personCardTemplate = input(null, ...(ngDevMode ? [{ debugName: "personCardTemplate" }] : []));
8701
9416
  notifyStoreOfValueChange(oldValue, newValue) {
8702
9417
  if (oldValue === newValue)
8703
9418
  return;
@@ -8710,6 +9425,11 @@ class DataCellComponent {
8710
9425
  });
8711
9426
  }
8712
9427
  constructor() {
9428
+ // Server-side validation verdicts land in the shared dynamic-data cache.
9429
+ effect(() => {
9430
+ this.eruGridStore().dynamicData();
9431
+ this.consumeServerValidationResponse();
9432
+ });
8713
9433
  effect(() => {
8714
9434
  const columnWidth = this.currentColumnWidth();
8715
9435
  this.formattedMultiSelectValue(columnWidth);
@@ -8777,9 +9497,14 @@ class DataCellComponent {
8777
9497
  this.validateField(value, false);
8778
9498
  }
8779
9499
  }
9500
+ // Blur runs the same validate-and-round path as the outside-click route in
9501
+ // validateAndDeactivate(), so a num_val bound is enforced however the user
9502
+ // leaves the cell. Deactivation is left to validateField(), which keeps an
9503
+ // invalid cell active so the value can be corrected — matching the textbox,
9504
+ // email and location cells.
8780
9505
  onCurrencyBlur(value) {
8781
9506
  this.currentValue.set(value);
8782
- this.eruGridStore().setActiveCell(null);
9507
+ this.onNumberBlur();
8783
9508
  }
8784
9509
  onCurrencyEditModeChange(isActive) {
8785
9510
  if (isActive && this.mode() === 'table') {
@@ -8792,7 +9517,7 @@ class DataCellComponent {
8792
9517
  // Number component configuration and handlers
8793
9518
  onNumberBlurHandler(value) {
8794
9519
  this.currentValue.set(value);
8795
- this.eruGridStore().setActiveCell(null);
9520
+ this.onNumberBlur();
8796
9521
  }
8797
9522
  onNumberEditModeChange(isActive) {
8798
9523
  if (isActive && this.mode() === 'table') {
@@ -9213,6 +9938,81 @@ class DataCellComponent {
9213
9938
  }, ...(ngDevMode ? [{ debugName: "columnCellConfiguration" }] : []));
9214
9939
  renderer = inject(Renderer2);
9215
9940
  datePipe = inject(DatePipe);
9941
+ // ===== SERVER-SIDE VALIDATION =====
9942
+ // Key the in-flight check is cached under, and the value it was issued for.
9943
+ ssvKey = null;
9944
+ ssvRequestedValue = undefined;
9945
+ /**
9946
+ * Ask the consumer to validate this cell's value, when the column sets
9947
+ * `system_validate`. Runs on focus-out, alongside the local validators.
9948
+ *
9949
+ * The grid's dynamic-data channel is a CACHE keyed by string, not a per-request
9950
+ * queue — so the key carries the row id and the submitted value. Keying on the
9951
+ * column alone would serve the first cell's verdict to every other cell in the
9952
+ * column, the same hazard the dependent-dropdown cache hit.
9953
+ */
9954
+ requestServerSideValidation() {
9955
+ const cfg = this.columnCellConfiguration();
9956
+ const store = this.eruGridStore();
9957
+ if (!cfg || !store)
9958
+ return;
9959
+ const flag = cfg.system_validate;
9960
+ const enabled = flag === true || String(flag ?? '').toLowerCase() === 'true';
9961
+ if (!enabled)
9962
+ return;
9963
+ const value = this.currentValue();
9964
+ if (value === null || value === undefined || value === '')
9965
+ return;
9966
+ if (this.ssvRequestedValue !== undefined && this.ssvRequestedValue === value)
9967
+ return;
9968
+ const rowId = this.row()?.entity_id || String(this.id());
9969
+ const key = `ds_server_validation:${rowId}:${this.columnName()}:${String(value)}`;
9970
+ this.ssvRequestedValue = value;
9971
+ this.ssvKey = key;
9972
+ if (store.hasDynamicDataRequest?.(key))
9973
+ return;
9974
+ store.setDynamicDataRequest({
9975
+ key,
9976
+ optionType: 'API',
9977
+ apiName: cfg.ssv_api || '',
9978
+ fieldName: this.columnName(),
9979
+ search: String(value),
9980
+ });
9981
+ }
9982
+ /**
9983
+ * Consume a verdict. Discards one for a value the user has since changed, so a
9984
+ * slow check cannot overwrite a newer entry, and clears the cache entry so the
9985
+ * same value can be re-checked later.
9986
+ */
9987
+ consumeServerValidationResponse() {
9988
+ const key = this.ssvKey;
9989
+ const store = this.eruGridStore();
9990
+ if (!key || !store)
9991
+ return;
9992
+ const data = store.getDynamicData(key);
9993
+ if (data === undefined)
9994
+ return;
9995
+ const stale = this.ssvRequestedValue !== this.currentValue();
9996
+ this.ssvKey = null;
9997
+ store.removeDynamicDataRequest?.(key);
9998
+ if (stale)
9999
+ return;
10000
+ const status = String(data?.status ?? '').toLowerCase();
10001
+ const msg = String(data?.msg ?? '');
10002
+ if (status === 'error') {
10003
+ // Reuse the cell's existing error channel so the failure shows the same way
10004
+ // a local validation failure does.
10005
+ this.error.set(msg || 'Validation failed');
10006
+ }
10007
+ if (msg.length > 0) {
10008
+ store.setServerValidationNotice({
10009
+ rowId: this.row()?.entity_id || String(this.id()),
10010
+ fieldName: this.columnName(),
10011
+ status: status === 'error' ? 'error' : 'success',
10012
+ msg,
10013
+ });
10014
+ }
10015
+ }
9216
10016
  validateField(value, shouldDeactivate = true) {
9217
10017
  const config = this.columnCellConfiguration();
9218
10018
  if (!config)
@@ -9226,6 +10026,11 @@ class DataCellComponent {
9226
10026
  this.eruGridStore().setActiveCell(null);
9227
10027
  }
9228
10028
  }
10029
+ // Local rules first — no point asking the server about a value that already
10030
+ // fails a client-side check.
10031
+ if (!this.error()) {
10032
+ this.requestServerSideValidation();
10033
+ }
9229
10034
  }
9230
10035
  onEmailBlur() {
9231
10036
  this.validateField(this.currentValue());
@@ -9235,9 +10040,13 @@ class DataCellComponent {
9235
10040
  }
9236
10041
  onNumberBlur() {
9237
10042
  const value = this.currentValue();
9238
- const numValue = Number(value);
10043
+ // An empty cell stays empty. Number('') and Number(null) are both 0, so
10044
+ // rounding unconditionally would write 0 into every blank numeric cell the
10045
+ // user tabs through. Validation still runs, so a mandatory blank is caught.
10046
+ const isBlank = value === null || value === undefined || value === '';
10047
+ const numValue = isBlank ? NaN : Number(value);
9239
10048
  this.validateField(numValue);
9240
- if (!this.error()) {
10049
+ if (!isBlank && !isNaN(numValue) && !this.error()) {
9241
10050
  const config = this.columnCellConfiguration();
9242
10051
  const decimalPlaces = config?.decimal || 0;
9243
10052
  this.currentValue.set(numValue.toFixed(decimalPlaces));
@@ -9258,34 +10067,6 @@ class DataCellComponent {
9258
10067
  setField(event) {
9259
10068
  this.currentValue.set(event);
9260
10069
  }
9261
- formatNumberSignal = computed(() => {
9262
- const config = this.columnCellConfiguration();
9263
- const value = this.currentValue();
9264
- if (config?.datatype !== 'number' && config?.datatype !== 'currency') {
9265
- return value?.toString() || '';
9266
- }
9267
- if (typeof value === 'string') {
9268
- return value;
9269
- }
9270
- if (value == 0 && this.replaceZeroValue !== undefined) {
9271
- return this.replaceZeroValue;
9272
- }
9273
- const numConfig = config;
9274
- const separator = numConfig.separator;
9275
- const decimalPlaces = numConfig.decimal;
9276
- if (!value)
9277
- return '';
9278
- if (separator === 'none')
9279
- return value.toString();
9280
- let locale = 'en-US';
9281
- if (separator === 'thousands') {
9282
- locale = 'en-IN';
9283
- }
9284
- return `${numConfig?.datatype === 'currency' ? numConfig.symbol + ' ' || '' : ''}${Number(value).toLocaleString(locale, {
9285
- minimumFractionDigits: decimalPlaces,
9286
- maximumFractionDigits: decimalPlaces
9287
- })}`;
9288
- }, ...(ngDevMode ? [{ debugName: "formatNumberSignal" }] : []));
9289
10070
  formatNumber(value, separator, decimalPlaces) {
9290
10071
  if (!value)
9291
10072
  return '';
@@ -9982,7 +10763,7 @@ class DataCellComponent {
9982
10763
  return this.getColumnValue()?.replace('default|', '') || '';
9983
10764
  }
9984
10765
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: DataCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9985
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: DataCellComponent, isStandalone: true, selector: "data-cell", inputs: { eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: true, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: true, transformFunction: null }, columnDatatype: { classPropertyName: "columnDatatype", publicName: "columnDatatype", isSignal: true, isRequired: true, transformFunction: null }, columnName: { classPropertyName: "columnName", publicName: "columnName", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, frozenGrandTotalCell: { classPropertyName: "frozenGrandTotalCell", publicName: "frozenGrandTotalCell", isSignal: true, isRequired: false, transformFunction: null }, td: { classPropertyName: "td", publicName: "td", isSignal: true, isRequired: false, transformFunction: null }, drillable: { classPropertyName: "drillable", publicName: "drillable", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { td: "tdChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "data-cell-component" }, providers: [MatDatepickerModule,
10766
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: DataCellComponent, isStandalone: true, selector: "data-cell", inputs: { eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: true, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: true, transformFunction: null }, columnDatatype: { classPropertyName: "columnDatatype", publicName: "columnDatatype", isSignal: true, isRequired: true, transformFunction: null }, columnName: { classPropertyName: "columnName", publicName: "columnName", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, frozenGrandTotalCell: { classPropertyName: "frozenGrandTotalCell", publicName: "frozenGrandTotalCell", isSignal: true, isRequired: false, transformFunction: null }, td: { classPropertyName: "td", publicName: "td", isSignal: true, isRequired: false, transformFunction: null }, drillable: { classPropertyName: "drillable", publicName: "drillable", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, personCardTemplate: { classPropertyName: "personCardTemplate", publicName: "personCardTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { td: "tdChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "data-cell-component" }, providers: [MatDatepickerModule,
9986
10767
  MatNativeDateModule,
9987
10768
  DatePipe,
9988
10769
  {
@@ -9990,7 +10771,7 @@ class DataCellComponent {
9990
10771
  useValue: cellValidators
9991
10772
  },
9992
10773
  ...MATERIAL_PROVIDERS
9993
- ], viewQueries: [{ propertyName: "attachmentTrigger", first: true, predicate: ["attachmentTrigger"], descendants: true }, { propertyName: "singleSelectTrigger", first: true, predicate: ["singleSelectTrigger"], descendants: true }, { propertyName: "multiSelectTrigger", first: true, predicate: ["multiSelectTrigger"], descendants: true }, { propertyName: "peopleTrigger", first: true, predicate: ["peopleTrigger"], descendants: true }], ngImport: i0, template: "<div class=\"container\">\n @switch (columnDatatype()) {\n @case ('textbox') {\n <eru-textbox [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextboxBlur($event)\"\n (editModeChange)=\"onTextboxEditModeChange($event)\" (drilldownClick)=\"onTextboxDrilldown($event)\">\n </eru-textbox>\n }\n @case ('currency') {\n <eru-currency [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCurrencyBlur($event)\"\n (editModeChange)=\"onCurrencyEditModeChange($event)\" (drilldownClick)=\"onCurrencyDrilldown($event)\">\n </eru-currency>\n }\n @case ('number') {\n <eru-number [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onNumberBlurHandler($event)\"\n (editModeChange)=\"onNumberEditModeChange($event)\" (drilldownClick)=\"onNumberDrilldown($event)\">\n </eru-number>\n }\n @case ('location') {\n <eru-location [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onLocationBlurHandler($event)\"\n (editModeChange)=\"onLocationEditModeChange($event)\" (drilldownClick)=\"onLocationDrilldown($event)\">\n </eru-location>\n }\n @case ('email') {\n <eru-email [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onEmailBlurHandler($event)\"\n (editModeChange)=\"onEmailEditModeChange($event)\" (drilldownClick)=\"onEmailDrilldown($event)\">\n </eru-email>\n }\n @case ('textarea') {\n <eru-textarea [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextareaBlur($event)\"\n (editModeChange)=\"onTextareaEditModeChange($event)\" (drilldownClick)=\"onTextareaDrilldown($event)\">\n </eru-textarea>\n }\n @case ('website') {\n <eru-website [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onWebsiteBlur($event)\"\n (editModeChange)=\"onWebsiteEditModeChange($event)\" (drilldownClick)=\"onWebsiteDrilldown($event)\">\n </eru-website>\n }\n <!-- @case ('dropdown_multi_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" #multiSelectTrigger\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}</span>\n } @else {\n {{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}\n }\n </div>\n } -->\n\n <!-- @case ('dropdown_single_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" cdkOverlayOrigin #singleSelectTrigger=\"cdkOverlayOrigin\"\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{currentValue()}}</span>\n } @else {\n {{currentValue()}}\n }\n </div>\n } -->\n\n @case ('checkbox') {\n <eru-checkbox [value]=\"currentValue()\" [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCheckboxBlur($event)\"\n (editModeChange)=\"onCheckboxEditModeChange($event)\" (drilldownClick)=\"onCheckboxDrilldown($event)\">\n </eru-checkbox>\n }\n\n @case ('people') {\n <eru-people [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPeopleBlur($event)\" (editModeChange)=\"onPeopleEditModeChange($event)\"\n (drilldownClick)=\"onPeopleDrilldown($event)\">\n </eru-people>\n }\n\n\n @case ('date') {\n <eru-date [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onDateEditModeChange($event)\"\n (drilldownClick)=\"onDateDrilldown($event)\">\n </eru-date>\n }\n\n @case ('datetime') {\n <eru-datetime [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (editModeChange)=\"onDatetimeEditModeChange($event)\" (drilldownClick)=\"onDatetimeDrilldown($event)\">\n </eru-datetime>\n }\n\n @case ('duration') {\n <eru-duration [value]=\"currentValue()\" [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onDurationBlur($event)\"\n (editModeChange)=\"onDurationEditModeChange($event)\" (drilldownClick)=\"onDurationDrilldown($event)\">\n </eru-duration>\n }\n\n\n @case ('priority') {\n <eru-priority [value]=\"currentValue()\" [config]=\"getPriorityConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPriorityBlur($event)\" (editModeChange)=\"onPriorityEditModeChange($event)\"\n (drilldownClick)=\"onPriorityDrilldown($event)\">\n </eru-priority>\n }\n @case ('progress') {\n <eru-progress [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n (valueChange)=\"onValueChange($event)\" (blur)=\"onProgressBlur($event)\" (focus)=\"onProgressFocus()\"\n (editModeChange)=\"onProgressEditModeChange($event)\" (drilldownClick)=\"onProgressDrilldown($event)\">\n </eru-progress>\n }\n\n @case ('rating') {\n <eru-rating [value]=\"currentValue()\" [config]=\"getRatingConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onRatingEditModeChange($event)\">\n </eru-rating>\n }\n\n @case ('status') {\n <eru-status [value]=\"currentValue()\" [config]=\"getStatusConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\"\n [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onStatusBlur($event)\"\n (editModeChange)=\"onStatusEditModeChange($event)\" (drilldownClick)=\"onStatusDrilldown($event)\">\n </eru-status>\n }\n\n @case ('tag') {\n <eru-tag [value]=\"currentValue()\" [config]=\"getTagConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTagBlur($event)\"\n (editModeChange)=\"onTagEditModeChange($event)\" (drilldownClick)=\"onTagDrilldown($event)\">\n </eru-tag>\n }\n\n @case ('phone') {\n <eru-phone [value]=\"currentValue()\" [defaultCountry]=\"columnCellConfiguration()?.default_country || 'US'\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPhoneBlur($event)\" (editModeChange)=\"onPhoneEditModeChange($event)\"\n (drilldownClick)=\"onPhoneDrilldown($event)\">\n </eru-phone>\n }\n\n @case ('dropdown_single_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"false\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n @case ('dropdown_multi_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"true\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n\n @case ('attachment') {\n <eru-attachment [value]=\"currentValue()\" [config]=\"getAttachmentConfig()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onAttachmentBlur($event)\" (editModeChange)=\"onAttachmentEditModeChange($event)\"\n (drilldownClick)=\"onAttachmentDrilldown($event)\">\n </eru-attachment>\n }\n @default {\n <div class=\"cell-default-display\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{value()}}</span>\n } @else {\n {{value()}}\n }\n </div>\n }\n }\n\n</div>\n\n\n<!-- <ng-template cdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"singleSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_single_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_single_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" (click)=\"$event.stopPropagation()\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\">\n </mat-form-field>\n <ul cdkListbox [ngModel]=\"currentValue()\" (ngModelChange)=\"selectedSingleSelect($event)\"\n aria-labelledby=\"listbox-label\" class=\"listbox\">\n <li [cdkOption]=\"'None'\" class=\"listbox-option notext-overflow\">\n None\n </li>\n @for (option of filteredOptions(); track option.value || option.name) {\n <li [cdkOption]=\"option.value || option.name\" class=\"listbox-option notext-overflow\">\n {{option.label || option.name}}\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->\n\n<!-- <ng-template \ncdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"multiSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_multi_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_multi_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\" (click)=\"$event.stopPropagation()\">\n </mat-form-field>\n \n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox \n [checked]=\"isAllSelected()\" \n [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n \n <ul cdkListboxMultiple=\"true\" cdkListboxUseActiveDescendant cdkListbox [ngModel]=\"currentValue()\"\n (ngModelChange)=\"selectedMultiSelect($event)\" aria-labelledby=\"listbox-labssel\" class=\"listbox\" (click)=\"$event.stopPropagation()\">\n <li [cdkOption]=\"'None'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('None')\" (click)=\"$event.stopPropagation();appendMultiSelect('None')\"></mat-checkbox>\n <span class=\"option-text\">None</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 1'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 1')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 1')\"></mat-checkbox>\n <span class=\"option-text\">option 1</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 2'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 2')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 2')\"></mat-checkbox>\n <span class=\"option-text\">option 2</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 3'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 3')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 3')\"></mat-checkbox>\n <span class=\"option-text\">option 3</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 4'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 4')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 4')\"></mat-checkbox>\n <span class=\"option-text\">option 4</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 5'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 5')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 5')\"></mat-checkbox>\n <span class=\"option-text\">option 5</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 6'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 6')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 6')\"></mat-checkbox>\n <span class=\"option-text\">option 6</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 7'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 7')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 7')\"></mat-checkbox>\n <span class=\"option-text\">option 7</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 8'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 8')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 8')\"></mat-checkbox>\n <span class=\"option-text\">option 8</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 9'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 9')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 9')\"></mat-checkbox>\n <span class=\"option-text\">option 9</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 10'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 10')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 10')\"></mat-checkbox>\n <span class=\"option-text\">option 10</span>\n </div>\n </li>\n @for (option of filteredOptions(); track option) {\n <li [cdkOption]=\"option.value\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected(option.value)\" (click)=\"$event.stopPropagation();appendMultiSelect(option.value)\"></mat-checkbox>\n <span class=\"option-text\">{{option.label}}</span>\n </div>\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.container{height:calc(100% - 2px);width:calc(100% - 2px);position:relative!important;overflow:hidden!important;max-width:100%!important;box-sizing:border-box!important}.container .cell-display-text{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.inputRef{height:inherit;width:inherit;border:none}.inputRef:focus{outline:none}.cell-checkbox{text-align:center}.cell-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-outline,.cell-form-field .mat-mdc-form-field-subscript-wrapper,.cell-form-field .mat-mdc-form-field-text-suffix{display:none!important}.cell-form-field .mat-mdc-form-field-wrapper,.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.cell-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.dropdown-menu{width:100%}.cell-display-text-editable{cursor:pointer!important}.cell-display-text{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important}.cell-display-text,.cell-display-text>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.cell-display-text:empty:before{content:\"Click to select\"!important;color:var(--grid-on-surface-variant, #49454f)!important;font-style:italic!important}.cell-display-number{text-align:var(--grid-number-text-align, right)}.aggregation .cell-display-number{text-align:var(--grid-aggregation-text-align, right)}.assignee-avatars{display:flex;align-items:center;padding:4px 8px;min-height:20px}.no-assignees{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}.drillable-value{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.drillable-link{cursor:pointer;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatSliderModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "component", type: CurrencyComponent, selector: "eru-currency", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "replaceZeroValue", "placeholder"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: NumberComponent, selector: "eru-number", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "replaceZeroValue", "placeholder"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: TextboxComponent, selector: "eru-textbox", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: EmailComponent, selector: "eru-email", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "placeholder", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: TextareaComponent, selector: "eru-textarea", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: WebsiteComponent, selector: "eru-website", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: LocationComponent, selector: "eru-location", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: CheckboxComponent, selector: "eru-checkbox", inputs: ["value", "isEditable", "isActive", "isDrillable", "label"], outputs: ["valueChange", "change", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: DateComponent, selector: "eru-date", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "placeholder"], outputs: ["valueChange", "dateChange", "drilldownClick", "editModeChange"] }, { kind: "component", type: DatetimeComponent, selector: "eru-datetime", inputs: ["value", "isEditable", "isActive", "isDrillable", "placeholder", "config"], outputs: ["valueChange", "datetimeChange", "drilldownClick", "editModeChange"] }, { kind: "component", type: DurationComponent, selector: "eru-duration", inputs: ["value", "isEditable", "isActive", "isDrillable", "placeholder"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: PhoneComponent, selector: "eru-phone", inputs: ["value", "defaultCountry", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "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: RatingComponent, selector: "eru-rating", inputs: ["value", "config", "isEditable", "isActive"], outputs: ["valueChange", "editModeChange"] }, { kind: "component", type: SelectComponent, selector: "eru-select", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "multiple", "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: PeopleComponent, selector: "eru-people", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { 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: AttachmentComponent, selector: "eru-attachment", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange", "fileAdded", "fileDeleted", "fileViewed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10774
+ ], viewQueries: [{ propertyName: "attachmentTrigger", first: true, predicate: ["attachmentTrigger"], descendants: true }, { propertyName: "singleSelectTrigger", first: true, predicate: ["singleSelectTrigger"], descendants: true }, { propertyName: "multiSelectTrigger", first: true, predicate: ["multiSelectTrigger"], descendants: true }, { propertyName: "peopleTrigger", first: true, predicate: ["peopleTrigger"], descendants: true }], ngImport: i0, template: "<div class=\"container\">\n @switch (columnDatatype()) {\n @case ('textbox') {\n <eru-textbox [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextboxBlur($event)\"\n (editModeChange)=\"onTextboxEditModeChange($event)\" (drilldownClick)=\"onTextboxDrilldown($event)\">\n </eru-textbox>\n }\n @case ('currency') {\n <eru-currency [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\" [row]=\"row()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCurrencyBlur($event)\"\n (editModeChange)=\"onCurrencyEditModeChange($event)\" (drilldownClick)=\"onCurrencyDrilldown($event)\">\n </eru-currency>\n }\n @case ('number') {\n <eru-number [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onNumberBlurHandler($event)\"\n (editModeChange)=\"onNumberEditModeChange($event)\" (drilldownClick)=\"onNumberDrilldown($event)\">\n </eru-number>\n }\n @case ('location') {\n <eru-location [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onLocationBlurHandler($event)\"\n (editModeChange)=\"onLocationEditModeChange($event)\" (drilldownClick)=\"onLocationDrilldown($event)\">\n </eru-location>\n }\n @case ('email') {\n <eru-email [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onEmailBlurHandler($event)\"\n (editModeChange)=\"onEmailEditModeChange($event)\" (drilldownClick)=\"onEmailDrilldown($event)\">\n </eru-email>\n }\n @case ('textarea') {\n <eru-textarea [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextareaBlur($event)\"\n (editModeChange)=\"onTextareaEditModeChange($event)\" (drilldownClick)=\"onTextareaDrilldown($event)\">\n </eru-textarea>\n }\n @case ('website') {\n <eru-website [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onWebsiteBlur($event)\"\n (editModeChange)=\"onWebsiteEditModeChange($event)\" (drilldownClick)=\"onWebsiteDrilldown($event)\">\n </eru-website>\n }\n <!-- @case ('dropdown_multi_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" #multiSelectTrigger\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}</span>\n } @else {\n {{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}\n }\n </div>\n } -->\n\n <!-- @case ('dropdown_single_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" cdkOverlayOrigin #singleSelectTrigger=\"cdkOverlayOrigin\"\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{currentValue()}}</span>\n } @else {\n {{currentValue()}}\n }\n </div>\n } -->\n\n @case ('checkbox') {\n <eru-checkbox [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCheckboxBlur($event)\"\n (editModeChange)=\"onCheckboxEditModeChange($event)\" (drilldownClick)=\"onCheckboxDrilldown($event)\">\n </eru-checkbox>\n }\n\n @case ('people') {\n <eru-people [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [personCardTemplate]=\"personCardTemplate()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPeopleBlur($event)\" (editModeChange)=\"onPeopleEditModeChange($event)\"\n (drilldownClick)=\"onPeopleDrilldown($event)\">\n </eru-people>\n }\n\n\n @case ('date') {\n <eru-date [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onDateEditModeChange($event)\"\n (drilldownClick)=\"onDateDrilldown($event)\">\n </eru-date>\n }\n\n @case ('datetime') {\n <eru-datetime [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (editModeChange)=\"onDatetimeEditModeChange($event)\" (drilldownClick)=\"onDatetimeDrilldown($event)\">\n </eru-datetime>\n }\n\n @case ('time') {\n <!-- The time-picker component existed but had no datatype and no case here, so\n a time field fell through to the plain-text renderer. -->\n <eru-time-picker [value]=\"(currentValue() ?? '') + ''\"\n [disabled]=\"!isEditable() || mode() !== 'table'\"\n [placeholder]=\"'HH:mm'\"\n (valueChange)=\"onValueChange($event)\">\n </eru-time-picker>\n }\n\n @case ('duration') {\n <eru-duration [value]=\"currentValue()\" [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onDurationBlur($event)\"\n (editModeChange)=\"onDurationEditModeChange($event)\" (drilldownClick)=\"onDurationDrilldown($event)\">\n </eru-duration>\n }\n\n\n @case ('priority') {\n <eru-priority [value]=\"currentValue()\" [config]=\"getPriorityConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPriorityBlur($event)\" (editModeChange)=\"onPriorityEditModeChange($event)\"\n (drilldownClick)=\"onPriorityDrilldown($event)\">\n </eru-priority>\n }\n @case ('progress') {\n <eru-progress [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n (valueChange)=\"onValueChange($event)\" (blur)=\"onProgressBlur($event)\" (focus)=\"onProgressFocus()\"\n (editModeChange)=\"onProgressEditModeChange($event)\" (drilldownClick)=\"onProgressDrilldown($event)\">\n </eru-progress>\n }\n\n @case ('rating') {\n <eru-rating [value]=\"currentValue()\" [config]=\"getRatingConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onRatingEditModeChange($event)\">\n </eru-rating>\n }\n\n @case ('status') {\n <eru-status [value]=\"currentValue()\" [config]=\"getStatusConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\"\n [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onStatusBlur($event)\"\n (editModeChange)=\"onStatusEditModeChange($event)\" (drilldownClick)=\"onStatusDrilldown($event)\">\n </eru-status>\n }\n\n @case ('tag') {\n <eru-tag [value]=\"currentValue()\" [config]=\"getTagConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTagBlur($event)\"\n (editModeChange)=\"onTagEditModeChange($event)\" (drilldownClick)=\"onTagDrilldown($event)\">\n </eru-tag>\n }\n\n @case ('phone') {\n <eru-phone [value]=\"currentValue()\" [defaultCountry]=\"columnCellConfiguration()?.default_country || 'US'\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPhoneBlur($event)\" (editModeChange)=\"onPhoneEditModeChange($event)\"\n (drilldownClick)=\"onPhoneDrilldown($event)\">\n </eru-phone>\n }\n\n @case ('dropdown_single_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [row]=\"row()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"false\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n @case ('dropdown_multi_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [row]=\"row()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"true\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n\n @case ('attachment') {\n <eru-attachment [value]=\"currentValue()\" [config]=\"getAttachmentConfig()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onAttachmentBlur($event)\" (editModeChange)=\"onAttachmentEditModeChange($event)\"\n (drilldownClick)=\"onAttachmentDrilldown($event)\">\n </eru-attachment>\n }\n @default {\n <div class=\"cell-default-display\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{value()}}</span>\n } @else {\n {{value()}}\n }\n </div>\n }\n }\n\n</div>\n\n\n<!-- <ng-template cdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"singleSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_single_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_single_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" (click)=\"$event.stopPropagation()\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\">\n </mat-form-field>\n <ul cdkListbox [ngModel]=\"currentValue()\" (ngModelChange)=\"selectedSingleSelect($event)\"\n aria-labelledby=\"listbox-label\" class=\"listbox\">\n <li [cdkOption]=\"'None'\" class=\"listbox-option notext-overflow\">\n None\n </li>\n @for (option of filteredOptions(); track option.value || option.name) {\n <li [cdkOption]=\"option.value || option.name\" class=\"listbox-option notext-overflow\">\n {{option.label || option.name}}\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->\n\n<!-- <ng-template \ncdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"multiSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_multi_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_multi_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\" (click)=\"$event.stopPropagation()\">\n </mat-form-field>\n \n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox \n [checked]=\"isAllSelected()\" \n [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n \n <ul cdkListboxMultiple=\"true\" cdkListboxUseActiveDescendant cdkListbox [ngModel]=\"currentValue()\"\n (ngModelChange)=\"selectedMultiSelect($event)\" aria-labelledby=\"listbox-labssel\" class=\"listbox\" (click)=\"$event.stopPropagation()\">\n <li [cdkOption]=\"'None'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('None')\" (click)=\"$event.stopPropagation();appendMultiSelect('None')\"></mat-checkbox>\n <span class=\"option-text\">None</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 1'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 1')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 1')\"></mat-checkbox>\n <span class=\"option-text\">option 1</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 2'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 2')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 2')\"></mat-checkbox>\n <span class=\"option-text\">option 2</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 3'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 3')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 3')\"></mat-checkbox>\n <span class=\"option-text\">option 3</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 4'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 4')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 4')\"></mat-checkbox>\n <span class=\"option-text\">option 4</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 5'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 5')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 5')\"></mat-checkbox>\n <span class=\"option-text\">option 5</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 6'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 6')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 6')\"></mat-checkbox>\n <span class=\"option-text\">option 6</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 7'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 7')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 7')\"></mat-checkbox>\n <span class=\"option-text\">option 7</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 8'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 8')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 8')\"></mat-checkbox>\n <span class=\"option-text\">option 8</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 9'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 9')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 9')\"></mat-checkbox>\n <span class=\"option-text\">option 9</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 10'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 10')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 10')\"></mat-checkbox>\n <span class=\"option-text\">option 10</span>\n </div>\n </li>\n @for (option of filteredOptions(); track option) {\n <li [cdkOption]=\"option.value\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected(option.value)\" (click)=\"$event.stopPropagation();appendMultiSelect(option.value)\"></mat-checkbox>\n <span class=\"option-text\">{{option.label}}</span>\n </div>\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.container{height:calc(100% - 2px);width:calc(100% - 2px);position:relative!important;overflow:hidden!important;max-width:100%!important;box-sizing:border-box!important}.container .cell-display-text{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.inputRef{height:inherit;width:inherit;border:none}.inputRef:focus{outline:none}.cell-checkbox{text-align:center}.cell-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-outline,.cell-form-field .mat-mdc-form-field-subscript-wrapper,.cell-form-field .mat-mdc-form-field-text-suffix{display:none!important}.cell-form-field .mat-mdc-form-field-wrapper,.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.cell-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.dropdown-menu{width:100%}.cell-display-text-editable{cursor:pointer!important}.cell-display-text{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important}.cell-display-text,.cell-display-text>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.cell-display-text:empty:before{content:\"Click to select\"!important;color:var(--grid-on-surface-variant, #49454f)!important;font-style:italic!important}.cell-display-number{text-align:var(--grid-number-text-align, right)}.aggregation .cell-display-number{text-align:var(--grid-aggregation-text-align, right)}.assignee-avatars{display:flex;align-items:center;padding:4px 8px;min-height:20px}.no-assignees{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}.drillable-value{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.drillable-link{cursor:pointer;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatSliderModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "component", type: CurrencyComponent, selector: "eru-currency", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "replaceZeroValue", "placeholder", "row"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: NumberComponent, selector: "eru-number", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "replaceZeroValue", "placeholder"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: TextboxComponent, selector: "eru-textbox", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: EmailComponent, selector: "eru-email", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "placeholder", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: TextareaComponent, selector: "eru-textarea", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: WebsiteComponent, selector: "eru-website", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: LocationComponent, selector: "eru-location", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore", "externalError"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "validationError", "editModeChange"] }, { kind: "component", type: CheckboxComponent, selector: "eru-checkbox", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "label"], outputs: ["valueChange", "change", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: DateComponent, selector: "eru-date", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "placeholder"], outputs: ["valueChange", "dateChange", "drilldownClick", "editModeChange"] }, { kind: "component", type: DatetimeComponent, selector: "eru-datetime", inputs: ["value", "isEditable", "isActive", "isDrillable", "placeholder", "config"], outputs: ["valueChange", "datetimeChange", "drilldownClick", "editModeChange"] }, { kind: "component", type: DurationComponent, selector: "eru-duration", inputs: ["value", "isEditable", "isActive", "isDrillable", "placeholder"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: PhoneComponent, selector: "eru-phone", inputs: ["value", "defaultCountry", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "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: RatingComponent, selector: "eru-rating", inputs: ["value", "config", "isEditable", "isActive"], outputs: ["valueChange", "editModeChange"] }, { kind: "component", type: SelectComponent, selector: "eru-select", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "multiple", "eruGridStore", "row"], 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: PeopleComponent, selector: "eru-people", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "eruGridStore", "personCardTemplate"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { 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: AttachmentComponent, selector: "eru-attachment", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange", "fileAdded", "fileDeleted", "fileViewed"] }, { kind: "component", type: TimePickerComponent, selector: "eru-time-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9994
10775
  }
9995
10776
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: DataCellComponent, decorators: [{
9996
10777
  type: Component,
@@ -10026,7 +10807,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
10026
10807
  TagComponent,
10027
10808
  PeopleComponent,
10028
10809
  PriorityComponent,
10029
- AttachmentComponent
10810
+ AttachmentComponent,
10811
+ TimePickerComponent
10030
10812
  ], providers: [MatDatepickerModule,
10031
10813
  MatNativeDateModule,
10032
10814
  DatePipe,
@@ -10038,7 +10820,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
10038
10820
  ], host: {
10039
10821
  '(document:click)': 'onDocumentClick($event)',
10040
10822
  'class': 'data-cell-component'
10041
- }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"container\">\n @switch (columnDatatype()) {\n @case ('textbox') {\n <eru-textbox [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextboxBlur($event)\"\n (editModeChange)=\"onTextboxEditModeChange($event)\" (drilldownClick)=\"onTextboxDrilldown($event)\">\n </eru-textbox>\n }\n @case ('currency') {\n <eru-currency [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCurrencyBlur($event)\"\n (editModeChange)=\"onCurrencyEditModeChange($event)\" (drilldownClick)=\"onCurrencyDrilldown($event)\">\n </eru-currency>\n }\n @case ('number') {\n <eru-number [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onNumberBlurHandler($event)\"\n (editModeChange)=\"onNumberEditModeChange($event)\" (drilldownClick)=\"onNumberDrilldown($event)\">\n </eru-number>\n }\n @case ('location') {\n <eru-location [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onLocationBlurHandler($event)\"\n (editModeChange)=\"onLocationEditModeChange($event)\" (drilldownClick)=\"onLocationDrilldown($event)\">\n </eru-location>\n }\n @case ('email') {\n <eru-email [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onEmailBlurHandler($event)\"\n (editModeChange)=\"onEmailEditModeChange($event)\" (drilldownClick)=\"onEmailDrilldown($event)\">\n </eru-email>\n }\n @case ('textarea') {\n <eru-textarea [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextareaBlur($event)\"\n (editModeChange)=\"onTextareaEditModeChange($event)\" (drilldownClick)=\"onTextareaDrilldown($event)\">\n </eru-textarea>\n }\n @case ('website') {\n <eru-website [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onWebsiteBlur($event)\"\n (editModeChange)=\"onWebsiteEditModeChange($event)\" (drilldownClick)=\"onWebsiteDrilldown($event)\">\n </eru-website>\n }\n <!-- @case ('dropdown_multi_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" #multiSelectTrigger\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}</span>\n } @else {\n {{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}\n }\n </div>\n } -->\n\n <!-- @case ('dropdown_single_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" cdkOverlayOrigin #singleSelectTrigger=\"cdkOverlayOrigin\"\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{currentValue()}}</span>\n } @else {\n {{currentValue()}}\n }\n </div>\n } -->\n\n @case ('checkbox') {\n <eru-checkbox [value]=\"currentValue()\" [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCheckboxBlur($event)\"\n (editModeChange)=\"onCheckboxEditModeChange($event)\" (drilldownClick)=\"onCheckboxDrilldown($event)\">\n </eru-checkbox>\n }\n\n @case ('people') {\n <eru-people [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPeopleBlur($event)\" (editModeChange)=\"onPeopleEditModeChange($event)\"\n (drilldownClick)=\"onPeopleDrilldown($event)\">\n </eru-people>\n }\n\n\n @case ('date') {\n <eru-date [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onDateEditModeChange($event)\"\n (drilldownClick)=\"onDateDrilldown($event)\">\n </eru-date>\n }\n\n @case ('datetime') {\n <eru-datetime [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (editModeChange)=\"onDatetimeEditModeChange($event)\" (drilldownClick)=\"onDatetimeDrilldown($event)\">\n </eru-datetime>\n }\n\n @case ('duration') {\n <eru-duration [value]=\"currentValue()\" [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onDurationBlur($event)\"\n (editModeChange)=\"onDurationEditModeChange($event)\" (drilldownClick)=\"onDurationDrilldown($event)\">\n </eru-duration>\n }\n\n\n @case ('priority') {\n <eru-priority [value]=\"currentValue()\" [config]=\"getPriorityConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPriorityBlur($event)\" (editModeChange)=\"onPriorityEditModeChange($event)\"\n (drilldownClick)=\"onPriorityDrilldown($event)\">\n </eru-priority>\n }\n @case ('progress') {\n <eru-progress [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n (valueChange)=\"onValueChange($event)\" (blur)=\"onProgressBlur($event)\" (focus)=\"onProgressFocus()\"\n (editModeChange)=\"onProgressEditModeChange($event)\" (drilldownClick)=\"onProgressDrilldown($event)\">\n </eru-progress>\n }\n\n @case ('rating') {\n <eru-rating [value]=\"currentValue()\" [config]=\"getRatingConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onRatingEditModeChange($event)\">\n </eru-rating>\n }\n\n @case ('status') {\n <eru-status [value]=\"currentValue()\" [config]=\"getStatusConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\"\n [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onStatusBlur($event)\"\n (editModeChange)=\"onStatusEditModeChange($event)\" (drilldownClick)=\"onStatusDrilldown($event)\">\n </eru-status>\n }\n\n @case ('tag') {\n <eru-tag [value]=\"currentValue()\" [config]=\"getTagConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTagBlur($event)\"\n (editModeChange)=\"onTagEditModeChange($event)\" (drilldownClick)=\"onTagDrilldown($event)\">\n </eru-tag>\n }\n\n @case ('phone') {\n <eru-phone [value]=\"currentValue()\" [defaultCountry]=\"columnCellConfiguration()?.default_country || 'US'\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPhoneBlur($event)\" (editModeChange)=\"onPhoneEditModeChange($event)\"\n (drilldownClick)=\"onPhoneDrilldown($event)\">\n </eru-phone>\n }\n\n @case ('dropdown_single_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"false\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n @case ('dropdown_multi_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"true\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n\n @case ('attachment') {\n <eru-attachment [value]=\"currentValue()\" [config]=\"getAttachmentConfig()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onAttachmentBlur($event)\" (editModeChange)=\"onAttachmentEditModeChange($event)\"\n (drilldownClick)=\"onAttachmentDrilldown($event)\">\n </eru-attachment>\n }\n @default {\n <div class=\"cell-default-display\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{value()}}</span>\n } @else {\n {{value()}}\n }\n </div>\n }\n }\n\n</div>\n\n\n<!-- <ng-template cdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"singleSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_single_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_single_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" (click)=\"$event.stopPropagation()\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\">\n </mat-form-field>\n <ul cdkListbox [ngModel]=\"currentValue()\" (ngModelChange)=\"selectedSingleSelect($event)\"\n aria-labelledby=\"listbox-label\" class=\"listbox\">\n <li [cdkOption]=\"'None'\" class=\"listbox-option notext-overflow\">\n None\n </li>\n @for (option of filteredOptions(); track option.value || option.name) {\n <li [cdkOption]=\"option.value || option.name\" class=\"listbox-option notext-overflow\">\n {{option.label || option.name}}\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->\n\n<!-- <ng-template \ncdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"multiSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_multi_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_multi_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\" (click)=\"$event.stopPropagation()\">\n </mat-form-field>\n \n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox \n [checked]=\"isAllSelected()\" \n [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n \n <ul cdkListboxMultiple=\"true\" cdkListboxUseActiveDescendant cdkListbox [ngModel]=\"currentValue()\"\n (ngModelChange)=\"selectedMultiSelect($event)\" aria-labelledby=\"listbox-labssel\" class=\"listbox\" (click)=\"$event.stopPropagation()\">\n <li [cdkOption]=\"'None'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('None')\" (click)=\"$event.stopPropagation();appendMultiSelect('None')\"></mat-checkbox>\n <span class=\"option-text\">None</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 1'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 1')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 1')\"></mat-checkbox>\n <span class=\"option-text\">option 1</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 2'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 2')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 2')\"></mat-checkbox>\n <span class=\"option-text\">option 2</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 3'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 3')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 3')\"></mat-checkbox>\n <span class=\"option-text\">option 3</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 4'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 4')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 4')\"></mat-checkbox>\n <span class=\"option-text\">option 4</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 5'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 5')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 5')\"></mat-checkbox>\n <span class=\"option-text\">option 5</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 6'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 6')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 6')\"></mat-checkbox>\n <span class=\"option-text\">option 6</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 7'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 7')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 7')\"></mat-checkbox>\n <span class=\"option-text\">option 7</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 8'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 8')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 8')\"></mat-checkbox>\n <span class=\"option-text\">option 8</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 9'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 9')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 9')\"></mat-checkbox>\n <span class=\"option-text\">option 9</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 10'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 10')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 10')\"></mat-checkbox>\n <span class=\"option-text\">option 10</span>\n </div>\n </li>\n @for (option of filteredOptions(); track option) {\n <li [cdkOption]=\"option.value\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected(option.value)\" (click)=\"$event.stopPropagation();appendMultiSelect(option.value)\"></mat-checkbox>\n <span class=\"option-text\">{{option.label}}</span>\n </div>\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.container{height:calc(100% - 2px);width:calc(100% - 2px);position:relative!important;overflow:hidden!important;max-width:100%!important;box-sizing:border-box!important}.container .cell-display-text{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.inputRef{height:inherit;width:inherit;border:none}.inputRef:focus{outline:none}.cell-checkbox{text-align:center}.cell-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-outline,.cell-form-field .mat-mdc-form-field-subscript-wrapper,.cell-form-field .mat-mdc-form-field-text-suffix{display:none!important}.cell-form-field .mat-mdc-form-field-wrapper,.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.cell-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.dropdown-menu{width:100%}.cell-display-text-editable{cursor:pointer!important}.cell-display-text{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important}.cell-display-text,.cell-display-text>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.cell-display-text:empty:before{content:\"Click to select\"!important;color:var(--grid-on-surface-variant, #49454f)!important;font-style:italic!important}.cell-display-number{text-align:var(--grid-number-text-align, right)}.aggregation .cell-display-number{text-align:var(--grid-aggregation-text-align, right)}.assignee-avatars{display:flex;align-items:center;padding:4px 8px;min-height:20px}.no-assignees{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}.drillable-value{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.drillable-link{cursor:pointer;padding:4px}\n"] }]
10823
+ }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"container\">\n @switch (columnDatatype()) {\n @case ('textbox') {\n <eru-textbox [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextboxBlur($event)\"\n (editModeChange)=\"onTextboxEditModeChange($event)\" (drilldownClick)=\"onTextboxDrilldown($event)\">\n </eru-textbox>\n }\n @case ('currency') {\n <eru-currency [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\" [row]=\"row()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCurrencyBlur($event)\"\n (editModeChange)=\"onCurrencyEditModeChange($event)\" (drilldownClick)=\"onCurrencyDrilldown($event)\">\n </eru-currency>\n }\n @case ('number') {\n <eru-number [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [replaceZeroValue]=\"replaceZeroValue\" (valueChange)=\"onValueChange($event)\" (blur)=\"onNumberBlurHandler($event)\"\n (editModeChange)=\"onNumberEditModeChange($event)\" (drilldownClick)=\"onNumberDrilldown($event)\">\n </eru-number>\n }\n @case ('location') {\n <eru-location [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onLocationBlurHandler($event)\"\n (editModeChange)=\"onLocationEditModeChange($event)\" (drilldownClick)=\"onLocationDrilldown($event)\">\n </eru-location>\n }\n @case ('email') {\n <eru-email [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onEmailBlurHandler($event)\"\n (editModeChange)=\"onEmailEditModeChange($event)\" (drilldownClick)=\"onEmailDrilldown($event)\">\n </eru-email>\n }\n @case ('textarea') {\n <eru-textarea [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTextareaBlur($event)\"\n (editModeChange)=\"onTextareaEditModeChange($event)\" (drilldownClick)=\"onTextareaDrilldown($event)\">\n </eru-textarea>\n }\n @case ('website') {\n <eru-website [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n [externalError]=\"error()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onWebsiteBlur($event)\"\n (editModeChange)=\"onWebsiteEditModeChange($event)\" (drilldownClick)=\"onWebsiteDrilldown($event)\">\n </eru-website>\n }\n <!-- @case ('dropdown_multi_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" #multiSelectTrigger\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}</span>\n } @else {\n {{formattedMultiSelectValue(currentColumnWidth()) || 'Click to select'}}\n }\n </div>\n } -->\n\n <!-- @case ('dropdown_single_select') {\n <div class=\"cell-display-text\" (dblclick)=\"toggleOverlayMenu($event)\" cdkOverlayOrigin #singleSelectTrigger=\"cdkOverlayOrigin\"\n [class.cell-display-text-editable]=\"isEditable()\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{currentValue()}}</span>\n } @else {\n {{currentValue()}}\n }\n </div>\n } -->\n\n @case ('checkbox') {\n <eru-checkbox [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onCheckboxBlur($event)\"\n (editModeChange)=\"onCheckboxEditModeChange($event)\" (drilldownClick)=\"onCheckboxDrilldown($event)\">\n </eru-checkbox>\n }\n\n @case ('people') {\n <eru-people [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [personCardTemplate]=\"personCardTemplate()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPeopleBlur($event)\" (editModeChange)=\"onPeopleEditModeChange($event)\"\n (drilldownClick)=\"onPeopleDrilldown($event)\">\n </eru-people>\n }\n\n\n @case ('date') {\n <eru-date [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onDateEditModeChange($event)\"\n (drilldownClick)=\"onDateDrilldown($event)\">\n </eru-date>\n }\n\n @case ('datetime') {\n <eru-datetime [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (editModeChange)=\"onDatetimeEditModeChange($event)\" (drilldownClick)=\"onDatetimeDrilldown($event)\">\n </eru-datetime>\n }\n\n @case ('time') {\n <!-- The time-picker component existed but had no datatype and no case here, so\n a time field fell through to the plain-text renderer. -->\n <eru-time-picker [value]=\"(currentValue() ?? '') + ''\"\n [disabled]=\"!isEditable() || mode() !== 'table'\"\n [placeholder]=\"'HH:mm'\"\n (valueChange)=\"onValueChange($event)\">\n </eru-time-picker>\n }\n\n @case ('duration') {\n <eru-duration [value]=\"currentValue()\" [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\"\n [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onDurationBlur($event)\"\n (editModeChange)=\"onDurationEditModeChange($event)\" (drilldownClick)=\"onDurationDrilldown($event)\">\n </eru-duration>\n }\n\n\n @case ('priority') {\n <eru-priority [value]=\"currentValue()\" [config]=\"getPriorityConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPriorityBlur($event)\" (editModeChange)=\"onPriorityEditModeChange($event)\"\n (drilldownClick)=\"onPriorityDrilldown($event)\">\n </eru-priority>\n }\n @case ('progress') {\n <eru-progress [value]=\"currentValue()\" [config]=\"columnCellConfiguration()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\"\n (valueChange)=\"onValueChange($event)\" (blur)=\"onProgressBlur($event)\" (focus)=\"onProgressFocus()\"\n (editModeChange)=\"onProgressEditModeChange($event)\" (drilldownClick)=\"onProgressDrilldown($event)\">\n </eru-progress>\n }\n\n @case ('rating') {\n <eru-rating [value]=\"currentValue()\" [config]=\"getRatingConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" (valueChange)=\"onValueChange($event)\" (editModeChange)=\"onRatingEditModeChange($event)\">\n </eru-rating>\n }\n\n @case ('status') {\n <eru-status [value]=\"currentValue()\" [config]=\"getStatusConfig()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\"\n [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onStatusBlur($event)\"\n (editModeChange)=\"onStatusEditModeChange($event)\" (drilldownClick)=\"onStatusDrilldown($event)\">\n </eru-status>\n }\n\n @case ('tag') {\n <eru-tag [value]=\"currentValue()\" [config]=\"getTagConfig()\" [eruGridStore]=\"eruGridStore()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [isDrillable]=\"drillable()\" (valueChange)=\"onValueChange($event)\" (blur)=\"onTagBlur($event)\"\n (editModeChange)=\"onTagEditModeChange($event)\" (drilldownClick)=\"onTagDrilldown($event)\">\n </eru-tag>\n }\n\n @case ('phone') {\n <eru-phone [value]=\"currentValue()\" [defaultCountry]=\"columnCellConfiguration()?.default_country || 'US'\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [fieldSize]=\"fieldSize()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onPhoneBlur($event)\" (editModeChange)=\"onPhoneEditModeChange($event)\"\n (drilldownClick)=\"onPhoneDrilldown($event)\">\n </eru-phone>\n }\n\n @case ('dropdown_single_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [row]=\"row()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"false\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n @case ('dropdown_multi_select') {\n <eru-select [value]=\"currentValue()\" [config]=\"getSelectConfig()\" [row]=\"row()\" [isEditable]=\"isEditable() && mode() === 'table'\"\n [isActive]=\"isActive()\" [multiple]=\"true\" [isDrillable]=\"drillable()\" [columnWidth]=\"currentColumnWidth()\"\n [fieldSize]=\"fieldSize()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onSelectBlur($event)\" (editModeChange)=\"onSelectEditModeChange($event)\"\n (drilldownClick)=\"onSelectDrilldown($event)\">\n </eru-select>\n }\n\n @case ('attachment') {\n <eru-attachment [value]=\"currentValue()\" [config]=\"getAttachmentConfig()\"\n [isEditable]=\"isEditable() && mode() === 'table'\" [isActive]=\"isActive()\" [isDrillable]=\"drillable()\"\n [columnWidth]=\"currentColumnWidth()\" [eruGridStore]=\"eruGridStore()\" (valueChange)=\"onValueChange($event)\"\n (blur)=\"onAttachmentBlur($event)\" (editModeChange)=\"onAttachmentEditModeChange($event)\"\n (drilldownClick)=\"onAttachmentDrilldown($event)\">\n </eru-attachment>\n }\n @default {\n <div class=\"cell-default-display\">\n @if (drillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">{{value()}}</span>\n } @else {\n {{value()}}\n }\n </div>\n }\n }\n\n</div>\n\n\n<!-- <ng-template cdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"singleSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_single_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_single_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" (click)=\"$event.stopPropagation()\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\">\n </mat-form-field>\n <ul cdkListbox [ngModel]=\"currentValue()\" (ngModelChange)=\"selectedSingleSelect($event)\"\n aria-labelledby=\"listbox-label\" class=\"listbox\">\n <li [cdkOption]=\"'None'\" class=\"listbox-option notext-overflow\">\n None\n </li>\n @for (option of filteredOptions(); track option.value || option.name) {\n <li [cdkOption]=\"option.value || option.name\" class=\"listbox-option notext-overflow\">\n {{option.label || option.name}}\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->\n\n<!-- <ng-template \ncdkConnectedOverlay\n[cdkConnectedOverlayOrigin]=\"multiSelectTrigger\"\n[cdkConnectedOverlayOpen]=\"showOverlayMenu('dropdown_multi_select')\"\n(detach)=\"isOpen = showOverlayMenu('dropdown_multi_select')\">\n <div class=\"dropdown-menu\" cdkMenu [style.width.px]=\"currentColumnWidth()\" (closed)=\"singleOptionClosed()\">\n <div class=\"listbox-container\">\n <mat-form-field appearance=\"outline\" class=\"search-form-field\">\n <input matInput type=\"search\" placeholder=\"Search...\" [ngModel]=\"optionSearchText()\"\n (ngModelChange)=\"optionSearchText.set($event)\" (click)=\"$event.stopPropagation()\">\n </mat-form-field>\n \n <div class=\"select-all-container\" (click)=\"$event.stopPropagation()\">\n <mat-checkbox \n [checked]=\"isAllSelected()\" \n [indeterminate]=\"isIndeterminate()\"\n (change)=\"toggleSelectAll($event.checked)\">\n <span class=\"select-all-text\">Select All</span>\n </mat-checkbox>\n </div>\n \n <ul cdkListboxMultiple=\"true\" cdkListboxUseActiveDescendant cdkListbox [ngModel]=\"currentValue()\"\n (ngModelChange)=\"selectedMultiSelect($event)\" aria-labelledby=\"listbox-labssel\" class=\"listbox\" (click)=\"$event.stopPropagation()\">\n <li [cdkOption]=\"'None'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('None')\" (click)=\"$event.stopPropagation();appendMultiSelect('None')\"></mat-checkbox>\n <span class=\"option-text\">None</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 1'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 1')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 1')\"></mat-checkbox>\n <span class=\"option-text\">option 1</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 2'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 2')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 2')\"></mat-checkbox>\n <span class=\"option-text\">option 2</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 3'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 3')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 3')\"></mat-checkbox>\n <span class=\"option-text\">option 3</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 4'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 4')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 4')\"></mat-checkbox>\n <span class=\"option-text\">option 4</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 5'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 5')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 5')\"></mat-checkbox>\n <span class=\"option-text\">option 5</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 6'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 6')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 6')\"></mat-checkbox>\n <span class=\"option-text\">option 6</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 7'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 7')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 7')\"></mat-checkbox>\n <span class=\"option-text\">option 7</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 8'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 8')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 8')\"></mat-checkbox>\n <span class=\"option-text\">option 8</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 9'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 9')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 9')\"></mat-checkbox>\n <span class=\"option-text\">option 9</span>\n </div>\n </li>\n <li [cdkOption]=\"'option 10'\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected('option 10')\" (click)=\"$event.stopPropagation();appendMultiSelect('option 10')\"></mat-checkbox>\n <span class=\"option-text\">option 10</span>\n </div>\n </li>\n @for (option of filteredOptions(); track option) {\n <li [cdkOption]=\"option.value\" class=\"multi-listbox-option notext-overflow\">\n <div class=\"option-content\">\n <mat-checkbox [checked]=\"isOptionSelected(option.value)\" (click)=\"$event.stopPropagation();appendMultiSelect(option.value)\"></mat-checkbox>\n <span class=\"option-text\">{{option.label}}</span>\n </div>\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template> -->", styles: [":host{display:block;height:100%;width:100%;position:relative;overflow:hidden!important}.container{height:calc(100% - 2px);width:calc(100% - 2px);position:relative!important;overflow:hidden!important;max-width:100%!important;box-sizing:border-box!important}.container .cell-display-text{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.inputRef{height:inherit;width:inherit;border:none}.inputRef:focus{outline:none}.cell-checkbox{text-align:center}.cell-form-field{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-outline,.cell-form-field .mat-mdc-form-field-subscript-wrapper,.cell-form-field .mat-mdc-form-field-text-suffix{display:none!important}.cell-form-field .mat-mdc-form-field-wrapper,.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex{width:100%!important;height:100%!important;padding:0!important;margin:0!important}.cell-form-field .mat-mdc-form-field-wrapper .mat-mdc-form-field-flex .mat-mdc-form-field-infix{width:100%!important;height:100%!important;padding:0!important;margin:0!important;min-height:auto!important;border-top:none!important}.cell-form-field input[matInput]{width:100%!important;height:100%!important;padding:2px!important;margin:0!important;border:none!important;outline:none!important;background:transparent!important;font-size:14px!important;line-height:normal!important;box-sizing:border-box!important;max-width:none!important;min-width:0!important;flex:none!important}.dropdown-menu{width:100%}.cell-display-text-editable{cursor:pointer!important}.cell-display-text{width:100%!important;height:100%!important;min-height:20px!important;display:block!important;padding:4px 8px!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--grid-font-size-body, 12px)!important;color:var(--grid-on-surface, #1d1b20)!important;background:transparent!important;border:none!important;outline:none!important;text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;box-sizing:border-box!important;transition:background-color .2s ease!important;line-height:1.4!important}.cell-display-text,.cell-display-text>*{text-overflow:ellipsis!important;overflow:hidden!important;white-space:nowrap!important;max-width:100%!important;width:100%!important;display:block!important}.cell-display-text:empty:before{content:\"Click to select\"!important;color:var(--grid-on-surface-variant, #49454f)!important;font-style:italic!important}.cell-display-number{text-align:var(--grid-number-text-align, right)}.aggregation .cell-display-number{text-align:var(--grid-aggregation-text-align, right)}.assignee-avatars{display:flex;align-items:center;padding:4px 8px;min-height:20px}.no-assignees{color:var(--grid-on-surface-variant, #49454f);font-style:italic;font-size:12px}.option-content{display:flex;align-items:center;gap:8px;width:100%}.option-avatar{width:24px;height:24px;border-radius:50%;background-color:var(--grid-primary, #6750a4);color:#fff;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}.option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.checkmark{color:var(--grid-primary, #6750a4);font-weight:700;font-size:14px;flex-shrink:0}.drillable-value{color:var(--grid-primary, #6750a4);text-decoration:underline;text-decoration-color:var(--grid-primary, #6750a4);text-decoration-thickness:1px;text-underline-offset:2px;transition:all .2s ease}.drillable-link{cursor:pointer;padding:4px}\n"] }]
10042
10824
  }], ctorParameters: () => [], propDecorators: { attachmentTrigger: [{
10043
10825
  type: ViewChild,
10044
10826
  args: ['attachmentTrigger']
@@ -10051,7 +10833,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
10051
10833
  }], peopleTrigger: [{
10052
10834
  type: ViewChild,
10053
10835
  args: ['peopleTrigger']
10054
- }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: true }] }], fieldSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSize", required: true }] }], columnDatatype: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnDatatype", required: true }] }], columnName: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnName", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], frozenGrandTotalCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenGrandTotalCell", required: false }] }], td: [{ type: i0.Input, args: [{ isSignal: true, alias: "td", required: false }] }, { type: i0.Output, args: ["tdChange"] }], drillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "drillable", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }] } });
10836
+ }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: true }] }], fieldSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSize", required: true }] }], columnDatatype: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnDatatype", required: true }] }], columnName: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnName", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], frozenGrandTotalCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenGrandTotalCell", required: false }] }], td: [{ type: i0.Input, args: [{ isSignal: true, alias: "td", required: false }] }, { type: i0.Output, args: ["tdChange"] }], drillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "drillable", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], personCardTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "personCardTemplate", required: false }] }] } });
10055
10837
 
10056
10838
  class ResizeColumnDirective {
10057
10839
  renderer;
@@ -10476,49 +11258,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
10476
11258
  args: ['dragend', ['$event']]
10477
11259
  }] } });
10478
11260
 
10479
- // Fields that a preset enforces. When preset !== 'custom', the library ignores
10480
- // any user-provided value for these fields and applies the preset's default.
10481
- // Users who need to tweak any of these must switch preset to 'custom' first.
10482
- const PRESET_MANAGED_FIELDS = [
10483
- 'showColumnLines',
10484
- 'showRowLines',
10485
- 'headerRowHeight',
10486
- 'dataRowHeight',
10487
- ];
10488
- // Per-preset enforced defaults for the preset-managed fields. Keep the
10489
- // surfaces (divider colors, header bg, radius, shadow, density) in SCSS —
10490
- // these are purely config-level values that pair with the SCSS DNA.
10491
- const PRESET_CONFIG_DEFAULTS = {
10492
- default: { showColumnLines: false, showRowLines: true, headerRowHeight: 40, dataRowHeight: 36 },
10493
- modern: { showColumnLines: false, showRowLines: true, headerRowHeight: 52, dataRowHeight: 52 },
10494
- compact: { showColumnLines: false, showRowLines: true, headerRowHeight: 28, dataRowHeight: 24 },
10495
- bold: { showColumnLines: true, showRowLines: true, headerRowHeight: 46, dataRowHeight: 38 },
10496
- financial: { showColumnLines: false, showRowLines: true, headerRowHeight: 40, dataRowHeight: 34 },
10497
- elevated: { showColumnLines: false, showRowLines: true, headerRowHeight: 50, dataRowHeight: 46 },
10498
- };
10499
- const DATA_TYPES = [
10500
- 'number',
10501
- 'textbox',
10502
- 'currency',
10503
- 'date',
10504
- 'datetime',
10505
- 'duration',
10506
- 'dropdown_single_select',
10507
- 'dropdown_multi_select',
10508
- 'location',
10509
- 'email',
10510
- 'people',
10511
- 'checkbox',
10512
- 'phone',
10513
- 'priority',
10514
- 'status',
10515
- 'progress',
10516
- 'attachment',
10517
- 'tag',
10518
- 'rating',
10519
- 'website',
10520
- ];
10521
-
10522
11261
  const WEEK_DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
10523
11262
  const UNIVERSAL_FIELDS = [
10524
11263
  { key: 'label', label: 'Label', control: 'text' },
@@ -10531,10 +11270,41 @@ const UNIVERSAL_FIELDS = [
10531
11270
  { key: 'tool_tip', label: 'Tooltip', control: 'text' },
10532
11271
  { key: 'description', label: 'Description', control: 'text' },
10533
11272
  { key: 'default', label: 'Default value', control: 'text' },
10534
- { key: 'required', label: 'Required', control: 'checkbox' },
11273
+ { key: 'mandatory', label: 'Required', control: 'checkbox' },
10535
11274
  { key: 'editable', label: 'Editable', control: 'checkbox', defaultValue: true },
10536
11275
  { key: 'is_hidden', label: 'Hidden', control: 'checkbox' },
10537
11276
  ];
11277
+ /**
11278
+ * Governance attributes the data model owns. Shown so a designer can see how a
11279
+ * field is classified, always read-only on a mapped column (they are in
11280
+ * INHERITED_FIELD_KEYS). Nothing acts on them yet — masking, encryption and the
11281
+ * uniqueness check are separate work — but a column carries them, so that work
11282
+ * will not need to touch the inheritance layer again.
11283
+ *
11284
+ * `unq_sa` reads "standalone": such a field is checked for uniqueness on its
11285
+ * own value. Fields with `is_unique` and without `unq_sa` are checked together
11286
+ * as a single composite instead.
11287
+ */
11288
+ const GOVERNANCE_FIELDS = [
11289
+ { key: 'is_pii', label: 'PII', control: 'checkbox' },
11290
+ { key: 'is_ephi', label: 'ePHI', control: 'checkbox' },
11291
+ { key: 'to_encrypt', label: 'Encrypted at rest', control: 'checkbox' },
11292
+ { key: 'is_pf', label: 'Priority filter', control: 'checkbox' },
11293
+ { key: 'is_unique', label: 'Unique', control: 'checkbox' },
11294
+ { key: 'unq_sa', label: 'Unique standalone', control: 'checkbox', showWhen: f => !!f.is_unique },
11295
+ { key: 'system_validate', label: 'Server-side validation', control: 'text' },
11296
+ { key: 'ssv_api', label: 'Validation API', control: 'text', showWhen: f => !!f.system_validate },
11297
+ { key: 'can_group', label: 'Groupable', control: 'checkbox' },
11298
+ { key: 'default_group', label: 'Grouped by default', control: 'checkbox', showWhen: f => !!f.can_group },
11299
+ ];
11300
+ /**
11301
+ * Governance attributes that do not apply to a given datatype, so the panel
11302
+ * omits them rather than showing a control that means nothing.
11303
+ */
11304
+ const GOVERNANCE_EXCLUSIONS = {
11305
+ // A free-text block is neither a sensible uniqueness key nor a group-by.
11306
+ textarea: ['is_unique', 'unq_sa', 'can_group', 'default_group'],
11307
+ };
10538
11308
  const NUMBER_FIELDS = [
10539
11309
  { key: 'decimal', label: 'Decimal places', control: 'number' },
10540
11310
  { key: 'symbol', label: 'Symbol', control: 'text' },
@@ -10568,8 +11338,12 @@ const NUMBER_FIELDS = [
10568
11338
  { value: 'mn', label: 'Millions' },
10569
11339
  ],
10570
11340
  },
11341
+ // Same bands, same shape and same matcher as progress — a number or currency
11342
+ // cell tints its value in view mode when it falls in a band.
11343
+ { key: 'color_ranges', label: 'Colour ranges (by value)', control: 'range_color_list' },
10571
11344
  ];
10572
11345
  const OPTION_FIELDS = [
11346
+ { key: 'searchable', label: 'Searchable', control: 'checkbox', defaultValue: true },
10573
11347
  {
10574
11348
  key: 'option_type',
10575
11349
  label: 'Option source',
@@ -10616,29 +11390,14 @@ const STATIC_COLOR_OPTION_FIELDS = [
10616
11390
  { key: 'options', label: 'Options', control: 'color_list', defaultColor: '#9CA3AF' },
10617
11391
  ];
10618
11392
  const DATE_FIELDS = [
10619
- {
10620
- key: 'date_format',
10621
- label: 'Date format',
10622
- control: 'select',
10623
- options: [
10624
- { value: 'dd-mm-yyyy', label: 'DD-MM-YYYY' },
10625
- { value: 'mm-dd-yyyy', label: 'MM-DD-YYYY' },
10626
- { value: 'yyyy-mm-dd', label: 'YYYY-MM-DD' },
10627
- ],
10628
- },
11393
+ // Shared list, so the panel offers exactly what eru-studio and processo do.
11394
+ { key: 'date_format', label: 'Date format', control: 'select', options: DATE_FORMATS },
10629
11395
  { key: 'allow_days', label: 'Allowed days', control: 'day_chips' },
10630
11396
  ];
10631
11397
  const DATETIME_FIELDS = [
10632
- {
10633
- key: 'dateTimeFormat',
10634
- label: 'Date-time format',
10635
- control: 'select',
10636
- options: [
10637
- { value: 'dd-mm-yyyy hh:mm:ss', label: 'DD-MM-YYYY HH:MM:SS' },
10638
- { value: 'mm-dd-yyyy hh:mm:ss', label: 'MM-DD-YYYY HH:MM:SS' },
10639
- { value: 'yyyy-mm-dd hh:mm:ss', label: 'YYYY-MM-DD HH:MM:SS' },
10640
- ],
10641
- },
11398
+ { key: 'date_format', label: 'Date-time format', control: 'select', options: DATETIME_FORMATS },
11399
+ // processo writes allow_days for datetime as well as date.
11400
+ { key: 'allow_days', label: 'Allowed days', control: 'day_chips' },
10642
11401
  ];
10643
11402
  const TEXTBOX_FIELDS = [
10644
11403
  { key: 'data_length', label: 'Max length', control: 'number' },
@@ -10670,19 +11429,47 @@ const DATATYPE_FIELDS = {
10670
11429
  attachment: [
10671
11430
  { key: 'max_files', label: 'Max files', control: 'number' },
10672
11431
  { key: 'allowed_file_types', label: 'Allowed types (one per line)', control: 'list' },
10673
- { key: 'maxFileSize', label: 'Max file size (bytes)', control: 'number' },
11432
+ { key: 'max_file_size', label: 'Max file size (bytes)', control: 'number' },
10674
11433
  ],
10675
11434
  phone: [{ key: 'default_country', label: 'Default country (ISO)', control: 'text' }],
11435
+ people: [
11436
+ { key: 'multiple', label: 'Allow multiple', control: 'checkbox', defaultValue: true },
11437
+ { key: 'searchable', label: 'Searchable', control: 'checkbox', defaultValue: true },
11438
+ { key: 'max_avatars', label: 'Max avatars', control: 'number' },
11439
+ { key: 'people_card', label: 'Person card page', control: 'text' },
11440
+ {
11441
+ key: 'show_people_card',
11442
+ label: 'Show person card',
11443
+ control: 'select',
11444
+ showWhen: f => !!f.people_card,
11445
+ options: [
11446
+ { value: 'none', label: 'Never' },
11447
+ { value: 'hover', label: 'On hover' },
11448
+ { value: 'click', label: 'On click' },
11449
+ ],
11450
+ },
11451
+ ],
10676
11452
  textbox: TEXTBOX_FIELDS,
11453
+ textarea: TEXTBOX_FIELDS,
11454
+ checkbox: [
11455
+ { key: 'value_true', label: 'Stored value when checked', control: 'text' },
11456
+ { key: 'value_false', label: 'Stored value when unchecked', control: 'text' },
11457
+ ],
10677
11458
  progress: [
10678
- { key: 'color_ranges', label: 'Colour ranges (by %)', control: 'range_color_list' },
11459
+ { key: 'start_value', label: 'Scale start', control: 'number' },
11460
+ { key: 'end_value', label: 'Scale end', control: 'number' },
11461
+ { key: 'is_perc', label: 'Show as percentage', control: 'checkbox', defaultValue: true },
11462
+ { key: 'color_ranges', label: 'Colour ranges (by value)', control: 'range_color_list' },
10679
11463
  ],
10680
11464
  };
10681
11465
  function getMetaFields(field) {
10682
11466
  if (!field)
10683
11467
  return [];
10684
11468
  const extra = field.datatype ? DATATYPE_FIELDS[field.datatype] ?? [] : [];
10685
- return [...UNIVERSAL_FIELDS, ...extra].filter(def => !def.showWhen || def.showWhen(field));
11469
+ const excluded = new Set(field.datatype ? GOVERNANCE_EXCLUSIONS[field.datatype] ?? [] : []);
11470
+ const governance = GOVERNANCE_FIELDS.filter(def => !excluded.has(def.key));
11471
+ return [...UNIVERSAL_FIELDS, ...extra, ...governance]
11472
+ .filter(def => !def.showWhen || def.showWhen(field));
10686
11473
  }
10687
11474
 
10688
11475
  class ColumnDesignPanelComponent {
@@ -10704,13 +11491,13 @@ class ColumnDesignPanelComponent {
10704
11491
  const choice = this._metaSourceChoice();
10705
11492
  if (!col)
10706
11493
  return;
10707
- const showingPickers = choice === 'entity' || (!!col.entity_name && !!col.field_name);
11494
+ const showingPickers = choice === 'entity' || (!!col.mapped_entity && !!col.mapped_field);
10708
11495
  if (!showingPickers)
10709
11496
  return;
10710
11497
  untracked(() => {
10711
11498
  this.ensureEntitiesLoaded();
10712
- if (col.entity_name)
10713
- this.ensureEntityFieldsLoaded(col.entity_name);
11499
+ if (col.mapped_entity)
11500
+ this.ensureEntityFieldsLoaded(col.mapped_entity);
10714
11501
  });
10715
11502
  });
10716
11503
  }
@@ -10733,7 +11520,7 @@ class ColumnDesignPanelComponent {
10733
11520
  static ENTITIES_KEY = 'ds_entities';
10734
11521
  static ENTITY_FIELDS_PREFIX = 'ds_entity_fields:';
10735
11522
  static ENTITY_FIELD_META_PREFIX = 'ds_entity_field_meta:';
10736
- isEntityMapped = computed(() => !!this.field()?.entity_name && !!this.field()?.field_name, ...(ngDevMode ? [{ debugName: "isEntityMapped" }] : []));
11523
+ isEntityMapped = computed(() => !!this.field()?.mapped_entity && !!this.field()?.mapped_field, ...(ngDevMode ? [{ debugName: "isEntityMapped" }] : []));
10737
11524
  /**
10738
11525
  * The source the user picked, before a mapping exists. Needed because a
10739
11526
  * column is only "mapped" once BOTH entity and field are chosen — deriving
@@ -10748,7 +11535,7 @@ class ColumnDesignPanelComponent {
10748
11535
  }, ...(ngDevMode ? [{ debugName: "entityOptions" }] : []));
10749
11536
  entityFieldOptions = computed(() => {
10750
11537
  this.gridStore.dynamicData();
10751
- const entity = this.field()?.entity_name;
11538
+ const entity = this.field()?.mapped_entity;
10752
11539
  if (!entity)
10753
11540
  return [];
10754
11541
  return this.gridStore.getDynamicData(ColumnDesignPanelComponent.ENTITY_FIELDS_PREFIX + entity) || [];
@@ -10760,7 +11547,7 @@ class ColumnDesignPanelComponent {
10760
11547
  return;
10761
11548
  this._metaSourceChoice.set(source);
10762
11549
  if (source === 'manual') {
10763
- this.gridStore.updateColumnMeta(name, { entity_name: undefined, field_name: undefined });
11550
+ this.gridStore.updateColumnMeta(name, { mapped_entity: undefined, mapped_field: undefined });
10764
11551
  return;
10765
11552
  }
10766
11553
  this.requestEntities();
@@ -10794,27 +11581,27 @@ class ColumnDesignPanelComponent {
10794
11581
  if (!name)
10795
11582
  return;
10796
11583
  // Changing the entity invalidates the field choice.
10797
- this.gridStore.updateColumnMeta(name, { entity_name: entityName, field_name: undefined });
11584
+ this.gridStore.updateColumnMeta(name, { mapped_entity: entityName, mapped_field: undefined });
10798
11585
  this.requestEntityFields(entityName);
10799
11586
  }
10800
- /** Attributes a mapped column inherits — editing them here would be silently
10801
- * overwritten on the next load, so the panel locks them instead. */
10802
- static INHERITED_KEYS = new Set([
10803
- 'datatype', 'label', 'options', 'option_type', 'open_status', 'close_status',
10804
- 'color_ranges', 'decimal', 'symbol', 'seperator', 'dynamic_number',
10805
- 'display_number_as', 'num_val', 'num_val_check', 'api_name', 'api_field',
10806
- ]);
11587
+ /**
11588
+ * Attributes a mapped column inherits editing them here would be silently
11589
+ * overwritten on the next load, so the panel locks them instead. Driven from
11590
+ * the same INHERITED_FIELD_KEYS the resolver merges, so the two can never
11591
+ * drift apart: a key that is inherited is always locked, and vice versa.
11592
+ */
11593
+ static INHERITED_KEYS = new Set(INHERITED_FIELD_KEYS);
10807
11594
  isInherited(key) {
10808
11595
  return this.isEntityMapped() && ColumnDesignPanelComponent.INHERITED_KEYS.has(key);
10809
11596
  }
10810
11597
  onEntityFieldChange(fieldName) {
10811
11598
  const name = this.gridStore.selectedDesignColumn();
10812
- const entityName = this.field()?.entity_name;
11599
+ const entityName = this.field()?.mapped_entity;
10813
11600
  if (!name || !entityName)
10814
11601
  return;
10815
11602
  // Only the mapping is persisted. option_type (and the rest of the field's
10816
11603
  // metadata) is inherited from the data model on every load.
10817
- this.gridStore.updateColumnMeta(name, { field_name: fieldName });
11604
+ this.gridStore.updateColumnMeta(name, { mapped_field: fieldName });
10818
11605
  this.gridStore.setDynamicDataRequest({
10819
11606
  key: ColumnDesignPanelComponent.ENTITY_FIELD_META_PREFIX + entityName + '.' + fieldName,
10820
11607
  optionType: 'ENTITY_DATA',
@@ -10902,28 +11689,37 @@ class ColumnDesignPanelComponent {
10902
11689
  this.patch(key, items);
10903
11690
  }
10904
11691
  // ── Range-colour-list control (value ranges -> colour, e.g. progress) ──
11692
+ // A blank bound means open-ended and is preserved as null rather than coerced
11693
+ // to 0 — bands authored on the data-model field ("80 and above") come through
11694
+ // here for display, and 0 would both read wrong and change their meaning.
11695
+ rangeBound(raw) {
11696
+ if (raw === null || raw === undefined || raw === '')
11697
+ return null;
11698
+ const n = Number(raw);
11699
+ return isNaN(n) ? null : n;
11700
+ }
10905
11701
  rangeListItems(key) {
10906
11702
  const arr = this.field()?.[key];
10907
11703
  if (!Array.isArray(arr))
10908
11704
  return [];
10909
11705
  return arr.map(it => ({
10910
- from: Number(it?.from) || 0,
10911
- to: Number(it?.to) || 0,
11706
+ from: this.rangeBound(it?.from),
11707
+ to: this.rangeBound(it?.to),
10912
11708
  color: it?.color ?? '#22C55E',
10913
11709
  }));
10914
11710
  }
10915
11711
  addRangeItem(key) {
10916
11712
  const items = this.rangeListItems(key);
10917
- const lastTo = items.length ? items[items.length - 1].to : -1;
10918
- const from = Math.min(100, lastTo + 1);
11713
+ const lastTo = items.length ? items[items.length - 1].to : null;
11714
+ const from = lastTo === null ? 0 : Math.min(100, lastTo + 1);
10919
11715
  this.patch(key, [...items, { from, to: 100, color: '#22C55E' }]);
10920
11716
  }
10921
11717
  removeRangeItem(key, index) {
10922
11718
  this.patch(key, this.rangeListItems(key).filter((_, i) => i !== index));
10923
11719
  }
10924
11720
  onRangeChange(key, index, prop, value) {
10925
- const num = value === '' ? 0 : Number(value);
10926
- const items = this.rangeListItems(key).map((it, i) => i === index ? { ...it, [prop]: num } : it);
11721
+ const bound = this.rangeBound(value);
11722
+ const items = this.rangeListItems(key).map((it, i) => i === index ? { ...it, [prop]: bound } : it);
10927
11723
  this.patch(key, items);
10928
11724
  }
10929
11725
  onRangeColorChange(key, index, color) {
@@ -10934,7 +11730,7 @@ class ColumnDesignPanelComponent {
10934
11730
  this.gridStore.selectDesignColumn(null);
10935
11731
  }
10936
11732
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ColumnDesignPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10937
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: ColumnDesignPanelComponent, isStandalone: true, selector: "eru-column-design-panel", ngImport: i0, template: "@if (isOpen()) {\n<div class=\"design-panel-backdrop\" (click)=\"close()\"></div>\n}\n<aside class=\"column-design-panel\" [class.open]=\"isOpen()\">\n @if (field(); as col) {\n <header class=\"design-panel-header\">\n <div class=\"design-panel-title\">\n <mat-icon>tune</mat-icon>\n <span>{{ col.label || col.name }}</span>\n </div>\n <button mat-icon-button (click)=\"close()\" title=\"Close\">\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"design-panel-body\">\n <!-- Where this column's metadata comes from: typed in here, or inherited\n from a field in the host app's data model so it stays defined once. -->\n <div class=\"design-field design-meta-source\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Metadata source</mat-label>\n <mat-select [ngModel]=\"metaSource()\" (ngModelChange)=\"onMetaSourceChange($event)\">\n <mat-option value=\"manual\">Defined here</mat-option>\n <mat-option value=\"entity\">Mapped to data model field</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (metaSource() === 'entity') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Entity</mat-label>\n <mat-select [ngModel]=\"getValue('entity_name')\" (ngModelChange)=\"onEntityChange($event)\"\n (openedChange)=\"$event && requestEntities()\">\n @for (opt of entityOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (getValue('entity_name')) {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Field</mat-label>\n <mat-select [ngModel]=\"getValue('field_name')\" (ngModelChange)=\"onEntityFieldChange($event)\">\n @for (opt of entityFieldOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (isEntityMapped()) {\n <p class=\"design-meta-hint\">\n Colours, options and formatting come from the data model. Edit them there to update\n every grid that maps this field.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track def.key) {\n <div class=\"design-field\" [class.design-field-inherited]=\"isInherited(def.key)\"\n [title]=\"isInherited(def.key) ? 'Inherited from the mapped data model field' : ''\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"isChecked(def)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\n }\n\n @case ('day_chips') {\n <div class=\"day-chips\">\n <label class=\"day-chips-label\">{{ def.label }}</label>\n <div class=\"day-chips-row\">\n @for (day of weekDays; track day) {\n <button type=\"button\" class=\"day-chip\" [class.selected]=\"isDaySelected(def.key, day)\"\n (click)=\"toggleDay(def.key, day)\">{{ day }}</button>\n }\n </div>\n </div>\n }\n\n @case ('list') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <textarea matInput rows=\"4\" [ngModel]=\"listText(def.key)\"\n (ngModelChange)=\"onList(def.key, $event)\"></textarea>\n </mat-form-field>\n }\n\n @case ('color_list') {\n <div class=\"color-list\">\n <label class=\"color-list-label\">{{ def.label }}</label>\n <div class=\"color-list-chips\">\n @for (opt of colorListItems(def.key); track $index) {\n <div class=\"color-list-chip\">\n <input type=\"color\" class=\"color-list-dot\" [value]=\"opt.color || '#9CA3AF'\"\n (input)=\"onColorListColorChange(def.key, $index, $any($event.target).value)\" title=\"Change colour\" />\n <span class=\"color-list-name\">{{ opt.name }}</span>\n <button type=\"button\" class=\"color-list-remove\" (click)=\"removeColorListItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n </div>\n <input class=\"color-list-add\" type=\"text\" placeholder=\"Add option, press Enter\"\n (keydown.enter)=\"addColorListItem(def.key, $any($event.target).value, def.defaultColor || '#9CA3AF'); $any($event.target).value = ''\" />\n </div>\n }\n\n @case ('range_color_list') {\n <div class=\"range-list\">\n <label class=\"range-list-label\">{{ def.label }}</label>\n @for (r of rangeListItems(def.key); track $index) {\n <div class=\"range-list-row\">\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.from\"\n (input)=\"onRangeChange(def.key, $index, 'from', $any($event.target).value)\" title=\"From\" />\n <span class=\"range-list-sep\">\u2013</span>\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.to\"\n (input)=\"onRangeChange(def.key, $index, 'to', $any($event.target).value)\" title=\"To\" />\n <input type=\"color\" class=\"range-list-color\" [value]=\"r.color || '#22C55E'\"\n (input)=\"onRangeColorChange(def.key, $index, $any($event.target).value)\" title=\"Colour\" />\n <button type=\"button\" class=\"range-list-remove\" (click)=\"removeRangeItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n <button type=\"button\" class=\"range-list-add\" (click)=\"addRangeItem(def.key)\">+ Add range</button>\n </div>\n }\n\n }\n </div>\n }\n </div>\n }\n</aside>\n", styles: [".design-panel-backdrop{position:fixed;inset:0;background:#0000002e;z-index:1000}.column-design-panel{position:fixed;top:0;right:0;bottom:0;width:360px;max-width:90vw;background:var(--grid-surface, #fff);box-shadow:-4px 0 16px #00000029;transform:translate(100%);transition:transform .22s ease;z-index:1001;display:flex;flex-direction:column;font-family:var(--grid-font-family, inherit)}.column-design-panel.open{transform:translate(0)}.design-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--grid-border-color, #e0e0e0)}.design-panel-header .design-panel-title{display:flex;align-items:center;gap:8px;font-weight:600;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.design-panel-body{padding:16px;overflow-y:auto;flex:1}.design-panel-body .design-field{margin-bottom:4px}.design-panel-body .design-field .full-width{width:100%}.design-panel-body .design-field mat-checkbox{display:block;margin:8px 0 16px}.design-panel-body .color-list{margin:4px 0 16px}.design-panel-body .color-list .color-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.design-panel-body .color-list .color-list-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}.design-panel-body .color-list .color-list-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px 3px 4px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:16px;background:var(--grid-surface, #fff);font-size:12px}.design-panel-body .color-list .color-list-dot{width:18px;height:18px;padding:0;border:none;background:none;border-radius:50%;cursor:pointer}.design-panel-body .color-list .color-list-remove{border:none;background:none;cursor:pointer;font-size:14px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.design-panel-body .color-list .color-list-remove:hover{color:var(--grid-error, #b3261e)}.design-panel-body .color-list .color-list-add{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.design-panel-body .color-list .color-list-add:focus{border-color:var(--grid-primary, #6750a4)}.range-list{margin:4px 0 16px}.range-list .range-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.range-list .range-list-row{display:flex;align-items:center;gap:6px;margin-bottom:6px}.range-list .range-list-num{width:56px;padding:6px 8px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.range-list .range-list-num:focus{border-color:var(--grid-primary, #6750a4)}.range-list .range-list-sep{color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-color{width:28px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;cursor:pointer}.range-list .range-list-remove{margin-left:auto;border:none;background:none;cursor:pointer;font-size:18px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-remove:hover{color:var(--grid-error, #b3261e)}.range-list .range-list-add{margin-top:2px;padding:6px 10px;border:1px dashed var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;font-size:13px;cursor:pointer;color:var(--grid-primary, #6750a4)}.range-list .range-list-add:hover{background:var(--grid-surface-variant, #f3edf7)}.day-chips{display:flex;flex-direction:column;gap:6px}.day-chips-label{font-size:12px;color:var(--eru-on-surface-variant, #5f6368)}.day-chips-row{display:flex;flex-wrap:wrap;gap:6px}.day-chip{border:1px solid var(--eru-outline, #c4c7c5);background:transparent;border-radius:16px;padding:4px 12px;font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s,color .15s,border-color .15s}.day-chip.selected{background:var(--eru-primary, #1a73e8);border-color:var(--eru-primary, #1a73e8);color:#fff}.design-field-inherited{opacity:.55;pointer-events:none}.design-meta-source{padding-bottom:8px;border-bottom:1px solid var(--grid-outline-variant, #e0e0e0);margin-bottom:12px}.design-meta-hint{margin:4px 0 0;font-size:11px;line-height:1.4;color:var(--grid-on-surface-variant, #49454f)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11733
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: ColumnDesignPanelComponent, isStandalone: true, selector: "eru-column-design-panel", ngImport: i0, template: "@if (isOpen()) {\n<div class=\"design-panel-backdrop\" (click)=\"close()\"></div>\n}\n<aside class=\"column-design-panel\" [class.open]=\"isOpen()\">\n @if (field(); as col) {\n <header class=\"design-panel-header\">\n <div class=\"design-panel-title\">\n <mat-icon>tune</mat-icon>\n <span>{{ col.label || col.name }}</span>\n </div>\n <button mat-icon-button (click)=\"close()\" title=\"Close\">\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"design-panel-body\">\n <!-- Where this column's metadata comes from: typed in here, or inherited\n from a field in the host app's data model so it stays defined once. -->\n <div class=\"design-field design-meta-source\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Metadata source</mat-label>\n <mat-select [ngModel]=\"metaSource()\" (ngModelChange)=\"onMetaSourceChange($event)\">\n <mat-option value=\"manual\">Defined here</mat-option>\n <mat-option value=\"entity\">Mapped to data model field</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (metaSource() === 'entity') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Entity</mat-label>\n <mat-select [ngModel]=\"getValue('mapped_entity')\" (ngModelChange)=\"onEntityChange($event)\"\n (openedChange)=\"$event && requestEntities()\">\n @for (opt of entityOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (getValue('mapped_entity')) {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Field</mat-label>\n <mat-select [ngModel]=\"getValue('mapped_field')\" (ngModelChange)=\"onEntityFieldChange($event)\">\n @for (opt of entityFieldOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (isEntityMapped()) {\n <p class=\"design-meta-hint\">\n Colours, options and formatting come from the data model. Edit them there to update\n every grid that maps this field.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track def.key) {\n <div class=\"design-field\" [class.design-field-inherited]=\"isInherited(def.key)\"\n [title]=\"isInherited(def.key) ? 'Inherited from the mapped data model field' : ''\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"isChecked(def)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\n }\n\n @case ('day_chips') {\n <div class=\"day-chips\">\n <label class=\"day-chips-label\">{{ def.label }}</label>\n <div class=\"day-chips-row\">\n @for (day of weekDays; track day) {\n <button type=\"button\" class=\"day-chip\" [class.selected]=\"isDaySelected(def.key, day)\"\n (click)=\"toggleDay(def.key, day)\">{{ day }}</button>\n }\n </div>\n </div>\n }\n\n @case ('list') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <textarea matInput rows=\"4\" [ngModel]=\"listText(def.key)\"\n (ngModelChange)=\"onList(def.key, $event)\"></textarea>\n </mat-form-field>\n }\n\n @case ('color_list') {\n <div class=\"color-list\">\n <label class=\"color-list-label\">{{ def.label }}</label>\n <div class=\"color-list-chips\">\n @for (opt of colorListItems(def.key); track $index) {\n <div class=\"color-list-chip\">\n <input type=\"color\" class=\"color-list-dot\" [value]=\"opt.color || '#9CA3AF'\"\n (input)=\"onColorListColorChange(def.key, $index, $any($event.target).value)\" title=\"Change colour\" />\n <span class=\"color-list-name\">{{ opt.name }}</span>\n <button type=\"button\" class=\"color-list-remove\" (click)=\"removeColorListItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n </div>\n <input class=\"color-list-add\" type=\"text\" placeholder=\"Add option, press Enter\"\n (keydown.enter)=\"addColorListItem(def.key, $any($event.target).value, def.defaultColor || '#9CA3AF'); $any($event.target).value = ''\" />\n </div>\n }\n\n @case ('range_color_list') {\n <div class=\"range-list\">\n <label class=\"range-list-label\">{{ def.label }}</label>\n @for (r of rangeListItems(def.key); track $index) {\n <div class=\"range-list-row\">\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.from\"\n (input)=\"onRangeChange(def.key, $index, 'from', $any($event.target).value)\" title=\"From\" />\n <span class=\"range-list-sep\">\u2013</span>\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.to\"\n (input)=\"onRangeChange(def.key, $index, 'to', $any($event.target).value)\" title=\"To\" />\n <input type=\"color\" class=\"range-list-color\" [value]=\"r.color || '#22C55E'\"\n (input)=\"onRangeColorChange(def.key, $index, $any($event.target).value)\" title=\"Colour\" />\n <button type=\"button\" class=\"range-list-remove\" (click)=\"removeRangeItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n <button type=\"button\" class=\"range-list-add\" (click)=\"addRangeItem(def.key)\">+ Add range</button>\n </div>\n }\n\n }\n </div>\n }\n </div>\n }\n</aside>\n", styles: [".design-panel-backdrop{position:fixed;inset:0;background:#0000002e;z-index:1000}.column-design-panel{position:fixed;top:0;right:0;bottom:0;width:360px;max-width:90vw;background:var(--grid-surface, #fff);box-shadow:-4px 0 16px #00000029;transform:translate(100%);transition:transform .22s ease;z-index:1001;display:flex;flex-direction:column;font-family:var(--grid-font-family, inherit)}.column-design-panel.open{transform:translate(0)}.design-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--grid-border-color, #e0e0e0)}.design-panel-header .design-panel-title{display:flex;align-items:center;gap:8px;font-weight:600;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.design-panel-body{padding:16px;overflow-y:auto;flex:1}.design-panel-body .design-field{margin-bottom:4px}.design-panel-body .design-field .full-width{width:100%}.design-panel-body .design-field mat-checkbox{display:block;margin:8px 0 16px}.design-panel-body .color-list{margin:4px 0 16px}.design-panel-body .color-list .color-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.design-panel-body .color-list .color-list-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}.design-panel-body .color-list .color-list-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px 3px 4px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:16px;background:var(--grid-surface, #fff);font-size:12px}.design-panel-body .color-list .color-list-dot{width:18px;height:18px;padding:0;border:none;background:none;border-radius:50%;cursor:pointer}.design-panel-body .color-list .color-list-remove{border:none;background:none;cursor:pointer;font-size:14px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.design-panel-body .color-list .color-list-remove:hover{color:var(--grid-error, #b3261e)}.design-panel-body .color-list .color-list-add{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.design-panel-body .color-list .color-list-add:focus{border-color:var(--grid-primary, #6750a4)}.range-list{margin:4px 0 16px}.range-list .range-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.range-list .range-list-row{display:flex;align-items:center;gap:6px;margin-bottom:6px}.range-list .range-list-num{width:56px;padding:6px 8px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.range-list .range-list-num:focus{border-color:var(--grid-primary, #6750a4)}.range-list .range-list-sep{color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-color{width:28px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;cursor:pointer}.range-list .range-list-remove{margin-left:auto;border:none;background:none;cursor:pointer;font-size:18px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-remove:hover{color:var(--grid-error, #b3261e)}.range-list .range-list-add{margin-top:2px;padding:6px 10px;border:1px dashed var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;font-size:13px;cursor:pointer;color:var(--grid-primary, #6750a4)}.range-list .range-list-add:hover{background:var(--grid-surface-variant, #f3edf7)}.day-chips{display:flex;flex-direction:column;gap:6px}.day-chips-label{font-size:12px;color:var(--eru-on-surface-variant, #5f6368)}.day-chips-row{display:flex;flex-wrap:wrap;gap:6px}.day-chip{border:1px solid var(--eru-outline, #c4c7c5);background:transparent;border-radius:16px;padding:4px 12px;font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s,color .15s,border-color .15s}.day-chip.selected{background:var(--eru-primary, #1a73e8);border-color:var(--eru-primary, #1a73e8);color:#fff}.design-field-inherited{opacity:.55;pointer-events:none}.design-meta-source{padding-bottom:8px;border-bottom:1px solid var(--grid-outline-variant, #e0e0e0);margin-bottom:12px}.design-meta-hint{margin:4px 0 0;font-size:11px;line-height:1.4;color:var(--grid-on-surface-variant, #49454f)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10938
11734
  }
10939
11735
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ColumnDesignPanelComponent, decorators: [{
10940
11736
  type: Component,
@@ -10947,7 +11743,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
10947
11743
  MatCheckboxModule,
10948
11744
  MatIconModule,
10949
11745
  MatButtonModule,
10950
- ], template: "@if (isOpen()) {\n<div class=\"design-panel-backdrop\" (click)=\"close()\"></div>\n}\n<aside class=\"column-design-panel\" [class.open]=\"isOpen()\">\n @if (field(); as col) {\n <header class=\"design-panel-header\">\n <div class=\"design-panel-title\">\n <mat-icon>tune</mat-icon>\n <span>{{ col.label || col.name }}</span>\n </div>\n <button mat-icon-button (click)=\"close()\" title=\"Close\">\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"design-panel-body\">\n <!-- Where this column's metadata comes from: typed in here, or inherited\n from a field in the host app's data model so it stays defined once. -->\n <div class=\"design-field design-meta-source\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Metadata source</mat-label>\n <mat-select [ngModel]=\"metaSource()\" (ngModelChange)=\"onMetaSourceChange($event)\">\n <mat-option value=\"manual\">Defined here</mat-option>\n <mat-option value=\"entity\">Mapped to data model field</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (metaSource() === 'entity') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Entity</mat-label>\n <mat-select [ngModel]=\"getValue('entity_name')\" (ngModelChange)=\"onEntityChange($event)\"\n (openedChange)=\"$event && requestEntities()\">\n @for (opt of entityOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (getValue('entity_name')) {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Field</mat-label>\n <mat-select [ngModel]=\"getValue('field_name')\" (ngModelChange)=\"onEntityFieldChange($event)\">\n @for (opt of entityFieldOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (isEntityMapped()) {\n <p class=\"design-meta-hint\">\n Colours, options and formatting come from the data model. Edit them there to update\n every grid that maps this field.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track def.key) {\n <div class=\"design-field\" [class.design-field-inherited]=\"isInherited(def.key)\"\n [title]=\"isInherited(def.key) ? 'Inherited from the mapped data model field' : ''\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"isChecked(def)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\n }\n\n @case ('day_chips') {\n <div class=\"day-chips\">\n <label class=\"day-chips-label\">{{ def.label }}</label>\n <div class=\"day-chips-row\">\n @for (day of weekDays; track day) {\n <button type=\"button\" class=\"day-chip\" [class.selected]=\"isDaySelected(def.key, day)\"\n (click)=\"toggleDay(def.key, day)\">{{ day }}</button>\n }\n </div>\n </div>\n }\n\n @case ('list') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <textarea matInput rows=\"4\" [ngModel]=\"listText(def.key)\"\n (ngModelChange)=\"onList(def.key, $event)\"></textarea>\n </mat-form-field>\n }\n\n @case ('color_list') {\n <div class=\"color-list\">\n <label class=\"color-list-label\">{{ def.label }}</label>\n <div class=\"color-list-chips\">\n @for (opt of colorListItems(def.key); track $index) {\n <div class=\"color-list-chip\">\n <input type=\"color\" class=\"color-list-dot\" [value]=\"opt.color || '#9CA3AF'\"\n (input)=\"onColorListColorChange(def.key, $index, $any($event.target).value)\" title=\"Change colour\" />\n <span class=\"color-list-name\">{{ opt.name }}</span>\n <button type=\"button\" class=\"color-list-remove\" (click)=\"removeColorListItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n </div>\n <input class=\"color-list-add\" type=\"text\" placeholder=\"Add option, press Enter\"\n (keydown.enter)=\"addColorListItem(def.key, $any($event.target).value, def.defaultColor || '#9CA3AF'); $any($event.target).value = ''\" />\n </div>\n }\n\n @case ('range_color_list') {\n <div class=\"range-list\">\n <label class=\"range-list-label\">{{ def.label }}</label>\n @for (r of rangeListItems(def.key); track $index) {\n <div class=\"range-list-row\">\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.from\"\n (input)=\"onRangeChange(def.key, $index, 'from', $any($event.target).value)\" title=\"From\" />\n <span class=\"range-list-sep\">\u2013</span>\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.to\"\n (input)=\"onRangeChange(def.key, $index, 'to', $any($event.target).value)\" title=\"To\" />\n <input type=\"color\" class=\"range-list-color\" [value]=\"r.color || '#22C55E'\"\n (input)=\"onRangeColorChange(def.key, $index, $any($event.target).value)\" title=\"Colour\" />\n <button type=\"button\" class=\"range-list-remove\" (click)=\"removeRangeItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n <button type=\"button\" class=\"range-list-add\" (click)=\"addRangeItem(def.key)\">+ Add range</button>\n </div>\n }\n\n }\n </div>\n }\n </div>\n }\n</aside>\n", styles: [".design-panel-backdrop{position:fixed;inset:0;background:#0000002e;z-index:1000}.column-design-panel{position:fixed;top:0;right:0;bottom:0;width:360px;max-width:90vw;background:var(--grid-surface, #fff);box-shadow:-4px 0 16px #00000029;transform:translate(100%);transition:transform .22s ease;z-index:1001;display:flex;flex-direction:column;font-family:var(--grid-font-family, inherit)}.column-design-panel.open{transform:translate(0)}.design-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--grid-border-color, #e0e0e0)}.design-panel-header .design-panel-title{display:flex;align-items:center;gap:8px;font-weight:600;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.design-panel-body{padding:16px;overflow-y:auto;flex:1}.design-panel-body .design-field{margin-bottom:4px}.design-panel-body .design-field .full-width{width:100%}.design-panel-body .design-field mat-checkbox{display:block;margin:8px 0 16px}.design-panel-body .color-list{margin:4px 0 16px}.design-panel-body .color-list .color-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.design-panel-body .color-list .color-list-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}.design-panel-body .color-list .color-list-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px 3px 4px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:16px;background:var(--grid-surface, #fff);font-size:12px}.design-panel-body .color-list .color-list-dot{width:18px;height:18px;padding:0;border:none;background:none;border-radius:50%;cursor:pointer}.design-panel-body .color-list .color-list-remove{border:none;background:none;cursor:pointer;font-size:14px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.design-panel-body .color-list .color-list-remove:hover{color:var(--grid-error, #b3261e)}.design-panel-body .color-list .color-list-add{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.design-panel-body .color-list .color-list-add:focus{border-color:var(--grid-primary, #6750a4)}.range-list{margin:4px 0 16px}.range-list .range-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.range-list .range-list-row{display:flex;align-items:center;gap:6px;margin-bottom:6px}.range-list .range-list-num{width:56px;padding:6px 8px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.range-list .range-list-num:focus{border-color:var(--grid-primary, #6750a4)}.range-list .range-list-sep{color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-color{width:28px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;cursor:pointer}.range-list .range-list-remove{margin-left:auto;border:none;background:none;cursor:pointer;font-size:18px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-remove:hover{color:var(--grid-error, #b3261e)}.range-list .range-list-add{margin-top:2px;padding:6px 10px;border:1px dashed var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;font-size:13px;cursor:pointer;color:var(--grid-primary, #6750a4)}.range-list .range-list-add:hover{background:var(--grid-surface-variant, #f3edf7)}.day-chips{display:flex;flex-direction:column;gap:6px}.day-chips-label{font-size:12px;color:var(--eru-on-surface-variant, #5f6368)}.day-chips-row{display:flex;flex-wrap:wrap;gap:6px}.day-chip{border:1px solid var(--eru-outline, #c4c7c5);background:transparent;border-radius:16px;padding:4px 12px;font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s,color .15s,border-color .15s}.day-chip.selected{background:var(--eru-primary, #1a73e8);border-color:var(--eru-primary, #1a73e8);color:#fff}.design-field-inherited{opacity:.55;pointer-events:none}.design-meta-source{padding-bottom:8px;border-bottom:1px solid var(--grid-outline-variant, #e0e0e0);margin-bottom:12px}.design-meta-hint{margin:4px 0 0;font-size:11px;line-height:1.4;color:var(--grid-on-surface-variant, #49454f)}\n"] }]
11746
+ ], template: "@if (isOpen()) {\n<div class=\"design-panel-backdrop\" (click)=\"close()\"></div>\n}\n<aside class=\"column-design-panel\" [class.open]=\"isOpen()\">\n @if (field(); as col) {\n <header class=\"design-panel-header\">\n <div class=\"design-panel-title\">\n <mat-icon>tune</mat-icon>\n <span>{{ col.label || col.name }}</span>\n </div>\n <button mat-icon-button (click)=\"close()\" title=\"Close\">\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"design-panel-body\">\n <!-- Where this column's metadata comes from: typed in here, or inherited\n from a field in the host app's data model so it stays defined once. -->\n <div class=\"design-field design-meta-source\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Metadata source</mat-label>\n <mat-select [ngModel]=\"metaSource()\" (ngModelChange)=\"onMetaSourceChange($event)\">\n <mat-option value=\"manual\">Defined here</mat-option>\n <mat-option value=\"entity\">Mapped to data model field</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (metaSource() === 'entity') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Entity</mat-label>\n <mat-select [ngModel]=\"getValue('mapped_entity')\" (ngModelChange)=\"onEntityChange($event)\"\n (openedChange)=\"$event && requestEntities()\">\n @for (opt of entityOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (getValue('mapped_entity')) {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Field</mat-label>\n <mat-select [ngModel]=\"getValue('mapped_field')\" (ngModelChange)=\"onEntityFieldChange($event)\">\n @for (opt of entityFieldOptions(); track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @if (isEntityMapped()) {\n <p class=\"design-meta-hint\">\n Colours, options and formatting come from the data model. Edit them there to update\n every grid that maps this field.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track def.key) {\n <div class=\"design-field\" [class.design-field-inherited]=\"isInherited(def.key)\"\n [title]=\"isInherited(def.key) ? 'Inherited from the mapped data model field' : ''\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"isChecked(def)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\n }\n\n @case ('day_chips') {\n <div class=\"day-chips\">\n <label class=\"day-chips-label\">{{ def.label }}</label>\n <div class=\"day-chips-row\">\n @for (day of weekDays; track day) {\n <button type=\"button\" class=\"day-chip\" [class.selected]=\"isDaySelected(def.key, day)\"\n (click)=\"toggleDay(def.key, day)\">{{ day }}</button>\n }\n </div>\n </div>\n }\n\n @case ('list') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <textarea matInput rows=\"4\" [ngModel]=\"listText(def.key)\"\n (ngModelChange)=\"onList(def.key, $event)\"></textarea>\n </mat-form-field>\n }\n\n @case ('color_list') {\n <div class=\"color-list\">\n <label class=\"color-list-label\">{{ def.label }}</label>\n <div class=\"color-list-chips\">\n @for (opt of colorListItems(def.key); track $index) {\n <div class=\"color-list-chip\">\n <input type=\"color\" class=\"color-list-dot\" [value]=\"opt.color || '#9CA3AF'\"\n (input)=\"onColorListColorChange(def.key, $index, $any($event.target).value)\" title=\"Change colour\" />\n <span class=\"color-list-name\">{{ opt.name }}</span>\n <button type=\"button\" class=\"color-list-remove\" (click)=\"removeColorListItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n </div>\n <input class=\"color-list-add\" type=\"text\" placeholder=\"Add option, press Enter\"\n (keydown.enter)=\"addColorListItem(def.key, $any($event.target).value, def.defaultColor || '#9CA3AF'); $any($event.target).value = ''\" />\n </div>\n }\n\n @case ('range_color_list') {\n <div class=\"range-list\">\n <label class=\"range-list-label\">{{ def.label }}</label>\n @for (r of rangeListItems(def.key); track $index) {\n <div class=\"range-list-row\">\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.from\"\n (input)=\"onRangeChange(def.key, $index, 'from', $any($event.target).value)\" title=\"From\" />\n <span class=\"range-list-sep\">\u2013</span>\n <input type=\"number\" class=\"range-list-num\" min=\"0\" max=\"100\" [value]=\"r.to\"\n (input)=\"onRangeChange(def.key, $index, 'to', $any($event.target).value)\" title=\"To\" />\n <input type=\"color\" class=\"range-list-color\" [value]=\"r.color || '#22C55E'\"\n (input)=\"onRangeColorChange(def.key, $index, $any($event.target).value)\" title=\"Colour\" />\n <button type=\"button\" class=\"range-list-remove\" (click)=\"removeRangeItem(def.key, $index)\"\n title=\"Remove\">&times;</button>\n </div>\n }\n <button type=\"button\" class=\"range-list-add\" (click)=\"addRangeItem(def.key)\">+ Add range</button>\n </div>\n }\n\n }\n </div>\n }\n </div>\n }\n</aside>\n", styles: [".design-panel-backdrop{position:fixed;inset:0;background:#0000002e;z-index:1000}.column-design-panel{position:fixed;top:0;right:0;bottom:0;width:360px;max-width:90vw;background:var(--grid-surface, #fff);box-shadow:-4px 0 16px #00000029;transform:translate(100%);transition:transform .22s ease;z-index:1001;display:flex;flex-direction:column;font-family:var(--grid-font-family, inherit)}.column-design-panel.open{transform:translate(0)}.design-panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--grid-border-color, #e0e0e0)}.design-panel-header .design-panel-title{display:flex;align-items:center;gap:8px;font-weight:600;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.design-panel-body{padding:16px;overflow-y:auto;flex:1}.design-panel-body .design-field{margin-bottom:4px}.design-panel-body .design-field .full-width{width:100%}.design-panel-body .design-field mat-checkbox{display:block;margin:8px 0 16px}.design-panel-body .color-list{margin:4px 0 16px}.design-panel-body .color-list .color-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.design-panel-body .color-list .color-list-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}.design-panel-body .color-list .color-list-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px 3px 4px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:16px;background:var(--grid-surface, #fff);font-size:12px}.design-panel-body .color-list .color-list-dot{width:18px;height:18px;padding:0;border:none;background:none;border-radius:50%;cursor:pointer}.design-panel-body .color-list .color-list-remove{border:none;background:none;cursor:pointer;font-size:14px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.design-panel-body .color-list .color-list-remove:hover{color:var(--grid-error, #b3261e)}.design-panel-body .color-list .color-list-add{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.design-panel-body .color-list .color-list-add:focus{border-color:var(--grid-primary, #6750a4)}.range-list{margin:4px 0 16px}.range-list .range-list-label{display:block;font-size:12px;color:var(--grid-on-surface-variant, #49454f);margin-bottom:6px}.range-list .range-list-row{display:flex;align-items:center;gap:6px;margin-bottom:6px}.range-list .range-list-num{width:56px;padding:6px 8px;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;font-size:13px;outline:none}.range-list .range-list-num:focus{border-color:var(--grid-primary, #6750a4)}.range-list .range-list-sep{color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-color{width:28px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;cursor:pointer}.range-list .range-list-remove{margin-left:auto;border:none;background:none;cursor:pointer;font-size:18px;line-height:1;color:var(--grid-on-surface-variant, #49454f)}.range-list .range-list-remove:hover{color:var(--grid-error, #b3261e)}.range-list .range-list-add{margin-top:2px;padding:6px 10px;border:1px dashed var(--grid-outline-variant, #cac4d0);border-radius:6px;background:none;font-size:13px;cursor:pointer;color:var(--grid-primary, #6750a4)}.range-list .range-list-add:hover{background:var(--grid-surface-variant, #f3edf7)}.day-chips{display:flex;flex-direction:column;gap:6px}.day-chips-label{font-size:12px;color:var(--eru-on-surface-variant, #5f6368)}.day-chips-row{display:flex;flex-wrap:wrap;gap:6px}.day-chip{border:1px solid var(--eru-outline, #c4c7c5);background:transparent;border-radius:16px;padding:4px 12px;font-size:12px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s,color .15s,border-color .15s}.day-chip.selected{background:var(--eru-primary, #1a73e8);border-color:var(--eru-primary, #1a73e8);color:#fff}.design-field-inherited{opacity:.55;pointer-events:none}.design-meta-source{padding-bottom:8px;border-bottom:1px solid var(--grid-outline-variant, #e0e0e0);margin-bottom:12px}.design-meta-hint{margin:4px 0 0;font-size:11px;line-height:1.4;color:var(--grid-on-surface-variant, #49454f)}\n"] }]
10951
11747
  }], ctorParameters: () => [] });
10952
11748
 
10953
11749
  /**
@@ -11007,6 +11803,13 @@ class EruGridComponent {
11007
11803
  * <eru-grid [boardCardTemplate]="myCard" [gridConfig]="cfg"></eru-grid>
11008
11804
  */
11009
11805
  boardCardTemplate;
11806
+ /**
11807
+ * Content for a people cell's person card. The grid owns the trigger,
11808
+ * placement and dismissal; the consumer renders the content, because the card
11809
+ * is an eru-studio page and only the consumer can render one. Omit it and the
11810
+ * card trigger stays inert.
11811
+ */
11812
+ personCardTemplate;
11010
11813
  /**
11011
11814
  * Height in pixels of each board card slot.
11012
11815
  * Must match the fixed height of the card rendered by boardCardTemplate (or the default card).
@@ -11498,11 +12301,15 @@ class EruGridComponent {
11498
12301
  constructor(changeDetectorRef) {
11499
12302
  this.changeDetectorRef = changeDetectorRef;
11500
12303
  // Resolve columns that are mapped to a data-model field. Only the mapping
11501
- // (entity_name + field_name) is persisted, so on every load we ask the
12304
+ // (mapped_entity + mapped_field) is persisted, so on every load we ask the
11502
12305
  // consumer for that field's metadata and merge it onto the column. The
11503
12306
  // data model therefore stays the single source of truth for things like
11504
12307
  // status colours, and edits there reach every mapped grid without the
11505
12308
  // values ever being copied into the page.
12309
+ //
12310
+ // These are deliberately NOT entity_name/field_name: on the same Field
12311
+ // object those two mean a dropdown's option source, so keying the mapping
12312
+ // off them made every entity-sourced dropdown look like a mapped column.
11506
12313
  effect(() => {
11507
12314
  const cols = this.gridStore.columns();
11508
12315
  const data = this.gridStore.dynamicData();
@@ -11510,9 +12317,9 @@ class EruGridComponent {
11510
12317
  return;
11511
12318
  untracked(() => {
11512
12319
  for (const col of cols) {
11513
- if (!col?.entity_name || !col?.field_name)
12320
+ if (!col?.mapped_entity || !col?.mapped_field)
11514
12321
  continue;
11515
- const key = `ds_entity_field_meta:${col.entity_name}.${col.field_name}`;
12322
+ const key = `ds_entity_field_meta:${col.mapped_entity}.${col.mapped_field}`;
11516
12323
  const meta = this.gridStore.getDynamicData(key);
11517
12324
  if (!meta) {
11518
12325
  // Not fetched yet (e.g. a freshly loaded page) — ask once.
@@ -11520,8 +12327,8 @@ class EruGridComponent {
11520
12327
  this.gridStore.setDynamicDataRequest({
11521
12328
  key,
11522
12329
  optionType: 'ENTITY_DATA',
11523
- entityName: col.entity_name,
11524
- fieldName: col.field_name,
12330
+ entityName: col.mapped_entity,
12331
+ fieldName: col.mapped_field,
11525
12332
  });
11526
12333
  }
11527
12334
  continue;
@@ -12264,12 +13071,7 @@ class EruGridComponent {
12264
13071
  */
12265
13072
  buildMappedColumnPatch(meta) {
12266
13073
  const patch = {};
12267
- const carry = [
12268
- 'datatype', 'label', 'options', 'option_type', 'open_status', 'close_status',
12269
- 'color_ranges', 'decimal', 'symbol', 'seperator', 'dynamic_number',
12270
- 'display_number_as', 'num_val', 'num_val_check', 'api_name', 'api_field',
12271
- ];
12272
- for (const k of carry) {
13074
+ for (const k of INHERITED_FIELD_KEYS) {
12273
13075
  const v = meta?.[k];
12274
13076
  if (v !== undefined && v !== null && v !== '')
12275
13077
  patch[k] = v;
@@ -12942,9 +13744,9 @@ class EruGridComponent {
12942
13744
  });
12943
13745
  }
12944
13746
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: EruGridComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
12945
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: EruGridComponent, isStandalone: true, selector: "eru-grid", inputs: { gridConfig: "gridConfig", boardCardTemplate: "boardCardTemplate", boardCardHeight: "boardCardHeight", boardCardGap: "boardCardGap", boardCardPadding: "boardCardPadding" }, outputs: { rowSelect: "rowSelect" }, providers: [EruGridStore, EruGridService,
13747
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: EruGridComponent, isStandalone: true, selector: "eru-grid", inputs: { gridConfig: "gridConfig", boardCardTemplate: "boardCardTemplate", personCardTemplate: "personCardTemplate", boardCardHeight: "boardCardHeight", boardCardGap: "boardCardGap", boardCardPadding: "boardCardPadding" }, outputs: { rowSelect: "rowSelect" }, providers: [EruGridStore, EruGridService,
12946
13748
  ...MATERIAL_PROVIDERS
12947
- ], viewQueries: [{ propertyName: "rowContainer", first: true, predicate: ["rowContainer"], descendants: true }, { propertyName: "headerScroller", first: true, predicate: ["headerScroller"], descendants: true, read: ElementRef }, { propertyName: "gtScroller", first: true, predicate: ["gtScroller"], descendants: true, read: ElementRef }, { propertyName: "viewport", first: true, predicate: ["vp"], descendants: true }, { propertyName: "groupsViewport", first: true, predicate: ["groupsViewport"], descendants: true }, { propertyName: "groupsScrollContainerEl", first: true, predicate: ["groupsScrollContainer"], descendants: true }, { propertyName: "allViewports", predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "headerScrollers", predicate: ["headerScroller"], descendants: true }], ngImport: i0, template: "<!-- <div style=\"background: #f0f0f0; font-size: 12px; border-bottom: 1px solid #ccc;\">\ncurrentPivotScrollIndex {{currentPivotScrollIndex()}} |\nfirstDataRowIndex {{firstDataRowIndex()}} |\nfirstTr {{firstTr}} |\nmaxDepth {{maxDepth()}}\n</div> -->\n<div class=\"incremental-row-container eru-grid\" #rowContainer [class.pivot-mode]=\"gridStore.isPivotMode()\"\n [class.table-mode]=\"!gridStore.isPivotMode() && !isBoardMode()\" [class.board-mode-host]=\"isBoardMode()\">\n <eru-column-design-panel></eru-column-design-panel>\n <!-- Pivot Mode Template -->\n @if (gridStore.isPivotMode()) {\n <ng-container>\n <div class=\"pivot-container\" style=\"display: flex; flex-direction: column; height: 100%;\"\n [style]=\"'--table-min-height: ' + getInitialMinHeightPx() + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <!-- Debug info for first visible row -->\n\n\n <div class=\"pivot-single-table\"\n style=\"height: 100%; width: 100%; overflow: hidden; display: flex; flex-direction: column;\">\n @if (freezeHeader()) {\n <div #headerScroller class=\"header-shell\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n @if(grandTotalPosition() === 'before' && freezeGrandTotal()) {\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n }\n </table>\n </div>\n }\n <!-- Virtual Scrolled Table Body -->\n <div>\n <cdk-virtual-scroll-viewport #vp [itemSize]=\"dataRowHeight()\" class=\"viewport pivot-viewport\"\n [class.apply-cdk-width]=\"applyCdkWidth()\" (scrolledIndexChange)=\"onPivotScroll($event)\"\n (scroll)=\"onBodyScroll($event)\" style=\"overflow: auto;\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n @if (!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n }\n <!-- Table Body with Virtual Scrolling -->\n <tbody class=\"pivot-tbody\">\n\n <tr *cdkVirtualFor=\"let pivotRow of gridStore.pivotDisplayData(); \n trackBy: trackByPivotRowFn; \n let i = index\" class=\"pivot-row\" [class.subtotal-row]=\"pivotRow._isSubtotal\"\n [class.grand-total-row]=\"pivotRow._isGrandTotal\"\n [class.subtotal-bold]=\"pivotRow._isSubtotal && subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"pivotRow._isSubtotal && subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"pivotRow._isSubtotal && subTotalStyle() === 'highlighted'\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" [attr.data-pivot-row]=\"i\">\n @if ((!pivotRow._isGrandTotal && freezeGrandTotal() ) || (!freezeGrandTotal() )) {\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.pivot-repeated-value]=\"isRepeatedDimensionValue(i, column.name)\"\n [class.pivot-group-start]=\"isPivotGroupStart(i, column.name)\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [class.aggregation]=\"!!column.aggregationFunction\" [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\" [value]=\"pivotRow[column.name]\"\n [column]=\"column\" [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\"\n [isEditable]=\"isEditable()\" [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\"\n [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n } @else {\n <td [style.height.px]=\"dataRowHeight()\" [attr.colspan]=\"getLeafColumns().length\"> </td>\n }\n </tr>\n </tbody>\n </table>\n </cdk-virtual-scroll-viewport>\n\n </div>\n @if (freezeGrandTotal() && grandTotalPosition() === 'after') {\n <div #gtScroller class=\"header-shell gt-shell\" [class.adjust-bottom]=\"!applyCdkWidth()\"\n [class.adjust-bottom-vs]=\"adjustScrollWidth()\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n\n </table>\n </div>\n }\n\n\n </div>\n </div>\n </ng-container>\n } @else if (isBoardMode()) {\n <!-- Board Mode Template -->\n <div class=\"board-view-container\">\n @if(showSortBar()) {\n <div class=\"board-sort-bar\">\n <span class=\"board-sort-label\">Sort by:</span>\n @for (entry of gridStore.sortColumns(); track getFieldName(entry)) {\n <span class=\"board-sort-chip board-sort-chip-active\">\n <span class=\"board-sort-chip-label\">{{getColumnLabel(entry)}}</span>\n <span class=\"board-sort-chip-arrow\" (click)=\"onBoardSortChipToggle($event, entry)\">\n @if(!entry.startsWith('-')) { \u25B2 } @else { \u25BC }\n </span>\n @if(gridStore.sortColumns().length > 1) {\n <span class=\"board-sort-chip-priority\">{{getSortPriority(getFieldName(entry))}}</span>\n }\n <span class=\"board-sort-chip-remove\" (click)=\"onBoardSortChipRemove($event, entry)\">\u2715</span>\n </span>\n }\n <button class=\"board-sort-add-btn\" [matMenuTriggerFor]=\"sortFieldMenu\">\n <mat-icon class=\"board-sort-add-icon\">add</mat-icon> Add field\n </button>\n <mat-menu #sortFieldMenu=\"matMenu\" class=\"board-sort-menu\">\n @for (column of columns(); track column.name) {\n <button mat-menu-item (click)=\"onBoardSortFieldSelect(column)\"\n [disabled]=\"getSortDirection(column.name) !== null\">\n @if(getSortDirection(column.name) !== null) {\n <mat-icon>check</mat-icon>\n } @else {\n <mat-icon></mat-icon>\n }\n {{column.label}}\n </button>\n }\n </mat-menu>\n @if(gridStore.sortColumns().length > 0) {\n <button class=\"board-sort-clear\" (click)=\"onBoardSortClear()\">\u2715 Clear</button>\n }\n </div>\n }\n <div class=\"board-columns-wrapper\">\n @for (group of groups(); track group.id) {\n <div class=\"board-column\" [class.board-column-accented]=\"!!boardGroupColor(group)\"\n [style.--board-group-color]=\"boardGroupColor(group)\">\n <div class=\"column-header\">\n <!-- Render the group value through the same read-only cell renderer a\n data cell uses, so the grouped field's datatype formats itself\n (status/tag pills, dates, numbers) instead of printing raw text. -->\n @if (groupByColumn(); as gcol) {\n <span class=\"column-header-title column-header-title-cell\">\n <data-cell\n [eruGridStore]=\"gridStore\"\n [column]=\"gcol\"\n [columnDatatype]=\"gcol.datatype\"\n [columnName]=\"gcol.name\"\n [value]=\"group.title\"\n [id]=\"'board-group-' + group.id\"\n [fieldSize]=\"0\"\n [isEditable]=\"false\"\n [mode]=\"'board-group-header'\">\n </data-cell>\n </span>\n } @else {\n <span class=\"column-header-title\">{{ group.title }}</span>\n }\n <span class=\"column-header-count\">{{ group.currentLoadedRows || 0 }} of {{ group.totalRowCount || 0 }}</span>\n </div>\n <cdk-virtual-scroll-viewport [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n (scrolledIndexChange)=\"onBoardScrolledIndexChange($event, group)\">\n <div\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); templateCacheSize: 0\"\n class=\"board-card-container\"\n [class.selected]=\"isBoardCardActive(row)\"\n [class.show-row-lines]=\"showRowLines()\"\n [style.height.px]=\"boardCardOuterHeight\"\n [style.padding.px]=\"boardCardPadding\"\n [style.marginBottom.px]=\"boardCardGap\"\n [style.cursor]=\"cursorOnHover() || null\"\n (click)=\"emitRowSelect(row, 'board', group)\">\n <!-- Custom template when consumer provides boardCardTemplate; default card otherwise -->\n <ng-container\n *ngTemplateOutlet=\"boardCardTemplate ?? defaultBoardCard;\n context: { $implicit: row, columns: visibleBoardFields(), group: group }\">\n </ng-container>\n </div>\n </cdk-virtual-scroll-viewport>\n @if (group.isLoading) {\n <div class=\"board-ghost-card\">\n <div class=\"board-ghost-line\"></div>\n <div class=\"board-ghost-line board-ghost-line--short\"></div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else {\n\n <!-- Table Mode Template -->\n <ng-template #excelDownloadIcon>\n <svg class=\"excel-download-icon\" title=\"Download Excel\" (click)=\"onExcelDownloadClick($event)\"\n xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"24px\" height=\"24px\">\n <path fill=\"#169154\" d=\"M29,6H15.744C14.781,6,14,6.781,14,7.744v7.259h15V6z\" />\n <path fill=\"#18482a\" d=\"M14,33.054v7.202C14,41.219,14.781,42,15.743,42H29v-8.946H14z\" />\n <path fill=\"#0c8045\" d=\"M14 15.003H29V24.005000000000003H14z\" />\n <path fill=\"#17472a\" d=\"M14 24.005H29V33.055H14z\" />\n <g>\n <path fill=\"#29c27f\" d=\"M42.256,6H29v9.003h15V7.744C44,6.781,43.219,6,42.256,6z\" />\n <path fill=\"#27663f\" d=\"M29,33.054V42h13.257C43.219,42,44,41.219,44,40.257v-7.202H29z\" />\n <path fill=\"#19ac65\" d=\"M29 15.003H44V24.005000000000003H29z\" />\n <path fill=\"#129652\" d=\"M29 24.005H44V33.055H29z\" />\n </g>\n <path fill=\"#0c7238\"\n d=\"M22.319,34H5.681C4.753,34,4,33.247,4,32.319V15.681C4,14.753,4.753,14,5.681,14h16.638 C23.247,14,24,14.753,24,15.681v16.638C24,33.247,23.247,34,22.319,34z\" />\n <path fill=\"#fff\"\n d=\"M9.807 19L12.193 19 14.129 22.754 16.175 19 18.404 19 15.333 24 18.474 29 16.123 29 14.013 25.07 11.912 29 9.526 29 12.719 23.982z\" />\n </svg>\n </ng-template>\n @if(showExcelDownload() && !showGroupBar()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\n <!-- Scrollable groups container \u2014 plain iteration avoids CDK fixed-height estimation errors -->\n <div #groupsScrollContainer class=\"groups-scroll-container\" (scroll)=\"onGroupsViewportScroll($event)\">\n\n @for (group of groups(); track trackByGroupFn($index, group); let i = $index) {\n <div class=\"group-container\"\n [attr.data-group-id]=\"group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id\">\n <!-- Combined sticky header with group info and table -->\n <div style=\"\n background:var(--grid-surface);\n position: sticky;\n top: 0;\n z-index: 115;\n \">\n @if(showGroupBar()) {\n <div class=\"group-header-row\">\n <div class=\"custom-collapse-header\" (click)=\"toggleGroupCollapse(group.id)\">\n <span class=\"collapse-arrow\" [ngClass]=\"{\n 'rotate-arrow': group.isExpanded,\n }\">\u25BC</span>\n <span class=\"f-12\">\n {{ group?.title || \"\" }}\n {{ group?.currentLoadedRows || 0 }} -\n {{ group?.totalRowCount || 0 }} rows...</span>\n @if(groupByField() && isSortable()) {\n <span class=\"group-sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'asc'\"\n (click)=\"onGroupSortToggle($event, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\"\n [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'desc'\"\n (click)=\"onGroupSortToggle($event, 'desc')\"></span>\n </span>\n </span>\n }\n </div>\n @if(i === 0 && showExcelDownload()) {\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n }\n </div>\n }\n\n @if(freezeHeader() && (group.isExpanded || !showGroupBar())) {\n <div #headerScroller class=\"header-shell\" [attr.data-group-id]=\"'header-shell-' + group.id\"\n [style]=\"'--table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <table class=\"eru-grid-table\" [class.freeze-header]=\"freezeHeader()\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n <!-- Grand Total row after sticky header (position: before) - only for first group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' && hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after sticky header (position: before) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'before' && hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n </table>\n </div>\n }\n </div>\n @if(group.isExpanded || !showGroupBar()) {\n <ng-container>\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"dataRowHeight()\" class=\"viewport table-viewport\"\n (scrolledIndexChange)=\"onScroll($event, group)\" (scroll)=\"onTableBodyScroll($event)\"\n [style]=\"'--table-height: ' + getGroupContentHeight(group.id) + 'px; --table-min-height: ' + getGroupContentHeight(group.id) + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <div class=\"table-wrapper\">\n <table class=\"eru-grid-table\" [class.show-column-lines]=\"showColumnLines()\"\n [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n @if(!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n }\n <!-- Grand Total row after normal header (position: before) - only for first group -->\n @if(!freezeHeader() && enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' &&\n hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after normal header (position: before) -->\n @if(!freezeHeader() && enableColumnSubtotals() && subtotalPositionColumn() === 'before' &&\n hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n <tbody>\n @if (columns(); as columnsList) {\n <!-- <tr *ngIf=\"groupItem.type === 'table-header' && groups().length > 1\" style=\"background:#fafafa\">\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column\" style=\"text-align: center;\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupSelected(groupItem.group?.id || '')\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection($event, groupItem.group?.id || '')\"\n >\n </th>\n }\n <th *ngFor=\"let column of columns(); trackBy: trackByColumnFn;let i =index\"\n style=\"text-align: center;\"\n [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"true\"\n [columnConfig]=\"column\"\n [columnDraggable]=\"i\"\n class=\"column-header\">\n <div class=\"column-drag-handle\"></div>\n {{column.label}} {{column.symbol}}\n </th>\n </tr> -->\n <!-- @if(getRowsForGroup(group.id).length > 0 && group.isExpanded) { -->\n <!-- *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id)(); \n trackBy: trackByRowFn; \n let i = index\" -->\n <!-- @for(row of getRowsForGroupSignal(group.id)(); track trackByRowFn($index, row); let i = $index) { -->\n <ng-container\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); trackBy: trackByRowFn; let i = index\">\n <tr class=\"row-item\" [attr.data-row-id]=\"i\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" (click)=\"emitRowSelect(row, 'table', group)\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\" style=\"text-align: center;\">\n <input type=\"checkbox\" [checked]=\"isRowSelected(row?.entity_id)\"\n (change)=\"toggleRowSelection($event, row)\">\n </td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\" (click)=\"toggleRowExpand(row, i, $event)\">\n <mat-icon class=\"row-expand-icon\" [class.expanded]=\"isRowExpanded(row, i)\">chevron_right</mat-icon>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td #cell [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"data-cell\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\"\n [matTooltipClass]=\"'error-message'\" [matTooltip]=\"datacell.error()?'Error: ' + datacell.error():''\"\n matTooltipPosition=\"below\">\n <div class=\"cell-content\">\n <data-cell #datacell [td]=cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\" [value]=\"(row?.['entity_data']?.[column.name] ?? row?.[column.name]) || ''\" [column]=\"column\"\n [mode]=\"mode()\" [isEditable]=\"isEditable() && column.editable !== false && column.editable !== 'false'\" [drillable]=\"column.enableDrilldown || false\"\n [id]=\"i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n </tr>\n @if(hasHiddenColumns() && isRowExpanded(row, i)) {\n <tr class=\"row-detail\">\n <td class=\"row-detail-cell\" [attr.colspan]=\"rowDetailColspan()\">\n <div class=\"row-detail-grid\">\n @for (hiddenCol of hiddenColumns(); track trackByColumnFn($index, hiddenCol)) {\n <div class=\"row-detail-field\">\n <span class=\"row-detail-label\">{{hiddenCol.label}}</span>\n <div class=\"row-detail-value\">\n <data-cell [fieldSize]=\"hiddenCol.field_size\" [columnDatatype]=\"hiddenCol.datatype\"\n [columnName]=\"hiddenCol.name\" [value]=\"(row?.['entity_data']?.[hiddenCol.name] ?? row?.[hiddenCol.name]) || ''\"\n [column]=\"hiddenCol\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [drillable]=\"hiddenCol.enableDrilldown || false\"\n [id]=\"'detail_' + i + '_' + hiddenCol.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </div>\n }\n </div>\n </td>\n </tr>\n }\n </ng-container>\n <!-- } -->\n <!-- } -->\n @if(group.isLoading && (group.isExpanded || !showGroupBar())) {\n @for(i of [].constructor(ghostRows()); let j = $index; track j) {\n <tr class=\"ghost-loading-row\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n </tr>\n }\n }\n <!-- <tr\n *ngIf=\"getRowsForGroup(group.id).length === 0 && !group.isExpanded\"\n class=\"group-separator\"\n >\n <td [attr.colspan]=\"groupSeperatorColSpan()\" class=\"separator-cell\"></td>\n </tr> -->\n <!-- Subtotal row at end of group (position: after) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'after' && hasSubtotalData(group)) {\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n }\n <!-- Grand Total row at end of group (position: after) - only for last group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'after' && hasGrandTotalData() && i ===\n groups().length - 1) {\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n }\n }\n </tbody>\n </table>\n </div>\n </cdk-virtual-scroll-viewport>\n </ng-container>\n }\n </div>\n }\n </div>\n }\n</div>\n\n<!-- Pivot Table Header Template -->\n<ng-template #pivotTableHead>\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n @if (hasNestedHeaders()) {\n <ng-container>\n @for (headerRow of getHeaderRows(); track headerRow; let rowIndex = $index) {\n <tr class=\"pivot-header pivot-header-container\" [class.pivot-header-level]=\"'level-' + rowIndex\">\n @for (header of headerRow; track trackByHeaderFn($index, header); let colIndex = $index) {\n <th [attr.colspan]=\"header.colspan\" [attr.rowspan]=\"header.rowspan\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable') && header.level === 0 && !isRowDimensionHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header)\" class=\"column-header pivot-column-header nested-header\"\n [class.row-dimension-header]=\"isRowDimensionHeader(header)\"\n [class.column-dimension-header]=\"!isRowDimensionHeader(header)\" [class.expanded]=\"header.isExpanded\"\n [class.collapsed]=\"!header.isExpanded\" [class.sticky-column]=\"isStickyColumn(header.name, colIndex)\"\n [style.position]=\"isStickyColumn(header.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(header.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(header.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto; padding: 8px 6px;\">\n <div class=\"header-content\">\n\n <data-cell [fieldSize]=\"header.field_size\" [columnDatatype]=\"header.dataType\" [columnName]=\"header.name\"\n [value]=\"header.label\" [column]=\"header\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"header.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + $index + '_' + header.name\" [eruGridStore]=\"gridStore\" [row]=\"header\">\n </data-cell>\n <!-- <span class=\"header-label header-wrap-text\">{{header.label}}</span> -->\n <!-- <button *ngIf=\"!isRowDimensionHeader(header)\"\n class=\"collapse-toggle-btn\"\n [title]=\"header.isExpanded ? 'Collapse group' : 'Expand group'\"\n (click)=\"toggleColumnGroup(header.groupKey)\"\n type=\"button\">\n <span class=\"collapse-icon\">+</span>\n </button> -->\n </div>\n </th>\n }\n </tr>\n }\n </ng-container>\n } @else {\n <!-- Simple header fallback -->\n <ng-container>\n <tr class=\"pivot-header\" [class.freeze-header-enabled]=\"freezeHeader()\">\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <th [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\" [columnConfig]=\"column\"\n class=\"column-header pivot-column-header\" [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto;padding: 8px 6px\">\n {{column.label}}\n </th>\n }\n </tr>\n </ng-container>\n }\n\n </thead>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #pivotColGroup>\n <colgroup>\n @for (column of getLeafColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n </colgroup>\n</ng-template>\n\n<ng-template #pivotGrandTotal>\n <tbody class=\"pivot-tbody\">\n @for (pivotRow of gridStore.pivotGrandTotalData(); track trackByPivotRowFn($index, pivotRow); let i = $index) {\n <tr class=\"pivot-row grand-total-row\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"50\" [attr.data-pivot-row]=\"i\">\n <!-- <td colspan=\"20\">{{pivotRow | json}}</td> -->\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [attr.rowspan]=\"getEffectiveRowspan(i, column.name)\" [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.rowspan-cell]=\"getEffectiveRowspan(i, column.name) || 1 > 1\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"50\" [attr.xx]=\"i\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getEffectiveCellValue(i,column.name, pivotRow)\" [column]=\"column\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #tableColGroup>\n <colgroup>\n @if(gridStore.configuration().config.allowSelection) {\n <col style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n }\n @if(shouldShowActionColumn('before')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n @if(hasHiddenColumns()) {\n <col style=\"width: 40px !important; min-width: 40px !important; max-width: 40px !important;\">\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n @if(shouldShowActionColumn('after')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <tr>\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column column-header table-column-header\">\n <input type=\"checkbox\" [checked]=\"isAllGroupsSelected()\" (change)=\"toggleAllGroups($event)\">\n </th>\n }\n @if(shouldShowActionColumn('before')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n @if(hasHiddenColumns()) {\n <th class=\"row-expand-toggle column-header table-column-header\"></th>\n }\n @for (column of visibleColumns(); track trackByColumnFn(i, column); let i = $index) {\n <th [style.width.px]=\"column.field_size\" [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\"\n [columnConfig]=\"column\" [index]=\"i\"\n [columnDraggable]=\"gridStore.isFeatureEnabled('columnReorderable') ? i : null\"\n [style.minWidth.px]=\"column.field_size\" class=\"column-header table-column-header\"\n [class.sortable-header]=\"isSortable()\"\n [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === column.name\"\n [class.sort-asc]=\"isSortable() && getSortDirection(column.name) === 'asc'\"\n [class.sort-desc]=\"isSortable() && getSortDirection(column.name) === 'desc'\">\n @if(gridStore.isFeatureEnabled('columnReorderable')) {\n <div class=\"column-drag-handle\"></div>\n }\n <span class=\"column-label\" [title]=\"column.tool_tip || column.description || ''\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\" title=\"Edit column\" (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n @if(isSortable()) {\n <span class=\"sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'asc'\"\n (click)=\"onSortColumn($event, column, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'desc'\"\n (click)=\"onSortColumn($event, column, 'desc')\"></span>\n </span>\n @if(getSortPriority(column.name) !== null && gridStore.sortColumns().length > 1) {\n <span class=\"sort-priority\">{{getSortPriority(column.name)}}</span>\n }\n </span>\n }\n </th>\n }\n @if(shouldShowActionColumn('after')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n </tr>\n </thead>\n</ng-template>\n\n<!-- Table Subtotal Row Template -->\n<ng-template #tableSubtotal let-group=\"group\">\n <tr class=\"subtotal-row\" [class.subtotal-bold]=\"subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"subTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"subtotal-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getSubtotalValue(group, column.name) === null) {\n <span class=\"subtotal-label\">{{subtotalLabel()}}</span>\n } @else {\n @if(getSubtotalValue(group, column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getSubtotalValue(group, column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'subtotal_' + group.id + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"group.subtotal\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- Table Grand Total Row Template -->\n<ng-template #tableGrandTotal>\n <tr class=\"grand-total-row\" [class.grand-total-bold]=\"grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"grandTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"grand-total-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getGrandTotalValue(column.name) === null) {\n <span class=\"grand-total-label\">Grand Total</span>\n } @else {\n @if(getGrandTotalValue(column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getGrandTotalValue(column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'grandtotal_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"gridStore.rowGrandTotal()\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- \u2500\u2500\u2500 Default board card template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Used when no boardCardTemplate is passed to <eru-grid>.\n Context: { $implicit: Row, columns: Field[], group: RowGroup }\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<ng-template #defaultBoardCard let-row let-columns=\"columns\" let-group=\"group\">\n <mat-card class=\"board-card\">\n <mat-card-content>\n @for (column of columns; track column.name) {\n @if ((row?.entity_data?.[column.name] ?? row?.[column.name]) !== undefined) {\n <div class=\"board-card-field\">\n <span class=\"board-field-label\">{{ column.label }}</span>\n <data-cell\n [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\"\n [column]=\"column\"\n [value]=\"row?.entity_data?.[column.name] ?? row?.[column.name]\"\n [id]=\"row?.entity_id + '_' + column.name\"\n [eruGridStore]=\"gridStore\"\n [mode]=\"'board'\"\n [row]=\"row\">\n </data-cell>\n </div>\n }\n }\n </mat-card-content>\n <mat-card-actions align=\"end\">\n <button mat-icon-button (click)=\"onActionClick($event, row)\">\n <mat-icon>more_horiz</mat-icon>\n </button>\n </mat-card-actions>\n </mat-card>\n</ng-template>", styles: ["@charset \"UTF-8\";:root{--grid-primary: #6750a4;--grid-on-primary: #ffffff;--grid-surface: #fef7ff;--grid-surface-variant: #e7e0ec;--grid-surface-container: #f3edf7;--grid-surface-container-high: #ede7f0;--grid-on-surface: #1d1b20;--grid-on-surface-variant: #49454f;--grid-outline: #79757f;--grid-outline-variant: #cac4d0;--grid-error: #ba1a1a;--grid-error-container: #ffdad6}:host,eru-grid{display:block!important;width:100%;height:100%;flex:1 1 0%;min-height:var(--grid-height, 300px);font-family:var(--grid-font-family);--grid-font-family: \"Poppins\", \"Roboto\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;--grid-font-size-body: 12px;--grid-font-size-caption: 12px !important;--grid-line-height-body: 1;--grid-aggregation-text-align: right;--grid-number-text-align: right;--grid-spacing-xxs: 2px;--grid-spacing-xs: 4px;--grid-spacing-sm: 8px;--grid-spacing-md: 16px;--grid-spacing-lg: 24px;--grid-border-radius: 4px;--grid-elevation-1: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 1px 3px 1px rgba(0, 0, 0, .15);--grid-elevation-2: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 2px 6px 2px rgba(0, 0, 0, .15);--grid-row-hover: var(--grid-surface-variant);--grid-row-selected: var(--grid-surface-container-high);--grid-zebra-odd: transparent;--grid-zebra-even: transparent;--grid-focus-ring: var(--grid-primary);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: var(--grid-font-size-caption);--grid-header-padding-x: 8px;--grid-header-padding-y: 12px;--grid-font-feature-numeric: normal;--grid-cell-padding-x: var(--grid-spacing-xs);--grid-cell-padding-y: var(--grid-spacing-xxs);--grid-tint-subtle: rgba(0, 0, 0, .025);--grid-tint-soft: rgba(0, 0, 0, .045);--grid-tint-strong: rgba(0, 0, 0, .08);--grid-radius-outer: 0;--grid-shadow-outer: none;--grid-divider-color: var(--grid-outline-variant);--grid-divider-width: 1px;--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-pill-radius: 999px;--grid-pill-padding-y: 3px;--grid-pill-padding-x: 10px;--grid-pill-font-size: 11px;--grid-pill-font-weight: 500;--grid-priority-dot-size: 8px;--grid-avatar-size: 24px;--grid-avatar-font-size: 10px;--grid-avatar-font-weight: 600;border-radius:var(--grid-radius-outer);box-shadow:var(--grid-shadow-outer)}eru-grid[data-preset=default]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .06em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-soft);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=modern]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: 13px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 16px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 12px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px}eru-grid[data-preset=compact]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 11px;--grid-header-padding-y: 4px;--grid-header-padding-x: 8px;--grid-cell-padding-y: 3px;--grid-cell-padding-x: 8px;--grid-font-size-body: 11px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 1px;--grid-pill-padding-x: 6px;--grid-pill-font-size: 10px}eru-grid[data-preset=bold]{--grid-header-bg: var(--grid-surface-container-high);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 700;--grid-header-text-transform: none;--grid-header-font-size: 13px;--grid-header-padding-y: 14px;--grid-header-padding-x: 12px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 12px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-strong);--grid-divider-width: 1px;--grid-radius-outer: 2px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=financial]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .08em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-zebra-odd: transparent;--grid-zebra-even: var(--grid-tint-subtle);--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=elevated]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 12px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 14px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 16px;--grid-shadow-outer: 0 1px 3px rgba(0, 0, 0, .06), 0 10px 28px rgba(0, 0, 0, .07);--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px;overflow:hidden}.group-container{padding-bottom:8px}.column-header.design-clickable .design-edit-icon{font-size:16px;width:16px;height:16px;margin-left:4px;opacity:.45;vertical-align:middle;cursor:pointer}.column-header.design-clickable:hover .design-edit-icon,.column-header.design-clickable .design-edit-icon:hover{opacity:1}.column-header.design-selected{background-color:var(--grid-primary-light, rgba(63, 81, 181, .12))}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-height, 300px);max-height:none;overflow:auto;position:relative;background-color:var(--grid-surface);border-radius:var(--grid-border-radius);font-family:var(--grid-font-family)}.viewport{height:100%;min-height:300px;overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface);scrollbar-gutter:stable}.viewport.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.groups-viewport{height:100%;min-height:300px}.groups-scroll-container{height:var(--grid-height, 600px);overflow-y:auto;overflow-x:hidden}.table-viewport{background-color:var(--grid-surface);height:var(--table-height, auto);min-height:var(--table-min-height, 100px);overflow-x:auto;overflow-y:auto}.pivot-viewport{min-height:var(--table-min-height, 300px);overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface)}.pivot-viewport .cdk-virtual-scroll-content-wrapper{width:auto;height:auto}.table-wrapper{min-width:100%;overflow-x:visible}.incremental-row-container .eru-grid-table,.eru-grid-table{width:100%!important;border-collapse:separate;border-spacing:0;table-layout:fixed!important;background-color:var(--grid-surface);color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);line-height:var(--grid-line-height-body)}.eru-grid-table th,.eru-grid-table td{text-align:left;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:var(--grid-on-surface);min-width:0;max-width:100%!important;box-sizing:border-box;position:relative}.eru-grid-table th{background-color:var(--grid-header-bg, var(--grid-surface-container))}thead.eru-wrap-headers th{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;height:auto}thead.eru-wrap-headers th .column-label,thead.eru-wrap-headers th .header-label{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;word-break:break-word;overflow-wrap:anywhere}.eru-grid-table tbody td{background-color:transparent}.eru-grid-table thead{background-color:var(--grid-header-bg, var(--grid-surface-container));transform:translateZ(0);will-change:transform;backface-visibility:hidden}.eru-grid-table thead.freeze-header-enabled{position:sticky!important;top:0!important;z-index:100!important}.eru-grid-table thead th{background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface));font-family:var(--grid-font-family);font-weight:var(--grid-header-font-weight);font-size:var(--grid-header-font-size)}.checkbox-column{width:50px;min-width:50px;max-width:50px;text-align:center;background-color:var(--grid-surface-container)}.checkbox-column input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--grid-primary);border-radius:var(--grid-border-radius)}.checkbox-column input[type=checkbox]:focus{outline:2px solid var(--grid-primary);outline-offset:2px}.action-column{width:40px;min-width:40px;max-width:40px;text-align:center;background-color:var(--grid-surface-container)}.action-column mat-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);cursor:pointer}.action-column mat-icon:hover{color:var(--grid-primary)}.group-header{background-color:var(--grid-surface-container);color:var(--grid-on-surface);font-size:var(--grid-font-size-caption);font-weight:500;border-bottom:1px solid var(--grid-outline);cursor:pointer;transition:background-color .2s ease}.group-header:hover{background-color:var(--grid-surface-container-high)}.group-header .group-title{font-weight:600;color:var(--grid-primary)}.group-header .group-row-count{color:var(--grid-on-surface-variant);font-size:var(--grid-font-size-caption);margin-left:var(--grid-spacing-sm)}.row-item{background-color:var(--grid-surface);transition:background-color .15s ease}.row-item:nth-child(odd){background-color:var(--grid-zebra-odd, var(--grid-surface))}.row-item:nth-child(2n){background-color:var(--grid-zebra-even, var(--grid-surface))}.row-item:hover{background-color:var(--grid-row-hover)}.required-toggle-row{background-color:var(--grid-surface-container, #f3edf7);border-bottom:1px solid var(--grid-outline-variant, #cac4d0)}.required-toggle-row .required-toggle-cell{padding:4px 8px!important;text-align:center;vertical-align:middle;position:relative}.required-toggle-row .required-toggle-cell .required-label{position:absolute;top:2px;left:4px;font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;text-transform:lowercase}.required-toggle-row .required-toggle-cell mat-checkbox{display:flex;justify-content:center;align-items:center}.table-column-header{padding:var(--grid-header-padding-y) var(--grid-header-padding-x)}.column-header{font-weight:var(--grid-header-font-weight);text-transform:var(--grid-header-text-transform);letter-spacing:var(--grid-header-letter-spacing);text-align:center!important;font-size:var(--grid-header-font-size);position:relative;-webkit-user-select:none;user-select:none;background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface))}.column-header:hover{background-color:var(--grid-header-hover-bg, var(--grid-surface-container-high))}.column-drag-handle{position:absolute;left:0;top:0;bottom:0;width:12px;cursor:grab;opacity:0;transition:opacity .2s ease,background-color .2s ease;z-index:2;display:flex;align-items:center;justify-content:center;border-right:1px solid transparent}.column-drag-handle:after{content:\"\\22ee\\22ee\";font-size:14px;color:var(--grid-on-surface-variant);transform:rotate(90deg)}.column-drag-handle:hover{background-color:var(--grid-surface-container-high);border-right-color:var(--grid-outline)}.column-header:hover .column-drag-handle{opacity:1}.column-drag-handle:active{cursor:grabbing}.sortable-header{cursor:pointer}.sortable-header .column-label{flex:1}.sortable-header .sort-indicator{display:inline-flex;align-items:center;gap:2px;margin-left:4px;cursor:pointer;vertical-align:middle;opacity:0;transition:opacity .15s ease}.sortable-header .sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.sortable-header .sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.sortable-header .sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-active{opacity:1}.sortable-header .sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-priority{font-size:9px;font-weight:600;color:var(--grid-primary, #6750a4);line-height:1;min-width:12px;text-align:center}.sortable-header:hover .sort-indicator,.sortable-header.sort-asc .sort-indicator,.sortable-header.sort-desc .sort-indicator{opacity:1}.sortable-header:hover .sort-indicator .sort-tri:not(.sort-tri-active){opacity:.6}.sort-asc,.sort-desc{background-color:var(--grid-surface-container-low, rgba(103, 80, 164, .04))}.dragging{opacity:1;background-color:var(--grid-surface-container);box-shadow:var(--grid-elevation-2)}.drag-over{background-color:var(--grid-surface-container);border-color:var(--grid-primary)}.data-cell{background-color:transparent;color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);font-feature-settings:var(--grid-font-feature-numeric);padding:var(--grid-cell-padding-y) var(--grid-cell-padding-x)}.cell-content{align-items:center}.cell-content .mdc-text-field{padding:0px var(--grid-spacing-xxs)!important}.cell-display-text{align-items:center;padding:0px var(--grid-spacing-xs)}.ghost-loading-row{background-color:transparent}.ghost-cell-container{padding:var(--grid-spacing-sm)}.ghost-cell{height:20px;width:100%;background-color:var(--grid-surface-container);animation:pulse 1.5s ease-in-out infinite;border-radius:var(--grid-border-radius)}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.column-resizer{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;background-color:transparent;transition:background-color .2s ease}.column-resizer:hover{background-color:var(--grid-primary)}.group-separator{height:var(--grid-spacing-sm);background-color:var(--grid-surface-variant)}.group-separator .separator-cell{background-color:var(--grid-surface-variant);border:none;height:var(--grid-spacing-sm)}.error-state{background-color:var(--grid-error-container);color:var(--grid-error);border-color:var(--grid-error)}.error-message{background-color:var(--grid-error);color:#fff;padding:var(--grid-spacing-sm);border-radius:var(--grid-border-radius);font-size:var(--grid-font-size-caption)}.incremental-row-container .eru-grid-table tbody,.incremental-row-container .eru-grid-table{position:relative}.incremental-row-container .eru-grid-table.show-column-lines{border-right:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important;border-top:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table.show-column-lines:not(.freeze-header){border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table:not(.show-column-lines){border:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table thead:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:calc(var(--grid-divider-width, 1px) * 2);background-color:var(--grid-divider-color, var(--grid-outline, #e0e0e0));pointer-events:none;z-index:10}.incremental-row-container .eru-grid-table.show-column-lines thead th,.incremental-row-container .eru-grid-table.show-column-lines tbody td{border-left:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}.incremental-row-container .eru-grid-table.show-row-lines thead th,.incremental-row-container .eru-grid-table.show-row-lines tbody td{border-bottom:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}@media(max-width:768px){.incremental-row-container{height:600px}.eru-grid-table th,.eru-grid-table td{font-size:var(--grid-font-size-caption)}.checkbox-column{width:40px;min-width:40px;max-width:40px}}@media(prefers-contrast:high){.eru-grid-table th,.eru-grid-table td{border-width:2px}.row-item:hover{border-width:2px;border-color:var(--grid-primary)}}@media(prefers-reduced-motion:reduce){.row-item,.column-drag-handle,.ghost-cell{transition:none;animation:none}}.pivot-table .nested-header{text-align:center;font-weight:600;background:var(--grid-surface-container)}.pivot-table .nested-header.row-dimension-header{background:var(--grid-surface-container);font-weight:600}.pivot-table .pivot-header-leafcols{padding:0;margin:0;height:0}.pivot-table .pivot-header-level.level-0 .nested-header{font-size:14px;padding:12px 8px}.pivot-table .pivot-header-level.level-1 .nested-header{font-size:13px;padding:10px 6px}.pivot-table .pivot-header-level.level-2 .nested-header{font-size:12px;padding:8px 4px}.pivot-table .nested-header:hover{background:var(--grid-surface-variant);color:var(--grid-primary);transition:all .2s ease}.pivot-table .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-table .pivot-cell-content{display:flex;justify-content:center;align-items:center;min-height:38px}.pivot-table .pivot-repeated-value .cell-content,.pivot-table .pivot-repeated-value .pivot-cell-content{visibility:hidden}.pivot-table .pivot-group-start.row-dimension-cell{border-top:1px solid var(--grid-outline, #79757f)}.pivot-mode .incremental-row-container{display:flex;flex-direction:column;height:auto;max-height:85vh;overflow:auto}.pivot-mode .h-shell{position:relative;width:calc(100% - var(--scrollbar-width, 17px))!important;top:0;z-index:1;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .h-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell{position:relative;bottom:50px;flex-shrink:0;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .gt-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell table{border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.pivot-mode .gt-shell.adjust-bottom-vs{bottom:66px!important}.pivot-mode .gt-shell.adjust-bottom:not(.adjust-bottom-vs){bottom:calc(66px - var(--scrollbar-width, 17px))!important}.pivot-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .header-shell::-webkit-scrollbar{display:none}.pivot-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.pivot-mode .header-shell .eru-grid-table{margin-bottom:0;width:100%;table-layout:fixed}.pivot-mode .header-shell .eru-grid-table thead{background:var(--grid-surface-container)}.pivot-mode .header-shell .eru-grid-table thead th{background:var(--grid-surface-container);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .header-shell .eru-grid-table thead th.sticky-column{position:sticky;background:var(--grid-surface-container);z-index:111}.pivot-mode .header-shell .eru-grid-table tbody td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.pivot-mode .pivot-table{width:auto!important;min-width:100%!important;table-layout:fixed!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important}.pivot-mode .pivot-table td,.pivot-mode .pivot-table th{box-sizing:border-box!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-table{table-layout:fixed!important;width:100%!important}.pivot-mode .pivot-table *{max-width:var(--col-width)!important;box-sizing:border-box!important}.pivot-mode .pivot-table colgroup{width:100%!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex-basis:var(--col-width)!important;flex:0 0 var(--col-width)!important}.pivot-mode .pivot-table table{width:100%!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important}.pivot-mode .pivot-table[style*=--table-total-width]{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:0 0 var(--col-width)!important;flex-basis:var(--col-width)!important;flex-grow:0!important;flex-shrink:0!important;overflow:hidden!important}.pivot-mode .pivot-table tbody td,.pivot-mode .pivot-table thead th{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.pivot-mode .pivot-table .cell-content,.pivot-mode .pivot-table data-cell{width:100%!important;max-width:100%!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;display:block!important}.pivot-mode .pivot-table table{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-tbody tr.pivot-row{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important}.pivot-mode .pivot-tbody tr.pivot-row:hover{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-tbody tr.pivot-row:nth-child(2n){background-color:#00000005}.pivot-mode .pivot-tbody tr.pivot-row td{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content{min-height:calc(var(--grid-data-row-height, 50px) - 2px);display:flex;align-items:center;justify-content:center}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content data-cell{width:100%;min-height:calc(var(--grid-data-row-height, 50px) - 4px);display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0}.pivot-mode .pivot-cell{vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-mode .pivot-cell .cell-content{display:flex;justify-content:center;align-items:center;min-height:var(--grid-header-row-height, 40px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.pivot-mode .pivot-table .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.pivot-mode .pivot-table .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .subtotal-row td:first-child{color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row td.aggregated-value{font-weight:500;color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.pivot-mode .pivot-table .subtotal-bold td{font-weight:600!important;font-style:normal!important}.pivot-mode .pivot-table .subtotal-bold td.aggregated-value{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td{font-style:italic!important}.pivot-mode .pivot-table .subtotal-italic td:first-child{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.pivot-mode .pivot-table .subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted td.aggregated-value{font-weight:500!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted:hover,.pivot-mode .pivot-table .subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700;font-size:var(--grid-font-size-body)}.pivot-mode .pivot-table .grand-total-row td{background-color:var(--grid-surface-container-high)!important;color:var(--grid-on-surface)}.pivot-mode .pivot-table .grand-total-row td:first-child{font-style:normal;font-weight:800;color:var(--grid-primary)}.pivot-mode .pivot-table .grand-total-row td.aggregated-value{font-weight:500;color:var(--grid-primary);font-family:Roboto Mono,monospace}.pivot-mode .pivot-table .grand-total-row:hover,.pivot-mode .pivot-table .grand-total-row:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-bold td{font-weight:700!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-bold td.aggregated-value{font-weight:700!important}.pivot-mode .pivot-table .grand-total-italic td,.pivot-mode .pivot-table .grand-total-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted{background-color:var(--grid-primary)!important;box-shadow:var(--grid-elevation-2)!important}.pivot-mode .pivot-table .grand-total-highlighted td{background-color:var(--grid-primary)!important;color:var(--grid-on-primary)!important;font-weight:500!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-highlighted td.aggregated-value{color:var(--grid-on-primary)!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted:hover,.pivot-mode .pivot-table .grand-total-highlighted:hover td{background-color:var(--grid-primary)!important}.pivot-mode .pivot-table .collapsible-header{position:relative}.pivot-mode .pivot-table .collapsible-header .header-content{display:flex;align-items:center;justify-content:space-between;gap:var(--grid-spacing-xs);padding:var(--grid-spacing-xs) var(--grid-spacing-sm)}.pivot-mode .pivot-table .collapsible-header .header-label{flex:1;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn{background:none;border:none;cursor:pointer;padding:var(--grid-spacing-xxs);margin:0;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--grid-border-radius);color:var(--grid-on-surface-variant);transition:all .2s ease;font-size:12px;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:hover{background-color:var(--grid-surface-container);color:var(--grid-primary);transform:scale(1.1)}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:focus{outline:2px solid var(--grid-primary);outline-offset:1px}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn .collapse-icon{display:block;line-height:1;font-family:monospace;font-size:14px}.pivot-mode .pivot-table .collapsible-header.expanded .collapse-toggle-btn .collapse-icon{color:var(--grid-primary)}.pivot-mode .pivot-table .collapsible-header.collapsed{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .header-label{font-style:italic;color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .collapse-toggle-btn .collapse-icon{color:var(--grid-outline)}.pivot-mode .pivot-table .collapsible-header:hover{background-color:var(--grid-surface-container)}.pivot-mode .pivot-table .collapsible-header:hover .header-label{color:var(--grid-on-surface)}.pivot-mode .pivot-table .pivot-single-table{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden;min-height:var(--table-min-height)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container{flex-shrink:0;background:var(--grid-surface)!important;overflow-x:auto;overflow-y:hidden;min-height:100px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table{width:auto;min-width:100%;height:auto!important;min-height:100px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th{background:var(--grid-surface-container)!important;padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:40px!important;height:auto!important;position:relative;visibility:visible!important;color:var(--grid-on-surface)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:101!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container{flex:1;overflow:auto;min-height:300px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-viewport{height:100%!important;width:100%!important;overflow-x:auto!important;overflow-y:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table{width:auto;min-width:100%;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td{padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:32px!important;height:auto!important;background:var(--grid-surface)!important;color:var(--grid-on-surface)!important;visibility:visible!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td.sticky-column,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:100!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr{height:auto!important;min-height:50px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr.pivot-row,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr.pivot-row{visibility:visible!important;display:table-row!important}.pivot-mode .pivot-table .collapsed-column-group{background-color:var(--grid-surface-container);border-left:3px solid var(--grid-primary)}.pivot-mode .pivot-table .collapsed-column-group:hover{background-color:var(--grid-surface-container-high)}.pivot-row.subtotal-row{background-color:var(--grid-surface-variant);font-weight:500}.pivot-row.subtotal-row.subtotal-bold{font-weight:500}.pivot-row.subtotal-row.subtotal-italic{font-style:italic}.pivot-row.subtotal-row.subtotal-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.grand-total-row{background-color:var(--grid-surface-container);font-weight:600}.pivot-row.grand-total-row.grand-total-bold{font-weight:800}.pivot-row.grand-total-row.grand-total-italic{font-style:italic}.pivot-row.grand-total-row.grand-total-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.first-visible-row{background-color:#6750a41a!important;position:relative}.pivot-row.first-visible-row:before{content:\"\\1f441\\fe0f First Visible\";position:absolute;top:-20px;left:0;background:var(--grid-primary);color:var(--grid-on-primary);padding:2px 6px;font-size:10px;border-radius:2px;z-index:1000}.header-wrap-text{white-space:pre-wrap;word-break:auto-phrase}.group-header-row{display:flex;align-items:center;justify-content:space-between;width:100%;padding-right:12px}.custom-collapse-header{background-color:var(--grid-surface-variant);padding:8px 20px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:pointer;display:flex;width:fit-content;align-items:center;-webkit-user-select:none;user-select:none;min-width:200px;margin-bottom:10px;position:sticky;left:1px;z-index:116}.custom-collapse-header .collapse-arrow{display:inline-block;margin-right:8px;font-size:12px;color:var(--grid-on-surface-variant);transition:transform .2s ease;transform:rotate(0)}.custom-collapse-header .collapse-arrow.rotate-arrow{transform:rotate(270deg)}.custom-collapse-header .f-12{font-size:12px;color:var(--grid-on-surface)}.custom-collapse-header .group-sort-indicator{display:inline-flex;align-items:center;margin-left:8px}.custom-collapse-header .group-sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.custom-collapse-header .group-sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.custom-collapse-header .group-sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-active{opacity:1}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.excel-download-icon{cursor:pointer}.excel-download-icon:hover{opacity:.75}.excel-download-bar{display:flex;justify-content:flex-end;padding:4px 12px}.table-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .header-shell::-webkit-scrollbar{display:none}.table-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.table-mode .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.table-mode .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.table-mode .subtotal-row td:first-child{color:var(--grid-primary)}.table-mode .subtotal-row td.subtotal-cell{font-weight:500}.table-mode .subtotal-row td.subtotal-cell .subtotal-label{font-weight:600;color:var(--grid-primary)}.table-mode .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .subtotal-row.subtotal-bold td{font-weight:600!important;font-style:normal!important}.table-mode .subtotal-row.subtotal-italic td{font-style:italic!important}.table-mode .subtotal-row.subtotal-italic td:first-child{font-weight:600!important}.table-mode .subtotal-row.subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .subtotal-row.subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .subtotal-row.subtotal-highlighted:hover,.table-mode .subtotal-row.subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row-shell{width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .subtotal-row-shell::-webkit-scrollbar{display:none}.board-mode-host{overflow:hidden;display:flex;flex-direction:column;height:var(--grid-height, 600px)}.board-mode-host .board-view-container{display:flex;flex-direction:column;flex:1;min-height:0}.board-mode-host .board-sort-bar{display:flex;align-items:center;gap:6px;padding:8px 16px;flex-shrink:0;border-bottom:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);overflow-x:auto}.board-mode-host .board-sort-bar .board-sort-label{font-size:12px;font-weight:500;color:var(--grid-on-surface-variant, #49454f);white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);color:var(--grid-on-surface, #1d1b20);font-size:12px;white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip-active{background:var(--grid-surface-container);border-color:var(--grid-outline, #79757f);color:var(--grid-on-surface, #1d1b20)}.board-mode-host .board-sort-bar .board-sort-chip-label{pointer-events:none}.board-mode-host .board-sort-bar .board-sort-chip-arrow{font-size:10px;line-height:1;cursor:pointer;padding:2px;border-radius:4px}.board-mode-host .board-sort-bar .board-sort-chip-arrow:hover{background:#00000014}.board-mode-host .board-sort-bar .board-sort-chip-priority{font-size:9px;font-weight:700;background:var(--grid-primary, #6750a4);color:var(--grid-on-primary, #ffffff);border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center}.board-mode-host .board-sort-bar .board-sort-chip-remove{font-size:10px;cursor:pointer;padding:2px;border-radius:4px;color:var(--grid-on-surface-variant, #49454f)}.board-mode-host .board-sort-bar .board-sort-chip-remove:hover{background:#00000014;color:var(--grid-error, #b3261e)}.board-mode-host .board-sort-bar .board-sort-add-btn{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px dashed var(--grid-outline-variant, #cac4d0);background:transparent;color:var(--grid-on-surface-variant, #49454f);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease,border-color .15s ease}.board-mode-host .board-sort-bar .board-sort-add-btn .board-sort-add-icon{font-size:14px;width:14px;height:14px}.board-mode-host .board-sort-bar .board-sort-add-btn:hover{background:var(--grid-surface-container-low, #f7f2fa);border-color:var(--grid-primary, #6750a4);color:var(--grid-primary, #6750a4)}.board-mode-host .board-sort-bar .board-sort-clear{display:inline-flex;align-items:center;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-error, #b3261e);background:transparent;color:var(--grid-error, #b3261e);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease}.board-mode-host .board-sort-bar .board-sort-clear:hover{background:#b3261e14}.board-mode-host .board-columns-wrapper{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--board-col-min-width, 300px),1fr));grid-auto-rows:var(--board-column-height, 420px);gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start}.board-mode-host .board-column{height:var(--board-column-height, 420px);min-width:0;display:flex;flex-direction:column;background:var(--grid-surface-container, #f3edf7);border-radius:12px;min-height:0;overflow:hidden}.board-mode-host .board-column.board-column-accented{border-top:3px solid var(--board-group-color, transparent)}.board-mode-host .board-column .column-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;font-weight:600;flex-shrink:0}.board-mode-host .board-column .column-header:hover{background-color:transparent}.board-mode-host .board-column .column-header .column-header-title{font-size:15px;font-weight:700;letter-spacing:.2px;line-height:1.2;color:var(--grid-on-surface, #1d1b20);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.board-mode-host .board-column .column-header .column-header-title-cell{display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:clip}.board-mode-host .board-column .column-header .column-header-title-cell data-cell,.board-mode-host .board-column .column-header .column-header-title-cell .data-cell-component,.board-mode-host .board-column .column-header .column-header-title-cell .container{padding:0!important;margin:0!important;border:none!important;background:transparent!important;min-height:0!important;height:auto!important;width:auto!important;max-width:100%!important;overflow:visible!important}.board-mode-host .board-column .column-header .column-header-title-cell .status-display,.board-mode-host .board-column .column-header .column-header-title-cell .status-display-content,.board-mode-host .board-column .column-header .column-header-title-cell .status-text,.board-mode-host .board-column .column-header .column-header-title-cell .tag-display,.board-mode-host .board-column .column-header .column-header-title-cell .tag-text{max-width:none!important;overflow:visible!important;text-overflow:clip!important}.board-mode-host .board-column .column-header .column-header-count{font-size:10px;font-weight:600;color:var(--grid-on-surface-variant, #49454f);background:var(--grid-surface-variant, #e7e0ec);border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:1;min-height:0;height:0}.board-mode-host .board-card-container{box-sizing:border-box;overflow:hidden;border-radius:8px;transition:background-color .15s ease,box-shadow .15s ease}.board-mode-host .board-card-container.show-row-lines{box-shadow:inset 0 0 0 var(--grid-divider-width, 1px) var(--grid-divider-color, var(--grid-outline, #e0e0e0))}.board-mode-host .board-card-container:hover{background-color:var(--eru-board-card-hover-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 8%, transparent))}.board-mode-host .board-card-container.selected{background-color:var(--eru-board-card-selected-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 14%, transparent));box-shadow:inset 0 0 0 2px var(--eru-board-card-selected-outline, var(--mat-sys-primary, #1976d2))}.board-mode-host .board-card{height:calc(100% - 8px);overflow:hidden;cursor:pointer}.board-mode-host .board-card mat-card-title{font-size:13px}.board-mode-host .board-card mat-card-subtitle{font-size:12px}.board-mode-host .board-card-field{display:flex;flex-direction:column;margin-bottom:4px}.board-mode-host .board-field-label{font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.board-mode-host .board-ghost-card{margin:8px;padding:16px;background:var(--grid-surface, #fef7ff);border-radius:8px;animation:board-pulse 1.5s ease-in-out infinite}.board-mode-host .board-ghost-line{height:12px;background:var(--grid-surface-variant, #e7e0ec);border-radius:4px;margin-bottom:8px}.board-mode-host .board-ghost-line--short{width:60%}@keyframes board-pulse{0%,to{opacity:1}50%{opacity:.5}}th.row-expand-toggle,td.row-expand-toggle{width:40px!important;min-width:40px!important;max-width:40px!important;padding:0!important;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.row-expand-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);transition:transform .15s ease-in-out}.row-expand-icon.expanded{transform:rotate(90deg)}.row-detail{background:var(--grid-surface-container)}.row-detail .row-detail-cell{padding:var(--grid-spacing-sm) var(--grid-spacing-md);border-bottom:1px solid var(--grid-outline-variant)}.row-detail .row-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--grid-spacing-sm) var(--grid-spacing-md)}.row-detail .row-detail-field{display:flex;flex-direction:column;gap:var(--grid-spacing-xxs);min-width:0}.row-detail .row-detail-label{font-size:var(--grid-font-size-caption);color:var(--grid-on-surface-variant);font-weight:500}.row-detail .row-detail-value{min-width:0}.row-detail .row-detail-value data-cell{display:block;width:100%}\n"], dependencies: [{ kind: "component", type: DataCellComponent, selector: "data-cell", inputs: ["eruGridStore", "fieldSize", "columnDatatype", "columnName", "column", "value", "id", "frozenGrandTotalCell", "td", "drillable", "mode", "isEditable", "row"], outputs: ["tdChange"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1$3.ɵɵCdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1$3.ɵɵCdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1$3.ɵɵCdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i5.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i5.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i5.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: ResizeColumnDirective, selector: "[resizeColumn]", inputs: ["resizeColumn", "index", "columnConfig", "gridConfig"] }, { kind: "directive", type: ColumnDragDirective, selector: "[columnDraggable]", inputs: ["columnDraggable"] }, { kind: "component", type: ColumnDesignPanelComponent, selector: "eru-column-design-panel" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
13749
+ ], viewQueries: [{ propertyName: "rowContainer", first: true, predicate: ["rowContainer"], descendants: true }, { propertyName: "headerScroller", first: true, predicate: ["headerScroller"], descendants: true, read: ElementRef }, { propertyName: "gtScroller", first: true, predicate: ["gtScroller"], descendants: true, read: ElementRef }, { propertyName: "viewport", first: true, predicate: ["vp"], descendants: true }, { propertyName: "groupsViewport", first: true, predicate: ["groupsViewport"], descendants: true }, { propertyName: "groupsScrollContainerEl", first: true, predicate: ["groupsScrollContainer"], descendants: true }, { propertyName: "allViewports", predicate: CdkVirtualScrollViewport, descendants: true }, { propertyName: "headerScrollers", predicate: ["headerScroller"], descendants: true }], ngImport: i0, template: "<!-- <div style=\"background: #f0f0f0; font-size: 12px; border-bottom: 1px solid #ccc;\">\ncurrentPivotScrollIndex {{currentPivotScrollIndex()}} |\nfirstDataRowIndex {{firstDataRowIndex()}} |\nfirstTr {{firstTr}} |\nmaxDepth {{maxDepth()}}\n</div> -->\n<div class=\"incremental-row-container eru-grid\" #rowContainer [class.pivot-mode]=\"gridStore.isPivotMode()\"\n [class.table-mode]=\"!gridStore.isPivotMode() && !isBoardMode()\" [class.board-mode-host]=\"isBoardMode()\">\n <eru-column-design-panel></eru-column-design-panel>\n <!-- Pivot Mode Template -->\n @if (gridStore.isPivotMode()) {\n <ng-container>\n <div class=\"pivot-container\" style=\"display: flex; flex-direction: column; height: 100%;\"\n [style]=\"'--table-min-height: ' + getInitialMinHeightPx() + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <!-- Debug info for first visible row -->\n\n\n <div class=\"pivot-single-table\"\n style=\"height: 100%; width: 100%; overflow: hidden; display: flex; flex-direction: column;\">\n @if (freezeHeader()) {\n <div #headerScroller class=\"header-shell\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n @if(grandTotalPosition() === 'before' && freezeGrandTotal()) {\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n }\n </table>\n </div>\n }\n <!-- Virtual Scrolled Table Body -->\n <div>\n <cdk-virtual-scroll-viewport #vp [itemSize]=\"dataRowHeight()\" class=\"viewport pivot-viewport\"\n [class.apply-cdk-width]=\"applyCdkWidth()\" (scrolledIndexChange)=\"onPivotScroll($event)\"\n (scroll)=\"onBodyScroll($event)\" style=\"overflow: auto;\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n @if (!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n }\n <!-- Table Body with Virtual Scrolling -->\n <tbody class=\"pivot-tbody\">\n\n <tr *cdkVirtualFor=\"let pivotRow of gridStore.pivotDisplayData(); \n trackBy: trackByPivotRowFn; \n let i = index\" class=\"pivot-row\" [class.subtotal-row]=\"pivotRow._isSubtotal\"\n [class.grand-total-row]=\"pivotRow._isGrandTotal\"\n [class.subtotal-bold]=\"pivotRow._isSubtotal && subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"pivotRow._isSubtotal && subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"pivotRow._isSubtotal && subTotalStyle() === 'highlighted'\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" [attr.data-pivot-row]=\"i\">\n @if ((!pivotRow._isGrandTotal && freezeGrandTotal() ) || (!freezeGrandTotal() )) {\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.pivot-repeated-value]=\"isRepeatedDimensionValue(i, column.name)\"\n [class.pivot-group-start]=\"isPivotGroupStart(i, column.name)\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [personCardTemplate]=\"personCardTemplate\" [class.aggregation]=\"!!column.aggregationFunction\" [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\" [value]=\"pivotRow[column.name]\"\n [column]=\"column\" [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\"\n [isEditable]=\"isEditable()\" [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\"\n [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n } @else {\n <td [style.height.px]=\"dataRowHeight()\" [attr.colspan]=\"getLeafColumns().length\"> </td>\n }\n </tr>\n </tbody>\n </table>\n </cdk-virtual-scroll-viewport>\n\n </div>\n @if (freezeGrandTotal() && grandTotalPosition() === 'after') {\n <div #gtScroller class=\"header-shell gt-shell\" [class.adjust-bottom]=\"!applyCdkWidth()\"\n [class.adjust-bottom-vs]=\"adjustScrollWidth()\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n\n </table>\n </div>\n }\n\n\n </div>\n </div>\n </ng-container>\n } @else if (isBoardMode()) {\n <!-- Board Mode Template -->\n <div class=\"board-view-container\">\n @if(showSortBar()) {\n <div class=\"board-sort-bar\">\n <span class=\"board-sort-label\">Sort by:</span>\n @for (entry of gridStore.sortColumns(); track getFieldName(entry)) {\n <span class=\"board-sort-chip board-sort-chip-active\">\n <span class=\"board-sort-chip-label\">{{getColumnLabel(entry)}}</span>\n <span class=\"board-sort-chip-arrow\" (click)=\"onBoardSortChipToggle($event, entry)\">\n @if(!entry.startsWith('-')) { \u25B2 } @else { \u25BC }\n </span>\n @if(gridStore.sortColumns().length > 1) {\n <span class=\"board-sort-chip-priority\">{{getSortPriority(getFieldName(entry))}}</span>\n }\n <span class=\"board-sort-chip-remove\" (click)=\"onBoardSortChipRemove($event, entry)\">\u2715</span>\n </span>\n }\n <button class=\"board-sort-add-btn\" [matMenuTriggerFor]=\"sortFieldMenu\">\n <mat-icon class=\"board-sort-add-icon\">add</mat-icon> Add field\n </button>\n <mat-menu #sortFieldMenu=\"matMenu\" class=\"board-sort-menu\">\n @for (column of columns(); track column.name) {\n <button mat-menu-item (click)=\"onBoardSortFieldSelect(column)\"\n [disabled]=\"getSortDirection(column.name) !== null\">\n @if(getSortDirection(column.name) !== null) {\n <mat-icon>check</mat-icon>\n } @else {\n <mat-icon></mat-icon>\n }\n {{column.label}}\n </button>\n }\n </mat-menu>\n @if(gridStore.sortColumns().length > 0) {\n <button class=\"board-sort-clear\" (click)=\"onBoardSortClear()\">\u2715 Clear</button>\n }\n </div>\n }\n <div class=\"board-columns-wrapper\">\n @for (group of groups(); track group.id) {\n <div class=\"board-column\" [class.board-column-accented]=\"!!boardGroupColor(group)\"\n [style.--board-group-color]=\"boardGroupColor(group)\">\n <div class=\"column-header\">\n <!-- Render the group value through the same read-only cell renderer a\n data cell uses, so the grouped field's datatype formats itself\n (status/tag pills, dates, numbers) instead of printing raw text. -->\n @if (groupByColumn(); as gcol) {\n <span class=\"column-header-title column-header-title-cell\">\n <data-cell\n [eruGridStore]=\"gridStore\"\n [column]=\"gcol\"\n [columnDatatype]=\"gcol.datatype\"\n [columnName]=\"gcol.name\"\n [value]=\"group.title\"\n [id]=\"'board-group-' + group.id\"\n [fieldSize]=\"0\"\n [isEditable]=\"false\"\n [mode]=\"'board-group-header'\">\n </data-cell>\n </span>\n } @else {\n <span class=\"column-header-title\">{{ group.title }}</span>\n }\n <span class=\"column-header-count\">{{ group.currentLoadedRows || 0 }} of {{ group.totalRowCount || 0 }}</span>\n </div>\n <cdk-virtual-scroll-viewport [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n (scrolledIndexChange)=\"onBoardScrolledIndexChange($event, group)\">\n <div\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); templateCacheSize: 0\"\n class=\"board-card-container\"\n [class.selected]=\"isBoardCardActive(row)\"\n [class.show-row-lines]=\"showRowLines()\"\n [style.height.px]=\"boardCardOuterHeight\"\n [style.padding.px]=\"boardCardPadding\"\n [style.marginBottom.px]=\"boardCardGap\"\n [style.cursor]=\"cursorOnHover() || null\"\n (click)=\"emitRowSelect(row, 'board', group)\">\n <!-- Custom template when consumer provides boardCardTemplate; default card otherwise -->\n <ng-container\n *ngTemplateOutlet=\"boardCardTemplate ?? defaultBoardCard;\n context: { $implicit: row, columns: visibleBoardFields(), group: group }\">\n </ng-container>\n </div>\n </cdk-virtual-scroll-viewport>\n @if (group.isLoading) {\n <div class=\"board-ghost-card\">\n <div class=\"board-ghost-line\"></div>\n <div class=\"board-ghost-line board-ghost-line--short\"></div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else {\n\n <!-- Table Mode Template -->\n <ng-template #excelDownloadIcon>\n <svg class=\"excel-download-icon\" title=\"Download Excel\" (click)=\"onExcelDownloadClick($event)\"\n xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"24px\" height=\"24px\">\n <path fill=\"#169154\" d=\"M29,6H15.744C14.781,6,14,6.781,14,7.744v7.259h15V6z\" />\n <path fill=\"#18482a\" d=\"M14,33.054v7.202C14,41.219,14.781,42,15.743,42H29v-8.946H14z\" />\n <path fill=\"#0c8045\" d=\"M14 15.003H29V24.005000000000003H14z\" />\n <path fill=\"#17472a\" d=\"M14 24.005H29V33.055H14z\" />\n <g>\n <path fill=\"#29c27f\" d=\"M42.256,6H29v9.003h15V7.744C44,6.781,43.219,6,42.256,6z\" />\n <path fill=\"#27663f\" d=\"M29,33.054V42h13.257C43.219,42,44,41.219,44,40.257v-7.202H29z\" />\n <path fill=\"#19ac65\" d=\"M29 15.003H44V24.005000000000003H29z\" />\n <path fill=\"#129652\" d=\"M29 24.005H44V33.055H29z\" />\n </g>\n <path fill=\"#0c7238\"\n d=\"M22.319,34H5.681C4.753,34,4,33.247,4,32.319V15.681C4,14.753,4.753,14,5.681,14h16.638 C23.247,14,24,14.753,24,15.681v16.638C24,33.247,23.247,34,22.319,34z\" />\n <path fill=\"#fff\"\n d=\"M9.807 19L12.193 19 14.129 22.754 16.175 19 18.404 19 15.333 24 18.474 29 16.123 29 14.013 25.07 11.912 29 9.526 29 12.719 23.982z\" />\n </svg>\n </ng-template>\n @if(showExcelDownload() && !showGroupBar()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\n <!-- Scrollable groups container \u2014 plain iteration avoids CDK fixed-height estimation errors -->\n <div #groupsScrollContainer class=\"groups-scroll-container\" (scroll)=\"onGroupsViewportScroll($event)\">\n\n @for (group of groups(); track trackByGroupFn($index, group); let i = $index) {\n <div class=\"group-container\"\n [attr.data-group-id]=\"group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id\">\n <!-- Combined sticky header with group info and table -->\n <div style=\"\n background:var(--grid-surface);\n position: sticky;\n top: 0;\n z-index: 115;\n \">\n @if(showGroupBar()) {\n <div class=\"group-header-row\">\n <div class=\"custom-collapse-header\" (click)=\"toggleGroupCollapse(group.id)\">\n <span class=\"collapse-arrow\" [ngClass]=\"{\n 'rotate-arrow': group.isExpanded,\n }\">\u25BC</span>\n <span class=\"f-12\">\n {{ group?.title || \"\" }}\n {{ group?.currentLoadedRows || 0 }} -\n {{ group?.totalRowCount || 0 }} rows...</span>\n @if(groupByField() && isSortable()) {\n <span class=\"group-sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'asc'\"\n (click)=\"onGroupSortToggle($event, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\"\n [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'desc'\"\n (click)=\"onGroupSortToggle($event, 'desc')\"></span>\n </span>\n </span>\n }\n </div>\n @if(i === 0 && showExcelDownload()) {\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n }\n </div>\n }\n\n @if(freezeHeader() && (group.isExpanded || !showGroupBar())) {\n <div #headerScroller class=\"header-shell\" [attr.data-group-id]=\"'header-shell-' + group.id\"\n [style]=\"'--table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <table class=\"eru-grid-table\" [class.freeze-header]=\"freezeHeader()\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n <!-- Grand Total row after sticky header (position: before) - only for first group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' && hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after sticky header (position: before) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'before' && hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n </table>\n </div>\n }\n </div>\n @if(group.isExpanded || !showGroupBar()) {\n <ng-container>\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"dataRowHeight()\" class=\"viewport table-viewport\"\n (scrolledIndexChange)=\"onScroll($event, group)\" (scroll)=\"onTableBodyScroll($event)\"\n [style]=\"'--table-height: ' + getGroupContentHeight(group.id) + 'px; --table-min-height: ' + getGroupContentHeight(group.id) + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <div class=\"table-wrapper\">\n <table class=\"eru-grid-table\" [class.show-column-lines]=\"showColumnLines()\"\n [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n @if(!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n }\n <!-- Grand Total row after normal header (position: before) - only for first group -->\n @if(!freezeHeader() && enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' &&\n hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after normal header (position: before) -->\n @if(!freezeHeader() && enableColumnSubtotals() && subtotalPositionColumn() === 'before' &&\n hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n <tbody>\n @if (columns(); as columnsList) {\n <!-- <tr *ngIf=\"groupItem.type === 'table-header' && groups().length > 1\" style=\"background:#fafafa\">\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column\" style=\"text-align: center;\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupSelected(groupItem.group?.id || '')\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection($event, groupItem.group?.id || '')\"\n >\n </th>\n }\n <th *ngFor=\"let column of columns(); trackBy: trackByColumnFn;let i =index\"\n style=\"text-align: center;\"\n [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"true\"\n [columnConfig]=\"column\"\n [columnDraggable]=\"i\"\n class=\"column-header\">\n <div class=\"column-drag-handle\"></div>\n {{column.label}} {{column.symbol}}\n </th>\n </tr> -->\n <!-- @if(getRowsForGroup(group.id).length > 0 && group.isExpanded) { -->\n <!-- *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id)(); \n trackBy: trackByRowFn; \n let i = index\" -->\n <!-- @for(row of getRowsForGroupSignal(group.id)(); track trackByRowFn($index, row); let i = $index) { -->\n <ng-container\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); trackBy: trackByRowFn; let i = index\">\n <tr class=\"row-item\" [attr.data-row-id]=\"i\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" (click)=\"emitRowSelect(row, 'table', group)\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\" style=\"text-align: center;\">\n <input type=\"checkbox\" [checked]=\"isRowSelected(row?.entity_id)\"\n (change)=\"toggleRowSelection($event, row)\">\n </td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\" (click)=\"toggleRowExpand(row, i, $event)\">\n <mat-icon class=\"row-expand-icon\" [class.expanded]=\"isRowExpanded(row, i)\">chevron_right</mat-icon>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td #cell [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"data-cell\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\"\n [matTooltipClass]=\"'error-message'\" [matTooltip]=\"datacell.error()?'Error: ' + datacell.error():''\"\n matTooltipPosition=\"below\">\n <div class=\"cell-content\">\n <data-cell #datacell [personCardTemplate]=\"personCardTemplate\" [td]=cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\" [value]=\"(row?.['entity_data']?.[column.name] ?? row?.[column.name]) || ''\" [column]=\"column\"\n [mode]=\"mode()\" [isEditable]=\"isEditable() && column.editable !== false && column.editable !== 'false'\" [drillable]=\"column.enableDrilldown || false\"\n [id]=\"i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n </tr>\n @if(hasHiddenColumns() && isRowExpanded(row, i)) {\n <tr class=\"row-detail\">\n <td class=\"row-detail-cell\" [attr.colspan]=\"rowDetailColspan()\">\n <div class=\"row-detail-grid\">\n @for (hiddenCol of hiddenColumns(); track trackByColumnFn($index, hiddenCol)) {\n <div class=\"row-detail-field\">\n <span class=\"row-detail-label\">{{hiddenCol.label}}</span>\n <div class=\"row-detail-value\">\n <data-cell [fieldSize]=\"hiddenCol.field_size\" [columnDatatype]=\"hiddenCol.datatype\"\n [columnName]=\"hiddenCol.name\" [value]=\"(row?.['entity_data']?.[hiddenCol.name] ?? row?.[hiddenCol.name]) || ''\"\n [column]=\"hiddenCol\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [drillable]=\"hiddenCol.enableDrilldown || false\"\n [id]=\"'detail_' + i + '_' + hiddenCol.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </div>\n }\n </div>\n </td>\n </tr>\n }\n </ng-container>\n <!-- } -->\n <!-- } -->\n @if(group.isLoading && (group.isExpanded || !showGroupBar())) {\n @for(i of [].constructor(ghostRows()); let j = $index; track j) {\n <tr class=\"ghost-loading-row\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n </tr>\n }\n }\n <!-- <tr\n *ngIf=\"getRowsForGroup(group.id).length === 0 && !group.isExpanded\"\n class=\"group-separator\"\n >\n <td [attr.colspan]=\"groupSeperatorColSpan()\" class=\"separator-cell\"></td>\n </tr> -->\n <!-- Subtotal row at end of group (position: after) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'after' && hasSubtotalData(group)) {\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n }\n <!-- Grand Total row at end of group (position: after) - only for last group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'after' && hasGrandTotalData() && i ===\n groups().length - 1) {\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n }\n }\n </tbody>\n </table>\n </div>\n </cdk-virtual-scroll-viewport>\n </ng-container>\n }\n </div>\n }\n </div>\n }\n</div>\n\n<!-- Pivot Table Header Template -->\n<ng-template #pivotTableHead>\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n @if (hasNestedHeaders()) {\n <ng-container>\n @for (headerRow of getHeaderRows(); track headerRow; let rowIndex = $index) {\n <tr class=\"pivot-header pivot-header-container\" [class.pivot-header-level]=\"'level-' + rowIndex\">\n @for (header of headerRow; track trackByHeaderFn($index, header); let colIndex = $index) {\n <th [attr.colspan]=\"header.colspan\" [attr.rowspan]=\"header.rowspan\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable') && header.level === 0 && !isRowDimensionHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header)\" class=\"column-header pivot-column-header nested-header\"\n [class.row-dimension-header]=\"isRowDimensionHeader(header)\"\n [class.column-dimension-header]=\"!isRowDimensionHeader(header)\" [class.expanded]=\"header.isExpanded\"\n [class.collapsed]=\"!header.isExpanded\" [class.sticky-column]=\"isStickyColumn(header.name, colIndex)\"\n [style.position]=\"isStickyColumn(header.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(header.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(header.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto; padding: 8px 6px;\">\n <div class=\"header-content\">\n\n <data-cell [fieldSize]=\"header.field_size\" [columnDatatype]=\"header.dataType\" [columnName]=\"header.name\"\n [value]=\"header.label\" [column]=\"header\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"header.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + $index + '_' + header.name\" [eruGridStore]=\"gridStore\" [row]=\"header\">\n </data-cell>\n <!-- <span class=\"header-label header-wrap-text\">{{header.label}}</span> -->\n <!-- <button *ngIf=\"!isRowDimensionHeader(header)\"\n class=\"collapse-toggle-btn\"\n [title]=\"header.isExpanded ? 'Collapse group' : 'Expand group'\"\n (click)=\"toggleColumnGroup(header.groupKey)\"\n type=\"button\">\n <span class=\"collapse-icon\">+</span>\n </button> -->\n </div>\n </th>\n }\n </tr>\n }\n </ng-container>\n } @else {\n <!-- Simple header fallback -->\n <ng-container>\n <tr class=\"pivot-header\" [class.freeze-header-enabled]=\"freezeHeader()\">\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <th [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\" [columnConfig]=\"column\"\n class=\"column-header pivot-column-header\" [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto;padding: 8px 6px\">\n {{column.label}}\n </th>\n }\n </tr>\n </ng-container>\n }\n\n </thead>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #pivotColGroup>\n <colgroup>\n @for (column of getLeafColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n </colgroup>\n</ng-template>\n\n<ng-template #pivotGrandTotal>\n <tbody class=\"pivot-tbody\">\n @for (pivotRow of gridStore.pivotGrandTotalData(); track trackByPivotRowFn($index, pivotRow); let i = $index) {\n <tr class=\"pivot-row grand-total-row\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"50\" [attr.data-pivot-row]=\"i\">\n <!-- <td colspan=\"20\">{{pivotRow | json}}</td> -->\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [attr.rowspan]=\"getEffectiveRowspan(i, column.name)\" [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.rowspan-cell]=\"getEffectiveRowspan(i, column.name) || 1 > 1\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"50\" [attr.xx]=\"i\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getEffectiveCellValue(i,column.name, pivotRow)\" [column]=\"column\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #tableColGroup>\n <colgroup>\n @if(gridStore.configuration().config.allowSelection) {\n <col style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n }\n @if(shouldShowActionColumn('before')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n @if(hasHiddenColumns()) {\n <col style=\"width: 40px !important; min-width: 40px !important; max-width: 40px !important;\">\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n @if(shouldShowActionColumn('after')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <tr>\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column column-header table-column-header\">\n <input type=\"checkbox\" [checked]=\"isAllGroupsSelected()\" (change)=\"toggleAllGroups($event)\">\n </th>\n }\n @if(shouldShowActionColumn('before')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n @if(hasHiddenColumns()) {\n <th class=\"row-expand-toggle column-header table-column-header\"></th>\n }\n @for (column of visibleColumns(); track trackByColumnFn(i, column); let i = $index) {\n <th [style.width.px]=\"column.field_size\" [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\"\n [columnConfig]=\"column\" [index]=\"i\"\n [columnDraggable]=\"gridStore.isFeatureEnabled('columnReorderable') ? i : null\"\n [style.minWidth.px]=\"column.field_size\" class=\"column-header table-column-header\"\n [class.sortable-header]=\"isSortable()\"\n [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === column.name\"\n [class.sort-asc]=\"isSortable() && getSortDirection(column.name) === 'asc'\"\n [class.sort-desc]=\"isSortable() && getSortDirection(column.name) === 'desc'\">\n @if(gridStore.isFeatureEnabled('columnReorderable')) {\n <div class=\"column-drag-handle\"></div>\n }\n <span class=\"column-label\" [title]=\"column.tool_tip || column.description || ''\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\" title=\"Edit column\" (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n @if(isSortable()) {\n <span class=\"sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'asc'\"\n (click)=\"onSortColumn($event, column, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'desc'\"\n (click)=\"onSortColumn($event, column, 'desc')\"></span>\n </span>\n @if(getSortPriority(column.name) !== null && gridStore.sortColumns().length > 1) {\n <span class=\"sort-priority\">{{getSortPriority(column.name)}}</span>\n }\n </span>\n }\n </th>\n }\n @if(shouldShowActionColumn('after')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n </tr>\n </thead>\n</ng-template>\n\n<!-- Table Subtotal Row Template -->\n<ng-template #tableSubtotal let-group=\"group\">\n <tr class=\"subtotal-row\" [class.subtotal-bold]=\"subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"subTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"subtotal-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getSubtotalValue(group, column.name) === null) {\n <span class=\"subtotal-label\">{{subtotalLabel()}}</span>\n } @else {\n @if(getSubtotalValue(group, column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getSubtotalValue(group, column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'subtotal_' + group.id + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"group.subtotal\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- Table Grand Total Row Template -->\n<ng-template #tableGrandTotal>\n <tr class=\"grand-total-row\" [class.grand-total-bold]=\"grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"grandTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"grand-total-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getGrandTotalValue(column.name) === null) {\n <span class=\"grand-total-label\">Grand Total</span>\n } @else {\n @if(getGrandTotalValue(column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getGrandTotalValue(column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'grandtotal_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"gridStore.rowGrandTotal()\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- \u2500\u2500\u2500 Default board card template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Used when no boardCardTemplate is passed to <eru-grid>.\n Context: { $implicit: Row, columns: Field[], group: RowGroup }\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<ng-template #defaultBoardCard let-row let-columns=\"columns\" let-group=\"group\">\n <mat-card class=\"board-card\">\n <mat-card-content>\n @for (column of columns; track column.name) {\n @if ((row?.entity_data?.[column.name] ?? row?.[column.name]) !== undefined) {\n <div class=\"board-card-field\">\n <span class=\"board-field-label\">{{ column.label }}</span>\n <data-cell\n [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\"\n [column]=\"column\"\n [value]=\"row?.entity_data?.[column.name] ?? row?.[column.name]\"\n [id]=\"row?.entity_id + '_' + column.name\"\n [eruGridStore]=\"gridStore\"\n [mode]=\"'board'\"\n [row]=\"row\">\n </data-cell>\n </div>\n }\n }\n </mat-card-content>\n <mat-card-actions align=\"end\">\n <button mat-icon-button (click)=\"onActionClick($event, row)\">\n <mat-icon>more_horiz</mat-icon>\n </button>\n </mat-card-actions>\n </mat-card>\n</ng-template>", styles: ["@charset \"UTF-8\";:root{--grid-primary: #6750a4;--grid-on-primary: #ffffff;--grid-surface: #fef7ff;--grid-surface-variant: #e7e0ec;--grid-surface-container: #f3edf7;--grid-surface-container-high: #ede7f0;--grid-on-surface: #1d1b20;--grid-on-surface-variant: #49454f;--grid-outline: #79757f;--grid-outline-variant: #cac4d0;--grid-error: #ba1a1a;--grid-error-container: #ffdad6}:host,eru-grid{display:block!important;width:100%;height:100%;flex:1 1 0%;min-height:var(--grid-height, 300px);font-family:var(--grid-font-family);--grid-font-family: \"Poppins\", \"Roboto\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;--grid-font-size-body: 12px;--grid-font-size-caption: 12px !important;--grid-line-height-body: 1;--grid-aggregation-text-align: right;--grid-number-text-align: right;--grid-spacing-xxs: 2px;--grid-spacing-xs: 4px;--grid-spacing-sm: 8px;--grid-spacing-md: 16px;--grid-spacing-lg: 24px;--grid-border-radius: 4px;--grid-elevation-1: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 1px 3px 1px rgba(0, 0, 0, .15);--grid-elevation-2: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 2px 6px 2px rgba(0, 0, 0, .15);--grid-row-hover: var(--grid-surface-variant);--grid-row-selected: var(--grid-surface-container-high);--grid-zebra-odd: transparent;--grid-zebra-even: transparent;--grid-focus-ring: var(--grid-primary);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: var(--grid-font-size-caption);--grid-header-padding-x: 8px;--grid-header-padding-y: 12px;--grid-font-feature-numeric: normal;--grid-cell-padding-x: var(--grid-spacing-xs);--grid-cell-padding-y: var(--grid-spacing-xxs);--grid-tint-subtle: rgba(0, 0, 0, .025);--grid-tint-soft: rgba(0, 0, 0, .045);--grid-tint-strong: rgba(0, 0, 0, .08);--grid-radius-outer: 0;--grid-shadow-outer: none;--grid-divider-color: var(--grid-outline-variant);--grid-divider-width: 1px;--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-pill-radius: 999px;--grid-pill-padding-y: 3px;--grid-pill-padding-x: 10px;--grid-pill-font-size: 11px;--grid-pill-font-weight: 500;--grid-priority-dot-size: 8px;--grid-avatar-size: 24px;--grid-avatar-font-size: 10px;--grid-avatar-font-weight: 600;border-radius:var(--grid-radius-outer);box-shadow:var(--grid-shadow-outer)}eru-grid[data-preset=default]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .06em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-soft);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=modern]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: 13px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 16px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 12px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px}eru-grid[data-preset=compact]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 11px;--grid-header-padding-y: 4px;--grid-header-padding-x: 8px;--grid-cell-padding-y: 3px;--grid-cell-padding-x: 8px;--grid-font-size-body: 11px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 1px;--grid-pill-padding-x: 6px;--grid-pill-font-size: 10px}eru-grid[data-preset=bold]{--grid-header-bg: var(--grid-surface-container-high);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 700;--grid-header-text-transform: none;--grid-header-font-size: 13px;--grid-header-padding-y: 14px;--grid-header-padding-x: 12px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 12px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-strong);--grid-divider-width: 1px;--grid-radius-outer: 2px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=financial]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .08em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-zebra-odd: transparent;--grid-zebra-even: var(--grid-tint-subtle);--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=elevated]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 12px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 14px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 16px;--grid-shadow-outer: 0 1px 3px rgba(0, 0, 0, .06), 0 10px 28px rgba(0, 0, 0, .07);--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px;overflow:hidden}.group-container{padding-bottom:8px}.column-header.design-clickable .design-edit-icon{font-size:16px;width:16px;height:16px;margin-left:4px;opacity:.45;vertical-align:middle;cursor:pointer}.column-header.design-clickable:hover .design-edit-icon,.column-header.design-clickable .design-edit-icon:hover{opacity:1}.column-header.design-selected{background-color:var(--grid-primary-light, rgba(63, 81, 181, .12))}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-height, 300px);max-height:none;overflow:auto;position:relative;background-color:var(--grid-surface);border-radius:var(--grid-border-radius);font-family:var(--grid-font-family)}.viewport{height:100%;min-height:300px;overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface);scrollbar-gutter:stable}.viewport.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.groups-viewport{height:100%;min-height:300px}.groups-scroll-container{height:var(--grid-height, 600px);overflow-y:auto;overflow-x:hidden}.table-viewport{background-color:var(--grid-surface);height:var(--table-height, auto);min-height:var(--table-min-height, 100px);overflow-x:auto;overflow-y:auto}.pivot-viewport{min-height:var(--table-min-height, 300px);overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface)}.pivot-viewport .cdk-virtual-scroll-content-wrapper{width:auto;height:auto}.table-wrapper{min-width:100%;overflow-x:visible}.incremental-row-container .eru-grid-table,.eru-grid-table{width:100%!important;border-collapse:separate;border-spacing:0;table-layout:fixed!important;background-color:var(--grid-surface);color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);line-height:var(--grid-line-height-body)}.eru-grid-table th,.eru-grid-table td{text-align:left;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:var(--grid-on-surface);min-width:0;max-width:100%!important;box-sizing:border-box;position:relative}.eru-grid-table th{background-color:var(--grid-header-bg, var(--grid-surface-container))}thead.eru-wrap-headers th{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;height:auto}thead.eru-wrap-headers th .column-label,thead.eru-wrap-headers th .header-label{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;word-break:break-word;overflow-wrap:anywhere}.eru-grid-table tbody td{background-color:transparent}.eru-grid-table thead{background-color:var(--grid-header-bg, var(--grid-surface-container));transform:translateZ(0);will-change:transform;backface-visibility:hidden}.eru-grid-table thead.freeze-header-enabled{position:sticky!important;top:0!important;z-index:100!important}.eru-grid-table thead th{background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface));font-family:var(--grid-font-family);font-weight:var(--grid-header-font-weight);font-size:var(--grid-header-font-size)}.checkbox-column{width:50px;min-width:50px;max-width:50px;text-align:center;background-color:var(--grid-surface-container)}.checkbox-column input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--grid-primary);border-radius:var(--grid-border-radius)}.checkbox-column input[type=checkbox]:focus{outline:2px solid var(--grid-primary);outline-offset:2px}.action-column{width:40px;min-width:40px;max-width:40px;text-align:center;background-color:var(--grid-surface-container)}.action-column mat-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);cursor:pointer}.action-column mat-icon:hover{color:var(--grid-primary)}.group-header{background-color:var(--grid-surface-container);color:var(--grid-on-surface);font-size:var(--grid-font-size-caption);font-weight:500;border-bottom:1px solid var(--grid-outline);cursor:pointer;transition:background-color .2s ease}.group-header:hover{background-color:var(--grid-surface-container-high)}.group-header .group-title{font-weight:600;color:var(--grid-primary)}.group-header .group-row-count{color:var(--grid-on-surface-variant);font-size:var(--grid-font-size-caption);margin-left:var(--grid-spacing-sm)}.row-item{background-color:var(--grid-surface);transition:background-color .15s ease}.row-item:nth-child(odd){background-color:var(--grid-zebra-odd, var(--grid-surface))}.row-item:nth-child(2n){background-color:var(--grid-zebra-even, var(--grid-surface))}.row-item:hover{background-color:var(--grid-row-hover)}.required-toggle-row{background-color:var(--grid-surface-container, #f3edf7);border-bottom:1px solid var(--grid-outline-variant, #cac4d0)}.required-toggle-row .required-toggle-cell{padding:4px 8px!important;text-align:center;vertical-align:middle;position:relative}.required-toggle-row .required-toggle-cell .required-label{position:absolute;top:2px;left:4px;font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;text-transform:lowercase}.required-toggle-row .required-toggle-cell mat-checkbox{display:flex;justify-content:center;align-items:center}.table-column-header{padding:var(--grid-header-padding-y) var(--grid-header-padding-x)}.column-header{font-weight:var(--grid-header-font-weight);text-transform:var(--grid-header-text-transform);letter-spacing:var(--grid-header-letter-spacing);text-align:center!important;font-size:var(--grid-header-font-size);position:relative;-webkit-user-select:none;user-select:none;background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface))}.column-header:hover{background-color:var(--grid-header-hover-bg, var(--grid-surface-container-high))}.column-drag-handle{position:absolute;left:0;top:0;bottom:0;width:12px;cursor:grab;opacity:0;transition:opacity .2s ease,background-color .2s ease;z-index:2;display:flex;align-items:center;justify-content:center;border-right:1px solid transparent}.column-drag-handle:after{content:\"\\22ee\\22ee\";font-size:14px;color:var(--grid-on-surface-variant);transform:rotate(90deg)}.column-drag-handle:hover{background-color:var(--grid-surface-container-high);border-right-color:var(--grid-outline)}.column-header:hover .column-drag-handle{opacity:1}.column-drag-handle:active{cursor:grabbing}.sortable-header{cursor:pointer}.sortable-header .column-label{flex:1}.sortable-header .sort-indicator{display:inline-flex;align-items:center;gap:2px;margin-left:4px;cursor:pointer;vertical-align:middle;opacity:0;transition:opacity .15s ease}.sortable-header .sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.sortable-header .sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.sortable-header .sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-active{opacity:1}.sortable-header .sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-priority{font-size:9px;font-weight:600;color:var(--grid-primary, #6750a4);line-height:1;min-width:12px;text-align:center}.sortable-header:hover .sort-indicator,.sortable-header.sort-asc .sort-indicator,.sortable-header.sort-desc .sort-indicator{opacity:1}.sortable-header:hover .sort-indicator .sort-tri:not(.sort-tri-active){opacity:.6}.sort-asc,.sort-desc{background-color:var(--grid-surface-container-low, rgba(103, 80, 164, .04))}.dragging{opacity:1;background-color:var(--grid-surface-container);box-shadow:var(--grid-elevation-2)}.drag-over{background-color:var(--grid-surface-container);border-color:var(--grid-primary)}.data-cell{background-color:transparent;color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);font-feature-settings:var(--grid-font-feature-numeric);padding:var(--grid-cell-padding-y) var(--grid-cell-padding-x)}.cell-content{align-items:center}.cell-content .mdc-text-field{padding:0px var(--grid-spacing-xxs)!important}.cell-display-text{align-items:center;padding:0px var(--grid-spacing-xs)}.ghost-loading-row{background-color:transparent}.ghost-cell-container{padding:var(--grid-spacing-sm)}.ghost-cell{height:20px;width:100%;background-color:var(--grid-surface-container);animation:pulse 1.5s ease-in-out infinite;border-radius:var(--grid-border-radius)}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.column-resizer{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;background-color:transparent;transition:background-color .2s ease}.column-resizer:hover{background-color:var(--grid-primary)}.group-separator{height:var(--grid-spacing-sm);background-color:var(--grid-surface-variant)}.group-separator .separator-cell{background-color:var(--grid-surface-variant);border:none;height:var(--grid-spacing-sm)}.error-state{background-color:var(--grid-error-container);color:var(--grid-error);border-color:var(--grid-error)}.error-message{background-color:var(--grid-error);color:#fff;padding:var(--grid-spacing-sm);border-radius:var(--grid-border-radius);font-size:var(--grid-font-size-caption)}.incremental-row-container .eru-grid-table tbody,.incremental-row-container .eru-grid-table{position:relative}.incremental-row-container .eru-grid-table.show-column-lines{border-right:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important;border-top:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table.show-column-lines:not(.freeze-header){border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table:not(.show-column-lines){border:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table thead:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:calc(var(--grid-divider-width, 1px) * 2);background-color:var(--grid-divider-color, var(--grid-outline, #e0e0e0));pointer-events:none;z-index:10}.incremental-row-container .eru-grid-table.show-column-lines thead th,.incremental-row-container .eru-grid-table.show-column-lines tbody td{border-left:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}.incremental-row-container .eru-grid-table.show-row-lines thead th,.incremental-row-container .eru-grid-table.show-row-lines tbody td{border-bottom:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}@media(max-width:768px){.incremental-row-container{height:600px}.eru-grid-table th,.eru-grid-table td{font-size:var(--grid-font-size-caption)}.checkbox-column{width:40px;min-width:40px;max-width:40px}}@media(prefers-contrast:high){.eru-grid-table th,.eru-grid-table td{border-width:2px}.row-item:hover{border-width:2px;border-color:var(--grid-primary)}}@media(prefers-reduced-motion:reduce){.row-item,.column-drag-handle,.ghost-cell{transition:none;animation:none}}.pivot-table .nested-header{text-align:center;font-weight:600;background:var(--grid-surface-container)}.pivot-table .nested-header.row-dimension-header{background:var(--grid-surface-container);font-weight:600}.pivot-table .pivot-header-leafcols{padding:0;margin:0;height:0}.pivot-table .pivot-header-level.level-0 .nested-header{font-size:14px;padding:12px 8px}.pivot-table .pivot-header-level.level-1 .nested-header{font-size:13px;padding:10px 6px}.pivot-table .pivot-header-level.level-2 .nested-header{font-size:12px;padding:8px 4px}.pivot-table .nested-header:hover{background:var(--grid-surface-variant);color:var(--grid-primary);transition:all .2s ease}.pivot-table .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-table .pivot-cell-content{display:flex;justify-content:center;align-items:center;min-height:38px}.pivot-table .pivot-repeated-value .cell-content,.pivot-table .pivot-repeated-value .pivot-cell-content{visibility:hidden}.pivot-table .pivot-group-start.row-dimension-cell{border-top:1px solid var(--grid-outline, #79757f)}.pivot-mode .incremental-row-container{display:flex;flex-direction:column;height:auto;max-height:85vh;overflow:auto}.pivot-mode .h-shell{position:relative;width:calc(100% - var(--scrollbar-width, 17px))!important;top:0;z-index:1;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .h-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell{position:relative;bottom:50px;flex-shrink:0;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .gt-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell table{border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.pivot-mode .gt-shell.adjust-bottom-vs{bottom:66px!important}.pivot-mode .gt-shell.adjust-bottom:not(.adjust-bottom-vs){bottom:calc(66px - var(--scrollbar-width, 17px))!important}.pivot-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .header-shell::-webkit-scrollbar{display:none}.pivot-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.pivot-mode .header-shell .eru-grid-table{margin-bottom:0;width:100%;table-layout:fixed}.pivot-mode .header-shell .eru-grid-table thead{background:var(--grid-surface-container)}.pivot-mode .header-shell .eru-grid-table thead th{background:var(--grid-surface-container);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .header-shell .eru-grid-table thead th.sticky-column{position:sticky;background:var(--grid-surface-container);z-index:111}.pivot-mode .header-shell .eru-grid-table tbody td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.pivot-mode .pivot-table{width:auto!important;min-width:100%!important;table-layout:fixed!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important}.pivot-mode .pivot-table td,.pivot-mode .pivot-table th{box-sizing:border-box!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-table{table-layout:fixed!important;width:100%!important}.pivot-mode .pivot-table *{max-width:var(--col-width)!important;box-sizing:border-box!important}.pivot-mode .pivot-table colgroup{width:100%!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex-basis:var(--col-width)!important;flex:0 0 var(--col-width)!important}.pivot-mode .pivot-table table{width:100%!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important}.pivot-mode .pivot-table[style*=--table-total-width]{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:0 0 var(--col-width)!important;flex-basis:var(--col-width)!important;flex-grow:0!important;flex-shrink:0!important;overflow:hidden!important}.pivot-mode .pivot-table tbody td,.pivot-mode .pivot-table thead th{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.pivot-mode .pivot-table .cell-content,.pivot-mode .pivot-table data-cell{width:100%!important;max-width:100%!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;display:block!important}.pivot-mode .pivot-table table{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-tbody tr.pivot-row{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important}.pivot-mode .pivot-tbody tr.pivot-row:hover{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-tbody tr.pivot-row:nth-child(2n){background-color:#00000005}.pivot-mode .pivot-tbody tr.pivot-row td{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content{min-height:calc(var(--grid-data-row-height, 50px) - 2px);display:flex;align-items:center;justify-content:center}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content data-cell{width:100%;min-height:calc(var(--grid-data-row-height, 50px) - 4px);display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0}.pivot-mode .pivot-cell{vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-mode .pivot-cell .cell-content{display:flex;justify-content:center;align-items:center;min-height:var(--grid-header-row-height, 40px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.pivot-mode .pivot-table .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.pivot-mode .pivot-table .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .subtotal-row td:first-child{color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row td.aggregated-value{font-weight:500;color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.pivot-mode .pivot-table .subtotal-bold td{font-weight:600!important;font-style:normal!important}.pivot-mode .pivot-table .subtotal-bold td.aggregated-value{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td{font-style:italic!important}.pivot-mode .pivot-table .subtotal-italic td:first-child{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.pivot-mode .pivot-table .subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted td.aggregated-value{font-weight:500!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted:hover,.pivot-mode .pivot-table .subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700;font-size:var(--grid-font-size-body)}.pivot-mode .pivot-table .grand-total-row td{background-color:var(--grid-surface-container-high)!important;color:var(--grid-on-surface)}.pivot-mode .pivot-table .grand-total-row td:first-child{font-style:normal;font-weight:800;color:var(--grid-primary)}.pivot-mode .pivot-table .grand-total-row td.aggregated-value{font-weight:500;color:var(--grid-primary);font-family:Roboto Mono,monospace}.pivot-mode .pivot-table .grand-total-row:hover,.pivot-mode .pivot-table .grand-total-row:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-bold td{font-weight:700!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-bold td.aggregated-value{font-weight:700!important}.pivot-mode .pivot-table .grand-total-italic td,.pivot-mode .pivot-table .grand-total-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted{background-color:var(--grid-primary)!important;box-shadow:var(--grid-elevation-2)!important}.pivot-mode .pivot-table .grand-total-highlighted td{background-color:var(--grid-primary)!important;color:var(--grid-on-primary)!important;font-weight:500!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-highlighted td.aggregated-value{color:var(--grid-on-primary)!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted:hover,.pivot-mode .pivot-table .grand-total-highlighted:hover td{background-color:var(--grid-primary)!important}.pivot-mode .pivot-table .collapsible-header{position:relative}.pivot-mode .pivot-table .collapsible-header .header-content{display:flex;align-items:center;justify-content:space-between;gap:var(--grid-spacing-xs);padding:var(--grid-spacing-xs) var(--grid-spacing-sm)}.pivot-mode .pivot-table .collapsible-header .header-label{flex:1;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn{background:none;border:none;cursor:pointer;padding:var(--grid-spacing-xxs);margin:0;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--grid-border-radius);color:var(--grid-on-surface-variant);transition:all .2s ease;font-size:12px;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:hover{background-color:var(--grid-surface-container);color:var(--grid-primary);transform:scale(1.1)}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:focus{outline:2px solid var(--grid-primary);outline-offset:1px}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn .collapse-icon{display:block;line-height:1;font-family:monospace;font-size:14px}.pivot-mode .pivot-table .collapsible-header.expanded .collapse-toggle-btn .collapse-icon{color:var(--grid-primary)}.pivot-mode .pivot-table .collapsible-header.collapsed{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .header-label{font-style:italic;color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .collapse-toggle-btn .collapse-icon{color:var(--grid-outline)}.pivot-mode .pivot-table .collapsible-header:hover{background-color:var(--grid-surface-container)}.pivot-mode .pivot-table .collapsible-header:hover .header-label{color:var(--grid-on-surface)}.pivot-mode .pivot-table .pivot-single-table{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden;min-height:var(--table-min-height)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container{flex-shrink:0;background:var(--grid-surface)!important;overflow-x:auto;overflow-y:hidden;min-height:100px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table{width:auto;min-width:100%;height:auto!important;min-height:100px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th{background:var(--grid-surface-container)!important;padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:40px!important;height:auto!important;position:relative;visibility:visible!important;color:var(--grid-on-surface)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:101!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container{flex:1;overflow:auto;min-height:300px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-viewport{height:100%!important;width:100%!important;overflow-x:auto!important;overflow-y:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table{width:auto;min-width:100%;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td{padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:32px!important;height:auto!important;background:var(--grid-surface)!important;color:var(--grid-on-surface)!important;visibility:visible!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td.sticky-column,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:100!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr{height:auto!important;min-height:50px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr.pivot-row,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr.pivot-row{visibility:visible!important;display:table-row!important}.pivot-mode .pivot-table .collapsed-column-group{background-color:var(--grid-surface-container);border-left:3px solid var(--grid-primary)}.pivot-mode .pivot-table .collapsed-column-group:hover{background-color:var(--grid-surface-container-high)}.pivot-row.subtotal-row{background-color:var(--grid-surface-variant);font-weight:500}.pivot-row.subtotal-row.subtotal-bold{font-weight:500}.pivot-row.subtotal-row.subtotal-italic{font-style:italic}.pivot-row.subtotal-row.subtotal-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.grand-total-row{background-color:var(--grid-surface-container);font-weight:600}.pivot-row.grand-total-row.grand-total-bold{font-weight:800}.pivot-row.grand-total-row.grand-total-italic{font-style:italic}.pivot-row.grand-total-row.grand-total-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.first-visible-row{background-color:#6750a41a!important;position:relative}.pivot-row.first-visible-row:before{content:\"\\1f441\\fe0f First Visible\";position:absolute;top:-20px;left:0;background:var(--grid-primary);color:var(--grid-on-primary);padding:2px 6px;font-size:10px;border-radius:2px;z-index:1000}.header-wrap-text{white-space:pre-wrap;word-break:auto-phrase}.group-header-row{display:flex;align-items:center;justify-content:space-between;width:100%;padding-right:12px}.custom-collapse-header{background-color:var(--grid-surface-variant);padding:8px 20px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:pointer;display:flex;width:fit-content;align-items:center;-webkit-user-select:none;user-select:none;min-width:200px;margin-bottom:10px;position:sticky;left:1px;z-index:116}.custom-collapse-header .collapse-arrow{display:inline-block;margin-right:8px;font-size:12px;color:var(--grid-on-surface-variant);transition:transform .2s ease;transform:rotate(0)}.custom-collapse-header .collapse-arrow.rotate-arrow{transform:rotate(270deg)}.custom-collapse-header .f-12{font-size:12px;color:var(--grid-on-surface)}.custom-collapse-header .group-sort-indicator{display:inline-flex;align-items:center;margin-left:8px}.custom-collapse-header .group-sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.custom-collapse-header .group-sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.custom-collapse-header .group-sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-active{opacity:1}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.excel-download-icon{cursor:pointer}.excel-download-icon:hover{opacity:.75}.excel-download-bar{display:flex;justify-content:flex-end;padding:4px 12px}.table-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .header-shell::-webkit-scrollbar{display:none}.table-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.table-mode .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.table-mode .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.table-mode .subtotal-row td:first-child{color:var(--grid-primary)}.table-mode .subtotal-row td.subtotal-cell{font-weight:500}.table-mode .subtotal-row td.subtotal-cell .subtotal-label{font-weight:600;color:var(--grid-primary)}.table-mode .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .subtotal-row.subtotal-bold td{font-weight:600!important;font-style:normal!important}.table-mode .subtotal-row.subtotal-italic td{font-style:italic!important}.table-mode .subtotal-row.subtotal-italic td:first-child{font-weight:600!important}.table-mode .subtotal-row.subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .subtotal-row.subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .subtotal-row.subtotal-highlighted:hover,.table-mode .subtotal-row.subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row-shell{width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .subtotal-row-shell::-webkit-scrollbar{display:none}.board-mode-host{overflow:hidden;display:flex;flex-direction:column;height:var(--grid-height, 600px)}.board-mode-host .board-view-container{display:flex;flex-direction:column;flex:1;min-height:0}.board-mode-host .board-sort-bar{display:flex;align-items:center;gap:6px;padding:8px 16px;flex-shrink:0;border-bottom:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);overflow-x:auto}.board-mode-host .board-sort-bar .board-sort-label{font-size:12px;font-weight:500;color:var(--grid-on-surface-variant, #49454f);white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);color:var(--grid-on-surface, #1d1b20);font-size:12px;white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip-active{background:var(--grid-surface-container);border-color:var(--grid-outline, #79757f);color:var(--grid-on-surface, #1d1b20)}.board-mode-host .board-sort-bar .board-sort-chip-label{pointer-events:none}.board-mode-host .board-sort-bar .board-sort-chip-arrow{font-size:10px;line-height:1;cursor:pointer;padding:2px;border-radius:4px}.board-mode-host .board-sort-bar .board-sort-chip-arrow:hover{background:#00000014}.board-mode-host .board-sort-bar .board-sort-chip-priority{font-size:9px;font-weight:700;background:var(--grid-primary, #6750a4);color:var(--grid-on-primary, #ffffff);border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center}.board-mode-host .board-sort-bar .board-sort-chip-remove{font-size:10px;cursor:pointer;padding:2px;border-radius:4px;color:var(--grid-on-surface-variant, #49454f)}.board-mode-host .board-sort-bar .board-sort-chip-remove:hover{background:#00000014;color:var(--grid-error, #b3261e)}.board-mode-host .board-sort-bar .board-sort-add-btn{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px dashed var(--grid-outline-variant, #cac4d0);background:transparent;color:var(--grid-on-surface-variant, #49454f);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease,border-color .15s ease}.board-mode-host .board-sort-bar .board-sort-add-btn .board-sort-add-icon{font-size:14px;width:14px;height:14px}.board-mode-host .board-sort-bar .board-sort-add-btn:hover{background:var(--grid-surface-container-low, #f7f2fa);border-color:var(--grid-primary, #6750a4);color:var(--grid-primary, #6750a4)}.board-mode-host .board-sort-bar .board-sort-clear{display:inline-flex;align-items:center;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-error, #b3261e);background:transparent;color:var(--grid-error, #b3261e);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease}.board-mode-host .board-sort-bar .board-sort-clear:hover{background:#b3261e14}.board-mode-host .board-columns-wrapper{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--board-col-min-width, 300px),1fr));grid-auto-rows:var(--board-column-height, 420px);gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start}.board-mode-host .board-column{height:var(--board-column-height, 420px);min-width:0;display:flex;flex-direction:column;background:var(--grid-surface-container, #f3edf7);border-radius:12px;min-height:0;overflow:hidden}.board-mode-host .board-column.board-column-accented{border-top:3px solid var(--board-group-color, transparent)}.board-mode-host .board-column .column-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;font-weight:600;flex-shrink:0}.board-mode-host .board-column .column-header:hover{background-color:transparent}.board-mode-host .board-column .column-header .column-header-title{font-size:15px;font-weight:700;letter-spacing:.2px;line-height:1.2;color:var(--grid-on-surface, #1d1b20);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.board-mode-host .board-column .column-header .column-header-title-cell{display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:clip}.board-mode-host .board-column .column-header .column-header-title-cell data-cell,.board-mode-host .board-column .column-header .column-header-title-cell .data-cell-component,.board-mode-host .board-column .column-header .column-header-title-cell .container{padding:0!important;margin:0!important;border:none!important;background:transparent!important;min-height:0!important;height:auto!important;width:auto!important;max-width:100%!important;overflow:visible!important}.board-mode-host .board-column .column-header .column-header-title-cell .status-display,.board-mode-host .board-column .column-header .column-header-title-cell .status-display-content,.board-mode-host .board-column .column-header .column-header-title-cell .status-text,.board-mode-host .board-column .column-header .column-header-title-cell .tag-display,.board-mode-host .board-column .column-header .column-header-title-cell .tag-text{max-width:none!important;overflow:visible!important;text-overflow:clip!important}.board-mode-host .board-column .column-header .column-header-count{font-size:10px;font-weight:600;color:var(--grid-on-surface-variant, #49454f);background:var(--grid-surface-variant, #e7e0ec);border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:1;min-height:0;height:0}.board-mode-host .board-card-container{box-sizing:border-box;overflow:hidden;border-radius:8px;transition:background-color .15s ease,box-shadow .15s ease}.board-mode-host .board-card-container.show-row-lines{box-shadow:inset 0 0 0 var(--grid-divider-width, 1px) var(--grid-divider-color, var(--grid-outline, #e0e0e0))}.board-mode-host .board-card-container:hover{background-color:var(--eru-board-card-hover-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 8%, transparent))}.board-mode-host .board-card-container.selected{background-color:var(--eru-board-card-selected-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 14%, transparent));box-shadow:inset 0 0 0 2px var(--eru-board-card-selected-outline, var(--mat-sys-primary, #1976d2))}.board-mode-host .board-card{height:calc(100% - 8px);overflow:hidden;cursor:pointer}.board-mode-host .board-card mat-card-title{font-size:13px}.board-mode-host .board-card mat-card-subtitle{font-size:12px}.board-mode-host .board-card-field{display:flex;flex-direction:column;margin-bottom:4px}.board-mode-host .board-field-label{font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.board-mode-host .board-ghost-card{margin:8px;padding:16px;background:var(--grid-surface, #fef7ff);border-radius:8px;animation:board-pulse 1.5s ease-in-out infinite}.board-mode-host .board-ghost-line{height:12px;background:var(--grid-surface-variant, #e7e0ec);border-radius:4px;margin-bottom:8px}.board-mode-host .board-ghost-line--short{width:60%}@keyframes board-pulse{0%,to{opacity:1}50%{opacity:.5}}th.row-expand-toggle,td.row-expand-toggle{width:40px!important;min-width:40px!important;max-width:40px!important;padding:0!important;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.row-expand-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);transition:transform .15s ease-in-out}.row-expand-icon.expanded{transform:rotate(90deg)}.row-detail{background:var(--grid-surface-container)}.row-detail .row-detail-cell{padding:var(--grid-spacing-sm) var(--grid-spacing-md);border-bottom:1px solid var(--grid-outline-variant)}.row-detail .row-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--grid-spacing-sm) var(--grid-spacing-md)}.row-detail .row-detail-field{display:flex;flex-direction:column;gap:var(--grid-spacing-xxs);min-width:0}.row-detail .row-detail-label{font-size:var(--grid-font-size-caption);color:var(--grid-on-surface-variant);font-weight:500}.row-detail .row-detail-value{min-width:0}.row-detail .row-detail-value data-cell{display:block;width:100%}\n"], dependencies: [{ kind: "component", type: DataCellComponent, selector: "data-cell", inputs: ["eruGridStore", "fieldSize", "columnDatatype", "columnName", "column", "value", "id", "frozenGrandTotalCell", "td", "drillable", "mode", "isEditable", "row", "personCardTemplate"], outputs: ["tdChange"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1$3.ɵɵCdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1$3.ɵɵCdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1$3.ɵɵCdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2$4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i5.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i5.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i5.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: ResizeColumnDirective, selector: "[resizeColumn]", inputs: ["resizeColumn", "index", "columnConfig", "gridConfig"] }, { kind: "directive", type: ColumnDragDirective, selector: "[columnDraggable]", inputs: ["columnDraggable"] }, { kind: "component", type: ColumnDesignPanelComponent, selector: "eru-column-design-panel" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
12948
13750
  }
12949
13751
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: EruGridComponent, decorators: [{
12950
13752
  type: Component,
@@ -12964,7 +13766,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
12964
13766
  ResizeColumnDirective,
12965
13767
  ColumnDragDirective,
12966
13768
  ColumnDesignPanelComponent
12967
- ], template: "<!-- <div style=\"background: #f0f0f0; font-size: 12px; border-bottom: 1px solid #ccc;\">\ncurrentPivotScrollIndex {{currentPivotScrollIndex()}} |\nfirstDataRowIndex {{firstDataRowIndex()}} |\nfirstTr {{firstTr}} |\nmaxDepth {{maxDepth()}}\n</div> -->\n<div class=\"incremental-row-container eru-grid\" #rowContainer [class.pivot-mode]=\"gridStore.isPivotMode()\"\n [class.table-mode]=\"!gridStore.isPivotMode() && !isBoardMode()\" [class.board-mode-host]=\"isBoardMode()\">\n <eru-column-design-panel></eru-column-design-panel>\n <!-- Pivot Mode Template -->\n @if (gridStore.isPivotMode()) {\n <ng-container>\n <div class=\"pivot-container\" style=\"display: flex; flex-direction: column; height: 100%;\"\n [style]=\"'--table-min-height: ' + getInitialMinHeightPx() + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <!-- Debug info for first visible row -->\n\n\n <div class=\"pivot-single-table\"\n style=\"height: 100%; width: 100%; overflow: hidden; display: flex; flex-direction: column;\">\n @if (freezeHeader()) {\n <div #headerScroller class=\"header-shell\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n @if(grandTotalPosition() === 'before' && freezeGrandTotal()) {\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n }\n </table>\n </div>\n }\n <!-- Virtual Scrolled Table Body -->\n <div>\n <cdk-virtual-scroll-viewport #vp [itemSize]=\"dataRowHeight()\" class=\"viewport pivot-viewport\"\n [class.apply-cdk-width]=\"applyCdkWidth()\" (scrolledIndexChange)=\"onPivotScroll($event)\"\n (scroll)=\"onBodyScroll($event)\" style=\"overflow: auto;\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n @if (!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n }\n <!-- Table Body with Virtual Scrolling -->\n <tbody class=\"pivot-tbody\">\n\n <tr *cdkVirtualFor=\"let pivotRow of gridStore.pivotDisplayData(); \n trackBy: trackByPivotRowFn; \n let i = index\" class=\"pivot-row\" [class.subtotal-row]=\"pivotRow._isSubtotal\"\n [class.grand-total-row]=\"pivotRow._isGrandTotal\"\n [class.subtotal-bold]=\"pivotRow._isSubtotal && subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"pivotRow._isSubtotal && subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"pivotRow._isSubtotal && subTotalStyle() === 'highlighted'\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" [attr.data-pivot-row]=\"i\">\n @if ((!pivotRow._isGrandTotal && freezeGrandTotal() ) || (!freezeGrandTotal() )) {\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.pivot-repeated-value]=\"isRepeatedDimensionValue(i, column.name)\"\n [class.pivot-group-start]=\"isPivotGroupStart(i, column.name)\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [class.aggregation]=\"!!column.aggregationFunction\" [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\" [value]=\"pivotRow[column.name]\"\n [column]=\"column\" [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\"\n [isEditable]=\"isEditable()\" [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\"\n [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n } @else {\n <td [style.height.px]=\"dataRowHeight()\" [attr.colspan]=\"getLeafColumns().length\"> </td>\n }\n </tr>\n </tbody>\n </table>\n </cdk-virtual-scroll-viewport>\n\n </div>\n @if (freezeGrandTotal() && grandTotalPosition() === 'after') {\n <div #gtScroller class=\"header-shell gt-shell\" [class.adjust-bottom]=\"!applyCdkWidth()\"\n [class.adjust-bottom-vs]=\"adjustScrollWidth()\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n\n </table>\n </div>\n }\n\n\n </div>\n </div>\n </ng-container>\n } @else if (isBoardMode()) {\n <!-- Board Mode Template -->\n <div class=\"board-view-container\">\n @if(showSortBar()) {\n <div class=\"board-sort-bar\">\n <span class=\"board-sort-label\">Sort by:</span>\n @for (entry of gridStore.sortColumns(); track getFieldName(entry)) {\n <span class=\"board-sort-chip board-sort-chip-active\">\n <span class=\"board-sort-chip-label\">{{getColumnLabel(entry)}}</span>\n <span class=\"board-sort-chip-arrow\" (click)=\"onBoardSortChipToggle($event, entry)\">\n @if(!entry.startsWith('-')) { \u25B2 } @else { \u25BC }\n </span>\n @if(gridStore.sortColumns().length > 1) {\n <span class=\"board-sort-chip-priority\">{{getSortPriority(getFieldName(entry))}}</span>\n }\n <span class=\"board-sort-chip-remove\" (click)=\"onBoardSortChipRemove($event, entry)\">\u2715</span>\n </span>\n }\n <button class=\"board-sort-add-btn\" [matMenuTriggerFor]=\"sortFieldMenu\">\n <mat-icon class=\"board-sort-add-icon\">add</mat-icon> Add field\n </button>\n <mat-menu #sortFieldMenu=\"matMenu\" class=\"board-sort-menu\">\n @for (column of columns(); track column.name) {\n <button mat-menu-item (click)=\"onBoardSortFieldSelect(column)\"\n [disabled]=\"getSortDirection(column.name) !== null\">\n @if(getSortDirection(column.name) !== null) {\n <mat-icon>check</mat-icon>\n } @else {\n <mat-icon></mat-icon>\n }\n {{column.label}}\n </button>\n }\n </mat-menu>\n @if(gridStore.sortColumns().length > 0) {\n <button class=\"board-sort-clear\" (click)=\"onBoardSortClear()\">\u2715 Clear</button>\n }\n </div>\n }\n <div class=\"board-columns-wrapper\">\n @for (group of groups(); track group.id) {\n <div class=\"board-column\" [class.board-column-accented]=\"!!boardGroupColor(group)\"\n [style.--board-group-color]=\"boardGroupColor(group)\">\n <div class=\"column-header\">\n <!-- Render the group value through the same read-only cell renderer a\n data cell uses, so the grouped field's datatype formats itself\n (status/tag pills, dates, numbers) instead of printing raw text. -->\n @if (groupByColumn(); as gcol) {\n <span class=\"column-header-title column-header-title-cell\">\n <data-cell\n [eruGridStore]=\"gridStore\"\n [column]=\"gcol\"\n [columnDatatype]=\"gcol.datatype\"\n [columnName]=\"gcol.name\"\n [value]=\"group.title\"\n [id]=\"'board-group-' + group.id\"\n [fieldSize]=\"0\"\n [isEditable]=\"false\"\n [mode]=\"'board-group-header'\">\n </data-cell>\n </span>\n } @else {\n <span class=\"column-header-title\">{{ group.title }}</span>\n }\n <span class=\"column-header-count\">{{ group.currentLoadedRows || 0 }} of {{ group.totalRowCount || 0 }}</span>\n </div>\n <cdk-virtual-scroll-viewport [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n (scrolledIndexChange)=\"onBoardScrolledIndexChange($event, group)\">\n <div\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); templateCacheSize: 0\"\n class=\"board-card-container\"\n [class.selected]=\"isBoardCardActive(row)\"\n [class.show-row-lines]=\"showRowLines()\"\n [style.height.px]=\"boardCardOuterHeight\"\n [style.padding.px]=\"boardCardPadding\"\n [style.marginBottom.px]=\"boardCardGap\"\n [style.cursor]=\"cursorOnHover() || null\"\n (click)=\"emitRowSelect(row, 'board', group)\">\n <!-- Custom template when consumer provides boardCardTemplate; default card otherwise -->\n <ng-container\n *ngTemplateOutlet=\"boardCardTemplate ?? defaultBoardCard;\n context: { $implicit: row, columns: visibleBoardFields(), group: group }\">\n </ng-container>\n </div>\n </cdk-virtual-scroll-viewport>\n @if (group.isLoading) {\n <div class=\"board-ghost-card\">\n <div class=\"board-ghost-line\"></div>\n <div class=\"board-ghost-line board-ghost-line--short\"></div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else {\n\n <!-- Table Mode Template -->\n <ng-template #excelDownloadIcon>\n <svg class=\"excel-download-icon\" title=\"Download Excel\" (click)=\"onExcelDownloadClick($event)\"\n xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"24px\" height=\"24px\">\n <path fill=\"#169154\" d=\"M29,6H15.744C14.781,6,14,6.781,14,7.744v7.259h15V6z\" />\n <path fill=\"#18482a\" d=\"M14,33.054v7.202C14,41.219,14.781,42,15.743,42H29v-8.946H14z\" />\n <path fill=\"#0c8045\" d=\"M14 15.003H29V24.005000000000003H14z\" />\n <path fill=\"#17472a\" d=\"M14 24.005H29V33.055H14z\" />\n <g>\n <path fill=\"#29c27f\" d=\"M42.256,6H29v9.003h15V7.744C44,6.781,43.219,6,42.256,6z\" />\n <path fill=\"#27663f\" d=\"M29,33.054V42h13.257C43.219,42,44,41.219,44,40.257v-7.202H29z\" />\n <path fill=\"#19ac65\" d=\"M29 15.003H44V24.005000000000003H29z\" />\n <path fill=\"#129652\" d=\"M29 24.005H44V33.055H29z\" />\n </g>\n <path fill=\"#0c7238\"\n d=\"M22.319,34H5.681C4.753,34,4,33.247,4,32.319V15.681C4,14.753,4.753,14,5.681,14h16.638 C23.247,14,24,14.753,24,15.681v16.638C24,33.247,23.247,34,22.319,34z\" />\n <path fill=\"#fff\"\n d=\"M9.807 19L12.193 19 14.129 22.754 16.175 19 18.404 19 15.333 24 18.474 29 16.123 29 14.013 25.07 11.912 29 9.526 29 12.719 23.982z\" />\n </svg>\n </ng-template>\n @if(showExcelDownload() && !showGroupBar()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\n <!-- Scrollable groups container \u2014 plain iteration avoids CDK fixed-height estimation errors -->\n <div #groupsScrollContainer class=\"groups-scroll-container\" (scroll)=\"onGroupsViewportScroll($event)\">\n\n @for (group of groups(); track trackByGroupFn($index, group); let i = $index) {\n <div class=\"group-container\"\n [attr.data-group-id]=\"group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id\">\n <!-- Combined sticky header with group info and table -->\n <div style=\"\n background:var(--grid-surface);\n position: sticky;\n top: 0;\n z-index: 115;\n \">\n @if(showGroupBar()) {\n <div class=\"group-header-row\">\n <div class=\"custom-collapse-header\" (click)=\"toggleGroupCollapse(group.id)\">\n <span class=\"collapse-arrow\" [ngClass]=\"{\n 'rotate-arrow': group.isExpanded,\n }\">\u25BC</span>\n <span class=\"f-12\">\n {{ group?.title || \"\" }}\n {{ group?.currentLoadedRows || 0 }} -\n {{ group?.totalRowCount || 0 }} rows...</span>\n @if(groupByField() && isSortable()) {\n <span class=\"group-sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'asc'\"\n (click)=\"onGroupSortToggle($event, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\"\n [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'desc'\"\n (click)=\"onGroupSortToggle($event, 'desc')\"></span>\n </span>\n </span>\n }\n </div>\n @if(i === 0 && showExcelDownload()) {\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n }\n </div>\n }\n\n @if(freezeHeader() && (group.isExpanded || !showGroupBar())) {\n <div #headerScroller class=\"header-shell\" [attr.data-group-id]=\"'header-shell-' + group.id\"\n [style]=\"'--table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <table class=\"eru-grid-table\" [class.freeze-header]=\"freezeHeader()\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n <!-- Grand Total row after sticky header (position: before) - only for first group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' && hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after sticky header (position: before) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'before' && hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n </table>\n </div>\n }\n </div>\n @if(group.isExpanded || !showGroupBar()) {\n <ng-container>\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"dataRowHeight()\" class=\"viewport table-viewport\"\n (scrolledIndexChange)=\"onScroll($event, group)\" (scroll)=\"onTableBodyScroll($event)\"\n [style]=\"'--table-height: ' + getGroupContentHeight(group.id) + 'px; --table-min-height: ' + getGroupContentHeight(group.id) + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <div class=\"table-wrapper\">\n <table class=\"eru-grid-table\" [class.show-column-lines]=\"showColumnLines()\"\n [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n @if(!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n }\n <!-- Grand Total row after normal header (position: before) - only for first group -->\n @if(!freezeHeader() && enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' &&\n hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after normal header (position: before) -->\n @if(!freezeHeader() && enableColumnSubtotals() && subtotalPositionColumn() === 'before' &&\n hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n <tbody>\n @if (columns(); as columnsList) {\n <!-- <tr *ngIf=\"groupItem.type === 'table-header' && groups().length > 1\" style=\"background:#fafafa\">\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column\" style=\"text-align: center;\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupSelected(groupItem.group?.id || '')\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection($event, groupItem.group?.id || '')\"\n >\n </th>\n }\n <th *ngFor=\"let column of columns(); trackBy: trackByColumnFn;let i =index\"\n style=\"text-align: center;\"\n [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"true\"\n [columnConfig]=\"column\"\n [columnDraggable]=\"i\"\n class=\"column-header\">\n <div class=\"column-drag-handle\"></div>\n {{column.label}} {{column.symbol}}\n </th>\n </tr> -->\n <!-- @if(getRowsForGroup(group.id).length > 0 && group.isExpanded) { -->\n <!-- *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id)(); \n trackBy: trackByRowFn; \n let i = index\" -->\n <!-- @for(row of getRowsForGroupSignal(group.id)(); track trackByRowFn($index, row); let i = $index) { -->\n <ng-container\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); trackBy: trackByRowFn; let i = index\">\n <tr class=\"row-item\" [attr.data-row-id]=\"i\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" (click)=\"emitRowSelect(row, 'table', group)\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\" style=\"text-align: center;\">\n <input type=\"checkbox\" [checked]=\"isRowSelected(row?.entity_id)\"\n (change)=\"toggleRowSelection($event, row)\">\n </td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\" (click)=\"toggleRowExpand(row, i, $event)\">\n <mat-icon class=\"row-expand-icon\" [class.expanded]=\"isRowExpanded(row, i)\">chevron_right</mat-icon>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td #cell [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"data-cell\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\"\n [matTooltipClass]=\"'error-message'\" [matTooltip]=\"datacell.error()?'Error: ' + datacell.error():''\"\n matTooltipPosition=\"below\">\n <div class=\"cell-content\">\n <data-cell #datacell [td]=cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\" [value]=\"(row?.['entity_data']?.[column.name] ?? row?.[column.name]) || ''\" [column]=\"column\"\n [mode]=\"mode()\" [isEditable]=\"isEditable() && column.editable !== false && column.editable !== 'false'\" [drillable]=\"column.enableDrilldown || false\"\n [id]=\"i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n </tr>\n @if(hasHiddenColumns() && isRowExpanded(row, i)) {\n <tr class=\"row-detail\">\n <td class=\"row-detail-cell\" [attr.colspan]=\"rowDetailColspan()\">\n <div class=\"row-detail-grid\">\n @for (hiddenCol of hiddenColumns(); track trackByColumnFn($index, hiddenCol)) {\n <div class=\"row-detail-field\">\n <span class=\"row-detail-label\">{{hiddenCol.label}}</span>\n <div class=\"row-detail-value\">\n <data-cell [fieldSize]=\"hiddenCol.field_size\" [columnDatatype]=\"hiddenCol.datatype\"\n [columnName]=\"hiddenCol.name\" [value]=\"(row?.['entity_data']?.[hiddenCol.name] ?? row?.[hiddenCol.name]) || ''\"\n [column]=\"hiddenCol\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [drillable]=\"hiddenCol.enableDrilldown || false\"\n [id]=\"'detail_' + i + '_' + hiddenCol.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </div>\n }\n </div>\n </td>\n </tr>\n }\n </ng-container>\n <!-- } -->\n <!-- } -->\n @if(group.isLoading && (group.isExpanded || !showGroupBar())) {\n @for(i of [].constructor(ghostRows()); let j = $index; track j) {\n <tr class=\"ghost-loading-row\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n </tr>\n }\n }\n <!-- <tr\n *ngIf=\"getRowsForGroup(group.id).length === 0 && !group.isExpanded\"\n class=\"group-separator\"\n >\n <td [attr.colspan]=\"groupSeperatorColSpan()\" class=\"separator-cell\"></td>\n </tr> -->\n <!-- Subtotal row at end of group (position: after) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'after' && hasSubtotalData(group)) {\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n }\n <!-- Grand Total row at end of group (position: after) - only for last group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'after' && hasGrandTotalData() && i ===\n groups().length - 1) {\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n }\n }\n </tbody>\n </table>\n </div>\n </cdk-virtual-scroll-viewport>\n </ng-container>\n }\n </div>\n }\n </div>\n }\n</div>\n\n<!-- Pivot Table Header Template -->\n<ng-template #pivotTableHead>\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n @if (hasNestedHeaders()) {\n <ng-container>\n @for (headerRow of getHeaderRows(); track headerRow; let rowIndex = $index) {\n <tr class=\"pivot-header pivot-header-container\" [class.pivot-header-level]=\"'level-' + rowIndex\">\n @for (header of headerRow; track trackByHeaderFn($index, header); let colIndex = $index) {\n <th [attr.colspan]=\"header.colspan\" [attr.rowspan]=\"header.rowspan\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable') && header.level === 0 && !isRowDimensionHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header)\" class=\"column-header pivot-column-header nested-header\"\n [class.row-dimension-header]=\"isRowDimensionHeader(header)\"\n [class.column-dimension-header]=\"!isRowDimensionHeader(header)\" [class.expanded]=\"header.isExpanded\"\n [class.collapsed]=\"!header.isExpanded\" [class.sticky-column]=\"isStickyColumn(header.name, colIndex)\"\n [style.position]=\"isStickyColumn(header.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(header.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(header.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto; padding: 8px 6px;\">\n <div class=\"header-content\">\n\n <data-cell [fieldSize]=\"header.field_size\" [columnDatatype]=\"header.dataType\" [columnName]=\"header.name\"\n [value]=\"header.label\" [column]=\"header\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"header.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + $index + '_' + header.name\" [eruGridStore]=\"gridStore\" [row]=\"header\">\n </data-cell>\n <!-- <span class=\"header-label header-wrap-text\">{{header.label}}</span> -->\n <!-- <button *ngIf=\"!isRowDimensionHeader(header)\"\n class=\"collapse-toggle-btn\"\n [title]=\"header.isExpanded ? 'Collapse group' : 'Expand group'\"\n (click)=\"toggleColumnGroup(header.groupKey)\"\n type=\"button\">\n <span class=\"collapse-icon\">+</span>\n </button> -->\n </div>\n </th>\n }\n </tr>\n }\n </ng-container>\n } @else {\n <!-- Simple header fallback -->\n <ng-container>\n <tr class=\"pivot-header\" [class.freeze-header-enabled]=\"freezeHeader()\">\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <th [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\" [columnConfig]=\"column\"\n class=\"column-header pivot-column-header\" [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto;padding: 8px 6px\">\n {{column.label}}\n </th>\n }\n </tr>\n </ng-container>\n }\n\n </thead>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #pivotColGroup>\n <colgroup>\n @for (column of getLeafColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n </colgroup>\n</ng-template>\n\n<ng-template #pivotGrandTotal>\n <tbody class=\"pivot-tbody\">\n @for (pivotRow of gridStore.pivotGrandTotalData(); track trackByPivotRowFn($index, pivotRow); let i = $index) {\n <tr class=\"pivot-row grand-total-row\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"50\" [attr.data-pivot-row]=\"i\">\n <!-- <td colspan=\"20\">{{pivotRow | json}}</td> -->\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [attr.rowspan]=\"getEffectiveRowspan(i, column.name)\" [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.rowspan-cell]=\"getEffectiveRowspan(i, column.name) || 1 > 1\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"50\" [attr.xx]=\"i\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getEffectiveCellValue(i,column.name, pivotRow)\" [column]=\"column\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #tableColGroup>\n <colgroup>\n @if(gridStore.configuration().config.allowSelection) {\n <col style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n }\n @if(shouldShowActionColumn('before')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n @if(hasHiddenColumns()) {\n <col style=\"width: 40px !important; min-width: 40px !important; max-width: 40px !important;\">\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n @if(shouldShowActionColumn('after')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <tr>\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column column-header table-column-header\">\n <input type=\"checkbox\" [checked]=\"isAllGroupsSelected()\" (change)=\"toggleAllGroups($event)\">\n </th>\n }\n @if(shouldShowActionColumn('before')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n @if(hasHiddenColumns()) {\n <th class=\"row-expand-toggle column-header table-column-header\"></th>\n }\n @for (column of visibleColumns(); track trackByColumnFn(i, column); let i = $index) {\n <th [style.width.px]=\"column.field_size\" [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\"\n [columnConfig]=\"column\" [index]=\"i\"\n [columnDraggable]=\"gridStore.isFeatureEnabled('columnReorderable') ? i : null\"\n [style.minWidth.px]=\"column.field_size\" class=\"column-header table-column-header\"\n [class.sortable-header]=\"isSortable()\"\n [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === column.name\"\n [class.sort-asc]=\"isSortable() && getSortDirection(column.name) === 'asc'\"\n [class.sort-desc]=\"isSortable() && getSortDirection(column.name) === 'desc'\">\n @if(gridStore.isFeatureEnabled('columnReorderable')) {\n <div class=\"column-drag-handle\"></div>\n }\n <span class=\"column-label\" [title]=\"column.tool_tip || column.description || ''\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\" title=\"Edit column\" (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n @if(isSortable()) {\n <span class=\"sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'asc'\"\n (click)=\"onSortColumn($event, column, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'desc'\"\n (click)=\"onSortColumn($event, column, 'desc')\"></span>\n </span>\n @if(getSortPriority(column.name) !== null && gridStore.sortColumns().length > 1) {\n <span class=\"sort-priority\">{{getSortPriority(column.name)}}</span>\n }\n </span>\n }\n </th>\n }\n @if(shouldShowActionColumn('after')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n </tr>\n </thead>\n</ng-template>\n\n<!-- Table Subtotal Row Template -->\n<ng-template #tableSubtotal let-group=\"group\">\n <tr class=\"subtotal-row\" [class.subtotal-bold]=\"subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"subTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"subtotal-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getSubtotalValue(group, column.name) === null) {\n <span class=\"subtotal-label\">{{subtotalLabel()}}</span>\n } @else {\n @if(getSubtotalValue(group, column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getSubtotalValue(group, column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'subtotal_' + group.id + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"group.subtotal\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- Table Grand Total Row Template -->\n<ng-template #tableGrandTotal>\n <tr class=\"grand-total-row\" [class.grand-total-bold]=\"grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"grandTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"grand-total-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getGrandTotalValue(column.name) === null) {\n <span class=\"grand-total-label\">Grand Total</span>\n } @else {\n @if(getGrandTotalValue(column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getGrandTotalValue(column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'grandtotal_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"gridStore.rowGrandTotal()\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- \u2500\u2500\u2500 Default board card template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Used when no boardCardTemplate is passed to <eru-grid>.\n Context: { $implicit: Row, columns: Field[], group: RowGroup }\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<ng-template #defaultBoardCard let-row let-columns=\"columns\" let-group=\"group\">\n <mat-card class=\"board-card\">\n <mat-card-content>\n @for (column of columns; track column.name) {\n @if ((row?.entity_data?.[column.name] ?? row?.[column.name]) !== undefined) {\n <div class=\"board-card-field\">\n <span class=\"board-field-label\">{{ column.label }}</span>\n <data-cell\n [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\"\n [column]=\"column\"\n [value]=\"row?.entity_data?.[column.name] ?? row?.[column.name]\"\n [id]=\"row?.entity_id + '_' + column.name\"\n [eruGridStore]=\"gridStore\"\n [mode]=\"'board'\"\n [row]=\"row\">\n </data-cell>\n </div>\n }\n }\n </mat-card-content>\n <mat-card-actions align=\"end\">\n <button mat-icon-button (click)=\"onActionClick($event, row)\">\n <mat-icon>more_horiz</mat-icon>\n </button>\n </mat-card-actions>\n </mat-card>\n</ng-template>", styles: ["@charset \"UTF-8\";:root{--grid-primary: #6750a4;--grid-on-primary: #ffffff;--grid-surface: #fef7ff;--grid-surface-variant: #e7e0ec;--grid-surface-container: #f3edf7;--grid-surface-container-high: #ede7f0;--grid-on-surface: #1d1b20;--grid-on-surface-variant: #49454f;--grid-outline: #79757f;--grid-outline-variant: #cac4d0;--grid-error: #ba1a1a;--grid-error-container: #ffdad6}:host,eru-grid{display:block!important;width:100%;height:100%;flex:1 1 0%;min-height:var(--grid-height, 300px);font-family:var(--grid-font-family);--grid-font-family: \"Poppins\", \"Roboto\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;--grid-font-size-body: 12px;--grid-font-size-caption: 12px !important;--grid-line-height-body: 1;--grid-aggregation-text-align: right;--grid-number-text-align: right;--grid-spacing-xxs: 2px;--grid-spacing-xs: 4px;--grid-spacing-sm: 8px;--grid-spacing-md: 16px;--grid-spacing-lg: 24px;--grid-border-radius: 4px;--grid-elevation-1: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 1px 3px 1px rgba(0, 0, 0, .15);--grid-elevation-2: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 2px 6px 2px rgba(0, 0, 0, .15);--grid-row-hover: var(--grid-surface-variant);--grid-row-selected: var(--grid-surface-container-high);--grid-zebra-odd: transparent;--grid-zebra-even: transparent;--grid-focus-ring: var(--grid-primary);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: var(--grid-font-size-caption);--grid-header-padding-x: 8px;--grid-header-padding-y: 12px;--grid-font-feature-numeric: normal;--grid-cell-padding-x: var(--grid-spacing-xs);--grid-cell-padding-y: var(--grid-spacing-xxs);--grid-tint-subtle: rgba(0, 0, 0, .025);--grid-tint-soft: rgba(0, 0, 0, .045);--grid-tint-strong: rgba(0, 0, 0, .08);--grid-radius-outer: 0;--grid-shadow-outer: none;--grid-divider-color: var(--grid-outline-variant);--grid-divider-width: 1px;--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-pill-radius: 999px;--grid-pill-padding-y: 3px;--grid-pill-padding-x: 10px;--grid-pill-font-size: 11px;--grid-pill-font-weight: 500;--grid-priority-dot-size: 8px;--grid-avatar-size: 24px;--grid-avatar-font-size: 10px;--grid-avatar-font-weight: 600;border-radius:var(--grid-radius-outer);box-shadow:var(--grid-shadow-outer)}eru-grid[data-preset=default]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .06em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-soft);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=modern]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: 13px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 16px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 12px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px}eru-grid[data-preset=compact]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 11px;--grid-header-padding-y: 4px;--grid-header-padding-x: 8px;--grid-cell-padding-y: 3px;--grid-cell-padding-x: 8px;--grid-font-size-body: 11px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 1px;--grid-pill-padding-x: 6px;--grid-pill-font-size: 10px}eru-grid[data-preset=bold]{--grid-header-bg: var(--grid-surface-container-high);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 700;--grid-header-text-transform: none;--grid-header-font-size: 13px;--grid-header-padding-y: 14px;--grid-header-padding-x: 12px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 12px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-strong);--grid-divider-width: 1px;--grid-radius-outer: 2px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=financial]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .08em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-zebra-odd: transparent;--grid-zebra-even: var(--grid-tint-subtle);--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=elevated]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 12px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 14px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 16px;--grid-shadow-outer: 0 1px 3px rgba(0, 0, 0, .06), 0 10px 28px rgba(0, 0, 0, .07);--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px;overflow:hidden}.group-container{padding-bottom:8px}.column-header.design-clickable .design-edit-icon{font-size:16px;width:16px;height:16px;margin-left:4px;opacity:.45;vertical-align:middle;cursor:pointer}.column-header.design-clickable:hover .design-edit-icon,.column-header.design-clickable .design-edit-icon:hover{opacity:1}.column-header.design-selected{background-color:var(--grid-primary-light, rgba(63, 81, 181, .12))}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-height, 300px);max-height:none;overflow:auto;position:relative;background-color:var(--grid-surface);border-radius:var(--grid-border-radius);font-family:var(--grid-font-family)}.viewport{height:100%;min-height:300px;overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface);scrollbar-gutter:stable}.viewport.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.groups-viewport{height:100%;min-height:300px}.groups-scroll-container{height:var(--grid-height, 600px);overflow-y:auto;overflow-x:hidden}.table-viewport{background-color:var(--grid-surface);height:var(--table-height, auto);min-height:var(--table-min-height, 100px);overflow-x:auto;overflow-y:auto}.pivot-viewport{min-height:var(--table-min-height, 300px);overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface)}.pivot-viewport .cdk-virtual-scroll-content-wrapper{width:auto;height:auto}.table-wrapper{min-width:100%;overflow-x:visible}.incremental-row-container .eru-grid-table,.eru-grid-table{width:100%!important;border-collapse:separate;border-spacing:0;table-layout:fixed!important;background-color:var(--grid-surface);color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);line-height:var(--grid-line-height-body)}.eru-grid-table th,.eru-grid-table td{text-align:left;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:var(--grid-on-surface);min-width:0;max-width:100%!important;box-sizing:border-box;position:relative}.eru-grid-table th{background-color:var(--grid-header-bg, var(--grid-surface-container))}thead.eru-wrap-headers th{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;height:auto}thead.eru-wrap-headers th .column-label,thead.eru-wrap-headers th .header-label{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;word-break:break-word;overflow-wrap:anywhere}.eru-grid-table tbody td{background-color:transparent}.eru-grid-table thead{background-color:var(--grid-header-bg, var(--grid-surface-container));transform:translateZ(0);will-change:transform;backface-visibility:hidden}.eru-grid-table thead.freeze-header-enabled{position:sticky!important;top:0!important;z-index:100!important}.eru-grid-table thead th{background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface));font-family:var(--grid-font-family);font-weight:var(--grid-header-font-weight);font-size:var(--grid-header-font-size)}.checkbox-column{width:50px;min-width:50px;max-width:50px;text-align:center;background-color:var(--grid-surface-container)}.checkbox-column input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--grid-primary);border-radius:var(--grid-border-radius)}.checkbox-column input[type=checkbox]:focus{outline:2px solid var(--grid-primary);outline-offset:2px}.action-column{width:40px;min-width:40px;max-width:40px;text-align:center;background-color:var(--grid-surface-container)}.action-column mat-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);cursor:pointer}.action-column mat-icon:hover{color:var(--grid-primary)}.group-header{background-color:var(--grid-surface-container);color:var(--grid-on-surface);font-size:var(--grid-font-size-caption);font-weight:500;border-bottom:1px solid var(--grid-outline);cursor:pointer;transition:background-color .2s ease}.group-header:hover{background-color:var(--grid-surface-container-high)}.group-header .group-title{font-weight:600;color:var(--grid-primary)}.group-header .group-row-count{color:var(--grid-on-surface-variant);font-size:var(--grid-font-size-caption);margin-left:var(--grid-spacing-sm)}.row-item{background-color:var(--grid-surface);transition:background-color .15s ease}.row-item:nth-child(odd){background-color:var(--grid-zebra-odd, var(--grid-surface))}.row-item:nth-child(2n){background-color:var(--grid-zebra-even, var(--grid-surface))}.row-item:hover{background-color:var(--grid-row-hover)}.required-toggle-row{background-color:var(--grid-surface-container, #f3edf7);border-bottom:1px solid var(--grid-outline-variant, #cac4d0)}.required-toggle-row .required-toggle-cell{padding:4px 8px!important;text-align:center;vertical-align:middle;position:relative}.required-toggle-row .required-toggle-cell .required-label{position:absolute;top:2px;left:4px;font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;text-transform:lowercase}.required-toggle-row .required-toggle-cell mat-checkbox{display:flex;justify-content:center;align-items:center}.table-column-header{padding:var(--grid-header-padding-y) var(--grid-header-padding-x)}.column-header{font-weight:var(--grid-header-font-weight);text-transform:var(--grid-header-text-transform);letter-spacing:var(--grid-header-letter-spacing);text-align:center!important;font-size:var(--grid-header-font-size);position:relative;-webkit-user-select:none;user-select:none;background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface))}.column-header:hover{background-color:var(--grid-header-hover-bg, var(--grid-surface-container-high))}.column-drag-handle{position:absolute;left:0;top:0;bottom:0;width:12px;cursor:grab;opacity:0;transition:opacity .2s ease,background-color .2s ease;z-index:2;display:flex;align-items:center;justify-content:center;border-right:1px solid transparent}.column-drag-handle:after{content:\"\\22ee\\22ee\";font-size:14px;color:var(--grid-on-surface-variant);transform:rotate(90deg)}.column-drag-handle:hover{background-color:var(--grid-surface-container-high);border-right-color:var(--grid-outline)}.column-header:hover .column-drag-handle{opacity:1}.column-drag-handle:active{cursor:grabbing}.sortable-header{cursor:pointer}.sortable-header .column-label{flex:1}.sortable-header .sort-indicator{display:inline-flex;align-items:center;gap:2px;margin-left:4px;cursor:pointer;vertical-align:middle;opacity:0;transition:opacity .15s ease}.sortable-header .sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.sortable-header .sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.sortable-header .sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-active{opacity:1}.sortable-header .sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-priority{font-size:9px;font-weight:600;color:var(--grid-primary, #6750a4);line-height:1;min-width:12px;text-align:center}.sortable-header:hover .sort-indicator,.sortable-header.sort-asc .sort-indicator,.sortable-header.sort-desc .sort-indicator{opacity:1}.sortable-header:hover .sort-indicator .sort-tri:not(.sort-tri-active){opacity:.6}.sort-asc,.sort-desc{background-color:var(--grid-surface-container-low, rgba(103, 80, 164, .04))}.dragging{opacity:1;background-color:var(--grid-surface-container);box-shadow:var(--grid-elevation-2)}.drag-over{background-color:var(--grid-surface-container);border-color:var(--grid-primary)}.data-cell{background-color:transparent;color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);font-feature-settings:var(--grid-font-feature-numeric);padding:var(--grid-cell-padding-y) var(--grid-cell-padding-x)}.cell-content{align-items:center}.cell-content .mdc-text-field{padding:0px var(--grid-spacing-xxs)!important}.cell-display-text{align-items:center;padding:0px var(--grid-spacing-xs)}.ghost-loading-row{background-color:transparent}.ghost-cell-container{padding:var(--grid-spacing-sm)}.ghost-cell{height:20px;width:100%;background-color:var(--grid-surface-container);animation:pulse 1.5s ease-in-out infinite;border-radius:var(--grid-border-radius)}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.column-resizer{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;background-color:transparent;transition:background-color .2s ease}.column-resizer:hover{background-color:var(--grid-primary)}.group-separator{height:var(--grid-spacing-sm);background-color:var(--grid-surface-variant)}.group-separator .separator-cell{background-color:var(--grid-surface-variant);border:none;height:var(--grid-spacing-sm)}.error-state{background-color:var(--grid-error-container);color:var(--grid-error);border-color:var(--grid-error)}.error-message{background-color:var(--grid-error);color:#fff;padding:var(--grid-spacing-sm);border-radius:var(--grid-border-radius);font-size:var(--grid-font-size-caption)}.incremental-row-container .eru-grid-table tbody,.incremental-row-container .eru-grid-table{position:relative}.incremental-row-container .eru-grid-table.show-column-lines{border-right:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important;border-top:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table.show-column-lines:not(.freeze-header){border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table:not(.show-column-lines){border:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table thead:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:calc(var(--grid-divider-width, 1px) * 2);background-color:var(--grid-divider-color, var(--grid-outline, #e0e0e0));pointer-events:none;z-index:10}.incremental-row-container .eru-grid-table.show-column-lines thead th,.incremental-row-container .eru-grid-table.show-column-lines tbody td{border-left:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}.incremental-row-container .eru-grid-table.show-row-lines thead th,.incremental-row-container .eru-grid-table.show-row-lines tbody td{border-bottom:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}@media(max-width:768px){.incremental-row-container{height:600px}.eru-grid-table th,.eru-grid-table td{font-size:var(--grid-font-size-caption)}.checkbox-column{width:40px;min-width:40px;max-width:40px}}@media(prefers-contrast:high){.eru-grid-table th,.eru-grid-table td{border-width:2px}.row-item:hover{border-width:2px;border-color:var(--grid-primary)}}@media(prefers-reduced-motion:reduce){.row-item,.column-drag-handle,.ghost-cell{transition:none;animation:none}}.pivot-table .nested-header{text-align:center;font-weight:600;background:var(--grid-surface-container)}.pivot-table .nested-header.row-dimension-header{background:var(--grid-surface-container);font-weight:600}.pivot-table .pivot-header-leafcols{padding:0;margin:0;height:0}.pivot-table .pivot-header-level.level-0 .nested-header{font-size:14px;padding:12px 8px}.pivot-table .pivot-header-level.level-1 .nested-header{font-size:13px;padding:10px 6px}.pivot-table .pivot-header-level.level-2 .nested-header{font-size:12px;padding:8px 4px}.pivot-table .nested-header:hover{background:var(--grid-surface-variant);color:var(--grid-primary);transition:all .2s ease}.pivot-table .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-table .pivot-cell-content{display:flex;justify-content:center;align-items:center;min-height:38px}.pivot-table .pivot-repeated-value .cell-content,.pivot-table .pivot-repeated-value .pivot-cell-content{visibility:hidden}.pivot-table .pivot-group-start.row-dimension-cell{border-top:1px solid var(--grid-outline, #79757f)}.pivot-mode .incremental-row-container{display:flex;flex-direction:column;height:auto;max-height:85vh;overflow:auto}.pivot-mode .h-shell{position:relative;width:calc(100% - var(--scrollbar-width, 17px))!important;top:0;z-index:1;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .h-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell{position:relative;bottom:50px;flex-shrink:0;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .gt-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell table{border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.pivot-mode .gt-shell.adjust-bottom-vs{bottom:66px!important}.pivot-mode .gt-shell.adjust-bottom:not(.adjust-bottom-vs){bottom:calc(66px - var(--scrollbar-width, 17px))!important}.pivot-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .header-shell::-webkit-scrollbar{display:none}.pivot-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.pivot-mode .header-shell .eru-grid-table{margin-bottom:0;width:100%;table-layout:fixed}.pivot-mode .header-shell .eru-grid-table thead{background:var(--grid-surface-container)}.pivot-mode .header-shell .eru-grid-table thead th{background:var(--grid-surface-container);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .header-shell .eru-grid-table thead th.sticky-column{position:sticky;background:var(--grid-surface-container);z-index:111}.pivot-mode .header-shell .eru-grid-table tbody td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.pivot-mode .pivot-table{width:auto!important;min-width:100%!important;table-layout:fixed!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important}.pivot-mode .pivot-table td,.pivot-mode .pivot-table th{box-sizing:border-box!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-table{table-layout:fixed!important;width:100%!important}.pivot-mode .pivot-table *{max-width:var(--col-width)!important;box-sizing:border-box!important}.pivot-mode .pivot-table colgroup{width:100%!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex-basis:var(--col-width)!important;flex:0 0 var(--col-width)!important}.pivot-mode .pivot-table table{width:100%!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important}.pivot-mode .pivot-table[style*=--table-total-width]{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:0 0 var(--col-width)!important;flex-basis:var(--col-width)!important;flex-grow:0!important;flex-shrink:0!important;overflow:hidden!important}.pivot-mode .pivot-table tbody td,.pivot-mode .pivot-table thead th{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.pivot-mode .pivot-table .cell-content,.pivot-mode .pivot-table data-cell{width:100%!important;max-width:100%!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;display:block!important}.pivot-mode .pivot-table table{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-tbody tr.pivot-row{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important}.pivot-mode .pivot-tbody tr.pivot-row:hover{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-tbody tr.pivot-row:nth-child(2n){background-color:#00000005}.pivot-mode .pivot-tbody tr.pivot-row td{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content{min-height:calc(var(--grid-data-row-height, 50px) - 2px);display:flex;align-items:center;justify-content:center}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content data-cell{width:100%;min-height:calc(var(--grid-data-row-height, 50px) - 4px);display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0}.pivot-mode .pivot-cell{vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-mode .pivot-cell .cell-content{display:flex;justify-content:center;align-items:center;min-height:var(--grid-header-row-height, 40px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.pivot-mode .pivot-table .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.pivot-mode .pivot-table .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .subtotal-row td:first-child{color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row td.aggregated-value{font-weight:500;color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.pivot-mode .pivot-table .subtotal-bold td{font-weight:600!important;font-style:normal!important}.pivot-mode .pivot-table .subtotal-bold td.aggregated-value{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td{font-style:italic!important}.pivot-mode .pivot-table .subtotal-italic td:first-child{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.pivot-mode .pivot-table .subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted td.aggregated-value{font-weight:500!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted:hover,.pivot-mode .pivot-table .subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700;font-size:var(--grid-font-size-body)}.pivot-mode .pivot-table .grand-total-row td{background-color:var(--grid-surface-container-high)!important;color:var(--grid-on-surface)}.pivot-mode .pivot-table .grand-total-row td:first-child{font-style:normal;font-weight:800;color:var(--grid-primary)}.pivot-mode .pivot-table .grand-total-row td.aggregated-value{font-weight:500;color:var(--grid-primary);font-family:Roboto Mono,monospace}.pivot-mode .pivot-table .grand-total-row:hover,.pivot-mode .pivot-table .grand-total-row:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-bold td{font-weight:700!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-bold td.aggregated-value{font-weight:700!important}.pivot-mode .pivot-table .grand-total-italic td,.pivot-mode .pivot-table .grand-total-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted{background-color:var(--grid-primary)!important;box-shadow:var(--grid-elevation-2)!important}.pivot-mode .pivot-table .grand-total-highlighted td{background-color:var(--grid-primary)!important;color:var(--grid-on-primary)!important;font-weight:500!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-highlighted td.aggregated-value{color:var(--grid-on-primary)!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted:hover,.pivot-mode .pivot-table .grand-total-highlighted:hover td{background-color:var(--grid-primary)!important}.pivot-mode .pivot-table .collapsible-header{position:relative}.pivot-mode .pivot-table .collapsible-header .header-content{display:flex;align-items:center;justify-content:space-between;gap:var(--grid-spacing-xs);padding:var(--grid-spacing-xs) var(--grid-spacing-sm)}.pivot-mode .pivot-table .collapsible-header .header-label{flex:1;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn{background:none;border:none;cursor:pointer;padding:var(--grid-spacing-xxs);margin:0;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--grid-border-radius);color:var(--grid-on-surface-variant);transition:all .2s ease;font-size:12px;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:hover{background-color:var(--grid-surface-container);color:var(--grid-primary);transform:scale(1.1)}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:focus{outline:2px solid var(--grid-primary);outline-offset:1px}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn .collapse-icon{display:block;line-height:1;font-family:monospace;font-size:14px}.pivot-mode .pivot-table .collapsible-header.expanded .collapse-toggle-btn .collapse-icon{color:var(--grid-primary)}.pivot-mode .pivot-table .collapsible-header.collapsed{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .header-label{font-style:italic;color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .collapse-toggle-btn .collapse-icon{color:var(--grid-outline)}.pivot-mode .pivot-table .collapsible-header:hover{background-color:var(--grid-surface-container)}.pivot-mode .pivot-table .collapsible-header:hover .header-label{color:var(--grid-on-surface)}.pivot-mode .pivot-table .pivot-single-table{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden;min-height:var(--table-min-height)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container{flex-shrink:0;background:var(--grid-surface)!important;overflow-x:auto;overflow-y:hidden;min-height:100px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table{width:auto;min-width:100%;height:auto!important;min-height:100px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th{background:var(--grid-surface-container)!important;padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:40px!important;height:auto!important;position:relative;visibility:visible!important;color:var(--grid-on-surface)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:101!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container{flex:1;overflow:auto;min-height:300px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-viewport{height:100%!important;width:100%!important;overflow-x:auto!important;overflow-y:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table{width:auto;min-width:100%;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td{padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:32px!important;height:auto!important;background:var(--grid-surface)!important;color:var(--grid-on-surface)!important;visibility:visible!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td.sticky-column,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:100!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr{height:auto!important;min-height:50px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr.pivot-row,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr.pivot-row{visibility:visible!important;display:table-row!important}.pivot-mode .pivot-table .collapsed-column-group{background-color:var(--grid-surface-container);border-left:3px solid var(--grid-primary)}.pivot-mode .pivot-table .collapsed-column-group:hover{background-color:var(--grid-surface-container-high)}.pivot-row.subtotal-row{background-color:var(--grid-surface-variant);font-weight:500}.pivot-row.subtotal-row.subtotal-bold{font-weight:500}.pivot-row.subtotal-row.subtotal-italic{font-style:italic}.pivot-row.subtotal-row.subtotal-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.grand-total-row{background-color:var(--grid-surface-container);font-weight:600}.pivot-row.grand-total-row.grand-total-bold{font-weight:800}.pivot-row.grand-total-row.grand-total-italic{font-style:italic}.pivot-row.grand-total-row.grand-total-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.first-visible-row{background-color:#6750a41a!important;position:relative}.pivot-row.first-visible-row:before{content:\"\\1f441\\fe0f First Visible\";position:absolute;top:-20px;left:0;background:var(--grid-primary);color:var(--grid-on-primary);padding:2px 6px;font-size:10px;border-radius:2px;z-index:1000}.header-wrap-text{white-space:pre-wrap;word-break:auto-phrase}.group-header-row{display:flex;align-items:center;justify-content:space-between;width:100%;padding-right:12px}.custom-collapse-header{background-color:var(--grid-surface-variant);padding:8px 20px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:pointer;display:flex;width:fit-content;align-items:center;-webkit-user-select:none;user-select:none;min-width:200px;margin-bottom:10px;position:sticky;left:1px;z-index:116}.custom-collapse-header .collapse-arrow{display:inline-block;margin-right:8px;font-size:12px;color:var(--grid-on-surface-variant);transition:transform .2s ease;transform:rotate(0)}.custom-collapse-header .collapse-arrow.rotate-arrow{transform:rotate(270deg)}.custom-collapse-header .f-12{font-size:12px;color:var(--grid-on-surface)}.custom-collapse-header .group-sort-indicator{display:inline-flex;align-items:center;margin-left:8px}.custom-collapse-header .group-sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.custom-collapse-header .group-sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.custom-collapse-header .group-sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-active{opacity:1}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.excel-download-icon{cursor:pointer}.excel-download-icon:hover{opacity:.75}.excel-download-bar{display:flex;justify-content:flex-end;padding:4px 12px}.table-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .header-shell::-webkit-scrollbar{display:none}.table-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.table-mode .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.table-mode .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.table-mode .subtotal-row td:first-child{color:var(--grid-primary)}.table-mode .subtotal-row td.subtotal-cell{font-weight:500}.table-mode .subtotal-row td.subtotal-cell .subtotal-label{font-weight:600;color:var(--grid-primary)}.table-mode .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .subtotal-row.subtotal-bold td{font-weight:600!important;font-style:normal!important}.table-mode .subtotal-row.subtotal-italic td{font-style:italic!important}.table-mode .subtotal-row.subtotal-italic td:first-child{font-weight:600!important}.table-mode .subtotal-row.subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .subtotal-row.subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .subtotal-row.subtotal-highlighted:hover,.table-mode .subtotal-row.subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row-shell{width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .subtotal-row-shell::-webkit-scrollbar{display:none}.board-mode-host{overflow:hidden;display:flex;flex-direction:column;height:var(--grid-height, 600px)}.board-mode-host .board-view-container{display:flex;flex-direction:column;flex:1;min-height:0}.board-mode-host .board-sort-bar{display:flex;align-items:center;gap:6px;padding:8px 16px;flex-shrink:0;border-bottom:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);overflow-x:auto}.board-mode-host .board-sort-bar .board-sort-label{font-size:12px;font-weight:500;color:var(--grid-on-surface-variant, #49454f);white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);color:var(--grid-on-surface, #1d1b20);font-size:12px;white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip-active{background:var(--grid-surface-container);border-color:var(--grid-outline, #79757f);color:var(--grid-on-surface, #1d1b20)}.board-mode-host .board-sort-bar .board-sort-chip-label{pointer-events:none}.board-mode-host .board-sort-bar .board-sort-chip-arrow{font-size:10px;line-height:1;cursor:pointer;padding:2px;border-radius:4px}.board-mode-host .board-sort-bar .board-sort-chip-arrow:hover{background:#00000014}.board-mode-host .board-sort-bar .board-sort-chip-priority{font-size:9px;font-weight:700;background:var(--grid-primary, #6750a4);color:var(--grid-on-primary, #ffffff);border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center}.board-mode-host .board-sort-bar .board-sort-chip-remove{font-size:10px;cursor:pointer;padding:2px;border-radius:4px;color:var(--grid-on-surface-variant, #49454f)}.board-mode-host .board-sort-bar .board-sort-chip-remove:hover{background:#00000014;color:var(--grid-error, #b3261e)}.board-mode-host .board-sort-bar .board-sort-add-btn{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px dashed var(--grid-outline-variant, #cac4d0);background:transparent;color:var(--grid-on-surface-variant, #49454f);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease,border-color .15s ease}.board-mode-host .board-sort-bar .board-sort-add-btn .board-sort-add-icon{font-size:14px;width:14px;height:14px}.board-mode-host .board-sort-bar .board-sort-add-btn:hover{background:var(--grid-surface-container-low, #f7f2fa);border-color:var(--grid-primary, #6750a4);color:var(--grid-primary, #6750a4)}.board-mode-host .board-sort-bar .board-sort-clear{display:inline-flex;align-items:center;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-error, #b3261e);background:transparent;color:var(--grid-error, #b3261e);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease}.board-mode-host .board-sort-bar .board-sort-clear:hover{background:#b3261e14}.board-mode-host .board-columns-wrapper{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--board-col-min-width, 300px),1fr));grid-auto-rows:var(--board-column-height, 420px);gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start}.board-mode-host .board-column{height:var(--board-column-height, 420px);min-width:0;display:flex;flex-direction:column;background:var(--grid-surface-container, #f3edf7);border-radius:12px;min-height:0;overflow:hidden}.board-mode-host .board-column.board-column-accented{border-top:3px solid var(--board-group-color, transparent)}.board-mode-host .board-column .column-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;font-weight:600;flex-shrink:0}.board-mode-host .board-column .column-header:hover{background-color:transparent}.board-mode-host .board-column .column-header .column-header-title{font-size:15px;font-weight:700;letter-spacing:.2px;line-height:1.2;color:var(--grid-on-surface, #1d1b20);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.board-mode-host .board-column .column-header .column-header-title-cell{display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:clip}.board-mode-host .board-column .column-header .column-header-title-cell data-cell,.board-mode-host .board-column .column-header .column-header-title-cell .data-cell-component,.board-mode-host .board-column .column-header .column-header-title-cell .container{padding:0!important;margin:0!important;border:none!important;background:transparent!important;min-height:0!important;height:auto!important;width:auto!important;max-width:100%!important;overflow:visible!important}.board-mode-host .board-column .column-header .column-header-title-cell .status-display,.board-mode-host .board-column .column-header .column-header-title-cell .status-display-content,.board-mode-host .board-column .column-header .column-header-title-cell .status-text,.board-mode-host .board-column .column-header .column-header-title-cell .tag-display,.board-mode-host .board-column .column-header .column-header-title-cell .tag-text{max-width:none!important;overflow:visible!important;text-overflow:clip!important}.board-mode-host .board-column .column-header .column-header-count{font-size:10px;font-weight:600;color:var(--grid-on-surface-variant, #49454f);background:var(--grid-surface-variant, #e7e0ec);border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:1;min-height:0;height:0}.board-mode-host .board-card-container{box-sizing:border-box;overflow:hidden;border-radius:8px;transition:background-color .15s ease,box-shadow .15s ease}.board-mode-host .board-card-container.show-row-lines{box-shadow:inset 0 0 0 var(--grid-divider-width, 1px) var(--grid-divider-color, var(--grid-outline, #e0e0e0))}.board-mode-host .board-card-container:hover{background-color:var(--eru-board-card-hover-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 8%, transparent))}.board-mode-host .board-card-container.selected{background-color:var(--eru-board-card-selected-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 14%, transparent));box-shadow:inset 0 0 0 2px var(--eru-board-card-selected-outline, var(--mat-sys-primary, #1976d2))}.board-mode-host .board-card{height:calc(100% - 8px);overflow:hidden;cursor:pointer}.board-mode-host .board-card mat-card-title{font-size:13px}.board-mode-host .board-card mat-card-subtitle{font-size:12px}.board-mode-host .board-card-field{display:flex;flex-direction:column;margin-bottom:4px}.board-mode-host .board-field-label{font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.board-mode-host .board-ghost-card{margin:8px;padding:16px;background:var(--grid-surface, #fef7ff);border-radius:8px;animation:board-pulse 1.5s ease-in-out infinite}.board-mode-host .board-ghost-line{height:12px;background:var(--grid-surface-variant, #e7e0ec);border-radius:4px;margin-bottom:8px}.board-mode-host .board-ghost-line--short{width:60%}@keyframes board-pulse{0%,to{opacity:1}50%{opacity:.5}}th.row-expand-toggle,td.row-expand-toggle{width:40px!important;min-width:40px!important;max-width:40px!important;padding:0!important;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.row-expand-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);transition:transform .15s ease-in-out}.row-expand-icon.expanded{transform:rotate(90deg)}.row-detail{background:var(--grid-surface-container)}.row-detail .row-detail-cell{padding:var(--grid-spacing-sm) var(--grid-spacing-md);border-bottom:1px solid var(--grid-outline-variant)}.row-detail .row-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--grid-spacing-sm) var(--grid-spacing-md)}.row-detail .row-detail-field{display:flex;flex-direction:column;gap:var(--grid-spacing-xxs);min-width:0}.row-detail .row-detail-label{font-size:var(--grid-font-size-caption);color:var(--grid-on-surface-variant);font-weight:500}.row-detail .row-detail-value{min-width:0}.row-detail .row-detail-value data-cell{display:block;width:100%}\n"] }]
13769
+ ], template: "<!-- <div style=\"background: #f0f0f0; font-size: 12px; border-bottom: 1px solid #ccc;\">\ncurrentPivotScrollIndex {{currentPivotScrollIndex()}} |\nfirstDataRowIndex {{firstDataRowIndex()}} |\nfirstTr {{firstTr}} |\nmaxDepth {{maxDepth()}}\n</div> -->\n<div class=\"incremental-row-container eru-grid\" #rowContainer [class.pivot-mode]=\"gridStore.isPivotMode()\"\n [class.table-mode]=\"!gridStore.isPivotMode() && !isBoardMode()\" [class.board-mode-host]=\"isBoardMode()\">\n <eru-column-design-panel></eru-column-design-panel>\n <!-- Pivot Mode Template -->\n @if (gridStore.isPivotMode()) {\n <ng-container>\n <div class=\"pivot-container\" style=\"display: flex; flex-direction: column; height: 100%;\"\n [style]=\"'--table-min-height: ' + getInitialMinHeightPx() + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <!-- Debug info for first visible row -->\n\n\n <div class=\"pivot-single-table\"\n style=\"height: 100%; width: 100%; overflow: hidden; display: flex; flex-direction: column;\">\n @if (freezeHeader()) {\n <div #headerScroller class=\"header-shell\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n @if(grandTotalPosition() === 'before' && freezeGrandTotal()) {\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n }\n </table>\n </div>\n }\n <!-- Virtual Scrolled Table Body -->\n <div>\n <cdk-virtual-scroll-viewport #vp [itemSize]=\"dataRowHeight()\" class=\"viewport pivot-viewport\"\n [class.apply-cdk-width]=\"applyCdkWidth()\" (scrolledIndexChange)=\"onPivotScroll($event)\"\n (scroll)=\"onBodyScroll($event)\" style=\"overflow: auto;\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <!-- Column Groups for consistent width -->\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n @if (!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"pivotTableHead\"></ng-container>\n }\n <!-- Table Body with Virtual Scrolling -->\n <tbody class=\"pivot-tbody\">\n\n <tr *cdkVirtualFor=\"let pivotRow of gridStore.pivotDisplayData(); \n trackBy: trackByPivotRowFn; \n let i = index\" class=\"pivot-row\" [class.subtotal-row]=\"pivotRow._isSubtotal\"\n [class.grand-total-row]=\"pivotRow._isGrandTotal\"\n [class.subtotal-bold]=\"pivotRow._isSubtotal && subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"pivotRow._isSubtotal && subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"pivotRow._isSubtotal && subTotalStyle() === 'highlighted'\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" [attr.data-pivot-row]=\"i\">\n @if ((!pivotRow._isGrandTotal && freezeGrandTotal() ) || (!freezeGrandTotal() )) {\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.pivot-repeated-value]=\"isRepeatedDimensionValue(i, column.name)\"\n [class.pivot-group-start]=\"isPivotGroupStart(i, column.name)\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [personCardTemplate]=\"personCardTemplate\" [class.aggregation]=\"!!column.aggregationFunction\" [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\" [value]=\"pivotRow[column.name]\"\n [column]=\"column\" [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\"\n [isEditable]=\"isEditable()\" [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\"\n [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n } @else {\n <td [style.height.px]=\"dataRowHeight()\" [attr.colspan]=\"getLeafColumns().length\"> </td>\n }\n </tr>\n </tbody>\n </table>\n </cdk-virtual-scroll-viewport>\n\n </div>\n @if (freezeGrandTotal() && grandTotalPosition() === 'after') {\n <div #gtScroller class=\"header-shell gt-shell\" [class.adjust-bottom]=\"!applyCdkWidth()\"\n [class.adjust-bottom-vs]=\"adjustScrollWidth()\">\n <table class=\"eru-grid-table pivot-table\"\n [style]=\"'width: auto; min-width: 100%; --table-total-width: ' + getInitialTotalWidth() + 'px'\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"pivotColGroup\"></ng-container>\n\n <ng-container *ngTemplateOutlet=\"pivotGrandTotal\"></ng-container>\n\n </table>\n </div>\n }\n\n\n </div>\n </div>\n </ng-container>\n } @else if (isBoardMode()) {\n <!-- Board Mode Template -->\n <div class=\"board-view-container\">\n @if(showSortBar()) {\n <div class=\"board-sort-bar\">\n <span class=\"board-sort-label\">Sort by:</span>\n @for (entry of gridStore.sortColumns(); track getFieldName(entry)) {\n <span class=\"board-sort-chip board-sort-chip-active\">\n <span class=\"board-sort-chip-label\">{{getColumnLabel(entry)}}</span>\n <span class=\"board-sort-chip-arrow\" (click)=\"onBoardSortChipToggle($event, entry)\">\n @if(!entry.startsWith('-')) { \u25B2 } @else { \u25BC }\n </span>\n @if(gridStore.sortColumns().length > 1) {\n <span class=\"board-sort-chip-priority\">{{getSortPriority(getFieldName(entry))}}</span>\n }\n <span class=\"board-sort-chip-remove\" (click)=\"onBoardSortChipRemove($event, entry)\">\u2715</span>\n </span>\n }\n <button class=\"board-sort-add-btn\" [matMenuTriggerFor]=\"sortFieldMenu\">\n <mat-icon class=\"board-sort-add-icon\">add</mat-icon> Add field\n </button>\n <mat-menu #sortFieldMenu=\"matMenu\" class=\"board-sort-menu\">\n @for (column of columns(); track column.name) {\n <button mat-menu-item (click)=\"onBoardSortFieldSelect(column)\"\n [disabled]=\"getSortDirection(column.name) !== null\">\n @if(getSortDirection(column.name) !== null) {\n <mat-icon>check</mat-icon>\n } @else {\n <mat-icon></mat-icon>\n }\n {{column.label}}\n </button>\n }\n </mat-menu>\n @if(gridStore.sortColumns().length > 0) {\n <button class=\"board-sort-clear\" (click)=\"onBoardSortClear()\">\u2715 Clear</button>\n }\n </div>\n }\n <div class=\"board-columns-wrapper\">\n @for (group of groups(); track group.id) {\n <div class=\"board-column\" [class.board-column-accented]=\"!!boardGroupColor(group)\"\n [style.--board-group-color]=\"boardGroupColor(group)\">\n <div class=\"column-header\">\n <!-- Render the group value through the same read-only cell renderer a\n data cell uses, so the grouped field's datatype formats itself\n (status/tag pills, dates, numbers) instead of printing raw text. -->\n @if (groupByColumn(); as gcol) {\n <span class=\"column-header-title column-header-title-cell\">\n <data-cell\n [eruGridStore]=\"gridStore\"\n [column]=\"gcol\"\n [columnDatatype]=\"gcol.datatype\"\n [columnName]=\"gcol.name\"\n [value]=\"group.title\"\n [id]=\"'board-group-' + group.id\"\n [fieldSize]=\"0\"\n [isEditable]=\"false\"\n [mode]=\"'board-group-header'\">\n </data-cell>\n </span>\n } @else {\n <span class=\"column-header-title\">{{ group.title }}</span>\n }\n <span class=\"column-header-count\">{{ group.currentLoadedRows || 0 }} of {{ group.totalRowCount || 0 }}</span>\n </div>\n <cdk-virtual-scroll-viewport [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n (scrolledIndexChange)=\"onBoardScrolledIndexChange($event, group)\">\n <div\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); templateCacheSize: 0\"\n class=\"board-card-container\"\n [class.selected]=\"isBoardCardActive(row)\"\n [class.show-row-lines]=\"showRowLines()\"\n [style.height.px]=\"boardCardOuterHeight\"\n [style.padding.px]=\"boardCardPadding\"\n [style.marginBottom.px]=\"boardCardGap\"\n [style.cursor]=\"cursorOnHover() || null\"\n (click)=\"emitRowSelect(row, 'board', group)\">\n <!-- Custom template when consumer provides boardCardTemplate; default card otherwise -->\n <ng-container\n *ngTemplateOutlet=\"boardCardTemplate ?? defaultBoardCard;\n context: { $implicit: row, columns: visibleBoardFields(), group: group }\">\n </ng-container>\n </div>\n </cdk-virtual-scroll-viewport>\n @if (group.isLoading) {\n <div class=\"board-ghost-card\">\n <div class=\"board-ghost-line\"></div>\n <div class=\"board-ghost-line board-ghost-line--short\"></div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else {\n\n <!-- Table Mode Template -->\n <ng-template #excelDownloadIcon>\n <svg class=\"excel-download-icon\" title=\"Download Excel\" (click)=\"onExcelDownloadClick($event)\"\n xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"24px\" height=\"24px\">\n <path fill=\"#169154\" d=\"M29,6H15.744C14.781,6,14,6.781,14,7.744v7.259h15V6z\" />\n <path fill=\"#18482a\" d=\"M14,33.054v7.202C14,41.219,14.781,42,15.743,42H29v-8.946H14z\" />\n <path fill=\"#0c8045\" d=\"M14 15.003H29V24.005000000000003H14z\" />\n <path fill=\"#17472a\" d=\"M14 24.005H29V33.055H14z\" />\n <g>\n <path fill=\"#29c27f\" d=\"M42.256,6H29v9.003h15V7.744C44,6.781,43.219,6,42.256,6z\" />\n <path fill=\"#27663f\" d=\"M29,33.054V42h13.257C43.219,42,44,41.219,44,40.257v-7.202H29z\" />\n <path fill=\"#19ac65\" d=\"M29 15.003H44V24.005000000000003H29z\" />\n <path fill=\"#129652\" d=\"M29 24.005H44V33.055H29z\" />\n </g>\n <path fill=\"#0c7238\"\n d=\"M22.319,34H5.681C4.753,34,4,33.247,4,32.319V15.681C4,14.753,4.753,14,5.681,14h16.638 C23.247,14,24,14.753,24,15.681v16.638C24,33.247,23.247,34,22.319,34z\" />\n <path fill=\"#fff\"\n d=\"M9.807 19L12.193 19 14.129 22.754 16.175 19 18.404 19 15.333 24 18.474 29 16.123 29 14.013 25.07 11.912 29 9.526 29 12.719 23.982z\" />\n </svg>\n </ng-template>\n @if(showExcelDownload() && !showGroupBar()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\n <!-- Scrollable groups container \u2014 plain iteration avoids CDK fixed-height estimation errors -->\n <div #groupsScrollContainer class=\"groups-scroll-container\" (scroll)=\"onGroupsViewportScroll($event)\">\n\n @for (group of groups(); track trackByGroupFn($index, group); let i = $index) {\n <div class=\"group-container\"\n [attr.data-group-id]=\"group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id\">\n <!-- Combined sticky header with group info and table -->\n <div style=\"\n background:var(--grid-surface);\n position: sticky;\n top: 0;\n z-index: 115;\n \">\n @if(showGroupBar()) {\n <div class=\"group-header-row\">\n <div class=\"custom-collapse-header\" (click)=\"toggleGroupCollapse(group.id)\">\n <span class=\"collapse-arrow\" [ngClass]=\"{\n 'rotate-arrow': group.isExpanded,\n }\">\u25BC</span>\n <span class=\"f-12\">\n {{ group?.title || \"\" }}\n {{ group?.currentLoadedRows || 0 }} -\n {{ group?.totalRowCount || 0 }} rows...</span>\n @if(groupByField() && isSortable()) {\n <span class=\"group-sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'asc'\"\n (click)=\"onGroupSortToggle($event, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\"\n [class.sort-tri-active]=\"getSortDirection(groupByField()!) === 'desc'\"\n (click)=\"onGroupSortToggle($event, 'desc')\"></span>\n </span>\n </span>\n }\n </div>\n @if(i === 0 && showExcelDownload()) {\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n }\n </div>\n }\n\n @if(freezeHeader() && (group.isExpanded || !showGroupBar())) {\n <div #headerScroller class=\"header-shell\" [attr.data-group-id]=\"'header-shell-' + group.id\"\n [style]=\"'--table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <table class=\"eru-grid-table\" [class.freeze-header]=\"freezeHeader()\"\n [class.show-column-lines]=\"showColumnLines()\" [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n <!-- Grand Total row after sticky header (position: before) - only for first group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' && hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after sticky header (position: before) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'before' && hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n </table>\n </div>\n }\n </div>\n @if(group.isExpanded || !showGroupBar()) {\n <ng-container>\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"dataRowHeight()\" class=\"viewport table-viewport\"\n (scrolledIndexChange)=\"onScroll($event, group)\" (scroll)=\"onTableBodyScroll($event)\"\n [style]=\"'--table-height: ' + getGroupContentHeight(group.id) + 'px; --table-min-height: ' + getGroupContentHeight(group.id) + 'px; --table-total-width: ' + getInitialTotalWidth() + 'px'\">\n <div class=\"table-wrapper\">\n <table class=\"eru-grid-table\" [class.show-column-lines]=\"showColumnLines()\"\n [class.show-row-lines]=\"showRowLines()\">\n <ng-container *ngTemplateOutlet=\"tableColGroup\"></ng-container>\n @if(!freezeHeader()) {\n <ng-container *ngTemplateOutlet=\"tableHeader\"></ng-container>\n }\n <!-- Grand Total row after normal header (position: before) - only for first group -->\n @if(!freezeHeader() && enableColumnGrandTotal() && grandTotalPositionColumn() === 'before' &&\n hasGrandTotalData() && i === 0) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n </tbody>\n }\n <!-- Subtotal row after normal header (position: before) -->\n @if(!freezeHeader() && enableColumnSubtotals() && subtotalPositionColumn() === 'before' &&\n hasSubtotalData(group)) {\n <tbody>\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n </tbody>\n }\n <tbody>\n @if (columns(); as columnsList) {\n <!-- <tr *ngIf=\"groupItem.type === 'table-header' && groups().length > 1\" style=\"background:#fafafa\">\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column\" style=\"text-align: center;\">\n <input\n type=\"checkbox\"\n [checked]=\"isGroupSelected(groupItem.group?.id || '')\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection($event, groupItem.group?.id || '')\"\n >\n </th>\n }\n <th *ngFor=\"let column of columns(); trackBy: trackByColumnFn;let i =index\"\n style=\"text-align: center;\"\n [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"true\"\n [columnConfig]=\"column\"\n [columnDraggable]=\"i\"\n class=\"column-header\">\n <div class=\"column-drag-handle\"></div>\n {{column.label}} {{column.symbol}}\n </th>\n </tr> -->\n <!-- @if(getRowsForGroup(group.id).length > 0 && group.isExpanded) { -->\n <!-- *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id)(); \n trackBy: trackByRowFn; \n let i = index\" -->\n <!-- @for(row of getRowsForGroupSignal(group.id)(); track trackByRowFn($index, row); let i = $index) { -->\n <ng-container\n *cdkVirtualFor=\"let row of getRowsForGroupSignal(group.id === null || group.id === undefined ? '__NULL_GROUP__' : group.id)(); trackBy: trackByRowFn; let i = index\">\n <tr class=\"row-item\" [attr.data-row-id]=\"i\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\" [style.cursor]=\"cursorOnHover() || null\" (click)=\"emitRowSelect(row, 'table', group)\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\" style=\"text-align: center;\">\n <input type=\"checkbox\" [checked]=\"isRowSelected(row?.entity_id)\"\n (change)=\"toggleRowSelection($event, row)\">\n </td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\" (click)=\"toggleRowExpand(row, i, $event)\">\n <mat-icon class=\"row-expand-icon\" [class.expanded]=\"isRowExpanded(row, i)\">chevron_right</mat-icon>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td #cell [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"data-cell\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\"\n [matTooltipClass]=\"'error-message'\" [matTooltip]=\"datacell.error()?'Error: ' + datacell.error():''\"\n matTooltipPosition=\"below\">\n <div class=\"cell-content\">\n <data-cell #datacell [personCardTemplate]=\"personCardTemplate\" [td]=cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\" [value]=\"(row?.['entity_data']?.[column.name] ?? row?.[column.name]) || ''\" [column]=\"column\"\n [mode]=\"mode()\" [isEditable]=\"isEditable() && column.editable !== false && column.editable !== 'false'\" [drillable]=\"column.enableDrilldown || false\"\n [id]=\"i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\"\n style=\"width: 40px; min-width: 40px; max-width: 40px; text-align: center; cursor: pointer;\">\n <mat-icon (click)=\"onActionClick($event, row)\">more_horiz</mat-icon>\n </td>\n }\n </tr>\n @if(hasHiddenColumns() && isRowExpanded(row, i)) {\n <tr class=\"row-detail\">\n <td class=\"row-detail-cell\" [attr.colspan]=\"rowDetailColspan()\">\n <div class=\"row-detail-grid\">\n @for (hiddenCol of hiddenColumns(); track trackByColumnFn($index, hiddenCol)) {\n <div class=\"row-detail-field\">\n <span class=\"row-detail-label\">{{hiddenCol.label}}</span>\n <div class=\"row-detail-value\">\n <data-cell [fieldSize]=\"hiddenCol.field_size\" [columnDatatype]=\"hiddenCol.datatype\"\n [columnName]=\"hiddenCol.name\" [value]=\"(row?.['entity_data']?.[hiddenCol.name] ?? row?.[hiddenCol.name]) || ''\"\n [column]=\"hiddenCol\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [drillable]=\"hiddenCol.enableDrilldown || false\"\n [id]=\"'detail_' + i + '_' + hiddenCol.name\" [eruGridStore]=\"gridStore\" [row]=\"row\"></data-cell>\n </div>\n </div>\n }\n </div>\n </td>\n </tr>\n }\n </ng-container>\n <!-- } -->\n <!-- } -->\n @if(group.isLoading && (group.isExpanded || !showGroupBar())) {\n @for(i of [].constructor(ghostRows()); let j = $index; track j) {\n <tr class=\"ghost-loading-row\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n class=\"ghost-cell-container\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column ghost-cell-container\" style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n <div class=\"ghost-cell\"></div>\n </td>\n }\n </tr>\n }\n }\n <!-- <tr\n *ngIf=\"getRowsForGroup(group.id).length === 0 && !group.isExpanded\"\n class=\"group-separator\"\n >\n <td [attr.colspan]=\"groupSeperatorColSpan()\" class=\"separator-cell\"></td>\n </tr> -->\n <!-- Subtotal row at end of group (position: after) -->\n @if(enableColumnSubtotals() && subtotalPositionColumn() === 'after' && hasSubtotalData(group)) {\n <ng-container *ngTemplateOutlet=\"tableSubtotal; context: { group: group }\"></ng-container>\n }\n <!-- Grand Total row at end of group (position: after) - only for last group -->\n @if(enableColumnGrandTotal() && grandTotalPositionColumn() === 'after' && hasGrandTotalData() && i ===\n groups().length - 1) {\n <ng-container *ngTemplateOutlet=\"tableGrandTotal\"></ng-container>\n }\n }\n </tbody>\n </table>\n </div>\n </cdk-virtual-scroll-viewport>\n </ng-container>\n }\n </div>\n }\n </div>\n }\n</div>\n\n<!-- Pivot Table Header Template -->\n<ng-template #pivotTableHead>\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n @if (hasNestedHeaders()) {\n <ng-container>\n @for (headerRow of getHeaderRows(); track headerRow; let rowIndex = $index) {\n <tr class=\"pivot-header pivot-header-container\" [class.pivot-header-level]=\"'level-' + rowIndex\">\n @for (header of headerRow; track trackByHeaderFn($index, header); let colIndex = $index) {\n <th [attr.colspan]=\"header.colspan\" [attr.rowspan]=\"header.rowspan\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable') && header.level === 0 && !isRowDimensionHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header)\" class=\"column-header pivot-column-header nested-header\"\n [class.row-dimension-header]=\"isRowDimensionHeader(header)\"\n [class.column-dimension-header]=\"!isRowDimensionHeader(header)\" [class.expanded]=\"header.isExpanded\"\n [class.collapsed]=\"!header.isExpanded\" [class.sticky-column]=\"isStickyColumn(header.name, colIndex)\"\n [style.position]=\"isStickyColumn(header.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(header.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(header.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto; padding: 8px 6px;\">\n <div class=\"header-content\">\n\n <data-cell [fieldSize]=\"header.field_size\" [columnDatatype]=\"header.dataType\" [columnName]=\"header.name\"\n [value]=\"header.label\" [column]=\"header\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"header.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + $index + '_' + header.name\" [eruGridStore]=\"gridStore\" [row]=\"header\">\n </data-cell>\n <!-- <span class=\"header-label header-wrap-text\">{{header.label}}</span> -->\n <!-- <button *ngIf=\"!isRowDimensionHeader(header)\"\n class=\"collapse-toggle-btn\"\n [title]=\"header.isExpanded ? 'Collapse group' : 'Expand group'\"\n (click)=\"toggleColumnGroup(header.groupKey)\"\n type=\"button\">\n <span class=\"collapse-icon\">+</span>\n </button> -->\n </div>\n </th>\n }\n </tr>\n }\n </ng-container>\n } @else {\n <!-- Simple header fallback -->\n <ng-container>\n <tr class=\"pivot-header\" [class.freeze-header-enabled]=\"freezeHeader()\">\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <th [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\"\n [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\" [columnConfig]=\"column\"\n class=\"column-header pivot-column-header\" [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 100 : 1\"\n [style.min-height.px]=\"headerRowHeight()\" style=\"height: auto;padding: 8px 6px\">\n {{column.label}}\n </th>\n }\n </tr>\n </ng-container>\n }\n\n </thead>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #pivotColGroup>\n <colgroup>\n @for (column of getLeafColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n </colgroup>\n</ng-template>\n\n<ng-template #pivotGrandTotal>\n <tbody class=\"pivot-tbody\">\n @for (pivotRow of gridStore.pivotGrandTotalData(); track trackByPivotRowFn($index, pivotRow); let i = $index) {\n <tr class=\"pivot-row grand-total-row\"\n [class.grand-total-bold]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"pivotRow._isGrandTotal && grandTotalStyle() === 'highlighted'\"\n [style.height.px]=\"50\" [attr.data-pivot-row]=\"i\">\n <!-- <td colspan=\"20\">{{pivotRow | json}}</td> -->\n @for (column of getLeafColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [attr.rowspan]=\"getEffectiveRowspan(i, column.name)\" [style.width.px]=\"column.field_size\"\n [style.minWidth.px]=\"column.field_size\" class=\"pivot-cell\"\n [class.row-dimension-cell]=\"isRowDimensionColumn(column.name)\"\n [class.column-dimension-cell]=\"!isRowDimensionColumn(column.name)\"\n [class.aggregated-value]=\"!isRowDimensionColumn(column.name) && column.datatype === 'number'\"\n [class.rowspan-cell]=\"getEffectiveRowspan(i, column.name) || 1 > 1\"\n [class.sticky-column]=\"isStickyColumn(column.name, colIndex)\"\n [style.position]=\"isStickyColumn(column.name, colIndex) ? 'sticky' : 'static'\"\n [style.left.px]=\"getStickyColumnLeft(column.name, colIndex)\"\n [style.z-index]=\"isStickyColumn(column.name, colIndex) ? 99 : 1\" [style.height.px]=\"50\" [attr.xx]=\"i\">\n <div class=\"cell-content pivot-cell-content\">\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getEffectiveCellValue(i,column.name, pivotRow)\" [column]=\"column\" [frozenGrandTotalCell]=\"true\"\n [drillable]=\"column.enableDrilldown || false\" [mode]=\"mode()\" [isEditable]=\"isEditable()\"\n [id]=\"'pivot_' + i + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"pivotRow\">\n </data-cell>\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n</ng-template>\n\n<!-- Column Group Template for consistent column widths -->\n<ng-template #tableColGroup>\n <colgroup>\n @if(gridStore.configuration().config.allowSelection) {\n <col style=\"width: 40px; min-width: 40px; max-width: 40px;\">\n }\n @if(shouldShowActionColumn('before')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n @if(hasHiddenColumns()) {\n <col style=\"width: 40px !important; min-width: 40px !important; max-width: 40px !important;\">\n }\n @for (column of visibleColumns(); track trackByColumnFn($index, column)) {\n <col\n [style]=\"'width: ' + column.field_size + 'px !important; min-width: ' + column.field_size + 'px !important; max-width: ' + column.field_size + 'px !important; --col-width: ' + column.field_size + 'px'\">\n }\n @if(shouldShowActionColumn('after')) {\n <col style=\"width: 60px; min-width: 60px; max-width: 60px;\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <tr>\n @if(gridStore.configuration().config.allowSelection) {\n <th class=\"checkbox-column column-header table-column-header\">\n <input type=\"checkbox\" [checked]=\"isAllGroupsSelected()\" (change)=\"toggleAllGroups($event)\">\n </th>\n }\n @if(shouldShowActionColumn('before')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n @if(hasHiddenColumns()) {\n <th class=\"row-expand-toggle column-header table-column-header\"></th>\n }\n @for (column of visibleColumns(); track trackByColumnFn(i, column); let i = $index) {\n <th [style.width.px]=\"column.field_size\" [resizeColumn]=\"gridStore.isFeatureEnabled('columnResizable')\"\n [columnConfig]=\"column\" [index]=\"i\"\n [columnDraggable]=\"gridStore.isFeatureEnabled('columnReorderable') ? i : null\"\n [style.minWidth.px]=\"column.field_size\" class=\"column-header table-column-header\"\n [class.sortable-header]=\"isSortable()\"\n [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === column.name\"\n [class.sort-asc]=\"isSortable() && getSortDirection(column.name) === 'asc'\"\n [class.sort-desc]=\"isSortable() && getSortDirection(column.name) === 'desc'\">\n @if(gridStore.isFeatureEnabled('columnReorderable')) {\n <div class=\"column-drag-handle\"></div>\n }\n <span class=\"column-label\" [title]=\"column.tool_tip || column.description || ''\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\" title=\"Edit column\" (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n @if(isSortable()) {\n <span class=\"sort-indicator\">\n <span class=\"sort-triangles\">\n <span class=\"sort-tri sort-tri-up\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'asc'\"\n (click)=\"onSortColumn($event, column, 'asc')\"></span>\n <span class=\"sort-tri sort-tri-down\" [class.sort-tri-active]=\"getSortDirection(column.name) === 'desc'\"\n (click)=\"onSortColumn($event, column, 'desc')\"></span>\n </span>\n @if(getSortPriority(column.name) !== null && gridStore.sortColumns().length > 1) {\n <span class=\"sort-priority\">{{getSortPriority(column.name)}}</span>\n }\n </span>\n }\n </th>\n }\n @if(shouldShowActionColumn('after')) {\n <th class=\"action-column column-header table-column-header\"\n style=\"width: 40px; min-width: 40px; max-width: 40px;\">Action</th>\n }\n </tr>\n </thead>\n</ng-template>\n\n<!-- Table Subtotal Row Template -->\n<ng-template #tableSubtotal let-group=\"group\">\n <tr class=\"subtotal-row\" [class.subtotal-bold]=\"subTotalStyle() === 'bold'\"\n [class.subtotal-italic]=\"subTotalStyle() === 'italic'\"\n [class.subtotal-highlighted]=\"subTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"subtotal-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getSubtotalValue(group, column.name) === null) {\n <span class=\"subtotal-label\">{{subtotalLabel()}}</span>\n } @else {\n @if(getSubtotalValue(group, column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getSubtotalValue(group, column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'subtotal_' + group.id + '_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"group.subtotal\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- Table Grand Total Row Template -->\n<ng-template #tableGrandTotal>\n <tr class=\"grand-total-row\" [class.grand-total-bold]=\"grandTotalStyle() === 'bold'\"\n [class.grand-total-italic]=\"grandTotalStyle() === 'italic'\"\n [class.grand-total-highlighted]=\"grandTotalStyle() === 'highlighted'\" [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n @if(gridStore.configuration().config.allowSelection) {\n <td class=\"checkbox-column\"></td>\n }\n @if(shouldShowActionColumn('before')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n @if(hasHiddenColumns()) {\n <td class=\"row-expand-toggle\"></td>\n }\n @for(column of visibleColumns(); track trackByColumnFn($index, column); let colIndex = $index) {\n <td [style.width.px]=\"column.field_size\" [style.minWidth.px]=\"column.field_size\" class=\"grand-total-cell\"\n [style.height.px]=\"dataRowHeight()\" [style.minHeight.px]=\"dataRowHeight()\">\n <div class=\"cell-content\">\n @if(colIndex === 0 && getGrandTotalValue(column.name) === null) {\n <span class=\"grand-total-label\">Grand Total</span>\n } @else {\n @if(getGrandTotalValue(column.name) !== null) {\n <data-cell [fieldSize]=\"column.field_size\" [columnDatatype]=\"column.datatype\" [columnName]=\"column.name\"\n [value]=\"getGrandTotalValue(column.name)\" [column]=\"column\" [mode]=\"mode()\" [isEditable]=\"false\"\n [id]=\"'grandtotal_' + column.name\" [eruGridStore]=\"gridStore\" [row]=\"gridStore.rowGrandTotal()\">\n </data-cell>\n }\n }\n </div>\n </td>\n }\n @if(shouldShowActionColumn('after')) {\n <td class=\"action-column\" style=\"width: 40px; min-width: 40px; max-width: 40px;\"></td>\n }\n </tr>\n</ng-template>\n\n<!-- \u2500\u2500\u2500 Default board card template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Used when no boardCardTemplate is passed to <eru-grid>.\n Context: { $implicit: Row, columns: Field[], group: RowGroup }\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<ng-template #defaultBoardCard let-row let-columns=\"columns\" let-group=\"group\">\n <mat-card class=\"board-card\">\n <mat-card-content>\n @for (column of columns; track column.name) {\n @if ((row?.entity_data?.[column.name] ?? row?.[column.name]) !== undefined) {\n <div class=\"board-card-field\">\n <span class=\"board-field-label\">{{ column.label }}</span>\n <data-cell\n [fieldSize]=\"column.field_size\"\n [columnDatatype]=\"column.datatype\"\n [columnName]=\"column.name\"\n [column]=\"column\"\n [value]=\"row?.entity_data?.[column.name] ?? row?.[column.name]\"\n [id]=\"row?.entity_id + '_' + column.name\"\n [eruGridStore]=\"gridStore\"\n [mode]=\"'board'\"\n [row]=\"row\">\n </data-cell>\n </div>\n }\n }\n </mat-card-content>\n <mat-card-actions align=\"end\">\n <button mat-icon-button (click)=\"onActionClick($event, row)\">\n <mat-icon>more_horiz</mat-icon>\n </button>\n </mat-card-actions>\n </mat-card>\n</ng-template>", styles: ["@charset \"UTF-8\";:root{--grid-primary: #6750a4;--grid-on-primary: #ffffff;--grid-surface: #fef7ff;--grid-surface-variant: #e7e0ec;--grid-surface-container: #f3edf7;--grid-surface-container-high: #ede7f0;--grid-on-surface: #1d1b20;--grid-on-surface-variant: #49454f;--grid-outline: #79757f;--grid-outline-variant: #cac4d0;--grid-error: #ba1a1a;--grid-error-container: #ffdad6}:host,eru-grid{display:block!important;width:100%;height:100%;flex:1 1 0%;min-height:var(--grid-height, 300px);font-family:var(--grid-font-family);--grid-font-family: \"Poppins\", \"Roboto\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;--grid-font-size-body: 12px;--grid-font-size-caption: 12px !important;--grid-line-height-body: 1;--grid-aggregation-text-align: right;--grid-number-text-align: right;--grid-spacing-xxs: 2px;--grid-spacing-xs: 4px;--grid-spacing-sm: 8px;--grid-spacing-md: 16px;--grid-spacing-lg: 24px;--grid-border-radius: 4px;--grid-elevation-1: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 1px 3px 1px rgba(0, 0, 0, .15);--grid-elevation-2: 0px 1px 2px 0px rgba(0, 0, 0, .3), 0px 2px 6px 2px rgba(0, 0, 0, .15);--grid-row-hover: var(--grid-surface-variant);--grid-row-selected: var(--grid-surface-container-high);--grid-zebra-odd: transparent;--grid-zebra-even: transparent;--grid-focus-ring: var(--grid-primary);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: var(--grid-font-size-caption);--grid-header-padding-x: 8px;--grid-header-padding-y: 12px;--grid-font-feature-numeric: normal;--grid-cell-padding-x: var(--grid-spacing-xs);--grid-cell-padding-y: var(--grid-spacing-xxs);--grid-tint-subtle: rgba(0, 0, 0, .025);--grid-tint-soft: rgba(0, 0, 0, .045);--grid-tint-strong: rgba(0, 0, 0, .08);--grid-radius-outer: 0;--grid-shadow-outer: none;--grid-divider-color: var(--grid-outline-variant);--grid-divider-width: 1px;--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-pill-radius: 999px;--grid-pill-padding-y: 3px;--grid-pill-padding-x: 10px;--grid-pill-font-size: 11px;--grid-pill-font-weight: 500;--grid-priority-dot-size: 8px;--grid-avatar-size: 24px;--grid-avatar-font-size: 10px;--grid-avatar-font-weight: 600;border-radius:var(--grid-radius-outer);box-shadow:var(--grid-shadow-outer)}eru-grid[data-preset=default]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .06em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-soft);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=modern]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: none;--grid-header-letter-spacing: normal;--grid-header-font-size: 13px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 16px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 12px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px}eru-grid[data-preset=compact]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 11px;--grid-header-padding-y: 4px;--grid-header-padding-x: 8px;--grid-cell-padding-y: 3px;--grid-cell-padding-x: 8px;--grid-font-size-body: 11px;--grid-row-hover: var(--grid-tint-subtle);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 1px;--grid-pill-padding-x: 6px;--grid-pill-font-size: 10px}eru-grid[data-preset=bold]{--grid-header-bg: var(--grid-surface-container-high);--grid-header-color: var(--grid-on-surface);--grid-header-font-weight: 700;--grid-header-text-transform: none;--grid-header-font-size: 13px;--grid-header-padding-y: 14px;--grid-header-padding-x: 12px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 12px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-strong);--grid-divider-width: 1px;--grid-radius-outer: 2px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=financial]{--grid-header-bg: var(--grid-surface-container);--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 500;--grid-header-text-transform: uppercase;--grid-header-letter-spacing: .08em;--grid-header-font-size: 11px;--grid-header-padding-y: 12px;--grid-header-padding-x: 14px;--grid-cell-padding-y: 10px;--grid-cell-padding-x: 14px;--grid-zebra-odd: transparent;--grid-zebra-even: var(--grid-tint-subtle);--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-font-feature-numeric: \"tnum\"}eru-grid[data-preset=elevated]{--grid-header-bg: transparent;--grid-header-color: var(--grid-on-surface-variant);--grid-header-font-weight: 600;--grid-header-text-transform: none;--grid-header-font-size: 12px;--grid-header-padding-y: 16px;--grid-header-padding-x: 18px;--grid-cell-padding-y: 14px;--grid-cell-padding-x: 18px;--grid-row-hover: var(--grid-tint-soft);--grid-divider-color: var(--grid-tint-subtle);--grid-divider-width: 1px;--grid-radius-outer: 16px;--grid-shadow-outer: 0 1px 3px rgba(0, 0, 0, .06), 0 10px 28px rgba(0, 0, 0, .07);--grid-font-feature-numeric: \"tnum\";--grid-pill-padding-y: 4px;--grid-pill-padding-x: 12px;overflow:hidden}.group-container{padding-bottom:8px}.column-header.design-clickable .design-edit-icon{font-size:16px;width:16px;height:16px;margin-left:4px;opacity:.45;vertical-align:middle;cursor:pointer}.column-header.design-clickable:hover .design-edit-icon,.column-header.design-clickable .design-edit-icon:hover{opacity:1}.column-header.design-selected{background-color:var(--grid-primary-light, rgba(63, 81, 181, .12))}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-height, 300px);max-height:none;overflow:auto;position:relative;background-color:var(--grid-surface);border-radius:var(--grid-border-radius);font-family:var(--grid-font-family)}.viewport{height:100%;min-height:300px;overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface);scrollbar-gutter:stable}.viewport.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.groups-viewport{height:100%;min-height:300px}.groups-scroll-container{height:var(--grid-height, 600px);overflow-y:auto;overflow-x:hidden}.table-viewport{background-color:var(--grid-surface);height:var(--table-height, auto);min-height:var(--table-min-height, 100px);overflow-x:auto;overflow-y:auto}.pivot-viewport{min-height:var(--table-min-height, 300px);overflow-x:auto;overflow-y:auto;background-color:var(--grid-surface)}.pivot-viewport .cdk-virtual-scroll-content-wrapper{width:auto;height:auto}.table-wrapper{min-width:100%;overflow-x:visible}.incremental-row-container .eru-grid-table,.eru-grid-table{width:100%!important;border-collapse:separate;border-spacing:0;table-layout:fixed!important;background-color:var(--grid-surface);color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);line-height:var(--grid-line-height-body)}.eru-grid-table th,.eru-grid-table td{text-align:left;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:var(--grid-on-surface);min-width:0;max-width:100%!important;box-sizing:border-box;position:relative}.eru-grid-table th{background-color:var(--grid-header-bg, var(--grid-surface-container))}thead.eru-wrap-headers th{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;height:auto}thead.eru-wrap-headers th .column-label,thead.eru-wrap-headers th .header-label{white-space:normal!important;overflow:visible!important;text-overflow:clip!important;word-break:break-word;overflow-wrap:anywhere}.eru-grid-table tbody td{background-color:transparent}.eru-grid-table thead{background-color:var(--grid-header-bg, var(--grid-surface-container));transform:translateZ(0);will-change:transform;backface-visibility:hidden}.eru-grid-table thead.freeze-header-enabled{position:sticky!important;top:0!important;z-index:100!important}.eru-grid-table thead th{background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface));font-family:var(--grid-font-family);font-weight:var(--grid-header-font-weight);font-size:var(--grid-header-font-size)}.checkbox-column{width:50px;min-width:50px;max-width:50px;text-align:center;background-color:var(--grid-surface-container)}.checkbox-column input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--grid-primary);border-radius:var(--grid-border-radius)}.checkbox-column input[type=checkbox]:focus{outline:2px solid var(--grid-primary);outline-offset:2px}.action-column{width:40px;min-width:40px;max-width:40px;text-align:center;background-color:var(--grid-surface-container)}.action-column mat-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);cursor:pointer}.action-column mat-icon:hover{color:var(--grid-primary)}.group-header{background-color:var(--grid-surface-container);color:var(--grid-on-surface);font-size:var(--grid-font-size-caption);font-weight:500;border-bottom:1px solid var(--grid-outline);cursor:pointer;transition:background-color .2s ease}.group-header:hover{background-color:var(--grid-surface-container-high)}.group-header .group-title{font-weight:600;color:var(--grid-primary)}.group-header .group-row-count{color:var(--grid-on-surface-variant);font-size:var(--grid-font-size-caption);margin-left:var(--grid-spacing-sm)}.row-item{background-color:var(--grid-surface);transition:background-color .15s ease}.row-item:nth-child(odd){background-color:var(--grid-zebra-odd, var(--grid-surface))}.row-item:nth-child(2n){background-color:var(--grid-zebra-even, var(--grid-surface))}.row-item:hover{background-color:var(--grid-row-hover)}.required-toggle-row{background-color:var(--grid-surface-container, #f3edf7);border-bottom:1px solid var(--grid-outline-variant, #cac4d0)}.required-toggle-row .required-toggle-cell{padding:4px 8px!important;text-align:center;vertical-align:middle;position:relative}.required-toggle-row .required-toggle-cell .required-label{position:absolute;top:2px;left:4px;font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:400;text-transform:lowercase}.required-toggle-row .required-toggle-cell mat-checkbox{display:flex;justify-content:center;align-items:center}.table-column-header{padding:var(--grid-header-padding-y) var(--grid-header-padding-x)}.column-header{font-weight:var(--grid-header-font-weight);text-transform:var(--grid-header-text-transform);letter-spacing:var(--grid-header-letter-spacing);text-align:center!important;font-size:var(--grid-header-font-size);position:relative;-webkit-user-select:none;user-select:none;background-color:var(--grid-header-bg, var(--grid-surface-container));color:var(--grid-header-color, var(--grid-on-surface))}.column-header:hover{background-color:var(--grid-header-hover-bg, var(--grid-surface-container-high))}.column-drag-handle{position:absolute;left:0;top:0;bottom:0;width:12px;cursor:grab;opacity:0;transition:opacity .2s ease,background-color .2s ease;z-index:2;display:flex;align-items:center;justify-content:center;border-right:1px solid transparent}.column-drag-handle:after{content:\"\\22ee\\22ee\";font-size:14px;color:var(--grid-on-surface-variant);transform:rotate(90deg)}.column-drag-handle:hover{background-color:var(--grid-surface-container-high);border-right-color:var(--grid-outline)}.column-header:hover .column-drag-handle{opacity:1}.column-drag-handle:active{cursor:grabbing}.sortable-header{cursor:pointer}.sortable-header .column-label{flex:1}.sortable-header .sort-indicator{display:inline-flex;align-items:center;gap:2px;margin-left:4px;cursor:pointer;vertical-align:middle;opacity:0;transition:opacity .15s ease}.sortable-header .sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.sortable-header .sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.sortable-header .sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.sortable-header .sort-indicator .sort-tri-active{opacity:1}.sortable-header .sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.sortable-header .sort-indicator .sort-priority{font-size:9px;font-weight:600;color:var(--grid-primary, #6750a4);line-height:1;min-width:12px;text-align:center}.sortable-header:hover .sort-indicator,.sortable-header.sort-asc .sort-indicator,.sortable-header.sort-desc .sort-indicator{opacity:1}.sortable-header:hover .sort-indicator .sort-tri:not(.sort-tri-active){opacity:.6}.sort-asc,.sort-desc{background-color:var(--grid-surface-container-low, rgba(103, 80, 164, .04))}.dragging{opacity:1;background-color:var(--grid-surface-container);box-shadow:var(--grid-elevation-2)}.drag-over{background-color:var(--grid-surface-container);border-color:var(--grid-primary)}.data-cell{background-color:transparent;color:var(--grid-on-surface);font-family:var(--grid-font-family);font-size:var(--grid-font-size-body);font-feature-settings:var(--grid-font-feature-numeric);padding:var(--grid-cell-padding-y) var(--grid-cell-padding-x)}.cell-content{align-items:center}.cell-content .mdc-text-field{padding:0px var(--grid-spacing-xxs)!important}.cell-display-text{align-items:center;padding:0px var(--grid-spacing-xs)}.ghost-loading-row{background-color:transparent}.ghost-cell-container{padding:var(--grid-spacing-sm)}.ghost-cell{height:20px;width:100%;background-color:var(--grid-surface-container);animation:pulse 1.5s ease-in-out infinite;border-radius:var(--grid-border-radius)}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.column-resizer{position:absolute;right:0;top:0;bottom:0;width:4px;cursor:col-resize;background-color:transparent;transition:background-color .2s ease}.column-resizer:hover{background-color:var(--grid-primary)}.group-separator{height:var(--grid-spacing-sm);background-color:var(--grid-surface-variant)}.group-separator .separator-cell{background-color:var(--grid-surface-variant);border:none;height:var(--grid-spacing-sm)}.error-state{background-color:var(--grid-error-container);color:var(--grid-error);border-color:var(--grid-error)}.error-message{background-color:var(--grid-error);color:#fff;padding:var(--grid-spacing-sm);border-radius:var(--grid-border-radius);font-size:var(--grid-font-size-caption)}.incremental-row-container .eru-grid-table tbody,.incremental-row-container .eru-grid-table{position:relative}.incremental-row-container .eru-grid-table.show-column-lines{border-right:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important;border-top:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table.show-column-lines:not(.freeze-header){border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table:not(.show-column-lines){border:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.incremental-row-container .eru-grid-table thead:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:calc(var(--grid-divider-width, 1px) * 2);background-color:var(--grid-divider-color, var(--grid-outline, #e0e0e0));pointer-events:none;z-index:10}.incremental-row-container .eru-grid-table.show-column-lines thead th,.incremental-row-container .eru-grid-table.show-column-lines tbody td{border-left:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}.incremental-row-container .eru-grid-table.show-row-lines thead th,.incremental-row-container .eru-grid-table.show-row-lines tbody td{border-bottom:var(--grid-divider-width, 1px) solid var(--grid-divider-color, var(--grid-outline, #e0e0e0))!important}@media(max-width:768px){.incremental-row-container{height:600px}.eru-grid-table th,.eru-grid-table td{font-size:var(--grid-font-size-caption)}.checkbox-column{width:40px;min-width:40px;max-width:40px}}@media(prefers-contrast:high){.eru-grid-table th,.eru-grid-table td{border-width:2px}.row-item:hover{border-width:2px;border-color:var(--grid-primary)}}@media(prefers-reduced-motion:reduce){.row-item,.column-drag-handle,.ghost-cell{transition:none;animation:none}}.pivot-table .nested-header{text-align:center;font-weight:600;background:var(--grid-surface-container)}.pivot-table .nested-header.row-dimension-header{background:var(--grid-surface-container);font-weight:600}.pivot-table .pivot-header-leafcols{padding:0;margin:0;height:0}.pivot-table .pivot-header-level.level-0 .nested-header{font-size:14px;padding:12px 8px}.pivot-table .pivot-header-level.level-1 .nested-header{font-size:13px;padding:10px 6px}.pivot-table .pivot-header-level.level-2 .nested-header{font-size:12px;padding:8px 4px}.pivot-table .nested-header:hover{background:var(--grid-surface-variant);color:var(--grid-primary);transition:all .2s ease}.pivot-table .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-table .pivot-cell-content{display:flex;justify-content:center;align-items:center;min-height:38px}.pivot-table .pivot-repeated-value .cell-content,.pivot-table .pivot-repeated-value .pivot-cell-content{visibility:hidden}.pivot-table .pivot-group-start.row-dimension-cell{border-top:1px solid var(--grid-outline, #79757f)}.pivot-mode .incremental-row-container{display:flex;flex-direction:column;height:auto;max-height:85vh;overflow:auto}.pivot-mode .h-shell{position:relative;width:calc(100% - var(--scrollbar-width, 17px))!important;top:0;z-index:1;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .h-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell{position:relative;bottom:50px;flex-shrink:0;overflow-x:hidden;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .gt-shell::-webkit-scrollbar{display:none}.pivot-mode .gt-shell table{border-bottom:var(--grid-outline-width, 1px) solid var(--grid-outline, #e0e0e0)!important}.pivot-mode .gt-shell.adjust-bottom-vs{bottom:66px!important}.pivot-mode .gt-shell.adjust-bottom:not(.adjust-bottom-vs){bottom:calc(66px - var(--scrollbar-width, 17px))!important}.pivot-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.pivot-mode .header-shell::-webkit-scrollbar{display:none}.pivot-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.pivot-mode .header-shell .eru-grid-table{margin-bottom:0;width:100%;table-layout:fixed}.pivot-mode .header-shell .eru-grid-table thead{background:var(--grid-surface-container)}.pivot-mode .header-shell .eru-grid-table thead th{background:var(--grid-surface-container);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .header-shell .eru-grid-table thead th.sticky-column{position:sticky;background:var(--grid-surface-container);z-index:111}.pivot-mode .header-shell .eru-grid-table tbody td{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-container{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.pivot-mode .pivot-table{width:auto!important;min-width:100%!important;table-layout:fixed!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important}.pivot-mode .pivot-table td,.pivot-mode .pivot-table th{box-sizing:border-box!important;flex:none!important;flex-shrink:0!important;flex-grow:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-table{table-layout:fixed!important;width:100%!important}.pivot-mode .pivot-table *{max-width:var(--col-width)!important;box-sizing:border-box!important}.pivot-mode .pivot-table colgroup{width:100%!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex-basis:var(--col-width)!important;flex:0 0 var(--col-width)!important}.pivot-mode .pivot-table table{width:100%!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important}.pivot-mode .pivot-table[style*=--table-total-width]{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important}.pivot-mode .pivot-table colgroup col{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;flex:0 0 var(--col-width)!important;flex-basis:var(--col-width)!important;flex-grow:0!important;flex-shrink:0!important;overflow:hidden!important}.pivot-mode .pivot-table tbody td,.pivot-mode .pivot-table thead th{width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.pivot-mode .pivot-table .cell-content,.pivot-mode .pivot-table data-cell{width:100%!important;max-width:100%!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;display:block!important}.pivot-mode .pivot-table table{width:var(--table-total-width)!important;min-width:var(--table-total-width)!important;max-width:var(--table-total-width)!important;table-layout:fixed!important;border-collapse:collapse!important;border-spacing:0!important;word-wrap:break-word!important;word-break:break-all!important}.pivot-mode .pivot-tbody tr.pivot-row{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important}.pivot-mode .pivot-tbody tr.pivot-row:hover{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-tbody tr.pivot-row:nth-child(2n){background-color:#00000005}.pivot-mode .pivot-tbody tr.pivot-row td{min-height:var(--grid-data-row-height, 50px)!important;height:var(--grid-data-row-height, 50px)!important;vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content{min-height:calc(var(--grid-data-row-height, 50px) - 2px);display:flex;align-items:center;justify-content:center}.pivot-mode .pivot-tbody tr.pivot-row td .cell-content data-cell{width:100%;min-height:calc(var(--grid-data-row-height, 50px) - 4px);display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0}.pivot-mode .pivot-cell{vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:var(--col-width)!important;min-width:var(--col-width)!important;max-width:var(--col-width)!important}.pivot-mode .pivot-cell.aggregated-value{font-weight:500;font-family:Roboto Mono,monospace}.pivot-mode .pivot-cell .cell-content{display:flex;justify-content:center;align-items:center;min-height:var(--grid-header-row-height, 40px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.pivot-mode .pivot-table .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.pivot-mode .pivot-table .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .subtotal-row td:first-child{color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row td.aggregated-value{font-weight:500;color:var(--grid-primary)}.pivot-mode .pivot-table .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.pivot-mode .pivot-table .subtotal-bold td{font-weight:600!important;font-style:normal!important}.pivot-mode .pivot-table .subtotal-bold td.aggregated-value{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td{font-style:italic!important}.pivot-mode .pivot-table .subtotal-italic td:first-child{font-weight:600!important}.pivot-mode .pivot-table .subtotal-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.pivot-mode .pivot-table .subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted td.aggregated-value{font-weight:500!important;color:var(--grid-primary)!important}.pivot-mode .pivot-table .subtotal-highlighted:hover,.pivot-mode .pivot-table .subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700;font-size:var(--grid-font-size-body)}.pivot-mode .pivot-table .grand-total-row td{background-color:var(--grid-surface-container-high)!important;color:var(--grid-on-surface)}.pivot-mode .pivot-table .grand-total-row td:first-child{font-style:normal;font-weight:800;color:var(--grid-primary)}.pivot-mode .pivot-table .grand-total-row td.aggregated-value{font-weight:500;color:var(--grid-primary);font-family:Roboto Mono,monospace}.pivot-mode .pivot-table .grand-total-row:hover,.pivot-mode .pivot-table .grand-total-row:hover td{background-color:var(--grid-surface-container-high)!important}.pivot-mode .pivot-table .grand-total-bold td{font-weight:700!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-bold td.aggregated-value{font-weight:700!important}.pivot-mode .pivot-table .grand-total-italic td,.pivot-mode .pivot-table .grand-total-italic td.aggregated-value{font-style:italic!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted{background-color:var(--grid-primary)!important;box-shadow:var(--grid-elevation-2)!important}.pivot-mode .pivot-table .grand-total-highlighted td{background-color:var(--grid-primary)!important;color:var(--grid-on-primary)!important;font-weight:500!important;font-style:normal!important}.pivot-mode .pivot-table .grand-total-highlighted td.aggregated-value{color:var(--grid-on-primary)!important;font-weight:500!important}.pivot-mode .pivot-table .grand-total-highlighted:hover,.pivot-mode .pivot-table .grand-total-highlighted:hover td{background-color:var(--grid-primary)!important}.pivot-mode .pivot-table .collapsible-header{position:relative}.pivot-mode .pivot-table .collapsible-header .header-content{display:flex;align-items:center;justify-content:space-between;gap:var(--grid-spacing-xs);padding:var(--grid-spacing-xs) var(--grid-spacing-sm)}.pivot-mode .pivot-table .collapsible-header .header-label{flex:1;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn{background:none;border:none;cursor:pointer;padding:var(--grid-spacing-xxs);margin:0;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--grid-border-radius);color:var(--grid-on-surface-variant);transition:all .2s ease;font-size:12px;font-weight:600}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:hover{background-color:var(--grid-surface-container);color:var(--grid-primary);transform:scale(1.1)}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn:focus{outline:2px solid var(--grid-primary);outline-offset:1px}.pivot-mode .pivot-table .collapsible-header .collapse-toggle-btn .collapse-icon{display:block;line-height:1;font-family:monospace;font-size:14px}.pivot-mode .pivot-table .collapsible-header.expanded .collapse-toggle-btn .collapse-icon{color:var(--grid-primary)}.pivot-mode .pivot-table .collapsible-header.collapsed{background-color:var(--grid-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .header-label{font-style:italic;color:var(--grid-on-surface-variant)}.pivot-mode .pivot-table .collapsible-header.collapsed .collapse-toggle-btn .collapse-icon{color:var(--grid-outline)}.pivot-mode .pivot-table .collapsible-header:hover{background-color:var(--grid-surface-container)}.pivot-mode .pivot-table .collapsible-header:hover .header-label{color:var(--grid-on-surface)}.pivot-mode .pivot-table .pivot-single-table{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden;min-height:var(--table-min-height)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container{flex-shrink:0;background:var(--grid-surface)!important;overflow-x:auto;overflow-y:hidden;min-height:100px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table{width:auto;min-width:100%;height:auto!important;min-height:100px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th{background:var(--grid-surface-container)!important;padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:40px!important;height:auto!important;position:relative;visibility:visible!important;color:var(--grid-on-surface)!important}.pivot-mode .pivot-table .pivot-single-table .pivot-header-container .pivot-table th.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:101!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container{flex:1;overflow:auto;min-height:300px!important;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-viewport{height:100%!important;width:100%!important;overflow-x:auto!important;overflow-y:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table{width:auto;min-width:100%;height:auto!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td{padding:8px 6px!important;white-space:nowrap;min-width:50px;min-height:32px!important;height:auto!important;background:var(--grid-surface)!important;color:var(--grid-on-surface)!important;visibility:visible!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table td.sticky-column,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table td.sticky-column{position:sticky!important;background:var(--grid-surface-container)!important;border-right:2px solid var(--grid-primary)!important;box-shadow:2px 0 4px #0000001a;z-index:100!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr{height:auto!important;min-height:50px!important}.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-table tbody tr.pivot-row,.pivot-mode .pivot-table .pivot-single-table .pivot-data-container .pivot-data-table tbody tr.pivot-row{visibility:visible!important;display:table-row!important}.pivot-mode .pivot-table .collapsed-column-group{background-color:var(--grid-surface-container);border-left:3px solid var(--grid-primary)}.pivot-mode .pivot-table .collapsed-column-group:hover{background-color:var(--grid-surface-container-high)}.pivot-row.subtotal-row{background-color:var(--grid-surface-variant);font-weight:500}.pivot-row.subtotal-row.subtotal-bold{font-weight:500}.pivot-row.subtotal-row.subtotal-italic{font-style:italic}.pivot-row.subtotal-row.subtotal-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.grand-total-row{background-color:var(--grid-surface-container);font-weight:600}.pivot-row.grand-total-row.grand-total-bold{font-weight:800}.pivot-row.grand-total-row.grand-total-italic{font-style:italic}.pivot-row.grand-total-row.grand-total-highlighted{background-color:var(--grid-primary);color:var(--grid-on-primary)}.pivot-row.first-visible-row{background-color:#6750a41a!important;position:relative}.pivot-row.first-visible-row:before{content:\"\\1f441\\fe0f First Visible\";position:absolute;top:-20px;left:0;background:var(--grid-primary);color:var(--grid-on-primary);padding:2px 6px;font-size:10px;border-radius:2px;z-index:1000}.header-wrap-text{white-space:pre-wrap;word-break:auto-phrase}.group-header-row{display:flex;align-items:center;justify-content:space-between;width:100%;padding-right:12px}.custom-collapse-header{background-color:var(--grid-surface-variant);padding:8px 20px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:pointer;display:flex;width:fit-content;align-items:center;-webkit-user-select:none;user-select:none;min-width:200px;margin-bottom:10px;position:sticky;left:1px;z-index:116}.custom-collapse-header .collapse-arrow{display:inline-block;margin-right:8px;font-size:12px;color:var(--grid-on-surface-variant);transition:transform .2s ease;transform:rotate(0)}.custom-collapse-header .collapse-arrow.rotate-arrow{transform:rotate(270deg)}.custom-collapse-header .f-12{font-size:12px;color:var(--grid-on-surface)}.custom-collapse-header .group-sort-indicator{display:inline-flex;align-items:center;margin-left:8px}.custom-collapse-header .group-sort-indicator .sort-triangles{display:flex;flex-direction:column;align-items:center;gap:2px}.custom-collapse-header .group-sort-indicator .sort-tri{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;cursor:pointer;transition:border-color .15s ease}.custom-collapse-header .group-sort-indicator .sort-tri-up{border-bottom:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-down{border-top:5px solid var(--grid-on-surface-variant, #49454f);opacity:.3}.custom-collapse-header .group-sort-indicator .sort-tri-active{opacity:1}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-up{border-bottom-color:var(--grid-primary, #6750a4)}.custom-collapse-header .group-sort-indicator .sort-tri-active.sort-tri-down{border-top-color:var(--grid-primary, #6750a4)}.excel-download-icon{cursor:pointer}.excel-download-icon:hover{opacity:.75}.excel-download-bar{display:flex;justify-content:flex-end;padding:4px 12px}.table-mode .header-shell{flex-shrink:0;width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .header-shell::-webkit-scrollbar{display:none}.table-mode .header-shell.apply-cdk-width{width:calc(var(--table-total-width) + 10px)!important}.table-mode .subtotal-row{background-color:var(--grid-surface-container)!important;font-weight:600}.table-mode .subtotal-row td{background-color:var(--grid-surface-container);color:var(--grid-on-surface-variant)}.table-mode .subtotal-row td:first-child{color:var(--grid-primary)}.table-mode .subtotal-row td.subtotal-cell{font-weight:500}.table-mode .subtotal-row td.subtotal-cell .subtotal-label{font-weight:600;color:var(--grid-primary)}.table-mode .subtotal-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .subtotal-row.subtotal-bold td{font-weight:600!important;font-style:normal!important}.table-mode .subtotal-row.subtotal-italic td{font-style:italic!important}.table-mode .subtotal-row.subtotal-italic td:first-child{font-weight:600!important}.table-mode .subtotal-row.subtotal-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .subtotal-row.subtotal-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:700!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .subtotal-row.subtotal-highlighted:hover,.table-mode .subtotal-row.subtotal-highlighted:hover td{background-color:var(--grid-surface-container-high)!important}.table-mode .subtotal-row-shell{width:100%;box-sizing:border-box;padding-right:var(--scrollbar-width, 17px);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}.table-mode .subtotal-row-shell::-webkit-scrollbar{display:none}.board-mode-host{overflow:hidden;display:flex;flex-direction:column;height:var(--grid-height, 600px)}.board-mode-host .board-view-container{display:flex;flex-direction:column;flex:1;min-height:0}.board-mode-host .board-sort-bar{display:flex;align-items:center;gap:6px;padding:8px 16px;flex-shrink:0;border-bottom:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);overflow-x:auto}.board-mode-host .board-sort-bar .board-sort-label{font-size:12px;font-weight:500;color:var(--grid-on-surface-variant, #49454f);white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-outline-variant, #cac4d0);background:var(--grid-surface, #fffbfe);color:var(--grid-on-surface, #1d1b20);font-size:12px;white-space:nowrap}.board-mode-host .board-sort-bar .board-sort-chip-active{background:var(--grid-surface-container);border-color:var(--grid-outline, #79757f);color:var(--grid-on-surface, #1d1b20)}.board-mode-host .board-sort-bar .board-sort-chip-label{pointer-events:none}.board-mode-host .board-sort-bar .board-sort-chip-arrow{font-size:10px;line-height:1;cursor:pointer;padding:2px;border-radius:4px}.board-mode-host .board-sort-bar .board-sort-chip-arrow:hover{background:#00000014}.board-mode-host .board-sort-bar .board-sort-chip-priority{font-size:9px;font-weight:700;background:var(--grid-primary, #6750a4);color:var(--grid-on-primary, #ffffff);border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center}.board-mode-host .board-sort-bar .board-sort-chip-remove{font-size:10px;cursor:pointer;padding:2px;border-radius:4px;color:var(--grid-on-surface-variant, #49454f)}.board-mode-host .board-sort-bar .board-sort-chip-remove:hover{background:#00000014;color:var(--grid-error, #b3261e)}.board-mode-host .board-sort-bar .board-sort-add-btn{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:16px;border:1px dashed var(--grid-outline-variant, #cac4d0);background:transparent;color:var(--grid-on-surface-variant, #49454f);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease,border-color .15s ease}.board-mode-host .board-sort-bar .board-sort-add-btn .board-sort-add-icon{font-size:14px;width:14px;height:14px}.board-mode-host .board-sort-bar .board-sort-add-btn:hover{background:var(--grid-surface-container-low, #f7f2fa);border-color:var(--grid-primary, #6750a4);color:var(--grid-primary, #6750a4)}.board-mode-host .board-sort-bar .board-sort-clear{display:inline-flex;align-items:center;padding:4px 10px;border-radius:16px;border:1px solid var(--grid-error, #b3261e);background:transparent;color:var(--grid-error, #b3261e);font-size:12px;cursor:pointer;white-space:nowrap;transition:background .15s ease}.board-mode-host .board-sort-bar .board-sort-clear:hover{background:#b3261e14}.board-mode-host .board-columns-wrapper{display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--board-col-min-width, 300px),1fr));grid-auto-rows:var(--board-column-height, 420px);gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start}.board-mode-host .board-column{height:var(--board-column-height, 420px);min-width:0;display:flex;flex-direction:column;background:var(--grid-surface-container, #f3edf7);border-radius:12px;min-height:0;overflow:hidden}.board-mode-host .board-column.board-column-accented{border-top:3px solid var(--board-group-color, transparent)}.board-mode-host .board-column .column-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;font-weight:600;flex-shrink:0}.board-mode-host .board-column .column-header:hover{background-color:transparent}.board-mode-host .board-column .column-header .column-header-title{font-size:15px;font-weight:700;letter-spacing:.2px;line-height:1.2;color:var(--grid-on-surface, #1d1b20);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.board-mode-host .board-column .column-header .column-header-title-cell{display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:clip}.board-mode-host .board-column .column-header .column-header-title-cell data-cell,.board-mode-host .board-column .column-header .column-header-title-cell .data-cell-component,.board-mode-host .board-column .column-header .column-header-title-cell .container{padding:0!important;margin:0!important;border:none!important;background:transparent!important;min-height:0!important;height:auto!important;width:auto!important;max-width:100%!important;overflow:visible!important}.board-mode-host .board-column .column-header .column-header-title-cell .status-display,.board-mode-host .board-column .column-header .column-header-title-cell .status-display-content,.board-mode-host .board-column .column-header .column-header-title-cell .status-text,.board-mode-host .board-column .column-header .column-header-title-cell .tag-display,.board-mode-host .board-column .column-header .column-header-title-cell .tag-text{max-width:none!important;overflow:visible!important;text-overflow:clip!important}.board-mode-host .board-column .column-header .column-header-count{font-size:10px;font-weight:600;color:var(--grid-on-surface-variant, #49454f);background:var(--grid-surface-variant, #e7e0ec);border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:1;min-height:0;height:0}.board-mode-host .board-card-container{box-sizing:border-box;overflow:hidden;border-radius:8px;transition:background-color .15s ease,box-shadow .15s ease}.board-mode-host .board-card-container.show-row-lines{box-shadow:inset 0 0 0 var(--grid-divider-width, 1px) var(--grid-divider-color, var(--grid-outline, #e0e0e0))}.board-mode-host .board-card-container:hover{background-color:var(--eru-board-card-hover-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 8%, transparent))}.board-mode-host .board-card-container.selected{background-color:var(--eru-board-card-selected-bg, color-mix(in srgb, var(--mat-sys-primary, #1976d2) 14%, transparent));box-shadow:inset 0 0 0 2px var(--eru-board-card-selected-outline, var(--mat-sys-primary, #1976d2))}.board-mode-host .board-card{height:calc(100% - 8px);overflow:hidden;cursor:pointer}.board-mode-host .board-card mat-card-title{font-size:13px}.board-mode-host .board-card mat-card-subtitle{font-size:12px}.board-mode-host .board-card-field{display:flex;flex-direction:column;margin-bottom:4px}.board-mode-host .board-field-label{font-size:10px;color:var(--grid-on-surface-variant, #49454f);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.board-mode-host .board-ghost-card{margin:8px;padding:16px;background:var(--grid-surface, #fef7ff);border-radius:8px;animation:board-pulse 1.5s ease-in-out infinite}.board-mode-host .board-ghost-line{height:12px;background:var(--grid-surface-variant, #e7e0ec);border-radius:4px;margin-bottom:8px}.board-mode-host .board-ghost-line--short{width:60%}@keyframes board-pulse{0%,to{opacity:1}50%{opacity:.5}}th.row-expand-toggle,td.row-expand-toggle{width:40px!important;min-width:40px!important;max-width:40px!important;padding:0!important;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;box-sizing:border-box}.row-expand-icon{font-size:20px;width:20px;height:20px;line-height:20px;color:var(--grid-on-surface-variant);transition:transform .15s ease-in-out}.row-expand-icon.expanded{transform:rotate(90deg)}.row-detail{background:var(--grid-surface-container)}.row-detail .row-detail-cell{padding:var(--grid-spacing-sm) var(--grid-spacing-md);border-bottom:1px solid var(--grid-outline-variant)}.row-detail .row-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--grid-spacing-sm) var(--grid-spacing-md)}.row-detail .row-detail-field{display:flex;flex-direction:column;gap:var(--grid-spacing-xxs);min-width:0}.row-detail .row-detail-label{font-size:var(--grid-font-size-caption);color:var(--grid-on-surface-variant);font-weight:500}.row-detail .row-detail-value{min-width:0}.row-detail .row-detail-value data-cell{display:block;width:100%}\n"] }]
12968
13770
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { allViewports: [{
12969
13771
  type: ViewChildren,
12970
13772
  args: [CdkVirtualScrollViewport]
@@ -12984,6 +13786,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
12984
13786
  type: Input
12985
13787
  }], boardCardTemplate: [{
12986
13788
  type: Input
13789
+ }], personCardTemplate: [{
13790
+ type: Input
12987
13791
  }], boardCardHeight: [{
12988
13792
  type: Input
12989
13793
  }], boardCardGap: [{
@@ -13100,5 +13904,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
13100
13904
  * Generated bundle index. Do not edit.
13101
13905
  */
13102
13906
 
13103
- export { AttachmentComponent, CheckboxComponent, ColumnConstraintsService, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, NumberComponent, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent };
13907
+ export { AttachmentComponent, CheckboxComponent, ColumnConstraintsService, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, formatDateWithPattern, matchColorRange, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseDateWithPattern };
13104
13908
  //# sourceMappingURL=eru-grid.mjs.map