@maro-tech/filters 0.1.3 → 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,28 +1,40 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { EventEmitter, signal, afterNextRender, 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 = {};
13
14
  valueChange = new EventEmitter();
14
15
  filtersIcon = LucideSlidersHorizontal;
15
16
  closeIcon = LucideX;
16
- isMobileView = signal(false, ...(ngDevMode ? [{ debugName: "isMobileView" }] : /* istanbul ignore next */ []));
17
17
  draft = {};
18
18
  advancedOpen = false;
19
19
  searchTimer;
20
- mediaQuery;
20
+ resizeObserver;
21
+ measureFrame;
22
+ lastRowWidth;
23
+ visibleQuickCount = signal(0, ...(ngDevMode ? [{ debugName: "visibleQuickCount" }] : /* istanbul ignore next */ []));
21
24
  constructor() {
22
25
  afterNextRender(() => {
23
- this.mediaQuery = window.matchMedia('(max-width: 767px)');
24
- this.isMobileView.set(this.mediaQuery.matches);
25
- this.mediaQuery.addEventListener('change', this.onViewportChange);
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();
26
38
  });
27
39
  }
28
40
  get quickFields() {
@@ -31,8 +43,12 @@ class FilterPanelComponent {
31
43
  get advancedFields() {
32
44
  return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick');
33
45
  }
46
+ get visibleQuickFields() {
47
+ return this.quickFields.slice(0, this.visibleQuickCount());
48
+ }
34
49
  get popupFields() {
35
- return this.isMobileView() ? (this.config?.fields ?? []) : this.advancedFields;
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));
36
52
  }
37
53
  get activeFilterCount() {
38
54
  return (this.config?.fields ?? []).filter((field) => {
@@ -61,6 +77,8 @@ class FilterPanelComponent {
61
77
  ngOnChanges(changes) {
62
78
  if (changes['value'])
63
79
  this.draft = { ...(this.value ?? {}) };
80
+ if (changes['config'])
81
+ this.scheduleQuickFieldMeasure();
64
82
  }
65
83
  setField(id, value) {
66
84
  this.draft = { ...this.draft, [id]: value };
@@ -111,18 +129,77 @@ class FilterPanelComponent {
111
129
  ngOnDestroy() {
112
130
  if (this.searchTimer)
113
131
  clearTimeout(this.searchTimer);
114
- this.mediaQuery?.removeEventListener('change', this.onViewportChange);
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
+ });
115
192
  }
116
- onViewportChange = (event) => {
117
- this.isMobileView.set(event.matches);
118
- };
119
193
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
120
- 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 min-w-0 flex-nowrap items-end gap-2 md:flex-wrap md:gap-3\">\n @if (config.search; as search) {\n <div 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 @if (!isMobileView()) {\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 }\n @if (popupFields.length) {\n <button type=\"button\" 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\" 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"] }] });
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"] }] });
121
195
  }
122
196
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FilterPanelComponent, decorators: [{
123
197
  type: Component,
124
- 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 min-w-0 flex-nowrap items-end gap-2 md:flex-wrap md:gap-3\">\n @if (config.search; as search) {\n <div 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 @if (!isMobileView()) {\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 }\n @if (popupFields.length) {\n <button type=\"button\" 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\" 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" }]
125
- }], ctorParameters: () => [], 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: [{
126
203
  type: ViewChildren,
127
204
  args: [InputSelectComponent]
128
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 { afterNextRender, Component, EventEmitter, Input, OnChanges, OnDestroy, Output, QueryList, signal, 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, OnDestroy {\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 readonly isMobileView = signal(false);\n draft: FilterState = {};\n advancedOpen = false;\n private searchTimer?: ReturnType<typeof setTimeout>;\n private mediaQuery?: MediaQueryList;\n\n constructor() {\n afterNextRender(() => {\n this.mediaQuery = window.matchMedia('(max-width: 767px)');\n this.isMobileView.set(this.mediaQuery.matches);\n this.mediaQuery.addEventListener('change', this.onViewportChange);\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 popupFields(): FilterFieldConfig[] {\n return this.isMobileView() ? (this.config?.fields ?? []) : this.advancedFields;\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 this.mediaQuery?.removeEventListener('change', this.onViewportChange);\n }\n\n private readonly onViewportChange = (event: MediaQueryListEvent): void => {\n this.isMobileView.set(event.matches);\n };\n}\n","<section class=\"rounded-2xl border border-slate-200 bg-white p-4 shadow-sm\">\n <div class=\"flex min-w-0 flex-nowrap items-end gap-2 md:flex-wrap md:gap-3\">\n @if (config.search; as search) {\n <div 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 @if (!isMobileView()) {\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 }\n @if (popupFields.length) {\n <button type=\"button\" 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\" 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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"}
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.3",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.2.0",
@@ -41,20 +41,24 @@ interface FilterPanelConfig {
41
41
  }
42
42
 
43
43
  declare class FilterPanelComponent implements OnChanges, OnDestroy {
44
+ private filterRow?;
44
45
  private selectInputs?;
45
46
  config: FilterPanelConfig;
46
47
  value: FilterState;
47
48
  valueChange: EventEmitter<FilterState>;
48
49
  readonly filtersIcon: typeof LucideSlidersHorizontal;
49
50
  readonly closeIcon: typeof LucideX;
50
- readonly isMobileView: i0.WritableSignal<boolean>;
51
51
  draft: FilterState;
52
52
  advancedOpen: boolean;
53
53
  private searchTimer?;
54
- private mediaQuery?;
54
+ private resizeObserver?;
55
+ private measureFrame?;
56
+ private lastRowWidth?;
57
+ private readonly visibleQuickCount;
55
58
  constructor();
56
59
  get quickFields(): FilterFieldConfig[];
57
60
  get advancedFields(): FilterFieldConfig[];
61
+ get visibleQuickFields(): FilterFieldConfig[];
58
62
  get popupFields(): FilterFieldConfig[];
59
63
  get activeFilterCount(): number;
60
64
  get activeEntries(): Array<{
@@ -76,7 +80,9 @@ declare class FilterPanelComponent implements OnChanges, OnDestroy {
76
80
  value: string | null;
77
81
  }>;
78
82
  ngOnDestroy(): void;
79
- private readonly onViewportChange;
83
+ private scheduleQuickFieldMeasure;
84
+ private measureQuickFieldFit;
85
+ private scheduleToolbarFitCheck;
80
86
  static ɵfac: i0.ɵɵFactoryDeclaration<FilterPanelComponent, never>;
81
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>;
82
88
  }