@maro-tech/filters 0.1.2 → 0.1.4

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.
@@ -1,12 +1,13 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { EventEmitter, Output, Input, ViewChildren, Component } from '@angular/core';
3
+ import { EventEmitter, signal, afterNextRender, ElementRef, Output, Input, ViewChildren, ViewChild, Component } from '@angular/core';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { FormsModule } from '@angular/forms';
6
6
  import { LucideSlidersHorizontal, LucideX, LucideDynamicIcon } from '@lucide/angular';
7
7
  import { InputComponent, InputSelectComponent } from '@maro-tech/form';
8
8
 
9
9
  class FilterPanelComponent {
10
+ filterRow;
10
11
  selectInputs;
11
12
  config;
12
13
  value = {};
@@ -16,12 +17,39 @@ class FilterPanelComponent {
16
17
  draft = {};
17
18
  advancedOpen = false;
18
19
  searchTimer;
20
+ resizeObserver;
21
+ measureFrame;
22
+ lastRowWidth;
23
+ visibleQuickCount = signal(0, ...(ngDevMode ? [{ debugName: "visibleQuickCount" }] : /* istanbul ignore next */ []));
24
+ constructor() {
25
+ afterNextRender(() => {
26
+ const row = this.filterRow?.nativeElement;
27
+ if (!row)
28
+ return;
29
+ this.resizeObserver = new ResizeObserver((entries) => {
30
+ const width = entries[0]?.contentRect.width;
31
+ if (width === undefined || (this.lastRowWidth !== undefined && Math.abs(width - this.lastRowWidth) < 1))
32
+ return;
33
+ this.lastRowWidth = width;
34
+ this.scheduleQuickFieldMeasure();
35
+ });
36
+ this.resizeObserver.observe(row);
37
+ this.scheduleQuickFieldMeasure();
38
+ });
39
+ }
19
40
  get quickFields() {
20
41
  return (this.config?.fields ?? []).filter((field) => field.placement === 'quick');
21
42
  }
22
43
  get advancedFields() {
23
44
  return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick');
24
45
  }
46
+ get visibleQuickFields() {
47
+ return this.quickFields.slice(0, this.visibleQuickCount());
48
+ }
49
+ get popupFields() {
50
+ const visibleIds = new Set(this.visibleQuickFields.map((field) => field.id));
51
+ return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick' || !visibleIds.has(field.id));
52
+ }
25
53
  get activeFilterCount() {
26
54
  return (this.config?.fields ?? []).filter((field) => {
27
55
  const value = this.value?.[field.id];
@@ -49,6 +77,8 @@ class FilterPanelComponent {
49
77
  ngOnChanges(changes) {
50
78
  if (changes['value'])
51
79
  this.draft = { ...(this.value ?? {}) };
80
+ if (changes['config'])
81
+ this.scheduleQuickFieldMeasure();
52
82
  }
53
83
  setField(id, value) {
54
84
  this.draft = { ...this.draft, [id]: value };
@@ -99,14 +129,77 @@ class FilterPanelComponent {
99
129
  ngOnDestroy() {
100
130
  if (this.searchTimer)
101
131
  clearTimeout(this.searchTimer);
132
+ this.resizeObserver?.disconnect();
133
+ if (this.measureFrame !== undefined)
134
+ cancelAnimationFrame(this.measureFrame);
135
+ }
136
+ scheduleQuickFieldMeasure() {
137
+ const row = this.filterRow?.nativeElement;
138
+ if (!row)
139
+ return;
140
+ this.visibleQuickCount.set(this.quickFields.length);
141
+ if (this.measureFrame !== undefined)
142
+ cancelAnimationFrame(this.measureFrame);
143
+ this.measureFrame = requestAnimationFrame(() => {
144
+ this.measureFrame = undefined;
145
+ this.measureQuickFieldFit();
146
+ });
147
+ }
148
+ measureQuickFieldFit() {
149
+ const row = this.filterRow?.nativeElement;
150
+ if (!row)
151
+ return;
152
+ const searchControl = row.querySelector('[data-filter-search] [data-field-control]');
153
+ const baseline = searchControl?.getBoundingClientRect().top
154
+ ?? row.querySelector('[data-filter-button]')?.getBoundingClientRect().top
155
+ ?? row.getBoundingClientRect().top;
156
+ const quickFields = Array.from(row.querySelectorAll('[data-quick-filter]'));
157
+ let visibleCount = quickFields.length;
158
+ const firstWrappedField = quickFields.findIndex((field) => {
159
+ const control = field.querySelector('[data-field-control]');
160
+ return (control?.getBoundingClientRect().top ?? field.getBoundingClientRect().top) > baseline + 2;
161
+ });
162
+ if (firstWrappedField !== -1)
163
+ visibleCount = firstWrappedField;
164
+ const toolbarActions = Array.from(row.querySelectorAll('[data-toolbar-action]'));
165
+ while (visibleCount > 0 && toolbarActions.some((action) => action.getBoundingClientRect().top > baseline + 2)) {
166
+ visibleCount -= 1;
167
+ }
168
+ this.visibleQuickCount.set(visibleCount);
169
+ if (visibleCount < quickFields.length)
170
+ this.scheduleToolbarFitCheck();
171
+ }
172
+ scheduleToolbarFitCheck() {
173
+ if (this.measureFrame !== undefined)
174
+ cancelAnimationFrame(this.measureFrame);
175
+ this.measureFrame = requestAnimationFrame(() => {
176
+ this.measureFrame = undefined;
177
+ const row = this.filterRow?.nativeElement;
178
+ if (!row)
179
+ return;
180
+ const searchControl = row.querySelector('[data-filter-search] [data-field-control]');
181
+ const baseline = searchControl?.getBoundingClientRect().top
182
+ ?? row.querySelector('[data-filter-button]')?.getBoundingClientRect().top
183
+ ?? row.getBoundingClientRect().top;
184
+ const toolbarActions = Array.from(row.querySelectorAll('[data-toolbar-action]'));
185
+ if (toolbarActions.some((action) => action.getBoundingClientRect().top > baseline + 2)) {
186
+ const nextCount = Math.max(0, this.visibleQuickCount() - 1);
187
+ this.visibleQuickCount.set(nextCount);
188
+ if (nextCount > 0)
189
+ this.scheduleToolbarFitCheck();
190
+ }
191
+ });
102
192
  }
103
193
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
104
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: FilterPanelComponent, isStandalone: true, selector: "maro-filter-panel", inputs: { config: "config", value: "value" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "selectInputs", predicate: InputSelectComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div class=\"flex flex-wrap items-end gap-3\">\n @if (config.search; as search) {\n <div class=\"min-w-[15rem] flex-[1.2]\">\n <input-base [icon]=\"search.icon || 'search'\" [placeholder]=\"search.placeholder || '\u041F\u043E\u0448\u0443\u043A\u2026'\" [ngModel]=\"draft[search.id] || ''\" (ngModelChange)=\"setSearch($event)\" />\n </div>\n }\n @for (field of quickFields; track field.id) {\n <div class=\"min-w-[10rem] flex-1\">\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n </div>\n }\n @if (advancedFields.length) {\n <button type=\"button\" class=\"flex h-10 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-4 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100\" (click)=\"toggleAdvanced()\">\n <svg [lucideIcon]=\"filtersIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.filters || '\u0424\u0456\u043B\u044C\u0442\u0440\u0438' }}\n @if (activeFilterCount) { <span class=\"rounded-full bg-[#0F172A] px-1.5 text-xs text-white\">{{ activeFilterCount }}</span> }\n </button>\n }\n <button type=\"button\" class=\"inline-flex h-10 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-4 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100\" (click)=\"clear()\">\n <svg [lucideIcon]=\"closeIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.clear || '\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438' }}\n </button>\n </div>\n\n @if (activeEntries.length) {\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n @for (entry of activeEntries; track entry.id) {\n <button type=\"button\" class=\"inline-flex items-center gap-2 rounded-full border border-[#E5E7EB] bg-slate-50 px-3 py-1.5 text-xs text-[#111827] transition hover:bg-slate-100\" (click)=\"remove(entry.id)\">{{ entry.text }} <svg [lucideIcon]=\"closeIcon\" class=\"h-3.5 w-3.5 text-slate-500\"></svg></button>\n }\n <button type=\"button\" class=\"px-2 text-xs font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u0432\u0441\u0435</button>\n </div>\n }\n</section>\n\n<div class=\"fixed inset-0 z-[1100]\" [class.hidden]=\"!advancedOpen\" [attr.aria-hidden]=\"!advancedOpen\" (click)=\"cancel()\">\n <div class=\"absolute inset-0 bg-slate-900/10\"></div>\n <section class=\"absolute right-4 top-24 w-[min(42rem,calc(100vw-2rem))] rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl\" (click)=\"$event.stopPropagation()\">\n <header class=\"mb-5 flex items-center justify-between\">\n <h2 class=\"text-base font-semibold text-slate-900\">{{ config.labels?.additionalFilters || '\u0414\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0456 \u0444\u0456\u043B\u044C\u0442\u0440\u0438' }}</h2>\n <button type=\"button\" class=\"rounded-lg p-1 text-slate-500 transition hover:bg-slate-100 hover:text-[#111827]\" (click)=\"cancel()\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"><svg [lucideIcon]=\"closeIcon\" class=\"h-5 w-5\"></svg></button>\n </header>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n @for (field of advancedFields; track field.id) {\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n }\n </div>\n <footer class=\"mt-6 flex flex-wrap items-center justify-between gap-2\">\n <button type=\"button\" class=\"text-sm font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">{{ config.labels?.reset || '\u0421\u043A\u0438\u043D\u0443\u0442\u0438' }}</button>\n <button type=\"button\" class=\"rounded-lg bg-[#0F172A] px-4 py-2 text-sm font-semibold text-white transition hover:bg-slate-800 active:bg-slate-900\" (click)=\"apply()\">{{ config.labels?.apply || '\u0413\u043E\u0442\u043E\u0432\u043E' }}</button>\n </footer>\n </section>\n </div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "component", type: InputComponent, selector: "input-base", inputs: ["label", "hint", "placeholder", "icon", "name", "id", "type", "element", "rows", "required", "autocomplete", "pattern", "error", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputSelectComponent, selector: "input-select", inputs: ["label", "hint", "error", "placeholder", "icon", "name", "data", "dataParams", "required", "disabled", "multiple", "emptyStateLabel", "searchable", "options", "triggerClass", "panelClass", "actions"], outputs: ["actionTriggered", "valueChange", "searchChange"] }] });
194
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: FilterPanelComponent, isStandalone: true, selector: "maro-filter-panel", inputs: { config: "config", value: "value" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "filterRow", first: true, predicate: ["filterRow"], descendants: true, read: ElementRef }, { propertyName: "selectInputs", predicate: InputSelectComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div #filterRow class=\"flex min-w-0 flex-wrap items-end gap-2 md:gap-3\">\n @if (config.search; as search) {\n <div data-filter-search class=\"min-w-0 flex-1 md:min-w-[15rem] md:flex-[1.2]\">\n <input-base [icon]=\"search.icon || 'search'\" [placeholder]=\"search.placeholder || '\u041F\u043E\u0448\u0443\u043A\u2026'\" [ngModel]=\"draft[search.id] || ''\" (ngModelChange)=\"setSearch($event)\" />\n </div>\n }\n @for (field of visibleQuickFields; track field.id) {\n <div data-quick-filter class=\"min-w-[10rem] flex-1\">\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n </div>\n }\n @if (popupFields.length) {\n <button type=\"button\" data-filter-button data-toolbar-action class=\"flex h-10 shrink-0 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-2 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100 md:px-4\" (click)=\"toggleAdvanced()\">\n <svg [lucideIcon]=\"filtersIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.filters || '\u0424\u0456\u043B\u044C\u0442\u0440\u0438' }}\n @if (activeFilterCount) { <span class=\"rounded-full bg-[#0F172A] px-1.5 text-xs text-white\">{{ activeFilterCount }}</span> }\n </button>\n }\n <button type=\"button\" data-toolbar-action class=\"inline-flex h-10 shrink-0 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-2 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100 md:px-4\" (click)=\"clear()\" [attr.aria-label]=\"config.labels?.clear || '\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438'\">\n <svg [lucideIcon]=\"closeIcon\" class=\"h-4 w-4\"></svg><span class=\"hidden md:inline\">{{ config.labels?.clear || '\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438' }}</span>\n </button>\n </div>\n\n @if (activeEntries.length) {\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n @for (entry of activeEntries; track entry.id) {\n <button type=\"button\" class=\"inline-flex max-w-[min(100%,24rem)] min-w-0 items-center gap-2 rounded-full border border-[#E5E7EB] bg-slate-50 px-3 py-1.5 text-xs text-[#111827] transition hover:bg-slate-100\" (click)=\"remove(entry.id)\"><span class=\"min-w-0 truncate\">{{ entry.text }}</span><svg [lucideIcon]=\"closeIcon\" class=\"h-3.5 w-3.5 shrink-0 text-slate-500\"></svg></button>\n }\n </div>\n }\n</section>\n\n<div class=\"fixed inset-0 z-[1100]\" [class.hidden]=\"!advancedOpen\" [attr.aria-hidden]=\"!advancedOpen\" (click)=\"cancel()\">\n <div class=\"absolute inset-0 bg-slate-900/10\"></div>\n <section class=\"absolute right-4 top-4 max-h-[calc(100dvh-2rem)] w-[min(42rem,calc(100vw-2rem))] overflow-y-auto rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl md:top-24\" (click)=\"$event.stopPropagation()\">\n <header class=\"mb-5 flex items-center justify-between\">\n <h2 class=\"text-base font-semibold text-slate-900\">{{ config.labels?.additionalFilters || '\u0414\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0456 \u0444\u0456\u043B\u044C\u0442\u0440\u0438' }}</h2>\n <button type=\"button\" class=\"rounded-lg p-1 text-slate-500 transition hover:bg-slate-100 hover:text-[#111827]\" (click)=\"cancel()\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"><svg [lucideIcon]=\"closeIcon\" class=\"h-5 w-5\"></svg></button>\n </header>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n @for (field of popupFields; track field.id) {\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n }\n </div>\n <footer class=\"mt-6 flex flex-wrap items-center justify-between gap-2\">\n <button type=\"button\" class=\"text-sm font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">{{ config.labels?.reset || '\u0421\u043A\u0438\u043D\u0443\u0442\u0438' }}</button>\n <button type=\"button\" class=\"rounded-lg bg-[#0F172A] px-4 py-2 text-sm font-semibold text-white transition hover:bg-slate-800 active:bg-slate-900\" (click)=\"apply()\">{{ config.labels?.apply || '\u0413\u043E\u0442\u043E\u0432\u043E' }}</button>\n </footer>\n </section>\n </div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "component", type: InputComponent, selector: "input-base", inputs: ["label", "hint", "placeholder", "icon", "name", "id", "type", "element", "rows", "required", "autocomplete", "pattern", "error", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputSelectComponent, selector: "input-select", inputs: ["label", "hint", "error", "placeholder", "icon", "name", "data", "dataParams", "required", "disabled", "multiple", "emptyStateLabel", "searchable", "options", "triggerClass", "panelClass", "actions"], outputs: ["actionTriggered", "valueChange", "searchChange"] }] });
105
195
  }
106
196
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FilterPanelComponent, decorators: [{
107
197
  type: Component,
108
- args: [{ selector: 'maro-filter-panel', standalone: true, imports: [CommonModule, FormsModule, LucideDynamicIcon, InputComponent, InputSelectComponent], template: "<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div class=\"flex flex-wrap items-end gap-3\">\n @if (config.search; as search) {\n <div class=\"min-w-[15rem] flex-[1.2]\">\n <input-base [icon]=\"search.icon || 'search'\" [placeholder]=\"search.placeholder || '\u041F\u043E\u0448\u0443\u043A\u2026'\" [ngModel]=\"draft[search.id] || ''\" (ngModelChange)=\"setSearch($event)\" />\n </div>\n }\n @for (field of quickFields; track field.id) {\n <div class=\"min-w-[10rem] flex-1\">\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n </div>\n }\n @if (advancedFields.length) {\n <button type=\"button\" class=\"flex h-10 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-4 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100\" (click)=\"toggleAdvanced()\">\n <svg [lucideIcon]=\"filtersIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.filters || '\u0424\u0456\u043B\u044C\u0442\u0440\u0438' }}\n @if (activeFilterCount) { <span class=\"rounded-full bg-[#0F172A] px-1.5 text-xs text-white\">{{ activeFilterCount }}</span> }\n </button>\n }\n <button type=\"button\" class=\"inline-flex h-10 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-4 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100\" (click)=\"clear()\">\n <svg [lucideIcon]=\"closeIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.clear || '\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438' }}\n </button>\n </div>\n\n @if (activeEntries.length) {\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n @for (entry of activeEntries; track entry.id) {\n <button type=\"button\" class=\"inline-flex items-center gap-2 rounded-full border border-[#E5E7EB] bg-slate-50 px-3 py-1.5 text-xs text-[#111827] transition hover:bg-slate-100\" (click)=\"remove(entry.id)\">{{ entry.text }} <svg [lucideIcon]=\"closeIcon\" class=\"h-3.5 w-3.5 text-slate-500\"></svg></button>\n }\n <button type=\"button\" class=\"px-2 text-xs font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u0432\u0441\u0435</button>\n </div>\n }\n</section>\n\n<div class=\"fixed inset-0 z-[1100]\" [class.hidden]=\"!advancedOpen\" [attr.aria-hidden]=\"!advancedOpen\" (click)=\"cancel()\">\n <div class=\"absolute inset-0 bg-slate-900/10\"></div>\n <section class=\"absolute right-4 top-24 w-[min(42rem,calc(100vw-2rem))] rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl\" (click)=\"$event.stopPropagation()\">\n <header class=\"mb-5 flex items-center justify-between\">\n <h2 class=\"text-base font-semibold text-slate-900\">{{ config.labels?.additionalFilters || '\u0414\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0456 \u0444\u0456\u043B\u044C\u0442\u0440\u0438' }}</h2>\n <button type=\"button\" class=\"rounded-lg p-1 text-slate-500 transition hover:bg-slate-100 hover:text-[#111827]\" (click)=\"cancel()\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"><svg [lucideIcon]=\"closeIcon\" class=\"h-5 w-5\"></svg></button>\n </header>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n @for (field of advancedFields; track field.id) {\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n }\n </div>\n <footer class=\"mt-6 flex flex-wrap items-center justify-between gap-2\">\n <button type=\"button\" class=\"text-sm font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">{{ config.labels?.reset || '\u0421\u043A\u0438\u043D\u0443\u0442\u0438' }}</button>\n <button type=\"button\" class=\"rounded-lg bg-[#0F172A] px-4 py-2 text-sm font-semibold text-white transition hover:bg-slate-800 active:bg-slate-900\" (click)=\"apply()\">{{ config.labels?.apply || '\u0413\u043E\u0442\u043E\u0432\u043E' }}</button>\n </footer>\n </section>\n </div>\n" }]
109
- }], propDecorators: { selectInputs: [{
198
+ args: [{ selector: 'maro-filter-panel', standalone: true, imports: [CommonModule, FormsModule, LucideDynamicIcon, InputComponent, InputSelectComponent], template: "<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div #filterRow class=\"flex min-w-0 flex-wrap items-end gap-2 md:gap-3\">\n @if (config.search; as search) {\n <div data-filter-search class=\"min-w-0 flex-1 md:min-w-[15rem] md:flex-[1.2]\">\n <input-base [icon]=\"search.icon || 'search'\" [placeholder]=\"search.placeholder || '\u041F\u043E\u0448\u0443\u043A\u2026'\" [ngModel]=\"draft[search.id] || ''\" (ngModelChange)=\"setSearch($event)\" />\n </div>\n }\n @for (field of visibleQuickFields; track field.id) {\n <div data-quick-filter class=\"min-w-[10rem] flex-1\">\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n </div>\n }\n @if (popupFields.length) {\n <button type=\"button\" data-filter-button data-toolbar-action class=\"flex h-10 shrink-0 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-2 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100 md:px-4\" (click)=\"toggleAdvanced()\">\n <svg [lucideIcon]=\"filtersIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.filters || '\u0424\u0456\u043B\u044C\u0442\u0440\u0438' }}\n @if (activeFilterCount) { <span class=\"rounded-full bg-[#0F172A] px-1.5 text-xs text-white\">{{ activeFilterCount }}</span> }\n </button>\n }\n <button type=\"button\" data-toolbar-action class=\"inline-flex h-10 shrink-0 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-2 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100 md:px-4\" (click)=\"clear()\" [attr.aria-label]=\"config.labels?.clear || '\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438'\">\n <svg [lucideIcon]=\"closeIcon\" class=\"h-4 w-4\"></svg><span class=\"hidden md:inline\">{{ config.labels?.clear || '\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438' }}</span>\n </button>\n </div>\n\n @if (activeEntries.length) {\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n @for (entry of activeEntries; track entry.id) {\n <button type=\"button\" class=\"inline-flex max-w-[min(100%,24rem)] min-w-0 items-center gap-2 rounded-full border border-[#E5E7EB] bg-slate-50 px-3 py-1.5 text-xs text-[#111827] transition hover:bg-slate-100\" (click)=\"remove(entry.id)\"><span class=\"min-w-0 truncate\">{{ entry.text }}</span><svg [lucideIcon]=\"closeIcon\" class=\"h-3.5 w-3.5 shrink-0 text-slate-500\"></svg></button>\n }\n </div>\n }\n</section>\n\n<div class=\"fixed inset-0 z-[1100]\" [class.hidden]=\"!advancedOpen\" [attr.aria-hidden]=\"!advancedOpen\" (click)=\"cancel()\">\n <div class=\"absolute inset-0 bg-slate-900/10\"></div>\n <section class=\"absolute right-4 top-4 max-h-[calc(100dvh-2rem)] w-[min(42rem,calc(100vw-2rem))] overflow-y-auto rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl md:top-24\" (click)=\"$event.stopPropagation()\">\n <header class=\"mb-5 flex items-center justify-between\">\n <h2 class=\"text-base font-semibold text-slate-900\">{{ config.labels?.additionalFilters || '\u0414\u043E\u0434\u0430\u0442\u043A\u043E\u0432\u0456 \u0444\u0456\u043B\u044C\u0442\u0440\u0438' }}</h2>\n <button type=\"button\" class=\"rounded-lg p-1 text-slate-500 transition hover:bg-slate-100 hover:text-[#111827]\" (click)=\"cancel()\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"><svg [lucideIcon]=\"closeIcon\" class=\"h-5 w-5\"></svg></button>\n </header>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n @for (field of popupFields; track field.id) {\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || '\u041E\u0431\u0435\u0440\u0456\u0442\u044C\u2026'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n }\n </div>\n <footer class=\"mt-6 flex flex-wrap items-center justify-between gap-2\">\n <button type=\"button\" class=\"text-sm font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">{{ config.labels?.reset || '\u0421\u043A\u0438\u043D\u0443\u0442\u0438' }}</button>\n <button type=\"button\" class=\"rounded-lg bg-[#0F172A] px-4 py-2 text-sm font-semibold text-white transition hover:bg-slate-800 active:bg-slate-900\" (click)=\"apply()\">{{ config.labels?.apply || '\u0413\u043E\u0442\u043E\u0432\u043E' }}</button>\n </footer>\n </section>\n </div>\n" }]
199
+ }], ctorParameters: () => [], propDecorators: { filterRow: [{
200
+ type: ViewChild,
201
+ args: ['filterRow', { read: ElementRef }]
202
+ }], selectInputs: [{
110
203
  type: ViewChildren,
111
204
  args: [InputSelectComponent]
112
205
  }], config: [{
@@ -1 +1 @@
1
- {"version":3,"file":"maro-tech-filters.mjs","sources":["../../../src/lib/filter-panel/filter-panel.component.ts","../../../src/lib/filter-panel/filter-panel.component.html","../../../src/maro-tech-filters.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport { Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { LucideDynamicIcon, LucideSlidersHorizontal, LucideX } from '@lucide/angular';\nimport { InputComponent, InputSelectComponent } from '@maro-tech/form';\nimport { FilterFieldConfig, FilterPanelConfig, FilterState, FilterValue } from './filter-panel.models';\n\n@Component({\n selector: 'maro-filter-panel',\n standalone: true,\n imports: [CommonModule, FormsModule, LucideDynamicIcon, InputComponent, InputSelectComponent],\n templateUrl: './filter-panel.component.html',\n})\nexport class FilterPanelComponent implements OnChanges {\n @ViewChildren(InputSelectComponent) private selectInputs?: QueryList<InputSelectComponent>;\n @Input({ required: true }) config!: FilterPanelConfig;\n @Input() value: FilterState = {};\n @Output() valueChange = new EventEmitter<FilterState>();\n\n readonly filtersIcon = LucideSlidersHorizontal;\n readonly closeIcon = LucideX;\n draft: FilterState = {};\n advancedOpen = false;\n private searchTimer?: ReturnType<typeof setTimeout>;\n\n get quickFields(): FilterFieldConfig[] {\n return (this.config?.fields ?? []).filter((field) => field.placement === 'quick');\n }\n\n get advancedFields(): FilterFieldConfig[] {\n return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick');\n }\n\n get activeFilterCount(): number {\n return (this.config?.fields ?? []).filter((field) => {\n const value = this.value?.[field.id];\n return value !== '' && value !== null && value !== undefined && (!Array.isArray(value) || value.length > 0);\n }).length;\n }\n\n get activeEntries(): Array<{ id: string; label: string; text: string }> {\n const fields = this.config?.fields ?? [];\n return Object.entries(this.value ?? {}).flatMap(([id, raw]) => {\n if (raw === '' || raw === null || raw === undefined || (Array.isArray(raw) && !raw.length)) return [];\n if (id === this.config?.search?.id) return [{ id, label: '', text: `Пошук: ${String(raw)}` }];\n const field = fields.find((item) => item.id === id);\n if (!field) return [];\n const values = Array.isArray(raw) ? raw : [raw];\n const selectedLabels = this.selectInputs?.find((input) => input.name === id)?.selectedLabelsByValue;\n const labels = values.map((entry) => field.options?.find((option) => String(option.value) === String(entry))?.label\n ?? selectedLabels?.[String(entry)]\n ?? String(entry));\n return [{ id, label: field.label, text: `${field.label}: ${labels.join(', ')}` }];\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['value']) this.draft = { ...(this.value ?? {}) };\n }\n\n setField(id: string, value: FilterValue): void {\n this.draft = { ...this.draft, [id]: value };\n this.valueChange.emit({ ...this.value, [id]: value });\n }\n\n setSearch(value: string): void {\n const id = this.config.search?.id;\n if (!id) return;\n this.draft = { ...this.draft, [id]: value };\n if (this.searchTimer) clearTimeout(this.searchTimer);\n this.searchTimer = setTimeout(() => {\n this.valueChange.emit({ ...this.value, [id]: value });\n }, 300);\n }\n\n apply(): void {\n this.advancedOpen = false;\n }\n\n cancel(): void {\n this.advancedOpen = false;\n }\n\n toggleAdvanced(): void {\n this.advancedOpen = !this.advancedOpen;\n if (this.advancedOpen) this.draft = { ...(this.value ?? {}) };\n }\n\n clear(): void {\n if (this.searchTimer) clearTimeout(this.searchTimer);\n this.searchTimer = undefined;\n this.draft = {};\n this.valueChange.emit({});\n this.advancedOpen = false;\n }\n\n remove(id: string): void {\n const next = { ...this.value };\n delete next[id];\n this.draft = { ...next };\n this.valueChange.emit(next);\n }\n\n fieldValue(field: FilterFieldConfig): FilterValue {\n return this.draft[field.id] ?? (field.multiple ? [] : '');\n }\n\n options(field: FilterFieldConfig): Array<{ label: string; value: string | null }> {\n return (field.options ?? []).map((option) => ({ label: option.label, value: String(option.value) }));\n }\n\n ngOnDestroy(): void {\n if (this.searchTimer) clearTimeout(this.searchTimer);\n }\n}\n","<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div class=\"flex flex-wrap items-end gap-3\">\n @if (config.search; as search) {\n <div class=\"min-w-[15rem] flex-[1.2]\">\n <input-base [icon]=\"search.icon || 'search'\" [placeholder]=\"search.placeholder || 'Пошук…'\" [ngModel]=\"draft[search.id] || ''\" (ngModelChange)=\"setSearch($event)\" />\n </div>\n }\n @for (field of quickFields; track field.id) {\n <div class=\"min-w-[10rem] flex-1\">\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || 'Оберіть…'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n </div>\n }\n @if (advancedFields.length) {\n <button type=\"button\" class=\"flex h-10 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-4 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100\" (click)=\"toggleAdvanced()\">\n <svg [lucideIcon]=\"filtersIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.filters || 'Фільтри' }}\n @if (activeFilterCount) { <span class=\"rounded-full bg-[#0F172A] px-1.5 text-xs text-white\">{{ activeFilterCount }}</span> }\n </button>\n }\n <button type=\"button\" class=\"inline-flex h-10 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-4 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100\" (click)=\"clear()\">\n <svg [lucideIcon]=\"closeIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.clear || 'Очистити' }}\n </button>\n </div>\n\n @if (activeEntries.length) {\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n @for (entry of activeEntries; track entry.id) {\n <button type=\"button\" class=\"inline-flex items-center gap-2 rounded-full border border-[#E5E7EB] bg-slate-50 px-3 py-1.5 text-xs text-[#111827] transition hover:bg-slate-100\" (click)=\"remove(entry.id)\">{{ entry.text }} <svg [lucideIcon]=\"closeIcon\" class=\"h-3.5 w-3.5 text-slate-500\"></svg></button>\n }\n <button type=\"button\" class=\"px-2 text-xs font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">Скинути все</button>\n </div>\n }\n</section>\n\n<div class=\"fixed inset-0 z-[1100]\" [class.hidden]=\"!advancedOpen\" [attr.aria-hidden]=\"!advancedOpen\" (click)=\"cancel()\">\n <div class=\"absolute inset-0 bg-slate-900/10\"></div>\n <section class=\"absolute right-4 top-24 w-[min(42rem,calc(100vw-2rem))] rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl\" (click)=\"$event.stopPropagation()\">\n <header class=\"mb-5 flex items-center justify-between\">\n <h2 class=\"text-base font-semibold text-slate-900\">{{ config.labels?.additionalFilters || 'Додаткові фільтри' }}</h2>\n <button type=\"button\" class=\"rounded-lg p-1 text-slate-500 transition hover:bg-slate-100 hover:text-[#111827]\" (click)=\"cancel()\" aria-label=\"Закрити\"><svg [lucideIcon]=\"closeIcon\" class=\"h-5 w-5\"></svg></button>\n </header>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n @for (field of advancedFields; track field.id) {\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || 'Оберіть…'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n }\n </div>\n <footer class=\"mt-6 flex flex-wrap items-center justify-between gap-2\">\n <button type=\"button\" class=\"text-sm font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">{{ config.labels?.reset || 'Скинути' }}</button>\n <button type=\"button\" class=\"rounded-lg bg-[#0F172A] px-4 py-2 text-sm font-semibold text-white transition hover:bg-slate-800 active:bg-slate-900\" (click)=\"apply()\">{{ config.labels?.apply || 'Готово' }}</button>\n </footer>\n </section>\n </div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"maro-tech-filters.mjs","sources":["../../../src/lib/filter-panel/filter-panel.component.ts","../../../src/lib/filter-panel/filter-panel.component.html","../../../src/maro-tech-filters.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport { afterNextRender, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, QueryList, signal, SimpleChanges, ViewChild, ViewChildren } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { LucideDynamicIcon, LucideSlidersHorizontal, LucideX } from '@lucide/angular';\nimport { InputComponent, InputSelectComponent } from '@maro-tech/form';\nimport { FilterFieldConfig, FilterPanelConfig, FilterState, FilterValue } from './filter-panel.models';\n\n@Component({\n selector: 'maro-filter-panel',\n standalone: true,\n imports: [CommonModule, FormsModule, LucideDynamicIcon, InputComponent, InputSelectComponent],\n templateUrl: './filter-panel.component.html',\n})\nexport class FilterPanelComponent implements OnChanges, OnDestroy {\n @ViewChild('filterRow', { read: ElementRef }) private filterRow?: ElementRef<HTMLElement>;\n @ViewChildren(InputSelectComponent) private selectInputs?: QueryList<InputSelectComponent>;\n @Input({ required: true }) config!: FilterPanelConfig;\n @Input() value: FilterState = {};\n @Output() valueChange = new EventEmitter<FilterState>();\n\n readonly filtersIcon = LucideSlidersHorizontal;\n readonly closeIcon = LucideX;\n draft: FilterState = {};\n advancedOpen = false;\n private searchTimer?: ReturnType<typeof setTimeout>;\n private resizeObserver?: ResizeObserver;\n private measureFrame?: number;\n private lastRowWidth?: number;\n private readonly visibleQuickCount = signal(0);\n\n constructor() {\n afterNextRender(() => {\n const row = this.filterRow?.nativeElement;\n if (!row) return;\n this.resizeObserver = new ResizeObserver((entries) => {\n const width = entries[0]?.contentRect.width;\n if (width === undefined || (this.lastRowWidth !== undefined && Math.abs(width - this.lastRowWidth) < 1)) return;\n this.lastRowWidth = width;\n this.scheduleQuickFieldMeasure();\n });\n this.resizeObserver.observe(row);\n this.scheduleQuickFieldMeasure();\n });\n }\n\n get quickFields(): FilterFieldConfig[] {\n return (this.config?.fields ?? []).filter((field) => field.placement === 'quick');\n }\n\n get advancedFields(): FilterFieldConfig[] {\n return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick');\n }\n\n get visibleQuickFields(): FilterFieldConfig[] {\n return this.quickFields.slice(0, this.visibleQuickCount());\n }\n\n get popupFields(): FilterFieldConfig[] {\n const visibleIds = new Set(this.visibleQuickFields.map((field) => field.id));\n return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick' || !visibleIds.has(field.id));\n }\n\n get activeFilterCount(): number {\n return (this.config?.fields ?? []).filter((field) => {\n const value = this.value?.[field.id];\n return value !== '' && value !== null && value !== undefined && (!Array.isArray(value) || value.length > 0);\n }).length;\n }\n\n get activeEntries(): Array<{ id: string; label: string; text: string }> {\n const fields = this.config?.fields ?? [];\n return Object.entries(this.value ?? {}).flatMap(([id, raw]) => {\n if (raw === '' || raw === null || raw === undefined || (Array.isArray(raw) && !raw.length)) return [];\n if (id === this.config?.search?.id) return [{ id, label: '', text: `Пошук: ${String(raw)}` }];\n const field = fields.find((item) => item.id === id);\n if (!field) return [];\n const values = Array.isArray(raw) ? raw : [raw];\n const selectedLabels = this.selectInputs?.find((input) => input.name === id)?.selectedLabelsByValue;\n const labels = values.map((entry) => field.options?.find((option) => String(option.value) === String(entry))?.label\n ?? selectedLabels?.[String(entry)]\n ?? String(entry));\n return [{ id, label: field.label, text: `${field.label}: ${labels.join(', ')}` }];\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['value']) this.draft = { ...(this.value ?? {}) };\n if (changes['config']) this.scheduleQuickFieldMeasure();\n }\n\n setField(id: string, value: FilterValue): void {\n this.draft = { ...this.draft, [id]: value };\n this.valueChange.emit({ ...this.value, [id]: value });\n }\n\n setSearch(value: string): void {\n const id = this.config.search?.id;\n if (!id) return;\n this.draft = { ...this.draft, [id]: value };\n if (this.searchTimer) clearTimeout(this.searchTimer);\n this.searchTimer = setTimeout(() => {\n this.valueChange.emit({ ...this.value, [id]: value });\n }, 300);\n }\n\n apply(): void {\n this.advancedOpen = false;\n }\n\n cancel(): void {\n this.advancedOpen = false;\n }\n\n toggleAdvanced(): void {\n this.advancedOpen = !this.advancedOpen;\n if (this.advancedOpen) this.draft = { ...(this.value ?? {}) };\n }\n\n clear(): void {\n if (this.searchTimer) clearTimeout(this.searchTimer);\n this.searchTimer = undefined;\n this.draft = {};\n this.valueChange.emit({});\n this.advancedOpen = false;\n }\n\n remove(id: string): void {\n const next = { ...this.value };\n delete next[id];\n this.draft = { ...next };\n this.valueChange.emit(next);\n }\n\n fieldValue(field: FilterFieldConfig): FilterValue {\n return this.draft[field.id] ?? (field.multiple ? [] : '');\n }\n\n options(field: FilterFieldConfig): Array<{ label: string; value: string | null }> {\n return (field.options ?? []).map((option) => ({ label: option.label, value: String(option.value) }));\n }\n\n ngOnDestroy(): void {\n if (this.searchTimer) clearTimeout(this.searchTimer);\n this.resizeObserver?.disconnect();\n if (this.measureFrame !== undefined) cancelAnimationFrame(this.measureFrame);\n }\n\n private scheduleQuickFieldMeasure(): void {\n const row = this.filterRow?.nativeElement;\n if (!row) return;\n this.visibleQuickCount.set(this.quickFields.length);\n if (this.measureFrame !== undefined) cancelAnimationFrame(this.measureFrame);\n this.measureFrame = requestAnimationFrame(() => {\n this.measureFrame = undefined;\n this.measureQuickFieldFit();\n });\n }\n\n private measureQuickFieldFit(): void {\n const row = this.filterRow?.nativeElement;\n if (!row) return;\n const searchControl = row.querySelector<HTMLElement>('[data-filter-search] [data-field-control]');\n const baseline = searchControl?.getBoundingClientRect().top\n ?? row.querySelector<HTMLElement>('[data-filter-button]')?.getBoundingClientRect().top\n ?? row.getBoundingClientRect().top;\n const quickFields = Array.from(row.querySelectorAll<HTMLElement>('[data-quick-filter]'));\n let visibleCount = quickFields.length;\n const firstWrappedField = quickFields.findIndex((field) => {\n const control = field.querySelector<HTMLElement>('[data-field-control]');\n return (control?.getBoundingClientRect().top ?? field.getBoundingClientRect().top) > baseline + 2;\n });\n if (firstWrappedField !== -1) visibleCount = firstWrappedField;\n\n const toolbarActions = Array.from(row.querySelectorAll<HTMLElement>('[data-toolbar-action]'));\n while (visibleCount > 0 && toolbarActions.some((action) => action.getBoundingClientRect().top > baseline + 2)) {\n visibleCount -= 1;\n }\n this.visibleQuickCount.set(visibleCount);\n if (visibleCount < quickFields.length) this.scheduleToolbarFitCheck();\n }\n\n private scheduleToolbarFitCheck(): void {\n if (this.measureFrame !== undefined) cancelAnimationFrame(this.measureFrame);\n this.measureFrame = requestAnimationFrame(() => {\n this.measureFrame = undefined;\n const row = this.filterRow?.nativeElement;\n if (!row) return;\n const searchControl = row.querySelector<HTMLElement>('[data-filter-search] [data-field-control]');\n const baseline = searchControl?.getBoundingClientRect().top\n ?? row.querySelector<HTMLElement>('[data-filter-button]')?.getBoundingClientRect().top\n ?? row.getBoundingClientRect().top;\n const toolbarActions = Array.from(row.querySelectorAll<HTMLElement>('[data-toolbar-action]'));\n if (toolbarActions.some((action) => action.getBoundingClientRect().top > baseline + 2)) {\n const nextCount = Math.max(0, this.visibleQuickCount() - 1);\n this.visibleQuickCount.set(nextCount);\n if (nextCount > 0) this.scheduleToolbarFitCheck();\n }\n });\n }\n}\n","<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div #filterRow class=\"flex min-w-0 flex-wrap items-end gap-2 md:gap-3\">\n @if (config.search; as search) {\n <div data-filter-search class=\"min-w-0 flex-1 md:min-w-[15rem] md:flex-[1.2]\">\n <input-base [icon]=\"search.icon || 'search'\" [placeholder]=\"search.placeholder || 'Пошук…'\" [ngModel]=\"draft[search.id] || ''\" (ngModelChange)=\"setSearch($event)\" />\n </div>\n }\n @for (field of visibleQuickFields; track field.id) {\n <div data-quick-filter class=\"min-w-[10rem] flex-1\">\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || 'Оберіть…'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n </div>\n }\n @if (popupFields.length) {\n <button type=\"button\" data-filter-button data-toolbar-action class=\"flex h-10 shrink-0 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-2 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100 md:px-4\" (click)=\"toggleAdvanced()\">\n <svg [lucideIcon]=\"filtersIcon\" class=\"h-4 w-4\"></svg>{{ config.labels?.filters || 'Фільтри' }}\n @if (activeFilterCount) { <span class=\"rounded-full bg-[#0F172A] px-1.5 text-xs text-white\">{{ activeFilterCount }}</span> }\n </button>\n }\n <button type=\"button\" data-toolbar-action class=\"inline-flex h-10 shrink-0 items-center gap-2 rounded-lg border border-[#E5E7EB] bg-white px-2 text-sm font-semibold text-[#111827] transition hover:border-slate-300 hover:bg-slate-50 active:bg-slate-100 md:px-4\" (click)=\"clear()\" [attr.aria-label]=\"config.labels?.clear || 'Очистити'\">\n <svg [lucideIcon]=\"closeIcon\" class=\"h-4 w-4\"></svg><span class=\"hidden md:inline\">{{ config.labels?.clear || 'Очистити' }}</span>\n </button>\n </div>\n\n @if (activeEntries.length) {\n <div class=\"mt-3 flex flex-wrap items-center gap-2\">\n @for (entry of activeEntries; track entry.id) {\n <button type=\"button\" class=\"inline-flex max-w-[min(100%,24rem)] min-w-0 items-center gap-2 rounded-full border border-[#E5E7EB] bg-slate-50 px-3 py-1.5 text-xs text-[#111827] transition hover:bg-slate-100\" (click)=\"remove(entry.id)\"><span class=\"min-w-0 truncate\">{{ entry.text }}</span><svg [lucideIcon]=\"closeIcon\" class=\"h-3.5 w-3.5 shrink-0 text-slate-500\"></svg></button>\n }\n </div>\n }\n</section>\n\n<div class=\"fixed inset-0 z-[1100]\" [class.hidden]=\"!advancedOpen\" [attr.aria-hidden]=\"!advancedOpen\" (click)=\"cancel()\">\n <div class=\"absolute inset-0 bg-slate-900/10\"></div>\n <section class=\"absolute right-4 top-4 max-h-[calc(100dvh-2rem)] w-[min(42rem,calc(100vw-2rem))] overflow-y-auto rounded-2xl border border-slate-200 bg-white p-5 shadow-2xl md:top-24\" (click)=\"$event.stopPropagation()\">\n <header class=\"mb-5 flex items-center justify-between\">\n <h2 class=\"text-base font-semibold text-slate-900\">{{ config.labels?.additionalFilters || 'Додаткові фільтри' }}</h2>\n <button type=\"button\" class=\"rounded-lg p-1 text-slate-500 transition hover:bg-slate-100 hover:text-[#111827]\" (click)=\"cancel()\" aria-label=\"Закрити\"><svg [lucideIcon]=\"closeIcon\" class=\"h-5 w-5\"></svg></button>\n </header>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n @for (field of popupFields; track field.id) {\n @if (field.type === 'select' || field.type === 'boolean') {\n <input-select [name]=\"field.id\" [label]=\"field.label\" [icon]=\"field.icon\" [data]=\"field.data || ''\" [options]=\"options(field)\" [multiple]=\"!!field.multiple\" [searchable]=\"!!field.data\" [placeholder]=\"field.placeholder || 'Оберіть…'\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n } @else {\n <input-base [label]=\"field.label\" [icon]=\"field.icon\" [type]=\"field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'\" [placeholder]=\"field.placeholder || ''\" [ngModel]=\"fieldValue(field)\" (ngModelChange)=\"setField(field.id, $event)\" />\n }\n }\n </div>\n <footer class=\"mt-6 flex flex-wrap items-center justify-between gap-2\">\n <button type=\"button\" class=\"text-sm font-semibold text-[#0F172A] hover:text-slate-600\" (click)=\"clear()\">{{ config.labels?.reset || 'Скинути' }}</button>\n <button type=\"button\" class=\"rounded-lg bg-[#0F172A] px-4 py-2 text-sm font-semibold text-white transition hover:bg-slate-800 active:bg-slate-900\" (click)=\"apply()\">{{ config.labels?.apply || 'Готово' }}</button>\n </footer>\n </section>\n </div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maro-tech/filters",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.2.0",
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { OnChanges, EventEmitter, SimpleChanges } from '@angular/core';
2
+ import { OnChanges, OnDestroy, EventEmitter, SimpleChanges } from '@angular/core';
3
3
  import { LucideSlidersHorizontal, LucideX } from '@lucide/angular';
4
4
 
5
5
  type FilterValue = string | string[] | number | boolean | null;
@@ -40,7 +40,8 @@ interface FilterPanelConfig {
40
40
  };
41
41
  }
42
42
 
43
- declare class FilterPanelComponent implements OnChanges {
43
+ declare class FilterPanelComponent implements OnChanges, OnDestroy {
44
+ private filterRow?;
44
45
  private selectInputs?;
45
46
  config: FilterPanelConfig;
46
47
  value: FilterState;
@@ -50,8 +51,15 @@ declare class FilterPanelComponent implements OnChanges {
50
51
  draft: FilterState;
51
52
  advancedOpen: boolean;
52
53
  private searchTimer?;
54
+ private resizeObserver?;
55
+ private measureFrame?;
56
+ private lastRowWidth?;
57
+ private readonly visibleQuickCount;
58
+ constructor();
53
59
  get quickFields(): FilterFieldConfig[];
54
60
  get advancedFields(): FilterFieldConfig[];
61
+ get visibleQuickFields(): FilterFieldConfig[];
62
+ get popupFields(): FilterFieldConfig[];
55
63
  get activeFilterCount(): number;
56
64
  get activeEntries(): Array<{
57
65
  id: string;
@@ -72,6 +80,9 @@ declare class FilterPanelComponent implements OnChanges {
72
80
  value: string | null;
73
81
  }>;
74
82
  ngOnDestroy(): void;
83
+ private scheduleQuickFieldMeasure;
84
+ private measureQuickFieldFit;
85
+ private scheduleToolbarFitCheck;
75
86
  static ɵfac: i0.ɵɵFactoryDeclaration<FilterPanelComponent, never>;
76
87
  static ɵcmp: i0.ɵɵComponentDeclaration<FilterPanelComponent, "maro-filter-panel", never, { "config": { "alias": "config"; "required": true; }; "value": { "alias": "value"; "required": false; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
77
88
  }