eru-grid 0.0.30 → 0.0.32
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 +100 -18
- package/fesm2022/eru-grid.mjs.map +1 -1
- package/package.json +1 -1
- package/types/eru-grid.d.ts +16 -2
package/fesm2022/eru-grid.mjs
CHANGED
|
@@ -2556,6 +2556,11 @@ class EruGridStore {
|
|
|
2556
2556
|
// Consumers (e.g. eru-studio) listen to persist the design back into their config.
|
|
2557
2557
|
_columnDesignUpdate = signal(null, ...(ngDevMode ? [{ debugName: "_columnDesignUpdate" }] : []));
|
|
2558
2558
|
columnDesignUpdate = this._columnDesignUpdate.asReadonly();
|
|
2559
|
+
// Emits ONLY the changed attribute(s) for a single column on each design edit.
|
|
2560
|
+
// Lets consumers store per-column overrides (deltas) rather than a full snapshot,
|
|
2561
|
+
// so non-overridden attributes keep flowing from their source (e.g. entity fields).
|
|
2562
|
+
_columnMetaPatch = signal(null, ...(ngDevMode ? [{ debugName: "_columnMetaPatch" }] : []));
|
|
2563
|
+
columnMetaPatch = this._columnMetaPatch.asReadonly();
|
|
2559
2564
|
// Computed signals
|
|
2560
2565
|
columns = this._columns.asReadonly();
|
|
2561
2566
|
groups = this._groups.asReadonly();
|
|
@@ -2799,6 +2804,7 @@ class EruGridStore {
|
|
|
2799
2804
|
const columns = this.columns().map(col => col.name === columnName ? { ...col, ...patch } : col);
|
|
2800
2805
|
this.setColumns(columns);
|
|
2801
2806
|
this._columnDesignUpdate.set(this.columns());
|
|
2807
|
+
this._columnMetaPatch.set({ name: columnName, patch });
|
|
2802
2808
|
}
|
|
2803
2809
|
selectDesignColumn(columnName) {
|
|
2804
2810
|
this._selectedDesignColumn.set(columnName);
|
|
@@ -3080,7 +3086,6 @@ class EruGridStore {
|
|
|
3080
3086
|
transformToPivot() {
|
|
3081
3087
|
// Prefer explicit pivot configuration signal, fall back to configuration().pivot
|
|
3082
3088
|
const pivotConfig = this.pivotConfiguration() || this.configuration().pivot;
|
|
3083
|
-
console.log('transformToPivot', pivotConfig, this.configuration().data, this.pivotResult());
|
|
3084
3089
|
// Prefer explicit source data, fall back to all loaded group rows
|
|
3085
3090
|
let sourceData = this.configuration().data || [];
|
|
3086
3091
|
if (sourceData.length === 0) {
|
|
@@ -3290,7 +3295,6 @@ class EruGridStore {
|
|
|
3290
3295
|
this.updateGroupLoadingState(groupId, false);
|
|
3291
3296
|
}
|
|
3292
3297
|
resetGroupRows() {
|
|
3293
|
-
console.log('resetGroupRows');
|
|
3294
3298
|
this._groupRows.set(new Map());
|
|
3295
3299
|
}
|
|
3296
3300
|
/**
|
|
@@ -3644,7 +3648,6 @@ class EruGridService {
|
|
|
3644
3648
|
* Switch grid to table mode or pivot mode
|
|
3645
3649
|
*/
|
|
3646
3650
|
set_grid_mode(mode) {
|
|
3647
|
-
console.log('set_grid_mode called for ', mode);
|
|
3648
3651
|
if (mode === 'pivot') {
|
|
3649
3652
|
this.eruGridStore.setGridMode('pivot');
|
|
3650
3653
|
}
|
|
@@ -6454,6 +6457,17 @@ class ProgressComponent {
|
|
|
6454
6457
|
// Clamp between 0 and 100
|
|
6455
6458
|
return Math.max(0, Math.min(100, numVal));
|
|
6456
6459
|
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
|
|
6460
|
+
// Bar colour from the column's configured value ranges (e.g. 0-20 green).
|
|
6461
|
+
// Applies in both editable and view/disabled modes. Null when no range
|
|
6462
|
+
// matches, so the bar falls back to the default theme colour.
|
|
6463
|
+
barColor = computed(() => {
|
|
6464
|
+
const ranges = this.config()?.color_ranges;
|
|
6465
|
+
if (!Array.isArray(ranges) || ranges.length === 0)
|
|
6466
|
+
return null;
|
|
6467
|
+
const v = this.displayValue();
|
|
6468
|
+
const match = ranges.find((r) => v >= Number(r?.from) && v <= Number(r?.to));
|
|
6469
|
+
return match?.color || null;
|
|
6470
|
+
}, ...(ngDevMode ? [{ debugName: "barColor" }] : []));
|
|
6457
6471
|
constructor() {
|
|
6458
6472
|
// Sync signal-based input with internal state
|
|
6459
6473
|
effect(() => {
|
|
@@ -6504,11 +6518,11 @@ class ProgressComponent {
|
|
|
6504
6518
|
return cfg[property];
|
|
6505
6519
|
}
|
|
6506
6520
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6507
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: ProgressComponent, isStandalone: true, selector: "eru-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, ngImport: i0, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"
|
|
6521
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: ProgressComponent, isStandalone: true, selector: "eru-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null }, columnWidth: { classPropertyName: "columnWidth", publicName: "columnWidth", isSignal: true, isRequired: false, transformFunction: null }, fieldSize: { classPropertyName: "fieldSize", publicName: "fieldSize", isSignal: true, isRequired: false, transformFunction: null }, eruGridStore: { classPropertyName: "eruGridStore", publicName: "eruGridStore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus", drilldownClick: "drilldownClick", editModeChange: "editModeChange" }, ngImport: i0, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"\n [style.--mat-slider-active-track-color]=\"barColor()\"\n [style.--mat-slider-disabled-active-track-color]=\"barColor()\"\n [style.--mat-slider-with-tick-marks-active-container-color]=\"barColor()\"\n [style.--mat-slider-handle-color]=\"barColor()\"\n [style.--mat-slider-focus-handle-color]=\"barColor()\"\n [style.--mat-slider-hover-handle-color]=\"barColor()\"\n [style.--mat-slider-disabled-handle-color]=\"barColor()\"\n [style.--mat-slider-ripple-color]=\"barColor()\"\n (dblclick)=\"onActivate(); $event.stopPropagation()\">\n <span class=\"progress-value\" [style.color]=\"barColor()\">{{displayValue()}}%</span>\n <mat-slider \n class=\"progress-slider\" \n [min]=\"0\" \n [max]=\"100\" \n [step]=\"1\" \n [discrete]=\"true\" \n [showTickMarks]=\"true\"\n [disabled]=\"!isActive()\">\n <input \n matSliderThumb \n [ngModel]=\"displayValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n (blur)=\"onBlur()\"\n [disabled]=\"!isActive()\">\n </mat-slider>\n</div>\n\n", styles: [":root{--mat-slider-with-overlap-handle-outline-color: var(--grid-primary, #6750a4) !important;--mat-slider-with-tick-marks-active-container-color: var(--grid-primary, #6750a4) !important;--mat-slider-handle-height: 12px;--mat-slider-handle-width: 12px;--mat-slider-disabled-active-track-color: var(--grid-primary, #6750a4);--mat-slider-active-track-color: var(--grid-primary, #6750a4)}:host{display:block;height:100%;width:100%}.progress-bar-container{width:100%;height:100%;min-height:30px;position:relative;background:#e0e0e0;border-radius:4px;cursor:pointer}.progress-bar{height:100%;background:var(--grid-primary, #6750a4);transition:width .3s;border-radius:4px}.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);pointer-events:none;z-index:1}.drillable-value{color:inherit;text-decoration:underline;cursor:pointer;pointer-events:auto}.drillable-value:hover{opacity:.8}.progress-input-container{display:flex;flex-direction:row-reverse;align-items:center;padding:4px;width:100%;box-sizing:border-box;overflow:visible;max-height:30px}.progress-input-container.disabled{cursor:pointer}.progress-input-container.disabled .progress-slider{pointer-events:none}.progress-value{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);flex:0 0 auto;min-width:35px;text-align:right;white-space:nowrap;flex-shrink:0}.progress-slider{flex:1 1 0%;min-width:0!important;max-width:100%!important;overflow:visible}.progress-slider .mdc-slider__track{left:0!important}.progress-slider .mdc-slider{padding-left:0!important;margin-left:0!important}.progress-slider .mdc-slider__value-indicator,.progress-slider .mdc-slider__value-indicator-container{display:none!important;visibility:hidden!important}.progress-slider .mdc-slider__thumb:before,.progress-slider .mdc-slider__thumb:after{display:none!important;content:none!important}.progress-slider .mdc-slider__thumb-knob:before,.progress-slider .mdc-slider__thumb-knob:after{display:none!important;content:none!important}.progress-slider [class*=value-indicator],.progress-slider [class*=tooltip]{display:none!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i2$3.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i2$3.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
6508
6522
|
}
|
|
6509
6523
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ProgressComponent, decorators: [{
|
|
6510
6524
|
type: Component,
|
|
6511
|
-
args: [{ selector: 'eru-progress', standalone: true, imports: [FormsModule, MatSliderModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"
|
|
6525
|
+
args: [{ selector: 'eru-progress', standalone: true, imports: [FormsModule, MatSliderModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"progress-input-container\" [class.disabled]=\"!isActive()\"\n [style.--mat-slider-active-track-color]=\"barColor()\"\n [style.--mat-slider-disabled-active-track-color]=\"barColor()\"\n [style.--mat-slider-with-tick-marks-active-container-color]=\"barColor()\"\n [style.--mat-slider-handle-color]=\"barColor()\"\n [style.--mat-slider-focus-handle-color]=\"barColor()\"\n [style.--mat-slider-hover-handle-color]=\"barColor()\"\n [style.--mat-slider-disabled-handle-color]=\"barColor()\"\n [style.--mat-slider-ripple-color]=\"barColor()\"\n (dblclick)=\"onActivate(); $event.stopPropagation()\">\n <span class=\"progress-value\" [style.color]=\"barColor()\">{{displayValue()}}%</span>\n <mat-slider \n class=\"progress-slider\" \n [min]=\"0\" \n [max]=\"100\" \n [step]=\"1\" \n [discrete]=\"true\" \n [showTickMarks]=\"true\"\n [disabled]=\"!isActive()\">\n <input \n matSliderThumb \n [ngModel]=\"displayValue()\" \n (ngModelChange)=\"onValueChange($event)\" \n (blur)=\"onBlur()\"\n [disabled]=\"!isActive()\">\n </mat-slider>\n</div>\n\n", styles: [":root{--mat-slider-with-overlap-handle-outline-color: var(--grid-primary, #6750a4) !important;--mat-slider-with-tick-marks-active-container-color: var(--grid-primary, #6750a4) !important;--mat-slider-handle-height: 12px;--mat-slider-handle-width: 12px;--mat-slider-disabled-active-track-color: var(--grid-primary, #6750a4);--mat-slider-active-track-color: var(--grid-primary, #6750a4)}:host{display:block;height:100%;width:100%}.progress-bar-container{width:100%;height:100%;min-height:30px;position:relative;background:#e0e0e0;border-radius:4px;cursor:pointer}.progress-bar{height:100%;background:var(--grid-primary, #6750a4);transition:width .3s;border-radius:4px}.progress-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);pointer-events:none;z-index:1}.drillable-value{color:inherit;text-decoration:underline;cursor:pointer;pointer-events:auto}.drillable-value:hover{opacity:.8}.progress-input-container{display:flex;flex-direction:row-reverse;align-items:center;padding:4px;width:100%;box-sizing:border-box;overflow:visible;max-height:30px}.progress-input-container.disabled{cursor:pointer}.progress-input-container.disabled .progress-slider{pointer-events:none}.progress-value{font-size:var(--grid-font-size-body, 12px);font-weight:500;color:var(--grid-on-surface, #1d1b20);flex:0 0 auto;min-width:35px;text-align:right;white-space:nowrap;flex-shrink:0}.progress-slider{flex:1 1 0%;min-width:0!important;max-width:100%!important;overflow:visible}.progress-slider .mdc-slider__track{left:0!important}.progress-slider .mdc-slider{padding-left:0!important;margin-left:0!important}.progress-slider .mdc-slider__value-indicator,.progress-slider .mdc-slider__value-indicator-container{display:none!important;visibility:hidden!important}.progress-slider .mdc-slider__thumb:before,.progress-slider .mdc-slider__thumb:after{display:none!important;content:none!important}.progress-slider .mdc-slider__thumb-knob:before,.progress-slider .mdc-slider__thumb-knob:after{display:none!important;content:none!important}.progress-slider [class*=value-indicator],.progress-slider [class*=tooltip]{display:none!important}\n"] }]
|
|
6512
6526
|
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], isActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActive", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], columnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidth", required: false }] }], fieldSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSize", required: false }] }], eruGridStore: [{ type: i0.Input, args: [{ isSignal: true, alias: "eruGridStore", required: false }] }], valueChange: [{
|
|
6513
6527
|
type: Output
|
|
6514
6528
|
}], blur: [{
|
|
@@ -7054,8 +7068,6 @@ class StatusComponent {
|
|
|
7054
7068
|
openOptions = filter(openOptions);
|
|
7055
7069
|
closeOptions = filter(closeOptions);
|
|
7056
7070
|
}
|
|
7057
|
-
console.log('openOptions', openOptions);
|
|
7058
|
-
console.log('closeOptions', closeOptions);
|
|
7059
7071
|
return { open: openOptions, close: closeOptions };
|
|
7060
7072
|
}, ...(ngDevMode ? [{ debugName: "groupedOptions" }] : []));
|
|
7061
7073
|
filteredOptions = computed(() => {
|
|
@@ -8310,7 +8322,6 @@ class AttachmentComponent {
|
|
|
8310
8322
|
this.pendingViewRequests.forEach((requestKey, fileName) => {
|
|
8311
8323
|
if (dynamicDataMap.has(requestKey)) {
|
|
8312
8324
|
const data = dynamicDataMap.get(requestKey);
|
|
8313
|
-
console.log('[eru-attachment] view-file response received', { requestKey, data });
|
|
8314
8325
|
if (data && data.file && data.file_type) {
|
|
8315
8326
|
this.openBase64InNewTab(data.file, data.file_type, fileName);
|
|
8316
8327
|
}
|
|
@@ -8485,7 +8496,6 @@ class AttachmentComponent {
|
|
|
8485
8496
|
const requestKey = `ds_view_file_grid:${file}`;
|
|
8486
8497
|
this.pendingViewRequests.set(file, requestKey);
|
|
8487
8498
|
store.setDynamicDataRequest(requestKey);
|
|
8488
|
-
console.log('[eru-attachment] view-file request fired', { requestKey });
|
|
8489
8499
|
}
|
|
8490
8500
|
openBase64InNewTab(base64, fileType, fileName) {
|
|
8491
8501
|
try {
|
|
@@ -9749,7 +9759,6 @@ class DataCellComponent {
|
|
|
9749
9759
|
// })
|
|
9750
9760
|
}
|
|
9751
9761
|
fileInputChange(e) {
|
|
9752
|
-
debugger;
|
|
9753
9762
|
Array.from(e.target.files).forEach((file) => {
|
|
9754
9763
|
this.processFile(file);
|
|
9755
9764
|
});
|
|
@@ -10406,16 +10415,20 @@ const OPTION_FIELDS = [
|
|
|
10406
10415
|
showWhen: f => f.option_type === 'API',
|
|
10407
10416
|
},
|
|
10408
10417
|
];
|
|
10418
|
+
// Priority and tag options are always static and carry a colour — no option source.
|
|
10419
|
+
const STATIC_COLOR_OPTION_FIELDS = [
|
|
10420
|
+
{ key: 'options', label: 'Options', control: 'color_list', defaultColor: '#9CA3AF' },
|
|
10421
|
+
];
|
|
10409
10422
|
const DATATYPE_FIELDS = {
|
|
10410
10423
|
number: NUMBER_FIELDS,
|
|
10411
10424
|
currency: NUMBER_FIELDS,
|
|
10412
10425
|
dropdown_single_select: OPTION_FIELDS,
|
|
10413
10426
|
dropdown_multi_select: OPTION_FIELDS,
|
|
10414
|
-
tag:
|
|
10415
|
-
priority:
|
|
10427
|
+
tag: STATIC_COLOR_OPTION_FIELDS,
|
|
10428
|
+
priority: STATIC_COLOR_OPTION_FIELDS,
|
|
10416
10429
|
status: [
|
|
10417
|
-
{ key: 'open_status', label: 'Open statuses
|
|
10418
|
-
{ key: 'close_status', label: 'Close statuses
|
|
10430
|
+
{ key: 'open_status', label: 'Open statuses', control: 'color_list', defaultColor: '#22C55E' },
|
|
10431
|
+
{ key: 'close_status', label: 'Close statuses', control: 'color_list', defaultColor: '#EF4444' },
|
|
10419
10432
|
],
|
|
10420
10433
|
rating: [
|
|
10421
10434
|
{ key: 'start_value', label: 'Scale start', control: 'number' },
|
|
@@ -10429,6 +10442,9 @@ const DATATYPE_FIELDS = {
|
|
|
10429
10442
|
],
|
|
10430
10443
|
phone: [{ key: 'default_country', label: 'Default country (ISO)', control: 'text' }],
|
|
10431
10444
|
textbox: [{ key: 'data_length', label: 'Max length', control: 'number' }],
|
|
10445
|
+
progress: [
|
|
10446
|
+
{ key: 'color_ranges', label: 'Colour ranges (by %)', control: 'range_color_list' },
|
|
10447
|
+
],
|
|
10432
10448
|
};
|
|
10433
10449
|
function getMetaFields(field) {
|
|
10434
10450
|
if (!field)
|
|
@@ -10480,11 +10496,64 @@ class ColumnDesignPanelComponent {
|
|
|
10480
10496
|
const value = this.OBJECT_LIST_KEYS.has(key) ? lines.map(name => ({ name, value: name })) : lines;
|
|
10481
10497
|
this.patch(key, value);
|
|
10482
10498
|
}
|
|
10499
|
+
// ── Colour-list control (status / priority options with a colour each) ──
|
|
10500
|
+
colorListItems(key) {
|
|
10501
|
+
const arr = this.field()?.[key];
|
|
10502
|
+
if (!Array.isArray(arr))
|
|
10503
|
+
return [];
|
|
10504
|
+
return arr.map(it => typeof it === 'string'
|
|
10505
|
+
? { name: it, color: '' }
|
|
10506
|
+
: { name: it?.name ?? it?.value ?? it?.label ?? '', color: it?.color ?? '' });
|
|
10507
|
+
}
|
|
10508
|
+
addColorListItem(key, name, defaultColor) {
|
|
10509
|
+
const trimmed = (name || '').trim();
|
|
10510
|
+
if (!trimmed)
|
|
10511
|
+
return;
|
|
10512
|
+
const items = this.colorListItems(key);
|
|
10513
|
+
this.patch(key, [...items, { name: trimmed, color: defaultColor || '#9CA3AF' }]);
|
|
10514
|
+
}
|
|
10515
|
+
removeColorListItem(key, index) {
|
|
10516
|
+
const items = this.colorListItems(key).filter((_, i) => i !== index);
|
|
10517
|
+
this.patch(key, items);
|
|
10518
|
+
}
|
|
10519
|
+
onColorListColorChange(key, index, color) {
|
|
10520
|
+
const items = this.colorListItems(key).map((it, i) => i === index ? { ...it, color } : it);
|
|
10521
|
+
this.patch(key, items);
|
|
10522
|
+
}
|
|
10523
|
+
// ── Range-colour-list control (value ranges -> colour, e.g. progress) ──
|
|
10524
|
+
rangeListItems(key) {
|
|
10525
|
+
const arr = this.field()?.[key];
|
|
10526
|
+
if (!Array.isArray(arr))
|
|
10527
|
+
return [];
|
|
10528
|
+
return arr.map(it => ({
|
|
10529
|
+
from: Number(it?.from) || 0,
|
|
10530
|
+
to: Number(it?.to) || 0,
|
|
10531
|
+
color: it?.color ?? '#22C55E',
|
|
10532
|
+
}));
|
|
10533
|
+
}
|
|
10534
|
+
addRangeItem(key) {
|
|
10535
|
+
const items = this.rangeListItems(key);
|
|
10536
|
+
const lastTo = items.length ? items[items.length - 1].to : -1;
|
|
10537
|
+
const from = Math.min(100, lastTo + 1);
|
|
10538
|
+
this.patch(key, [...items, { from, to: 100, color: '#22C55E' }]);
|
|
10539
|
+
}
|
|
10540
|
+
removeRangeItem(key, index) {
|
|
10541
|
+
this.patch(key, this.rangeListItems(key).filter((_, i) => i !== index));
|
|
10542
|
+
}
|
|
10543
|
+
onRangeChange(key, index, prop, value) {
|
|
10544
|
+
const num = value === '' ? 0 : Number(value);
|
|
10545
|
+
const items = this.rangeListItems(key).map((it, i) => i === index ? { ...it, [prop]: num } : it);
|
|
10546
|
+
this.patch(key, items);
|
|
10547
|
+
}
|
|
10548
|
+
onRangeColorChange(key, index, color) {
|
|
10549
|
+
const items = this.rangeListItems(key).map((it, i) => i === index ? { ...it, color } : it);
|
|
10550
|
+
this.patch(key, items);
|
|
10551
|
+
}
|
|
10483
10552
|
close() {
|
|
10484
10553
|
this.gridStore.selectDesignColumn(null);
|
|
10485
10554
|
}
|
|
10486
10555
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ColumnDesignPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10487
|
-
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 @for (def of metaFields(); track def.key) {\n <div class=\"design-field\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"!!getValue(def.key)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\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 }\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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
10556
|
+
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 @for (def of metaFields(); track def.key) {\n <div class=\"design-field\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"!!getValue(def.key)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\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)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
10488
10557
|
}
|
|
10489
10558
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ColumnDesignPanelComponent, decorators: [{
|
|
10490
10559
|
type: Component,
|
|
@@ -10497,7 +10566,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
10497
10566
|
MatCheckboxModule,
|
|
10498
10567
|
MatIconModule,
|
|
10499
10568
|
MatButtonModule,
|
|
10500
|
-
], 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 @for (def of metaFields(); track def.key) {\n <div class=\"design-field\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"!!getValue(def.key)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\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 }\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}\n"] }]
|
|
10569
|
+
], 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 @for (def of metaFields(); track def.key) {\n <div class=\"design-field\">\n @switch (def.control) {\n\n @case ('text') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onText(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <input matInput type=\"number\" [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onNumber(def.key, $event)\" />\n </mat-form-field>\n }\n\n @case ('select') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>{{ def.label }}</mat-label>\n <mat-select [ngModel]=\"getValue(def.key)\" (ngModelChange)=\"onSelect(def.key, $event)\">\n @for (opt of def.options; track opt.value) {\n <mat-option [value]=\"opt.value\">{{ opt.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n\n @case ('checkbox') {\n <mat-checkbox [checked]=\"!!getValue(def.key)\" (change)=\"onCheckbox(def.key, $event.checked)\">\n {{ def.label }}\n </mat-checkbox>\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)}\n"] }]
|
|
10501
10570
|
}] });
|
|
10502
10571
|
|
|
10503
10572
|
/**
|
|
@@ -10629,6 +10698,14 @@ class EruGridComponent {
|
|
|
10629
10698
|
if (rowCount === 0) {
|
|
10630
10699
|
height = Math.min(minHeight / 2, 200);
|
|
10631
10700
|
}
|
|
10701
|
+
else if (groups.length === 1) {
|
|
10702
|
+
// Single (ungrouped) group — the common table case. Fill the grid's
|
|
10703
|
+
// available height so the table occupies the configured gridHeight and
|
|
10704
|
+
// only scrolls once the rows exceed it. Sizing to the estimated content
|
|
10705
|
+
// height instead (rowCount * ROW_HEIGHT) under-counts taller rows
|
|
10706
|
+
// (progress/status/chips), producing a scrollbar even for a few rows.
|
|
10707
|
+
height = availableViewHeight;
|
|
10708
|
+
}
|
|
10632
10709
|
else if (rowCount < 50 && rowsHeight < availableViewHeight) {
|
|
10633
10710
|
// Add scrollbar buffer so the CDK viewport has enough room to render all rows even
|
|
10634
10711
|
// when a horizontal scrollbar is present (scrollbar ~17px reduces usable height).
|
|
@@ -10883,6 +10960,11 @@ class EruGridComponent {
|
|
|
10883
10960
|
} */
|
|
10884
10961
|
ROWS_PER_PAGE = 50;
|
|
10885
10962
|
SCROLL_THRESHOLD = 10;
|
|
10963
|
+
/** Rows per lazy page: grid config `pageSize` if set, else ROWS_PER_PAGE. */
|
|
10964
|
+
get rowsPerPage() {
|
|
10965
|
+
const size = this.gridStore.configuration()?.pageSize;
|
|
10966
|
+
return typeof size === 'number' && size > 0 ? size : this.ROWS_PER_PAGE;
|
|
10967
|
+
}
|
|
10886
10968
|
lastLoadedGroupIds = new Set();
|
|
10887
10969
|
requestedGroupIds = new Set(); // Track which groups have been requested for data
|
|
10888
10970
|
columnsInitialized = signal(false, ...(ngDevMode ? [{ debugName: "columnsInitialized" }] : []));
|
|
@@ -11398,7 +11480,7 @@ class EruGridComponent {
|
|
|
11398
11480
|
groupId: group.id,
|
|
11399
11481
|
groupKey: group.key,
|
|
11400
11482
|
page: group.currentPage || 0,
|
|
11401
|
-
pageSize: group.totalRowCount < this.
|
|
11483
|
+
pageSize: group.totalRowCount < this.rowsPerPage ? group.totalRowCount : this.rowsPerPage,
|
|
11402
11484
|
timestamp: Date.now(),
|
|
11403
11485
|
groupTitle: group.title
|
|
11404
11486
|
};
|
|
@@ -12217,7 +12299,7 @@ class EruGridComponent {
|
|
|
12217
12299
|
groupId: group.id,
|
|
12218
12300
|
groupKey: group.key,
|
|
12219
12301
|
page: group.currentPage || 0,
|
|
12220
|
-
pageSize: this.
|
|
12302
|
+
pageSize: this.rowsPerPage,
|
|
12221
12303
|
timestamp: Date.now(),
|
|
12222
12304
|
groupTitle: group.title,
|
|
12223
12305
|
};
|