@gp-grid/angular 0.12.0 → 0.12.1

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.
@@ -32,37 +32,37 @@ const TEMPLATE$1 = `
32
32
  </ng-container>
33
33
  } @else {
34
34
  <span class="gp-grid-header-text">{{ entry.column.headerName ?? entry.column.field }}</span>
35
- <span class="gp-grid-header-icons">
36
- @if (sortingEnabled() && entry.column.sortable !== false) {
37
- <span class="gp-grid-sort-arrows">
38
- <span class="gp-grid-sort-arrows-stack">
39
- <svg
40
- [class]="'gp-grid-sort-arrow-up' + (headerData?.sortDirection === 'asc' ? ' active' : '')"
41
- width="8" height="6" viewBox="0 0 8 6">
42
- <path d="M4 0L8 6H0L4 0Z" fill="currentColor"/>
43
- </svg>
44
- <svg
45
- [class]="'gp-grid-sort-arrow-down' + (headerData?.sortDirection === 'desc' ? ' active' : '')"
46
- width="8" height="6" viewBox="0 0 8 6">
47
- <path d="M4 6L0 0H8L4 6Z" fill="currentColor"/>
48
- </svg>
49
- </span>
50
- @if ((headerData?.sortIndex ?? 0) > 0) {
51
- <span class="gp-grid-sort-index">{{ headerData?.sortIndex }}</span>
52
- }
53
- </span>
54
- }
55
- @if (entry.column.filterable) {
56
- <span
57
- [class]="'gp-grid-filter-icon' + (headerData?.hasFilter ? ' active' : '')"
58
- (pointerdown)="onFilterPointerDown($event, entry.originalIndex)">
59
- <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
60
- <path d="M4 4h16l-6 8v5l-4 2v-7L4 4z"/>
35
+ }
36
+ <span class="gp-grid-header-icons">
37
+ @if (sortingEnabled() && entry.column.sortable !== false) {
38
+ <span class="gp-grid-sort-arrows">
39
+ <span class="gp-grid-sort-arrows-stack">
40
+ <svg
41
+ [class]="'gp-grid-sort-arrow-up' + (headerData?.sortDirection === 'asc' ? ' active' : '')"
42
+ width="8" height="6" viewBox="0 0 8 6">
43
+ <path d="M4 0L8 6H0L4 0Z" fill="currentColor"/>
44
+ </svg>
45
+ <svg
46
+ [class]="'gp-grid-sort-arrow-down' + (headerData?.sortDirection === 'desc' ? ' active' : '')"
47
+ width="8" height="6" viewBox="0 0 8 6">
48
+ <path d="M4 6L0 0H8L4 6Z" fill="currentColor"/>
61
49
  </svg>
62
50
  </span>
63
- }
64
- </span>
65
- }
51
+ @if ((headerData?.sortIndex ?? 0) > 0) {
52
+ <span class="gp-grid-sort-index">{{ headerData?.sortIndex }}</span>
53
+ }
54
+ </span>
55
+ }
56
+ @if (entry.column.filterable !== false) {
57
+ <span
58
+ [class]="'gp-grid-filter-icon' + (headerData?.hasFilter ? ' active' : '')"
59
+ (pointerdown)="onFilterPointerDown($event, entry.originalIndex)">
60
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
61
+ <path d="M4 4h16l-6 8v5l-4 2v-7L4 4z"/>
62
+ </svg>
63
+ </span>
64
+ }
65
+ </span>
66
66
  @if (entry.column.resizable !== false) {
67
67
  <div
68
68
  class="gp-grid-header-resize-handle"
@@ -122,7 +122,7 @@ class GridHeaderComponent {
122
122
  }
123
123
  headerParams(column, colIndex, headerData) {
124
124
  const sortable = this.sortingEnabled() && column.sortable !== false;
125
- const filterable = column.filterable === true;
125
+ const filterable = column.filterable !== false;
126
126
  return {
127
127
  column,
128
128
  colIndex,
@@ -144,7 +144,7 @@ class GridHeaderComponent {
144
144
  };
145
145
  }
146
146
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: GridHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
147
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: GridHeaderComponent, isStandalone: true, selector: "gp-grid-header", inputs: { headerHeight: { classPropertyName: "headerHeight", publicName: "headerHeight", isSignal: true, isRequired: true, transformFunction: null }, scrollLeft: { classPropertyName: "scrollLeft", publicName: "scrollLeft", isSignal: true, isRequired: true, transformFunction: null }, contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: true, transformFunction: null }, totalWidth: { classPropertyName: "totalWidth", publicName: "totalWidth", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, visibleColumnsWithIndices: { classPropertyName: "visibleColumnsWithIndices", publicName: "visibleColumnsWithIndices", isSignal: true, isRequired: true, transformFunction: null }, columnPositions: { classPropertyName: "columnPositions", publicName: "columnPositions", isSignal: true, isRequired: true, transformFunction: null }, columnWidths: { classPropertyName: "columnWidths", publicName: "columnWidths", isSignal: true, isRequired: true, transformFunction: null }, headers: { classPropertyName: "headers", publicName: "headers", isSignal: true, isRequired: true, transformFunction: null }, sortingEnabled: { classPropertyName: "sortingEnabled", publicName: "sortingEnabled", isSignal: true, isRequired: false, transformFunction: null }, headerRenderers: { classPropertyName: "headerRenderers", publicName: "headerRenderers", isSignal: true, isRequired: false, transformFunction: null }, globalHeaderRenderer: { classPropertyName: "globalHeaderRenderer", publicName: "globalHeaderRenderer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { headerPointerDown: "headerPointerDown", filterPointerDown: "filterPointerDown", resizePointerDown: "resizePointerDown", headerSort: "headerSort", headerFilterOpen: "headerFilterOpen" }, ngImport: i0, template: "\n <div\n class=\"gp-grid-header\"\n [class.gp-grid-header--loading]=\"isLoading()\"\n [style.height.px]=\"headerHeight()\">\n <div\n style=\"position: absolute; top: 0; left: 0;\"\n [style.transform]=\"transformStyle()\"\n [style.width.px]=\"innerWidth()\"\n [style.height.px]=\"headerHeight()\">\n @for (entry of visibleColumnsWithIndices(); track entry.originalIndex; let i = $index) {\n @let colW = columnWidths()[i] ?? 0;\n @let headerData = headers().get(entry.originalIndex);\n @let tpl = headerTemplate(entry.column);\n <div\n class=\"gp-grid-header-cell\"\n [attr.data-col-index]=\"entry.originalIndex\"\n [style.left.px]=\"columnPositions()[i]\"\n [style.width.px]=\"colW\"\n [style.height.px]=\"headerHeight()\"\n (pointerdown)=\"onHeaderPointerDown($event, entry.originalIndex, colW)\">\n @if (tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: headerParams(entry.column, entry.originalIndex, headerData) }\">\n </ng-container>\n } @else {\n <span class=\"gp-grid-header-text\">{{ entry.column.headerName ?? entry.column.field }}</span>\n <span class=\"gp-grid-header-icons\">\n @if (sortingEnabled() && entry.column.sortable !== false) {\n <span class=\"gp-grid-sort-arrows\">\n <span class=\"gp-grid-sort-arrows-stack\">\n <svg\n [class]=\"'gp-grid-sort-arrow-up' + (headerData?.sortDirection === 'asc' ? ' active' : '')\"\n width=\"8\" height=\"6\" viewBox=\"0 0 8 6\">\n <path d=\"M4 0L8 6H0L4 0Z\" fill=\"currentColor\"/>\n </svg>\n <svg\n [class]=\"'gp-grid-sort-arrow-down' + (headerData?.sortDirection === 'desc' ? ' active' : '')\"\n width=\"8\" height=\"6\" viewBox=\"0 0 8 6\">\n <path d=\"M4 6L0 0H8L4 6Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n @if ((headerData?.sortIndex ?? 0) > 0) {\n <span class=\"gp-grid-sort-index\">{{ headerData?.sortIndex }}</span>\n }\n </span>\n }\n @if (entry.column.filterable) {\n <span\n [class]=\"'gp-grid-filter-icon' + (headerData?.hasFilter ? ' active' : '')\"\n (pointerdown)=\"onFilterPointerDown($event, entry.originalIndex)\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M4 4h16l-6 8v5l-4 2v-7L4 4z\"/>\n </svg>\n </span>\n }\n </span>\n }\n @if (entry.column.resizable !== false) {\n <div\n class=\"gp-grid-header-resize-handle\"\n (pointerdown)=\"onResizePointerDown($event, entry.originalIndex, colW)\">\n </div>\n }\n </div>\n }\n </div>\n </div>\n", isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
147
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: GridHeaderComponent, isStandalone: true, selector: "gp-grid-header", inputs: { headerHeight: { classPropertyName: "headerHeight", publicName: "headerHeight", isSignal: true, isRequired: true, transformFunction: null }, scrollLeft: { classPropertyName: "scrollLeft", publicName: "scrollLeft", isSignal: true, isRequired: true, transformFunction: null }, contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: true, transformFunction: null }, totalWidth: { classPropertyName: "totalWidth", publicName: "totalWidth", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, visibleColumnsWithIndices: { classPropertyName: "visibleColumnsWithIndices", publicName: "visibleColumnsWithIndices", isSignal: true, isRequired: true, transformFunction: null }, columnPositions: { classPropertyName: "columnPositions", publicName: "columnPositions", isSignal: true, isRequired: true, transformFunction: null }, columnWidths: { classPropertyName: "columnWidths", publicName: "columnWidths", isSignal: true, isRequired: true, transformFunction: null }, headers: { classPropertyName: "headers", publicName: "headers", isSignal: true, isRequired: true, transformFunction: null }, sortingEnabled: { classPropertyName: "sortingEnabled", publicName: "sortingEnabled", isSignal: true, isRequired: false, transformFunction: null }, headerRenderers: { classPropertyName: "headerRenderers", publicName: "headerRenderers", isSignal: true, isRequired: false, transformFunction: null }, globalHeaderRenderer: { classPropertyName: "globalHeaderRenderer", publicName: "globalHeaderRenderer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { headerPointerDown: "headerPointerDown", filterPointerDown: "filterPointerDown", resizePointerDown: "resizePointerDown", headerSort: "headerSort", headerFilterOpen: "headerFilterOpen" }, ngImport: i0, template: "\n <div\n class=\"gp-grid-header\"\n [class.gp-grid-header--loading]=\"isLoading()\"\n [style.height.px]=\"headerHeight()\">\n <div\n style=\"position: absolute; top: 0; left: 0;\"\n [style.transform]=\"transformStyle()\"\n [style.width.px]=\"innerWidth()\"\n [style.height.px]=\"headerHeight()\">\n @for (entry of visibleColumnsWithIndices(); track entry.originalIndex; let i = $index) {\n @let colW = columnWidths()[i] ?? 0;\n @let headerData = headers().get(entry.originalIndex);\n @let tpl = headerTemplate(entry.column);\n <div\n class=\"gp-grid-header-cell\"\n [attr.data-col-index]=\"entry.originalIndex\"\n [style.left.px]=\"columnPositions()[i]\"\n [style.width.px]=\"colW\"\n [style.height.px]=\"headerHeight()\"\n (pointerdown)=\"onHeaderPointerDown($event, entry.originalIndex, colW)\">\n @if (tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: headerParams(entry.column, entry.originalIndex, headerData) }\">\n </ng-container>\n } @else {\n <span class=\"gp-grid-header-text\">{{ entry.column.headerName ?? entry.column.field }}</span>\n }\n <span class=\"gp-grid-header-icons\">\n @if (sortingEnabled() && entry.column.sortable !== false) {\n <span class=\"gp-grid-sort-arrows\">\n <span class=\"gp-grid-sort-arrows-stack\">\n <svg\n [class]=\"'gp-grid-sort-arrow-up' + (headerData?.sortDirection === 'asc' ? ' active' : '')\"\n width=\"8\" height=\"6\" viewBox=\"0 0 8 6\">\n <path d=\"M4 0L8 6H0L4 0Z\" fill=\"currentColor\"/>\n </svg>\n <svg\n [class]=\"'gp-grid-sort-arrow-down' + (headerData?.sortDirection === 'desc' ? ' active' : '')\"\n width=\"8\" height=\"6\" viewBox=\"0 0 8 6\">\n <path d=\"M4 6L0 0H8L4 6Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n @if ((headerData?.sortIndex ?? 0) > 0) {\n <span class=\"gp-grid-sort-index\">{{ headerData?.sortIndex }}</span>\n }\n </span>\n }\n @if (entry.column.filterable !== false) {\n <span\n [class]=\"'gp-grid-filter-icon' + (headerData?.hasFilter ? ' active' : '')\"\n (pointerdown)=\"onFilterPointerDown($event, entry.originalIndex)\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M4 4h16l-6 8v5l-4 2v-7L4 4z\"/>\n </svg>\n </span>\n }\n </span>\n @if (entry.column.resizable !== false) {\n <div\n class=\"gp-grid-header-resize-handle\"\n (pointerdown)=\"onResizePointerDown($event, entry.originalIndex, colW)\">\n </div>\n }\n </div>\n }\n </div>\n </div>\n", isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
148
148
  }
149
149
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: GridHeaderComponent, decorators: [{
150
150
  type: Component,
@@ -539,13 +539,13 @@ const FILTER_POPUP_TEMPLATE = `
539
539
  (change)="includeBlanks = $any($event.target).checked" />
540
540
  <span class="gp-grid-filter-blank">(Blanks)</span>
541
541
  </label>
542
- @for (val of filteredUniqueValues(); track val) {
542
+ @for (entry of filteredUniqueEntries(); track entry.key) {
543
543
  <label class="gp-grid-filter-option">
544
544
  <input
545
545
  type="checkbox"
546
- [checked]="selectedValues.has(val)"
547
- (change)="toggleValue(val, $any($event.target).checked)" />
548
- <span>{{ val }}</span>
546
+ [checked]="selectedValues.has(entry.key)"
547
+ (change)="toggleValue(entry.key, $any($event.target).checked)" />
548
+ <span>{{ entry.label }}</span>
549
549
  </label>
550
550
  }
551
551
  </div>
@@ -650,19 +650,19 @@ const defaultNumberCondition = () => ({
650
650
  valueTo: '',
651
651
  nextOperator: 'and',
652
652
  });
653
- const computeUniqueValues = (distinctValues) => {
653
+ const computeUniqueValues = (distinctValues, formatter) => {
654
654
  const seen = new Set();
655
655
  const result = [];
656
656
  for (const val of distinctValues) {
657
657
  if (val === null || val === undefined || val === '')
658
658
  continue;
659
- const str = String(val);
660
- if (!seen.has(str)) {
661
- seen.add(str);
662
- result.push(str);
659
+ const key = String(val);
660
+ if (!seen.has(key)) {
661
+ seen.add(key);
662
+ result.push({ key, label: formatter ? formatter(val) : key });
663
663
  }
664
664
  }
665
- return result.sort();
665
+ return result.sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: 'base' }));
666
666
  };
667
667
  const initTextState = (filter, uniqueValues) => {
668
668
  if (!filter)
@@ -833,16 +833,19 @@ class FilterPopupComponent {
833
833
  return isNumberColumn(this.column());
834
834
  }
835
835
  showValuesMode() {
836
- return this.uniqueValues().length <= MAX_CHECKBOX_VALUES;
836
+ return this.uniqueEntries().length <= MAX_CHECKBOX_VALUES;
837
+ }
838
+ uniqueEntries() {
839
+ return computeUniqueValues(this.distinctValues(), this.column().valueFormatter);
837
840
  }
838
841
  uniqueValues() {
839
- return computeUniqueValues(this.distinctValues());
842
+ return this.uniqueEntries().map(e => e.key);
840
843
  }
841
- filteredUniqueValues() {
844
+ filteredUniqueEntries() {
842
845
  const search = this.searchText.toLowerCase();
843
846
  if (!search)
844
- return this.uniqueValues();
845
- return this.uniqueValues().filter(v => v.toLowerCase().includes(search));
847
+ return this.uniqueEntries();
848
+ return this.uniqueEntries().filter(e => e.label.toLowerCase().includes(search));
846
849
  }
847
850
  toggleValue(val, checked) {
848
851
  if (checked) {
@@ -938,7 +941,7 @@ class FilterPopupComponent {
938
941
  this.updatePosition();
939
942
  };
940
943
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: FilterPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
941
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: FilterPopupComponent, isStandalone: true, selector: "gp-grid-filter-popup", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: true, transformFunction: null }, distinctValues: { classPropertyName: "distinctValues", publicName: "distinctValues", isSignal: true, isRequired: true, transformFunction: null }, currentFilter: { classPropertyName: "currentFilter", publicName: "currentFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", close: "close" }, host: { listeners: { "keydown.escape": "onEscape()" } }, viewQueries: [{ propertyName: "popupEl", first: true, predicate: ["popupEl"], descendants: true }], ngImport: i0, template: "\n<div\n #popupEl\n class=\"gp-grid-filter-popup\"\n [style.position]=\"'fixed'\"\n [style.zIndex]=\"10000\"\n [style.top.px]=\"popupTop()\"\n [style.left.px]=\"popupLeft()\"\n [style.minWidth.px]=\"popupMinWidth()\"\n [style.visibility]=\"positioned() ? 'visible' : 'hidden'\"\n (keydown.escape)=\"close.emit()\"\n (click)=\"$event.stopPropagation()\">\n\n <div class=\"gp-grid-filter-header\">\n Filter: {{ column().headerName ?? column().field }}\n </div>\n\n <div [class]=\"'gp-grid-filter-content ' + (isNumberColumn() ? 'gp-grid-filter-number' : 'gp-grid-filter-text')\">\n @if (isNumberColumn()) {\n @for (cond of numberConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setNumberNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setNumberNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onNumberOperatorChange(i, $any($event.target).value)\">\n @for (op of numberOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessNumberOp(cond.operator)) {\n <input\n type=\"number\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n @if (cond.operator === 'between') {\n <span class=\"gp-grid-filter-to\">to</span>\n <input\n type=\"number\"\n [value]=\"cond.valueTo\"\n (input)=\"cond.valueTo = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n }\n @if (numberConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeNumberCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addNumberCondition()\">\n + Add condition\n </button>\n } @else {\n @if (showValuesMode()) {\n <div class=\"gp-grid-filter-mode-toggle\">\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'values'\"\n (click)=\"filterMode = 'values'\">\n Values\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'condition'\"\n (click)=\"filterMode = 'condition'\">\n Condition\n </button>\n </div>\n }\n\n @if (filterMode === 'values' && showValuesMode()) {\n <input\n class=\"gp-grid-filter-search\"\n type=\"text\"\n [value]=\"searchText\"\n (input)=\"searchText = $any($event.target).value\"\n placeholder=\"Search...\" />\n <div class=\"gp-grid-filter-actions\">\n <button type=\"button\" (click)=\"selectAll()\">Select All</button>\n <button type=\"button\" (click)=\"deselectAll()\">Deselect All</button>\n </div>\n <div class=\"gp-grid-filter-list\">\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"includeBlanks\"\n (change)=\"includeBlanks = $any($event.target).checked\" />\n <span class=\"gp-grid-filter-blank\">(Blanks)</span>\n </label>\n @for (val of filteredUniqueValues(); track val) {\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedValues.has(val)\"\n (change)=\"toggleValue(val, $any($event.target).checked)\" />\n <span>{{ val }}</span>\n </label>\n }\n </div>\n }\n\n @if (filterMode === 'condition') {\n @for (cond of textConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setTextNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setTextNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onTextOperatorChange(i, $any($event.target).value)\">\n @for (op of textOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessTextOp(cond.operator)) {\n <input\n class=\"gp-grid-filter-text-input\"\n type=\"text\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n @if (textConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeTextCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addTextCondition()\">\n + Add condition\n </button>\n }\n }\n\n <div class=\"gp-grid-filter-buttons\">\n <button type=\"button\" class=\"gp-grid-filter-btn-clear\" (click)=\"handleClear()\">\n Clear\n </button>\n <button type=\"button\" class=\"gp-grid-filter-btn-apply\" (click)=\"handleApply()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n", isInline: true, changeDetection: i0.ChangeDetectionStrategy.Eager });
944
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: FilterPopupComponent, isStandalone: true, selector: "gp-grid-filter-popup", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: true, transformFunction: null }, distinctValues: { classPropertyName: "distinctValues", publicName: "distinctValues", isSignal: true, isRequired: true, transformFunction: null }, currentFilter: { classPropertyName: "currentFilter", publicName: "currentFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply", close: "close" }, host: { listeners: { "keydown.escape": "onEscape()" } }, viewQueries: [{ propertyName: "popupEl", first: true, predicate: ["popupEl"], descendants: true }], ngImport: i0, template: "\n<div\n #popupEl\n class=\"gp-grid-filter-popup\"\n [style.position]=\"'fixed'\"\n [style.zIndex]=\"10000\"\n [style.top.px]=\"popupTop()\"\n [style.left.px]=\"popupLeft()\"\n [style.minWidth.px]=\"popupMinWidth()\"\n [style.visibility]=\"positioned() ? 'visible' : 'hidden'\"\n (keydown.escape)=\"close.emit()\"\n (click)=\"$event.stopPropagation()\">\n\n <div class=\"gp-grid-filter-header\">\n Filter: {{ column().headerName ?? column().field }}\n </div>\n\n <div [class]=\"'gp-grid-filter-content ' + (isNumberColumn() ? 'gp-grid-filter-number' : 'gp-grid-filter-text')\">\n @if (isNumberColumn()) {\n @for (cond of numberConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setNumberNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"numberConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setNumberNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onNumberOperatorChange(i, $any($event.target).value)\">\n @for (op of numberOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessNumberOp(cond.operator)) {\n <input\n type=\"number\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n @if (cond.operator === 'between') {\n <span class=\"gp-grid-filter-to\">to</span>\n <input\n type=\"number\"\n [value]=\"cond.valueTo\"\n (input)=\"cond.valueTo = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n }\n @if (numberConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeNumberCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addNumberCondition()\">\n + Add condition\n </button>\n } @else {\n @if (showValuesMode()) {\n <div class=\"gp-grid-filter-mode-toggle\">\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'values'\"\n (click)=\"filterMode = 'values'\">\n Values\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterMode === 'condition'\"\n (click)=\"filterMode = 'condition'\">\n Condition\n </button>\n </div>\n }\n\n @if (filterMode === 'values' && showValuesMode()) {\n <input\n class=\"gp-grid-filter-search\"\n type=\"text\"\n [value]=\"searchText\"\n (input)=\"searchText = $any($event.target).value\"\n placeholder=\"Search...\" />\n <div class=\"gp-grid-filter-actions\">\n <button type=\"button\" (click)=\"selectAll()\">Select All</button>\n <button type=\"button\" (click)=\"deselectAll()\">Deselect All</button>\n </div>\n <div class=\"gp-grid-filter-list\">\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"includeBlanks\"\n (change)=\"includeBlanks = $any($event.target).checked\" />\n <span class=\"gp-grid-filter-blank\">(Blanks)</span>\n </label>\n @for (entry of filteredUniqueEntries(); track entry.key) {\n <label class=\"gp-grid-filter-option\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedValues.has(entry.key)\"\n (change)=\"toggleValue(entry.key, $any($event.target).checked)\" />\n <span>{{ entry.label }}</span>\n </label>\n }\n </div>\n }\n\n @if (filterMode === 'condition') {\n @for (cond of textConditions; track $index; let i = $index) {\n <div class=\"gp-grid-filter-condition\">\n @if (i > 0) {\n <div class=\"gp-grid-filter-combination\">\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'and'\"\n (click)=\"setTextNextOp(i - 1, 'and')\">\n AND\n </button>\n <button\n type=\"button\"\n [class.active]=\"textConditions[i - 1]?.nextOperator === 'or'\"\n (click)=\"setTextNextOp(i - 1, 'or')\">\n OR\n </button>\n </div>\n }\n <div class=\"gp-grid-filter-row\">\n <select\n [value]=\"cond.operator\"\n (change)=\"onTextOperatorChange(i, $any($event.target).value)\">\n @for (op of textOperators; track op.value) {\n <option [value]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n @if (!isValueLessTextOp(cond.operator)) {\n <input\n class=\"gp-grid-filter-text-input\"\n type=\"text\"\n [value]=\"cond.value\"\n (input)=\"cond.value = $any($event.target).value\"\n placeholder=\"Value\" />\n }\n @if (textConditions.length > 1) {\n <button\n type=\"button\"\n class=\"gp-grid-filter-remove\"\n (click)=\"removeTextCondition(i)\">\u00D7</button>\n }\n </div>\n </div>\n }\n <button type=\"button\" class=\"gp-grid-filter-add\" (click)=\"addTextCondition()\">\n + Add condition\n </button>\n }\n }\n\n <div class=\"gp-grid-filter-buttons\">\n <button type=\"button\" class=\"gp-grid-filter-btn-clear\" (click)=\"handleClear()\">\n Clear\n </button>\n <button type=\"button\" class=\"gp-grid-filter-btn-apply\" (click)=\"handleApply()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n", isInline: true, changeDetection: i0.ChangeDetectionStrategy.Eager });
942
945
  }
943
946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: FilterPopupComponent, decorators: [{
944
947
  type: Component,
@@ -1428,10 +1431,10 @@ class GpGridComponent {
1428
1431
  getHeaderHeight: () => this.headerHeight(),
1429
1432
  });
1430
1433
  constructor() {
1431
- effect(() => this.bindings.applyPendingScroll());
1434
+ effect(() => this.bindings.applyPendingScroll(), { allowSignalWrites: true });
1432
1435
  effect(() => this.bindings.syncHighlighting(this.highlighting()));
1433
- effect(() => this.bindings.syncColumns(this.columns()));
1434
- effect(() => this.bindings.syncRows(this.rows(), this.dataSource()));
1436
+ effect(() => this.bindings.syncColumns(this.columns()), { allowSignalWrites: true });
1437
+ effect(() => this.bindings.syncRows(this.rows(), this.dataSource()), { allowSignalWrites: true });
1435
1438
  }
1436
1439
  ngOnInit() {
1437
1440
  const core = buildGridCore({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gp-grid/angular",
3
3
  "description": "A high-performance Angular data grid component with virtual scrolling, cell selection, sorting, filtering, and Excel-like editing",
4
- "version": "0.12.0",
4
+ "version": "0.12.1",
5
5
  "license": "Apache-2.0",
6
6
  "module": "fesm2022/gp-grid-angular.mjs",
7
7
  "typings": "types/gp-grid-angular.d.ts",
@@ -51,7 +51,7 @@
51
51
  "@angular/core": ">=18.0.0"
52
52
  },
53
53
  "dependencies": {
54
- "@gp-grid/core": "0.12.0",
54
+ "@gp-grid/core": "0.12.1",
55
55
  "tslib": "^2.8.1"
56
56
  },
57
57
  "sideEffects": false,
@@ -156,6 +156,10 @@ interface NumberConditionState {
156
156
  nextOperator: 'and' | 'or';
157
157
  }
158
158
  type FilterMode = 'values' | 'condition';
159
+ interface FilterEntry {
160
+ key: string;
161
+ label: string;
162
+ }
159
163
 
160
164
  declare class FilterPopupComponent implements AfterViewInit, OnDestroy {
161
165
  popupEl: ElementRef<HTMLDivElement>;
@@ -195,8 +199,9 @@ declare class FilterPopupComponent implements AfterViewInit, OnDestroy {
195
199
  onEscape(): void;
196
200
  isNumberColumn(): boolean;
197
201
  showValuesMode(): boolean;
202
+ uniqueEntries(): FilterEntry[];
198
203
  uniqueValues(): string[];
199
- filteredUniqueValues(): string[];
204
+ filteredUniqueEntries(): FilterEntry[];
200
205
  toggleValue(val: string, checked: boolean): void;
201
206
  selectAll(): void;
202
207
  deselectAll(): void;