@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
|
|
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-
|
|
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-
|
|
105
|
-
}], propDecorators: {
|
|
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.
|
|
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.
|
|
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
|
}
|