ngx-kit 8.3.9 → 8.4.0

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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, input, inject, TemplateRef, Directive, output, ElementRef, HostListener, model, computed, effect, ChangeDetectionStrategy, Component, signal, DestroyRef, ContentChildren } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, input, inject, TemplateRef, Directive, output, ElementRef, HostListener, model, computed, effect, ChangeDetectionStrategy, Component, Injectable, signal, DestroyRef, ContentChildren } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -33,6 +33,25 @@ function defineFields(_renderers, fields) {
33
33
  return fields;
34
34
  }
35
35
 
36
+ var DynamicDataFilterOperator;
37
+ (function (DynamicDataFilterOperator) {
38
+ DynamicDataFilterOperator[DynamicDataFilterOperator["Equal"] = 0] = "Equal";
39
+ DynamicDataFilterOperator[DynamicDataFilterOperator["NotEqual"] = 1] = "NotEqual";
40
+ DynamicDataFilterOperator[DynamicDataFilterOperator["Contains"] = 2] = "Contains";
41
+ DynamicDataFilterOperator[DynamicDataFilterOperator["StartsWith"] = 3] = "StartsWith";
42
+ DynamicDataFilterOperator[DynamicDataFilterOperator["EndsWith"] = 4] = "EndsWith";
43
+ DynamicDataFilterOperator[DynamicDataFilterOperator["GreaterThan"] = 5] = "GreaterThan";
44
+ DynamicDataFilterOperator[DynamicDataFilterOperator["GreaterThanOrEqual"] = 6] = "GreaterThanOrEqual";
45
+ DynamicDataFilterOperator[DynamicDataFilterOperator["LessThan"] = 7] = "LessThan";
46
+ DynamicDataFilterOperator[DynamicDataFilterOperator["LessThanOrEqual"] = 8] = "LessThanOrEqual";
47
+ DynamicDataFilterOperator[DynamicDataFilterOperator["Between"] = 9] = "Between";
48
+ DynamicDataFilterOperator[DynamicDataFilterOperator["In"] = 10] = "In";
49
+ DynamicDataFilterOperator[DynamicDataFilterOperator["IsNull"] = 11] = "IsNull";
50
+ DynamicDataFilterOperator[DynamicDataFilterOperator["IsNotNull"] = 12] = "IsNotNull";
51
+ DynamicDataFilterOperator[DynamicDataFilterOperator["IsEmpty"] = 13] = "IsEmpty";
52
+ DynamicDataFilterOperator[DynamicDataFilterOperator["IsNotEmpty"] = 14] = "IsNotEmpty";
53
+ })(DynamicDataFilterOperator || (DynamicDataFilterOperator = {}));
54
+
36
55
  const NGX_PAGINATION_CONFIG_DEFAULT = {
37
56
  defaultPageSize: 10,
38
57
  pageSizeOptions: [5, 10, 20, 30, 50, 100],
@@ -54,6 +73,31 @@ class TableLocalization {
54
73
  noData = 'There is no data to display!';
55
74
  loading = 'Loading...';
56
75
  operation = 'Operation';
76
+ // Filters
77
+ filter = 'Filter';
78
+ clearFilter = 'Clear';
79
+ applyFilter = 'Apply';
80
+ search = 'Search...';
81
+ selectAll = 'Select All';
82
+ from = 'From';
83
+ to = 'To';
84
+ trueText = 'Yes';
85
+ falseText = 'No';
86
+ // Operators
87
+ opEqual = 'Equals';
88
+ opNotEqual = 'Does not equal';
89
+ opContains = 'Contains';
90
+ opStartsWith = 'Starts with';
91
+ opEndsWith = 'Ends with';
92
+ opGreaterThan = 'Greater than';
93
+ opGreaterThanOrEqual = 'Greater than or equal';
94
+ opLessThan = 'Less than';
95
+ opLessThanOrEqual = 'Less than or equal';
96
+ opBetween = 'Between';
97
+ opIsNull = 'Is null';
98
+ opIsNotNull = 'Is not null';
99
+ opIsEmpty = 'Is empty';
100
+ opIsNotEmpty = 'Is not empty';
57
101
  }
58
102
 
59
103
  const NGX_TABLE_CONFIG = new InjectionToken('NGX_TABLE_CONFIG', {
@@ -326,24 +370,369 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
326
370
  args: [{ selector: 'ngx-pagination', standalone: true, imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"ngx-pagination\">\n <div class=\"ngx-pagination__summary\">\n {{ lables.showing }}\n <span class=\"fw-semibold\">{{ firstItem() }}</span>\n {{ lables.to }}\n <span class=\"fw-semibold\">{{ lastItem() }}</span>\n {{ lables.of }}\n <span class=\"fw-semibold\">{{ total() }}</span>\n {{ lables.results }}\n </div>\n\n <div class=\"ngx-pagination__controls\">\n <ul class=\"ngx-pagination__list\">\n <li [class.disabled]=\"page() === 1\" [title]=\"lables.first\">\n <a (click)=\"first()\" class=\"icon\">\u00AB</a>\n </li>\n <li [class.disabled]=\"page() === 1\" [title]=\"lables.previous\">\n <a (click)=\"previous()\" class=\"icon\">\u2039</a>\n </li>\n\n @for (item of pages(); track $index) {\n <li class=\"ngx-pagination__page-num\" [class.active]=\"item === page()\">\n @if (item !== page() && item !== '...') {\n <a (click)=\"goto(+item)\">{{ item }}</a>\n }\n @if (item === page() || item === '...') {\n <span>{{ item }}</span>\n }\n </li>\n }\n\n <li [class.disabled]=\"page() === totalPages()\" [title]=\"lables.next\">\n <a (click)=\"next()\" class=\"icon\">\u203A</a>\n </li>\n <li [class.disabled]=\"page() === totalPages()\" [title]=\"lables.last\">\n <a (click)=\"last()\" class=\"icon\">\u00BB</a>\n </li>\n </ul>\n\n <div class=\"ngx-pagination__page-size\">\n <span>{{ lables.pageSize }}:</span>\n <select [ngModel]=\"pageSize()\" (ngModelChange)=\"onPageSizeChange($event)\">\n @for (opt of pageSizeOptions(); track opt) {\n <option [value]=\"opt\" [selected]=\"opt === pageSize()\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n</nav>\n", styles: [".ngx-pagination{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;padding:.625rem 1rem;font-size:.8125rem}.ngx-pagination__controls{display:flex;align-items:center;gap:1rem;flex-wrap:wrap}.ngx-pagination__list{display:flex;align-items:center;gap:.25rem;list-style:none;margin:0;padding:0}.ngx-pagination__list li{min-width:1.75rem;height:1.75rem;display:inline-flex;align-items:center;justify-content:center;border-radius:6px}.ngx-pagination__list li a,.ngx-pagination__list li span{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%;cursor:pointer;border-radius:6px;-webkit-user-select:none;user-select:none}.ngx-pagination__list li a.icon,.ngx-pagination__list li span.icon{font-size:20px;font-family:Courier New,Courier,monospace}.ngx-pagination__list li.active a,.ngx-pagination__list li.active span{background:var(--ngx-table-accent, #4f46e5);color:#fff;cursor:default}.ngx-pagination__list li.disabled{opacity:.4;pointer-events:none}.ngx-pagination__list li:hover:not(.active):not(.disabled) a{background:color-mix(in srgb,var(--ngx-table-accent, #4f46e5) 12%,transparent)}.ngx-pagination__page-size{display:flex;align-items:center;gap:.5rem}.ngx-pagination__page-size select{border-radius:6px;padding:.25rem .5rem;font-size:.8125rem}\n"] }]
327
371
  }], ctorParameters: () => [], propDecorators: { total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], boundaryCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundaryCount", required: false }] }], paginate: [{ type: i0.Output, args: ["paginate"] }] } });
328
372
 
329
- /**
330
- * جدول داده‌ی جنریک و Type-Safe.
331
- *
332
- * `T` از روی چیزی که به [data] بایند می‌کنید استنتاج می‌شود؛ `field.column` در
333
- * `TableField<T, R>` محدود به `keyof T` است، و اگر از defineFields(renderers, ...)
334
- * استفاده کنید، `field.renderer` محدود به کلیدهای رجیستری و `rendererInputs`
335
- * دقیقاً همان ورودی‌های اضافه‌ی همان renderer است.
336
- *
337
- * دو حالت:
338
- * - کلاینت‌ساید (پیش‌فرض): [data] کل آرایه است؛ سورت/صفحه‌بندی داخلی و روی
339
- * یک computed خالص انجام می‌شود (بدون mutation روی آبجکت‌های شما).
340
- * - lazy: [lazy]="true" + [totalRecords]؛ [data] فقط صفحه‌ی جاری است؛ به
341
- * (lazyLoad) گوش بدهید.
342
- */
373
+ class ColumnFilterService {
374
+ applyFilters(data, filters) {
375
+ const activeFilters = Object.values(filters).filter((f) => this.isFilterActive(f));
376
+ if (activeFilters.length === 0)
377
+ return [...data];
378
+ return data.filter((item) => activeFilters.every((filter) => this.evaluateFilter(item, filter)));
379
+ }
380
+ isFilterActive(filter) {
381
+ if (!filter)
382
+ return false;
383
+ const { operator, value, valueTo } = filter;
384
+ const noValueOps = [
385
+ DynamicDataFilterOperator.IsNull,
386
+ DynamicDataFilterOperator.IsNotNull,
387
+ DynamicDataFilterOperator.IsEmpty,
388
+ DynamicDataFilterOperator.IsNotEmpty,
389
+ ];
390
+ if (noValueOps.includes(operator))
391
+ return true;
392
+ if (operator === DynamicDataFilterOperator.Between) {
393
+ return (value !== undefined &&
394
+ value !== null &&
395
+ value !== '' &&
396
+ valueTo !== undefined &&
397
+ valueTo !== null &&
398
+ valueTo !== '');
399
+ }
400
+ if (operator === DynamicDataFilterOperator.In) {
401
+ return Array.isArray(value) && value.length > 0;
402
+ }
403
+ return value !== undefined && value !== null && value !== '';
404
+ }
405
+ evaluateFilter(item, filter) {
406
+ const rawVal = item?.[filter.field];
407
+ const op = filter.operator;
408
+ const target = filter.value;
409
+ const targetTo = filter.valueTo;
410
+ if (op === DynamicDataFilterOperator.IsNull)
411
+ return rawVal === null || rawVal === undefined;
412
+ if (op === DynamicDataFilterOperator.IsNotNull)
413
+ return rawVal !== null && rawVal !== undefined;
414
+ if (op === DynamicDataFilterOperator.IsEmpty)
415
+ return rawVal === '' || rawVal === null || rawVal === undefined;
416
+ if (op === DynamicDataFilterOperator.IsNotEmpty)
417
+ return rawVal !== '' && rawVal !== null && rawVal !== undefined;
418
+ if (rawVal === null || rawVal === undefined)
419
+ return false;
420
+ if (rawVal instanceof Date || (typeof rawVal === 'string' && !isNaN(Date.parse(rawVal)) && isNaN(Number(rawVal)))) {
421
+ const itemTime = new Date(rawVal).getTime();
422
+ const targetTime = target ? new Date(target).getTime() : NaN;
423
+ const targetToTime = targetTo ? new Date(targetTo).getTime() : NaN;
424
+ if (!isNaN(itemTime)) {
425
+ switch (op) {
426
+ case DynamicDataFilterOperator.Equal:
427
+ return new Date(rawVal).toDateString() === new Date(target).toDateString();
428
+ case DynamicDataFilterOperator.GreaterThan:
429
+ return itemTime > targetTime;
430
+ case DynamicDataFilterOperator.GreaterThanOrEqual:
431
+ return itemTime >= targetTime;
432
+ case DynamicDataFilterOperator.LessThan:
433
+ return itemTime < targetTime;
434
+ case DynamicDataFilterOperator.LessThanOrEqual:
435
+ return itemTime <= targetTime;
436
+ case DynamicDataFilterOperator.Between:
437
+ return itemTime >= targetTime && itemTime <= targetToTime;
438
+ }
439
+ }
440
+ }
441
+ switch (op) {
442
+ case DynamicDataFilterOperator.Equal:
443
+ return String(rawVal).trim().toLowerCase() === String(target).trim().toLowerCase();
444
+ case DynamicDataFilterOperator.NotEqual:
445
+ return String(rawVal).trim().toLowerCase() !== String(target).trim().toLowerCase();
446
+ case DynamicDataFilterOperator.Contains:
447
+ return String(rawVal).toLowerCase().includes(String(target).toLowerCase());
448
+ case DynamicDataFilterOperator.StartsWith:
449
+ return String(rawVal).toLowerCase().startsWith(String(target).toLowerCase());
450
+ case DynamicDataFilterOperator.EndsWith:
451
+ return String(rawVal).toLowerCase().endsWith(String(target).toLowerCase());
452
+ case DynamicDataFilterOperator.GreaterThan:
453
+ return Number(rawVal) > Number(target);
454
+ case DynamicDataFilterOperator.GreaterThanOrEqual:
455
+ return Number(rawVal) >= Number(target);
456
+ case DynamicDataFilterOperator.LessThan:
457
+ return Number(rawVal) < Number(target);
458
+ case DynamicDataFilterOperator.LessThanOrEqual:
459
+ return Number(rawVal) <= Number(target);
460
+ case DynamicDataFilterOperator.Between:
461
+ return Number(rawVal) >= Number(target) && Number(rawVal) <= Number(targetTo);
462
+ case DynamicDataFilterOperator.In:
463
+ return Array.isArray(target)
464
+ ? target.some((t) => String(t).toLowerCase() === String(rawVal).toLowerCase())
465
+ : false;
466
+ default:
467
+ return true;
468
+ }
469
+ }
470
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ColumnFilterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
471
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ColumnFilterService, providedIn: 'root' });
472
+ }
473
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ColumnFilterService, decorators: [{
474
+ type: Injectable,
475
+ args: [{ providedIn: 'root' }]
476
+ }] });
477
+
478
+ class NgxColumnFilter {
479
+ labels = inject(NGX_TABLE_I18N);
480
+ elementRef = inject(ElementRef);
481
+ tableEl = input.required(/* @ts-ignore */
482
+ ...(ngDevMode ? [{ debugName: "tableEl" }] : /* istanbul ignore next */ []));
483
+ field = input.required(/* @ts-ignore */
484
+ ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
485
+ config = input(/* @ts-ignore */
486
+ ...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
487
+ currentFilter = input(null, /* @ts-ignore */
488
+ ...(ngDevMode ? [{ debugName: "currentFilter" }] : /* istanbul ignore next */ []));
489
+ distinctValues = input([], /* @ts-ignore */
490
+ ...(ngDevMode ? [{ debugName: "distinctValues" }] : /* istanbul ignore next */ []));
491
+ applyFilter = output();
492
+ clearFilter = output();
493
+ isOpen = signal(false, /* @ts-ignore */
494
+ ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
495
+ filterType = computed(() => this.config()?.type ?? 'text', /* @ts-ignore */
496
+ ...(ngDevMode ? [{ debugName: "filterType" }] : /* istanbul ignore next */ []));
497
+ selectedOperator = signal(DynamicDataFilterOperator.Contains, /* @ts-ignore */
498
+ ...(ngDevMode ? [{ debugName: "selectedOperator" }] : /* istanbul ignore next */ []));
499
+ filterValue = signal('', /* @ts-ignore */
500
+ ...(ngDevMode ? [{ debugName: "filterValue" }] : /* istanbul ignore next */ []));
501
+ filterValueTo = signal('', /* @ts-ignore */
502
+ ...(ngDevMode ? [{ debugName: "filterValueTo" }] : /* istanbul ignore next */ []));
503
+ selectedEnumValues = signal(new Set(), /* @ts-ignore */
504
+ ...(ngDevMode ? [{ debugName: "selectedEnumValues" }] : /* istanbul ignore next */ []));
505
+ enumSearchQuery = signal('', /* @ts-ignore */
506
+ ...(ngDevMode ? [{ debugName: "enumSearchQuery" }] : /* istanbul ignore next */ []));
507
+ operators = computed(() => {
508
+ const type = this.filterType();
509
+ const l = this.labels;
510
+ if (type === 'number') {
511
+ return [
512
+ { label: l.opEqual, value: DynamicDataFilterOperator.Equal },
513
+ { label: l.opNotEqual, value: DynamicDataFilterOperator.NotEqual },
514
+ { label: l.opGreaterThan, value: DynamicDataFilterOperator.GreaterThan },
515
+ { label: l.opGreaterThanOrEqual, value: DynamicDataFilterOperator.GreaterThanOrEqual },
516
+ { label: l.opLessThan, value: DynamicDataFilterOperator.LessThan },
517
+ { label: l.opLessThanOrEqual, value: DynamicDataFilterOperator.LessThanOrEqual },
518
+ { label: l.opBetween, value: DynamicDataFilterOperator.Between },
519
+ { label: l.opIsNull, value: DynamicDataFilterOperator.IsNull },
520
+ { label: l.opIsNotNull, value: DynamicDataFilterOperator.IsNotNull },
521
+ ];
522
+ }
523
+ if (type === 'date') {
524
+ return [
525
+ { label: l.opEqual, value: DynamicDataFilterOperator.Equal },
526
+ { label: l.opGreaterThan, value: DynamicDataFilterOperator.GreaterThan },
527
+ { label: l.opLessThan, value: DynamicDataFilterOperator.LessThan },
528
+ { label: l.opBetween, value: DynamicDataFilterOperator.Between },
529
+ { label: l.opIsNull, value: DynamicDataFilterOperator.IsNull },
530
+ { label: l.opIsNotNull, value: DynamicDataFilterOperator.IsNotNull },
531
+ ];
532
+ }
533
+ if (type === 'boolean') {
534
+ return [
535
+ { label: l.opEqual, value: DynamicDataFilterOperator.Equal },
536
+ { label: l.opIsNull, value: DynamicDataFilterOperator.IsNull },
537
+ { label: l.opIsNotNull, value: DynamicDataFilterOperator.IsNotNull },
538
+ ];
539
+ }
540
+ // Default: text
541
+ return [
542
+ { label: l.opContains, value: DynamicDataFilterOperator.Contains },
543
+ { label: l.opStartsWith, value: DynamicDataFilterOperator.StartsWith },
544
+ { label: l.opEndsWith, value: DynamicDataFilterOperator.EndsWith },
545
+ { label: l.opEqual, value: DynamicDataFilterOperator.Equal },
546
+ { label: l.opNotEqual, value: DynamicDataFilterOperator.NotEqual },
547
+ { label: l.opIsEmpty, value: DynamicDataFilterOperator.IsEmpty },
548
+ { label: l.opIsNotEmpty, value: DynamicDataFilterOperator.IsNotEmpty },
549
+ { label: l.opIsNull, value: DynamicDataFilterOperator.IsNull },
550
+ { label: l.opIsNotNull, value: DynamicDataFilterOperator.IsNotNull },
551
+ ];
552
+ }, /* @ts-ignore */
553
+ ...(ngDevMode ? [{ debugName: "operators" }] : /* istanbul ignore next */ []));
554
+ enumOptions = computed(() => {
555
+ const cfg = this.config();
556
+ if (cfg?.options && cfg.options.length > 0)
557
+ return cfg.options;
558
+ const values = this.distinctValues();
559
+ return values
560
+ .filter((v) => v !== null && v !== undefined && v !== '')
561
+ .map((v) => ({ label: String(v), value: v }));
562
+ }, /* @ts-ignore */
563
+ ...(ngDevMode ? [{ debugName: "enumOptions" }] : /* istanbul ignore next */ []));
564
+ filteredEnumOptions = computed(() => {
565
+ const q = this.enumSearchQuery().trim().toLowerCase();
566
+ const opts = this.enumOptions();
567
+ if (!q)
568
+ return opts;
569
+ return opts.filter((o) => o.label.toLowerCase().includes(q));
570
+ }, /* @ts-ignore */
571
+ ...(ngDevMode ? [{ debugName: "filteredEnumOptions" }] : /* istanbul ignore next */ []));
572
+ isNoValueOperator = computed(() => {
573
+ const op = this.selectedOperator();
574
+ return [
575
+ DynamicDataFilterOperator.IsNull,
576
+ DynamicDataFilterOperator.IsNotNull,
577
+ DynamicDataFilterOperator.IsEmpty,
578
+ DynamicDataFilterOperator.IsNotEmpty,
579
+ ].includes(op);
580
+ }, /* @ts-ignore */
581
+ ...(ngDevMode ? [{ debugName: "isNoValueOperator" }] : /* istanbul ignore next */ []));
582
+ toggleDropdown(event) {
583
+ event.stopPropagation();
584
+ const nextState = !this.isOpen();
585
+ if (nextState) {
586
+ this.syncStateFromInput();
587
+ this.isOpen.set(true);
588
+ this.updatePosition();
589
+ }
590
+ else {
591
+ this.isOpen.set(false);
592
+ }
593
+ }
594
+ updatePosition() {
595
+ if (!this.isOpen())
596
+ return;
597
+ setTimeout(() => {
598
+ const popover = this.elementRef.nativeElement.querySelector('.filter-popover');
599
+ if (!popover)
600
+ return;
601
+ // پیدا کردن کانتینر اسکرول جدول
602
+ const containerEl = this.tableEl()?.nativeElement ??
603
+ this.elementRef.nativeElement.closest('.ngx-table-scroll') ??
604
+ this.elementRef.nativeElement.closest('.ngx-table-wrapper');
605
+ if (!containerEl) {
606
+ popover.style.visibility = 'visible';
607
+ return;
608
+ }
609
+ // ۱. ریست کردن ترنسفورم قبلی تا موقعیت واقعی بدون تغییر سنجیده شود
610
+ popover.style.transform = 'none';
611
+ const rect = popover.getBoundingClientRect();
612
+ const container = containerEl.getBoundingClientRect();
613
+ const margin = 6; // فاصله ایمن از لبه جدول
614
+ let x = 0;
615
+ const threshold = 10;
616
+ // اگر از لبه سمت راست کانتینر بیرون زده باشد (حالت ستون‌های آخر):
617
+ if (rect.right > container.right - margin) {
618
+ // شیفت به سمت چپ (مقدار منفی)
619
+ x = container.right - margin - rect.right - threshold;
620
+ }
621
+ // اگر از لبه سمت چپ بیرون زده باشد:
622
+ else if (rect.left < container.left + margin) {
623
+ // شیفت به سمت راست (مقدار مثبت)
624
+ x = container.left + margin - rect.left + threshold;
625
+ }
626
+ // اطمینان از اینکه شیفت به چپ، باعث خروج از سمت چپ کانتینر نشود
627
+ if (rect.left + x < container.left + margin) {
628
+ x = container.left + margin - rect.left;
629
+ }
630
+ // ۲. اعمال جابجایی
631
+ if (x !== 0) {
632
+ popover.style.transform = `translateX(${Math.round(x)}px)`;
633
+ }
634
+ else {
635
+ popover.style.transform = 'none';
636
+ }
637
+ // ۳. نمایش پاپ‌آپ پس از تنظیم موقعیت بدون پرش تصویر (Flicker)
638
+ popover.style.visibility = 'visible';
639
+ }, 0);
640
+ }
641
+ syncStateFromInput() {
642
+ const cur = this.currentFilter();
643
+ const type = this.filterType();
644
+ if (cur) {
645
+ this.selectedOperator.set(cur.operator);
646
+ if (type === 'enum' && Array.isArray(cur.value)) {
647
+ this.selectedEnumValues.set(new Set(cur.value));
648
+ }
649
+ else {
650
+ this.filterValue.set(cur.value ?? '');
651
+ this.filterValueTo.set(cur.valueTo ?? '');
652
+ }
653
+ }
654
+ else {
655
+ const defaultOp = this.config()?.defaultOperator ??
656
+ (type === 'number' || type === 'date' || type === 'boolean'
657
+ ? DynamicDataFilterOperator.Equal
658
+ : DynamicDataFilterOperator.Contains);
659
+ this.selectedOperator.set(defaultOp);
660
+ this.filterValue.set('');
661
+ this.filterValueTo.set('');
662
+ if (type === 'enum') {
663
+ this.selectedEnumValues.set(new Set(this.enumOptions().map((o) => o.value)));
664
+ }
665
+ }
666
+ }
667
+ onApply() {
668
+ const type = this.filterType();
669
+ let value = this.filterValue();
670
+ let operator = this.selectedOperator();
671
+ if (type === 'enum') {
672
+ operator = DynamicDataFilterOperator.In;
673
+ value = Array.from(this.selectedEnumValues());
674
+ }
675
+ this.applyFilter.emit({
676
+ field: this.field(),
677
+ operator,
678
+ value,
679
+ valueTo: this.selectedOperator() === DynamicDataFilterOperator.Between
680
+ ? this.filterValueTo()
681
+ : undefined,
682
+ });
683
+ this.isOpen.set(false);
684
+ }
685
+ onClear() {
686
+ this.filterValue.set('');
687
+ this.filterValueTo.set('');
688
+ this.selectedEnumValues.set(new Set());
689
+ this.clearFilter.emit(this.field());
690
+ this.isOpen.set(false);
691
+ }
692
+ toggleEnumSelection(val) {
693
+ const current = new Set(this.selectedEnumValues());
694
+ if (current.has(val))
695
+ current.delete(val);
696
+ else
697
+ current.add(val);
698
+ this.selectedEnumValues.set(current);
699
+ }
700
+ toggleSelectAll(checked) {
701
+ if (checked) {
702
+ this.selectedEnumValues.set(new Set(this.enumOptions().map((o) => o.value)));
703
+ }
704
+ else {
705
+ this.selectedEnumValues.set(new Set());
706
+ }
707
+ }
708
+ onDocumentClick(event) {
709
+ if (this.isOpen() && !this.elementRef.nativeElement.contains(event.target)) {
710
+ this.isOpen.set(false);
711
+ }
712
+ }
713
+ onEscape() {
714
+ if (this.isOpen())
715
+ this.isOpen.set(false);
716
+ }
717
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxColumnFilter, deps: [], target: i0.ɵɵFactoryTarget.Component });
718
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxColumnFilter, isStandalone: true, selector: "ngx-column-filter", inputs: { tableEl: { classPropertyName: "tableEl", publicName: "tableEl", isSignal: true, isRequired: true, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, currentFilter: { classPropertyName: "currentFilter", publicName: "currentFilter", isSignal: true, isRequired: false, transformFunction: null }, distinctValues: { classPropertyName: "distinctValues", publicName: "distinctValues", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { applyFilter: "applyFilter", clearFilter: "clearFilter" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<div class=\"ngx-col-filter\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"filter-trigger\"\n [class.active]=\"!!currentFilter()\"\n [title]=\"labels.filter\"\n (click)=\"toggleDropdown($event)\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\" class=\"funnel-icon\">\n <path\n fill=\"currentColor\"\n d=\"M3.9 54.9C10.5 36.5 28 24 47.6 24H464.4c19.6 0 37.1 12.5 43.7 30.9c6.6 18.4 0 38.8-16.1 49.9L320 223.5V432c0 13.5-8.4 25.5-21.1 30.2s-27 1.1-35.8-9.1l-64-75.1c-4.7-5.5-7.1-12.6-7.1-19.8V223.5L4.1 104.8C-1.9 93.7-2.6 73.3 3.9 54.9z\" />\n </svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"filter-popover\" role=\"dialog\">\n @if (filterType() !== 'enum') {\n <div class=\"filter-section\">\n <label class=\"filter-label\">{{ labels.filter }}:</label>\n <select\n class=\"filter-select\"\n [ngModel]=\"selectedOperator()\"\n (ngModelChange)=\"selectedOperator.set($event)\">\n @for (op of operators(); track op.value) {\n <option [ngValue]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n </div>\n\n @if (!isNoValueOperator()) {\n <div class=\"filter-section\">\n @if (filterType() === 'number') {\n <input\n type=\"number\"\n class=\"filter-input\"\n [placeholder]=\"labels.search\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\" />\n @if (selectedOperator() === 9) {\n <input\n type=\"number\"\n class=\"filter-input mt-1\"\n [placeholder]=\"labels.to\"\n [ngModel]=\"filterValueTo()\"\n (ngModelChange)=\"filterValueTo.set($event)\" />\n }\n } @else if (filterType() === 'date') {\n <input\n type=\"date\"\n class=\"filter-input\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\" />\n @if (selectedOperator() === 9) {\n <input\n type=\"date\"\n class=\"filter-input mt-1\"\n [ngModel]=\"filterValueTo()\"\n (ngModelChange)=\"filterValueTo.set($event)\" />\n }\n } @else if (filterType() === 'boolean') {\n <select\n class=\"filter-select\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\">\n <option [ngValue]=\"true\">{{ labels.trueText }}</option>\n <option [ngValue]=\"false\">{{ labels.falseText }}</option>\n </select>\n } @else {\n <input\n type=\"text\"\n class=\"filter-input\"\n [placeholder]=\"labels.search\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\" />\n }\n </div>\n }\n } @else {\n <!-- Excel-like Set Filter (Enum) -->\n <div class=\"filter-section\">\n <input\n type=\"text\"\n class=\"filter-input\"\n [placeholder]=\"labels.search\"\n [ngModel]=\"enumSearchQuery()\"\n (ngModelChange)=\"enumSearchQuery.set($event)\" />\n </div>\n <div class=\"filter-set-list\">\n <label class=\"filter-set-item font-bold\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedEnumValues().size === enumOptions().length\"\n (change)=\"toggleSelectAll($any($event.target).checked)\" />\n <span>({{ labels.selectAll }})</span>\n </label>\n @for (opt of filteredEnumOptions(); track opt.value) {\n <label class=\"filter-set-item\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedEnumValues().has(opt.value)\"\n (change)=\"toggleEnumSelection(opt.value)\" />\n <span>{{ opt.label }}</span>\n </label>\n }\n </div>\n }\n\n <div class=\"filter-actions\">\n <button type=\"button\" class=\"btn-clear\" (click)=\"onClear()\">\n {{ labels.clearFilter }}\n </button>\n <button type=\"button\" class=\"btn-apply\" (click)=\"onApply()\">\n {{ labels.applyFilter }}\n </button>\n </div>\n </div>\n }\n</div>\n", styles: [".ngx-col-filter{position:relative;display:inline-flex;align-items:center}.filter-trigger{background:none;border:none;cursor:pointer;padding:2px 4px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;color:inherit;opacity:.35;transition:opacity .15s ease,color .15s ease}.filter-trigger:hover{opacity:.8}.filter-trigger.active{opacity:1;color:var(--ngx-table-accent, #4f46e5)}.filter-trigger .funnel-icon{width:.8rem;height:.8rem}.filter-popover{position:absolute;top:calc(100% + 6px);inset-inline-start:-10px;z-index:100;width:220px;background:var(--ngx-table-row-bg, #ffffff);border:1px solid var(--ngx-table-border, #e5e7eb);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;border-radius:8px;padding:.75rem;display:flex;flex-direction:column;gap:.625rem;font-size:.8125rem;color:var(--ngx-table-text, #1e293b);cursor:default;box-sizing:border-box;visibility:hidden}.filter-section{display:flex;flex-direction:column;gap:.25rem}.filter-label{font-weight:500;font-size:.75rem;color:#64748b}.filter-select,.filter-input{width:100%;box-sizing:border-box;padding:.375rem .5rem;border-radius:6px;border:1px solid var(--ngx-table-border, #d1d5db);background:var(--ngx-table-row-bg, #ffffff);color:inherit;font-size:.8125rem}.filter-select:focus,.filter-input:focus{outline:none;border-color:var(--ngx-table-accent, #4f46e5)}.mt-1{margin-top:.375rem}.filter-set-list{max-height:140px;overflow-y:auto;display:flex;flex-direction:column;gap:.25rem;padding:.25rem;border:1px solid var(--ngx-table-border, #e5e7eb);border-radius:6px}.filter-set-item{display:flex;align-items:center;gap:.375rem;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:.75rem}.filter-set-item.font-bold{font-weight:600}.filter-actions{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.25rem;border-top:1px solid var(--ngx-table-border, #e5e7eb)}.filter-actions button{padding:.3rem .6rem;border-radius:4px;font-size:.75rem;cursor:pointer;border:none;font-weight:500}.filter-actions .btn-clear{background:transparent;color:#64748b}.filter-actions .btn-clear:hover{background:#f1f5f9}.filter-actions .btn-apply{background:var(--ngx-table-accent, #4f46e5);color:#fff}.filter-actions .btn-apply:hover{filter:brightness(.95)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
719
+ }
720
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxColumnFilter, decorators: [{
721
+ type: Component,
722
+ args: [{ selector: 'ngx-column-filter', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ngx-col-filter\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"filter-trigger\"\n [class.active]=\"!!currentFilter()\"\n [title]=\"labels.filter\"\n (click)=\"toggleDropdown($event)\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\" class=\"funnel-icon\">\n <path\n fill=\"currentColor\"\n d=\"M3.9 54.9C10.5 36.5 28 24 47.6 24H464.4c19.6 0 37.1 12.5 43.7 30.9c6.6 18.4 0 38.8-16.1 49.9L320 223.5V432c0 13.5-8.4 25.5-21.1 30.2s-27 1.1-35.8-9.1l-64-75.1c-4.7-5.5-7.1-12.6-7.1-19.8V223.5L4.1 104.8C-1.9 93.7-2.6 73.3 3.9 54.9z\" />\n </svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"filter-popover\" role=\"dialog\">\n @if (filterType() !== 'enum') {\n <div class=\"filter-section\">\n <label class=\"filter-label\">{{ labels.filter }}:</label>\n <select\n class=\"filter-select\"\n [ngModel]=\"selectedOperator()\"\n (ngModelChange)=\"selectedOperator.set($event)\">\n @for (op of operators(); track op.value) {\n <option [ngValue]=\"op.value\">{{ op.label }}</option>\n }\n </select>\n </div>\n\n @if (!isNoValueOperator()) {\n <div class=\"filter-section\">\n @if (filterType() === 'number') {\n <input\n type=\"number\"\n class=\"filter-input\"\n [placeholder]=\"labels.search\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\" />\n @if (selectedOperator() === 9) {\n <input\n type=\"number\"\n class=\"filter-input mt-1\"\n [placeholder]=\"labels.to\"\n [ngModel]=\"filterValueTo()\"\n (ngModelChange)=\"filterValueTo.set($event)\" />\n }\n } @else if (filterType() === 'date') {\n <input\n type=\"date\"\n class=\"filter-input\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\" />\n @if (selectedOperator() === 9) {\n <input\n type=\"date\"\n class=\"filter-input mt-1\"\n [ngModel]=\"filterValueTo()\"\n (ngModelChange)=\"filterValueTo.set($event)\" />\n }\n } @else if (filterType() === 'boolean') {\n <select\n class=\"filter-select\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\">\n <option [ngValue]=\"true\">{{ labels.trueText }}</option>\n <option [ngValue]=\"false\">{{ labels.falseText }}</option>\n </select>\n } @else {\n <input\n type=\"text\"\n class=\"filter-input\"\n [placeholder]=\"labels.search\"\n [ngModel]=\"filterValue()\"\n (ngModelChange)=\"filterValue.set($event)\" />\n }\n </div>\n }\n } @else {\n <!-- Excel-like Set Filter (Enum) -->\n <div class=\"filter-section\">\n <input\n type=\"text\"\n class=\"filter-input\"\n [placeholder]=\"labels.search\"\n [ngModel]=\"enumSearchQuery()\"\n (ngModelChange)=\"enumSearchQuery.set($event)\" />\n </div>\n <div class=\"filter-set-list\">\n <label class=\"filter-set-item font-bold\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedEnumValues().size === enumOptions().length\"\n (change)=\"toggleSelectAll($any($event.target).checked)\" />\n <span>({{ labels.selectAll }})</span>\n </label>\n @for (opt of filteredEnumOptions(); track opt.value) {\n <label class=\"filter-set-item\">\n <input\n type=\"checkbox\"\n [checked]=\"selectedEnumValues().has(opt.value)\"\n (change)=\"toggleEnumSelection(opt.value)\" />\n <span>{{ opt.label }}</span>\n </label>\n }\n </div>\n }\n\n <div class=\"filter-actions\">\n <button type=\"button\" class=\"btn-clear\" (click)=\"onClear()\">\n {{ labels.clearFilter }}\n </button>\n <button type=\"button\" class=\"btn-apply\" (click)=\"onApply()\">\n {{ labels.applyFilter }}\n </button>\n </div>\n </div>\n }\n</div>\n", styles: [".ngx-col-filter{position:relative;display:inline-flex;align-items:center}.filter-trigger{background:none;border:none;cursor:pointer;padding:2px 4px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;color:inherit;opacity:.35;transition:opacity .15s ease,color .15s ease}.filter-trigger:hover{opacity:.8}.filter-trigger.active{opacity:1;color:var(--ngx-table-accent, #4f46e5)}.filter-trigger .funnel-icon{width:.8rem;height:.8rem}.filter-popover{position:absolute;top:calc(100% + 6px);inset-inline-start:-10px;z-index:100;width:220px;background:var(--ngx-table-row-bg, #ffffff);border:1px solid var(--ngx-table-border, #e5e7eb);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;border-radius:8px;padding:.75rem;display:flex;flex-direction:column;gap:.625rem;font-size:.8125rem;color:var(--ngx-table-text, #1e293b);cursor:default;box-sizing:border-box;visibility:hidden}.filter-section{display:flex;flex-direction:column;gap:.25rem}.filter-label{font-weight:500;font-size:.75rem;color:#64748b}.filter-select,.filter-input{width:100%;box-sizing:border-box;padding:.375rem .5rem;border-radius:6px;border:1px solid var(--ngx-table-border, #d1d5db);background:var(--ngx-table-row-bg, #ffffff);color:inherit;font-size:.8125rem}.filter-select:focus,.filter-input:focus{outline:none;border-color:var(--ngx-table-accent, #4f46e5)}.mt-1{margin-top:.375rem}.filter-set-list{max-height:140px;overflow-y:auto;display:flex;flex-direction:column;gap:.25rem;padding:.25rem;border:1px solid var(--ngx-table-border, #e5e7eb);border-radius:6px}.filter-set-item{display:flex;align-items:center;gap:.375rem;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:.75rem}.filter-set-item.font-bold{font-weight:600}.filter-actions{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.25rem;border-top:1px solid var(--ngx-table-border, #e5e7eb)}.filter-actions button{padding:.3rem .6rem;border-radius:4px;font-size:.75rem;cursor:pointer;border:none;font-weight:500}.filter-actions .btn-clear{background:transparent;color:#64748b}.filter-actions .btn-clear:hover{background:#f1f5f9}.filter-actions .btn-apply{background:var(--ngx-table-accent, #4f46e5);color:#fff}.filter-actions .btn-apply:hover{filter:brightness(.95)}\n"] }]
723
+ }], propDecorators: { tableEl: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableEl", required: true }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], currentFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentFilter", required: false }] }], distinctValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "distinctValues", required: false }] }], applyFilter: [{ type: i0.Output, args: ["applyFilter"] }], clearFilter: [{ type: i0.Output, args: ["clearFilter"] }], onDocumentClick: [{
724
+ type: HostListener,
725
+ args: ['document:click', ['$event']]
726
+ }], onEscape: [{
727
+ type: HostListener,
728
+ args: ['document:keydown.escape']
729
+ }] } });
730
+
343
731
  class NgxTable {
344
732
  config = inject(NGX_TABLE_CONFIG);
345
733
  labels = inject(NGX_TABLE_I18N);
346
734
  paginationConfig = inject(NGX_PAGINATION_CONFIG);
735
+ columnFilterService = inject(ColumnFilterService);
347
736
  // ---------------------------------------------------------------- inputs
348
737
  fields = input.required(/* @ts-ignore */
349
738
  ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
@@ -374,15 +763,15 @@ class NgxTable {
374
763
  ...(ngDevMode ? [{ debugName: "resizable" }] : /* istanbul ignore next */ []));
375
764
  height = input('75dvh', /* @ts-ignore */
376
765
  ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
377
- /** پیش‌فرض بر اساس ایندکس؛ برای جلوگیری از گم‌شدن state سطرها هنگام سورت،
378
- * یک شناسه‌ی یکتا بدهید: [trackBy]="(i, row) => row.id" */
379
766
  trackBy = input((index) => index, /* @ts-ignore */
380
767
  ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
381
768
  // --------------------------------------------------------------- outputs
382
769
  lazyLoad = output();
383
770
  sortChange = output();
384
771
  columnResize = output();
772
+ filterChange = output();
385
773
  // ------------------------------------------------------------ int. state
774
+ elementRef = inject(ElementRef);
386
775
  pageIndex = signal(1, /* @ts-ignore */
387
776
  ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
388
777
  pageSizeS = signal(this.paginationConfig.defaultPageSize, /* @ts-ignore */
@@ -391,6 +780,8 @@ class NgxTable {
391
780
  ...(ngDevMode ? [{ debugName: "sorts" }] : /* istanbul ignore next */ []));
392
781
  widths = signal({}, /* @ts-ignore */
393
782
  ...(ngDevMode ? [{ debugName: "widths" }] : /* istanbul ignore next */ []));
783
+ activeFilters = signal({}, /* @ts-ignore */
784
+ ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
394
785
  manuallyResized = new Set();
395
786
  rendererInputsCache = new Map();
396
787
  cellTemplates;
@@ -403,18 +794,9 @@ class NgxTable {
403
794
  ...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
404
795
  constructor() {
405
796
  effect(() => {
406
- const ps = this.pageSize();
407
- this.pageSizeS.set(ps);
408
- const pi = this.page();
409
- this.pageIndex.set(pi);
797
+ this.pageSizeS.set(this.pageSize());
798
+ this.pageIndex.set(this.page());
410
799
  });
411
- // برای هر ستونِ تازه‌دیده‌شده که width عددیِ صریح دارد، یک عرض seed کن؛
412
- // ستون‌هایی که کاربر دستی ریسایز کرده دیگر هیچ‌وقت دوباره نوشته نمی‌شوند.
413
- // مهم: ستون‌هایی که اصلاً width ندارند (یا width رشته‌ای مثل '20%' دارند)
414
- // عمداً اینجا seed نمی‌شوند — قبلاً همه‌ی ستون‌ها با یک defaultWidth ثابت
415
- // seed می‌شدند که باعث می‌شد جدول با flex:0 0 روی هر ستون هیچ‌وقت واقعاً
416
- // 100% عرض رو پر نکنه، حتی وقتی کاربر عمداً یک ستون رو بدون width گذاشته
417
- // بود تا فضای باقی‌مونده رو خودش پر کنه.
418
800
  effect(() => {
419
801
  const cols = this.fields();
420
802
  const current = this.widths();
@@ -434,17 +816,9 @@ class NgxTable {
434
816
  this.widths.set(next);
435
817
  });
436
818
  }
437
- ngOnInit() {
438
- // نیاز به این نیست در لود خود صفحه بندی paginate را صدا میزند
439
- // if (this.lazy()) {
440
- // this.lazyLoad.emit({ first: 0, pageIndex: 1, pageSize: this.pageSizeS(), sorts: [] });
441
- // }
442
- }
819
+ ngOnInit() { }
443
820
  ngAfterContentInit() {
444
821
  this.syncTemplateMap();
445
- // این Observable خودش کامل نمی‌شه (EventEmitter ساده‌ست، نه یه stream که
446
- // روی destroy کامپوننت تموم بشه)؛ بدون takeUntilDestroyed، این subscribe
447
- // برای همیشه فعال می‌موند و رفرنس کامپوننت رو زنده نگه می‌داشت.
448
822
  this.cellTemplates.changes
449
823
  .pipe(takeUntilDestroyed(this.destroyRef))
450
824
  .subscribe(() => this.syncTemplateMap());
@@ -452,18 +826,25 @@ class NgxTable {
452
826
  syncTemplateMap() {
453
827
  this.templateMap.clear();
454
828
  this.cellTemplates.forEach((cell) => {
455
- // نکته‌ی مهم: cell.column() باید فراخوانی شود (سیگنال است)، نه cell.column.name.
456
829
  this.templateMap.set(cell.column(), cell.templateRef);
457
830
  });
458
831
  }
459
- // ------------------------------------------------------------- داده‌ها
832
+ // ------------------------------------------------------------- داده‌ها (فیلتر ➔ سورت ➔ صفحه‌بندی)
833
+ filteredData = computed(() => {
834
+ const raw = this.data();
835
+ if (this.lazy())
836
+ return raw;
837
+ return this.columnFilterService.applyFilters(raw, this.activeFilters());
838
+ }, /* @ts-ignore */
839
+ ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
460
840
  sortedData = computed(() => {
841
+ const source = this.filteredData();
461
842
  if (this.lazy())
462
- return this.data();
843
+ return source;
463
844
  const sorts = this.sorts();
464
845
  if (sorts.length === 0)
465
- return this.data();
466
- const copy = [...this.data()];
846
+ return source;
847
+ const copy = [...source];
467
848
  copy.sort((a, b) => {
468
849
  for (const s of sorts) {
469
850
  const cmp = this.compare(a[s.field], b[s.field]);
@@ -475,7 +856,7 @@ class NgxTable {
475
856
  return copy;
476
857
  }, /* @ts-ignore */
477
858
  ...(ngDevMode ? [{ debugName: "sortedData" }] : /* istanbul ignore next */ []));
478
- totalCount = computed(() => this.lazy() ? this.totalRecords() : this.data().length, /* @ts-ignore */
859
+ totalCount = computed(() => this.lazy() ? this.totalRecords() : this.filteredData().length, /* @ts-ignore */
479
860
  ...(ngDevMode ? [{ debugName: "totalCount" }] : /* istanbul ignore next */ []));
480
861
  pagedData = computed(() => {
481
862
  if (this.lazy())
@@ -499,7 +880,6 @@ class NgxTable {
499
880
  return a.getTime() - b.getTime();
500
881
  return String(a).localeCompare(String(b));
501
882
  }
502
- /** بدون mutation روی داده‌ی ورودی: شماره‌ی ردیف صرفاً از روی page/pageSize/rowIndex محاسبه می‌شود. */
503
883
  recordNumber(rowIndex) {
504
884
  return (this.pageIndex() - 1) * this.pageSizeS() + rowIndex + 1;
505
885
  }
@@ -542,6 +922,40 @@ class NgxTable {
542
922
  this.sortChange.emit(next);
543
923
  this.emitLazyIfNeeded();
544
924
  }
925
+ // ------------------------------------------------------------ فیلتر
926
+ isFilterable(field) {
927
+ return field.filterable === true || !!field.filterConfig?.enabled;
928
+ }
929
+ getFilterState(column) {
930
+ const f = this.activeFilters()[column];
931
+ return this.columnFilterService.isFilterActive(f) ? f : null;
932
+ }
933
+ getDistinctValues(column) {
934
+ const raw = this.data();
935
+ if (!raw || raw.length === 0)
936
+ return [];
937
+ return Array.from(new Set(raw.map((r) => r[column])));
938
+ }
939
+ onFilterApply(state) {
940
+ this.activeFilters.update((prev) => ({ ...prev, [state.field]: state }));
941
+ this.pageIndex.set(1);
942
+ this.pageChange.emit(1);
943
+ const activeList = Object.values(this.activeFilters()).filter((f) => this.columnFilterService.isFilterActive(f));
944
+ this.filterChange.emit(activeList);
945
+ this.emitLazyIfNeeded();
946
+ }
947
+ onFilterClear(field) {
948
+ this.activeFilters.update((prev) => {
949
+ const copy = { ...prev };
950
+ delete copy[field];
951
+ return copy;
952
+ });
953
+ this.pageIndex.set(1);
954
+ this.pageChange.emit(1);
955
+ const activeList = Object.values(this.activeFilters()).filter((f) => this.columnFilterService.isFilterActive(f));
956
+ this.filterChange.emit(activeList);
957
+ this.emitLazyIfNeeded();
958
+ }
545
959
  // ------------------------------------------------------------ صفحه‌بندی
546
960
  onPaginate(event) {
547
961
  this.pageIndex.set(event.page);
@@ -554,6 +968,7 @@ class NgxTable {
554
968
  return;
555
969
  const pageIndex = this.pageIndex();
556
970
  const pageSize = this.pageSizeS();
971
+ const activeFilterList = Object.values(this.activeFilters()).filter((f) => this.columnFilterService.isFilterActive(f));
557
972
  this.lazyLoad.emit({
558
973
  pageIndex,
559
974
  pageSize,
@@ -562,18 +977,13 @@ class NgxTable {
562
977
  sorting: this.sorts()
563
978
  ?.map((s) => `${s.field} ${s.direction}`)
564
979
  .join(','),
980
+ filters: activeFilterList,
565
981
  });
566
982
  }
567
983
  // -------------------------------------------------------------- ریسایز
568
984
  columnWidth(column) {
569
985
  return this.widths()[column] ?? this.config.column.defaultWidth;
570
986
  }
571
- /**
572
- * مقدار CSS `flex` واقعیِ این ستون:
573
- * - اگه دستی ریسایز شده یا width عددی داره → `0 0 <px>` (ثابت، بدون رشد/کوچک‌شدن)
574
- * - اگه width رشته‌ای داره (مثلاً '20%') → `0 0 <رشته>` (ثابت، هر واحد CSS معتبری)
575
- * - اگه اصلاً width نداره → `1 1 0` (رشد می‌کنه تا فضای باقی‌مونده‌ی جدول رو پر کنه)
576
- */
577
987
  columnFlex(field) {
578
988
  const resized = this.widths()[field.column];
579
989
  if (resized != null)
@@ -610,8 +1020,6 @@ class NgxTable {
610
1020
  getRenderer(field) {
611
1021
  if (!field.renderer)
612
1022
  return null;
613
- // نکته: نوع دقیق field.renderer (که به کلیدهای رجیستری محدود است) در
614
- // defineFields() چک می‌شود؛ اینجا فقط یک lookup رانتایم ساده لازم است.
615
1023
  return this.config.renderers[field.renderer] ?? null;
616
1024
  }
617
1025
  getCellValue(row, field) {
@@ -632,9 +1040,6 @@ class NgxTable {
632
1040
  stringify(value) {
633
1041
  return value == null ? '' : String(value);
634
1042
  }
635
- /** ورودی‌های renderer را به‌ازای هر (field, row) کش می‌کند تا هر Change
636
- * Detection یک آبجکت جدید ساخته نشود؛ با WeakMap، وقتی row از حافظه خارج
637
- * شود (مثلاً بعد از عوض‌شدن صفحه)، entry آن هم خودکار پاک می‌شود. */
638
1043
  getRendererInputs(row, field) {
639
1044
  let fieldCache = this.rendererInputsCache.get(field);
640
1045
  if (!fieldCache) {
@@ -654,15 +1059,15 @@ class NgxTable {
654
1059
  return inputs;
655
1060
  }
656
1061
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
657
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxTable, isStandalone: true, selector: "ngx-table", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: true, transformFunction: null }, showRecordNumber: { classPropertyName: "showRecordNumber", publicName: "showRecordNumber", isSignal: true, isRequired: false, transformFunction: null }, operations: { classPropertyName: "operations", publicName: "operations", isSignal: true, isRequired: false, transformFunction: null }, operationWidth: { classPropertyName: "operationWidth", publicName: "operationWidth", isSignal: true, isRequired: false, transformFunction: null }, numWidth: { classPropertyName: "numWidth", publicName: "numWidth", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, multiSort: { classPropertyName: "multiSort", publicName: "multiSort", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", lazyLoad: "lazyLoad", sortChange: "sortChange", columnResize: "columnResize" }, host: { properties: { "style.--ngx-table-scroll-height": "height()" }, classAttribute: "ngx-table-host" }, queries: [{ propertyName: "cellTemplates", predicate: TableCell }], ngImport: i0, template: "<div class=\"ngx-table-wrapper\" [class.ngx-table--loading]=\"loading()\">\n <div class=\"ngx-table-scroll\">\n <div\n class=\"ngx-table\"\n [class.ngx-table--striped]=\"config.stripedRows\"\n [class.ngx-table--hover]=\"config.hoverable\"\n role=\"table\">\n <div class=\"thead-mrs\" [class.thead-mrs--sticky]=\"config.stickyHeader\" role=\"rowgroup\">\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n {{ labels.operation }}\n </div>\n }\n @if (showRecordNumber()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n #\n </div>\n }\n\n @for (f of fields(); track f.column) {\n <div\n class=\"th-mrs\"\n role=\"columnheader\"\n [class.th-mrs--sortable]=\"f.sortable !== false\"\n [style.flex]=\"columnFlex(f)\"\n [attr.aria-sort]=\"\n sortDirectionFor(f.column) === 'asc'\n ? 'ascending'\n : sortDirectionFor(f.column) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(f, $event)\">\n <span class=\"th-mrs__title\">{{ f.title }}</span>\n\n @if (f.sortable !== false) {\n <span class=\"sort-icon\" [class.active]=\"sortDirectionFor(f.column) !== null\">\n @if (sortDirectionFor(f.column) === 'asc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M182.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H96V448c0 17.7 14.3 32 32 32s32-14.3 32-32V224h64c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z\" />\n </svg>\n } @else if (sortDirectionFor(f.column) === 'desc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 470.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8H224V64c0-17.7-14.3-32-32-32s-32 14.3-32 32V288H96c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z\" />\n </svg>\n } @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l96 96c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8H224v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V192H96c-12.9 0-24.6-7.8-29.6-19.8s-2.3-25.7 6.9-34.9l96-96zM32 400c0-17.7 14.3-32 32-32H256c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-17.7 0-32-14.3-32-32z\"\n opacity=\".35\" />\n </svg>\n }\n </span>\n }\n\n @if (isResizable(f)) {\n <span\n ngxColumnResize\n [ngxColumnResizeWidth]=\"columnWidth(f.column)\"\n [ngxColumnResizeMin]=\"columnMin(f)\"\n [ngxColumnResizeMax]=\"columnMax(f)\"\n [ngxColumnResizeDir]=\"resolvedDirection()\"\n (resizing)=\"onColumnResizing(f.column, $event)\"\n (resizeEnd)=\"onColumnResizeEnd(f.column, $event)\"\n (click)=\"$event.stopPropagation()\"></span>\n }\n </div>\n }\n </div>\n </div>\n\n <div class=\"tbody-mrs\" role=\"rowgroup\">\n @if (!loading() && pagedData().length === 0) {\n <div class=\"no-data\">{{ labels.noData }}</div>\n }\n\n @for (row of pagedData(); track trackBy()($index, row); let rowIndex = $index) {\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"td-mrs operations\"\n role=\"cell\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n <div class=\"cel\">\n <span class=\"cell-title\">{{ labels.operation }}:</span>\n <ng-container\n [ngTemplateOutlet]=\"operations()!\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </div>\n </div>\n }\n\n @if (showRecordNumber()) {\n <div class=\"td-mrs operations\" role=\"cell\" [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n <div class=\"cel\">{{ recordNumber(rowIndex) }}</div>\n </div>\n }\n\n @for (f of fields(); track f.column) {\n @let template = getTemplate(f.column);\n @let renderer = getRenderer(f);\n\n <div class=\"td-mrs\" role=\"cell\" [style.flex]=\"columnFlex(f)\">\n <div class=\"cel\" [class.wrap]=\"f.wrap\">\n <span class=\"cell-title\">{{ f.title }}</span>\n\n @if (template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{\n $implicit: row,\n row: row,\n value: getCellValue(row, f),\n rowIndex: rowIndex,\n field: f,\n }\"></ng-container>\n } @else if (renderer) {\n <ng-container\n *ngComponentOutlet=\"\n renderer;\n inputs: getRendererInputs(row, f)\n \"></ng-container>\n } @else {\n {{ formatCellValue(row, f) }}\n }\n\n @if (f.prefix) {\n <span>{{ f.prefix }}</span>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"loading-container\">\n <div class=\"loading\">{{ labels.loading }}</div>\n </div>\n }\n </div>\n</div>\n\n<ngx-pagination\n [page]=\"page()\"\n [pageSize]=\"pageSizeS()\"\n [total]=\"totalCount()\"\n (paginate)=\"onPaginate($event)\"></ngx-pagination>\n", styles: ["@charset \"UTF-8\";:host{--ngx-table-accent: light-dark(#4f46e5, #818cf8);--ngx-table-border: light-dark(#e5e7eb, #27272a);--ngx-table-header-bg: light-dark(#f8fafc, #1a1a26);--ngx-table-header-fg: light-dark(#334155, #d4d4d8);--ngx-table-row-bg: light-dark(#ffffff, #111118);--ngx-table-row-alt-bg: light-dark(#f9fafb, #17171b);--ngx-table-row-hover-bg: light-dark(#eef2ff, #7dc4fb27);--ngx-table-text: light-dark(#1e293b, #e4e4e7);--ngx-table-radius: 12px;display:block;color:var(--ngx-table-text)}.ngx-table-wrapper{position:relative;border:1px solid var(--ngx-table-border);border-radius:var(--ngx-table-radius);overflow:hidden;background:var(--ngx-table-row-bg)}.ngx-table-scroll{overflow:auto;max-height:var(--ngx-table-scroll-height, 75vh)}.ngx-table-scroll::-webkit-scrollbar{height:10px;width:10px}.ngx-table-scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ngx-table-border) 60%,gray);border-radius:8px}.ngx-table{display:flex;flex-direction:column;min-width:100%;width:max-content}.thead-mrs{display:flex;flex-direction:column;z-index:2;background:var(--ngx-table-header-bg);color:var(--ngx-table-header-fg)}.thead-mrs--sticky{position:sticky;top:0}.tr-mrs{display:flex;align-items:stretch}.th-mrs,.td-mrs{position:relative;box-sizing:border-box;overflow:hidden;min-inline-size:0;border-bottom:1px solid var(--ngx-table-border);border-inline-end:1px solid var(--ngx-table-border)}.th-mrs:last-child,.td-mrs:last-child{border-inline-end:none}.th-mrs{display:flex;align-items:center;gap:.375rem;font-weight:600;font-size:.8125rem;letter-spacing:.02em;padding:.75rem 1rem;-webkit-user-select:none;user-select:none;white-space:nowrap;text-overflow:ellipsis}.th-mrs--sortable{cursor:pointer}.th-mrs--sortable:hover{color:var(--ngx-table-accent)}.th-mrs.operations{justify-content:center}.th-mrs__title{overflow:hidden;text-overflow:ellipsis}.sort-icon{width:.75rem;height:.75rem;flex:none;opacity:.4;display:inline-flex}.sort-icon svg{width:100%;height:100%}.sort-icon.active{opacity:1}.ngx-table__resize-handle,[ngxColumnResize]{position:absolute;top:0;bottom:0;inset-inline-end:-3px;width:6px;cursor:col-resize;touch-action:none;z-index:3}.ngx-table__resize-handle:hover,.ngx-table__resize-handle:active,[ngxColumnResize]:hover,[ngxColumnResize]:active{background:var(--ngx-table-accent);opacity:.5}.td-mrs{display:flex;align-items:center;padding:.75rem 1rem;font-size:.875rem}.td-mrs.operations{justify-content:center}.cel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:.25rem}.cel.wrap{white-space:normal}.cell-title{display:none;font-weight:600;color:var(--ngx-table-header-fg)}.ngx-table--striped .tbody-mrs .tr-mrs:nth-child(2n){background:var(--ngx-table-row-alt-bg)}.ngx-table--hover .tbody-mrs .tr-mrs:hover{background:var(--ngx-table-row-hover-bg)}.tr-mrs{transition:background-color .15s ease}.no-data{padding:2.5rem 1rem;text-align:center;color:#94a3b8;font-size:.875rem}.loading-container{-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.loading-container .loading{border:5px rgba(88,84,84,.82) solid;border-radius:10px;padding:18px 25px;background:#3b3b3bab;color:#fff}.loading{font-size:.875rem;color:var(--ngx-table-accent)}@media(max-width:768px){.ngx-table-scroll{overflow:visible;max-height:none}.ngx-table{width:100%}.thead-mrs{display:none}.tbody-mrs{display:flex;flex-direction:column;gap:.75rem;padding:.75rem}.tr-mrs{flex-direction:column;border:1px solid var(--ngx-table-border);border-radius:10px;overflow:hidden}.td-mrs{flex:1 1 auto!important;width:100%;border-inline-end:none;justify-content:space-between}.cel{justify-content:flex-end;white-space:normal;text-align:end}.cell-title{display:inline}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgxPagination, selector: "ngx-pagination", inputs: ["total", "page", "pageSize", "pageSizeOptions", "siblingCount", "boundaryCount"], outputs: ["pageChange", "pageSizeChange", "paginate"] }, { kind: "directive", type: ColumnResize, selector: "[ngxColumnResize]", inputs: ["ngxColumnResizeWidth", "ngxColumnResizeMin", "ngxColumnResizeMax", "ngxColumnResizeDir"], outputs: ["resizing", "resizeEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1062
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxTable, isStandalone: true, selector: "ngx-table", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: true, transformFunction: null }, showRecordNumber: { classPropertyName: "showRecordNumber", publicName: "showRecordNumber", isSignal: true, isRequired: false, transformFunction: null }, operations: { classPropertyName: "operations", publicName: "operations", isSignal: true, isRequired: false, transformFunction: null }, operationWidth: { classPropertyName: "operationWidth", publicName: "operationWidth", isSignal: true, isRequired: false, transformFunction: null }, numWidth: { classPropertyName: "numWidth", publicName: "numWidth", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, multiSort: { classPropertyName: "multiSort", publicName: "multiSort", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", lazyLoad: "lazyLoad", sortChange: "sortChange", columnResize: "columnResize", filterChange: "filterChange" }, host: { properties: { "style.--ngx-table-scroll-height": "height()" }, classAttribute: "ngx-table-host" }, queries: [{ propertyName: "cellTemplates", predicate: TableCell }], ngImport: i0, template: "<div class=\"ngx-table-wrapper\" [class.ngx-table--loading]=\"loading()\">\n <div class=\"ngx-table-scroll\">\n <div\n class=\"ngx-table\"\n [class.ngx-table--striped]=\"config.stripedRows\"\n [class.ngx-table--hover]=\"config.hoverable\"\n role=\"table\">\n <div class=\"thead-mrs\" [class.thead-mrs--sticky]=\"config.stickyHeader\" role=\"rowgroup\">\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n {{ labels.operation }}\n </div>\n }\n @if (showRecordNumber()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n #\n </div>\n }\n\n @for (f of fields(); track f.column) {\n <div\n class=\"th-mrs\"\n role=\"columnheader\"\n [class.th-mrs--sortable]=\"f.sortable !== false\"\n [style.flex]=\"columnFlex(f)\"\n [attr.aria-sort]=\"\n sortDirectionFor(f.column) === 'asc'\n ? 'ascending'\n : sortDirectionFor(f.column) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(f, $event)\">\n <span class=\"th-mrs__title\">{{ f.title }}</span>\n\n @if (f.sortable !== false) {\n <span class=\"sort-icon\" [class.active]=\"sortDirectionFor(f.column) !== null\">\n @if (sortDirectionFor(f.column) === 'asc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M182.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H96V448c0 17.7 14.3 32 32 32s32-14.3 32-32V224h64c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z\" />\n </svg>\n } @else if (sortDirectionFor(f.column) === 'desc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 470.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8H224V64c0-17.7-14.3-32-32-32s-32 14.3-32 32V288H96c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z\" />\n </svg>\n } @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l96 96c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8H224v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V192H96c-12.9 0-24.6-7.8-29.6-19.8s-2.3-25.7 6.9-34.9l96-96zM32 400c0-17.7 14.3-32 32-32H256c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-17.7 0-32-14.3-32-32z\"\n opacity=\".35\" />\n </svg>\n }\n </span>\n }\n\n @if (isFilterable(f)) {\n <ngx-column-filter\n [tableEl]=\"elementRef\"\n [field]=\"f.column\"\n [config]=\"f.filterConfig\"\n [currentFilter]=\"getFilterState(f.column)\"\n [distinctValues]=\"getDistinctValues(f.column)\"\n (applyFilter)=\"onFilterApply($event)\"\n (clearFilter)=\"onFilterClear($event)\"></ngx-column-filter>\n }\n\n @if (isResizable(f)) {\n <span\n ngxColumnResize\n [ngxColumnResizeWidth]=\"columnWidth(f.column)\"\n [ngxColumnResizeMin]=\"columnMin(f)\"\n [ngxColumnResizeMax]=\"columnMax(f)\"\n [ngxColumnResizeDir]=\"resolvedDirection()\"\n (resizing)=\"onColumnResizing(f.column, $event)\"\n (resizeEnd)=\"onColumnResizeEnd(f.column, $event)\"\n (click)=\"$event.stopPropagation()\"></span>\n }\n </div>\n }\n </div>\n </div>\n\n <div class=\"tbody-mrs\" role=\"rowgroup\">\n @if (!loading() && pagedData().length === 0) {\n <div class=\"no-data\">{{ labels.noData }}</div>\n }\n\n @for (row of pagedData(); track trackBy()($index, row); let rowIndex = $index) {\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"td-mrs operations\"\n role=\"cell\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n <div class=\"cel\">\n <span class=\"cell-title\">{{ labels.operation }}:</span>\n <ng-container\n [ngTemplateOutlet]=\"operations()!\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </div>\n </div>\n }\n\n @if (showRecordNumber()) {\n <div class=\"td-mrs operations\" role=\"cell\" [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n <div class=\"cel\">{{ recordNumber(rowIndex) }}</div>\n </div>\n }\n\n @for (f of fields(); track f.column) {\n @let template = getTemplate(f.column);\n @let renderer = getRenderer(f);\n\n <div class=\"td-mrs\" role=\"cell\" [style.flex]=\"columnFlex(f)\">\n <div class=\"cel\" [class.wrap]=\"f.wrap\">\n <span class=\"cell-title\">{{ f.title }}</span>\n\n @if (template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{\n $implicit: row,\n row: row,\n value: getCellValue(row, f),\n rowIndex: rowIndex,\n field: f,\n }\"></ng-container>\n } @else if (renderer) {\n <ng-container\n *ngComponentOutlet=\"\n renderer;\n inputs: getRendererInputs(row, f)\n \"></ng-container>\n } @else {\n {{ formatCellValue(row, f) }}\n }\n\n @if (f.prefix) {\n <span>{{ f.prefix }}</span>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"loading-container\">\n <div class=\"loading\">{{ labels.loading }}</div>\n </div>\n }\n </div>\n</div>\n\n<ngx-pagination\n [page]=\"pageIndex()\"\n [pageSize]=\"pageSizeS()\"\n [total]=\"totalCount()\"\n (paginate)=\"onPaginate($event)\"></ngx-pagination>\n", styles: ["@charset \"UTF-8\";:host{--ngx-table-accent: light-dark(#4f46e5, #818cf8);--ngx-table-border: light-dark(#e5e7eb, #27272a);--ngx-table-header-bg: light-dark(#f8fafc, #1a1a26);--ngx-table-header-fg: light-dark(#334155, #d4d4d8);--ngx-table-row-bg: light-dark(#ffffff, #111118);--ngx-table-row-alt-bg: light-dark(#f9fafb, #17171b);--ngx-table-row-hover-bg: light-dark(#eef2ff, #7dc4fb27);--ngx-table-text: light-dark(#1e293b, #e4e4e7);--ngx-table-radius: 12px;display:block;color:var(--ngx-table-text)}.ngx-table-wrapper{position:relative;border:1px solid var(--ngx-table-border);border-radius:var(--ngx-table-radius);overflow:hidden;background:var(--ngx-table-row-bg)}.ngx-table-scroll{overflow:auto;height:var(--ngx-table-scroll-height, 75vh)}.ngx-table-scroll::-webkit-scrollbar{height:10px;width:10px}.ngx-table-scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ngx-table-border) 60%,gray);border-radius:8px}.ngx-table{display:flex;flex-direction:column;min-width:100%;width:max-content}.thead-mrs{display:flex;flex-direction:column;z-index:2;background:var(--ngx-table-header-bg);color:var(--ngx-table-header-fg)}.thead-mrs--sticky{position:sticky;top:0}.tr-mrs{display:flex;align-items:stretch}.th-mrs,.td-mrs{position:relative;box-sizing:border-box;min-inline-size:0;border-bottom:1px solid var(--ngx-table-border);border-inline-end:1px solid var(--ngx-table-border)}.th-mrs:last-child,.td-mrs:last-child{border-inline-end:none}.th-mrs{display:flex;align-items:center;gap:.375rem;font-weight:600;font-size:.8125rem;letter-spacing:.02em;padding:.75rem 1rem;-webkit-user-select:none;user-select:none;white-space:nowrap;text-overflow:ellipsis}.th-mrs--sortable{cursor:pointer}.th-mrs--sortable:hover{color:var(--ngx-table-accent)}.th-mrs.operations{justify-content:center}.th-mrs__title{overflow:hidden;text-overflow:ellipsis}.sort-icon{width:.75rem;height:.75rem;flex:none;opacity:.4;display:inline-flex}.sort-icon svg{width:100%;height:100%}.sort-icon.active{opacity:1}.ngx-table__resize-handle,[ngxColumnResize]{position:absolute;top:0;bottom:0;inset-inline-end:-3px;width:6px;cursor:col-resize;touch-action:none;z-index:3}.ngx-table__resize-handle:hover,.ngx-table__resize-handle:active,[ngxColumnResize]:hover,[ngxColumnResize]:active{background:var(--ngx-table-accent);opacity:.5}.td-mrs{display:flex;align-items:center;padding:.75rem 1rem;font-size:.875rem}.td-mrs.operations{justify-content:center}.cel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:.25rem}.cel.wrap{white-space:normal}.cell-title{display:none;font-weight:600;color:var(--ngx-table-header-fg)}.ngx-table--striped .tbody-mrs .tr-mrs:nth-child(2n){background:var(--ngx-table-row-alt-bg)}.ngx-table--hover .tbody-mrs .tr-mrs:hover{background:var(--ngx-table-row-hover-bg)}.tr-mrs{transition:background-color .15s ease}.no-data{padding:2.5rem 1rem;text-align:center;color:#94a3b8;font-size:.875rem}.loading-container{-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.loading-container .loading{border:5px rgba(88,84,84,.82) solid;border-radius:10px;padding:18px 25px;background:#3b3b3bab;color:#fff}.loading{font-size:.875rem;color:var(--ngx-table-accent)}@media(max-width:768px){.ngx-table-scroll{overflow:visible;max-height:none}.ngx-table{width:100%}.thead-mrs{display:none}.tbody-mrs{display:flex;flex-direction:column;gap:.75rem;padding:.75rem}.tr-mrs{flex-direction:column;border:1px solid var(--ngx-table-border);border-radius:10px;overflow:hidden}.td-mrs{flex:1 1 auto!important;width:100%;border-inline-end:none;justify-content:space-between}.cel{justify-content:flex-end;white-space:normal;text-align:end}.cell-title{display:inline}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgxPagination, selector: "ngx-pagination", inputs: ["total", "page", "pageSize", "pageSizeOptions", "siblingCount", "boundaryCount"], outputs: ["pageChange", "pageSizeChange", "paginate"] }, { kind: "directive", type: ColumnResize, selector: "[ngxColumnResize]", inputs: ["ngxColumnResizeWidth", "ngxColumnResizeMin", "ngxColumnResizeMax", "ngxColumnResizeDir"], outputs: ["resizing", "resizeEnd"] }, { kind: "component", type: NgxColumnFilter, selector: "ngx-column-filter", inputs: ["tableEl", "field", "config", "currentFilter", "distinctValues"], outputs: ["applyFilter", "clearFilter"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
658
1063
  }
659
1064
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxTable, decorators: [{
660
1065
  type: Component,
661
- args: [{ selector: 'ngx-table', standalone: true, imports: [CommonModule, NgxPagination, ColumnResize], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1066
+ args: [{ selector: 'ngx-table', standalone: true, imports: [CommonModule, NgxPagination, ColumnResize, NgxColumnFilter], changeDetection: ChangeDetectionStrategy.OnPush, host: {
662
1067
  class: 'ngx-table-host',
663
1068
  '[style.--ngx-table-scroll-height]': 'height()',
664
- }, template: "<div class=\"ngx-table-wrapper\" [class.ngx-table--loading]=\"loading()\">\n <div class=\"ngx-table-scroll\">\n <div\n class=\"ngx-table\"\n [class.ngx-table--striped]=\"config.stripedRows\"\n [class.ngx-table--hover]=\"config.hoverable\"\n role=\"table\">\n <div class=\"thead-mrs\" [class.thead-mrs--sticky]=\"config.stickyHeader\" role=\"rowgroup\">\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n {{ labels.operation }}\n </div>\n }\n @if (showRecordNumber()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n #\n </div>\n }\n\n @for (f of fields(); track f.column) {\n <div\n class=\"th-mrs\"\n role=\"columnheader\"\n [class.th-mrs--sortable]=\"f.sortable !== false\"\n [style.flex]=\"columnFlex(f)\"\n [attr.aria-sort]=\"\n sortDirectionFor(f.column) === 'asc'\n ? 'ascending'\n : sortDirectionFor(f.column) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(f, $event)\">\n <span class=\"th-mrs__title\">{{ f.title }}</span>\n\n @if (f.sortable !== false) {\n <span class=\"sort-icon\" [class.active]=\"sortDirectionFor(f.column) !== null\">\n @if (sortDirectionFor(f.column) === 'asc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M182.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H96V448c0 17.7 14.3 32 32 32s32-14.3 32-32V224h64c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z\" />\n </svg>\n } @else if (sortDirectionFor(f.column) === 'desc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 470.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8H224V64c0-17.7-14.3-32-32-32s-32 14.3-32 32V288H96c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z\" />\n </svg>\n } @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l96 96c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8H224v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V192H96c-12.9 0-24.6-7.8-29.6-19.8s-2.3-25.7 6.9-34.9l96-96zM32 400c0-17.7 14.3-32 32-32H256c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-17.7 0-32-14.3-32-32z\"\n opacity=\".35\" />\n </svg>\n }\n </span>\n }\n\n @if (isResizable(f)) {\n <span\n ngxColumnResize\n [ngxColumnResizeWidth]=\"columnWidth(f.column)\"\n [ngxColumnResizeMin]=\"columnMin(f)\"\n [ngxColumnResizeMax]=\"columnMax(f)\"\n [ngxColumnResizeDir]=\"resolvedDirection()\"\n (resizing)=\"onColumnResizing(f.column, $event)\"\n (resizeEnd)=\"onColumnResizeEnd(f.column, $event)\"\n (click)=\"$event.stopPropagation()\"></span>\n }\n </div>\n }\n </div>\n </div>\n\n <div class=\"tbody-mrs\" role=\"rowgroup\">\n @if (!loading() && pagedData().length === 0) {\n <div class=\"no-data\">{{ labels.noData }}</div>\n }\n\n @for (row of pagedData(); track trackBy()($index, row); let rowIndex = $index) {\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"td-mrs operations\"\n role=\"cell\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n <div class=\"cel\">\n <span class=\"cell-title\">{{ labels.operation }}:</span>\n <ng-container\n [ngTemplateOutlet]=\"operations()!\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </div>\n </div>\n }\n\n @if (showRecordNumber()) {\n <div class=\"td-mrs operations\" role=\"cell\" [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n <div class=\"cel\">{{ recordNumber(rowIndex) }}</div>\n </div>\n }\n\n @for (f of fields(); track f.column) {\n @let template = getTemplate(f.column);\n @let renderer = getRenderer(f);\n\n <div class=\"td-mrs\" role=\"cell\" [style.flex]=\"columnFlex(f)\">\n <div class=\"cel\" [class.wrap]=\"f.wrap\">\n <span class=\"cell-title\">{{ f.title }}</span>\n\n @if (template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{\n $implicit: row,\n row: row,\n value: getCellValue(row, f),\n rowIndex: rowIndex,\n field: f,\n }\"></ng-container>\n } @else if (renderer) {\n <ng-container\n *ngComponentOutlet=\"\n renderer;\n inputs: getRendererInputs(row, f)\n \"></ng-container>\n } @else {\n {{ formatCellValue(row, f) }}\n }\n\n @if (f.prefix) {\n <span>{{ f.prefix }}</span>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"loading-container\">\n <div class=\"loading\">{{ labels.loading }}</div>\n </div>\n }\n </div>\n</div>\n\n<ngx-pagination\n [page]=\"page()\"\n [pageSize]=\"pageSizeS()\"\n [total]=\"totalCount()\"\n (paginate)=\"onPaginate($event)\"></ngx-pagination>\n", styles: ["@charset \"UTF-8\";:host{--ngx-table-accent: light-dark(#4f46e5, #818cf8);--ngx-table-border: light-dark(#e5e7eb, #27272a);--ngx-table-header-bg: light-dark(#f8fafc, #1a1a26);--ngx-table-header-fg: light-dark(#334155, #d4d4d8);--ngx-table-row-bg: light-dark(#ffffff, #111118);--ngx-table-row-alt-bg: light-dark(#f9fafb, #17171b);--ngx-table-row-hover-bg: light-dark(#eef2ff, #7dc4fb27);--ngx-table-text: light-dark(#1e293b, #e4e4e7);--ngx-table-radius: 12px;display:block;color:var(--ngx-table-text)}.ngx-table-wrapper{position:relative;border:1px solid var(--ngx-table-border);border-radius:var(--ngx-table-radius);overflow:hidden;background:var(--ngx-table-row-bg)}.ngx-table-scroll{overflow:auto;max-height:var(--ngx-table-scroll-height, 75vh)}.ngx-table-scroll::-webkit-scrollbar{height:10px;width:10px}.ngx-table-scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ngx-table-border) 60%,gray);border-radius:8px}.ngx-table{display:flex;flex-direction:column;min-width:100%;width:max-content}.thead-mrs{display:flex;flex-direction:column;z-index:2;background:var(--ngx-table-header-bg);color:var(--ngx-table-header-fg)}.thead-mrs--sticky{position:sticky;top:0}.tr-mrs{display:flex;align-items:stretch}.th-mrs,.td-mrs{position:relative;box-sizing:border-box;overflow:hidden;min-inline-size:0;border-bottom:1px solid var(--ngx-table-border);border-inline-end:1px solid var(--ngx-table-border)}.th-mrs:last-child,.td-mrs:last-child{border-inline-end:none}.th-mrs{display:flex;align-items:center;gap:.375rem;font-weight:600;font-size:.8125rem;letter-spacing:.02em;padding:.75rem 1rem;-webkit-user-select:none;user-select:none;white-space:nowrap;text-overflow:ellipsis}.th-mrs--sortable{cursor:pointer}.th-mrs--sortable:hover{color:var(--ngx-table-accent)}.th-mrs.operations{justify-content:center}.th-mrs__title{overflow:hidden;text-overflow:ellipsis}.sort-icon{width:.75rem;height:.75rem;flex:none;opacity:.4;display:inline-flex}.sort-icon svg{width:100%;height:100%}.sort-icon.active{opacity:1}.ngx-table__resize-handle,[ngxColumnResize]{position:absolute;top:0;bottom:0;inset-inline-end:-3px;width:6px;cursor:col-resize;touch-action:none;z-index:3}.ngx-table__resize-handle:hover,.ngx-table__resize-handle:active,[ngxColumnResize]:hover,[ngxColumnResize]:active{background:var(--ngx-table-accent);opacity:.5}.td-mrs{display:flex;align-items:center;padding:.75rem 1rem;font-size:.875rem}.td-mrs.operations{justify-content:center}.cel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:.25rem}.cel.wrap{white-space:normal}.cell-title{display:none;font-weight:600;color:var(--ngx-table-header-fg)}.ngx-table--striped .tbody-mrs .tr-mrs:nth-child(2n){background:var(--ngx-table-row-alt-bg)}.ngx-table--hover .tbody-mrs .tr-mrs:hover{background:var(--ngx-table-row-hover-bg)}.tr-mrs{transition:background-color .15s ease}.no-data{padding:2.5rem 1rem;text-align:center;color:#94a3b8;font-size:.875rem}.loading-container{-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.loading-container .loading{border:5px rgba(88,84,84,.82) solid;border-radius:10px;padding:18px 25px;background:#3b3b3bab;color:#fff}.loading{font-size:.875rem;color:var(--ngx-table-accent)}@media(max-width:768px){.ngx-table-scroll{overflow:visible;max-height:none}.ngx-table{width:100%}.thead-mrs{display:none}.tbody-mrs{display:flex;flex-direction:column;gap:.75rem;padding:.75rem}.tr-mrs{flex-direction:column;border:1px solid var(--ngx-table-border);border-radius:10px;overflow:hidden}.td-mrs{flex:1 1 auto!important;width:100%;border-inline-end:none;justify-content:space-between}.cel{justify-content:flex-end;white-space:normal;text-align:end}.cell-title{display:inline}}\n"] }]
665
- }], ctorParameters: () => [], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: true }] }], showRecordNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRecordNumber", required: false }] }], operations: [{ type: i0.Input, args: [{ isSignal: true, alias: "operations", required: false }] }], operationWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "operationWidth", required: false }] }], numWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "numWidth", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], multiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSort", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], cellTemplates: [{
1069
+ }, template: "<div class=\"ngx-table-wrapper\" [class.ngx-table--loading]=\"loading()\">\n <div class=\"ngx-table-scroll\">\n <div\n class=\"ngx-table\"\n [class.ngx-table--striped]=\"config.stripedRows\"\n [class.ngx-table--hover]=\"config.hoverable\"\n role=\"table\">\n <div class=\"thead-mrs\" [class.thead-mrs--sticky]=\"config.stickyHeader\" role=\"rowgroup\">\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n {{ labels.operation }}\n </div>\n }\n @if (showRecordNumber()) {\n <div\n class=\"th-mrs operations\"\n role=\"columnheader\"\n [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n #\n </div>\n }\n\n @for (f of fields(); track f.column) {\n <div\n class=\"th-mrs\"\n role=\"columnheader\"\n [class.th-mrs--sortable]=\"f.sortable !== false\"\n [style.flex]=\"columnFlex(f)\"\n [attr.aria-sort]=\"\n sortDirectionFor(f.column) === 'asc'\n ? 'ascending'\n : sortDirectionFor(f.column) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(f, $event)\">\n <span class=\"th-mrs__title\">{{ f.title }}</span>\n\n @if (f.sortable !== false) {\n <span class=\"sort-icon\" [class.active]=\"sortDirectionFor(f.column) !== null\">\n @if (sortDirectionFor(f.column) === 'asc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M182.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H96V448c0 17.7 14.3 32 32 32s32-14.3 32-32V224h64c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z\" />\n </svg>\n } @else if (sortDirectionFor(f.column) === 'desc') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 470.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8H224V64c0-17.7-14.3-32-32-32s-32 14.3-32 32V288H96c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z\" />\n </svg>\n } @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\">\n <path\n fill=\"currentColor\"\n d=\"M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l96 96c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8H224v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V192H96c-12.9 0-24.6-7.8-29.6-19.8s-2.3-25.7 6.9-34.9l96-96zM32 400c0-17.7 14.3-32 32-32H256c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-17.7 0-32-14.3-32-32z\"\n opacity=\".35\" />\n </svg>\n }\n </span>\n }\n\n @if (isFilterable(f)) {\n <ngx-column-filter\n [tableEl]=\"elementRef\"\n [field]=\"f.column\"\n [config]=\"f.filterConfig\"\n [currentFilter]=\"getFilterState(f.column)\"\n [distinctValues]=\"getDistinctValues(f.column)\"\n (applyFilter)=\"onFilterApply($event)\"\n (clearFilter)=\"onFilterClear($event)\"></ngx-column-filter>\n }\n\n @if (isResizable(f)) {\n <span\n ngxColumnResize\n [ngxColumnResizeWidth]=\"columnWidth(f.column)\"\n [ngxColumnResizeMin]=\"columnMin(f)\"\n [ngxColumnResizeMax]=\"columnMax(f)\"\n [ngxColumnResizeDir]=\"resolvedDirection()\"\n (resizing)=\"onColumnResizing(f.column, $event)\"\n (resizeEnd)=\"onColumnResizeEnd(f.column, $event)\"\n (click)=\"$event.stopPropagation()\"></span>\n }\n </div>\n }\n </div>\n </div>\n\n <div class=\"tbody-mrs\" role=\"rowgroup\">\n @if (!loading() && pagedData().length === 0) {\n <div class=\"no-data\">{{ labels.noData }}</div>\n }\n\n @for (row of pagedData(); track trackBy()($index, row); let rowIndex = $index) {\n <div class=\"tr-mrs\" role=\"row\">\n @if (operations()) {\n <div\n class=\"td-mrs operations\"\n role=\"cell\"\n [style.flex]=\"'0 0 ' + operationWidth() + 'px'\">\n <div class=\"cel\">\n <span class=\"cell-title\">{{ labels.operation }}:</span>\n <ng-container\n [ngTemplateOutlet]=\"operations()!\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </div>\n </div>\n }\n\n @if (showRecordNumber()) {\n <div class=\"td-mrs operations\" role=\"cell\" [style.flex]=\"'0 0 ' + numWidth() + 'px'\">\n <div class=\"cel\">{{ recordNumber(rowIndex) }}</div>\n </div>\n }\n\n @for (f of fields(); track f.column) {\n @let template = getTemplate(f.column);\n @let renderer = getRenderer(f);\n\n <div class=\"td-mrs\" role=\"cell\" [style.flex]=\"columnFlex(f)\">\n <div class=\"cel\" [class.wrap]=\"f.wrap\">\n <span class=\"cell-title\">{{ f.title }}</span>\n\n @if (template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{\n $implicit: row,\n row: row,\n value: getCellValue(row, f),\n rowIndex: rowIndex,\n field: f,\n }\"></ng-container>\n } @else if (renderer) {\n <ng-container\n *ngComponentOutlet=\"\n renderer;\n inputs: getRendererInputs(row, f)\n \"></ng-container>\n } @else {\n {{ formatCellValue(row, f) }}\n }\n\n @if (f.prefix) {\n <span>{{ f.prefix }}</span>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"loading-container\">\n <div class=\"loading\">{{ labels.loading }}</div>\n </div>\n }\n </div>\n</div>\n\n<ngx-pagination\n [page]=\"pageIndex()\"\n [pageSize]=\"pageSizeS()\"\n [total]=\"totalCount()\"\n (paginate)=\"onPaginate($event)\"></ngx-pagination>\n", styles: ["@charset \"UTF-8\";:host{--ngx-table-accent: light-dark(#4f46e5, #818cf8);--ngx-table-border: light-dark(#e5e7eb, #27272a);--ngx-table-header-bg: light-dark(#f8fafc, #1a1a26);--ngx-table-header-fg: light-dark(#334155, #d4d4d8);--ngx-table-row-bg: light-dark(#ffffff, #111118);--ngx-table-row-alt-bg: light-dark(#f9fafb, #17171b);--ngx-table-row-hover-bg: light-dark(#eef2ff, #7dc4fb27);--ngx-table-text: light-dark(#1e293b, #e4e4e7);--ngx-table-radius: 12px;display:block;color:var(--ngx-table-text)}.ngx-table-wrapper{position:relative;border:1px solid var(--ngx-table-border);border-radius:var(--ngx-table-radius);overflow:hidden;background:var(--ngx-table-row-bg)}.ngx-table-scroll{overflow:auto;height:var(--ngx-table-scroll-height, 75vh)}.ngx-table-scroll::-webkit-scrollbar{height:10px;width:10px}.ngx-table-scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ngx-table-border) 60%,gray);border-radius:8px}.ngx-table{display:flex;flex-direction:column;min-width:100%;width:max-content}.thead-mrs{display:flex;flex-direction:column;z-index:2;background:var(--ngx-table-header-bg);color:var(--ngx-table-header-fg)}.thead-mrs--sticky{position:sticky;top:0}.tr-mrs{display:flex;align-items:stretch}.th-mrs,.td-mrs{position:relative;box-sizing:border-box;min-inline-size:0;border-bottom:1px solid var(--ngx-table-border);border-inline-end:1px solid var(--ngx-table-border)}.th-mrs:last-child,.td-mrs:last-child{border-inline-end:none}.th-mrs{display:flex;align-items:center;gap:.375rem;font-weight:600;font-size:.8125rem;letter-spacing:.02em;padding:.75rem 1rem;-webkit-user-select:none;user-select:none;white-space:nowrap;text-overflow:ellipsis}.th-mrs--sortable{cursor:pointer}.th-mrs--sortable:hover{color:var(--ngx-table-accent)}.th-mrs.operations{justify-content:center}.th-mrs__title{overflow:hidden;text-overflow:ellipsis}.sort-icon{width:.75rem;height:.75rem;flex:none;opacity:.4;display:inline-flex}.sort-icon svg{width:100%;height:100%}.sort-icon.active{opacity:1}.ngx-table__resize-handle,[ngxColumnResize]{position:absolute;top:0;bottom:0;inset-inline-end:-3px;width:6px;cursor:col-resize;touch-action:none;z-index:3}.ngx-table__resize-handle:hover,.ngx-table__resize-handle:active,[ngxColumnResize]:hover,[ngxColumnResize]:active{background:var(--ngx-table-accent);opacity:.5}.td-mrs{display:flex;align-items:center;padding:.75rem 1rem;font-size:.875rem}.td-mrs.operations{justify-content:center}.cel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:.25rem}.cel.wrap{white-space:normal}.cell-title{display:none;font-weight:600;color:var(--ngx-table-header-fg)}.ngx-table--striped .tbody-mrs .tr-mrs:nth-child(2n){background:var(--ngx-table-row-alt-bg)}.ngx-table--hover .tbody-mrs .tr-mrs:hover{background:var(--ngx-table-row-hover-bg)}.tr-mrs{transition:background-color .15s ease}.no-data{padding:2.5rem 1rem;text-align:center;color:#94a3b8;font-size:.875rem}.loading-container{-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.loading-container .loading{border:5px rgba(88,84,84,.82) solid;border-radius:10px;padding:18px 25px;background:#3b3b3bab;color:#fff}.loading{font-size:.875rem;color:var(--ngx-table-accent)}@media(max-width:768px){.ngx-table-scroll{overflow:visible;max-height:none}.ngx-table{width:100%}.thead-mrs{display:none}.tbody-mrs{display:flex;flex-direction:column;gap:.75rem;padding:.75rem}.tr-mrs{flex-direction:column;border:1px solid var(--ngx-table-border);border-radius:10px;overflow:hidden}.td-mrs{flex:1 1 auto!important;width:100%;border-inline-end:none;justify-content:space-between}.cel{justify-content:flex-end;white-space:normal;text-align:end}.cell-title{display:inline}}\n"] }]
1070
+ }], ctorParameters: () => [], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: true }] }], showRecordNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRecordNumber", required: false }] }], operations: [{ type: i0.Input, args: [{ isSignal: true, alias: "operations", required: false }] }], operationWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "operationWidth", required: false }] }], numWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "numWidth", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], multiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSort", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], cellTemplates: [{
666
1071
  type: ContentChildren,
667
1072
  args: [TableCell]
668
1073
  }] } });
@@ -673,5 +1078,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
673
1078
  * Generated bundle index. Do not edit.
674
1079
  */
675
1080
 
676
- export { ColumnResize, NGX_PAGINATION_CONFIG, NGX_PAGINATION_I18N, NGX_TABLE_CONFIG, NGX_TABLE_I18N, NgxPagination, NgxTable, PaginationLocalization, TableCell, TableLocalization, defineFields, defineRenderers, providePagination, provideTable };
1081
+ export { ColumnFilterService, ColumnResize, DynamicDataFilterOperator, NGX_PAGINATION_CONFIG, NGX_PAGINATION_I18N, NGX_TABLE_CONFIG, NGX_TABLE_I18N, NgxColumnFilter, NgxPagination, NgxTable, PaginationLocalization, TableCell, TableLocalization, defineFields, defineRenderers, providePagination, provideTable };
677
1082
  //# sourceMappingURL=ngx-kit-data-table.mjs.map