UNPKG

@firestitch/filter

Version:
1 lines 349 kB
{"version":3,"file":"firestitch-filter.mjs","sources":["../../src/app/services/saved-filter-controller.service.ts","../../src/app/enums/action-mode.enum.ts","../../src/app/enums/action-type.enum.ts","../../src/app/enums/button-style.ts","../../src/app/enums/item-date-mode.enum.ts","../../src/app/enums/item-type.enum.ts","../../src/app/enums/menu-action-mode.enum.ts","../../src/app/enums/picker-view-type.enum.ts","../../src/app/models/action-menu-item.model.ts","../../src/app/models/action.model.ts","../../src/app/components/action-button/action-button.component.ts","../../src/app/components/action-button/action-button.component.html","../../src/app/components/action-kebab-actions/action-kebab-actions.component.ts","../../src/app/components/action-kebab-actions/action-kebab-actions.component.html","../../src/app/components/actions/actions.component.ts","../../src/app/components/actions/actions.component.html","../../src/app/helpers/compare.ts","../../src/app/helpers/encode-query-parm.ts","../../src/app/helpers/find-value.ts","../../src/app/helpers/get-range-name.ts","../../src/app/helpers/parse-date.ts","../../src/app/helpers/query-param-transformers.ts","../../src/app/helpers/parse-item-value-from-stored.ts","../../src/app/models/items/base-item.ts","../../src/app/models/items/base-date-range-item.ts","../../src/app/models/items/date-range-item.ts","../../src/app/models/items/date-time-range-item.ts","../../src/app/models/items/range-item.ts","../../src/app/models/items/week-item.ts","../../src/app/helpers/restore-items.ts","../../src/app/models/items/base-autocomplete-item.ts","../../src/app/models/items/autocomplete-chips-item.ts","../../src/app/models/items/autocomplete-item.ts","../../src/app/models/items/checkbox-item.ts","../../src/app/models/items/chips-item.ts","../../src/app/models/items/base-date-item.ts","../../src/app/models/items/date-item.ts","../../src/app/models/items/date-time-item.ts","../../src/app/models/items/text-item.ts","../../src/app/models/items/keyword-item.ts","../../src/app/models/items/select-item.ts","../../src/app/helpers/create-filter-item.ts","../../src/app/services/keyword-controller.service.ts","../../src/app/services/persistance-controller.service.ts","../../src/app/services/sort-controller.service.ts","../../src/app/services/query-param-controller.service.ts","../../src/app/services/filter-controller.service.ts","../../src/app/services/focus-controller.service.ts","../../src/app/components/filter-chips/filter-chips.component.ts","../../src/app/components/filter-chips/filter-chips.component.html","../../src/app/components/filter-drawer-actions/filter-drawer-actions.component.ts","../../src/app/components/filter-drawer-actions/filter-drawer-actions.component.html","../../src/app/injectors/filter-drawer-data.ts","../../src/app/injectors/filter-drawer-overlay.ts","../../src/app/services/root-filter-overlay.service.ts","../../src/app/services/filter-overlay.service.ts","../../src/app/directives/focus-to-item.directive.ts","../../src/app/components/filters-item/base-item/base-item.component.ts","../../src/app/components/filters-item/autocomplete/autocomplete.component.ts","../../src/app/components/filters-item/autocomplete/autocomplete.component.html","../../src/app/classes/actions-controller.ts","../../src/app/consts/filter-icon.ts","../../src/app/directives/heading.directive.ts","../../src/app/directives/status-bar.directive.ts","../../src/app/models/filter-config.ts","../../src/app/components/keyword-input/keyword-input.component.ts","../../src/app/components/keyword-input/keyword-input.component.html","../../src/app/components/saved-filter/saved-filter-manage/components/saved-filter-chips/saved-filter-chips.component.ts","../../src/app/components/saved-filter/saved-filter-manage/components/saved-filter-chips/saved-filter-chips.component.html","../../src/app/components/saved-filter/saved-filter-manage/saved-filter-manage.component.ts","../../src/app/components/saved-filter/saved-filter-manage/saved-filter-manage.component.html","../../src/app/components/saved-filter/saved-filter-autocomplete-chips/saved-filter-autocomplete-chips.component.ts","../../src/app/components/saved-filter/saved-filter-autocomplete-chips/saved-filter-autocomplete-chips.component.html","../../src/app/injectors/filter-config.ts","../../src/app/components/filter/filter.component.ts","../../src/app/components/filter/filter.component.html","../../src/app/components/filters-item/autocompletechips/autocompletechips.component.ts","../../src/app/components/filters-item/autocompletechips/autocompletechips.component.html","../../src/app/components/filters-item/checkbox/checkbox.component.ts","../../src/app/components/filters-item/checkbox/checkbox.component.html","../../src/app/components/filters-item/chips/chips.component.ts","../../src/app/components/filters-item/chips/chips.component.html","../../src/app/components/filters-item/date-range/date-range.component.ts","../../src/app/components/filters-item/date-range/date-range.component.html","../../src/app/components/filters-item/date/date.component.ts","../../src/app/components/filters-item/date/date.component.html","../../src/app/components/filters-item/range/range.component.ts","../../src/app/components/filters-item/range/range.component.html","../../src/app/components/filters-item/select/select.component.ts","../../src/app/components/filters-item/select/select.component.html","../../src/app/components/filters-item/text/text.component.ts","../../src/app/components/filters-item/text/text.component.html","../../src/app/components/filters-item/week/week.component.ts","../../src/app/components/filters-item/week/week.component.html","../../src/app/components/filters-item/filter-item.component.ts","../../src/app/components/filters-item/filter-item.component.html","../../src/app/components/filter-drawer/filter-drawer.component.ts","../../src/app/components/filter-drawer/filter-drawer.component.html","../../src/app/fs-filter.module.ts","../../src/firestitch-filter.ts"],"sourcesContent":["import { inject, Injectable, OnDestroy } from '@angular/core';\n\nimport { FsPrompt } from '@firestitch/prompt';\n\nimport { BehaviorSubject, Observable, of, Subject } from 'rxjs';\nimport {\n distinctUntilChanged,\n switchMap,\n tap,\n} from 'rxjs/operators';\n\nimport { KeyValue } from '../interfaces/external-params.interface';\nimport {\n IFilterSavedFilter,\n} from '../interfaces/saved-filters.interface';\n\nimport type { FilterController } from './filter-controller.service';\n\n\n@Injectable()\nexport class SavedFilterController implements OnDestroy {\n\n private _filterController: FilterController;\n private _savedFilters$ = new BehaviorSubject<IFilterSavedFilter[]>([]);\n private _activeFilter$ = new BehaviorSubject<IFilterSavedFilter>(null);\n private _enabled$ = new BehaviorSubject<boolean>(false);\n private _destroy$ = new Subject<void>();\n private _prompt = inject(FsPrompt);\n\n public get singularLabel(): string {\n return this._filterController.config.savedFilters?.label?.singular || 'Saved filter';\n }\n\n public get singularLabelLower(): string {\n return this.singularLabel.toLowerCase();\n }\n\n public get labelIcon(): string {\n return this._filterController.config.savedFilters?.label?.icon || 'save';\n }\n\n public get pluralLabel(): string {\n return this._filterController.config.savedFilters?.label?.plural || 'Saved filters';\n }\n\n public get pluralLabelLower(): string {\n return this.pluralLabel.toLowerCase();\n }\n\n public get enabled(): boolean {\n return this._enabled$.getValue();\n }\n\n public get enabled$(): Observable<boolean> {\n return this._enabled$\n .pipe(\n distinctUntilChanged(),\n );\n }\n\n public get savedFilters(): IFilterSavedFilter[] {\n return this._savedFilters$.getValue();\n }\n\n public set savedFilters(filters: IFilterSavedFilter[]) {\n this._savedFilters$.next(filters);\n }\n\n public get savedFilters$(): Observable<IFilterSavedFilter[]> {\n return this._savedFilters$\n .pipe(\n distinctUntilChanged(),\n );\n }\n\n public get activeFilter(): IFilterSavedFilter {\n return this._activeFilter$.getValue();\n }\n\n public get activeFilter$(): Observable<IFilterSavedFilter> {\n return this._activeFilter$\n .pipe(\n distinctUntilChanged(),\n );\n }\n\n public get activeFilterData(): KeyValue {\n return this._activeFilter$.getValue()?.filters;\n }\n\n public ngOnDestroy(): void {\n this._destroy$.next(null);\n this._destroy$.complete();\n }\n\n public init(\n filterController: FilterController,\n ): Observable<any> {\n this._filterController = filterController;\n \n if (!filterController.config.savedFilters) {\n this._setEnabledStatus(false);\n\n return of(null);\n }\n\n this._setEnabledStatus(true);\n\n return this.load();\n }\n\n public initSavedFilters(savedFilters: IFilterSavedFilter[]): void {\n this.savedFilters = savedFilters;\n const acitveFilter = this.savedFilters\n .find((f) => f.active);\n\n if (acitveFilter) {\n this._activeFilter$.next(acitveFilter);\n }\n }\n\n public load(): Observable<IFilterSavedFilter[]> {\n if (!this.enabled) {\n return of([]);\n }\n\n return this._filterController.config.savedFilters.load()\n .pipe(\n tap((savedFilters) => {\n this.initSavedFilters(savedFilters);\n }),\n );\n }\n\n public create(): Observable<IFilterSavedFilter> {\n return this._prompt.input({\n title: `Create ${this.singularLabelLower}`,\n label: 'Name',\n required: true,\n commitLabel: 'Create',\n dialogConfig: {\n restoreFocus: false,\n },\n })\n .pipe(\n switchMap((name) => {\n const data: IFilterSavedFilter = {\n name,\n };\n\n return this.save(data);\n }),\n tap((savedFilter) => {\n this.setActiveFilter(savedFilter);\n }),\n );\n }\n\n public save(savedFilter: IFilterSavedFilter): Observable<IFilterSavedFilter> {\n savedFilter = {\n ...(this.activeFilter || {}),\n ...savedFilter,\n filters: this._filterController.items\n .filter((item) => item.hasValue)\n .reduce((accum, item) => {\n return {\n ...accum,\n [item.name]: item.value,\n };\n }, {}),\n }; \n\n return this._filterController.config.savedFilters\n .save(savedFilter)\n .pipe(\n tap((_savedFilter) => {\n const exists = this.savedFilters.find((f) => f.id === _savedFilter.id);\n\n this.savedFilters = exists ? \n this.savedFilters\n .map((item) => item.id === _savedFilter.id ? _savedFilter : item) : \n [...this.savedFilters, _savedFilter];\n \n this.setActiveFilter(_savedFilter);\n }),\n );\n }\n\n public get orderable(): boolean {\n return !!this._filterController.config.savedFilters.order;\n }\n\n public order(savedFilters: IFilterSavedFilter[]): Observable<IFilterSavedFilter[]> {\n return this._filterController.config.savedFilters\n .order(savedFilters)\n .pipe(\n tap(() => {\n this.savedFilters = [\n ...savedFilters,\n ];\n }),\n );\n }\n\n public delete(savedFilter: IFilterSavedFilter): Observable<IFilterSavedFilter> {\n return this._filterController.config.savedFilters\n .delete(savedFilter)\n .pipe(\n tap(() => {\n this.savedFilters = this.savedFilters\n .filter((f) => f.id !== savedFilter.id);\n \n if (this.activeFilter?.id === savedFilter.id) {\n this.setActiveFilter(null);\n }\n }),\n );\n }\n\n public setActiveFilter(savedFilter: IFilterSavedFilter): void {\n if (savedFilter) {\n const existingFilter = this.savedFilters\n .find((f) => f.id === savedFilter.id);\n\n if (!existingFilter) {\n throw new Error(`Saved filter cannot be activated, because it does not exists. Filter ID = ${savedFilter.id}`);\n }\n\n this._filterController.values = existingFilter.filters; \n this._activeFilter$.next(existingFilter);\n } else {\n this._activeFilter$.next(null);\n }\n }\n\n private _setEnabledStatus(value: boolean): void {\n this._enabled$.next(value);\n }\n}\n","export enum ActionMode {\n Button = 'button',\n SelectButton = 'selectButton',\n Menu = 'menu',\n File = 'file',\n}\n","import { ButtonStyle } from './button-style';\n\nexport enum ActionType {\n Basic = ButtonStyle.Basic,\n Raised = ButtonStyle.Raised,\n Icon = ButtonStyle.Icon,\n Fab = ButtonStyle.Fab,\n MiniFab = ButtonStyle.MiniFab,\n Flat = ButtonStyle.Flat,\n Stroked = ButtonStyle.Stroked, \n}\n","export enum ButtonStyle {\n Basic = 'basic',\n Raised = 'raised',\n Icon = 'icon',\n Fab = 'fab',\n MiniFab = 'mini-fab',\n Flat = 'flat',\n Stroked = 'stroked',\n}\n","export enum ItemDateMode {\n Calendar = 'calendar',\n ScrollMonthYear = 'monthyear',\n ScrollMonthDayYear = 'monthdayyear'\n}\n","export enum ItemType {\n Text = 'text',\n Select = 'select',\n Range = 'range',\n Date = 'date',\n DateTime = 'datetime',\n DateRange = 'daterange',\n Week = 'week',\n DateTimeRange = 'datetimerange',\n AutoComplete = 'autocomplete',\n AutoCompleteChips = 'autocompletechips',\n Checkbox = 'checkbox',\n Chips = 'chips',\n Keyword = 'keyword',\n}\n","export enum MenuActionMode {\n Menu = 'menu',\n File = 'file',\n Group = 'group',\n}\n","export enum PickerViewType {\n Date = \"date\",\n DateTime = \"datetime\",\n Time = \"time\",\n Week = \"week\",\n MonthRange = \"monthrange\"\n}\n","import { BehaviorSubject, Observable } from 'rxjs';\n\nimport { MenuActionMode } from '../enums';\nimport {\n FsFilterActionClickFn, FsFilterActionShowFn,\n FsFilterFileActionErrorFn,\n FsFilterFileActionSelectFn,\n IFsFilterMenuActionFileItem,\n IFsFilterMenuActionGroupItem,\n IFsFilterMenuActionItem, IFsFilterMenuActionLink,\n} from '../interfaces/action.interface';\n\n\nexport class ActionMenuItem {\n\n public icon: string;\n public label: string;\n public mode: MenuActionMode;\n public fileSelected: FsFilterFileActionSelectFn;\n public fileError: FsFilterFileActionErrorFn;\n public multiple: boolean;\n public accept: string;\n public minWidth: number;\n public minHeight: number;\n public maxWidth: number;\n public maxHeight: number;\n public imageQuality: number;\n public click: FsFilterActionClickFn;\n public routerLink: IFsFilterMenuActionLink;\n public items: ActionMenuItem[] = [];\n\n private _isGroup = false;\n private _showFn: FsFilterActionShowFn;\n private _visible$ = new BehaviorSubject<boolean>(true);\n private _disabled$ = new BehaviorSubject<boolean>(false);\n\n constructor(\n config: IFsFilterMenuActionGroupItem | IFsFilterMenuActionItem = {},\n private _parent?: ActionMenuItem,\n ) {\n this._init(config);\n }\n\n public get isGroup(): boolean {\n return this._isGroup;\n }\n\n public get visible(): boolean {\n return this._visible$.getValue();\n }\n\n public get visible$(): Observable<boolean> {\n return this._visible$.asObservable();\n }\n\n public set disabled(value: boolean) {\n this._disabled$.next(value);\n }\n\n public get disabled(): boolean {\n return this._disabled$.getValue();\n }\n\n public get disabled$(): Observable<boolean> {\n return this._disabled$.asObservable();\n }\n\n public updateVisibility(): void {\n const visible = this._showFn ? this._showFn() : true;\n\n if (!visible || !this.isGroup) {\n this._visible$.next(visible);\n\n return;\n }\n\n const numberOfVisibleChildren = this.items\n .reduce((acc, item) => {\n item.updateVisibility();\n\n if (item.visible) {\n acc++;\n }\n\n return acc;\n }, 0);\n\n this._visible$.next(!!numberOfVisibleChildren);\n }\n\n private _initFile(config: IFsFilterMenuActionFileItem): void {\n this.multiple = config.multiple;\n this.accept = config.accept;\n this.minWidth = config.minWidth;\n this.minHeight = config.minHeight;\n this.maxWidth = config.maxWidth;\n this.maxHeight = config.maxHeight;\n this.imageQuality = config.imageQuality;\n this.fileSelected = config.fileSelected;\n }\n\n private _init(\n config: IFsFilterMenuActionGroupItem | IFsFilterMenuActionItem | IFsFilterMenuActionFileItem,\n ) {\n this.label = config.label;\n this.icon = config.icon;\n this.mode = config.mode || MenuActionMode.Menu;\n this._showFn = config.show;\n\n if (this.mode === MenuActionMode.File) {\n this._initFile(config as IFsFilterMenuActionFileItem);\n }\n\n if ('items' in config) {\n this._isGroup = true;\n\n if (Array.isArray(config.items)) {\n this.items = config\n .items\n .map((item) => new ActionMenuItem(item, this));\n }\n\n this.updateVisibility();\n } else {\n this.click = config.click;\n this.routerLink = config.link;\n\n if (!this._parent) {\n this.updateVisibility();\n }\n }\n }\n}\n","import { ThemePalette } from '@angular/material/core';\n\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nimport { ButtonStyle } from '../enums';\nimport { ActionMode } from '../enums/action-mode.enum';\nimport {\n FsFilterAction,\n FsFilterActionClickFn,\n FsFilterActionDisabledFn,\n FsFilterActionShowFn,\n FsFilterFileActionErrorFn,\n FsFilterFileActionSelectFn,\n IFsFilterFileAction,\n IFsFilterSelectButtonAction,\n} from '../interfaces/action.interface';\n\nimport { ActionMenuItem } from './action-menu-item.model';\nimport { FsFilterConfig } from './filter-config';\n\n\nexport class Action {\n\n public primary = true;\n public icon: string;\n public iconPlacement: 'left' | 'right';\n public label: string;\n public value: any;\n public menu: boolean;\n public color: ThemePalette;\n public customize: boolean;\n public className: string;\n public click: FsFilterActionClickFn;\n public style: ButtonStyle;\n public tabIndex: number;\n public fileSelected: FsFilterFileActionSelectFn;\n public fileError: FsFilterFileActionErrorFn;\n public multiple: boolean;\n public accept: string;\n public tooltip: string;\n public minWidth: number;\n public minHeight: number;\n public maxWidth: number;\n public maxHeight: number;\n public imageQuality: number;\n public change: (value: any) => void;\n public values: any[];\n public mode: ActionMode;\n public isReorderAction = false;\n public deselect = false;\n public classArray: string[] = [];\n public items: ActionMenuItem[] = [];\n\n private _visible$ = new BehaviorSubject<boolean>(true);\n private _disabled$ = new BehaviorSubject<boolean>(false);\n private _showFn: FsFilterActionShowFn;\n private _disabledFn: FsFilterActionDisabledFn;\n\n constructor(filterConfig: FsFilterConfig, actionConfig: FsFilterAction = {}) {\n this._init(filterConfig, actionConfig);\n }\n\n public get visible(): boolean {\n return this._visible$.getValue();\n }\n\n public get visible$(): Observable<boolean> {\n return this._visible$.asObservable();\n }\n\n public set disabled(value: boolean) {\n this._disabled$.next(value);\n }\n\n public get disabled(): boolean {\n return this._disabled$.getValue();\n }\n\n public get disabled$(): Observable<boolean> {\n return this._disabled$.asObservable();\n }\n\n public updateVisibility(): void {\n const visible = this._showFn ? this._showFn() : true;\n\n if (!visible || this.mode !== ActionMode.Menu) {\n this._visible$.next(visible);\n\n return;\n }\n\n const hasVisibleChildren = this.items.some((item) => item.visible);\n this._visible$.next(hasVisibleChildren);\n }\n\n public updateDisabledState(): void {\n if (this._disabledFn) {\n this.disabled = this._disabledFn();\n }\n }\n\n private _init(filterConfig: FsFilterConfig, config: FsFilterAction = {}): void {\n config.mode = config.mode ?? ActionMode.Button;\n this._initCore(filterConfig, config);\n\n switch (config.mode) {\n case ActionMode.Button: {\n this.customize = config.customize;\n this.click = config.click ?? (() => {\n // do nothing\n });\n this._disabledFn = config.disabled;\n\n this.updateDisabledState();\n } break;\n\n case ActionMode.Menu: {\n if (config.items && Array.isArray(config.items)) {\n this.items = config.items.map((item) => new ActionMenuItem(item));\n }\n } break;\n\n case ActionMode.SelectButton: {\n this._initSelectButton(config);\n } break;\n\n case ActionMode.File: {\n this._initFile(config);\n } break;\n }\n\n this.updateVisibility();\n }\n\n private _initCore(filterConfig: FsFilterConfig, config: FsFilterAction): void {\n this.primary = config.primary ?? true;\n this.color = config.color;\n this.tooltip = config.tooltip;\n this.label = config.label;\n this.mode = config.mode;\n this.icon = config.icon;\n this.iconPlacement = config.iconPlacement;\n this._showFn = config.show;\n this.tabIndex = config.tabIndex ?? 0;\n this.menu = config.menu;\n\n if (!this.style) {\n this.style = config.icon && !config.label ?\n ButtonStyle.Icon :\n (config.style || filterConfig.buttonStyle || ButtonStyle.Flat);\n\n if(!this.primary && this.style === ButtonStyle.Flat) {\n this.style = ButtonStyle.Stroked;\n }\n }\n\n if (config.className) {\n this.className = config.className;\n this.classArray = this.className\n .split(' ');\n }\n\n if (!this.primary && this.mode === ActionMode.SelectButton) {\n this.color = null; // should be null because of styles logic in select-button\n }\n\n if (this.primary) {\n this.color = 'primary';\n }\n\n }\n\n private _initSelectButton(config: IFsFilterSelectButtonAction): void {\n this.values = config.values;\n this.value = config.default;\n this.change = config.change;\n this.deselect = config.deselect ?? false;\n }\n\n private _initFile(config: IFsFilterFileAction): void {\n this.fileSelected = config.select;\n this.fileError = config.error;\n this.accept = config.accept;\n this.imageQuality = config.imageQuality;\n this.minWidth = config.minWidth;\n this.minHeight = config.minHeight;\n this.maxWidth = config.maxWidth;\n this.maxHeight = config.maxHeight;\n this.click = config.click ?? (() => {\n //\n });\n this._disabledFn = config.disabled;\n\n if ((config).multiple !== undefined) {\n this.multiple = (config).multiple;\n }\n\n this.updateDisabledState();\n }\n}\n","import { ChangeDetectionStrategy, Component, Input } from '@angular/core';\n\nimport { ButtonStyle } from '../../enums';\nimport { Action } from '../../models/action.model';\nimport { NgSwitch, NgSwitchCase, NgClass, NgTemplateOutlet, NgSwitchDefault, NgIf, AsyncPipe } from '@angular/common';\nimport { MatIconButton, MatFabButton, MatMiniFabButton, MatButton } from '@angular/material/button';\nimport { FsFormModule } from '@firestitch/form';\nimport { MatIcon } from '@angular/material/icon';\n\n\n@Component({\n selector: 'fs-filter-action-button',\n templateUrl: './action-button.component.html',\n styleUrl: './action-button.component.scss',\n host: { class: 'action-button' },\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [\n NgSwitch,\n NgSwitchCase,\n MatIconButton,\n NgClass,\n NgTemplateOutlet,\n MatFabButton,\n MatMiniFabButton,\n NgSwitchDefault,\n MatButton,\n FsFormModule,\n NgIf,\n MatIcon,\n AsyncPipe,\n ],\n})\nexport class FsFilterActionButtonComponent {\n\n public ButtonStyle = ButtonStyle;\n\n @Input()\n public action: Action;\n\n}\n","<ng-container [ngSwitch]=\"action.style\">\n <button\n type=\"button\"\n *ngSwitchCase=\"ButtonStyle.Icon\"\n mat-icon-button\n (click)=\"action.click && action.click($event)\"\n [color]=\"action.color\"\n [ngClass]=\"action.classArray\"\n [disabled]=\"action.disabled$ | async\"\n [tabIndex]=\"action.tabIndex\">\n <ng-template [ngTemplateOutlet]=\"buttonContent\"></ng-template>\n </button>\n <!-- Fab button -->\n <button\n type=\"button\"\n *ngSwitchCase=\"ButtonStyle.Fab\"\n mat-fab\n (click)=\"action.click && action.click($event)\"\n [color]=\"action.color\"\n [ngClass]=\"action.classArray\"\n [disabled]=\"action.disabled$ | async\"\n [tabIndex]=\"action.tabIndex\">\n <ng-template [ngTemplateOutlet]=\"buttonContent\"></ng-template>\n </button>\n <!-- Mini Fab button -->\n <button\n type=\"button\"\n *ngSwitchCase=\"ButtonStyle.MiniFab\"\n mat-mini-fab\n (click)=\"action.click && action.click($event)\"\n [color]=\"action.color\"\n [ngClass]=\"action.classArray\"\n [disabled]=\"action.disabled$ | async\"\n [tabIndex]=\"action.tabIndex\">\n <ng-template [ngTemplateOutlet]=\"buttonContent\"></ng-template>\n </button>\n <button\n type=\"button\"\n *ngSwitchDefault\n mat-button\n class=\"button-basic\"\n [ngClass]=\"{ \n 'mat-mdc-raised-button': action.style === ButtonStyle.Raised,\n 'mat-mdc-unelevated-button': action.style === ButtonStyle.Flat,\n 'mat-mdc-outlined-button': action.style === ButtonStyle.Stroked,\n 'mat-mdc-icon-button': action.style === ButtonStyle.Icon,\n 'icon-placement-right': action.iconPlacement === 'right'\n }\"\n (click)=\"action.click && action.click($event)\"\n [color]=\"action.color\"\n [class]=\"action.classArray.join(' ')\"\n [disabled]=\"action.disabled$ | async\"\n [tabIndex]=\"action.tabIndex\">\n <ng-template [ngTemplateOutlet]=\"buttonContent\"></ng-template>\n </button>\n <ng-template #buttonContent>\n <ng-container *ngIf=\"!action.icon else withIcon\">\n {{ action.label }}\n </ng-container>\n <ng-template #withIcon>\n <mat-icon>\n {{ action.icon }}\n </mat-icon>\n {{ action.label }}\n </ng-template>\n </ng-template>\n</ng-container>","import { ChangeDetectionStrategy, Component, Input } from '@angular/core';\n\nimport { Action } from '../../models/action.model';\nimport { MatIconButton } from '@angular/material/button';\nimport { FsMenuModule } from '@firestitch/menu';\nimport { MatIcon } from '@angular/material/icon';\nimport { AsyncPipe } from '@angular/common';\n\n\n@Component({\n selector: 'fs-filter-action-kebab-actions',\n styleUrls: ['./action-kebab-actions.component.scss'],\n templateUrl: './action-kebab-actions.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [\n MatIconButton,\n FsMenuModule,\n MatIcon,\n AsyncPipe,\n ],\n})\nexport class FsFilterActionKebabActionsComponent {\n\n @Input()\n public kebabActions: Action[];\n\n public actionClick(action, event: MouseEvent) {\n if(action.click) {\n action.click(event);\n }\n }\n}\n","<button\n type=\"button\"\n mat-icon-button\n class=\"menu-button\"\n [fsMenuTriggerFor]=\"kebabActionsMenu\">\n <mat-icon>more_vert</mat-icon>\n</button>\n<fs-menu #kebabActionsMenu>\n @for (action of kebabActions; track action) {\n @switch (action.mode) {\n <!-- Case when actions was collapsed from action with mode = 'menu'-->\n @case ('menu') {\n @for (childAction of action.items; track childAction) {\n @if (childAction.isGroup) {\n <fs-menu-group>\n <ng-template fs-group-menu-item-template>\n {{ action.label }} <mat-icon style=\"margin: 0;\">arrow_right</mat-icon> {{childAction.label}}\n </ng-template>\n @for (subAction of childAction.items; track subAction) {\n <ng-template\n fs-menu-item\n [link]=\"subAction.routerLink?.link\"\n [queryParams]=\"subAction.routerLink?.queryParams\"\n [hidden]=\"!(subAction.visible$ | async)\"\n (click)=\"actionClick(subAction, $event)\">\n @if (subAction.icon) {\n <mat-icon>{{subAction.icon}}</mat-icon>\n }\n {{subAction.label}}\n </ng-template>\n }\n </fs-menu-group>\n } @else {\n <ng-template\n fs-menu-item\n [link]=\"childAction.routerLink?.link\"\n [queryParams]=\"childAction.routerLink?.queryParams\"\n [hidden]=\"!(childAction.visible$ | async)\"\n (click)=\"actionClick(childAction, $event)\">\n @if (childAction.icon) {\n <mat-icon>{{childAction.icon}}</mat-icon>\n }\n {{ action.label }} <mat-icon style=\"margin: 0;\">arrow_right</mat-icon>{{ childAction.label }}\n </ng-template>\n }\n }\n }\n @case ('file') {\n <ng-template\n fs-menu-file-item\n [fsClass]=\"action.classArray\"\n [multiple]=\"action.multiple\"\n [accept]=\"action.accept || '*'\"\n [minWidth]=\"action.minWidth\"\n [minHeight]=\"action.minHeight\"\n [imageWidth]=\"action.maxWidth\"\n [imageHeight]=\"action.maxHeight\"\n (error)=\"action.fileError($event)\"\n (select)=\"action.fileSelected($event)\"\n (click)=\"action.click($event)\">\n @if (action.icon) {\n <mat-icon>{{action.icon}}</mat-icon>\n } {{action.label}}\n </ng-template>\n }\n @default {\n <ng-template\n fs-menu-item\n (click)=\"action.click($event)\"\n [fsClass]=\"action.classArray\">\n @if (action.icon) {\n <mat-icon>{{action.icon}}</mat-icon>\n } {{action.label}}\n </ng-template>\n }\n }\n }\n </fs-menu>\n","import {\n ChangeDetectionStrategy,\n Component,\n Input,\n} from '@angular/core';\n\nimport { MatSelect } from '@angular/material/select';\n\nimport { ActionMode, ButtonStyle } from '../../enums';\nimport { Action } from '../../models/action.model';\nimport { FsFilterActionButtonComponent } from '../action-button/action-button.component';\nimport { FsPopoverModule } from '@firestitch/popover';\nimport { FsMenuModule } from '@firestitch/menu';\nimport { MatIcon } from '@angular/material/icon';\nimport { FsSelectButtonModule } from '@firestitch/selectbutton';\nimport { NgClass, AsyncPipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { FsFormModule } from '@firestitch/form';\nimport { MatOption } from '@angular/material/core';\nimport { FsFileModule } from '@firestitch/file';\nimport { FsFilterActionKebabActionsComponent } from '../action-kebab-actions/action-kebab-actions.component';\n\n\n@Component({\n selector: 'fs-filter-actions',\n templateUrl: './actions.component.html',\n styleUrls: ['./actions.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [\n FsFilterActionButtonComponent,\n FsPopoverModule,\n FsMenuModule,\n MatIcon,\n MatSelect,\n FsSelectButtonModule,\n NgClass,\n FormsModule,\n FsFormModule,\n MatOption,\n FsFileModule,\n FsFilterActionKebabActionsComponent,\n AsyncPipe,\n ],\n})\nexport class FsFilterActionsComponent {\n\n @Input()\n public kebabActions: Action[] = [];\n\n @Input()\n public actions: Action[] = [];\n\n public ButtonStyle = ButtonStyle;\n public ActionMode = ActionMode;\n\n public actionChange(action: Action, value: any, selectButton: MatSelect): void {\n if(action.change) {\n action.change(value); \n\n if(action.deselect) {\n selectButton.writeValue(null);\n }\n }\n }\n\n public actionClick(action, event: MouseEvent) {\n if(action.click) {\n action.click(event);\n }\n }\n\n}\n","@for (action of actions; track action) {\n @switch (action.mode) {\n @case (ActionMode.Button) {\n <fs-filter-action-button\n [action]=\"action\"\n class=\"action\"\n fsPopover\n [enabled]=\"!!action.tooltip\"\n [text]=\"action.tooltip\">\n </fs-filter-action-button>\n }\n @case (ActionMode.Menu) {\n <fs-filter-action-button\n class=\"action\"\n [action]=\"action\"\n [fsMenuTriggerFor]=\"someRef\"\n fsPopover\n [enabled]=\"!!action.tooltip\"\n [text]=\"action.tooltip\">\n </fs-filter-action-button>\n <fs-menu\n #someRef\n class=\"action\">\n @for (childAction of action.items; track childAction) {\n @if (childAction.isGroup) {\n <fs-menu-group [label]=\"childAction.label\">\n @for (subAction of childAction.items; track subAction) {\n @switch (subAction.mode) {\n @case ('menu') {\n <ng-template\n fs-menu-item\n [link]=\"subAction.routerLink?.link\"\n [queryParams]=\"subAction.routerLink?.queryParams\"\n [hidden]=\"(subAction.visible$ | async) === false\"\n (click)=\"actionClick(subAction, $event)\">\n @if (subAction.icon) {\n <mat-icon>\n {{ subAction.icon }}\n </mat-icon>\n }\n {{ subAction.label }}\n </ng-template>\n }\n @case ('file') {\n <ng-template\n fs-menu-file-item\n [multiple]=\"subAction.multiple\"\n [accept]=\"subAction.accept || '*'\"\n [minWidth]=\"subAction.minWidth\"\n [minHeight]=\"subAction.minHeight\"\n [imageWidth]=\"subAction.maxWidth\"\n [imageHeight]=\"subAction.maxHeight\"\n (select)=\"subAction.fileSelected($event)\"\n [hidden]=\"(subAction.visible$ | async) === false\"\n (click)=\"actionClick(subAction, $event)\">\n @if (subAction.icon) {\n <mat-icon>\n {{ subAction.icon }}\n </mat-icon>\n }\n {{ subAction.label }}\n </ng-template>\n }\n }\n }\n </fs-menu-group>\n } @else {\n @switch (childAction.mode) {\n @case ('menu') {\n <ng-template\n fs-menu-item\n [link]=\"childAction.routerLink?.link\"\n [queryParams]=\"childAction.routerLink?.queryParams\"\n [hidden]=\"(childAction.visible$ | async) === false\"\n (click)=\"childAction.click($event);\">\n @if (childAction.icon) {\n <mat-icon>\n {{ childAction.icon }}\n </mat-icon>\n }\n {{ childAction.label }}\n </ng-template>\n }\n @case ('file') {\n <ng-template\n fs-menu-file-item\n [multiple]=\"childAction.multiple\"\n [accept]=\"childAction.accept || '*'\"\n [minWidth]=\"childAction.minWidth\"\n [minHeight]=\"childAction.minHeight\"\n [imageWidth]=\"childAction.maxWidth\"\n [imageHeight]=\"childAction.maxHeight\"\n (select)=\"childAction.fileSelected($event)\"\n [hidden]=\"(childAction.visible$ | async) === false\">\n @if (childAction.icon) {\n <mat-icon>\n {{ childAction.icon }}\n </mat-icon>\n }\n {{ childAction.label }}\n </ng-template>\n }\n }\n }\n }\n </fs-menu>\n }\n @case (ActionMode.SelectButton) {\n <mat-select\n class=\"action action-select-button\"\n [buttonType]=\"'basic'\"\n [ngClass]=\"{\n 'mat-mdc-raised-button': action.style === ButtonStyle.Raised,\n 'mat-mdc-unelevated-button': action.style === ButtonStyle.Flat,\n 'mat-mdc-outlined-button': action.style === ButtonStyle.Stroked,\n }\"\n [placeholder]=\"action.label\"\n [(ngModel)]=\"action.value\"\n [color]=\"action.color\"\n (ngModelChange)=\"actionChange(action, $event, selectButton)\"\n fsSelectButton\n #selectButton\n [deselectOnChange]=\"action.deselect\">\n @for (item of action.values; track item) {\n <mat-option\n [value]=\"item.value\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n }\n @case (ActionMode.File) {\n <fs-file\n class=\"action action-button\"\n [accept]=\"action.accept || '*'\"\n [multiple]=\"action.multiple\"\n [minWidth]=\"action.minWidth\"\n [minHeight]=\"action.minHeight\"\n [imageWidth]=\"action.maxWidth\"\n [imageHeight]=\"action.maxHeight\"\n (select)=\"action.fileSelected($event)\"\n (error)=\"action.fileError($event)\"\n (clicked)=\"action.click($event)\"\n fsPopover\n [enabled]=\"!!action.tooltip\"\n [text]=\"action.tooltip\">\n <fs-filter-action-button [action]=\"action\"></fs-filter-action-button>\n </fs-file>\n }\n }\n}\n@if (kebabActions?.length) {\n <fs-filter-action-kebab-actions [kebabActions]=\"kebabActions\"></fs-filter-action-kebab-actions>\n}\n","import { isObject } from 'lodash-es';\n\n\nexport function objectsAreEquals(obj1, obj2) {\n const oldKeys = Object.keys(obj1);\n const currKeys = Object.keys(obj2);\n\n if (oldKeys.length !== currKeys.length) {\n return false;\n }\n\n for (const key in obj1) {\n if (obj1.hasOwnProperty(key)) {\n const oldItem = obj1[key];\n const currItem = obj2[key];\n const isArrays = Array.isArray(oldItem) && Array.isArray(currItem);\n const isObjects = isObject(oldItem) && isObject(currItem);\n\n if (isArrays && !arraysAreEquals(oldItem, currItem)) {\n return false;\n } else if (isObjects && !objectsAreEquals(oldItem, currItem)) {\n return false;\n } else if (!isArrays && !isObjects && oldItem !== currItem) {\n return false;\n }\n }\n }\n\n return true;\n}\n\nexport function arraysAreEquals(arr1, arr2) {\n if (arr1?.length !== arr2.length) {\n return false;\n }\n\n for (const el of arr1) {\n if (arr2.indexOf(el) === -1) {\n return false;\n }\n }\n\n return true;\n}\n","export function encodeQueryParam(value) {\n return value\n .replace(/,/g, '\\\\,')\n .replace(/:/g, '\\\\:');\n}\n","export function findValue(values, value, children) {\n for (let i = 0; i < values.length; i++) {\n const val = values[i];\n\n if (val[children]) {\n return findValue(val[children], value, children);\n }\n\n if (val.value === value) {\n return val;\n }\n }\n\n return undefined;\n}\n","export function getRangeName(name: string, range: string) {\n return name.concat(range.charAt(0).toUpperCase()).concat(range.slice(1));\n}\n","import { isDate, isValid, parseISO } from 'date-fns';\n\nexport function parseDate(value: string) {\n if (value && (!isDate(value) || !isValid(value))) {\n return parseISO(value);\n }\n\n return value;\n}\n","export function filterToQueryParam(value, name): string {\n return `${encodeURIComponent(value)}:${encodeURIComponent(name)}`;\n}\n\nexport function filterFromQueryParam(param: string): string[] {\n const parts = param.split(/(?<!\\\\):/);\n\n return [decodeURIComponent(parts[0]), decodeURIComponent(parts[1])];\n}\n\n","import { ItemType } from '../enums/item-type.enum';\nimport { SelectItem } from '../models/items';\n\nimport { getRangeName } from './get-range-name';\nimport { filterFromQueryParam } from './query-param-transformers';\n\nexport function parseItemValueFromStored(item, params) {\n const param = params[item.name];\n\n switch (item.type) {\n case ItemType.Range: {\n const min = params[getRangeName(item.name, 'min')];\n const max = params[getRangeName(item.name, 'max')];\n\n return { min: min, max: max };\n }\n\n case ItemType.DateRange: case ItemType.DateTimeRange: {\n const from = params[getRangeName(item.name, 'from')];\n const to = params[getRangeName(item.name, 'to')];\n\n return { from: from, to: to };\n }\n\n case ItemType.Week: {\n const from = params[getRangeName(item.name, 'from')];\n const to = params[getRangeName(item.name, 'to')];\n const period = params[`${item.name}Period`];\n\n return { from, to, period };\n }\n\n case ItemType.Select: {\n return itemTypeSelect(item, param); \n }\n\n case ItemType.Checkbox: {\n return param === 'true' || param === true;\n }\n\n case ItemType.AutoComplete: {\n const filterParts = filterFromQueryParam(param);\n\n return {\n name: filterParts[1],\n value: filterParts[0],\n };\n }\n\n case ItemType.AutoCompleteChips: \n case ItemType.Chips: {\n const filterParts = param.split(/(?<!\\\\),/);\n\n return filterParts.reduce((arry, value) => {\n const chipParts = filterFromQueryParam(value);\n\n arry.push({\n name: chipParts[1]\n .replace(/\\\\,/g, ',')\n .replace(/\\\\:/g, ':'),\n value: chipParts[0],\n });\n\n return arry;\n }, []);\n }\n\n default: {\n return param;\n }\n }\n}\n\nfunction itemTypeSelect(item: SelectItem, param) {\n if (!param) {\n return [];\n }\n\n const values = param.split(',');\n\n return values;\n}\n","\nimport { BehaviorSubject, forkJoin, Observable, of, Subject } from 'rxjs';\nimport {\n map,\n switchMap,\n tap,\n} from 'rxjs/operators';\n\nimport { isFunction } from 'lodash-es';\n\nimport type { FilterComponent } from '../../components/filter/filter.component';\nimport { ItemType } from '../../enums/item-type.enum';\nimport { IFilterConfigItem } from '../../interfaces/config.interface';\nimport { IFilterDefaultFn } from '../../interfaces/items/base.interface';\n\n\nexport abstract class BaseItem<T extends IFilterConfigItem> {\n\n public name: string;\n public label: any;\n public chipLabel: string | string[];\n public defaultValueFn: IFilterDefaultFn;\n public defaultValue: any;\n public clearable: boolean;\n public persistanceDisabled: boolean;\n public queryParamsDisabled: boolean;\n public changeCallback: (item: BaseItem<T>, filter: FilterComponent) => void;\n public initCallback: (item: BaseItem<T>, filter?) => void;\n\n protected readonly _type: T['type'];\n\n protected _valuesFn: (keyword?: string, filter?: FilterComponent) => Observable<any> | any[];\n\n private _hidden$ = new BehaviorSubject(false);\n private _value$ = new BehaviorSubject<{ value: any, emitChange: boolean }>({ value: undefined, emitChange: true });\n private _values$ = new BehaviorSubject<{ name: string, value: string|null }[]>(null);\n private _destroy$ = new Subject<void>();\n\n constructor(\n itemConfig: T,\n protected _additionalConfig: unknown,\n protected _filter: FilterComponent,\n ) {\n this._type = itemConfig.type;\n this._initConfig(itemConfig);\n }\n\n public get filter(): FilterComponent {\n return this._filter;\n }\n\n public get hidden$(): Observable<boolean> {\n return this._hidden$.asObservable();\n }\n\n public get visible$(): Observable<boolean> {\n return this._hidden$\n .pipe(\n map((hidden) => !hidden),\n );\n }\n\n public get hidden(): boolean {\n return this._hidden$.getValue();\n }\n\n public get isTypeAutocomplete() {\n return this.type === ItemType.AutoComplete;\n }\n\n public get isTypeAutocompleteChips() {\n return this.type === ItemType.AutoCompleteChips;\n }\n\n public get isTypeChips() {\n return this.type === ItemType.Chips;\n }\n\n public get isTypeCheckbox() {\n return this.type === ItemType.Checkbox;\n }\n\n public get isTypeSelect() {\n return this.type === ItemType.Select;\n }\n\n public get isTypeDate() {\n return this.type === ItemType.Date;\n }\n\n public get isTypeDateRange() {\n return this.type === ItemType.DateRange;\n }\n\n public get isTypeRange() {\n return this.type === ItemType.Range;\n }\n\n public get isTypeDateTimeRange() {\n return this.type === ItemType.DateTimeRange;\n }\n\n public get isTypeDateTime() {\n return this.type === ItemType.DateTime;\n }\n\n public get isTypeKeyword() {\n return this.type === ItemType.Keyword;\n }\n\n public get isChipVisible(): boolean {\n return this.hasValue;\n }\n\n public get destroy$() {\n return this._destroy$.asObservable();\n }\n\n public get type(): T['type'] {\n return this._type;\n }\n\n public get hasValue() {\n return this.value !== null && this.value !== undefined;\n }\n\n public get hasValue$() {\n return this.value$\n .pipe(\n map(() => this.hasValue),\n );\n }\n\n public get chips$() {\n return this.value$\n .pipe(\n map(() => this.chips || []),\n );\n }\n\n public set values(values) {\n this._values$.next(values);\n }\n\n public get values() {\n return this._values$.getValue();\n }\n\n public get values$(): Observable<any> {\n return this._values$.asObservable();\n }\n\n public get valueEvent$() {\n return this._value$.asObservable();\n }\n\n public get value$() {\n return this._value$.asObservable()\n .pipe(\n map((event) => event.value),\n );\n }\n\n public get value() {\n return this._value$.getValue().value;\n }\n\n public set value(value) {\n this.setValue(value);\n }\n\n /**\n * @deprecated Use value instead\n */\n public set model(value) {\n this.value = value;\n }\n\n public setValue(value: unknown, emitChange: boolean = true) {\n this._value$.next({ value, emitChange });\n }\n\n public get queryParam(): Record<string, unknown> {\n return this.query;\n }\n\n public hide() {\n this._hidden$.next(true);\n }\n\n public show() {\n this._hidden$.next(false);\n }\n\n public get query(): Record<string, any> {\n if(!this.hasValue) {\n return {};\n }\n\n return {\n [this.name]: this.value,\n };\n }\n\n public init(value: unknown): Observable<any> {\n return forkJoin([\n this.loadDefault(),\n this.loadValues(),\n ])\n .pipe(\n tap(() => this.initValue(value)), \n );\n }\n\n public loadDefault(): Observable<any> {\n return this.defaultValueFn()\n .pipe(\n tap((value) => {\n this.defaultValue = value;\n }),\n );\n }\n\n public loadValues() {\n return of(null)\n .pipe(\n switchMap(() => {\n if((this.type === ItemType.AutoComplete || this.type === ItemType.AutoCompleteChips)) {\n return of([]);\n } \n\n if (typeof this._valuesFn === 'function') {\n const values = this._valuesFn();\n\n return values instanceof Observable ? values : of(values);\n }\n \n return of(this.values);\n }),\n tap((values) => {\n this.values = (values || [])\n .map((item) => ({\n ...item,\n value: (item.value ?? null) === null ? null : `${item.value}`,\n }));\n }),\n );\n }\n\n public initValue(value: unknown) {\n this.setValue(value === undefined ? this.defaultValue : value);\n }\n\n public clear(emitChange: boolean = true) {\n this.setValue(undefined, emitChange);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n public clearByName(name: string, emitChange: boolean = true) {\n //\n }\n\n public destroy() {\n this._destroy$.next(null);\n this._destroy$.complete();\n }\n\n public clone(): BaseItem<T> {\n return Object.assign(\n Object.create(Object.getPrototypeOf(this)), \n this,\n );\n }\n\n private _initConfig(item: T) {\n const hidden = item.hide ?? !(item.show ?? true);\n \n this.name = item.name;\n this.label = item.label;\n this.chipLabel = item.chipLabel;\n this._hidden$.next(hidden);\n this.clearable = item.clear ?? true;\n this.persistanceDisabled = item.disablePersist ?? false;\n this.queryParamsDisabled = item.disableQueryParams ?? false;\n\n this.defaultValueFn = typeof item.default === 'function' ?\n item.default as IFilterDefaultFn : () => of(item.default);\n\n this.initCallback = item.init || (() => {\n //\n });\n\n this.changeCallback = item.change || (() => {\n //\n });\n\n if (isFunction(item.values)) {\n this._valuesFn =