eru-grid 0.0.50 → 0.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/eru-grid.mjs +619 -43
- package/fesm2022/eru-grid.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/styles/theme.scss +43 -0
- package/types/eru-grid.d.ts +204 -2
package/fesm2022/eru-grid.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, signal, computed, DOCUMENT, effect, InjectionToken, ElementRef, input, EventEmitter, Output, ViewEncapsulation, ChangeDetectionStrategy, Component, ViewChild,
|
|
2
|
+
import { Injectable, inject, signal, computed, DOCUMENT, effect, InjectionToken, ElementRef, input, HostListener, Directive, EventEmitter, Output, ViewEncapsulation, ChangeDetectionStrategy, Component, ViewChild, Input, untracked, ChangeDetectorRef, output, NgZone, model, Renderer2, ViewChildren } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormsModule } from '@angular/forms';
|
|
5
5
|
import * as i2 from '@angular/material/form-field';
|
|
@@ -28,7 +28,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|
|
28
28
|
import * as i4$2 from '@angular/material/tabs';
|
|
29
29
|
import { MatTabsModule } from '@angular/material/tabs';
|
|
30
30
|
import * as i2$3 from '@angular/material/select';
|
|
31
|
-
import { MatSelectModule } from '@angular/material/select';
|
|
31
|
+
import { MatSelectModule, MatSelect } from '@angular/material/select';
|
|
32
32
|
import { polyfillCountryFlagEmojis } from 'country-flag-emoji-polyfill';
|
|
33
33
|
import { MatChipsModule } from '@angular/material/chips';
|
|
34
34
|
import { FixedSizeVirtualScrollStrategy, ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
@@ -77,6 +77,8 @@ const PRESET_CONFIG_DEFAULTS = {
|
|
|
77
77
|
* searchable whether a picker needs a filter box depends on how
|
|
78
78
|
* many options this grid shows, not on the field
|
|
79
79
|
* dateTimeFormat legacy alias; the model writes `date_format`
|
|
80
|
+
* cell_rules conditional formatting is a grid feature; it
|
|
81
|
+
* overrides the field's `color_ranges` per grid
|
|
80
82
|
*
|
|
81
83
|
* See SEEDED_FIELD_KEYS for the middle ground: keys the data model supplies a
|
|
82
84
|
* starting value for, which this grid may then override.
|
|
@@ -480,7 +482,7 @@ function statRefRequests(field, ref) {
|
|
|
480
482
|
function collectColumnStatRequests(columns) {
|
|
481
483
|
const byAlias = new Map();
|
|
482
484
|
for (const column of columns || []) {
|
|
483
|
-
const rules =
|
|
485
|
+
const rules = columnCellRules(column);
|
|
484
486
|
if (!rules.length || !column?.name)
|
|
485
487
|
continue;
|
|
486
488
|
for (const rule of rules) {
|
|
@@ -497,6 +499,32 @@ function collectColumnStatRequests(columns) {
|
|
|
497
499
|
}
|
|
498
500
|
return [...byAlias.values()];
|
|
499
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* True when this grid's column carries conditional formatting of its own.
|
|
504
|
+
*
|
|
505
|
+
* The test for "configured" is a non-empty list: an empty `cell_rules` is what
|
|
506
|
+
* removing the last rule leaves behind, and that has to read as "nothing
|
|
507
|
+
* configured here" so the data model's bands come back rather than the column
|
|
508
|
+
* being stuck with no formatting at all.
|
|
509
|
+
*/
|
|
510
|
+
function hasOwnCellRules(column) {
|
|
511
|
+
return Array.isArray(column?.cell_rules) && column.cell_rules.length > 0;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* The rules in force on a column: its own conditional formatting if configured,
|
|
515
|
+
* otherwise the data model's `color_ranges` bands.
|
|
516
|
+
*
|
|
517
|
+
* Conditional formatting is a property of this grid's view, `color_ranges` a
|
|
518
|
+
* property of the field — so the two are separate keys and this is the one place
|
|
519
|
+
* that decides between them. Every consumer (cell painting, stat requests, the
|
|
520
|
+
* design pane) reads through here, so they cannot disagree about which list is
|
|
521
|
+
* live.
|
|
522
|
+
*/
|
|
523
|
+
function columnCellRules(column) {
|
|
524
|
+
if (hasOwnCellRules(column))
|
|
525
|
+
return column.cell_rules;
|
|
526
|
+
return Array.isArray(column?.color_ranges) ? column.color_ranges : [];
|
|
527
|
+
}
|
|
500
528
|
/** A statistic's value, or null when it has not come back (or cannot apply). */
|
|
501
529
|
function resolveStatValue(field, ref, stats) {
|
|
502
530
|
if (!ref?.stat || !stats)
|
|
@@ -521,7 +549,7 @@ function resolveStatValue(field, ref, stats) {
|
|
|
521
549
|
* worse than leaving the cell unformatted until the numbers land.
|
|
522
550
|
*/
|
|
523
551
|
function resolveColumnRules(column, stats) {
|
|
524
|
-
const rules =
|
|
552
|
+
const rules = columnCellRules(column);
|
|
525
553
|
if (!rules.length)
|
|
526
554
|
return [];
|
|
527
555
|
const name = column?.name || '';
|
|
@@ -652,13 +680,24 @@ function cellRuleToCss(value, rule) {
|
|
|
652
680
|
const css = {};
|
|
653
681
|
if (!rule)
|
|
654
682
|
return css;
|
|
655
|
-
|
|
683
|
+
// Set twice, for the reason cellTextStyleToCss sets both: a cell component
|
|
684
|
+
// that declares its own colour never inherits one from the cell box, and
|
|
685
|
+
// number, currency, date, datetime, textbox and textarea all declare
|
|
686
|
+
// `color: var(--cell-color, …) !important`. So a rule's text colour reached
|
|
687
|
+
// the box as a plain `color` and was dropped on exactly the datatypes a
|
|
688
|
+
// numeric rule is written for. Feeding the custom property makes those
|
|
689
|
+
// components yield, while keeping their default when no rule matches.
|
|
690
|
+
if (rule.color) {
|
|
656
691
|
css['color'] = rule.color;
|
|
657
|
-
|
|
692
|
+
css['--cell-color'] = rule.color;
|
|
693
|
+
}
|
|
694
|
+
if (rule.bold) {
|
|
658
695
|
css['font-weight'] = '600';
|
|
696
|
+
css['--cell-font-weight'] = '600';
|
|
697
|
+
}
|
|
659
698
|
const bar = cellRuleBarPercent(value, rule);
|
|
660
699
|
if (bar !== null) {
|
|
661
|
-
const fillColor = rule.background || 'var(--grid-primary-
|
|
700
|
+
const fillColor = rule.background || 'var(--grid-primary-container, var(--grid-primary))';
|
|
662
701
|
css['background'] = `linear-gradient(to right, ${fillColor} ${bar}%, transparent ${bar}%)`;
|
|
663
702
|
return css;
|
|
664
703
|
}
|
|
@@ -705,10 +744,32 @@ const DATA_TYPES = [
|
|
|
705
744
|
* the grid's own `--grid-*` set — a column styled with a token keeps tracking
|
|
706
745
|
* the preset/theme instead of freezing one hex.
|
|
707
746
|
*/
|
|
747
|
+
/**
|
|
748
|
+
* Theme colours offerable on a column or a conditional-format rule.
|
|
749
|
+
*
|
|
750
|
+
* A one-for-one mirror of eru-studio's `colorTokens`, label for label, with
|
|
751
|
+
* `--studio-` swapped for `--grid-`: the two libraries dress the same page, so a
|
|
752
|
+
* colour chosen on a field and the same colour chosen on a grid column have to
|
|
753
|
+
* mean the same thing. Keep the two lists in step when either changes.
|
|
754
|
+
*
|
|
755
|
+
* 'Primary Light' used to sit in here and was never part of that set — nor was
|
|
756
|
+
* `--grid-primary-light` ever defined, so picking it produced an invalid
|
|
757
|
+
* `var()` and silently dropped whatever it was applied to. Its M3 equivalent is
|
|
758
|
+
* Primary Container, which is what studio calls it.
|
|
759
|
+
*/
|
|
708
760
|
const GRID_COLOR_TOKENS = [
|
|
709
761
|
{ label: 'Primary', value: 'var(--grid-primary)' },
|
|
710
762
|
{ label: 'On Primary', value: 'var(--grid-on-primary)' },
|
|
711
|
-
{ label: 'Primary
|
|
763
|
+
{ label: 'Primary Container', value: 'var(--grid-primary-container)' },
|
|
764
|
+
{ label: 'On Primary Container', value: 'var(--grid-on-primary-container)' },
|
|
765
|
+
{ label: 'Secondary', value: 'var(--grid-secondary)' },
|
|
766
|
+
{ label: 'On Secondary', value: 'var(--grid-on-secondary)' },
|
|
767
|
+
{ label: 'Secondary Container', value: 'var(--grid-secondary-container)' },
|
|
768
|
+
{ label: 'On Secondary Container', value: 'var(--grid-on-secondary-container)' },
|
|
769
|
+
{ label: 'Tertiary', value: 'var(--grid-tertiary)' },
|
|
770
|
+
{ label: 'On Tertiary', value: 'var(--grid-on-tertiary)' },
|
|
771
|
+
{ label: 'Tertiary Container', value: 'var(--grid-tertiary-container)' },
|
|
772
|
+
{ label: 'On Tertiary Container', value: 'var(--grid-on-tertiary-container)' },
|
|
712
773
|
{ label: 'Surface', value: 'var(--grid-surface)' },
|
|
713
774
|
{ label: 'Surface Variant', value: 'var(--grid-surface-variant)' },
|
|
714
775
|
{ label: 'Surface Container', value: 'var(--grid-surface-container)' },
|
|
@@ -719,6 +780,9 @@ const GRID_COLOR_TOKENS = [
|
|
|
719
780
|
{ label: 'Outline Variant', value: 'var(--grid-outline-variant)' },
|
|
720
781
|
{ label: 'Error', value: 'var(--grid-error)' },
|
|
721
782
|
{ label: 'Error Container', value: 'var(--grid-error-container)' },
|
|
783
|
+
{ label: 'Base Surface (white/dark)', value: 'var(--grid-base-surface)' },
|
|
784
|
+
{ label: 'Base On Surface (black/light)', value: 'var(--grid-base-on-surface)' },
|
|
785
|
+
{ label: 'Base Border', value: 'var(--grid-base-border)' },
|
|
722
786
|
];
|
|
723
787
|
/**
|
|
724
788
|
* Read one of the three shapes a colour value takes: a bare token, a token
|
|
@@ -853,6 +917,18 @@ function abbreviateNumber(num, system, decimals) {
|
|
|
853
917
|
return `${sign}${trim(abs / 1e3)} k`;
|
|
854
918
|
return `${sign}${trim(abs)}`;
|
|
855
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Which scale a value abbreviates on when no `display_number_as` is chosen:
|
|
922
|
+
* the one its digit grouping already implies.
|
|
923
|
+
*
|
|
924
|
+
* `seperator` and `display_number_as` were asking the same question twice — the
|
|
925
|
+
* data model already says whether a field reads Indian or Western, and a column
|
|
926
|
+
* grouped 1,00,00,000 that abbreviates to 'mn' is a contradiction. So the
|
|
927
|
+
* grouping supplies the scale and 'Display as' stays editable to override it.
|
|
928
|
+
*/
|
|
929
|
+
function abbreviationScaleFor(seperator) {
|
|
930
|
+
return String(seperator ?? '').toLowerCase() === 'thousands' ? 'lacs' : 'mn';
|
|
931
|
+
}
|
|
856
932
|
/**
|
|
857
933
|
* Render a number or currency value the way its column configures it —
|
|
858
934
|
* decimals, separator locale, abbreviation, optional symbol prefix.
|
|
@@ -877,11 +953,17 @@ function formatNumberValue(value, cfg, options) {
|
|
|
877
953
|
}
|
|
878
954
|
if (numValue === 0 && options?.replaceZero !== undefined)
|
|
879
955
|
return options.replaceZero;
|
|
880
|
-
|
|
956
|
+
// Lower-cased, as eru-studio's copy of this rule already does: a model value
|
|
957
|
+
// stored as 'None' otherwise missed the check below and got grouped anyway.
|
|
958
|
+
const separator = String(cfg?.seperator || 'thousands').toLowerCase();
|
|
881
959
|
const decimalPlaces = cfg?.decimal ?? 2;
|
|
882
960
|
const prefix = options?.prefix ? options.prefix + ' ' : '';
|
|
961
|
+
// Only the explicit 'Abbreviate' flag abbreviates. `seperator` and
|
|
962
|
+
// `display_number_as` are about how digits are GROUPED and, when abbreviating,
|
|
963
|
+
// on which scale — neither is a request to abbreviate, so a column carrying
|
|
964
|
+
// them still reads 30,00,000 rather than 3 mn.
|
|
883
965
|
if (cfg?.dynamic_number) {
|
|
884
|
-
return `${prefix}${abbreviateNumber(numValue, cfg?.display_number_as
|
|
966
|
+
return `${prefix}${abbreviateNumber(numValue, cfg?.display_number_as || abbreviationScaleFor(separator), decimalPlaces)}`;
|
|
885
967
|
}
|
|
886
968
|
if (separator === 'none') {
|
|
887
969
|
return `${prefix}${numValue.toFixed(decimalPlaces)}`;
|
|
@@ -5306,6 +5388,110 @@ const MATERIAL_PROVIDERS = [
|
|
|
5306
5388
|
];
|
|
5307
5389
|
const MATERIAL_MODULES = [];
|
|
5308
5390
|
|
|
5391
|
+
/**
|
|
5392
|
+
* Restricts an `<input type="text">` to a numeric value.
|
|
5393
|
+
*
|
|
5394
|
+
* Lives in eru-grid and is exported from its public API so the grid's cells and
|
|
5395
|
+
* eru-studio's page controls share one implementation — the same value typed in
|
|
5396
|
+
* a grid and on a page has to be accepted or rejected identically.
|
|
5397
|
+
*
|
|
5398
|
+
* Text rather than `type="number"` on purpose: a number input reformats the
|
|
5399
|
+
* value on its own, refuses to show a grouped or symbol-prefixed value, and
|
|
5400
|
+
* renders spinners the design does not use. Every eru-studio numeric field is
|
|
5401
|
+
* therefore a text input — which accepted any string at all until this.
|
|
5402
|
+
*
|
|
5403
|
+
* Filtering happens at `beforeinput`, so the character is judged in the context
|
|
5404
|
+
* it would land in: this is what tells a second '.' from the first, and a '-'
|
|
5405
|
+
* typed at the start from one typed mid-value. A `keypress` filter cannot see
|
|
5406
|
+
* either, and blur-time validation lets the user type a whole wrong value first.
|
|
5407
|
+
*/
|
|
5408
|
+
class NumericInputDirective {
|
|
5409
|
+
el = inject(ElementRef);
|
|
5410
|
+
/** Allow a decimal point. Off for an integer-only field. */
|
|
5411
|
+
allowDecimal = input(true, { ...(ngDevMode ? { debugName: "allowDecimal" } : {}), alias: 'eruAllowDecimal' });
|
|
5412
|
+
/** Allow a leading minus. Off where a negative makes no sense. */
|
|
5413
|
+
allowNegative = input(true, { ...(ngDevMode ? { debugName: "allowNegative" } : {}), alias: 'eruAllowNegative' });
|
|
5414
|
+
/**
|
|
5415
|
+
* Would this be a valid numeric string, allowing the in-progress forms a
|
|
5416
|
+
* user necessarily passes through — '', '-', '1.' — none of which parse as a
|
|
5417
|
+
* number but all of which are on the way to one.
|
|
5418
|
+
*/
|
|
5419
|
+
isAcceptable(value) {
|
|
5420
|
+
if (value === '')
|
|
5421
|
+
return true;
|
|
5422
|
+
const sign = this.allowNegative() ? '-?' : '';
|
|
5423
|
+
const decimal = this.allowDecimal() ? '(\\.\\d*)?' : '';
|
|
5424
|
+
return new RegExp(`^${sign}(\\d*${decimal})$`).test(value);
|
|
5425
|
+
}
|
|
5426
|
+
/** The value the input would hold if this event were allowed through. */
|
|
5427
|
+
projectedValue(data) {
|
|
5428
|
+
const input = this.el.nativeElement;
|
|
5429
|
+
const start = input.selectionStart ?? input.value.length;
|
|
5430
|
+
const end = input.selectionEnd ?? input.value.length;
|
|
5431
|
+
return input.value.slice(0, start) + data + input.value.slice(end);
|
|
5432
|
+
}
|
|
5433
|
+
onBeforeInput(event) {
|
|
5434
|
+
// Deletions, undo and the like carry no data and can only ever shorten the
|
|
5435
|
+
// value, so they are always safe.
|
|
5436
|
+
if (event.data === null || event.data === undefined)
|
|
5437
|
+
return;
|
|
5438
|
+
if (!this.isAcceptable(this.projectedValue(event.data)))
|
|
5439
|
+
event.preventDefault();
|
|
5440
|
+
}
|
|
5441
|
+
/**
|
|
5442
|
+
* A paste is filtered rather than blocked: pasting a formatted amount
|
|
5443
|
+
* ("$1,234.50") is a normal thing to do, and stripping it to 1234.50 is what
|
|
5444
|
+
* the user meant. Only a paste with no digits at all is rejected outright.
|
|
5445
|
+
*/
|
|
5446
|
+
onPaste(event) {
|
|
5447
|
+
const text = event.clipboardData?.getData('text') ?? '';
|
|
5448
|
+
const cleaned = this.clean(text);
|
|
5449
|
+
event.preventDefault();
|
|
5450
|
+
if (cleaned === '')
|
|
5451
|
+
return;
|
|
5452
|
+
const input = this.el.nativeElement;
|
|
5453
|
+
const start = input.selectionStart ?? input.value.length;
|
|
5454
|
+
const end = input.selectionEnd ?? input.value.length;
|
|
5455
|
+
const next = input.value.slice(0, start) + cleaned + input.value.slice(end);
|
|
5456
|
+
if (!this.isAcceptable(next))
|
|
5457
|
+
return;
|
|
5458
|
+
input.value = next;
|
|
5459
|
+
input.setSelectionRange(start + cleaned.length, start + cleaned.length);
|
|
5460
|
+
// Angular binds on `input`; setting `value` alone would leave the model behind.
|
|
5461
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
5462
|
+
}
|
|
5463
|
+
/** Digits, at most one decimal point, and a single leading minus. */
|
|
5464
|
+
clean(text) {
|
|
5465
|
+
let out = text.replace(/[^\d.\-]/g, '');
|
|
5466
|
+
const negative = this.allowNegative() && out.startsWith('-');
|
|
5467
|
+
out = out.replace(/-/g, '');
|
|
5468
|
+
if (this.allowDecimal()) {
|
|
5469
|
+
const first = out.indexOf('.');
|
|
5470
|
+
if (first !== -1)
|
|
5471
|
+
out = out.slice(0, first + 1) + out.slice(first + 1).replace(/\./g, '');
|
|
5472
|
+
}
|
|
5473
|
+
else {
|
|
5474
|
+
out = out.replace(/\./g, '');
|
|
5475
|
+
}
|
|
5476
|
+
return (negative ? '-' : '') + out;
|
|
5477
|
+
}
|
|
5478
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: NumericInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5479
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.2", type: NumericInputDirective, isStandalone: true, selector: "input[eruNumericInput]", inputs: { allowDecimal: { classPropertyName: "allowDecimal", publicName: "eruAllowDecimal", isSignal: true, isRequired: false, transformFunction: null }, allowNegative: { classPropertyName: "allowNegative", publicName: "eruAllowNegative", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "beforeinput": "onBeforeInput($event)", "paste": "onPaste($event)" } }, ngImport: i0 });
|
|
5480
|
+
}
|
|
5481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: NumericInputDirective, decorators: [{
|
|
5482
|
+
type: Directive,
|
|
5483
|
+
args: [{
|
|
5484
|
+
selector: 'input[eruNumericInput]',
|
|
5485
|
+
standalone: true,
|
|
5486
|
+
}]
|
|
5487
|
+
}], propDecorators: { allowDecimal: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruAllowDecimal", required: false }] }], allowNegative: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruAllowNegative", required: false }] }], onBeforeInput: [{
|
|
5488
|
+
type: HostListener,
|
|
5489
|
+
args: ['beforeinput', ['$event']]
|
|
5490
|
+
}], onPaste: [{
|
|
5491
|
+
type: HostListener,
|
|
5492
|
+
args: ['paste', ['$event']]
|
|
5493
|
+
}] } });
|
|
5494
|
+
|
|
5309
5495
|
class CurrencyComponent {
|
|
5310
5496
|
el = inject(ElementRef);
|
|
5311
5497
|
// Inputs
|
|
@@ -5452,15 +5638,16 @@ class CurrencyComponent {
|
|
|
5452
5638
|
setTimeout(tryFocus, 50);
|
|
5453
5639
|
}
|
|
5454
5640
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CurrencyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5455
|
-
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:var(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;color:var(--cell-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(--cell-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 });
|
|
5641
|
+
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 eruNumericInput\n inputmode=\"decimal\"\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:var(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;color:var(--cell-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(--cell-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}.currency-form-field .mat-mdc-form-field-text-prefix{padding-right: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"] }, { kind: "directive", type: NumericInputDirective, selector: "input[eruNumericInput]", inputs: ["eruAllowDecimal", "eruAllowNegative"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5456
5642
|
}
|
|
5457
5643
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CurrencyComponent, decorators: [{
|
|
5458
5644
|
type: Component,
|
|
5459
5645
|
args: [{ selector: 'eru-currency', standalone: true, imports: [
|
|
5460
5646
|
FormsModule,
|
|
5461
5647
|
MatFormFieldModule,
|
|
5462
|
-
MatInputModule
|
|
5463
|
-
|
|
5648
|
+
MatInputModule,
|
|
5649
|
+
NumericInputDirective
|
|
5650
|
+
], 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 eruNumericInput\n inputmode=\"decimal\"\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:var(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;color:var(--cell-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(--cell-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}.currency-form-field .mat-mdc-form-field-text-prefix{padding-right:4px}\n"] }]
|
|
5464
5651
|
}], 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: [{
|
|
5465
5652
|
type: Output
|
|
5466
5653
|
}], blur: [{
|
|
@@ -5594,15 +5781,16 @@ class NumberComponent {
|
|
|
5594
5781
|
setTimeout(tryFocus, 50);
|
|
5595
5782
|
}
|
|
5596
5783
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: NumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5597
|
-
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:var(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;color:var(--cell-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(--cell-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 });
|
|
5784
|
+
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 eruNumericInput\n inputmode=\"decimal\"\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:var(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;color:var(--cell-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(--cell-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"] }, { kind: "directive", type: NumericInputDirective, selector: "input[eruNumericInput]", inputs: ["eruAllowDecimal", "eruAllowNegative"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5598
5785
|
}
|
|
5599
5786
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: NumberComponent, decorators: [{
|
|
5600
5787
|
type: Component,
|
|
5601
5788
|
args: [{ selector: 'eru-number', standalone: true, imports: [
|
|
5602
5789
|
FormsModule,
|
|
5603
5790
|
MatFormFieldModule,
|
|
5604
|
-
MatInputModule
|
|
5605
|
-
|
|
5791
|
+
MatInputModule,
|
|
5792
|
+
NumericInputDirective
|
|
5793
|
+
], 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 eruNumericInput\n inputmode=\"decimal\"\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:var(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;color:var(--cell-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(--cell-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"] }]
|
|
5606
5794
|
}], 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: [{
|
|
5607
5795
|
type: Output
|
|
5608
5796
|
}], blur: [{
|
|
@@ -8067,6 +8255,227 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
8067
8255
|
type: Output
|
|
8068
8256
|
}] } });
|
|
8069
8257
|
|
|
8258
|
+
/**
|
|
8259
|
+
* A row of chips that fits its container, with a `+N` for the rest.
|
|
8260
|
+
*
|
|
8261
|
+
* Shared by the grid's and eru-studio's multi-selects so a set of values reads
|
|
8262
|
+
* the same in a cell, on a page and inside a select's trigger.
|
|
8263
|
+
*
|
|
8264
|
+
* Widths are MEASURED, not estimated. The tag cell assumes ~30px per chip,
|
|
8265
|
+
* which is close enough for short tags and wrong for the entity names a select
|
|
8266
|
+
* holds ("Acme Industries Pvt Ltd" is four times that) — it either wraps or
|
|
8267
|
+
* hides chips that would have fitted. Measuring with a canvas rather than by
|
|
8268
|
+
* rendering avoids the two-pass flicker of laying every chip out and then
|
|
8269
|
+
* hiding the overflow.
|
|
8270
|
+
*/
|
|
8271
|
+
class ChipListComponent {
|
|
8272
|
+
host = inject(ElementRef);
|
|
8273
|
+
// Classic @Input rather than signal input(): this component is consumed
|
|
8274
|
+
// ACROSS libraries (eru-grid's cells and eru-studio's page controls), and a
|
|
8275
|
+
// signal input's branded type is generated per @angular/core instance — so a
|
|
8276
|
+
// template in the other library cannot type-check against it.
|
|
8277
|
+
_chips = signal([], ...(ngDevMode ? [{ debugName: "_chips" }] : []));
|
|
8278
|
+
/** Plain labels — the neutral chip. */
|
|
8279
|
+
set labels(value) {
|
|
8280
|
+
this._chips.set((Array.isArray(value) ? value : []).map(label => ({ label: String(label) })));
|
|
8281
|
+
}
|
|
8282
|
+
/**
|
|
8283
|
+
* Chips that carry their own colours — a tag or a status, where the colour is
|
|
8284
|
+
* part of the value's meaning rather than decoration. Takes precedence over
|
|
8285
|
+
* `labels`; set one or the other.
|
|
8286
|
+
*/
|
|
8287
|
+
set chips(value) {
|
|
8288
|
+
this._chips.set(Array.isArray(value) ? value.filter(c => !!c && c.label !== undefined && c.label !== null) : []);
|
|
8289
|
+
}
|
|
8290
|
+
_compact = signal(false, ...(ngDevMode ? [{ debugName: "_compact" }] : []));
|
|
8291
|
+
set compact(value) { this._compact.set(!!value); }
|
|
8292
|
+
/**
|
|
8293
|
+
* Width to fit into. 0 means "measure the host", which is what a page control
|
|
8294
|
+
* wants; a grid cell passes its column width, which is known before layout.
|
|
8295
|
+
*/
|
|
8296
|
+
_availableWidth = signal(0, ...(ngDevMode ? [{ debugName: "_availableWidth" }] : []));
|
|
8297
|
+
set availableWidth(value) { this._availableWidth.set(Number(value) || 0); }
|
|
8298
|
+
isCompact = computed(() => this._compact(), ...(ngDevMode ? [{ debugName: "isCompact" }] : []));
|
|
8299
|
+
/** Width assumed when nothing above the row constrains it. */
|
|
8300
|
+
static UNCONSTRAINED_WIDTH = 280;
|
|
8301
|
+
measuredWidth = signal(0, ...(ngDevMode ? [{ debugName: "measuredWidth" }] : []));
|
|
8302
|
+
observer = null;
|
|
8303
|
+
constructor() {
|
|
8304
|
+
effect(() => {
|
|
8305
|
+
// Re-measure when the labels change: the same width fits a different
|
|
8306
|
+
// number of chips.
|
|
8307
|
+
this._chips();
|
|
8308
|
+
this._availableWidth();
|
|
8309
|
+
queueMicrotask(() => this.measure());
|
|
8310
|
+
});
|
|
8311
|
+
this.observe();
|
|
8312
|
+
}
|
|
8313
|
+
observe() {
|
|
8314
|
+
if (typeof ResizeObserver === 'undefined')
|
|
8315
|
+
return;
|
|
8316
|
+
this.observer = new ResizeObserver(() => this.measure());
|
|
8317
|
+
this.observer.observe(this.host.nativeElement);
|
|
8318
|
+
}
|
|
8319
|
+
ngOnDestroy() {
|
|
8320
|
+
this.observer?.disconnect();
|
|
8321
|
+
this.observer = null;
|
|
8322
|
+
}
|
|
8323
|
+
/**
|
|
8324
|
+
* The width to fit into.
|
|
8325
|
+
*
|
|
8326
|
+
* A grid cell hands us its column width and that is the constraint. A page
|
|
8327
|
+
* control does not: eru-studio sizes every field shell with `width:
|
|
8328
|
+
* fit-content`, so the host hugs whatever we just rendered — measuring the
|
|
8329
|
+
* host would mean measuring our own output, and the row could never grow past
|
|
8330
|
+
* the chip it happens to be showing. That is why view mode sat at one chip
|
|
8331
|
+
* while edit mode, which gets Material's fixed infix width, showed three.
|
|
8332
|
+
*
|
|
8333
|
+
* So when no width is given, look UP for the first ancestor that is wider
|
|
8334
|
+
* than the host: that is the box actually constraining the layout, and it
|
|
8335
|
+
* does not change when our content does.
|
|
8336
|
+
*/
|
|
8337
|
+
measure() {
|
|
8338
|
+
const explicit = this._availableWidth();
|
|
8339
|
+
if (explicit > 0) {
|
|
8340
|
+
if (explicit !== this.measuredWidth())
|
|
8341
|
+
this.measuredWidth.set(explicit);
|
|
8342
|
+
return;
|
|
8343
|
+
}
|
|
8344
|
+
const host = this.host.nativeElement;
|
|
8345
|
+
let width = 0;
|
|
8346
|
+
let node = host.parentElement;
|
|
8347
|
+
// Look for the first ancestor wider than the host: that is a real box, not
|
|
8348
|
+
// one hugging our output. A handful of levels is enough to clear the
|
|
8349
|
+
// content-sized shells without ending up measuring the page.
|
|
8350
|
+
for (let depth = 0; node && depth < 4; depth++, node = node.parentElement) {
|
|
8351
|
+
if (node.clientWidth > host.clientWidth) {
|
|
8352
|
+
width = node.clientWidth;
|
|
8353
|
+
break;
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
// Nothing above us is wider, so every ancestor is sized by our content and
|
|
8357
|
+
// there is no constraint to respect. Falling back to the host's own width
|
|
8358
|
+
// is what pinned the row to whatever it rendered first. A default budget
|
|
8359
|
+
// gives a few chips and a `+N` — and an author who wants a specific width
|
|
8360
|
+
// sets one, which lands as `availableWidth` and takes over.
|
|
8361
|
+
if (width <= 0)
|
|
8362
|
+
width = ChipListComponent.UNCONSTRAINED_WIDTH;
|
|
8363
|
+
if (width !== this.measuredWidth())
|
|
8364
|
+
this.measuredWidth.set(width);
|
|
8365
|
+
}
|
|
8366
|
+
/** Chip text width for the host's actual font, via a shared canvas. */
|
|
8367
|
+
static canvas = null;
|
|
8368
|
+
textWidth(text, font) {
|
|
8369
|
+
if (!ChipListComponent.canvas)
|
|
8370
|
+
ChipListComponent.canvas = document.createElement('canvas');
|
|
8371
|
+
const ctx = ChipListComponent.canvas.getContext('2d');
|
|
8372
|
+
if (!ctx)
|
|
8373
|
+
return text.length * 7;
|
|
8374
|
+
ctx.font = font;
|
|
8375
|
+
return ctx.measureText(text).width;
|
|
8376
|
+
}
|
|
8377
|
+
fit = computed(() => {
|
|
8378
|
+
const items = (this._chips() || []).filter(c => String(c.label).length > 0);
|
|
8379
|
+
const labels = items.map(c => String(c.label));
|
|
8380
|
+
const explicitWidth = this._availableWidth() > 0;
|
|
8381
|
+
const width = this.measuredWidth();
|
|
8382
|
+
if (items.length === 0)
|
|
8383
|
+
return { visible: [], hidden: [] };
|
|
8384
|
+
// Before the first measurement, show the first chip only: showing all of
|
|
8385
|
+
// them would flash a too-wide row for one frame.
|
|
8386
|
+
if (width <= 0)
|
|
8387
|
+
return { visible: items.slice(0, 1), hidden: items.slice(1) };
|
|
8388
|
+
const style = getComputedStyle(this.host.nativeElement);
|
|
8389
|
+
const font = `${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
|
|
8390
|
+
// A dot adds its own width plus its margin to every chip that has one.
|
|
8391
|
+
const hasDot = items.some(c => !!c.dot);
|
|
8392
|
+
const padding = (this._compact() ? 12 : 16) + (hasDot ? 10 : 0);
|
|
8393
|
+
const gap = 4;
|
|
8394
|
+
const chipWidth = (label) => this.textWidth(label, font) + padding;
|
|
8395
|
+
// A single value has no counter to fall back on, so it is always rendered
|
|
8396
|
+
// and left to the CSS ellipsis — "+1" would say less than a truncated name.
|
|
8397
|
+
if (items.length === 1)
|
|
8398
|
+
return { visible: items, hidden: [] };
|
|
8399
|
+
let used = 0;
|
|
8400
|
+
const visible = [];
|
|
8401
|
+
for (let i = 0; i < labels.length; i++) {
|
|
8402
|
+
const next = chipWidth(labels[i]) + (visible.length ? gap : 0);
|
|
8403
|
+
const remaining = labels.length - i - 1;
|
|
8404
|
+
// Room for the `+N` has to be reserved whenever anything would be left
|
|
8405
|
+
// over, or the counter itself is what overflows.
|
|
8406
|
+
const reserve = remaining > 0 ? chipWidth(`+${remaining}`) + gap : 0;
|
|
8407
|
+
if (used + next + reserve > width)
|
|
8408
|
+
break;
|
|
8409
|
+
used += next;
|
|
8410
|
+
visible.push(labels[i]);
|
|
8411
|
+
}
|
|
8412
|
+
// Too narrow for even the first chip: show the count alone. A clipped
|
|
8413
|
+
// fragment of one label ("B…") tells the reader neither which value it is
|
|
8414
|
+
// nor that there are others; "+5" at least says how many there are.
|
|
8415
|
+
//
|
|
8416
|
+
// Only when the width was HANDED to us, though — a grid cell knows its
|
|
8417
|
+
// column width, so the constraint is real. A page control is sized by its
|
|
8418
|
+
// content (eru-studio gives every field shell `width: fit-content`), which
|
|
8419
|
+
// makes width and content circular: collapsing to "+4" narrows the parent,
|
|
8420
|
+
// which keeps it collapsed. Keeping one chip breaks that loop — the parent
|
|
8421
|
+
// grows to fit it, the next measurement sees the wider box, and it settles
|
|
8422
|
+
// at however many genuinely fit.
|
|
8423
|
+
if (!explicitWidth && visible.length === 0) {
|
|
8424
|
+
return { visible: items.slice(0, 1), hidden: items.slice(1) };
|
|
8425
|
+
}
|
|
8426
|
+
return { visible: items.slice(0, visible.length), hidden: items.slice(visible.length) };
|
|
8427
|
+
}, ...(ngDevMode ? [{ debugName: "fit" }] : []));
|
|
8428
|
+
visible = computed(() => this.fit().visible, ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
8429
|
+
hiddenCount = computed(() => this.fit().hidden.length, ...(ngDevMode ? [{ debugName: "hiddenCount" }] : []));
|
|
8430
|
+
hiddenLabels = computed(() => this.fit().hidden.map(c => c.label).join(', '), ...(ngDevMode ? [{ debugName: "hiddenLabels" }] : []));
|
|
8431
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ChipListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8432
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: ChipListComponent, isStandalone: true, selector: "eru-chip-list", inputs: { labels: "labels", chips: "chips", compact: "compact", availableWidth: "availableWidth" }, ngImport: i0, template: `
|
|
8433
|
+
<div class="eru-chip-list" [class.eru-chip-list--compact]="isCompact()">
|
|
8434
|
+
@for (chip of visible(); track $index) {
|
|
8435
|
+
<span class="eru-chip" [title]="chip.label"
|
|
8436
|
+
[style.background]="chip.background || null"
|
|
8437
|
+
[style.color]="chip.color || null">
|
|
8438
|
+
@if (chip.dot) {
|
|
8439
|
+
<span class="eru-chip__dot" [style.background]="chip.dot"></span>
|
|
8440
|
+
}
|
|
8441
|
+
{{ chip.label }}
|
|
8442
|
+
</span>
|
|
8443
|
+
}
|
|
8444
|
+
@if (hiddenCount() > 0) {
|
|
8445
|
+
<span class="eru-chip eru-chip--more" [title]="hiddenLabels()">+{{ hiddenCount() }}</span>
|
|
8446
|
+
}
|
|
8447
|
+
</div>
|
|
8448
|
+
`, isInline: true, styles: [".eru-chip-list{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--eru-chip-gap, 4px);min-width:0;overflow:hidden}.eru-chip{flex:0 0 auto;max-width:100%;padding:var(--grid-pill-padding-y, 2px) var(--grid-pill-padding-x, 8px);border-radius:var(--grid-pill-radius, 999px);background:var(--grid-surface-container-high, #e6e0e9);color:var(--grid-on-surface, #1d1b20);font-size:var(--grid-pill-font-size, inherit);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.eru-chip--more{background:var(--grid-surface-variant, #e7e0ec);color:var(--grid-on-surface-variant, #49454f)}.eru-chip-list--compact .eru-chip{padding:1px 6px}.eru-chip__dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px;vertical-align:middle;flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
8449
|
+
}
|
|
8450
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ChipListComponent, decorators: [{
|
|
8451
|
+
type: Component,
|
|
8452
|
+
args: [{ selector: 'eru-chip-list', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
8453
|
+
<div class="eru-chip-list" [class.eru-chip-list--compact]="isCompact()">
|
|
8454
|
+
@for (chip of visible(); track $index) {
|
|
8455
|
+
<span class="eru-chip" [title]="chip.label"
|
|
8456
|
+
[style.background]="chip.background || null"
|
|
8457
|
+
[style.color]="chip.color || null">
|
|
8458
|
+
@if (chip.dot) {
|
|
8459
|
+
<span class="eru-chip__dot" [style.background]="chip.dot"></span>
|
|
8460
|
+
}
|
|
8461
|
+
{{ chip.label }}
|
|
8462
|
+
</span>
|
|
8463
|
+
}
|
|
8464
|
+
@if (hiddenCount() > 0) {
|
|
8465
|
+
<span class="eru-chip eru-chip--more" [title]="hiddenLabels()">+{{ hiddenCount() }}</span>
|
|
8466
|
+
}
|
|
8467
|
+
</div>
|
|
8468
|
+
`, styles: [".eru-chip-list{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--eru-chip-gap, 4px);min-width:0;overflow:hidden}.eru-chip{flex:0 0 auto;max-width:100%;padding:var(--grid-pill-padding-y, 2px) var(--grid-pill-padding-x, 8px);border-radius:var(--grid-pill-radius, 999px);background:var(--grid-surface-container-high, #e6e0e9);color:var(--grid-on-surface, #1d1b20);font-size:var(--grid-pill-font-size, inherit);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.eru-chip--more{background:var(--grid-surface-variant, #e7e0ec);color:var(--grid-on-surface-variant, #49454f)}.eru-chip-list--compact .eru-chip{padding:1px 6px}.eru-chip__dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px;vertical-align:middle;flex:0 0 auto}\n"] }]
|
|
8469
|
+
}], ctorParameters: () => [], propDecorators: { labels: [{
|
|
8470
|
+
type: Input
|
|
8471
|
+
}], chips: [{
|
|
8472
|
+
type: Input
|
|
8473
|
+
}], compact: [{
|
|
8474
|
+
type: Input
|
|
8475
|
+
}], availableWidth: [{
|
|
8476
|
+
type: Input
|
|
8477
|
+
}] } });
|
|
8478
|
+
|
|
8070
8479
|
class SelectComponent {
|
|
8071
8480
|
el = inject(ElementRef);
|
|
8072
8481
|
selectContainer;
|
|
@@ -8095,6 +8504,19 @@ class SelectComponent {
|
|
|
8095
8504
|
// Internal state
|
|
8096
8505
|
currentValue = signal(null, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
|
|
8097
8506
|
searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
8507
|
+
// ── Server-side search and paging (ENTITY_DATA / API) ───────────────────
|
|
8508
|
+
// The consumer answers a page at a time and does its own matching, so the
|
|
8509
|
+
// search box is not limited to whichever page is loaded. STATIC options are
|
|
8510
|
+
// already in memory and keep filtering on the client.
|
|
8511
|
+
static OPTIONS_PAGE_SIZE = 50;
|
|
8512
|
+
/** The rendered mat-select, for its `panel` element once the overlay opens. */
|
|
8513
|
+
matSelect;
|
|
8514
|
+
/** Debounced search text, sent to the consumer as `search` (→ field_str). */
|
|
8515
|
+
serverSearch = signal('', ...(ngDevMode ? [{ debugName: "serverSearch" }] : []));
|
|
8516
|
+
optionsSkip = signal(0, ...(ngDevMode ? [{ debugName: "optionsSkip" }] : []));
|
|
8517
|
+
/** Set when a short page comes back — nothing more to ask for. */
|
|
8518
|
+
optionsExhausted = signal(false, ...(ngDevMode ? [{ debugName: "optionsExhausted" }] : []));
|
|
8519
|
+
searchDebounce = null;
|
|
8098
8520
|
error = signal('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
8099
8521
|
// Computed properties for Select All functionality
|
|
8100
8522
|
isAllSelected = computed(() => {
|
|
@@ -8275,7 +8697,9 @@ class SelectComponent {
|
|
|
8275
8697
|
fieldName: cfg.field_name,
|
|
8276
8698
|
apiName: cfg.api_name,
|
|
8277
8699
|
apiField: cfg.api_field,
|
|
8278
|
-
search:
|
|
8700
|
+
search: this.serverSearch(),
|
|
8701
|
+
limit: SelectComponent.OPTIONS_PAGE_SIZE,
|
|
8702
|
+
skip: this.optionsSkip(),
|
|
8279
8703
|
...(Object.keys(filter).length > 0 ? { filter } : {}),
|
|
8280
8704
|
// API sources keep the legacy single dependency.
|
|
8281
8705
|
...(cfg.option_type === 'API' && cfg.dpef
|
|
@@ -8462,18 +8886,93 @@ class SelectComponent {
|
|
|
8462
8886
|
return '';
|
|
8463
8887
|
const filter = this.dependencyFilter();
|
|
8464
8888
|
const keys = Object.keys(filter).sort();
|
|
8465
|
-
if (keys.length === 0)
|
|
8466
|
-
return baseKey;
|
|
8467
8889
|
// Sorted so the same filter always produces the same key regardless of the
|
|
8468
8890
|
// order the pairs were authored in.
|
|
8469
|
-
|
|
8891
|
+
const scoped = keys.length === 0 ? baseKey : `${baseKey}|${keys.map(k => `${k}=${filter[k]}`).join('&')}`;
|
|
8892
|
+
// The search term is part of the identity of the list: without it the cache
|
|
8893
|
+
// would answer a search from the unfiltered page it already holds, and the
|
|
8894
|
+
// request would be skipped as already-fetched.
|
|
8895
|
+
const search = this.serverSearch();
|
|
8896
|
+
return search ? `${scoped}|q=${search}` : scoped;
|
|
8470
8897
|
}, ...(ngDevMode ? [{ debugName: "optionsCacheKey" }] : []));
|
|
8471
8898
|
onSearchChange(searchValue) {
|
|
8472
|
-
|
|
8899
|
+
const value = searchValue || '';
|
|
8900
|
+
this.searchText.set(value);
|
|
8901
|
+
const optionType = this.config()?.option_type;
|
|
8902
|
+
if (optionType !== 'ENTITY_DATA' && optionType !== 'API')
|
|
8903
|
+
return;
|
|
8904
|
+
// Debounced: the box would otherwise ask the consumer once per keystroke.
|
|
8905
|
+
if (this.searchDebounce)
|
|
8906
|
+
clearTimeout(this.searchDebounce);
|
|
8907
|
+
this.searchDebounce = setTimeout(() => {
|
|
8908
|
+
this.searchDebounce = null;
|
|
8909
|
+
if (this.serverSearch() === value)
|
|
8910
|
+
return;
|
|
8911
|
+
// A new term is a new list: back to the first page.
|
|
8912
|
+
this.optionsSkip.set(0);
|
|
8913
|
+
this.optionsExhausted.set(false);
|
|
8914
|
+
this.serverSearch.set(value);
|
|
8915
|
+
}, 300);
|
|
8916
|
+
}
|
|
8917
|
+
/**
|
|
8918
|
+
* Ask the consumer for the next page when the panel nears its end.
|
|
8919
|
+
*
|
|
8920
|
+
* Options are appended, never recycled: mat-select addresses MatOption
|
|
8921
|
+
* instances for its selection, trigger text and keyboard navigation, so a
|
|
8922
|
+
* virtual scroller destroying them as they leave the viewport breaks all
|
|
8923
|
+
* three. Paging the data while keeping the DOM is what is wanted here.
|
|
8924
|
+
*/
|
|
8925
|
+
panelScrollTeardown = null;
|
|
8926
|
+
/** Attach the paging listener to mat-select's panel once the overlay exists. */
|
|
8927
|
+
attachPanelScroll() {
|
|
8928
|
+
this.detachPanelScroll();
|
|
8929
|
+
const optionType = this.config()?.option_type;
|
|
8930
|
+
if (optionType !== 'ENTITY_DATA' && optionType !== 'API')
|
|
8931
|
+
return;
|
|
8932
|
+
setTimeout(() => {
|
|
8933
|
+
const panel = this.matSelect?.panel?.nativeElement ?? null;
|
|
8934
|
+
if (!panel)
|
|
8935
|
+
return;
|
|
8936
|
+
const handler = () => this.onPanelScroll(panel);
|
|
8937
|
+
panel.addEventListener('scroll', handler, { passive: true });
|
|
8938
|
+
this.panelScrollTeardown = () => panel.removeEventListener('scroll', handler);
|
|
8939
|
+
});
|
|
8940
|
+
}
|
|
8941
|
+
detachPanelScroll() {
|
|
8942
|
+
this.panelScrollTeardown?.();
|
|
8943
|
+
this.panelScrollTeardown = null;
|
|
8944
|
+
}
|
|
8945
|
+
onPanelScroll(panel) {
|
|
8946
|
+
const optionType = this.config()?.option_type;
|
|
8947
|
+
if (optionType !== 'ENTITY_DATA' && optionType !== 'API')
|
|
8948
|
+
return;
|
|
8949
|
+
if (this.optionsExhausted())
|
|
8950
|
+
return;
|
|
8951
|
+
if (!panel)
|
|
8952
|
+
return;
|
|
8953
|
+
if (panel.scrollHeight - panel.scrollTop - panel.clientHeight > 96)
|
|
8954
|
+
return;
|
|
8955
|
+
const store = this.eruGridStore();
|
|
8956
|
+
if (!store || store.hasDynamicDataRequest(this.optionsCacheKey()))
|
|
8957
|
+
return;
|
|
8958
|
+
this.optionsSkip.update(skip => skip + SelectComponent.OPTIONS_PAGE_SIZE);
|
|
8473
8959
|
}
|
|
8474
8960
|
onOpenedChange(opened) {
|
|
8961
|
+
if (opened) {
|
|
8962
|
+
this.attachPanelScroll();
|
|
8963
|
+
}
|
|
8475
8964
|
if (!opened) {
|
|
8965
|
+
this.detachPanelScroll();
|
|
8476
8966
|
this.searchText.set('');
|
|
8967
|
+
if (this.searchDebounce) {
|
|
8968
|
+
clearTimeout(this.searchDebounce);
|
|
8969
|
+
this.searchDebounce = null;
|
|
8970
|
+
}
|
|
8971
|
+
// Reopening starts clean, or the previous session's term would still be
|
|
8972
|
+
// in force behind an empty search box.
|
|
8973
|
+
this.serverSearch.set('');
|
|
8974
|
+
this.optionsSkip.set(0);
|
|
8975
|
+
this.optionsExhausted.set(false);
|
|
8477
8976
|
} /* else {
|
|
8478
8977
|
// Set panel width when opened
|
|
8479
8978
|
setTimeout(() => {
|
|
@@ -8514,6 +9013,25 @@ class SelectComponent {
|
|
|
8514
9013
|
hasRequiredValidation() {
|
|
8515
9014
|
return this.getProperty('required') === true;
|
|
8516
9015
|
}
|
|
9016
|
+
/**
|
|
9017
|
+
* Selected values as their option labels, for the chip row.
|
|
9018
|
+
*
|
|
9019
|
+
* Same resolution as formatDisplayValue — the chip list only changes how they
|
|
9020
|
+
* are laid out, so the two must never disagree about what a value is called.
|
|
9021
|
+
*/
|
|
9022
|
+
selectedLabels = computed(() => {
|
|
9023
|
+
const value = this.currentValue();
|
|
9024
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
9025
|
+
return [];
|
|
9026
|
+
const options = this.filteredOptions();
|
|
9027
|
+
return value
|
|
9028
|
+
.map(v => {
|
|
9029
|
+
const option = options.find((opt) => opt.value === v);
|
|
9030
|
+
return option ? option.label : String(v);
|
|
9031
|
+
})
|
|
9032
|
+
.filter(Boolean)
|
|
9033
|
+
.map(String);
|
|
9034
|
+
}, ...(ngDevMode ? [{ debugName: "selectedLabels" }] : []));
|
|
8517
9035
|
formatDisplayValue() {
|
|
8518
9036
|
const value = this.currentValue();
|
|
8519
9037
|
const cfg = this.config();
|
|
@@ -8589,7 +9107,7 @@ class SelectComponent {
|
|
|
8589
9107
|
return undefined;
|
|
8590
9108
|
}
|
|
8591
9109
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8592
|
-
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:var(--cell-color, #1976d2);cursor:pointer;text-decoration:underline}.select-drillable:hover{color:var(--cell-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(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--cell-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:var(--cell-font-size, 16px);font-weight:var(--cell-font-weight, 700);color:var(--cell-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:var(--cell-font-size, 16px);font-weight:var(--cell-font-weight, 700);color:var(--cell-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$3.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$3.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: i2$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 });
|
|
9110
|
+
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 }, { propertyName: "matSelect", first: true, predicate: MatSelect, 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 <!-- Material's own custom-trigger hook (MatSelectTrigger). Everything\n mat-select does \u2014 selection model, keyboard, a11y \u2014 is untouched;\n only the comma-joined text is replaced by the chip row. -->\n @if (multiple() && selectedLabels().length > 0) {\n <mat-select-trigger>\n <eru-chip-list [labels]=\"selectedLabels()\" [compact]=\"true\"></eru-chip-list>\n </mat-select-trigger>\n }\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 (multiple() && selectedLabels().length > 0) {\n <eru-chip-list [labels]=\"selectedLabels()\" [compact]=\"true\" [availableWidth]=\"columnWidth()\"></eru-chip-list>\n } @else 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:var(--cell-color, #1976d2);cursor:pointer;text-decoration:underline}.select-drillable:hover{color:var(--cell-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(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--cell-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:var(--cell-font-size, 16px);font-weight:var(--cell-font-weight, 700);color:var(--cell-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:var(--cell-font-size, 16px);font-weight:var(--cell-font-weight, 700);color:var(--cell-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$3.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$3.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i2$3.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: i2$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: "component", type: ChipListComponent, selector: "eru-chip-list", inputs: ["labels", "chips", "compact", "availableWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
8593
9111
|
}
|
|
8594
9112
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SelectComponent, decorators: [{
|
|
8595
9113
|
type: Component,
|
|
@@ -8599,8 +9117,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
8599
9117
|
MatSelectModule,
|
|
8600
9118
|
MatInputModule,
|
|
8601
9119
|
MatOptionModule,
|
|
8602
|
-
MatCheckboxModule
|
|
8603
|
-
|
|
9120
|
+
MatCheckboxModule,
|
|
9121
|
+
ChipListComponent
|
|
9122
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, 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 <!-- Material's own custom-trigger hook (MatSelectTrigger). Everything\n mat-select does \u2014 selection model, keyboard, a11y \u2014 is untouched;\n only the comma-joined text is replaced by the chip row. -->\n @if (multiple() && selectedLabels().length > 0) {\n <mat-select-trigger>\n <eru-chip-list [labels]=\"selectedLabels()\" [compact]=\"true\"></eru-chip-list>\n </mat-select-trigger>\n }\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 (multiple() && selectedLabels().length > 0) {\n <eru-chip-list [labels]=\"selectedLabels()\" [compact]=\"true\" [availableWidth]=\"columnWidth()\"></eru-chip-list>\n } @else 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:var(--cell-color, #1976d2);cursor:pointer;text-decoration:underline}.select-drillable:hover{color:var(--cell-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(--cell-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(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--cell-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:var(--cell-font-size, 16px);font-weight:var(--cell-font-weight, 700);color:var(--cell-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:var(--cell-font-size, 16px);font-weight:var(--cell-font-weight, 700);color:var(--cell-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"] }]
|
|
8604
9123
|
}], ctorParameters: () => [], propDecorators: { selectContainer: [{
|
|
8605
9124
|
type: ViewChild,
|
|
8606
9125
|
args: ['selectContainer', { static: false }]
|
|
@@ -8614,6 +9133,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
8614
9133
|
type: Output
|
|
8615
9134
|
}], editModeChange: [{
|
|
8616
9135
|
type: Output
|
|
9136
|
+
}], matSelect: [{
|
|
9137
|
+
type: ViewChild,
|
|
9138
|
+
args: [MatSelect]
|
|
8617
9139
|
}] } });
|
|
8618
9140
|
|
|
8619
9141
|
class StatusComponent {
|
|
@@ -8973,6 +9495,20 @@ class TagComponent {
|
|
|
8973
9495
|
}
|
|
8974
9496
|
return processedOptions.filter((option) => option.label.toLowerCase().includes(searchText) || option.value.toLowerCase().includes(searchText));
|
|
8975
9497
|
}, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : []));
|
|
9498
|
+
/**
|
|
9499
|
+
* Selected tags as coloured chips for the shared chip row.
|
|
9500
|
+
*
|
|
9501
|
+
* The colour is part of a tag's meaning, so it travels with the label rather
|
|
9502
|
+
* than the chip list assuming a neutral pill.
|
|
9503
|
+
*/
|
|
9504
|
+
tagChips = computed(() => {
|
|
9505
|
+
return (this.currentValue() || [])
|
|
9506
|
+
.filter(tag => tag !== null && tag !== undefined && String(tag).length > 0)
|
|
9507
|
+
.map(tag => {
|
|
9508
|
+
const colors = this.getTagColor(String(tag));
|
|
9509
|
+
return { label: String(tag), background: colors.background, color: colors.text, dot: colors.dot };
|
|
9510
|
+
});
|
|
9511
|
+
}, ...(ngDevMode ? [{ debugName: "tagChips" }] : []));
|
|
8976
9512
|
// Calculate max display tags based on column width
|
|
8977
9513
|
maxDisplayTags = computed(() => {
|
|
8978
9514
|
const columnWidth = this.columnWidth();
|
|
@@ -9217,7 +9753,7 @@ class TagComponent {
|
|
|
9217
9753
|
this.newTagColor.set('#cccccc');
|
|
9218
9754
|
}
|
|
9219
9755
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: TagComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9220
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: TagComponent, isStandalone: true, selector: "eru-tag", 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", newTagAdded: "newTagAdded" }, viewQueries: [{ propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true }], ngImport: i0, template: "@if(isActive()) {\n <mat-form-field \n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"tag-form-field\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"tag-container\">\n <mat-select\n [placeholder]=\"getProperty('placeholder') || ''\"\n [multiple]=\"true\"\n [disabled]=\"getProperty('disabled') || !isEditable()\"\n [required]=\"hasRequiredValidation()\"\n [value]=\"currentValue()\"\n [compareWith]=\"compareWith\"\n panelClass=\"tag-panel\"\n (selectionChange)=\"onValueChange($event.value)\"\n (blur)=\"onBlur($event)\"\n (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n \n <mat-option disabled class=\"search-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n @if (isSearchable()) {\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"search-input\"\n [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n }\n <div class=\"select-all-container mat-mdc-option mdc-list-item\"\n [class.mdc-list-item--selected]=\"isAllSelected() || isIndeterminate()\"\n [class.select-all-partial]=\"isIndeterminate()\"\n (click)=\"toggleSelectAll(!isAllSelected()); $event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-pseudo-checkbox\n class=\"mat-mdc-option-pseudo-checkbox select-all-checkbox\"\n [state]=\"isIndeterminate() ? 'indeterminate' : (isAllSelected() ? 'checked' : 'unchecked')\"></mat-pseudo-checkbox>\n <span class=\"mdc-list-item__primary-text select-all-text\">Select All</span>\n </div>\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" \n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\" \n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"tag-option\" \n [style.background]=\"getTagColor(option.value).background\" \n [style.color]=\"getTagColor(option.value).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(option.value).dot\"></span>\n {{ option.label }}\n </span>\n </mat-option>\n }\n\n <mat-option class=\"add-tag-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"add-tag-container\" (click)=\"$event.stopPropagation()\">\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"add-tag-form-field\">\n <mat-label>Add new tag</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"add-tag-input\"\n [value]=\"newTagInput()\"\n (input)=\"onNewTagInputChange($any($event.target).value)\"\n (keydown.enter)=\"addNewTag()\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n <div class=\"add-tag-actions\">\n <input\n type=\"color\"\n class=\"add-tag-color-picker\"\n [value]=\"newTagColor()\"\n (input)=\"onNewTagColorChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n title=\"Select tag color\">\n <button\n mat-icon-button\n class=\"add-tag-button\"\n [disabled]=\"!canAddNewTag()\"\n (click)=\"addNewTag(); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n </mat-option>\n\n </mat-select>\n </div>\n </mat-form-field>\n} @else {\n <div \n class=\"tag-display\" \n [class.tag-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n @for (tag of tagDisplay().displayTags; track tag) {\n <span class=\"tag\" \n [style.background]=\"getTagColor(tag).background\" \n [style.color]=\"getTagColor(tag).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(tag).dot\"></span>\n {{tag}}\n </span>\n }\n @if(tagDisplay().moreCount > 0) {\n <span class=\"tag\">\n + {{tagDisplay().moreCount}}\n </span>\n }\n </span>\n } @else {\n @for (tag of tagDisplay().displayTags; track tag) {\n <span class=\"tag\" \n [style.background]=\"getTagColor(tag).background\" \n [style.color]=\"getTagColor(tag).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(tag).dot\"></span>\n {{tag}}\n </span>\n }\n @if(tagDisplay().moreCount > 0) {\n <span class=\"tag-more\">\n + {{tagDisplay().moreCount}}\n </span>\n }\n }\n </div>\n}\n\n", styles: [".tag-form-field{width:100%}.tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.tag-container{width:100%}.tag-display{width:100%;padding:8px 12px;min-height:30px;display:flex;flex-wrap:wrap;align-items:center;gap:4px;cursor:default}.tag-display.tag-display-editable{cursor:pointer}.tag-display.tag-display-editable:hover{background-color:#0000000a}.tag{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);border-radius:var(--grid-pill-radius, 12px);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 500));white-space:nowrap;line-height:1.2}.tag-dot{flex:0 0 auto;width:var(--grid-tag-dot-size, 6px);height:var(--grid-tag-dot-size, 6px);border-radius:50%}.tag-more{display:inline-block;border-radius:var(--grid-pill-radius, 12px);background-color:var(--grid-surface-container-high, #f5f5f5);color:var(--grid-on-surface-variant, #49454f);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 700));font-style:italic;padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);white-space:nowrap;line-height:1.2}.drillable-value{display:flex;flex-wrap:wrap;gap:4px;align-items:center;cursor:pointer}.drillable-value .tag{text-decoration:underline}.add-option{position:sticky;position:-webkit-sticky;bottom:0;z-index:3;background:var(--grid-surface-container-high, #fff);box-shadow:0 -2px 8px -4px #00000017;border-top:1px solid var(--grid-outline-variant, #e0e0e0)}.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-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;display:flex!important;flex-direction:column!important;gap:8px!important;opacity:1!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(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.search-option .select-all-container{padding:4px 0!important;min-height:auto!important;display:flex!important;align-items:center!important;pointer-events:auto!important;cursor:pointer!important;background:transparent!important}.search-option .select-all-container:hover{background:var(--grid-row-hover, rgba(0, 0, 0, .04))!important}.search-option .select-all-container .select-all-text{display:inline-flex!important;flex-direction:row!important;align-items:center!important;width:auto!important;max-width:none!important;gap:0!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--grid-on-surface, #1d1b20)!important}.search-option .select-all-container.select-all-partial .select-all-text:before,.search-option .select-all-container.select-all-partial .select-all-checkbox{opacity:.5}.tag-panel-footer{position:absolute;bottom:0;left:0;right:0;background:#fff;border-top:1px solid rgba(0,0,0,.12);padding:8px;z-index:10;box-shadow:0 -2px 4px #0000001a}.tag-panel-footer .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;pointer-events:auto}.tag-panel-footer .add-tag-form-field-wrapper{flex:1;min-width:0;position:relative}.tag-panel-footer .new-tag-input{width:100%;border:1px solid rgba(0,0,0,.38);border-radius:4px;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 12px;font-family:var(--grid-font-family, \"Poppins\")!important;box-sizing:border-box;cursor:text}.tag-panel-footer .new-tag-input:focus{border-color:var(--grid-primary, #6750a4);border-width:2px}.tag-panel-footer .add-tag-button{flex-shrink:0;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 16px!important;min-width:auto!important;pointer-events:auto!important;background-color:var(--grid-primary, #6750a4);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:var(--grid-font-family, \"Poppins\")!important}.tag-panel-footer .add-tag-button:hover:not([disabled]){opacity:.9}.tag-panel-footer .add-tag-button[disabled]{opacity:.5;cursor:not-allowed}.cdk-overlay-pane.tag-panel-overlay{position:relative;padding-bottom:60px!important}.cdk-overlay-pane.tag-panel-overlay .mat-mdc-select-panel{max-height:calc(100% - 60px)!important}.add-tag-option{position:sticky!important;background:var(--grid-surface, #fff)!important;bottom:-10px!important;z-index:10!important;padding:8px 8px 10px!important;margin-top:auto!important;margin-bottom:0!important;box-shadow:0 -2px 8px #00000026!important;border-top:1px solid rgba(0,0,0,.12)!important}.add-tag-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mat-mdc-option{min-height:auto!important;padding:0!important;height:auto!important}.add-tag-option .mat-mdc-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mdc-list-item__primary-text{width:100%!important;padding:0!important;margin:0!important}.add-tag-option .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;padding:0;margin-bottom:0}.add-tag-option .add-tag-form-field{flex:1;min-width:0}.add-tag-option .add-tag-form-field .mat-mdc-form-field-focus-overlay{background:transparent!important}.add-tag-option .add-tag-form-field .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field .mdc-notched-outline__trailing{border-color:var(--grid-outline-variant, #cac4d0)!important;border-width:1px!important}.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--grid-outline, #79747e)!important}.add-tag-option .add-tag-form-field .mdc-floating-label,.add-tag-option .add-tag-form-field label{color:var(--grid-on-surface-variant, #49454f)!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.add-tag-option .add-tag-form-field .mat-mdc-text-field-wrapper{background:transparent!important;padding-bottom:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-infix{min-height:auto!important;padding:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-flex{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.add-tag-option .add-tag-actions{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;flex-shrink:0}.add-tag-option .add-tag-color-picker{flex-shrink:0;width:20px;height:20px;border:1px solid rgba(0,0,0,.12);border-radius:4px;cursor:pointer;padding:2px;background:transparent;box-sizing:border-box;margin:0 auto}.add-tag-option .add-tag-color-picker::-webkit-color-swatch-wrapper{padding:0}.add-tag-option .add-tag-color-picker::-webkit-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker::-moz-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker:hover{border-color:var(--grid-primary, #6750a4)}.add-tag-option .add-tag-color-picker:focus{outline:2px solid var(--grid-primary, #6750a4);outline-offset:2px}.add-tag-option .add-tag-button{flex-shrink:0;width:20px;height:20px;color:var(--grid-primary, #6750a4)!important;display:flex!important;align-items:center!important;justify-content:center!important;margin:0 auto}.add-tag-option .add-tag-button:not([disabled]){color:var(--grid-primary, #6750a4)!important}.add-tag-option .add-tag-button:not([disabled]):hover{background-color:#6750a414!important}.add-tag-option .add-tag-button[disabled]{opacity:.38;color:#00000061!important}.add-tag-option .add-tag-button mat-icon{font-size:var(--cell-font-size, 24px);width:24px;height:24px;line-height:24px;display:flex;align-items:center;justify-content:center;margin-right:0}.tag-panel .search-option>mat-pseudo-checkbox,.tag-panel .search-option>.mat-mdc-option-pseudo-checkbox,.tag-panel .add-tag-option>mat-pseudo-checkbox,.tag-panel .add-tag-option>.mat-mdc-option-pseudo-checkbox{display:none!important}.tag-panel{min-width:200px!important}.tag-panel .search-option .mat-mdc-option,.mat-mdc-select-panel .search-option .mat-mdc-option{min-height:auto!important;padding:2px 4px!important}.tag-panel .mdc-list-item--disabled,.mat-mdc-select-panel .mdc-list-item--disabled{pointer-events:auto!important;cursor:default!important}.tag-panel .mdc-list-item--disabled:hover,.mat-mdc-select-panel .mdc-list-item--disabled:hover{background:transparent!important}mat-option .mdc-list-item__primary-text{line-height:1.5!important;white-space:normal!important;display:flex!important;align-items:center!important}mat-option .tag-option{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:4px 8px;border-radius:12px;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px));font-weight:var(--cell-font-weight, 500);white-space:nowrap;line-height:1.2}\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$3.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$3.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: MatPseudoCheckboxModule }, { kind: "component", type: i1$1.MatPseudoCheckbox, selector: "mat-pseudo-checkbox", inputs: ["state", "disabled", "appearance"] }, { kind: "ngmodule", type: MatChipsModule }, { 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: MatIconModule }, { kind: "component", type: i2$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
9756
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: TagComponent, isStandalone: true, selector: "eru-tag", 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", newTagAdded: "newTagAdded" }, viewQueries: [{ propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true }], ngImport: i0, template: "@if(isActive()) {\n <mat-form-field \n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"tag-form-field\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"tag-container\">\n <mat-select\n [placeholder]=\"getProperty('placeholder') || ''\"\n [multiple]=\"true\"\n [disabled]=\"getProperty('disabled') || !isEditable()\"\n [required]=\"hasRequiredValidation()\"\n [value]=\"currentValue()\"\n [compareWith]=\"compareWith\"\n panelClass=\"tag-panel\"\n (selectionChange)=\"onValueChange($event.value)\"\n (blur)=\"onBlur($event)\"\n (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n <!-- Material's own custom-trigger hook (MatSelectTrigger), so the closed\n control shows the same coloured chip row as view mode instead of\n mat-select's comma-joined text. Nothing about mat-select's own\n behaviour is replaced. -->\n <!-- Unconditional: `customTrigger` is a content query, and gating the\n element behind an @if leaves it unresolved on the pass that matters.\n mat-select renders its placeholder while the value is empty, so an\n always-present trigger costs nothing. -->\n <mat-select-trigger>\n <eru-chip-list [chips]=\"tagChips()\" [compact]=\"true\"></eru-chip-list>\n </mat-select-trigger>\n \n <mat-option disabled class=\"search-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n @if (isSearchable()) {\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"search-input\"\n [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n }\n <div class=\"select-all-container mat-mdc-option mdc-list-item\"\n [class.mdc-list-item--selected]=\"isAllSelected() || isIndeterminate()\"\n [class.select-all-partial]=\"isIndeterminate()\"\n (click)=\"toggleSelectAll(!isAllSelected()); $event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-pseudo-checkbox\n class=\"mat-mdc-option-pseudo-checkbox select-all-checkbox\"\n [state]=\"isIndeterminate() ? 'indeterminate' : (isAllSelected() ? 'checked' : 'unchecked')\"></mat-pseudo-checkbox>\n <span class=\"mdc-list-item__primary-text select-all-text\">Select All</span>\n </div>\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" \n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\" \n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"tag-option\" \n [style.background]=\"getTagColor(option.value).background\" \n [style.color]=\"getTagColor(option.value).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(option.value).dot\"></span>\n {{ option.label }}\n </span>\n </mat-option>\n }\n\n <mat-option class=\"add-tag-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"add-tag-container\" (click)=\"$event.stopPropagation()\">\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"add-tag-form-field\">\n <mat-label>Add new tag</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"add-tag-input\"\n [value]=\"newTagInput()\"\n (input)=\"onNewTagInputChange($any($event.target).value)\"\n (keydown.enter)=\"addNewTag()\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n <div class=\"add-tag-actions\">\n <input\n type=\"color\"\n class=\"add-tag-color-picker\"\n [value]=\"newTagColor()\"\n (input)=\"onNewTagColorChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n title=\"Select tag color\">\n <button\n mat-icon-button\n class=\"add-tag-button\"\n [disabled]=\"!canAddNewTag()\"\n (click)=\"addNewTag(); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n </mat-option>\n\n </mat-select>\n </div>\n </mat-form-field>\n} @else {\n <div \n class=\"tag-display\" \n [class.tag-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <eru-chip-list [chips]=\"tagChips()\" [compact]=\"true\" [availableWidth]=\"columnWidth()\"></eru-chip-list>\n </span>\n } @else {\n <eru-chip-list [chips]=\"tagChips()\" [compact]=\"true\" [availableWidth]=\"columnWidth()\"></eru-chip-list>\n }\n </div>\n}\n\n", styles: [".tag-form-field{width:100%}.tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.tag-container{width:100%}.tag-display{width:100%;padding:8px 12px;min-height:30px;display:flex;flex-wrap:wrap;align-items:center;gap:4px;cursor:default}.tag-display.tag-display-editable{cursor:pointer}.tag-display.tag-display-editable:hover{background-color:#0000000a}.tag{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);border-radius:var(--grid-pill-radius, 12px);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 500));white-space:nowrap;line-height:1.2}.tag-dot{flex:0 0 auto;width:var(--grid-tag-dot-size, 6px);height:var(--grid-tag-dot-size, 6px);border-radius:50%}.tag-more{display:inline-block;border-radius:var(--grid-pill-radius, 12px);background-color:var(--grid-surface-container-high, #f5f5f5);color:var(--grid-on-surface-variant, #49454f);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 700));font-style:italic;padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);white-space:nowrap;line-height:1.2}.drillable-value{display:flex;flex-wrap:wrap;gap:4px;align-items:center;cursor:pointer}.drillable-value .tag{text-decoration:underline}.add-option{position:sticky;position:-webkit-sticky;bottom:0;z-index:3;background:var(--grid-surface-container-high, #fff);box-shadow:0 -2px 8px -4px #00000017;border-top:1px solid var(--grid-outline-variant, #e0e0e0)}.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-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;display:flex!important;flex-direction:column!important;gap:8px!important;opacity:1!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(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.search-option .select-all-container{padding:4px 0!important;min-height:auto!important;display:flex!important;align-items:center!important;pointer-events:auto!important;cursor:pointer!important;background:transparent!important}.search-option .select-all-container:hover{background:var(--grid-row-hover, rgba(0, 0, 0, .04))!important}.search-option .select-all-container .select-all-text{display:inline-flex!important;flex-direction:row!important;align-items:center!important;width:auto!important;max-width:none!important;gap:0!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--grid-on-surface, #1d1b20)!important}.search-option .select-all-container.select-all-partial .select-all-text:before,.search-option .select-all-container.select-all-partial .select-all-checkbox{opacity:.5}.tag-panel-footer{position:absolute;bottom:0;left:0;right:0;background:#fff;border-top:1px solid rgba(0,0,0,.12);padding:8px;z-index:10;box-shadow:0 -2px 4px #0000001a}.tag-panel-footer .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;pointer-events:auto}.tag-panel-footer .add-tag-form-field-wrapper{flex:1;min-width:0;position:relative}.tag-panel-footer .new-tag-input{width:100%;border:1px solid rgba(0,0,0,.38);border-radius:4px;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 12px;font-family:var(--grid-font-family, \"Poppins\")!important;box-sizing:border-box;cursor:text}.tag-panel-footer .new-tag-input:focus{border-color:var(--grid-primary, #6750a4);border-width:2px}.tag-panel-footer .add-tag-button{flex-shrink:0;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 16px!important;min-width:auto!important;pointer-events:auto!important;background-color:var(--grid-primary, #6750a4);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:var(--grid-font-family, \"Poppins\")!important}.tag-panel-footer .add-tag-button:hover:not([disabled]){opacity:.9}.tag-panel-footer .add-tag-button[disabled]{opacity:.5;cursor:not-allowed}.cdk-overlay-pane.tag-panel-overlay{position:relative;padding-bottom:60px!important}.cdk-overlay-pane.tag-panel-overlay .mat-mdc-select-panel{max-height:calc(100% - 60px)!important}.add-tag-option{position:sticky!important;background:var(--grid-surface, #fff)!important;bottom:-10px!important;z-index:10!important;padding:8px 8px 10px!important;margin-top:auto!important;margin-bottom:0!important;box-shadow:0 -2px 8px #00000026!important;border-top:1px solid rgba(0,0,0,.12)!important}.add-tag-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mat-mdc-option{min-height:auto!important;padding:0!important;height:auto!important}.add-tag-option .mat-mdc-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mdc-list-item__primary-text{width:100%!important;padding:0!important;margin:0!important}.add-tag-option .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;padding:0;margin-bottom:0}.add-tag-option .add-tag-form-field{flex:1;min-width:0}.add-tag-option .add-tag-form-field .mat-mdc-form-field-focus-overlay{background:transparent!important}.add-tag-option .add-tag-form-field .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field .mdc-notched-outline__trailing{border-color:var(--grid-outline-variant, #cac4d0)!important;border-width:1px!important}.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--grid-outline, #79747e)!important}.add-tag-option .add-tag-form-field .mdc-floating-label,.add-tag-option .add-tag-form-field label{color:var(--grid-on-surface-variant, #49454f)!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.add-tag-option .add-tag-form-field .mat-mdc-text-field-wrapper{background:transparent!important;padding-bottom:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-infix{min-height:auto!important;padding:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-flex{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.add-tag-option .add-tag-actions{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;flex-shrink:0}.add-tag-option .add-tag-color-picker{flex-shrink:0;width:20px;height:20px;border:1px solid rgba(0,0,0,.12);border-radius:4px;cursor:pointer;padding:2px;background:transparent;box-sizing:border-box;margin:0 auto}.add-tag-option .add-tag-color-picker::-webkit-color-swatch-wrapper{padding:0}.add-tag-option .add-tag-color-picker::-webkit-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker::-moz-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker:hover{border-color:var(--grid-primary, #6750a4)}.add-tag-option .add-tag-color-picker:focus{outline:2px solid var(--grid-primary, #6750a4);outline-offset:2px}.add-tag-option .add-tag-button{flex-shrink:0;width:20px;height:20px;color:var(--grid-primary, #6750a4)!important;display:flex!important;align-items:center!important;justify-content:center!important;margin:0 auto}.add-tag-option .add-tag-button:not([disabled]){color:var(--grid-primary, #6750a4)!important}.add-tag-option .add-tag-button:not([disabled]):hover{background-color:#6750a414!important}.add-tag-option .add-tag-button[disabled]{opacity:.38;color:#00000061!important}.add-tag-option .add-tag-button mat-icon{font-size:var(--cell-font-size, 24px);width:24px;height:24px;line-height:24px;display:flex;align-items:center;justify-content:center;margin-right:0}.tag-panel .search-option>mat-pseudo-checkbox,.tag-panel .search-option>.mat-mdc-option-pseudo-checkbox,.tag-panel .add-tag-option>mat-pseudo-checkbox,.tag-panel .add-tag-option>.mat-mdc-option-pseudo-checkbox{display:none!important}.tag-panel{min-width:200px!important}.tag-panel .search-option .mat-mdc-option,.mat-mdc-select-panel .search-option .mat-mdc-option{min-height:auto!important;padding:2px 4px!important}.tag-panel .mdc-list-item--disabled,.mat-mdc-select-panel .mdc-list-item--disabled{pointer-events:auto!important;cursor:default!important}.tag-panel .mdc-list-item--disabled:hover,.mat-mdc-select-panel .mdc-list-item--disabled:hover{background:transparent!important}mat-option .mdc-list-item__primary-text{line-height:1.5!important;white-space:normal!important;display:flex!important;align-items:center!important}mat-option .tag-option{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:4px 8px;border-radius:12px;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px));font-weight:var(--cell-font-weight, 500);white-space:nowrap;line-height:1.2}.tag-form-field .mat-mdc-select-value-text{overflow:visible;text-overflow:clip}.tag-display eru-chip-list{display:block;min-width: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: MatSelectModule }, { kind: "component", type: i2$3.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$3.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i2$3.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: MatPseudoCheckboxModule }, { kind: "component", type: i1$1.MatPseudoCheckbox, selector: "mat-pseudo-checkbox", inputs: ["state", "disabled", "appearance"] }, { kind: "ngmodule", type: MatChipsModule }, { 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: MatIconModule }, { kind: "component", type: i2$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ChipListComponent, selector: "eru-chip-list", inputs: ["labels", "chips", "compact", "availableWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
9221
9757
|
}
|
|
9222
9758
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: TagComponent, decorators: [{
|
|
9223
9759
|
type: Component,
|
|
@@ -9230,8 +9766,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
9230
9766
|
MatPseudoCheckboxModule,
|
|
9231
9767
|
MatChipsModule,
|
|
9232
9768
|
MatButtonModule,
|
|
9233
|
-
MatIconModule
|
|
9234
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()) {\n <mat-form-field \n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"tag-form-field\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"tag-container\">\n <mat-select\n [placeholder]=\"getProperty('placeholder') || ''\"\n [multiple]=\"true\"\n [disabled]=\"getProperty('disabled') || !isEditable()\"\n [required]=\"hasRequiredValidation()\"\n [value]=\"currentValue()\"\n [compareWith]=\"compareWith\"\n panelClass=\"tag-panel\"\n (selectionChange)=\"onValueChange($event.value)\"\n (blur)=\"onBlur($event)\"\n (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n \n <mat-option disabled class=\"search-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n @if (isSearchable()) {\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"search-input\"\n [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n }\n <div class=\"select-all-container mat-mdc-option mdc-list-item\"\n [class.mdc-list-item--selected]=\"isAllSelected() || isIndeterminate()\"\n [class.select-all-partial]=\"isIndeterminate()\"\n (click)=\"toggleSelectAll(!isAllSelected()); $event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-pseudo-checkbox\n class=\"mat-mdc-option-pseudo-checkbox select-all-checkbox\"\n [state]=\"isIndeterminate() ? 'indeterminate' : (isAllSelected() ? 'checked' : 'unchecked')\"></mat-pseudo-checkbox>\n <span class=\"mdc-list-item__primary-text select-all-text\">Select All</span>\n </div>\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" \n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\" \n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"tag-option\" \n [style.background]=\"getTagColor(option.value).background\" \n [style.color]=\"getTagColor(option.value).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(option.value).dot\"></span>\n {{ option.label }}\n </span>\n </mat-option>\n }\n\n <mat-option class=\"add-tag-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"add-tag-container\" (click)=\"$event.stopPropagation()\">\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"add-tag-form-field\">\n <mat-label>Add new tag</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"add-tag-input\"\n [value]=\"newTagInput()\"\n (input)=\"onNewTagInputChange($any($event.target).value)\"\n (keydown.enter)=\"addNewTag()\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n <div class=\"add-tag-actions\">\n <input\n type=\"color\"\n class=\"add-tag-color-picker\"\n [value]=\"newTagColor()\"\n (input)=\"onNewTagColorChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n title=\"Select tag color\">\n <button\n mat-icon-button\n class=\"add-tag-button\"\n [disabled]=\"!canAddNewTag()\"\n (click)=\"addNewTag(); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n </mat-option>\n\n </mat-select>\n </div>\n </mat-form-field>\n} @else {\n <div \n class=\"tag-display\" \n [class.tag-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n @for (tag of tagDisplay().displayTags; track tag) {\n <span class=\"tag\" \n [style.background]=\"getTagColor(tag).background\" \n [style.color]=\"getTagColor(tag).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(tag).dot\"></span>\n {{tag}}\n </span>\n }\n @if(tagDisplay().moreCount > 0) {\n <span class=\"tag\">\n + {{tagDisplay().moreCount}}\n </span>\n }\n </span>\n } @else {\n @for (tag of tagDisplay().displayTags; track tag) {\n <span class=\"tag\" \n [style.background]=\"getTagColor(tag).background\" \n [style.color]=\"getTagColor(tag).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(tag).dot\"></span>\n {{tag}}\n </span>\n }\n @if(tagDisplay().moreCount > 0) {\n <span class=\"tag-more\">\n + {{tagDisplay().moreCount}}\n </span>\n }\n }\n </div>\n}\n\n", styles: [".tag-form-field{width:100%}.tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.tag-container{width:100%}.tag-display{width:100%;padding:8px 12px;min-height:30px;display:flex;flex-wrap:wrap;align-items:center;gap:4px;cursor:default}.tag-display.tag-display-editable{cursor:pointer}.tag-display.tag-display-editable:hover{background-color:#0000000a}.tag{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);border-radius:var(--grid-pill-radius, 12px);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 500));white-space:nowrap;line-height:1.2}.tag-dot{flex:0 0 auto;width:var(--grid-tag-dot-size, 6px);height:var(--grid-tag-dot-size, 6px);border-radius:50%}.tag-more{display:inline-block;border-radius:var(--grid-pill-radius, 12px);background-color:var(--grid-surface-container-high, #f5f5f5);color:var(--grid-on-surface-variant, #49454f);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 700));font-style:italic;padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);white-space:nowrap;line-height:1.2}.drillable-value{display:flex;flex-wrap:wrap;gap:4px;align-items:center;cursor:pointer}.drillable-value .tag{text-decoration:underline}.add-option{position:sticky;position:-webkit-sticky;bottom:0;z-index:3;background:var(--grid-surface-container-high, #fff);box-shadow:0 -2px 8px -4px #00000017;border-top:1px solid var(--grid-outline-variant, #e0e0e0)}.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-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;display:flex!important;flex-direction:column!important;gap:8px!important;opacity:1!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(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.search-option .select-all-container{padding:4px 0!important;min-height:auto!important;display:flex!important;align-items:center!important;pointer-events:auto!important;cursor:pointer!important;background:transparent!important}.search-option .select-all-container:hover{background:var(--grid-row-hover, rgba(0, 0, 0, .04))!important}.search-option .select-all-container .select-all-text{display:inline-flex!important;flex-direction:row!important;align-items:center!important;width:auto!important;max-width:none!important;gap:0!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--grid-on-surface, #1d1b20)!important}.search-option .select-all-container.select-all-partial .select-all-text:before,.search-option .select-all-container.select-all-partial .select-all-checkbox{opacity:.5}.tag-panel-footer{position:absolute;bottom:0;left:0;right:0;background:#fff;border-top:1px solid rgba(0,0,0,.12);padding:8px;z-index:10;box-shadow:0 -2px 4px #0000001a}.tag-panel-footer .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;pointer-events:auto}.tag-panel-footer .add-tag-form-field-wrapper{flex:1;min-width:0;position:relative}.tag-panel-footer .new-tag-input{width:100%;border:1px solid rgba(0,0,0,.38);border-radius:4px;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 12px;font-family:var(--grid-font-family, \"Poppins\")!important;box-sizing:border-box;cursor:text}.tag-panel-footer .new-tag-input:focus{border-color:var(--grid-primary, #6750a4);border-width:2px}.tag-panel-footer .add-tag-button{flex-shrink:0;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 16px!important;min-width:auto!important;pointer-events:auto!important;background-color:var(--grid-primary, #6750a4);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:var(--grid-font-family, \"Poppins\")!important}.tag-panel-footer .add-tag-button:hover:not([disabled]){opacity:.9}.tag-panel-footer .add-tag-button[disabled]{opacity:.5;cursor:not-allowed}.cdk-overlay-pane.tag-panel-overlay{position:relative;padding-bottom:60px!important}.cdk-overlay-pane.tag-panel-overlay .mat-mdc-select-panel{max-height:calc(100% - 60px)!important}.add-tag-option{position:sticky!important;background:var(--grid-surface, #fff)!important;bottom:-10px!important;z-index:10!important;padding:8px 8px 10px!important;margin-top:auto!important;margin-bottom:0!important;box-shadow:0 -2px 8px #00000026!important;border-top:1px solid rgba(0,0,0,.12)!important}.add-tag-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mat-mdc-option{min-height:auto!important;padding:0!important;height:auto!important}.add-tag-option .mat-mdc-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mdc-list-item__primary-text{width:100%!important;padding:0!important;margin:0!important}.add-tag-option .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;padding:0;margin-bottom:0}.add-tag-option .add-tag-form-field{flex:1;min-width:0}.add-tag-option .add-tag-form-field .mat-mdc-form-field-focus-overlay{background:transparent!important}.add-tag-option .add-tag-form-field .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field .mdc-notched-outline__trailing{border-color:var(--grid-outline-variant, #cac4d0)!important;border-width:1px!important}.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--grid-outline, #79747e)!important}.add-tag-option .add-tag-form-field .mdc-floating-label,.add-tag-option .add-tag-form-field label{color:var(--grid-on-surface-variant, #49454f)!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.add-tag-option .add-tag-form-field .mat-mdc-text-field-wrapper{background:transparent!important;padding-bottom:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-infix{min-height:auto!important;padding:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-flex{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.add-tag-option .add-tag-actions{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;flex-shrink:0}.add-tag-option .add-tag-color-picker{flex-shrink:0;width:20px;height:20px;border:1px solid rgba(0,0,0,.12);border-radius:4px;cursor:pointer;padding:2px;background:transparent;box-sizing:border-box;margin:0 auto}.add-tag-option .add-tag-color-picker::-webkit-color-swatch-wrapper{padding:0}.add-tag-option .add-tag-color-picker::-webkit-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker::-moz-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker:hover{border-color:var(--grid-primary, #6750a4)}.add-tag-option .add-tag-color-picker:focus{outline:2px solid var(--grid-primary, #6750a4);outline-offset:2px}.add-tag-option .add-tag-button{flex-shrink:0;width:20px;height:20px;color:var(--grid-primary, #6750a4)!important;display:flex!important;align-items:center!important;justify-content:center!important;margin:0 auto}.add-tag-option .add-tag-button:not([disabled]){color:var(--grid-primary, #6750a4)!important}.add-tag-option .add-tag-button:not([disabled]):hover{background-color:#6750a414!important}.add-tag-option .add-tag-button[disabled]{opacity:.38;color:#00000061!important}.add-tag-option .add-tag-button mat-icon{font-size:var(--cell-font-size, 24px);width:24px;height:24px;line-height:24px;display:flex;align-items:center;justify-content:center;margin-right:0}.tag-panel .search-option>mat-pseudo-checkbox,.tag-panel .search-option>.mat-mdc-option-pseudo-checkbox,.tag-panel .add-tag-option>mat-pseudo-checkbox,.tag-panel .add-tag-option>.mat-mdc-option-pseudo-checkbox{display:none!important}.tag-panel{min-width:200px!important}.tag-panel .search-option .mat-mdc-option,.mat-mdc-select-panel .search-option .mat-mdc-option{min-height:auto!important;padding:2px 4px!important}.tag-panel .mdc-list-item--disabled,.mat-mdc-select-panel .mdc-list-item--disabled{pointer-events:auto!important;cursor:default!important}.tag-panel .mdc-list-item--disabled:hover,.mat-mdc-select-panel .mdc-list-item--disabled:hover{background:transparent!important}mat-option .mdc-list-item__primary-text{line-height:1.5!important;white-space:normal!important;display:flex!important;align-items:center!important}mat-option .tag-option{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:4px 8px;border-radius:12px;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px));font-weight:var(--cell-font-weight, 500);white-space:nowrap;line-height:1.2}\n"] }]
|
|
9769
|
+
MatIconModule,
|
|
9770
|
+
ChipListComponent
|
|
9771
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if(isActive()) {\n <mat-form-field \n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"tag-form-field\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <div #selectContainer class=\"tag-container\">\n <mat-select\n [placeholder]=\"getProperty('placeholder') || ''\"\n [multiple]=\"true\"\n [disabled]=\"getProperty('disabled') || !isEditable()\"\n [required]=\"hasRequiredValidation()\"\n [value]=\"currentValue()\"\n [compareWith]=\"compareWith\"\n panelClass=\"tag-panel\"\n (selectionChange)=\"onValueChange($event.value)\"\n (blur)=\"onBlur($event)\"\n (openedChange)=\"onOpenedChange($event)\"\n (click)=\"$event.stopPropagation()\">\n <!-- Material's own custom-trigger hook (MatSelectTrigger), so the closed\n control shows the same coloured chip row as view mode instead of\n mat-select's comma-joined text. Nothing about mat-select's own\n behaviour is replaced. -->\n <!-- Unconditional: `customTrigger` is a content query, and gating the\n element behind an @if leaves it unresolved on the pass that matters.\n mat-select renders its placeholder while the value is empty, so an\n always-present trigger costs nothing. -->\n <mat-select-trigger>\n <eru-chip-list [chips]=\"tagChips()\" [compact]=\"true\"></eru-chip-list>\n </mat-select-trigger>\n \n <mat-option disabled class=\"search-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n @if (isSearchable()) {\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"search-form-field\">\n <mat-label>Search</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"search-input\"\n [value]=\"searchText()\"\n (input)=\"onSearchChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n }\n <div class=\"select-all-container mat-mdc-option mdc-list-item\"\n [class.mdc-list-item--selected]=\"isAllSelected() || isIndeterminate()\"\n [class.select-all-partial]=\"isIndeterminate()\"\n (click)=\"toggleSelectAll(!isAllSelected()); $event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <mat-pseudo-checkbox\n class=\"mat-mdc-option-pseudo-checkbox select-all-checkbox\"\n [state]=\"isIndeterminate() ? 'indeterminate' : (isAllSelected() ? 'checked' : 'unchecked')\"></mat-pseudo-checkbox>\n <span class=\"mdc-list-item__primary-text select-all-text\">Select All</span>\n </div>\n </mat-option>\n\n @for (option of filteredOptions(); track trackByValueAndIndex($index, option)) {\n <mat-option [value]=\"option.value\" \n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\" \n (click)=\"$event.stopPropagation(); $event.preventDefault()\">\n <span class=\"tag-option\" \n [style.background]=\"getTagColor(option.value).background\" \n [style.color]=\"getTagColor(option.value).text\">\n <span class=\"tag-dot\" [style.background]=\"getTagColor(option.value).dot\"></span>\n {{ option.label }}\n </span>\n </mat-option>\n }\n\n <mat-option class=\"add-tag-option\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n (mousedown)=\"$event.stopPropagation(); $event.preventDefault()\">\n <div class=\"add-tag-container\" (click)=\"$event.stopPropagation()\">\n <mat-form-field\n [appearance]=\"getProperty('appearance') || 'outline'\"\n class=\"add-tag-form-field\">\n <mat-label>Add new tag</mat-label>\n <input\n matInput\n type=\"text\"\n class=\"add-tag-input\"\n [value]=\"newTagInput()\"\n (input)=\"onNewTagInputChange($any($event.target).value)\"\n (keydown.enter)=\"addNewTag()\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (focus)=\"$event.stopPropagation()\">\n </mat-form-field>\n <div class=\"add-tag-actions\">\n <input\n type=\"color\"\n class=\"add-tag-color-picker\"\n [value]=\"newTagColor()\"\n (input)=\"onNewTagColorChange($any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n title=\"Select tag color\">\n <button\n mat-icon-button\n class=\"add-tag-button\"\n [disabled]=\"!canAddNewTag()\"\n (click)=\"addNewTag(); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n </div>\n </mat-option>\n\n </mat-select>\n </div>\n </mat-form-field>\n} @else {\n <div \n class=\"tag-display\" \n [class.tag-display-editable]=\"isEditable()\"\n (dblclick)=\"onActivate()\">\n @if (isDrillable()) {\n <span class=\"drillable-value\" (click)=\"onDrillableClick($event)\">\n <eru-chip-list [chips]=\"tagChips()\" [compact]=\"true\" [availableWidth]=\"columnWidth()\"></eru-chip-list>\n </span>\n } @else {\n <eru-chip-list [chips]=\"tagChips()\" [compact]=\"true\" [availableWidth]=\"columnWidth()\"></eru-chip-list>\n }\n </div>\n}\n\n", styles: [".tag-form-field{width:100%}.tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.tag-container{width:100%}.tag-display{width:100%;padding:8px 12px;min-height:30px;display:flex;flex-wrap:wrap;align-items:center;gap:4px;cursor:default}.tag-display.tag-display-editable{cursor:pointer}.tag-display.tag-display-editable:hover{background-color:#0000000a}.tag{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);border-radius:var(--grid-pill-radius, 12px);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 500));white-space:nowrap;line-height:1.2}.tag-dot{flex:0 0 auto;width:var(--grid-tag-dot-size, 6px);height:var(--grid-tag-dot-size, 6px);border-radius:50%}.tag-more{display:inline-block;border-radius:var(--grid-pill-radius, 12px);background-color:var(--grid-surface-container-high, #f5f5f5);color:var(--grid-on-surface-variant, #49454f);font-size:var(--cell-font-size, var(--grid-pill-font-size, var(--grid-font-size-body, 12px)));font-weight:var(--cell-font-weight, var(--grid-pill-font-weight, 700));font-style:italic;padding:var(--grid-pill-padding-y, 3px) var(--grid-pill-padding-x, 10px);white-space:nowrap;line-height:1.2}.drillable-value{display:flex;flex-wrap:wrap;gap:4px;align-items:center;cursor:pointer}.drillable-value .tag{text-decoration:underline}.add-option{position:sticky;position:-webkit-sticky;bottom:0;z-index:3;background:var(--grid-surface-container-high, #fff);box-shadow:0 -2px 8px -4px #00000017;border-top:1px solid var(--grid-outline-variant, #e0e0e0)}.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-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;display:flex!important;flex-direction:column!important;gap:8px!important;opacity:1!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(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.search-option .select-all-container{padding:4px 0!important;min-height:auto!important;display:flex!important;align-items:center!important;pointer-events:auto!important;cursor:pointer!important;background:transparent!important}.search-option .select-all-container:hover{background:var(--grid-row-hover, rgba(0, 0, 0, .04))!important}.search-option .select-all-container .select-all-text{display:inline-flex!important;flex-direction:row!important;align-items:center!important;width:auto!important;max-width:none!important;gap:0!important;font-family:var(--grid-font-family, \"Poppins\")!important;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;font-weight:var(--cell-font-weight, 500)!important;color:var(--grid-on-surface, #1d1b20)!important}.search-option .select-all-container.select-all-partial .select-all-text:before,.search-option .select-all-container.select-all-partial .select-all-checkbox{opacity:.5}.tag-panel-footer{position:absolute;bottom:0;left:0;right:0;background:#fff;border-top:1px solid rgba(0,0,0,.12);padding:8px;z-index:10;box-shadow:0 -2px 4px #0000001a}.tag-panel-footer .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;pointer-events:auto}.tag-panel-footer .add-tag-form-field-wrapper{flex:1;min-width:0;position:relative}.tag-panel-footer .new-tag-input{width:100%;border:1px solid rgba(0,0,0,.38);border-radius:4px;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 12px;font-family:var(--grid-font-family, \"Poppins\")!important;box-sizing:border-box;cursor:text}.tag-panel-footer .new-tag-input:focus{border-color:var(--grid-primary, #6750a4);border-width:2px}.tag-panel-footer .add-tag-button{flex-shrink:0;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 16px!important;min-width:auto!important;pointer-events:auto!important;background-color:var(--grid-primary, #6750a4);color:#fff;border:none;border-radius:4px;cursor:pointer;font-family:var(--grid-font-family, \"Poppins\")!important}.tag-panel-footer .add-tag-button:hover:not([disabled]){opacity:.9}.tag-panel-footer .add-tag-button[disabled]{opacity:.5;cursor:not-allowed}.cdk-overlay-pane.tag-panel-overlay{position:relative;padding-bottom:60px!important}.cdk-overlay-pane.tag-panel-overlay .mat-mdc-select-panel{max-height:calc(100% - 60px)!important}.add-tag-option{position:sticky!important;background:var(--grid-surface, #fff)!important;bottom:-10px!important;z-index:10!important;padding:8px 8px 10px!important;margin-top:auto!important;margin-bottom:0!important;box-shadow:0 -2px 8px #00000026!important;border-top:1px solid rgba(0,0,0,.12)!important}.add-tag-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mat-mdc-option{min-height:auto!important;padding:0!important;height:auto!important}.add-tag-option .mat-mdc-option:hover{background:var(--grid-surface, #fff)!important}.add-tag-option .mdc-list-item__primary-text{width:100%!important;padding:0!important;margin:0!important}.add-tag-option .add-tag-container{display:flex;align-items:center;gap:8px;width:100%;padding:0;margin-bottom:0}.add-tag-option .add-tag-form-field{flex:1;min-width:0}.add-tag-option .add-tag-form-field .mat-mdc-form-field-focus-overlay{background:transparent!important}.add-tag-option .add-tag-form-field .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field .mdc-notched-outline__trailing{border-color:var(--grid-outline-variant, #cac4d0)!important;border-width:1px!important}.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__leading,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__notch,.add-tag-option .add-tag-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--grid-outline, #79747e)!important}.add-tag-option .add-tag-form-field .mdc-floating-label,.add-tag-option .add-tag-form-field label{color:var(--grid-on-surface-variant, #49454f)!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-subscript-wrapper{display:none}.add-tag-option .add-tag-form-field .mat-mdc-text-field-wrapper{background:transparent!important;padding-bottom:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-infix{min-height:auto!important;padding:0!important;width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-form-field .mat-mdc-form-field-flex{width:100%!important;max-width:100%!important;box-sizing:border-box!important}.add-tag-option .add-tag-input{width:100%;border:none;outline:none;background:transparent;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px))!important;padding:8px 0}.add-tag-option .add-tag-actions{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;flex-shrink:0}.add-tag-option .add-tag-color-picker{flex-shrink:0;width:20px;height:20px;border:1px solid rgba(0,0,0,.12);border-radius:4px;cursor:pointer;padding:2px;background:transparent;box-sizing:border-box;margin:0 auto}.add-tag-option .add-tag-color-picker::-webkit-color-swatch-wrapper{padding:0}.add-tag-option .add-tag-color-picker::-webkit-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker::-moz-color-swatch{border:none;border-radius:2px}.add-tag-option .add-tag-color-picker:hover{border-color:var(--grid-primary, #6750a4)}.add-tag-option .add-tag-color-picker:focus{outline:2px solid var(--grid-primary, #6750a4);outline-offset:2px}.add-tag-option .add-tag-button{flex-shrink:0;width:20px;height:20px;color:var(--grid-primary, #6750a4)!important;display:flex!important;align-items:center!important;justify-content:center!important;margin:0 auto}.add-tag-option .add-tag-button:not([disabled]){color:var(--grid-primary, #6750a4)!important}.add-tag-option .add-tag-button:not([disabled]):hover{background-color:#6750a414!important}.add-tag-option .add-tag-button[disabled]{opacity:.38;color:#00000061!important}.add-tag-option .add-tag-button mat-icon{font-size:var(--cell-font-size, 24px);width:24px;height:24px;line-height:24px;display:flex;align-items:center;justify-content:center;margin-right:0}.tag-panel .search-option>mat-pseudo-checkbox,.tag-panel .search-option>.mat-mdc-option-pseudo-checkbox,.tag-panel .add-tag-option>mat-pseudo-checkbox,.tag-panel .add-tag-option>.mat-mdc-option-pseudo-checkbox{display:none!important}.tag-panel{min-width:200px!important}.tag-panel .search-option .mat-mdc-option,.mat-mdc-select-panel .search-option .mat-mdc-option{min-height:auto!important;padding:2px 4px!important}.tag-panel .mdc-list-item--disabled,.mat-mdc-select-panel .mdc-list-item--disabled{pointer-events:auto!important;cursor:default!important}.tag-panel .mdc-list-item--disabled:hover,.mat-mdc-select-panel .mdc-list-item--disabled:hover{background:transparent!important}mat-option .mdc-list-item__primary-text{line-height:1.5!important;white-space:normal!important;display:flex!important;align-items:center!important}mat-option .tag-option{display:inline-flex;align-items:center;gap:var(--grid-tag-dot-gap, 5px);padding:4px 8px;border-radius:12px;font-size:var(--cell-font-size, var(--grid-font-size-body, 12px));font-weight:var(--cell-font-weight, 500);white-space:nowrap;line-height:1.2}.tag-form-field .mat-mdc-select-value-text{overflow:visible;text-overflow:clip}.tag-display eru-chip-list{display:block;min-width:0}\n"] }]
|
|
9235
9772
|
}], ctorParameters: () => [], propDecorators: { selectContainer: [{
|
|
9236
9773
|
type: ViewChild,
|
|
9237
9774
|
args: ['selectContainer', { static: false }]
|
|
@@ -10953,8 +11490,27 @@ class DataCellComponent {
|
|
|
10953
11490
|
// (they paint the band on the value, not the box). Hand them the RESOLVED
|
|
10954
11491
|
// rules, or a rule whose bound comes from a column statistic would reach
|
|
10955
11492
|
// them as an operand-less comparison and never match.
|
|
10956
|
-
|
|
10957
|
-
|
|
11493
|
+
//
|
|
11494
|
+
// Overridden on `hasOwnCellRules` as well as on a non-empty resolve: once
|
|
11495
|
+
// this grid configures conditional formatting it replaces the field's bands
|
|
11496
|
+
// outright, so a rule still waiting on its statistic must leave the cell
|
|
11497
|
+
// unformatted rather than fall back to the model's colours for a moment.
|
|
11498
|
+
//
|
|
11499
|
+
// The fill is stripped on the way down. This cell already paints the
|
|
11500
|
+
// matched rule's background on the box (`ruleStyle`), and the number and
|
|
11501
|
+
// currency cells paint their own from the same rule — so the fill went on
|
|
11502
|
+
// twice. Two opaque layers hide that; two TRANSLUCENT ones composite, which
|
|
11503
|
+
// is why the same token at 40% read far darker on a cell than on the grid
|
|
11504
|
+
// header. The inner paint is also flat, so it ignored a data bar's
|
|
11505
|
+
// proportion and filled the whole cell. Only the value colour is handed
|
|
11506
|
+
// over, which does not stack: the child simply wins.
|
|
11507
|
+
const resolved = this.resolvedRules().map(rule => {
|
|
11508
|
+
const { background, bar, bar_min, bar_max, bar_auto, ...rest } = rule;
|
|
11509
|
+
return rest;
|
|
11510
|
+
});
|
|
11511
|
+
return (resolved.length || hasOwnCellRules(base))
|
|
11512
|
+
? { ...base, color_ranges: resolved }
|
|
11513
|
+
: base;
|
|
10958
11514
|
}, ...(ngDevMode ? [{ debugName: "columnCellConfiguration" }] : []));
|
|
10959
11515
|
renderer = inject(Renderer2);
|
|
10960
11516
|
datePipe = inject(DatePipe);
|
|
@@ -12323,7 +12879,7 @@ const UNIVERSAL_FIELDS = [
|
|
|
12323
12879
|
{ key: 'minWidth', label: 'Min width (px)', control: 'number' },
|
|
12324
12880
|
{ key: 'maxWidth', label: 'Max width (px)', control: 'number' },
|
|
12325
12881
|
{ key: 'cell_style', label: 'Value style', control: 'text_style' },
|
|
12326
|
-
{ key: '
|
|
12882
|
+
{ key: 'cell_rules', label: 'Conditional formatting', control: 'rule_list' },
|
|
12327
12883
|
];
|
|
12328
12884
|
/**
|
|
12329
12885
|
* Properties a `text_style` control edits, in the order they are shown.
|
|
@@ -12407,10 +12963,13 @@ const NUMBER_FIELDS = [
|
|
|
12407
12963
|
key: 'seperator',
|
|
12408
12964
|
label: 'Separator',
|
|
12409
12965
|
control: 'select',
|
|
12966
|
+
// Spelled out with an example each, because 'Thousands'/'Millions' read as
|
|
12967
|
+
// scales rather than as groupings and so looked like a second copy of
|
|
12968
|
+
// 'Display as'. They are groupings: 'thousands' is the Indian one.
|
|
12410
12969
|
options: [
|
|
12411
|
-
{ value: 'none', label: 'None' },
|
|
12412
|
-
{ value: 'thousands', label: '
|
|
12413
|
-
{ value: 'millions', label: '
|
|
12970
|
+
{ value: 'none', label: 'None (10000000)' },
|
|
12971
|
+
{ value: 'thousands', label: 'Indian (1,00,00,000)' },
|
|
12972
|
+
{ value: 'millions', label: 'Western (10,000,000)' },
|
|
12414
12973
|
],
|
|
12415
12974
|
},
|
|
12416
12975
|
{ key: 'num_val', label: 'Limit value', control: 'number' },
|
|
@@ -12428,9 +12987,13 @@ const NUMBER_FIELDS = [
|
|
|
12428
12987
|
key: 'display_number_as',
|
|
12429
12988
|
label: 'Display as',
|
|
12430
12989
|
control: 'select',
|
|
12990
|
+
// Choosing a scale is itself what turns abbreviation on, so the blank
|
|
12991
|
+
// option has to exist: without a way back to "unset" a scale picked once
|
|
12992
|
+
// could never be undone, and the column would abbreviate for ever.
|
|
12431
12993
|
options: [
|
|
12432
|
-
{ value: '
|
|
12433
|
-
{ value: '
|
|
12994
|
+
{ value: '', label: 'From separator' },
|
|
12995
|
+
{ value: 'lacs', label: 'Lacs (k / L / Cr)' },
|
|
12996
|
+
{ value: 'mn', label: 'Millions (k / mn / bn / tn)' },
|
|
12434
12997
|
],
|
|
12435
12998
|
},
|
|
12436
12999
|
// One function per column, shared by the group subtotal row and the grand
|
|
@@ -12874,7 +13437,20 @@ class ColumnDesignPanelComponent {
|
|
|
12874
13437
|
this.onStyleChange(key, 'fill', fill);
|
|
12875
13438
|
}
|
|
12876
13439
|
// ── Conditional-format rules ───────────────────────────────────────────
|
|
13440
|
+
/**
|
|
13441
|
+
* The rules the pane edits: whatever is in force on the column, which for
|
|
13442
|
+
* `cell_rules` means the data model's `color_ranges` until this grid has rules
|
|
13443
|
+
* of its own (columnCellRules decides).
|
|
13444
|
+
*
|
|
13445
|
+
* Showing the effective list rather than only the column's own is what makes
|
|
13446
|
+
* the first edit a starting point instead of a blank slate: the author sees
|
|
13447
|
+
* the bands actually painting, and because every mutation writes the whole
|
|
13448
|
+
* list back to `cell_rules`, that edit materialises them as this grid's
|
|
13449
|
+
* override and the field's bands are left untouched.
|
|
13450
|
+
*/
|
|
12877
13451
|
ruleItems(key) {
|
|
13452
|
+
if (key === 'cell_rules')
|
|
13453
|
+
return columnCellRules(this.field());
|
|
12878
13454
|
const arr = this.field()?.[key];
|
|
12879
13455
|
return Array.isArray(arr) ? arr : [];
|
|
12880
13456
|
}
|
|
@@ -13109,7 +13685,7 @@ class ColumnDesignPanelComponent {
|
|
|
13109
13685
|
this.gridStore.selectDesignColumn(null);
|
|
13110
13686
|
}
|
|
13111
13687
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ColumnDesignPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13112
|
-
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 <!-- Which field is being designed. A pivot with one measure renders no\n measure-name row, so its single header cell stands for the column\n dimension and the measure at once and can only open one of them; this\n reaches the other. Also the way to a header too narrow to click, or a\n hidden column. Only shown when there is more than one thing to pick. -->\n @if (designTargets().length > 0 && targetCount() > 1) {\n <div class=\"design-field design-target\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Editing</mat-label>\n <mat-select [ngModel]=\"gridStore.selectedDesignColumn()\"\n (ngModelChange)=\"gridStore.selectDesignColumn($event)\">\n @for (group of designTargets(); track group.group) {\n <mat-optgroup [label]=\"group.group\">\n @for (target of group.fields; track target.name) {\n <mat-option [value]=\"target.name\">\n {{ target.label || target.name }}\n @if (target.is_hidden) {<span class=\"design-target-hidden\">hidden</span>}\n </mat-option>\n }\n </mat-optgroup>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\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. The date format, abbreviation, display scale and\n symbol field start from the model but can be changed here for this grid alone.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track trackMetaField(def)) {\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) ?? def.defaultValue\" (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 ('field_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 <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSelectOptions(); 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 ('field_slot') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"slotValue(def)\" (ngModelChange)=\"onSlotChange(def, $event)\">\n <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSlotOptions(); 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 ('text_style') {\n <div class=\"text-style-control\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Size</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_size')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_size', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Weight</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_weight')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_weight', $event)\" />\n </mat-form-field>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!styleValue(def.key, 'italic')\"\n (change)=\"onStyleChange(def.key, 'italic', $event.checked)\">Italic</mat-checkbox>\n </div>\n\n @if (!isSelfColoured()) {\n <div class=\"color-control\">\n <span class=\"color-control-label\">Text colour</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'color'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'color'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'color')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'color')) }}%</span>\n @if (styleColor(def.key, 'color')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'color')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">theme</span>\n }\n </div>\n </div>\n }\n\n <div class=\"color-control\">\n <span class=\"color-control-label\">{{ isSelfColoured() ? 'Cell background' : 'Background' }}</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'background'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'background', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'background'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'background')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'background')) }}%</span>\n @if (styleColor(def.key, 'background')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'background')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">none</span>\n }\n </div>\n </div>\n\n @if (styleColor(def.key, 'background') && !isSelfColoured()) {\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"fill-select\">\n <mat-label>Fill</mat-label>\n <mat-select [ngModel]=\"styleFill(def.key)\" (ngModelChange)=\"onStyleFill(def.key, $event)\">\n <mat-option value=\"text\">Behind the text (pill)</mat-option>\n <mat-option value=\"cell\">Whole cell</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n }\n </div>\n }\n\n @case ('rule_list') {\n <div class=\"rule-list\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <p class=\"rule-list-hint\">Checked top to bottom \u2014 the first rule that matches wins.</p>\n @for (rule of ruleItems(def.key); track $index) {\n <div class=\"rule-row\">\n <div class=\"rule-row-head\">\n <mat-form-field appearance=\"outline\" class=\"rule-op\">\n <mat-label>When</mat-label>\n <mat-select [ngModel]=\"rule.op || 'between'\" (ngModelChange)=\"updateRule(def.key, $index, 'op', $event)\">\n @for (o of ruleOperators; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"rule-actions\">\n <button type=\"button\" class=\"rule-btn\" title=\"Move up\" (click)=\"moveRule(def.key, $index, -1)\">\u2191</button>\n <button type=\"button\" class=\"rule-btn\" title=\"Move down\" (click)=\"moveRule(def.key, $index, 1)\">\u2193</button>\n <button type=\"button\" class=\"rule-btn rule-btn-remove\" title=\"Remove\" (click)=\"removeRule(def.key, $index)\">\u2715</button>\n </div>\n </div>\n @if (operandCount(rule.op) > 0) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>{{ operandCount(rule.op) === 2 ? 'From' : 'Value' }}</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'from', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'from')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.from\" (ngModelChange)=\"updateRule(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n } @else {\n @if (ruleSourceArg(def.key, $index, 'from'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>\u00B1 std dev</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleStddevOffset(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleStddevOffset(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n </div>\n @if (operandCount(rule.op) === 2) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>To</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'to', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'to')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.to\" (ngModelChange)=\"updateRule(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n } @else if (ruleSourceArg(def.key, $index, 'to'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n }\n </div>\n }\n }\n <div class=\"rule-row-format\">\n <span class=\"rule-format-label\">Text</span>\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.color)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'color', { hex: $any($event.target).value })\" />\n <mat-form-field appearance=\"outline\" class=\"color-token-field rule-token\">\n <mat-select [ngModel]=\"colorToken(rule.color)\"\n (ngModelChange)=\"onRuleColorEdit(def.key, $index, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\">{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <span class=\"rule-format-label\">Fill</span>\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { hex: $any($event.target).value })\" />\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\" [value]=\"colorAlpha(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { alpha: +$any($event.target).value })\" />\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bold\"\n (change)=\"updateRule(def.key, $index, 'bold', $event.checked)\">Bold</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"rule.fill === 'text'\"\n (change)=\"updateRule(def.key, $index, 'fill', $event.checked ? 'text' : 'cell')\">Pill</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar\"\n (change)=\"updateRule(def.key, $index, 'bar', $event.checked)\">Data bar</mat-checkbox>\n @if (rule.bar) {\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar_auto\"\n (change)=\"updateRule(def.key, $index, 'bar_auto', $event.checked)\">Scale to column</mat-checkbox>\n }\n </div>\n @if (rule.bar && !rule.bar_auto) {\n <div class=\"rule-row-format\">\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar min</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_min\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_min', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar max</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_max\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_max', $event)\" />\n </mat-form-field>\n </div>\n }\n </div>\n }\n <button type=\"button\" class=\"rule-add\" (click)=\"addRule(def.key)\">+ Add rule</button>\n </div>\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\">×</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\">×</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)}.text-style-control{display:flex;flex-direction:column;gap:6px}.text-style-label{font-size:12px;color:var(--grid-on-surface-variant, #49454f)}.text-style-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.text-style-number{width:96px}.text-style-color{display:flex;align-items:center;gap:6px}.text-style-color input[type=color]{width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.text-style-color-label{font-size:12px}.text-style-clear{border:none;background:none;padding:0;font-size:11px;color:var(--grid-primary, #6750a4);cursor:pointer}.text-style-color--unset input[type=color]{opacity:.3}.text-style-unset-hint{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.design-target-hidden{margin-left:6px;padding:1px 6px;border-radius:8px;font-size:10px;text-transform:uppercase;letter-spacing:.4px;background:var(--grid-surface-container-high, #e6e0e9);color:var(--grid-on-surface-variant, #49454f)}.color-control{display:block;margin-top:4px}.color-control-label{display:block;font-size:12px;margin-bottom:2px;color:var(--grid-on-surface-variant, #49454f)}.color-control-body{display:flex;align-items:center;gap:6px;min-width:0}.color-token-field{flex:1 1 auto;min-width:0}.color-swatch{display:inline-block;width:12px;height:12px;margin-right:6px;border-radius:3px;border:1px solid var(--grid-outline-variant, #cac4d0);vertical-align:middle}.color-hex{flex:0 0 auto;width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.color-preview{width:32px;height:28px;border-radius:4px;border:1px solid var(--grid-outline-variant, #cac4d0)}.color-alpha{flex:0 1 68px;min-width:44px}.color-alpha-value{flex:0 0 auto;font-size:11px;min-width:30px;color:var(--grid-on-surface-variant, #49454f)}.color-preview{flex:0 0 auto}.fill-select{width:220px}.rule-list{display:flex;flex-direction:column;gap:8px}.rule-list-hint{margin:0;font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-row{border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;padding:8px;display:flex;flex-direction:column;gap:6px}.rule-row-head{display:flex;align-items:center;gap:6px;min-width:0}.rule-row-operands,.rule-row-format{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.rule-actions{margin-left:auto;flex:0 0 auto;display:flex;gap:4px}.rule-op{flex:1 1 auto;min-width:0}.rule-operand{flex:1 1 96px;min-width:88px}.rule-source{flex:1 1 140px;min-width:128px}.rule-token{flex:1 1 96px;min-width:88px}.rule-format-label{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-btn{border:1px solid var(--grid-outline-variant, #cac4d0);background:none;border-radius:4px;width:26px;height:26px;cursor:pointer;font-size:12px;line-height:1}.rule-btn-remove{color:var(--grid-error, #b3261e)}.rule-add{align-self:flex-start;border:1px dashed var(--grid-outline, #79747e);background:none;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;color:var(--grid-primary, #6750a4)}\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$3.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$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i2$3.MatOptgroup, selector: "mat-optgroup", inputs: ["label", "disabled"], exportAs: ["matOptgroup"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i2$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$2.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 });
|
|
13688
|
+
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 <!-- Which field is being designed. A pivot with one measure renders no\n measure-name row, so its single header cell stands for the column\n dimension and the measure at once and can only open one of them; this\n reaches the other. Also the way to a header too narrow to click, or a\n hidden column. Only shown when there is more than one thing to pick. -->\n @if (designTargets().length > 0 && targetCount() > 1) {\n <div class=\"design-field design-target\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Editing</mat-label>\n <mat-select [ngModel]=\"gridStore.selectedDesignColumn()\"\n (ngModelChange)=\"gridStore.selectDesignColumn($event)\">\n @for (group of designTargets(); track group.group) {\n <mat-optgroup [label]=\"group.group\">\n @for (target of group.fields; track target.name) {\n <mat-option [value]=\"target.name\">\n {{ target.label || target.name }}\n @if (target.is_hidden) {<span class=\"design-target-hidden\">hidden</span>}\n </mat-option>\n }\n </mat-optgroup>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\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. The date format, abbreviation, display scale and\n symbol field start from the model but can be changed here for this grid alone.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track trackMetaField(def)) {\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) ?? def.defaultValue\" (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 ('field_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 <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSelectOptions(); 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 ('field_slot') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"slotValue(def)\" (ngModelChange)=\"onSlotChange(def, $event)\">\n <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSlotOptions(); 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 ('text_style') {\n <div class=\"text-style-control\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Size</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_size')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_size', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Weight</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_weight')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_weight', $event)\" />\n </mat-form-field>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!styleValue(def.key, 'italic')\"\n (change)=\"onStyleChange(def.key, 'italic', $event.checked)\">Italic</mat-checkbox>\n </div>\n\n @if (!isSelfColoured()) {\n <div class=\"color-control\">\n <span class=\"color-control-label\">Text colour</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'color'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'color'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'color')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'color')) }}%</span>\n @if (styleColor(def.key, 'color')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'color')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">theme</span>\n }\n </div>\n </div>\n }\n\n <div class=\"color-control\">\n <span class=\"color-control-label\">{{ isSelfColoured() ? 'Cell background' : 'Background' }}</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'background'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'background', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'background'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'background')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'background')) }}%</span>\n @if (styleColor(def.key, 'background')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'background')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">none</span>\n }\n </div>\n </div>\n\n @if (styleColor(def.key, 'background') && !isSelfColoured()) {\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"fill-select\">\n <mat-label>Fill</mat-label>\n <mat-select [ngModel]=\"styleFill(def.key)\" (ngModelChange)=\"onStyleFill(def.key, $event)\">\n <mat-option value=\"text\">Behind the text (pill)</mat-option>\n <mat-option value=\"cell\">Whole cell</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n }\n </div>\n }\n\n @case ('rule_list') {\n <div class=\"rule-list\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <p class=\"rule-list-hint\">Checked top to bottom \u2014 the first rule that matches wins.</p>\n @for (rule of ruleItems(def.key); track $index) {\n <div class=\"rule-row\">\n <div class=\"rule-row-head\">\n <mat-form-field appearance=\"outline\" class=\"rule-op\">\n <mat-label>When</mat-label>\n <mat-select [ngModel]=\"rule.op || 'between'\" (ngModelChange)=\"updateRule(def.key, $index, 'op', $event)\">\n @for (o of ruleOperators; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"rule-actions\">\n <button type=\"button\" class=\"rule-btn\" title=\"Move up\" (click)=\"moveRule(def.key, $index, -1)\">\u2191</button>\n <button type=\"button\" class=\"rule-btn\" title=\"Move down\" (click)=\"moveRule(def.key, $index, 1)\">\u2193</button>\n <button type=\"button\" class=\"rule-btn rule-btn-remove\" title=\"Remove\" (click)=\"removeRule(def.key, $index)\">\u2715</button>\n </div>\n </div>\n @if (operandCount(rule.op) > 0) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>{{ operandCount(rule.op) === 2 ? 'From' : 'Value' }}</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'from', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'from')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.from\" (ngModelChange)=\"updateRule(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n } @else {\n @if (ruleSourceArg(def.key, $index, 'from'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>\u00B1 std dev</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleStddevOffset(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleStddevOffset(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n </div>\n @if (operandCount(rule.op) === 2) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>To</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'to', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'to')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.to\" (ngModelChange)=\"updateRule(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n } @else if (ruleSourceArg(def.key, $index, 'to'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n }\n </div>\n }\n }\n <div class=\"rule-row-format\">\n <span class=\"rule-color-group\">\n <span class=\"rule-format-label\">Text</span>\n <!-- Swapped for a read-only preview while a token is in force, as\n the column-level control does: with a token set,\n composeColorValue returns the token and a hex picked here is\n discarded, so an editable swatch invited an edit that\n silently did nothing. -->\n @if (!isTokenColor(rule.color)) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.color)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'color', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"rule.color\"></span>\n }\n <mat-form-field appearance=\"outline\" class=\"color-token-field rule-token\">\n <mat-select [ngModel]=\"colorToken(rule.color)\"\n (ngModelChange)=\"onRuleColorEdit(def.key, $index, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </span>\n <!-- Grouped so the row wraps between the two colours rather than\n through one: the percentage broke onto the next line beside\n 'Bold' and read as belonging to it. -->\n <span class=\"rule-color-group\">\n <span class=\"rule-format-label\">Fill</span>\n @if (!isTokenColor(rule.background)) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"rule.background\"></span>\n }\n <!-- A fill is as likely to want a theme colour as the text is, and\n the alpha slider works for either: composeColorValue wraps a\n token in color-mix below 100%. -->\n <mat-form-field appearance=\"outline\" class=\"color-token-field rule-token\">\n <mat-select [ngModel]=\"colorToken(rule.background)\"\n (ngModelChange)=\"onRuleColorEdit(def.key, $index, 'background', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- Its own line: the label, swatch and token select already fill\n the panel's width, and a token name as long as 'On Secondary\n Container' pushed the slider off the right edge. -->\n <span class=\"rule-alpha-group\">\n <span class=\"rule-format-label\">Opacity</span>\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\" [value]=\"colorAlpha(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(rule.background) }}%</span>\n </span>\n </span>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bold\"\n (change)=\"updateRule(def.key, $index, 'bold', $event.checked)\">Bold</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"rule.fill === 'text'\"\n (change)=\"updateRule(def.key, $index, 'fill', $event.checked ? 'text' : 'cell')\">Pill</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar\"\n (change)=\"updateRule(def.key, $index, 'bar', $event.checked)\">Data bar</mat-checkbox>\n @if (rule.bar) {\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar_auto\"\n (change)=\"updateRule(def.key, $index, 'bar_auto', $event.checked)\">Scale to column</mat-checkbox>\n }\n </div>\n @if (rule.bar && !rule.bar_auto) {\n <div class=\"rule-row-format\">\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar min</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_min\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_min', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar max</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_max\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_max', $event)\" />\n </mat-form-field>\n </div>\n }\n </div>\n }\n <button type=\"button\" class=\"rule-add\" (click)=\"addRule(def.key)\">+ Add rule</button>\n </div>\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\">×</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\">×</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)}.text-style-control{display:flex;flex-direction:column;gap:6px}.text-style-label{font-size:12px;color:var(--grid-on-surface-variant, #49454f)}.text-style-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.text-style-number{width:96px}.text-style-color{display:flex;align-items:center;gap:6px}.text-style-color input[type=color]{width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.text-style-color-label{font-size:12px}.text-style-clear{border:none;background:none;padding:0;font-size:11px;color:var(--grid-primary, #6750a4);cursor:pointer}.text-style-color--unset input[type=color]{opacity:.3}.text-style-unset-hint{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.design-target-hidden{margin-left:6px;padding:1px 6px;border-radius:8px;font-size:10px;text-transform:uppercase;letter-spacing:.4px;background:var(--grid-surface-container-high, #e6e0e9);color:var(--grid-on-surface-variant, #49454f)}.color-control{display:block;margin-top:4px}.color-control-label{display:block;font-size:12px;margin-bottom:2px;color:var(--grid-on-surface-variant, #49454f)}.color-control-body{display:flex;align-items:center;gap:6px;min-width:0}.color-token-field{flex:1 1 auto;min-width:0}.color-swatch{display:inline-block;width:12px;height:12px;margin-right:6px;border-radius:3px;border:1px solid var(--grid-outline-variant, #cac4d0);vertical-align:middle}.color-hex{flex:0 0 auto;width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.color-preview{width:32px;height:28px;border-radius:4px;border:1px solid var(--grid-outline-variant, #cac4d0)}.color-alpha{flex:0 1 68px;min-width:44px}.color-alpha-value{flex:0 0 auto;font-size:11px;min-width:30px;color:var(--grid-on-surface-variant, #49454f)}.color-preview{flex:0 0 auto}.fill-select{width:220px}.rule-list{display:flex;flex-direction:column;gap:8px}.rule-list-hint{margin:0;font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-row{border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;padding:8px;display:flex;flex-direction:column;gap:6px}.rule-row-head{display:flex;align-items:center;gap:6px;min-width:0}.rule-row-operands,.rule-row-format{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.rule-actions{margin-left:auto;flex:0 0 auto;display:flex;gap:4px}.rule-op{flex:1 1 auto;min-width:0}.rule-operand{flex:1 1 96px;min-width:88px}.rule-source{flex:1 1 140px;min-width:128px}.rule-token{flex:1 1 96px;min-width:88px}.rule-format-label{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-color-group{display:flex;align-items:center;flex-wrap:wrap;gap:6px;flex:1 1 100%}.rule-alpha-group{display:flex;align-items:center;gap:6px;flex:1 1 100%}.rule-alpha-group .color-alpha{flex:1 1 auto}.rule-btn{border:1px solid var(--grid-outline-variant, #cac4d0);background:none;border-radius:4px;width:26px;height:26px;cursor:pointer;font-size:12px;line-height:1}.rule-btn-remove{color:var(--grid-error, #b3261e)}.rule-add{align-self:flex-start;border:1px dashed var(--grid-outline, #79747e);background:none;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;color:var(--grid-primary, #6750a4)}\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$3.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$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i2$3.MatOptgroup, selector: "mat-optgroup", inputs: ["label", "disabled"], exportAs: ["matOptgroup"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i2$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$2.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 });
|
|
13113
13689
|
}
|
|
13114
13690
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ColumnDesignPanelComponent, decorators: [{
|
|
13115
13691
|
type: Component,
|
|
@@ -13122,7 +13698,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
13122
13698
|
MatCheckboxModule,
|
|
13123
13699
|
MatIconModule,
|
|
13124
13700
|
MatButtonModule,
|
|
13125
|
-
], 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 <!-- Which field is being designed. A pivot with one measure renders no\n measure-name row, so its single header cell stands for the column\n dimension and the measure at once and can only open one of them; this\n reaches the other. Also the way to a header too narrow to click, or a\n hidden column. Only shown when there is more than one thing to pick. -->\n @if (designTargets().length > 0 && targetCount() > 1) {\n <div class=\"design-field design-target\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Editing</mat-label>\n <mat-select [ngModel]=\"gridStore.selectedDesignColumn()\"\n (ngModelChange)=\"gridStore.selectDesignColumn($event)\">\n @for (group of designTargets(); track group.group) {\n <mat-optgroup [label]=\"group.group\">\n @for (target of group.fields; track target.name) {\n <mat-option [value]=\"target.name\">\n {{ target.label || target.name }}\n @if (target.is_hidden) {<span class=\"design-target-hidden\">hidden</span>}\n </mat-option>\n }\n </mat-optgroup>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\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. The date format, abbreviation, display scale and\n symbol field start from the model but can be changed here for this grid alone.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track trackMetaField(def)) {\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) ?? def.defaultValue\" (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 ('field_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 <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSelectOptions(); 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 ('field_slot') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"slotValue(def)\" (ngModelChange)=\"onSlotChange(def, $event)\">\n <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSlotOptions(); 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 ('text_style') {\n <div class=\"text-style-control\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Size</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_size')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_size', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Weight</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_weight')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_weight', $event)\" />\n </mat-form-field>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!styleValue(def.key, 'italic')\"\n (change)=\"onStyleChange(def.key, 'italic', $event.checked)\">Italic</mat-checkbox>\n </div>\n\n @if (!isSelfColoured()) {\n <div class=\"color-control\">\n <span class=\"color-control-label\">Text colour</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'color'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'color'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'color')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'color')) }}%</span>\n @if (styleColor(def.key, 'color')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'color')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">theme</span>\n }\n </div>\n </div>\n }\n\n <div class=\"color-control\">\n <span class=\"color-control-label\">{{ isSelfColoured() ? 'Cell background' : 'Background' }}</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'background'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'background', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'background'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'background')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'background')) }}%</span>\n @if (styleColor(def.key, 'background')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'background')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">none</span>\n }\n </div>\n </div>\n\n @if (styleColor(def.key, 'background') && !isSelfColoured()) {\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"fill-select\">\n <mat-label>Fill</mat-label>\n <mat-select [ngModel]=\"styleFill(def.key)\" (ngModelChange)=\"onStyleFill(def.key, $event)\">\n <mat-option value=\"text\">Behind the text (pill)</mat-option>\n <mat-option value=\"cell\">Whole cell</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n }\n </div>\n }\n\n @case ('rule_list') {\n <div class=\"rule-list\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <p class=\"rule-list-hint\">Checked top to bottom \u2014 the first rule that matches wins.</p>\n @for (rule of ruleItems(def.key); track $index) {\n <div class=\"rule-row\">\n <div class=\"rule-row-head\">\n <mat-form-field appearance=\"outline\" class=\"rule-op\">\n <mat-label>When</mat-label>\n <mat-select [ngModel]=\"rule.op || 'between'\" (ngModelChange)=\"updateRule(def.key, $index, 'op', $event)\">\n @for (o of ruleOperators; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"rule-actions\">\n <button type=\"button\" class=\"rule-btn\" title=\"Move up\" (click)=\"moveRule(def.key, $index, -1)\">\u2191</button>\n <button type=\"button\" class=\"rule-btn\" title=\"Move down\" (click)=\"moveRule(def.key, $index, 1)\">\u2193</button>\n <button type=\"button\" class=\"rule-btn rule-btn-remove\" title=\"Remove\" (click)=\"removeRule(def.key, $index)\">\u2715</button>\n </div>\n </div>\n @if (operandCount(rule.op) > 0) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>{{ operandCount(rule.op) === 2 ? 'From' : 'Value' }}</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'from', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'from')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.from\" (ngModelChange)=\"updateRule(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n } @else {\n @if (ruleSourceArg(def.key, $index, 'from'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>\u00B1 std dev</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleStddevOffset(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleStddevOffset(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n </div>\n @if (operandCount(rule.op) === 2) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>To</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'to', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'to')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.to\" (ngModelChange)=\"updateRule(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n } @else if (ruleSourceArg(def.key, $index, 'to'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n }\n </div>\n }\n }\n <div class=\"rule-row-format\">\n <span class=\"rule-format-label\">Text</span>\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.color)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'color', { hex: $any($event.target).value })\" />\n <mat-form-field appearance=\"outline\" class=\"color-token-field rule-token\">\n <mat-select [ngModel]=\"colorToken(rule.color)\"\n (ngModelChange)=\"onRuleColorEdit(def.key, $index, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\">{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <span class=\"rule-format-label\">Fill</span>\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { hex: $any($event.target).value })\" />\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\" [value]=\"colorAlpha(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { alpha: +$any($event.target).value })\" />\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bold\"\n (change)=\"updateRule(def.key, $index, 'bold', $event.checked)\">Bold</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"rule.fill === 'text'\"\n (change)=\"updateRule(def.key, $index, 'fill', $event.checked ? 'text' : 'cell')\">Pill</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar\"\n (change)=\"updateRule(def.key, $index, 'bar', $event.checked)\">Data bar</mat-checkbox>\n @if (rule.bar) {\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar_auto\"\n (change)=\"updateRule(def.key, $index, 'bar_auto', $event.checked)\">Scale to column</mat-checkbox>\n }\n </div>\n @if (rule.bar && !rule.bar_auto) {\n <div class=\"rule-row-format\">\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar min</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_min\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_min', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar max</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_max\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_max', $event)\" />\n </mat-form-field>\n </div>\n }\n </div>\n }\n <button type=\"button\" class=\"rule-add\" (click)=\"addRule(def.key)\">+ Add rule</button>\n </div>\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\">×</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\">×</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)}.text-style-control{display:flex;flex-direction:column;gap:6px}.text-style-label{font-size:12px;color:var(--grid-on-surface-variant, #49454f)}.text-style-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.text-style-number{width:96px}.text-style-color{display:flex;align-items:center;gap:6px}.text-style-color input[type=color]{width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.text-style-color-label{font-size:12px}.text-style-clear{border:none;background:none;padding:0;font-size:11px;color:var(--grid-primary, #6750a4);cursor:pointer}.text-style-color--unset input[type=color]{opacity:.3}.text-style-unset-hint{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.design-target-hidden{margin-left:6px;padding:1px 6px;border-radius:8px;font-size:10px;text-transform:uppercase;letter-spacing:.4px;background:var(--grid-surface-container-high, #e6e0e9);color:var(--grid-on-surface-variant, #49454f)}.color-control{display:block;margin-top:4px}.color-control-label{display:block;font-size:12px;margin-bottom:2px;color:var(--grid-on-surface-variant, #49454f)}.color-control-body{display:flex;align-items:center;gap:6px;min-width:0}.color-token-field{flex:1 1 auto;min-width:0}.color-swatch{display:inline-block;width:12px;height:12px;margin-right:6px;border-radius:3px;border:1px solid var(--grid-outline-variant, #cac4d0);vertical-align:middle}.color-hex{flex:0 0 auto;width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.color-preview{width:32px;height:28px;border-radius:4px;border:1px solid var(--grid-outline-variant, #cac4d0)}.color-alpha{flex:0 1 68px;min-width:44px}.color-alpha-value{flex:0 0 auto;font-size:11px;min-width:30px;color:var(--grid-on-surface-variant, #49454f)}.color-preview{flex:0 0 auto}.fill-select{width:220px}.rule-list{display:flex;flex-direction:column;gap:8px}.rule-list-hint{margin:0;font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-row{border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;padding:8px;display:flex;flex-direction:column;gap:6px}.rule-row-head{display:flex;align-items:center;gap:6px;min-width:0}.rule-row-operands,.rule-row-format{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.rule-actions{margin-left:auto;flex:0 0 auto;display:flex;gap:4px}.rule-op{flex:1 1 auto;min-width:0}.rule-operand{flex:1 1 96px;min-width:88px}.rule-source{flex:1 1 140px;min-width:128px}.rule-token{flex:1 1 96px;min-width:88px}.rule-format-label{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-btn{border:1px solid var(--grid-outline-variant, #cac4d0);background:none;border-radius:4px;width:26px;height:26px;cursor:pointer;font-size:12px;line-height:1}.rule-btn-remove{color:var(--grid-error, #b3261e)}.rule-add{align-self:flex-start;border:1px dashed var(--grid-outline, #79747e);background:none;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;color:var(--grid-primary, #6750a4)}\n"] }]
|
|
13701
|
+
], 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 <!-- Which field is being designed. A pivot with one measure renders no\n measure-name row, so its single header cell stands for the column\n dimension and the measure at once and can only open one of them; this\n reaches the other. Also the way to a header too narrow to click, or a\n hidden column. Only shown when there is more than one thing to pick. -->\n @if (designTargets().length > 0 && targetCount() > 1) {\n <div class=\"design-field design-target\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Editing</mat-label>\n <mat-select [ngModel]=\"gridStore.selectedDesignColumn()\"\n (ngModelChange)=\"gridStore.selectDesignColumn($event)\">\n @for (group of designTargets(); track group.group) {\n <mat-optgroup [label]=\"group.group\">\n @for (target of group.fields; track target.name) {\n <mat-option [value]=\"target.name\">\n {{ target.label || target.name }}\n @if (target.is_hidden) {<span class=\"design-target-hidden\">hidden</span>}\n </mat-option>\n }\n </mat-optgroup>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n\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. The date format, abbreviation, display scale and\n symbol field start from the model but can be changed here for this grid alone.\n </p>\n }\n }\n </div>\n\n @for (def of metaFields(); track trackMetaField(def)) {\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) ?? def.defaultValue\" (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 ('field_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 <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSelectOptions(); 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 ('field_slot') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"slotValue(def)\" (ngModelChange)=\"onSlotChange(def, $event)\">\n <mat-option [value]=\"null\">None</mat-option>\n @for (opt of fieldSlotOptions(); 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 ('text_style') {\n <div class=\"text-style-control\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Size</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_size')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_size', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"text-style-number\">\n <mat-label>Weight</mat-label>\n <input matInput type=\"number\" [ngModel]=\"styleValue(def.key, 'font_weight')\"\n (ngModelChange)=\"onStyleChange(def.key, 'font_weight', $event)\" />\n </mat-form-field>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!styleValue(def.key, 'italic')\"\n (change)=\"onStyleChange(def.key, 'italic', $event.checked)\">Italic</mat-checkbox>\n </div>\n\n @if (!isSelfColoured()) {\n <div class=\"color-control\">\n <span class=\"color-control-label\">Text colour</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'color'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'color'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'color')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'color'))\"\n (input)=\"onStyleColorEdit(def.key, 'color', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'color')) }}%</span>\n @if (styleColor(def.key, 'color')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'color')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">theme</span>\n }\n </div>\n </div>\n }\n\n <div class=\"color-control\">\n <span class=\"color-control-label\">{{ isSelfColoured() ? 'Cell background' : 'Background' }}</span>\n <div class=\"color-control-body\">\n <mat-form-field appearance=\"outline\" class=\"color-token-field\">\n <mat-select [ngModel]=\"colorToken(styleColor(def.key, 'background'))\"\n (ngModelChange)=\"onStyleColorEdit(def.key, 'background', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!isTokenColor(styleColor(def.key, 'background'))) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"styleColor(def.key, 'background')\"></span>\n }\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\"\n [value]=\"colorAlpha(styleColor(def.key, 'background'))\"\n (input)=\"onStyleColorEdit(def.key, 'background', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(styleColor(def.key, 'background')) }}%</span>\n @if (styleColor(def.key, 'background')) {\n <button type=\"button\" class=\"text-style-clear\" (click)=\"clearStyleColor(def.key, 'background')\">Reset</button>\n } @else {\n <span class=\"text-style-unset-hint\">none</span>\n }\n </div>\n </div>\n\n @if (styleColor(def.key, 'background') && !isSelfColoured()) {\n <div class=\"text-style-row\">\n <mat-form-field appearance=\"outline\" class=\"fill-select\">\n <mat-label>Fill</mat-label>\n <mat-select [ngModel]=\"styleFill(def.key)\" (ngModelChange)=\"onStyleFill(def.key, $event)\">\n <mat-option value=\"text\">Behind the text (pill)</mat-option>\n <mat-option value=\"cell\">Whole cell</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n }\n </div>\n }\n\n @case ('rule_list') {\n <div class=\"rule-list\">\n <label class=\"text-style-label\">{{ def.label }}</label>\n <p class=\"rule-list-hint\">Checked top to bottom \u2014 the first rule that matches wins.</p>\n @for (rule of ruleItems(def.key); track $index) {\n <div class=\"rule-row\">\n <div class=\"rule-row-head\">\n <mat-form-field appearance=\"outline\" class=\"rule-op\">\n <mat-label>When</mat-label>\n <mat-select [ngModel]=\"rule.op || 'between'\" (ngModelChange)=\"updateRule(def.key, $index, 'op', $event)\">\n @for (o of ruleOperators; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"rule-actions\">\n <button type=\"button\" class=\"rule-btn\" title=\"Move up\" (click)=\"moveRule(def.key, $index, -1)\">\u2191</button>\n <button type=\"button\" class=\"rule-btn\" title=\"Move down\" (click)=\"moveRule(def.key, $index, 1)\">\u2193</button>\n <button type=\"button\" class=\"rule-btn rule-btn-remove\" title=\"Remove\" (click)=\"removeRule(def.key, $index)\">\u2715</button>\n </div>\n </div>\n @if (operandCount(rule.op) > 0) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>{{ operandCount(rule.op) === 2 ? 'From' : 'Value' }}</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'from', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'from')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.from\" (ngModelChange)=\"updateRule(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n } @else {\n @if (ruleSourceArg(def.key, $index, 'from'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>\u00B1 std dev</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleStddevOffset(def.key, $index, 'from')\"\n (ngModelChange)=\"onRuleStddevOffset(def.key, $index, 'from', $event)\" />\n </mat-form-field>\n }\n </div>\n @if (operandCount(rule.op) === 2) {\n <div class=\"rule-row-operands\">\n <mat-form-field appearance=\"outline\" class=\"rule-source\">\n <mat-label>To</mat-label>\n <mat-select [ngModel]=\"ruleSource(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSource(def.key, $index, 'to', $event)\">\n @for (o of operandSources; track o.value) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (!ruleSource(def.key, $index, 'to')) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Value</mat-label>\n <input matInput [ngModel]=\"rule.to\" (ngModelChange)=\"updateRule(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n } @else if (ruleSourceArg(def.key, $index, 'to'); as arg) {\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>{{ arg === 'p' ? 'Percentile' : 'N' }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"ruleSourceArgValue(def.key, $index, 'to')\"\n (ngModelChange)=\"onRuleSourceArg(def.key, $index, 'to', $event)\" />\n </mat-form-field>\n }\n </div>\n }\n }\n <div class=\"rule-row-format\">\n <span class=\"rule-color-group\">\n <span class=\"rule-format-label\">Text</span>\n <!-- Swapped for a read-only preview while a token is in force, as\n the column-level control does: with a token set,\n composeColorValue returns the token and a hex picked here is\n discarded, so an editable swatch invited an edit that\n silently did nothing. -->\n @if (!isTokenColor(rule.color)) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.color)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'color', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"rule.color\"></span>\n }\n <mat-form-field appearance=\"outline\" class=\"color-token-field rule-token\">\n <mat-select [ngModel]=\"colorToken(rule.color)\"\n (ngModelChange)=\"onRuleColorEdit(def.key, $index, 'color', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </span>\n <!-- Grouped so the row wraps between the two colours rather than\n through one: the percentage broke onto the next line beside\n 'Bold' and read as belonging to it. -->\n <span class=\"rule-color-group\">\n <span class=\"rule-format-label\">Fill</span>\n @if (!isTokenColor(rule.background)) {\n <input type=\"color\" class=\"color-hex\" [value]=\"colorHex(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { hex: $any($event.target).value })\" />\n } @else {\n <span class=\"color-preview\" [style.background]=\"rule.background\"></span>\n }\n <!-- A fill is as likely to want a theme colour as the text is, and\n the alpha slider works for either: composeColorValue wraps a\n token in color-mix below 100%. -->\n <mat-form-field appearance=\"outline\" class=\"color-token-field rule-token\">\n <mat-select [ngModel]=\"colorToken(rule.background)\"\n (ngModelChange)=\"onRuleColorEdit(def.key, $index, 'background', { token: $event })\">\n <mat-option value=\"\">Custom</mat-option>\n @for (t of colorTokens; track t.value) {\n <mat-option [value]=\"t.value\"><span class=\"color-swatch\" [style.background]=\"t.value\"></span>{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <!-- Its own line: the label, swatch and token select already fill\n the panel's width, and a token name as long as 'On Secondary\n Container' pushed the slider off the right edge. -->\n <span class=\"rule-alpha-group\">\n <span class=\"rule-format-label\">Opacity</span>\n <input type=\"range\" class=\"color-alpha\" min=\"0\" max=\"100\" step=\"1\" [value]=\"colorAlpha(rule.background)\"\n (input)=\"onRuleColorEdit(def.key, $index, 'background', { alpha: +$any($event.target).value })\" />\n <span class=\"color-alpha-value\">{{ colorAlpha(rule.background) }}%</span>\n </span>\n </span>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bold\"\n (change)=\"updateRule(def.key, $index, 'bold', $event.checked)\">Bold</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"rule.fill === 'text'\"\n (change)=\"updateRule(def.key, $index, 'fill', $event.checked ? 'text' : 'cell')\">Pill</mat-checkbox>\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar\"\n (change)=\"updateRule(def.key, $index, 'bar', $event.checked)\">Data bar</mat-checkbox>\n @if (rule.bar) {\n <mat-checkbox class=\"text-style-check\" [checked]=\"!!rule.bar_auto\"\n (change)=\"updateRule(def.key, $index, 'bar_auto', $event.checked)\">Scale to column</mat-checkbox>\n }\n </div>\n @if (rule.bar && !rule.bar_auto) {\n <div class=\"rule-row-format\">\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar min</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_min\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_min', $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"rule-operand\">\n <mat-label>Bar max</mat-label>\n <input matInput type=\"number\" [ngModel]=\"rule.bar_max\" (ngModelChange)=\"updateRule(def.key, $index, 'bar_max', $event)\" />\n </mat-form-field>\n </div>\n }\n </div>\n }\n <button type=\"button\" class=\"rule-add\" (click)=\"addRule(def.key)\">+ Add rule</button>\n </div>\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\">×</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\">×</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)}.text-style-control{display:flex;flex-direction:column;gap:6px}.text-style-label{font-size:12px;color:var(--grid-on-surface-variant, #49454f)}.text-style-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.text-style-number{width:96px}.text-style-color{display:flex;align-items:center;gap:6px}.text-style-color input[type=color]{width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.text-style-color-label{font-size:12px}.text-style-clear{border:none;background:none;padding:0;font-size:11px;color:var(--grid-primary, #6750a4);cursor:pointer}.text-style-color--unset input[type=color]{opacity:.3}.text-style-unset-hint{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.design-target-hidden{margin-left:6px;padding:1px 6px;border-radius:8px;font-size:10px;text-transform:uppercase;letter-spacing:.4px;background:var(--grid-surface-container-high, #e6e0e9);color:var(--grid-on-surface-variant, #49454f)}.color-control{display:block;margin-top:4px}.color-control-label{display:block;font-size:12px;margin-bottom:2px;color:var(--grid-on-surface-variant, #49454f)}.color-control-body{display:flex;align-items:center;gap:6px;min-width:0}.color-token-field{flex:1 1 auto;min-width:0}.color-swatch{display:inline-block;width:12px;height:12px;margin-right:6px;border-radius:3px;border:1px solid var(--grid-outline-variant, #cac4d0);vertical-align:middle}.color-hex{flex:0 0 auto;width:32px;height:28px;padding:0;border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:4px;background:none;cursor:pointer}.color-preview{width:32px;height:28px;border-radius:4px;border:1px solid var(--grid-outline-variant, #cac4d0)}.color-alpha{flex:0 1 68px;min-width:44px}.color-alpha-value{flex:0 0 auto;font-size:11px;min-width:30px;color:var(--grid-on-surface-variant, #49454f)}.color-preview{flex:0 0 auto}.fill-select{width:220px}.rule-list{display:flex;flex-direction:column;gap:8px}.rule-list-hint{margin:0;font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-row{border:1px solid var(--grid-outline-variant, #cac4d0);border-radius:6px;padding:8px;display:flex;flex-direction:column;gap:6px}.rule-row-head{display:flex;align-items:center;gap:6px;min-width:0}.rule-row-operands,.rule-row-format{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.rule-actions{margin-left:auto;flex:0 0 auto;display:flex;gap:4px}.rule-op{flex:1 1 auto;min-width:0}.rule-operand{flex:1 1 96px;min-width:88px}.rule-source{flex:1 1 140px;min-width:128px}.rule-token{flex:1 1 96px;min-width:88px}.rule-format-label{font-size:11px;color:var(--grid-on-surface-variant, #49454f)}.rule-color-group{display:flex;align-items:center;flex-wrap:wrap;gap:6px;flex:1 1 100%}.rule-alpha-group{display:flex;align-items:center;gap:6px;flex:1 1 100%}.rule-alpha-group .color-alpha{flex:1 1 auto}.rule-btn{border:1px solid var(--grid-outline-variant, #cac4d0);background:none;border-radius:4px;width:26px;height:26px;cursor:pointer;font-size:12px;line-height:1}.rule-btn-remove{color:var(--grid-error, #b3261e)}.rule-add{align-self:flex-start;border:1px dashed var(--grid-outline, #79747e);background:none;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;color:var(--grid-primary, #6750a4)}\n"] }]
|
|
13126
13702
|
}], ctorParameters: () => [] });
|
|
13127
13703
|
|
|
13128
13704
|
/**
|
|
@@ -14731,7 +15307,7 @@ class EruGridComponent {
|
|
|
14731
15307
|
*/
|
|
14732
15308
|
static PIVOT_INHERITED_KEYS = [
|
|
14733
15309
|
'datatype', 'symbol', 'decimal', 'seperator', 'dynamic_number',
|
|
14734
|
-
'display_number_as', 'num_val', 'num_val_check', 'color_ranges',
|
|
15310
|
+
'display_number_as', 'num_val', 'num_val_check', 'color_ranges', 'cell_rules',
|
|
14735
15311
|
'is_perc', 'start_value', 'end_value', 'tool_tip', 'description',
|
|
14736
15312
|
'enableDrilldown',
|
|
14737
15313
|
// Width is held on the measure/dimension, not per generated column, so a
|
|
@@ -15715,7 +16291,7 @@ class EruGridComponent {
|
|
|
15715
16291
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: EruGridComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
15716
16292
|
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", cellTemplate: "cellTemplate", boardCardHeight: "boardCardHeight", boardCardGap: "boardCardGap", boardCardPadding: "boardCardPadding" }, outputs: { rowSelect: "rowSelect", actionClick: "actionClick" }, providers: [EruGridStore, EruGridService,
|
|
15717
16293
|
...MATERIAL_PROVIDERS
|
|
15718
|
-
], 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<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\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 @if(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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\" [class.board-columns-nowrap]=\"!boardWrapColumns()\">\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 @if (showBoardColumnHeader()) {\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 }\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n [style.height.px]=\"boardColumnBodyHeight(group)\"\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 @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(enableGrandTotal() && grandTotalPosition() === '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(enableRowSubtotals() && subtotalPosition() === '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() && enableGrandTotal() && grandTotalPosition() === '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() && enableRowSubtotals() && subtotalPosition() === '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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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\" [cellTemplate]=\"cellTemplate\" [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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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 [cellTemplate]=\"cellTemplate\" [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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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(enableRowSubtotals() && subtotalPosition() === '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(enableGrandTotal() && grandTotalPosition() === '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]=\"isResizablePivotHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header) || $any(header)\"\n 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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor($any(header))\"\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 @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader($any(header)) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, $any(header))\">tune</mat-icon>\n }\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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor(column)\"\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 <!-- Label and control laid out as a row: the label truncates, the\n control keeps its place. Left as a bare text node the long\n aggregation labels pushed the icon past the cell edge, where\n `overflow: hidden` clipped it out of sight entirely. -->\n <div class=\"pivot-header-content\">\n <!-- Deliberately not `.column-label`: that class carries the\n wrap-headers rule, which broke these labels onto one word per\n line. This header truncates, as it did before. -->\n <span class=\"pivot-header-label\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader(column) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n </div>\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<!-- \u2500\u2500\u2500 Action column cell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 One template for both column positions. With no `config.actions` set it\n falls back to the single more_horiz icon the column has always shown, so\n grids that only listen to the store's actionClick signal keep working.\n Context: { $implicit: Row, mode: 'table' | 'board', 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 #actionCell let-row let-mode=\"mode\" let-group=\"group\">\n @if(!hasConfiguredActions()) {\n <mat-icon (click)=\"onActionClick($event, row, undefined, mode || 'table', group)\">more_horiz</mat-icon>\n } @else if(actionDisplayType() === 'kebab') {\n @if(visibleActionsFor(row).length > 0) {\n <mat-icon class=\"action-kebab\" [matMenuTriggerFor]=\"rowActionMenu\"\n [matMenuTriggerData]=\"{ row: row, mode: mode || 'table', group: group }\"\n (click)=\"$event.stopPropagation()\">more_vert</mat-icon>\n }\n } @else {\n <div class=\"action-icons\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <mat-icon class=\"action-icon\" [matTooltip]=\"action.action_name\" matTooltipPosition=\"above\"\n (click)=\"onActionClick($event, row, action, mode || 'table', group)\">{{action.action_icon || 'play_arrow'}}</mat-icon>\n }\n </div>\n }\n</ng-template>\n\n<!-- Kebab menu shared by every row; the row is passed through matMenuTriggerData. -->\n<mat-menu #rowActionMenu=\"matMenu\" class=\"eru-grid-action-menu\">\n <ng-template matMenuContent let-row=\"row\" let-mode=\"mode\" let-group=\"group\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <button mat-menu-item (click)=\"onActionClick($event, row, action, mode || 'table', group)\">\n <mat-icon>{{action.action_icon || 'play_arrow'}}</mat-icon>\n <span>{{action.action_name}}</span>\n </button>\n }\n </ng-template>\n</mat-menu>\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <!-- headerRowHeight rides on the row, not the cells: `thead.eru-wrap-headers\n th { height: auto }` outranks any class-level height we could put on a\n th, which is why a configured header height was ignored while data rows\n (inline height on tr.row-item) honoured theirs. On a table row `height`\n is a minimum, so a wrapped two-line header still grows past it. -->\n <tr [style.height.px]=\"headerRowHeight()\" [style.minHeight.px]=\"headerRowHeight()\">\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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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%;max-height:var(--grid-height, none);min-height:var(--grid-min-height, 120px);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-inset-x: 8px;--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))}.pivot-column-header .pivot-header-content{display:flex;align-items:center;justify-content:center;gap:4px;min-width:0}.pivot-column-header .pivot-header-content .pivot-header-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.pivot-column-header .pivot-header-content .design-edit-icon,.pivot-column-header .header-content .design-edit-icon{flex:0 0 auto}.pivot-column-header .header-content data-cell,.pivot-column-header .header-content data-cell *{color:inherit!important}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-min-height, 120px);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{max-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!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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{text-align:center!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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)}.action-column .action-icons{display:flex;align-items:center;justify-content:center;gap:6px;overflow-x:auto;scrollbar-width:none}.action-column .action-icons::-webkit-scrollbar{display:none}.action-column .action-icon{flex:0 0 auto}.eru-grid-action-menu .mat-mdc-menu-item mat-icon{margin-right:8px;font-size:18px;width:18px;height:18px;line-height:18px;color:var(--grid-on-surface-variant)}.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:0 var(--grid-header-padding-x);height:var(--grid-header-row-height, auto)}.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:0 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;flex-shrink:0}.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 .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700}.table-mode .grand-total-row td{background-color:var(--grid-surface-container-high);color:var(--grid-on-surface)}.table-mode .grand-total-row td:first-child{color:var(--grid-primary)}.table-mode .grand-total-row td.grand-total-cell{font-weight:600}.table-mode .grand-total-row td.grand-total-cell .grand-total-label{font-weight:700;color:var(--grid-primary)}.table-mode .grand-total-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .grand-total-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .grand-total-row.grand-total-bold td{font-weight:700!important;font-style:normal!important}.table-mode .grand-total-row.grand-total-italic td{font-style:italic!important}.table-mode .grand-total-row.grand-total-italic td:first-child{font-weight:700!important}.table-mode .grand-total-row.grand-total-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .grand-total-row.grand-total-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:800!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .grand-total-row.grand-total-highlighted:hover,.table-mode .grand-total-row.grand-total-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;max-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),var(--board-col-max-width, 1fr)));grid-auto-rows:auto;align-items:start;gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start;justify-content:start}.board-mode-host .board-columns-wrapper.board-columns-nowrap{grid-auto-flow:column;grid-template-columns:none;grid-template-rows:auto;grid-auto-rows:auto;grid-auto-columns:minmax(var(--board-col-min-width, 300px),var(--board-col-max-width, 1fr));overflow-x:auto;overflow-y:hidden}.board-mode-host .board-column{max-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;background-color:transparent}.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(--eru-board-count-color, var(--grid-on-surface-variant, #49454f));background:var(--eru-board-count-bg, var(--grid-surface-variant, #e7e0ec));border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:0 1 auto;min-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", "cellTemplate"], 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: i1$4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$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$2.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.MatMenuContent, selector: "ng-template[matMenuContent]" }, { 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 });
|
|
16294
|
+
], 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<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\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 @if(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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\" [class.board-columns-nowrap]=\"!boardWrapColumns()\">\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 @if (showBoardColumnHeader()) {\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 }\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n [style.height.px]=\"boardColumnBodyHeight(group)\"\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 @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(enableGrandTotal() && grandTotalPosition() === '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(enableRowSubtotals() && subtotalPosition() === '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() && enableGrandTotal() && grandTotalPosition() === '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() && enableRowSubtotals() && subtotalPosition() === '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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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\" [cellTemplate]=\"cellTemplate\" [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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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 [cellTemplate]=\"cellTemplate\" [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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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(enableRowSubtotals() && subtotalPosition() === '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(enableGrandTotal() && grandTotalPosition() === '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]=\"isResizablePivotHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header) || $any(header)\"\n 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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor($any(header))\"\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 @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader($any(header)) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, $any(header))\">tune</mat-icon>\n }\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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor(column)\"\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 <!-- Label and control laid out as a row: the label truncates, the\n control keeps its place. Left as a bare text node the long\n aggregation labels pushed the icon past the cell edge, where\n `overflow: hidden` clipped it out of sight entirely. -->\n <div class=\"pivot-header-content\">\n <!-- Deliberately not `.column-label`: that class carries the\n wrap-headers rule, which broke these labels onto one word per\n line. This header truncates, as it did before. -->\n <span class=\"pivot-header-label\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader(column) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n </div>\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<!-- \u2500\u2500\u2500 Action column cell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 One template for both column positions. With no `config.actions` set it\n falls back to the single more_horiz icon the column has always shown, so\n grids that only listen to the store's actionClick signal keep working.\n Context: { $implicit: Row, mode: 'table' | 'board', 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 #actionCell let-row let-mode=\"mode\" let-group=\"group\">\n @if(!hasConfiguredActions()) {\n <mat-icon (click)=\"onActionClick($event, row, undefined, mode || 'table', group)\">more_horiz</mat-icon>\n } @else if(actionDisplayType() === 'kebab') {\n @if(visibleActionsFor(row).length > 0) {\n <mat-icon class=\"action-kebab\" [matMenuTriggerFor]=\"rowActionMenu\"\n [matMenuTriggerData]=\"{ row: row, mode: mode || 'table', group: group }\"\n (click)=\"$event.stopPropagation()\">more_vert</mat-icon>\n }\n } @else {\n <div class=\"action-icons\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <mat-icon class=\"action-icon\" [matTooltip]=\"action.action_name\" matTooltipPosition=\"above\"\n (click)=\"onActionClick($event, row, action, mode || 'table', group)\">{{action.action_icon || 'play_arrow'}}</mat-icon>\n }\n </div>\n }\n</ng-template>\n\n<!-- Kebab menu shared by every row; the row is passed through matMenuTriggerData. -->\n<mat-menu #rowActionMenu=\"matMenu\" class=\"eru-grid-action-menu\">\n <ng-template matMenuContent let-row=\"row\" let-mode=\"mode\" let-group=\"group\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <button mat-menu-item (click)=\"onActionClick($event, row, action, mode || 'table', group)\">\n <mat-icon>{{action.action_icon || 'play_arrow'}}</mat-icon>\n <span>{{action.action_name}}</span>\n </button>\n }\n </ng-template>\n</mat-menu>\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <!-- headerRowHeight rides on the row, not the cells: `thead.eru-wrap-headers\n th { height: auto }` outranks any class-level height we could put on a\n th, which is why a configured header height was ignored while data rows\n (inline height on tr.row-item) honoured theirs. On a table row `height`\n is a minimum, so a wrapped two-line header still grows past it. -->\n <tr [style.height.px]=\"headerRowHeight()\" [style.minHeight.px]=\"headerRowHeight()\">\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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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: var(--mat-sys-primary, #6750a4);--grid-on-primary: var(--mat-sys-on-primary, #ffffff);--grid-primary-container: var(--mat-sys-primary-container, #eaddff);--grid-on-primary-container: var(--mat-sys-on-primary-container, #21005d);--grid-secondary: var(--mat-sys-secondary, #625b71);--grid-on-secondary: var(--mat-sys-on-secondary, #ffffff);--grid-secondary-container: var(--mat-sys-secondary-container, #e8def8);--grid-on-secondary-container: var(--mat-sys-on-secondary-container, #1d192b);--grid-tertiary: var(--mat-sys-tertiary, #7d5260);--grid-on-tertiary: var(--mat-sys-on-tertiary, #ffffff);--grid-tertiary-container: var(--mat-sys-tertiary-container, #ffd8e4);--grid-on-tertiary-container: var(--mat-sys-on-tertiary-container, #31111d);--grid-surface: var(--mat-sys-surface, #fef7ff);--grid-surface-variant: var(--mat-sys-surface-variant, #e7e0ec);--grid-surface-container: var(--mat-sys-surface-container, #f3edf7);--grid-surface-container-high: var(--mat-sys-surface-container-high, #ede7f0);--grid-on-surface: var(--mat-sys-on-surface, #1d1b20);--grid-on-surface-variant: var(--mat-sys-on-surface-variant, #49454f);--grid-outline: var(--mat-sys-outline, #79757f);--grid-outline-variant: var(--mat-sys-outline-variant, #cac4d0);--grid-error: var(--mat-sys-error, #ba1a1a);--grid-error-container: var(--mat-sys-error-container, #ffdad6);--grid-base-surface: var(--surface, #ffffff);--grid-base-on-surface: var(--on-surface, #000000);--grid-base-border: var(--border, #e5e7eb);--grid-primary-light: var(--grid-primary-container)}:host,eru-grid{display:block!important;width:100%;height:100%;flex:1 1 0%;max-height:var(--grid-height, none);min-height:var(--grid-min-height, 120px);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-inset-x: 8px;--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-container, rgba(63, 81, 181, .12))}.pivot-column-header .pivot-header-content{display:flex;align-items:center;justify-content:center;gap:4px;min-width:0}.pivot-column-header .pivot-header-content .pivot-header-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.pivot-column-header .pivot-header-content .design-edit-icon,.pivot-column-header .header-content .design-edit-icon{flex:0 0 auto}.pivot-column-header .header-content data-cell,.pivot-column-header .header-content data-cell *{color:inherit!important}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-min-height, 120px);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{max-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!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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{text-align:center!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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)}.action-column .action-icons{display:flex;align-items:center;justify-content:center;gap:6px;overflow-x:auto;scrollbar-width:none}.action-column .action-icons::-webkit-scrollbar{display:none}.action-column .action-icon{flex:0 0 auto}.eru-grid-action-menu .mat-mdc-menu-item mat-icon{margin-right:8px;font-size:18px;width:18px;height:18px;line-height:18px;color:var(--grid-on-surface-variant)}.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:0 var(--grid-header-padding-x);height:var(--grid-header-row-height, auto)}.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;--grid-header-affordance-space: 0px;--grid-column-resizer-width: 10px;--grid-header-sort-right: calc(var(--grid-column-resizer-width) + 2px);--grid-header-design-right: calc(var(--grid-column-resizer-width) + 2px);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}.table-column-header.sortable-header{--grid-header-affordance-space: 20px}.table-column-header.design-clickable{--grid-header-affordance-space: 28px}.table-column-header.sortable-header.design-clickable{--grid-header-affordance-space: 40px;--grid-header-design-right: calc(var(--grid-column-resizer-width) + 13px)}.table-column-header .column-label{display:block;padding-right:var(--grid-header-affordance-space)}.table-column-header .sort-indicator,.table-column-header .design-edit-icon{position:absolute;top:50%;transform:translateY(-50%);margin-left:0}.table-column-header .sort-indicator{right:var(--grid-header-sort-right)}.table-column-header .design-edit-icon{right:var(--grid-header-design-right)}.sortable-header{cursor:pointer}.sortable-header .sort-indicator{display:inline-flex;align-items:center;gap:2px;cursor:pointer;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:0 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;flex-shrink:0}.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 .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700}.table-mode .grand-total-row td{background-color:var(--grid-surface-container-high);color:var(--grid-on-surface)}.table-mode .grand-total-row td:first-child{color:var(--grid-primary)}.table-mode .grand-total-row td.grand-total-cell{font-weight:600}.table-mode .grand-total-row td.grand-total-cell .grand-total-label{font-weight:700;color:var(--grid-primary)}.table-mode .grand-total-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .grand-total-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .grand-total-row.grand-total-bold td{font-weight:700!important;font-style:normal!important}.table-mode .grand-total-row.grand-total-italic td{font-style:italic!important}.table-mode .grand-total-row.grand-total-italic td:first-child{font-weight:700!important}.table-mode .grand-total-row.grand-total-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .grand-total-row.grand-total-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:800!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .grand-total-row.grand-total-highlighted:hover,.table-mode .grand-total-row.grand-total-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;max-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),var(--board-col-max-width, 1fr)));grid-auto-rows:auto;align-items:start;gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start;justify-content:start}.board-mode-host .board-columns-wrapper.board-columns-nowrap{grid-auto-flow:column;grid-template-columns:none;grid-template-rows:auto;grid-auto-rows:auto;grid-auto-columns:minmax(var(--board-col-min-width, 300px),var(--board-col-max-width, 1fr));overflow-x:auto;overflow-y:hidden}.board-mode-host .board-column{max-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;background-color:transparent}.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(--eru-board-count-color, var(--grid-on-surface-variant, #49454f));background:var(--eru-board-count-bg, var(--grid-surface-variant, #e7e0ec));border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:0 1 auto;min-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", "cellTemplate"], 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: i1$4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$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$2.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.MatMenuContent, selector: "ng-template[matMenuContent]" }, { 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 });
|
|
15719
16295
|
}
|
|
15720
16296
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: EruGridComponent, decorators: [{
|
|
15721
16297
|
type: Component,
|
|
@@ -15735,7 +16311,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
15735
16311
|
ResizeColumnDirective,
|
|
15736
16312
|
ColumnDragDirective,
|
|
15737
16313
|
ColumnDesignPanelComponent
|
|
15738
|
-
], template: "<!-- <div style=\"background: #f0f0f0; font-size: 12px; border-bottom: 1px solid #ccc;\">\ncurrentPivotScrollIndex {{currentPivotScrollIndex()}} |\nfirstDataRowIndex {{firstDataRowIndex()}} |\nfirstTr {{firstTr}} |\nmaxDepth {{maxDepth()}}\n</div> -->\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\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 @if(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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\" [class.board-columns-nowrap]=\"!boardWrapColumns()\">\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 @if (showBoardColumnHeader()) {\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 }\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n [style.height.px]=\"boardColumnBodyHeight(group)\"\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 @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(enableGrandTotal() && grandTotalPosition() === '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(enableRowSubtotals() && subtotalPosition() === '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() && enableGrandTotal() && grandTotalPosition() === '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() && enableRowSubtotals() && subtotalPosition() === '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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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\" [cellTemplate]=\"cellTemplate\" [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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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 [cellTemplate]=\"cellTemplate\" [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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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(enableRowSubtotals() && subtotalPosition() === '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(enableGrandTotal() && grandTotalPosition() === '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]=\"isResizablePivotHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header) || $any(header)\"\n 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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor($any(header))\"\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 @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader($any(header)) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, $any(header))\">tune</mat-icon>\n }\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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor(column)\"\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 <!-- Label and control laid out as a row: the label truncates, the\n control keeps its place. Left as a bare text node the long\n aggregation labels pushed the icon past the cell edge, where\n `overflow: hidden` clipped it out of sight entirely. -->\n <div class=\"pivot-header-content\">\n <!-- Deliberately not `.column-label`: that class carries the\n wrap-headers rule, which broke these labels onto one word per\n line. This header truncates, as it did before. -->\n <span class=\"pivot-header-label\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader(column) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n </div>\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<!-- \u2500\u2500\u2500 Action column cell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 One template for both column positions. With no `config.actions` set it\n falls back to the single more_horiz icon the column has always shown, so\n grids that only listen to the store's actionClick signal keep working.\n Context: { $implicit: Row, mode: 'table' | 'board', 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 #actionCell let-row let-mode=\"mode\" let-group=\"group\">\n @if(!hasConfiguredActions()) {\n <mat-icon (click)=\"onActionClick($event, row, undefined, mode || 'table', group)\">more_horiz</mat-icon>\n } @else if(actionDisplayType() === 'kebab') {\n @if(visibleActionsFor(row).length > 0) {\n <mat-icon class=\"action-kebab\" [matMenuTriggerFor]=\"rowActionMenu\"\n [matMenuTriggerData]=\"{ row: row, mode: mode || 'table', group: group }\"\n (click)=\"$event.stopPropagation()\">more_vert</mat-icon>\n }\n } @else {\n <div class=\"action-icons\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <mat-icon class=\"action-icon\" [matTooltip]=\"action.action_name\" matTooltipPosition=\"above\"\n (click)=\"onActionClick($event, row, action, mode || 'table', group)\">{{action.action_icon || 'play_arrow'}}</mat-icon>\n }\n </div>\n }\n</ng-template>\n\n<!-- Kebab menu shared by every row; the row is passed through matMenuTriggerData. -->\n<mat-menu #rowActionMenu=\"matMenu\" class=\"eru-grid-action-menu\">\n <ng-template matMenuContent let-row=\"row\" let-mode=\"mode\" let-group=\"group\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <button mat-menu-item (click)=\"onActionClick($event, row, action, mode || 'table', group)\">\n <mat-icon>{{action.action_icon || 'play_arrow'}}</mat-icon>\n <span>{{action.action_name}}</span>\n </button>\n }\n </ng-template>\n</mat-menu>\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <!-- headerRowHeight rides on the row, not the cells: `thead.eru-wrap-headers\n th { height: auto }` outranks any class-level height we could put on a\n th, which is why a configured header height was ignored while data rows\n (inline height on tr.row-item) honoured theirs. On a table row `height`\n is a minimum, so a wrapped two-line header still grows past it. -->\n <tr [style.height.px]=\"headerRowHeight()\" [style.minHeight.px]=\"headerRowHeight()\">\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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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%;max-height:var(--grid-height, none);min-height:var(--grid-min-height, 120px);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-inset-x: 8px;--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))}.pivot-column-header .pivot-header-content{display:flex;align-items:center;justify-content:center;gap:4px;min-width:0}.pivot-column-header .pivot-header-content .pivot-header-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.pivot-column-header .pivot-header-content .design-edit-icon,.pivot-column-header .header-content .design-edit-icon{flex:0 0 auto}.pivot-column-header .header-content data-cell,.pivot-column-header .header-content data-cell *{color:inherit!important}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-min-height, 120px);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{max-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!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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{text-align:center!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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)}.action-column .action-icons{display:flex;align-items:center;justify-content:center;gap:6px;overflow-x:auto;scrollbar-width:none}.action-column .action-icons::-webkit-scrollbar{display:none}.action-column .action-icon{flex:0 0 auto}.eru-grid-action-menu .mat-mdc-menu-item mat-icon{margin-right:8px;font-size:18px;width:18px;height:18px;line-height:18px;color:var(--grid-on-surface-variant)}.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:0 var(--grid-header-padding-x);height:var(--grid-header-row-height, auto)}.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:0 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;flex-shrink:0}.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 .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700}.table-mode .grand-total-row td{background-color:var(--grid-surface-container-high);color:var(--grid-on-surface)}.table-mode .grand-total-row td:first-child{color:var(--grid-primary)}.table-mode .grand-total-row td.grand-total-cell{font-weight:600}.table-mode .grand-total-row td.grand-total-cell .grand-total-label{font-weight:700;color:var(--grid-primary)}.table-mode .grand-total-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .grand-total-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .grand-total-row.grand-total-bold td{font-weight:700!important;font-style:normal!important}.table-mode .grand-total-row.grand-total-italic td{font-style:italic!important}.table-mode .grand-total-row.grand-total-italic td:first-child{font-weight:700!important}.table-mode .grand-total-row.grand-total-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .grand-total-row.grand-total-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:800!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .grand-total-row.grand-total-highlighted:hover,.table-mode .grand-total-row.grand-total-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;max-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),var(--board-col-max-width, 1fr)));grid-auto-rows:auto;align-items:start;gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start;justify-content:start}.board-mode-host .board-columns-wrapper.board-columns-nowrap{grid-auto-flow:column;grid-template-columns:none;grid-template-rows:auto;grid-auto-rows:auto;grid-auto-columns:minmax(var(--board-col-min-width, 300px),var(--board-col-max-width, 1fr));overflow-x:auto;overflow-y:hidden}.board-mode-host .board-column{max-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;background-color:transparent}.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(--eru-board-count-color, var(--grid-on-surface-variant, #49454f));background:var(--eru-board-count-bg, var(--grid-surface-variant, #e7e0ec));border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:0 1 auto;min-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"] }]
|
|
16314
|
+
], template: "<!-- <div style=\"background: #f0f0f0; font-size: 12px; border-bottom: 1px solid #ccc;\">\ncurrentPivotScrollIndex {{currentPivotScrollIndex()}} |\nfirstDataRowIndex {{firstDataRowIndex()}} |\nfirstTr {{firstTr}} |\nmaxDepth {{maxDepth()}}\n</div> -->\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\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 @if(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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(showExcelDownload()) {\n <div class=\"excel-download-bar\">\n <ng-container *ngTemplateOutlet=\"excelDownloadIcon\"></ng-container>\n </div>\n }\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\" [class.board-columns-nowrap]=\"!boardWrapColumns()\">\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 @if (showBoardColumnHeader()) {\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 }\n <cdk-virtual-scroll-viewport [attr.data-group-id]=\"group.id\" [itemSize]=\"boardCardSlot\" class=\"board-column-body\"\n [style.height.px]=\"boardColumnBodyHeight(group)\"\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 @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(enableGrandTotal() && grandTotalPosition() === '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(enableRowSubtotals() && subtotalPosition() === '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() && enableGrandTotal() && grandTotalPosition() === '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() && enableRowSubtotals() && subtotalPosition() === '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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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\" [cellTemplate]=\"cellTemplate\" [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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\n <ng-container\n *ngTemplateOutlet=\"actionCell; context: { $implicit: row, mode: 'table', group: group }\"></ng-container>\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 [cellTemplate]=\"cellTemplate\" [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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">\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(enableRowSubtotals() && subtotalPosition() === '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(enableGrandTotal() && grandTotalPosition() === '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]=\"isResizablePivotHeader(header)\"\n [columnConfig]=\"getFieldForPivotHeader(header) || $any(header)\"\n 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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor($any(header))\"\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 @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader($any(header)) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, $any(header))\">tune</mat-icon>\n }\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 [class.design-clickable]=\"isDesignMode()\"\n [class.design-selected]=\"isDesignMode() && gridStore.selectedDesignColumn() === designTargetFor(column)\"\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 <!-- Label and control laid out as a row: the label truncates, the\n control keeps its place. Left as a bare text node the long\n aggregation labels pushed the icon past the cell edge, where\n `overflow: hidden` clipped it out of sight entirely. -->\n <div class=\"pivot-header-content\">\n <!-- Deliberately not `.column-label`: that class carries the\n wrap-headers rule, which broke these labels onto one word per\n line. This header truncates, as it did before. -->\n <span class=\"pivot-header-label\">{{column.label}}</span>\n @if(isDesignMode()) {\n <mat-icon class=\"design-edit-icon\"\n [title]=\"isAggregationHeader(column) ? 'Edit aggregation' : 'Edit column'\"\n (click)=\"onHeaderDesignClick($event, column)\">tune</mat-icon>\n }\n </div>\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<!-- \u2500\u2500\u2500 Action column cell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 One template for both column positions. With no `config.actions` set it\n falls back to the single more_horiz icon the column has always shown, so\n grids that only listen to the store's actionClick signal keep working.\n Context: { $implicit: Row, mode: 'table' | 'board', 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 #actionCell let-row let-mode=\"mode\" let-group=\"group\">\n @if(!hasConfiguredActions()) {\n <mat-icon (click)=\"onActionClick($event, row, undefined, mode || 'table', group)\">more_horiz</mat-icon>\n } @else if(actionDisplayType() === 'kebab') {\n @if(visibleActionsFor(row).length > 0) {\n <mat-icon class=\"action-kebab\" [matMenuTriggerFor]=\"rowActionMenu\"\n [matMenuTriggerData]=\"{ row: row, mode: mode || 'table', group: group }\"\n (click)=\"$event.stopPropagation()\">more_vert</mat-icon>\n }\n } @else {\n <div class=\"action-icons\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <mat-icon class=\"action-icon\" [matTooltip]=\"action.action_name\" matTooltipPosition=\"above\"\n (click)=\"onActionClick($event, row, action, mode || 'table', group)\">{{action.action_icon || 'play_arrow'}}</mat-icon>\n }\n </div>\n }\n</ng-template>\n\n<!-- Kebab menu shared by every row; the row is passed through matMenuTriggerData. -->\n<mat-menu #rowActionMenu=\"matMenu\" class=\"eru-grid-action-menu\">\n <ng-template matMenuContent let-row=\"row\" let-mode=\"mode\" let-group=\"group\">\n @for(action of visibleActionsFor(row); track action.action_name) {\n <button mat-menu-item (click)=\"onActionClick($event, row, action, mode || 'table', group)\">\n <mat-icon>{{action.action_icon || 'play_arrow'}}</mat-icon>\n <span>{{action.action_name}}</span>\n </button>\n }\n </ng-template>\n</mat-menu>\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\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\n [style]=\"'width: ' + actionColumnWidth() + 'px !important; min-width: ' + actionColumnWidth() + 'px !important; max-width: ' + actionColumnWidth() + 'px !important;'\">\n }\n </colgroup>\n</ng-template>\n\n\n<ng-template #tableHeader>\n\n <thead [class.eru-wrap-headers]=\"wrapHeaders()\">\n <!-- headerRowHeight rides on the row, not the cells: `thead.eru-wrap-headers\n th { height: auto }` outranks any class-level height we could put on a\n th, which is why a configured header height was ignored while data rows\n (inline height on tr.row-item) honoured theirs. On a table row `height`\n is a minimum, so a wrapped two-line header still grows past it. -->\n <tr [style.height.px]=\"headerRowHeight()\" [style.minHeight.px]=\"headerRowHeight()\">\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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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\" [style.width.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\">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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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.px]=\"actionColumnWidth()\"\n [style.minWidth.px]=\"actionColumnWidth()\" [style.maxWidth.px]=\"actionColumnWidth()\"></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: var(--mat-sys-primary, #6750a4);--grid-on-primary: var(--mat-sys-on-primary, #ffffff);--grid-primary-container: var(--mat-sys-primary-container, #eaddff);--grid-on-primary-container: var(--mat-sys-on-primary-container, #21005d);--grid-secondary: var(--mat-sys-secondary, #625b71);--grid-on-secondary: var(--mat-sys-on-secondary, #ffffff);--grid-secondary-container: var(--mat-sys-secondary-container, #e8def8);--grid-on-secondary-container: var(--mat-sys-on-secondary-container, #1d192b);--grid-tertiary: var(--mat-sys-tertiary, #7d5260);--grid-on-tertiary: var(--mat-sys-on-tertiary, #ffffff);--grid-tertiary-container: var(--mat-sys-tertiary-container, #ffd8e4);--grid-on-tertiary-container: var(--mat-sys-on-tertiary-container, #31111d);--grid-surface: var(--mat-sys-surface, #fef7ff);--grid-surface-variant: var(--mat-sys-surface-variant, #e7e0ec);--grid-surface-container: var(--mat-sys-surface-container, #f3edf7);--grid-surface-container-high: var(--mat-sys-surface-container-high, #ede7f0);--grid-on-surface: var(--mat-sys-on-surface, #1d1b20);--grid-on-surface-variant: var(--mat-sys-on-surface-variant, #49454f);--grid-outline: var(--mat-sys-outline, #79757f);--grid-outline-variant: var(--mat-sys-outline-variant, #cac4d0);--grid-error: var(--mat-sys-error, #ba1a1a);--grid-error-container: var(--mat-sys-error-container, #ffdad6);--grid-base-surface: var(--surface, #ffffff);--grid-base-on-surface: var(--on-surface, #000000);--grid-base-border: var(--border, #e5e7eb);--grid-primary-light: var(--grid-primary-container)}:host,eru-grid{display:block!important;width:100%;height:100%;flex:1 1 0%;max-height:var(--grid-height, none);min-height:var(--grid-min-height, 120px);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-inset-x: 8px;--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-container, rgba(63, 81, 181, .12))}.pivot-column-header .pivot-header-content{display:flex;align-items:center;justify-content:center;gap:4px;min-width:0}.pivot-column-header .pivot-header-content .pivot-header-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.pivot-column-header .pivot-header-content .design-edit-icon,.pivot-column-header .header-content .design-edit-icon{flex:0 0 auto}.pivot-column-header .header-content data-cell,.pivot-column-header .header-content data-cell *{color:inherit!important}.incremental-row-container{width:100%;height:100%;min-height:var(--grid-min-height, 120px);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{max-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!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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{text-align:center!important;padding-left:0!important;padding-right:0!important;text-overflow:clip!important;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)}.action-column .action-icons{display:flex;align-items:center;justify-content:center;gap:6px;overflow-x:auto;scrollbar-width:none}.action-column .action-icons::-webkit-scrollbar{display:none}.action-column .action-icon{flex:0 0 auto}.eru-grid-action-menu .mat-mdc-menu-item mat-icon{margin-right:8px;font-size:18px;width:18px;height:18px;line-height:18px;color:var(--grid-on-surface-variant)}.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:0 var(--grid-header-padding-x);height:var(--grid-header-row-height, auto)}.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;--grid-header-affordance-space: 0px;--grid-column-resizer-width: 10px;--grid-header-sort-right: calc(var(--grid-column-resizer-width) + 2px);--grid-header-design-right: calc(var(--grid-column-resizer-width) + 2px);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}.table-column-header.sortable-header{--grid-header-affordance-space: 20px}.table-column-header.design-clickable{--grid-header-affordance-space: 28px}.table-column-header.sortable-header.design-clickable{--grid-header-affordance-space: 40px;--grid-header-design-right: calc(var(--grid-column-resizer-width) + 13px)}.table-column-header .column-label{display:block;padding-right:var(--grid-header-affordance-space)}.table-column-header .sort-indicator,.table-column-header .design-edit-icon{position:absolute;top:50%;transform:translateY(-50%);margin-left:0}.table-column-header .sort-indicator{right:var(--grid-header-sort-right)}.table-column-header .design-edit-icon{right:var(--grid-header-design-right)}.sortable-header{cursor:pointer}.sortable-header .sort-indicator{display:inline-flex;align-items:center;gap:2px;cursor:pointer;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:0 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;flex-shrink:0}.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 .grand-total-row{background-color:var(--grid-surface-container-high)!important;font-weight:700}.table-mode .grand-total-row td{background-color:var(--grid-surface-container-high);color:var(--grid-on-surface)}.table-mode .grand-total-row td:first-child{color:var(--grid-primary)}.table-mode .grand-total-row td.grand-total-cell{font-weight:600}.table-mode .grand-total-row td.grand-total-cell .grand-total-label{font-weight:700;color:var(--grid-primary)}.table-mode .grand-total-row:hover{background-color:var(--grid-surface-container-high)!important}.table-mode .grand-total-row:hover td{background-color:var(--grid-surface-container-high)}.table-mode .grand-total-row.grand-total-bold td{font-weight:700!important;font-style:normal!important}.table-mode .grand-total-row.grand-total-italic td{font-style:italic!important}.table-mode .grand-total-row.grand-total-italic td:first-child{font-weight:700!important}.table-mode .grand-total-row.grand-total-highlighted{background-color:var(--grid-surface-variant)!important}.table-mode .grand-total-row.grand-total-highlighted td{background-color:var(--grid-surface-variant)!important;font-weight:800!important;font-style:normal!important;color:var(--grid-primary)!important}.table-mode .grand-total-row.grand-total-highlighted:hover,.table-mode .grand-total-row.grand-total-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;max-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),var(--board-col-max-width, 1fr)));grid-auto-rows:auto;align-items:start;gap:16px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;align-content:start;justify-content:start}.board-mode-host .board-columns-wrapper.board-columns-nowrap{grid-auto-flow:column;grid-template-columns:none;grid-template-rows:auto;grid-auto-rows:auto;grid-auto-columns:minmax(var(--board-col-min-width, 300px),var(--board-col-max-width, 1fr));overflow-x:auto;overflow-y:hidden}.board-mode-host .board-column{max-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;background-color:transparent}.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(--eru-board-count-color, var(--grid-on-surface-variant, #49454f));background:var(--eru-board-count-bg, var(--grid-surface-variant, #e7e0ec));border-radius:10px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.board-mode-host .board-column-body{flex:0 1 auto;min-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"] }]
|
|
15739
16315
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { allViewports: [{
|
|
15740
16316
|
type: ViewChildren,
|
|
15741
16317
|
args: [CdkVirtualScrollViewport]
|
|
@@ -15833,7 +16409,7 @@ class ThemeToggleComponent {
|
|
|
15833
16409
|
</button>
|
|
15834
16410
|
}
|
|
15835
16411
|
</mat-menu>
|
|
15836
|
-
`, isInline: true, styles: [".theme-toggle-button{color:var(--grid-on-surface)}.theme-toggle-button:hover{background-color:var(--grid-surface-variant)}.active{background-color:var(--grid-primary-
|
|
16412
|
+
`, isInline: true, styles: [".theme-toggle-button{color:var(--grid-on-surface)}.theme-toggle-button:hover{background-color:var(--grid-surface-variant)}.active{background-color:var(--grid-primary-container);color:var(--grid-primary-color)}.check-icon{margin-left:auto;color:var(--grid-primary-color)}mat-menu-item{display:flex;align-items:center;gap:8px}\n"], dependencies: [{ 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: MatIconModule }, { kind: "component", type: i2$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15837
16413
|
}
|
|
15838
16414
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ThemeToggleComponent, decorators: [{
|
|
15839
16415
|
type: Component,
|
|
@@ -15865,7 +16441,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
15865
16441
|
</button>
|
|
15866
16442
|
}
|
|
15867
16443
|
</mat-menu>
|
|
15868
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".theme-toggle-button{color:var(--grid-on-surface)}.theme-toggle-button:hover{background-color:var(--grid-surface-variant)}.active{background-color:var(--grid-primary-
|
|
16444
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".theme-toggle-button{color:var(--grid-on-surface)}.theme-toggle-button:hover{background-color:var(--grid-surface-variant)}.active{background-color:var(--grid-primary-container);color:var(--grid-primary-color)}.check-icon{margin-left:auto;color:var(--grid-primary-color)}mat-menu-item{display:flex;align-items:center;gap:8px}\n"] }]
|
|
15869
16445
|
}] });
|
|
15870
16446
|
|
|
15871
16447
|
/*
|
|
@@ -15877,5 +16453,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
15877
16453
|
* Generated bundle index. Do not edit.
|
|
15878
16454
|
*/
|
|
15879
16455
|
|
|
15880
|
-
export { ACTION_COLUMN_MIN_WIDTH, AttachmentComponent, CELL_RULE_OPERATORS, CheckboxComponent, ColumnConstraintsService, CompositeComponent, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, GRID_COLOR_TOKENS, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, PRESENTATION_DATATYPES, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SEEDED_FIELD_KEYS, SELF_COLOURED_DATATYPES, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, abbreviateNumber, cellRuleBarPercent, cellRuleMatches, cellRuleToCss, cellStatAlias, cellTextStyleToCss, collectColumnStatRequests, composeColorValue, evaluateRowCondition, formatCellValue, formatDateWithPattern, formatNumberValue, matchCellRule, matchColorRange, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseCellDate, parseColorValue, parseDateWithPattern, readRowValue, resolveColumnRules, resolveRowValue, resolveStatValue, statusPillColors, tagPillColors };
|
|
16456
|
+
export { ACTION_COLUMN_MIN_WIDTH, AttachmentComponent, CELL_RULE_OPERATORS, CheckboxComponent, ChipListComponent, ColumnConstraintsService, CompositeComponent, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EmailComponent, EruGridComponent, EruGridService, EruGridStore, GRID_COLOR_TOKENS, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, NumericInputDirective, PRESENTATION_DATATYPES, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SEEDED_FIELD_KEYS, SELF_COLOURED_DATATYPES, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, abbreviateNumber, abbreviationScaleFor, cellRuleBarPercent, cellRuleMatches, cellRuleToCss, cellStatAlias, cellTextStyleToCss, collectColumnStatRequests, columnCellRules, composeColorValue, evaluateRowCondition, formatCellValue, formatDateWithPattern, formatNumberValue, hasOwnCellRules, matchCellRule, matchColorRange, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseCellDate, parseColorValue, parseDateWithPattern, readRowValue, resolveColumnRules, resolveRowValue, resolveStatValue, statusPillColors, tagPillColors };
|
|
15881
16457
|
//# sourceMappingURL=eru-grid.mjs.map
|