@maro-tech/filters 0.1.1 → 0.1.3

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,26 +1,39 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { EventEmitter, Output, Input, Component } from '@angular/core';
3
+ import { EventEmitter, signal, afterNextRender, Output, Input, ViewChildren, 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
+ selectInputs;
10
11
  config;
11
12
  value = {};
12
13
  valueChange = new EventEmitter();
13
14
  filtersIcon = LucideSlidersHorizontal;
14
15
  closeIcon = LucideX;
16
+ isMobileView = signal(false, ...(ngDevMode ? [{ debugName: "isMobileView" }] : /* istanbul ignore next */ []));
15
17
  draft = {};
16
18
  advancedOpen = false;
17
19
  searchTimer;
20
+ mediaQuery;
21
+ constructor() {
22
+ afterNextRender(() => {
23
+ this.mediaQuery = window.matchMedia('(max-width: 767px)');
24
+ this.isMobileView.set(this.mediaQuery.matches);
25
+ this.mediaQuery.addEventListener('change', this.onViewportChange);
26
+ });
27
+ }
18
28
  get quickFields() {
19
29
  return (this.config?.fields ?? []).filter((field) => field.placement === 'quick');
20
30
  }
21
31
  get advancedFields() {
22
32
  return (this.config?.fields ?? []).filter((field) => field.placement !== 'quick');
23
33
  }
34
+ get popupFields() {
35
+ return this.isMobileView() ? (this.config?.fields ?? []) : this.advancedFields;
36
+ }
24
37
  get activeFilterCount() {
25
38
  return (this.config?.fields ?? []).filter((field) => {
26
39
  const value = this.value?.[field.id];
@@ -38,7 +51,10 @@ class FilterPanelComponent {
38
51
  if (!field)
39
52
  return [];
40
53
  const values = Array.isArray(raw) ? raw : [raw];
41
- const labels = values.map((entry) => field.options?.find((option) => String(option.value) === String(entry))?.label ?? String(entry));
54
+ const selectedLabels = this.selectInputs?.find((input) => input.name === id)?.selectedLabelsByValue;
55
+ const labels = values.map((entry) => field.options?.find((option) => String(option.value) === String(entry))?.label
56
+ ?? selectedLabels?.[String(entry)]
57
+ ?? String(entry));
42
58
  return [{ id, label: field.label, text: `${field.label}: ${labels.join(', ')}` }];
43
59
  });
44
60
  }
@@ -95,14 +111,21 @@ class FilterPanelComponent {
95
111
  ngOnDestroy() {
96
112
  if (this.searchTimer)
97
113
  clearTimeout(this.searchTimer);
114
+ this.mediaQuery?.removeEventListener('change', this.onViewportChange);
98
115
  }
116
+ onViewportChange = (event) => {
117
+ this.isMobileView.set(event.matches);
118
+ };
99
119
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
100
- 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" }, 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 [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@if (advancedOpen) {\n <div class=\"fixed inset-0 z-[1100]\" (click)=\"advancedOpen = false\">\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 [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}\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"] }] });
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"] }] });
101
121
  }
102
122
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FilterPanelComponent, decorators: [{
103
123
  type: Component,
104
- 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 [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@if (advancedOpen) {\n <div class=\"fixed inset-0 z-[1100]\" (click)=\"advancedOpen = false\">\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 [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}\n" }]
105
- }], propDecorators: { config: [{
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: [{
126
+ type: ViewChildren,
127
+ args: [InputSelectComponent]
128
+ }], config: [{
106
129
  type: Input,
107
130
  args: [{ required: true }]
108
131
  }], value: [{
@@ -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, SimpleChanges } 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 @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 labels = values.map((entry) => field.options?.find((option) => String(option.value) === String(entry))?.label ?? 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 [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@if (advancedOpen) {\n <div class=\"fixed inset-0 z-[1100]\" (click)=\"advancedOpen = false\">\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 [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","/**\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, 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maro-tech/filters",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.2.0",
@@ -8,7 +8,7 @@
8
8
  "@angular/forms": "^21.2.0",
9
9
  "@lucide/angular": "^1.48.0",
10
10
  "@maro-tech/core": "*",
11
- "@maro-tech/form": "^0.2.9",
11
+ "@maro-tech/form": "^0.2.11",
12
12
  "rxjs": "~7.8.0"
13
13
  },
14
14
  "dependencies": {
@@ -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,17 +40,22 @@ interface FilterPanelConfig {
40
40
  };
41
41
  }
42
42
 
43
- declare class FilterPanelComponent implements OnChanges {
43
+ declare class FilterPanelComponent implements OnChanges, OnDestroy {
44
+ private selectInputs?;
44
45
  config: FilterPanelConfig;
45
46
  value: FilterState;
46
47
  valueChange: EventEmitter<FilterState>;
47
48
  readonly filtersIcon: typeof LucideSlidersHorizontal;
48
49
  readonly closeIcon: typeof LucideX;
50
+ readonly isMobileView: i0.WritableSignal<boolean>;
49
51
  draft: FilterState;
50
52
  advancedOpen: boolean;
51
53
  private searchTimer?;
54
+ private mediaQuery?;
55
+ constructor();
52
56
  get quickFields(): FilterFieldConfig[];
53
57
  get advancedFields(): FilterFieldConfig[];
58
+ get popupFields(): FilterFieldConfig[];
54
59
  get activeFilterCount(): number;
55
60
  get activeEntries(): Array<{
56
61
  id: string;
@@ -71,6 +76,7 @@ declare class FilterPanelComponent implements OnChanges {
71
76
  value: string | null;
72
77
  }>;
73
78
  ngOnDestroy(): void;
79
+ private readonly onViewportChange;
74
80
  static ɵfac: i0.ɵɵFactoryDeclaration<FilterPanelComponent, never>;
75
81
  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>;
76
82
  }