@frame-kit/ui-ng 0.0.1
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.
- package/COMPONENTS.md +683 -0
- package/DEVELOPMENT_GUIDE.md +1102 -0
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/THEMING.md +130 -0
- package/core/headline/README.md +121 -0
- package/core/icon/README.md +173 -0
- package/core/image/README.md +210 -0
- package/core/link/README.md +297 -0
- package/core/separator/README.md +145 -0
- package/core/text/README.md +240 -0
- package/directives/infinite-scroll/README.md +102 -0
- package/directives/spotlight/README.md +154 -0
- package/directives/tooltip/README.md +147 -0
- package/docs/endpoint-link/README.md +142 -0
- package/docs/method-badge/README.md +154 -0
- package/fesm2022/frame-kit-ui-ng-core-headline.mjs +122 -0
- package/fesm2022/frame-kit-ui-ng-core-headline.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-icon.mjs +189 -0
- package/fesm2022/frame-kit-ui-ng-core-icon.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-image.mjs +123 -0
- package/fesm2022/frame-kit-ui-ng-core-image.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-link.mjs +369 -0
- package/fesm2022/frame-kit-ui-ng-core-link.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-separator.mjs +59 -0
- package/fesm2022/frame-kit-ui-ng-core-separator.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-core-text.mjs +204 -0
- package/fesm2022/frame-kit-ui-ng-core-text.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs +74 -0
- package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs +76 -0
- package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs +425 -0
- package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs +63 -0
- package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs +43 -0
- package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-forms.mjs +3632 -0
- package/fesm2022/frame-kit-ui-ng-forms.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs +239 -0
- package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs +132 -0
- package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs +133 -0
- package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs +60 -0
- package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-services-toast.mjs +166 -0
- package/fesm2022/frame-kit-ui-ng-services-toast.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs +214 -0
- package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-alert.mjs +82 -0
- package/fesm2022/frame-kit-ui-ng-ui-alert.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs +76 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-badge.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-badge.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs +68 -0
- package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-button.mjs +108 -0
- package/fesm2022/frame-kit-ui-ng-ui-button.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-callout.mjs +58 -0
- package/fesm2022/frame-kit-ui-ng-ui-callout.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-card.mjs +70 -0
- package/fesm2022/frame-kit-ui-ng-ui-card.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs +113 -0
- package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs +1288 -0
- package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs +456 -0
- package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs +398 -0
- package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs +398 -0
- package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs +125 -0
- package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs +113 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs +111 -0
- package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs +103 -0
- package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs +135 -0
- package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-loader.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-loader.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs +79 -0
- package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs +40 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs +110 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs +91 -0
- package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs +86 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs +443 -0
- package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-note.mjs +56 -0
- package/fesm2022/frame-kit-ui-ng-ui-note.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs +105 -0
- package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs +110 -0
- package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs +129 -0
- package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs +42 -0
- package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs +894 -0
- package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs +81 -0
- package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-toast.mjs +179 -0
- package/fesm2022/frame-kit-ui-ng-ui-toast.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs +143 -0
- package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs +191 -0
- package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs.map +1 -0
- package/fesm2022/frame-kit-ui-ng.mjs +58 -0
- package/fesm2022/frame-kit-ui-ng.mjs.map +1 -0
- package/layouts/app-shell/README.md +357 -0
- package/layouts/content-split/README.md +180 -0
- package/package.json +253 -0
- package/services/overlay-orchestrator/README.md +184 -0
- package/services/spotlight/README.md +61 -0
- package/services/toast/README.md +118 -0
- package/types/frame-kit-ui-ng-core-headline.d.ts +38 -0
- package/types/frame-kit-ui-ng-core-icon.d.ts +74 -0
- package/types/frame-kit-ui-ng-core-image.d.ts +93 -0
- package/types/frame-kit-ui-ng-core-link.d.ts +251 -0
- package/types/frame-kit-ui-ng-core-separator.d.ts +28 -0
- package/types/frame-kit-ui-ng-core-text.d.ts +186 -0
- package/types/frame-kit-ui-ng-directives-infinite-scroll.d.ts +42 -0
- package/types/frame-kit-ui-ng-directives-spotlight.d.ts +51 -0
- package/types/frame-kit-ui-ng-directives-tooltip.d.ts +70 -0
- package/types/frame-kit-ui-ng-docs-endpoint-link.d.ts +43 -0
- package/types/frame-kit-ui-ng-docs-method-badge.d.ts +30 -0
- package/types/frame-kit-ui-ng-forms.d.ts +1674 -0
- package/types/frame-kit-ui-ng-layouts-app-shell.d.ts +75 -0
- package/types/frame-kit-ui-ng-layouts-content-split.d.ts +43 -0
- package/types/frame-kit-ui-ng-services-overlay-orchestrator.d.ts +96 -0
- package/types/frame-kit-ui-ng-services-spotlight.d.ts +32 -0
- package/types/frame-kit-ui-ng-services-toast.d.ts +100 -0
- package/types/frame-kit-ui-ng-ui-accordion.d.ts +86 -0
- package/types/frame-kit-ui-ng-ui-alert.d.ts +34 -0
- package/types/frame-kit-ui-ng-ui-avatar-stack.d.ts +38 -0
- package/types/frame-kit-ui-ng-ui-avatar.d.ts +36 -0
- package/types/frame-kit-ui-ng-ui-badge.d.ts +33 -0
- package/types/frame-kit-ui-ng-ui-breadcrumb.d.ts +45 -0
- package/types/frame-kit-ui-ng-ui-button.d.ts +48 -0
- package/types/frame-kit-ui-ng-ui-callout.d.ts +26 -0
- package/types/frame-kit-ui-ng-ui-card.d.ts +30 -0
- package/types/frame-kit-ui-ng-ui-copyable-field.d.ts +62 -0
- package/types/frame-kit-ui-ng-ui-data-table.d.ts +482 -0
- package/types/frame-kit-ui-ng-ui-dialog.d.ts +166 -0
- package/types/frame-kit-ui-ng-ui-drawer.d.ts +130 -0
- package/types/frame-kit-ui-ng-ui-dropdown-menu.d.ts +77 -0
- package/types/frame-kit-ui-ng-ui-editable-field.d.ts +65 -0
- package/types/frame-kit-ui-ng-ui-icon-badge.d.ts +45 -0
- package/types/frame-kit-ui-ng-ui-icon-list.d.ts +67 -0
- package/types/frame-kit-ui-ng-ui-inline-edit.d.ts +44 -0
- package/types/frame-kit-ui-ng-ui-list-editor.d.ts +56 -0
- package/types/frame-kit-ui-ng-ui-loader.d.ts +32 -0
- package/types/frame-kit-ui-ng-ui-menu-item.d.ts +27 -0
- package/types/frame-kit-ui-ng-ui-nav-brand.d.ts +25 -0
- package/types/frame-kit-ui-ng-ui-nav-group.d.ts +60 -0
- package/types/frame-kit-ui-ng-ui-nav-separator.d.ts +33 -0
- package/types/frame-kit-ui-ng-ui-node-tree-breadcrumb.d.ts +35 -0
- package/types/frame-kit-ui-ng-ui-node-tree.d.ts +135 -0
- package/types/frame-kit-ui-ng-ui-note.d.ts +22 -0
- package/types/frame-kit-ui-ng-ui-numbered-list.d.ts +52 -0
- package/types/frame-kit-ui-ng-ui-pagination.d.ts +49 -0
- package/types/frame-kit-ui-ng-ui-progress-bar.d.ts +50 -0
- package/types/frame-kit-ui-ng-ui-sidenav-link.d.ts +24 -0
- package/types/frame-kit-ui-ng-ui-tabs.d.ts +266 -0
- package/types/frame-kit-ui-ng-ui-timeline.d.ts +42 -0
- package/types/frame-kit-ui-ng-ui-toast.d.ts +56 -0
- package/types/frame-kit-ui-ng-ui-user-menu.d.ts +87 -0
- package/types/frame-kit-ui-ng-ui-wizard-dialog.d.ts +116 -0
- package/types/frame-kit-ui-ng.d.ts +53 -0
- package/ui/accordion/README.md +261 -0
- package/ui/alert/README.md +211 -0
- package/ui/avatar/README.md +167 -0
- package/ui/avatar-stack/README.md +164 -0
- package/ui/badge/README.md +162 -0
- package/ui/breadcrumb/README.md +240 -0
- package/ui/button/README.md +184 -0
- package/ui/callout/README.md +159 -0
- package/ui/card/README.md +174 -0
- package/ui/copyable-field/README.md +235 -0
- package/ui/data-table/README.md +408 -0
- package/ui/dialog/README.md +222 -0
- package/ui/drawer/README.md +274 -0
- package/ui/dropdown-menu/README.md +336 -0
- package/ui/editable-field/README.md +171 -0
- package/ui/icon-badge/README.md +131 -0
- package/ui/icon-list/README.md +205 -0
- package/ui/inline-edit/README.md +135 -0
- package/ui/list-editor/README.md +162 -0
- package/ui/loader/README.md +160 -0
- package/ui/menu-item/README.md +204 -0
- package/ui/nav-brand/README.md +111 -0
- package/ui/nav-group/README.md +145 -0
- package/ui/nav-separator/README.md +44 -0
- package/ui/node-tree/README.md +278 -0
- package/ui/node-tree-breadcrumb/README.md +164 -0
- package/ui/note/README.md +146 -0
- package/ui/numbered-list/README.md +187 -0
- package/ui/pagination/README.md +174 -0
- package/ui/progress-bar/README.md +223 -0
- package/ui/sidenav-link/README.md +214 -0
- package/ui/tabs/README.md +204 -0
- package/ui/timeline/README.md +285 -0
- package/ui/toast/README.md +243 -0
- package/ui/user-menu/README.md +260 -0
- package/ui/wizard-dialog/README.md +283 -0
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, inject, ElementRef, Renderer2, NgZone, Directive, Injectable, signal, computed, ChangeDetectionStrategy, Component, DestroyRef, viewChild, effect, HostBinding } from '@angular/core';
|
|
3
|
+
import { NgTemplateOutlet, NgStyle } from '@angular/common';
|
|
4
|
+
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
5
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
+
|
|
7
|
+
class ColumnResizeDirective {
|
|
8
|
+
columnId = input.required({ ...(ngDevMode ? { debugName: "columnId" } : /* istanbul ignore next */ {}), alias: 'fkColumnResize' });
|
|
9
|
+
enabled = input(true, ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
10
|
+
minWidth = input(50, ...(ngDevMode ? [{ debugName: "minWidth" }] : /* istanbul ignore next */ []));
|
|
11
|
+
maxWidth = input(null, ...(ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []));
|
|
12
|
+
columnResize = output();
|
|
13
|
+
el = inject((ElementRef));
|
|
14
|
+
renderer = inject(Renderer2);
|
|
15
|
+
ngZone = inject(NgZone);
|
|
16
|
+
handle = null;
|
|
17
|
+
startX = 0;
|
|
18
|
+
startWidth = 0;
|
|
19
|
+
dragging = false;
|
|
20
|
+
moveListener = null;
|
|
21
|
+
upListener = null;
|
|
22
|
+
ngOnInit() {
|
|
23
|
+
if (!this.enabled()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.handle = this.renderer.createElement('div');
|
|
27
|
+
this.renderer.addClass(this.handle, 'fk-data-table__resize-handle');
|
|
28
|
+
this.renderer.appendChild(this.el.nativeElement, this.handle);
|
|
29
|
+
this.ngZone.runOutsideAngular(() => {
|
|
30
|
+
this.handle?.addEventListener('mousedown', this.onMouseDown);
|
|
31
|
+
this.handle?.addEventListener('touchstart', this.onTouchStart, {
|
|
32
|
+
passive: false,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
ngOnDestroy() {
|
|
37
|
+
this.cleanupListeners();
|
|
38
|
+
this.handle?.removeEventListener('mousedown', this.onMouseDown);
|
|
39
|
+
this.handle?.removeEventListener('touchstart', this.onTouchStart);
|
|
40
|
+
}
|
|
41
|
+
onMouseDown = (event) => {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
this.startDrag(event.clientX);
|
|
44
|
+
this.moveListener = this.renderer.listen('document', 'mousemove', (e) => this.onDrag(e.clientX));
|
|
45
|
+
this.upListener = this.renderer.listen('document', 'mouseup', () => this.endDrag());
|
|
46
|
+
};
|
|
47
|
+
onTouchStart = (event) => {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
const touch = event.touches[0];
|
|
50
|
+
this.startDrag(touch.clientX);
|
|
51
|
+
this.moveListener = this.renderer.listen('document', 'touchmove', (e) => this.onDrag(e.touches[0].clientX));
|
|
52
|
+
this.upListener = this.renderer.listen('document', 'touchend', () => this.endDrag());
|
|
53
|
+
};
|
|
54
|
+
startDrag(clientX) {
|
|
55
|
+
this.dragging = true;
|
|
56
|
+
this.startX = clientX;
|
|
57
|
+
this.startWidth = this.el.nativeElement.offsetWidth;
|
|
58
|
+
this.renderer.addClass(document.body, 'fk-data-table--resizing');
|
|
59
|
+
}
|
|
60
|
+
onDrag(clientX) {
|
|
61
|
+
if (!this.dragging) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const diff = clientX - this.startX;
|
|
65
|
+
const min = this.minWidth();
|
|
66
|
+
const max = this.maxWidth();
|
|
67
|
+
let newWidth = this.startWidth + diff;
|
|
68
|
+
if (newWidth < min) {
|
|
69
|
+
newWidth = min;
|
|
70
|
+
}
|
|
71
|
+
if (max !== null && newWidth > max) {
|
|
72
|
+
newWidth = max;
|
|
73
|
+
}
|
|
74
|
+
this.renderer.setStyle(this.el.nativeElement, 'width', `${newWidth}px`);
|
|
75
|
+
}
|
|
76
|
+
endDrag() {
|
|
77
|
+
if (!this.dragging) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.dragging = false;
|
|
81
|
+
this.renderer.removeClass(document.body, 'fk-data-table--resizing');
|
|
82
|
+
this.cleanupListeners();
|
|
83
|
+
const finalWidth = this.el.nativeElement.offsetWidth;
|
|
84
|
+
this.ngZone.run(() => {
|
|
85
|
+
this.columnResize.emit({
|
|
86
|
+
columnId: this.columnId(),
|
|
87
|
+
width: finalWidth,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
cleanupListeners() {
|
|
92
|
+
if (this.moveListener) {
|
|
93
|
+
this.moveListener();
|
|
94
|
+
this.moveListener = null;
|
|
95
|
+
}
|
|
96
|
+
if (this.upListener) {
|
|
97
|
+
this.upListener();
|
|
98
|
+
this.upListener = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ColumnResizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
102
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: ColumnResizeDirective, isStandalone: true, selector: "[fkColumnResize]", inputs: { columnId: { classPropertyName: "columnId", publicName: "fkColumnResize", isSignal: true, isRequired: true, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { columnResize: "columnResize" }, ngImport: i0 });
|
|
103
|
+
}
|
|
104
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ColumnResizeDirective, decorators: [{
|
|
105
|
+
type: Directive,
|
|
106
|
+
args: [{
|
|
107
|
+
selector: '[fkColumnResize]',
|
|
108
|
+
standalone: true,
|
|
109
|
+
}]
|
|
110
|
+
}], propDecorators: { columnId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fkColumnResize", required: true }] }], enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "enabled", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }] } });
|
|
111
|
+
|
|
112
|
+
const DEFAULT_TABLE_CONFIG = {
|
|
113
|
+
responsiveMode: 'auto',
|
|
114
|
+
forceScroll: false,
|
|
115
|
+
stickyHeader: true,
|
|
116
|
+
showHeader: true,
|
|
117
|
+
rowExpansion: false,
|
|
118
|
+
emptyMessage: 'No data available',
|
|
119
|
+
trackBy: (index) => index,
|
|
120
|
+
rowStyleFn: null,
|
|
121
|
+
virtualization: 'auto',
|
|
122
|
+
virtualizationThreshold: 200,
|
|
123
|
+
rowHeight: 48,
|
|
124
|
+
overscan: 10,
|
|
125
|
+
breakpoints: {
|
|
126
|
+
sm: '(max-width: 35.999em)',
|
|
127
|
+
md: '(max-width: 47.999em)',
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Coordinator scoped to a single `<fk-data-table-group>`. Mirrors
|
|
133
|
+
* `AccordionGroupCoordinator` — when a wrapped data-table expands a
|
|
134
|
+
* row, the previously-active row is collapsed so only one detail
|
|
135
|
+
* panel is open at a time.
|
|
136
|
+
*
|
|
137
|
+
* The wrapped data-table picks this up via `inject({ optional: true })`
|
|
138
|
+
* and routes every expansion toggle through it. Absence of the
|
|
139
|
+
* coordinator means multi-expand behavior — that's the default.
|
|
140
|
+
*/
|
|
141
|
+
class DataTableGroupCoordinator {
|
|
142
|
+
activeTable = null;
|
|
143
|
+
activeKey = null;
|
|
144
|
+
/**
|
|
145
|
+
* Resolve an expansion request. Caller passes the new expanded
|
|
146
|
+
* state alongside the table + row key; coordinator returns the
|
|
147
|
+
* Set the caller should commit. For multi-table groups this also
|
|
148
|
+
* collapses any active row on a sibling table.
|
|
149
|
+
*/
|
|
150
|
+
resolveToggle(table, key, nextExpanded, currentKeysOnThisTable) {
|
|
151
|
+
if (!nextExpanded) {
|
|
152
|
+
// Collapse: drop the key from this table's set. If it was the
|
|
153
|
+
// active one, clear our pointer.
|
|
154
|
+
const next = new Set(currentKeysOnThisTable);
|
|
155
|
+
next.delete(key);
|
|
156
|
+
if (this.activeTable === table && this.activeKey === key) {
|
|
157
|
+
this.activeTable = null;
|
|
158
|
+
this.activeKey = null;
|
|
159
|
+
}
|
|
160
|
+
return { keysForThisTable: next, siblingTableToClear: null };
|
|
161
|
+
}
|
|
162
|
+
// Expand: replace this table's set with just the new key, and
|
|
163
|
+
// surface any sibling table that needs to drop its active row.
|
|
164
|
+
const next = new Set([key]);
|
|
165
|
+
const sibling = this.activeTable && this.activeTable !== table ? this.activeTable : null;
|
|
166
|
+
this.activeTable = table;
|
|
167
|
+
this.activeKey = key;
|
|
168
|
+
return { keysForThisTable: next, siblingTableToClear: sibling };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Called when a data-table inside the group disconnects. Clears the
|
|
172
|
+
* active pointer if it was tracking this table.
|
|
173
|
+
*/
|
|
174
|
+
unregister(table) {
|
|
175
|
+
if (this.activeTable === table) {
|
|
176
|
+
this.activeTable = null;
|
|
177
|
+
this.activeKey = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableGroupCoordinator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
181
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableGroupCoordinator });
|
|
182
|
+
}
|
|
183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableGroupCoordinator, decorators: [{
|
|
184
|
+
type: Injectable
|
|
185
|
+
}] });
|
|
186
|
+
|
|
187
|
+
class DataTableStackComponent {
|
|
188
|
+
/** Row data array rendered as stacked cards in mobile layout. */
|
|
189
|
+
rows = input([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
190
|
+
/** Column definitions used to extract field labels and cell values. */
|
|
191
|
+
columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
192
|
+
/** Merged table configuration passed down from the host data-table. */
|
|
193
|
+
config = input(null, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
194
|
+
/** TrackBy function used by the `@for` loop over rows. */
|
|
195
|
+
trackByFn = input((index) => index, ...(ngDevMode ? [{ debugName: "trackByFn" }] : /* istanbul ignore next */ []));
|
|
196
|
+
// ===== SELECTION =====
|
|
197
|
+
// Mirrors the host data-table's selection inputs. The stack path renders a
|
|
198
|
+
// checkbox at the top-left of each card when `rowSelection` is true and
|
|
199
|
+
// bubbles toggle events up to the host so it can route them through the
|
|
200
|
+
// shared engine.
|
|
201
|
+
/** Mirrors the host table's `rowSelection` input to show checkboxes on each card. */
|
|
202
|
+
rowSelection = input(false, ...(ngDevMode ? [{ debugName: "rowSelection" }] : /* istanbul ignore next */ []));
|
|
203
|
+
/** Currently selected row keys forwarded from the host table engine. */
|
|
204
|
+
selectedKeys = input(new Set(), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
205
|
+
/** Row identity resolver forwarded from the host table. */
|
|
206
|
+
getRowId = input(null, ...(ngDevMode ? [{ debugName: "getRowId" }] : /* istanbul ignore next */ []));
|
|
207
|
+
/** Predicate (or static boolean) that disables selection for matching rows. */
|
|
208
|
+
selectionDisabled = input(false, ...(ngDevMode ? [{ debugName: "selectionDisabled" }] : /* istanbul ignore next */ []));
|
|
209
|
+
/** Accessible label for each card's selection checkbox. */
|
|
210
|
+
selectionLabel = input('Select row', ...(ngDevMode ? [{ debugName: "selectionLabel" }] : /* istanbul ignore next */ []));
|
|
211
|
+
/** Fires when the user clicks a card row, emitting the row context. */
|
|
212
|
+
rowClick = output();
|
|
213
|
+
/** Fires when the user toggles a card's selection checkbox, bubbling up to the host. */
|
|
214
|
+
toggleRow = output();
|
|
215
|
+
expandedRows = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedRows" }] : /* istanbul ignore next */ []));
|
|
216
|
+
visibleFieldCount = 3;
|
|
217
|
+
primaryColumns = computed(() => {
|
|
218
|
+
return this.columns().slice(0, this.visibleFieldCount);
|
|
219
|
+
}, ...(ngDevMode ? [{ debugName: "primaryColumns" }] : /* istanbul ignore next */ []));
|
|
220
|
+
secondaryColumns = computed(() => {
|
|
221
|
+
return this.columns().slice(this.visibleFieldCount);
|
|
222
|
+
}, ...(ngDevMode ? [{ debugName: "secondaryColumns" }] : /* istanbul ignore next */ []));
|
|
223
|
+
hasSecondary = computed(() => {
|
|
224
|
+
return this.secondaryColumns().length > 0;
|
|
225
|
+
}, ...(ngDevMode ? [{ debugName: "hasSecondary" }] : /* istanbul ignore next */ []));
|
|
226
|
+
getCellValue(column, row) {
|
|
227
|
+
if (typeof column.accessor === 'function') {
|
|
228
|
+
return column.accessor(row);
|
|
229
|
+
}
|
|
230
|
+
return row[column.accessor];
|
|
231
|
+
}
|
|
232
|
+
getLabel(column) {
|
|
233
|
+
return column.mobileLabel ?? column.header;
|
|
234
|
+
}
|
|
235
|
+
isExpanded(index) {
|
|
236
|
+
return this.expandedRows().has(index);
|
|
237
|
+
}
|
|
238
|
+
toggleExpand(index) {
|
|
239
|
+
const next = new Set(this.expandedRows());
|
|
240
|
+
if (next.has(index)) {
|
|
241
|
+
next.delete(index);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
next.add(index);
|
|
245
|
+
}
|
|
246
|
+
this.expandedRows.set(next);
|
|
247
|
+
}
|
|
248
|
+
onRowClick(row, index) {
|
|
249
|
+
this.rowClick.emit({
|
|
250
|
+
row,
|
|
251
|
+
index,
|
|
252
|
+
selected: this.isRowSelected(row),
|
|
253
|
+
expanded: this.isExpanded(index),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
onToggleSelection(row, event) {
|
|
257
|
+
event.stopPropagation();
|
|
258
|
+
if (this.isRowSelectionDisabled(row)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.toggleRow.emit({ row, event });
|
|
262
|
+
}
|
|
263
|
+
isRowSelected(row) {
|
|
264
|
+
if (!this.rowSelection()) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return this.selectedKeys().has(this.getSelectionKey(row));
|
|
268
|
+
}
|
|
269
|
+
isRowSelectionDisabled(row) {
|
|
270
|
+
const disabled = this.selectionDisabled();
|
|
271
|
+
if (typeof disabled === 'function') {
|
|
272
|
+
return disabled(row);
|
|
273
|
+
}
|
|
274
|
+
return disabled;
|
|
275
|
+
}
|
|
276
|
+
getSelectionKey(row) {
|
|
277
|
+
const idFn = this.getRowId();
|
|
278
|
+
if (idFn) {
|
|
279
|
+
return idFn(row);
|
|
280
|
+
}
|
|
281
|
+
return row;
|
|
282
|
+
}
|
|
283
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableStackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
284
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DataTableStackComponent, isStandalone: true, selector: "fk-data-table-stack", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, trackByFn: { classPropertyName: "trackByFn", publicName: "trackByFn", isSignal: true, isRequired: false, transformFunction: null }, rowSelection: { classPropertyName: "rowSelection", publicName: "rowSelection", isSignal: true, isRequired: false, transformFunction: null }, selectedKeys: { classPropertyName: "selectedKeys", publicName: "selectedKeys", isSignal: true, isRequired: false, transformFunction: null }, getRowId: { classPropertyName: "getRowId", publicName: "getRowId", isSignal: true, isRequired: false, transformFunction: null }, selectionDisabled: { classPropertyName: "selectionDisabled", publicName: "selectionDisabled", isSignal: true, isRequired: false, transformFunction: null }, selectionLabel: { classPropertyName: "selectionLabel", publicName: "selectionLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", toggleRow: "toggleRow" }, ngImport: i0, template: "<div class=\"fk-data-table-stack\">\n @for (row of rows(); track trackByFn()($index, row); let i = $index) {\n <div\n class=\"fk-data-table-stack__card\"\n [class.fk-data-table-stack__card--selected]=\"isRowSelected(row)\"\n >\n @if (rowSelection()) {\n <div class=\"fk-data-table-stack__select\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table-stack__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleSelection(row, $event)\"\n />\n </div>\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-data-table-stack__primary\" (click)=\"onRowClick(row, i)\">\n @for (column of primaryColumns(); track column.id) {\n <div class=\"fk-data-table-stack__field\">\n <span class=\"fk-data-table-stack__label\">{{\n getLabel(column)\n }}</span>\n <span class=\"fk-data-table-stack__value\">\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </span>\n </div>\n }\n </div>\n\n @if (hasSecondary()) {\n @if (isExpanded(i)) {\n <div class=\"fk-data-table-stack__secondary\">\n @for (column of secondaryColumns(); track column.id) {\n <div class=\"fk-data-table-stack__field\">\n <span class=\"fk-data-table-stack__label\">{{\n getLabel(column)\n }}</span>\n <span class=\"fk-data-table-stack__value\">\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </span>\n </div>\n }\n </div>\n }\n\n <button\n class=\"fk-data-table-stack__toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"isExpanded(i)\"\n (click)=\"toggleExpand(i)\"\n >\n {{ isExpanded(i) ? 'Show less' : 'Show more' }}\n </button>\n }\n </div>\n }\n\n @if (rows().length === 0) {\n <div class=\"fk-data-table-stack__empty\">\n {{ config()?.emptyMessage ?? 'No data available' }}\n </div>\n }\n</div>\n", styles: [":host{display:block}.fk-data-table-stack{display:flex;flex-direction:column;gap:var(--fk-data-table-stack-gap, var(--fk-rhythm-3, .75rem))}.fk-data-table-stack__card{background:var(--fk-data-table-body-bg, var(--fk-color-surface, #ffffff));border:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-stack-radius, var(--fk-radius-md, .5rem));overflow:hidden;position:relative}.fk-data-table-stack__card--selected{background:var(--fk-data-table-row-selected-bg, var(--fk-color-primary-light, #eaf4ff))}.fk-data-table-stack__select{padding:var(--fk-rhythm-2, .5rem) var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem)) 0}.fk-data-table-stack__checkbox{appearance:none;width:var(--fk-data-table-checkbox-size, 1rem);height:var(--fk-data-table-checkbox-size, 1rem);margin:0;border:1px solid var(--fk-data-table-checkbox-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-checkbox-radius, .25rem);background:var(--fk-data-table-checkbox-bg, var(--fk-color-surface, #ffffff));cursor:pointer;position:relative;vertical-align:middle}.fk-data-table-stack__checkbox:focus-visible{outline:none;box-shadow:var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18))}.fk-data-table-stack__checkbox:checked{background:var(--fk-data-table-checkbox-checked-bg, var(--fk-color-primary, #0a84ff));border-color:var(--fk-data-table-checkbox-checked-border-color, var(--fk-color-primary, #0a84ff))}.fk-data-table-stack__checkbox:checked:after{content:\"\";position:absolute;inset:0;background:var(--fk-data-table-checkbox-icon-color, var(--fk-white, #ffffff));mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat;-webkit-mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat}.fk-data-table-stack__checkbox:disabled{cursor:not-allowed;opacity:.5}.fk-data-table-stack__primary{padding:var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem));cursor:pointer}.fk-data-table-stack__primary:hover{background:var(--fk-data-table-row-hover-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table-stack__secondary{padding:0 var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem)) var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem))}.fk-data-table-stack__field{display:flex;justify-content:space-between;align-items:baseline;padding:var(--fk-rhythm-1, .25rem) 0}.fk-data-table-stack__field:not(:last-child){border-bottom:1px solid var(--fk-data-table-stack-field-border, var(--fk-color-border-light, rgba(0, 0, 0, .06)))}.fk-data-table-stack__label{font-size:var(--fk-data-table-head-font-size, var(--fk-typography-small-font-size, .875rem));font-weight:var(--fk-data-table-head-font-weight, var(--fk-font-weight-semibold, 600));color:var(--fk-data-table-head-color, var(--fk-color-text-muted, #6b7a8d));flex-shrink:0;margin-inline-end:var(--fk-rhythm-3, .75rem)}.fk-data-table-stack__value{font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem));color:var(--fk-data-table-body-color, var(--fk-color-text, #1f2d3d));text-align:end;word-break:break-word}.fk-data-table-stack__toggle{display:block;width:100%;padding:var(--fk-rhythm-2, .5rem);border:none;border-top:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));background:var(--fk-data-table-head-bg, var(--fk-color-surface-muted, #f7f9fb));color:var(--fk-data-table-stack-toggle-color, var(--fk-color-primary, #3b82f6));font-size:var(--fk-typography-small-font-size, .875rem);font-weight:var(--fk-font-weight-semibold, 600);cursor:pointer;text-align:center}.fk-data-table-stack__toggle:hover{background:var(--fk-data-table-row-hover-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table-stack__toggle:focus-visible{outline:2px solid var(--fk-color-primary, #3b82f6);outline-offset:-2px}.fk-data-table-stack__empty{padding:var(--fk-data-table-empty-padding, var(--fk-rhythm-8, 2rem));text-align:center;color:var(--fk-data-table-empty-color, var(--fk-color-text-muted, #6b7a8d));font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem))}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
285
|
+
}
|
|
286
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableStackComponent, decorators: [{
|
|
287
|
+
type: Component,
|
|
288
|
+
args: [{ selector: 'fk-data-table-stack', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fk-data-table-stack\">\n @for (row of rows(); track trackByFn()($index, row); let i = $index) {\n <div\n class=\"fk-data-table-stack__card\"\n [class.fk-data-table-stack__card--selected]=\"isRowSelected(row)\"\n >\n @if (rowSelection()) {\n <div class=\"fk-data-table-stack__select\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table-stack__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleSelection(row, $event)\"\n />\n </div>\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div class=\"fk-data-table-stack__primary\" (click)=\"onRowClick(row, i)\">\n @for (column of primaryColumns(); track column.id) {\n <div class=\"fk-data-table-stack__field\">\n <span class=\"fk-data-table-stack__label\">{{\n getLabel(column)\n }}</span>\n <span class=\"fk-data-table-stack__value\">\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </span>\n </div>\n }\n </div>\n\n @if (hasSecondary()) {\n @if (isExpanded(i)) {\n <div class=\"fk-data-table-stack__secondary\">\n @for (column of secondaryColumns(); track column.id) {\n <div class=\"fk-data-table-stack__field\">\n <span class=\"fk-data-table-stack__label\">{{\n getLabel(column)\n }}</span>\n <span class=\"fk-data-table-stack__value\">\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </span>\n </div>\n }\n </div>\n }\n\n <button\n class=\"fk-data-table-stack__toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"isExpanded(i)\"\n (click)=\"toggleExpand(i)\"\n >\n {{ isExpanded(i) ? 'Show less' : 'Show more' }}\n </button>\n }\n </div>\n }\n\n @if (rows().length === 0) {\n <div class=\"fk-data-table-stack__empty\">\n {{ config()?.emptyMessage ?? 'No data available' }}\n </div>\n }\n</div>\n", styles: [":host{display:block}.fk-data-table-stack{display:flex;flex-direction:column;gap:var(--fk-data-table-stack-gap, var(--fk-rhythm-3, .75rem))}.fk-data-table-stack__card{background:var(--fk-data-table-body-bg, var(--fk-color-surface, #ffffff));border:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-stack-radius, var(--fk-radius-md, .5rem));overflow:hidden;position:relative}.fk-data-table-stack__card--selected{background:var(--fk-data-table-row-selected-bg, var(--fk-color-primary-light, #eaf4ff))}.fk-data-table-stack__select{padding:var(--fk-rhythm-2, .5rem) var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem)) 0}.fk-data-table-stack__checkbox{appearance:none;width:var(--fk-data-table-checkbox-size, 1rem);height:var(--fk-data-table-checkbox-size, 1rem);margin:0;border:1px solid var(--fk-data-table-checkbox-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-checkbox-radius, .25rem);background:var(--fk-data-table-checkbox-bg, var(--fk-color-surface, #ffffff));cursor:pointer;position:relative;vertical-align:middle}.fk-data-table-stack__checkbox:focus-visible{outline:none;box-shadow:var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18))}.fk-data-table-stack__checkbox:checked{background:var(--fk-data-table-checkbox-checked-bg, var(--fk-color-primary, #0a84ff));border-color:var(--fk-data-table-checkbox-checked-border-color, var(--fk-color-primary, #0a84ff))}.fk-data-table-stack__checkbox:checked:after{content:\"\";position:absolute;inset:0;background:var(--fk-data-table-checkbox-icon-color, var(--fk-white, #ffffff));mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat;-webkit-mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat}.fk-data-table-stack__checkbox:disabled{cursor:not-allowed;opacity:.5}.fk-data-table-stack__primary{padding:var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem));cursor:pointer}.fk-data-table-stack__primary:hover{background:var(--fk-data-table-row-hover-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table-stack__secondary{padding:0 var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem)) var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem))}.fk-data-table-stack__field{display:flex;justify-content:space-between;align-items:baseline;padding:var(--fk-rhythm-1, .25rem) 0}.fk-data-table-stack__field:not(:last-child){border-bottom:1px solid var(--fk-data-table-stack-field-border, var(--fk-color-border-light, rgba(0, 0, 0, .06)))}.fk-data-table-stack__label{font-size:var(--fk-data-table-head-font-size, var(--fk-typography-small-font-size, .875rem));font-weight:var(--fk-data-table-head-font-weight, var(--fk-font-weight-semibold, 600));color:var(--fk-data-table-head-color, var(--fk-color-text-muted, #6b7a8d));flex-shrink:0;margin-inline-end:var(--fk-rhythm-3, .75rem)}.fk-data-table-stack__value{font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem));color:var(--fk-data-table-body-color, var(--fk-color-text, #1f2d3d));text-align:end;word-break:break-word}.fk-data-table-stack__toggle{display:block;width:100%;padding:var(--fk-rhythm-2, .5rem);border:none;border-top:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));background:var(--fk-data-table-head-bg, var(--fk-color-surface-muted, #f7f9fb));color:var(--fk-data-table-stack-toggle-color, var(--fk-color-primary, #3b82f6));font-size:var(--fk-typography-small-font-size, .875rem);font-weight:var(--fk-font-weight-semibold, 600);cursor:pointer;text-align:center}.fk-data-table-stack__toggle:hover{background:var(--fk-data-table-row-hover-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table-stack__toggle:focus-visible{outline:2px solid var(--fk-color-primary, #3b82f6);outline-offset:-2px}.fk-data-table-stack__empty{padding:var(--fk-data-table-empty-padding, var(--fk-rhythm-8, 2rem));text-align:center;color:var(--fk-data-table-empty-color, var(--fk-color-text-muted, #6b7a8d));font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem))}\n"] }]
|
|
289
|
+
}], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], trackByFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByFn", required: false }] }], rowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowSelection", required: false }] }], selectedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedKeys", required: false }] }], getRowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "getRowId", required: false }] }], selectionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionDisabled", required: false }] }], selectionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionLabel", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], toggleRow: [{ type: i0.Output, args: ["toggleRow"] }] } });
|
|
290
|
+
|
|
291
|
+
class ResponsiveStrategyService {
|
|
292
|
+
// ===== INJECTIONS =====
|
|
293
|
+
breakpointObserver = inject(BreakpointObserver);
|
|
294
|
+
destroyRef = inject(DestroyRef);
|
|
295
|
+
// ===== SOURCE DATA =====
|
|
296
|
+
columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
297
|
+
config = signal(null, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
298
|
+
// ===== STATE =====
|
|
299
|
+
currentBreakpoint = signal('lg', ...(ngDevMode ? [{ debugName: "currentBreakpoint" }] : /* istanbul ignore next */ []));
|
|
300
|
+
// ===== COMPUTED =====
|
|
301
|
+
activeMode = computed(() => {
|
|
302
|
+
const cfg = this.config();
|
|
303
|
+
if (!cfg) {
|
|
304
|
+
return 'table';
|
|
305
|
+
}
|
|
306
|
+
if (cfg.forceScroll) {
|
|
307
|
+
return 'table';
|
|
308
|
+
}
|
|
309
|
+
const mode = cfg.responsiveMode;
|
|
310
|
+
const bp = this.currentBreakpoint();
|
|
311
|
+
if (mode === 'scroll') {
|
|
312
|
+
return 'table';
|
|
313
|
+
}
|
|
314
|
+
if (mode === 'stack') {
|
|
315
|
+
return bp === 'sm' ? 'stack' : 'table';
|
|
316
|
+
}
|
|
317
|
+
if (mode === 'priority') {
|
|
318
|
+
return 'table';
|
|
319
|
+
}
|
|
320
|
+
// mode === "auto"
|
|
321
|
+
if (bp === 'sm') {
|
|
322
|
+
return 'stack';
|
|
323
|
+
}
|
|
324
|
+
return 'table';
|
|
325
|
+
}, ...(ngDevMode ? [{ debugName: "activeMode" }] : /* istanbul ignore next */ []));
|
|
326
|
+
responsiveColumns = computed(() => {
|
|
327
|
+
const cols = this.columns();
|
|
328
|
+
const cfg = this.config();
|
|
329
|
+
if (!cfg || cfg.forceScroll) {
|
|
330
|
+
return cols;
|
|
331
|
+
}
|
|
332
|
+
const bp = this.currentBreakpoint();
|
|
333
|
+
const mode = cfg.responsiveMode;
|
|
334
|
+
if (bp === 'lg') {
|
|
335
|
+
return cols;
|
|
336
|
+
}
|
|
337
|
+
if (mode === 'scroll') {
|
|
338
|
+
return cols;
|
|
339
|
+
}
|
|
340
|
+
// For "auto", "priority", or "stack" at md breakpoint — apply priority hiding
|
|
341
|
+
if (bp === 'md' && (mode === 'auto' || mode === 'priority')) {
|
|
342
|
+
return cols.filter((col) => {
|
|
343
|
+
if (col.hideBelow === 'md' || col.hideBelow === 'lg') {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
if (col.priority !== undefined && col.priority >= 2) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return cols;
|
|
353
|
+
}, ...(ngDevMode ? [{ debugName: "responsiveColumns" }] : /* istanbul ignore next */ []));
|
|
354
|
+
hiddenColumns = computed(() => {
|
|
355
|
+
const all = this.columns();
|
|
356
|
+
const visible = this.responsiveColumns();
|
|
357
|
+
const visibleIds = new Set(visible.map((c) => c.id));
|
|
358
|
+
return all.filter((c) => !visibleIds.has(c.id));
|
|
359
|
+
}, ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : /* istanbul ignore next */ []));
|
|
360
|
+
// ===== INITIALIZATION =====
|
|
361
|
+
initialized = false;
|
|
362
|
+
init(breakpoints) {
|
|
363
|
+
if (this.initialized) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.initialized = true;
|
|
367
|
+
this.breakpointObserver
|
|
368
|
+
.observe([breakpoints.sm, breakpoints.md])
|
|
369
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
370
|
+
.subscribe((result) => {
|
|
371
|
+
if (result.breakpoints[breakpoints.sm]) {
|
|
372
|
+
this.currentBreakpoint.set('sm');
|
|
373
|
+
}
|
|
374
|
+
else if (result.breakpoints[breakpoints.md]) {
|
|
375
|
+
this.currentBreakpoint.set('md');
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
this.currentBreakpoint.set('lg');
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResponsiveStrategyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
383
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResponsiveStrategyService });
|
|
384
|
+
}
|
|
385
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResponsiveStrategyService, decorators: [{
|
|
386
|
+
type: Injectable
|
|
387
|
+
}] });
|
|
388
|
+
|
|
389
|
+
class TableEngineService {
|
|
390
|
+
// ===== SOURCE DATA =====
|
|
391
|
+
rows = signal([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
392
|
+
columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
393
|
+
// ===== INTERNAL STATE =====
|
|
394
|
+
internalSortState = signal(null, ...(ngDevMode ? [{ debugName: "internalSortState" }] : /* istanbul ignore next */ []));
|
|
395
|
+
internalFilterState = signal({
|
|
396
|
+
query: '',
|
|
397
|
+
columnFilters: {},
|
|
398
|
+
}, ...(ngDevMode ? [{ debugName: "internalFilterState" }] : /* istanbul ignore next */ []));
|
|
399
|
+
internalPageState = signal(null, ...(ngDevMode ? [{ debugName: "internalPageState" }] : /* istanbul ignore next */ []));
|
|
400
|
+
// ===== CONTROLLED STATE =====
|
|
401
|
+
controlledSortState = signal(undefined, ...(ngDevMode ? [{ debugName: "controlledSortState" }] : /* istanbul ignore next */ []));
|
|
402
|
+
controlledFilterState = signal(undefined, ...(ngDevMode ? [{ debugName: "controlledFilterState" }] : /* istanbul ignore next */ []));
|
|
403
|
+
controlledPageState = signal(undefined, ...(ngDevMode ? [{ debugName: "controlledPageState" }] : /* istanbul ignore next */ []));
|
|
404
|
+
// ===== EFFECTIVE STATE =====
|
|
405
|
+
sortState = computed(() => {
|
|
406
|
+
const controlled = this.controlledSortState();
|
|
407
|
+
if (controlled !== undefined) {
|
|
408
|
+
return controlled;
|
|
409
|
+
}
|
|
410
|
+
return this.internalSortState();
|
|
411
|
+
}, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
|
|
412
|
+
filterState = computed(() => {
|
|
413
|
+
const controlled = this.controlledFilterState();
|
|
414
|
+
if (controlled !== undefined && controlled !== null) {
|
|
415
|
+
return controlled;
|
|
416
|
+
}
|
|
417
|
+
return this.internalFilterState();
|
|
418
|
+
}, ...(ngDevMode ? [{ debugName: "filterState" }] : /* istanbul ignore next */ []));
|
|
419
|
+
pageState = computed(() => {
|
|
420
|
+
const controlled = this.controlledPageState();
|
|
421
|
+
if (controlled !== undefined) {
|
|
422
|
+
return controlled;
|
|
423
|
+
}
|
|
424
|
+
return this.internalPageState();
|
|
425
|
+
}, ...(ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []));
|
|
426
|
+
// ===== SELECTION =====
|
|
427
|
+
// Engine-owned (uncontrolled) selection state. The host data-table reads
|
|
428
|
+
// from a controlled `selectedKeys` input when present and only writes
|
|
429
|
+
// through `setSelectedKeys` here when running in uncontrolled mode.
|
|
430
|
+
// Action methods stay pure — they compute the next Set from a current
|
|
431
|
+
// Set and return it; they never mutate the input Set.
|
|
432
|
+
selectedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
433
|
+
// ===== EXPANSION =====
|
|
434
|
+
// Mirror of selection: engine-owned Set of expanded row keys when the
|
|
435
|
+
// host runs in uncontrolled mode. The host swaps to a controlled
|
|
436
|
+
// `expandedKeys` input when supplied, the same way selection does.
|
|
437
|
+
expandedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
|
|
438
|
+
// ===== PROCESSED ROWS =====
|
|
439
|
+
sortedRows = computed(() => {
|
|
440
|
+
const rows = this.rows();
|
|
441
|
+
const sort = this.sortState();
|
|
442
|
+
if (!sort || !sort.direction) {
|
|
443
|
+
return rows;
|
|
444
|
+
}
|
|
445
|
+
const column = this.columns().find((c) => c.id === sort.columnId);
|
|
446
|
+
if (!column) {
|
|
447
|
+
return rows;
|
|
448
|
+
}
|
|
449
|
+
const direction = sort.direction;
|
|
450
|
+
return [...rows].sort((a, b) => {
|
|
451
|
+
const valA = column.sortAccessor
|
|
452
|
+
? column.sortAccessor(a)
|
|
453
|
+
: this.resolveAccessor(column.accessor, a);
|
|
454
|
+
const valB = column.sortAccessor
|
|
455
|
+
? column.sortAccessor(b)
|
|
456
|
+
: this.resolveAccessor(column.accessor, b);
|
|
457
|
+
return this.compare(valA, valB, direction);
|
|
458
|
+
});
|
|
459
|
+
}, ...(ngDevMode ? [{ debugName: "sortedRows" }] : /* istanbul ignore next */ []));
|
|
460
|
+
filteredRows = computed(() => {
|
|
461
|
+
const rows = this.sortedRows();
|
|
462
|
+
const filter = this.filterState();
|
|
463
|
+
if (!filter.query && Object.keys(filter.columnFilters).length === 0) {
|
|
464
|
+
return rows;
|
|
465
|
+
}
|
|
466
|
+
const columns = this.columns();
|
|
467
|
+
const query = filter.query.toLowerCase();
|
|
468
|
+
return rows.filter((row) => {
|
|
469
|
+
if (query) {
|
|
470
|
+
const matches = columns.some((col) => {
|
|
471
|
+
const val = this.resolveAccessor(col.accessor, row);
|
|
472
|
+
return String(val ?? '')
|
|
473
|
+
.toLowerCase()
|
|
474
|
+
.includes(query);
|
|
475
|
+
});
|
|
476
|
+
if (!matches) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
|
+
});
|
|
482
|
+
}, ...(ngDevMode ? [{ debugName: "filteredRows" }] : /* istanbul ignore next */ []));
|
|
483
|
+
processedRows = computed(() => {
|
|
484
|
+
const rows = this.filteredRows();
|
|
485
|
+
const page = this.pageState();
|
|
486
|
+
if (!page) {
|
|
487
|
+
return rows;
|
|
488
|
+
}
|
|
489
|
+
const start = page.page * page.pageSize;
|
|
490
|
+
return rows.slice(start, start + page.pageSize);
|
|
491
|
+
}, ...(ngDevMode ? [{ debugName: "processedRows" }] : /* istanbul ignore next */ []));
|
|
492
|
+
totalFilteredRows = computed(() => {
|
|
493
|
+
return this.filteredRows().length;
|
|
494
|
+
}, ...(ngDevMode ? [{ debugName: "totalFilteredRows" }] : /* istanbul ignore next */ []));
|
|
495
|
+
// ===== ACTIONS =====
|
|
496
|
+
toggleSort(columnId) {
|
|
497
|
+
const current = this.sortState();
|
|
498
|
+
let direction;
|
|
499
|
+
if (current?.columnId === columnId) {
|
|
500
|
+
if (current.direction === 'asc') {
|
|
501
|
+
direction = 'desc';
|
|
502
|
+
}
|
|
503
|
+
else if (current.direction === 'desc') {
|
|
504
|
+
direction = null;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
direction = 'asc';
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
direction = 'asc';
|
|
512
|
+
}
|
|
513
|
+
const next = { columnId, direction };
|
|
514
|
+
if (this.controlledSortState() === undefined) {
|
|
515
|
+
this.internalSortState.set(next);
|
|
516
|
+
}
|
|
517
|
+
return next;
|
|
518
|
+
}
|
|
519
|
+
setFilter(query) {
|
|
520
|
+
const next = {
|
|
521
|
+
...this.filterState(),
|
|
522
|
+
query,
|
|
523
|
+
};
|
|
524
|
+
if (this.controlledFilterState() === undefined) {
|
|
525
|
+
this.internalFilterState.set(next);
|
|
526
|
+
}
|
|
527
|
+
return next;
|
|
528
|
+
}
|
|
529
|
+
setPage(page, pageSize) {
|
|
530
|
+
const current = this.pageState();
|
|
531
|
+
const next = {
|
|
532
|
+
page,
|
|
533
|
+
pageSize: pageSize ?? current?.pageSize ?? 10,
|
|
534
|
+
totalRows: this.totalFilteredRows(),
|
|
535
|
+
};
|
|
536
|
+
if (this.controlledPageState() === undefined) {
|
|
537
|
+
this.internalPageState.set(next);
|
|
538
|
+
}
|
|
539
|
+
return next;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Replace the engine's internal selection state. Used by the host
|
|
543
|
+
* data-table when running in uncontrolled mode (controlled mode goes
|
|
544
|
+
* through the parent and never touches the engine here).
|
|
545
|
+
*/
|
|
546
|
+
setSelectedKeys(next) {
|
|
547
|
+
this.selectedKeys.set(next);
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Replace the engine's internal expansion state. Used by the host
|
|
551
|
+
* data-table when running in uncontrolled mode.
|
|
552
|
+
*/
|
|
553
|
+
setExpandedKeys(next) {
|
|
554
|
+
this.expandedKeys.set(next);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Toggle a single key in a Set. Derives from `current` and returns a
|
|
558
|
+
* brand-new Set — never mutates the input.
|
|
559
|
+
*/
|
|
560
|
+
toggleSelection(key, current = this.selectedKeys()) {
|
|
561
|
+
const next = new Set(current);
|
|
562
|
+
if (next.has(key)) {
|
|
563
|
+
next.delete(key);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
next.add(key);
|
|
567
|
+
}
|
|
568
|
+
return next;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Page-local select-all. Toggles every key in `visibleKeys`:
|
|
572
|
+
* - if all visible keys are already selected → deselect them
|
|
573
|
+
* - otherwise → select them all (existing selections outside the visible
|
|
574
|
+
* range stay intact)
|
|
575
|
+
*
|
|
576
|
+
* `disabledKeys` is excluded from both the toggle target and the
|
|
577
|
+
* "all-selected" check, so a row marked `selectionDisabled` never
|
|
578
|
+
* blocks the header checkbox from going fully checked.
|
|
579
|
+
*/
|
|
580
|
+
toggleVisibleRows(visibleKeys, disabledKeys = new Set(), current = this.selectedKeys()) {
|
|
581
|
+
const eligible = [];
|
|
582
|
+
for (const key of visibleKeys) {
|
|
583
|
+
if (!disabledKeys.has(key)) {
|
|
584
|
+
eligible.push(key);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const allSelected = eligible.length > 0 && eligible.every((key) => current.has(key));
|
|
588
|
+
const next = new Set(current);
|
|
589
|
+
if (allSelected) {
|
|
590
|
+
for (const key of eligible) {
|
|
591
|
+
next.delete(key);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
for (const key of eligible) {
|
|
596
|
+
next.add(key);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return next;
|
|
600
|
+
}
|
|
601
|
+
clearSelection() {
|
|
602
|
+
return new Set();
|
|
603
|
+
}
|
|
604
|
+
isSelected(key) {
|
|
605
|
+
return this.selectedKeys().has(key);
|
|
606
|
+
}
|
|
607
|
+
// ===== HELPERS =====
|
|
608
|
+
resolveAccessor(accessor, row) {
|
|
609
|
+
if (typeof accessor === 'function') {
|
|
610
|
+
return accessor(row);
|
|
611
|
+
}
|
|
612
|
+
return row[accessor];
|
|
613
|
+
}
|
|
614
|
+
compare(a, b, direction) {
|
|
615
|
+
const multiplier = direction === 'asc' ? 1 : -1;
|
|
616
|
+
if (a == null && b == null) {
|
|
617
|
+
return 0;
|
|
618
|
+
}
|
|
619
|
+
if (a == null) {
|
|
620
|
+
return 1 * multiplier;
|
|
621
|
+
}
|
|
622
|
+
if (b == null) {
|
|
623
|
+
return -1 * multiplier;
|
|
624
|
+
}
|
|
625
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
626
|
+
return a.localeCompare(b) * multiplier;
|
|
627
|
+
}
|
|
628
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
629
|
+
return (a - b) * multiplier;
|
|
630
|
+
}
|
|
631
|
+
return String(a).localeCompare(String(b)) * multiplier;
|
|
632
|
+
}
|
|
633
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TableEngineService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
634
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TableEngineService });
|
|
635
|
+
}
|
|
636
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TableEngineService, decorators: [{
|
|
637
|
+
type: Injectable
|
|
638
|
+
}] });
|
|
639
|
+
|
|
640
|
+
class VirtualizationEngineService {
|
|
641
|
+
// ===== CONFIGURATION =====
|
|
642
|
+
rowHeight = signal(48, ...(ngDevMode ? [{ debugName: "rowHeight" }] : /* istanbul ignore next */ []));
|
|
643
|
+
overscan = signal(10, ...(ngDevMode ? [{ debugName: "overscan" }] : /* istanbul ignore next */ []));
|
|
644
|
+
totalRows = signal(0, ...(ngDevMode ? [{ debugName: "totalRows" }] : /* istanbul ignore next */ []));
|
|
645
|
+
// ===== VIEWPORT STATE =====
|
|
646
|
+
viewportHeight = signal(0, ...(ngDevMode ? [{ debugName: "viewportHeight" }] : /* istanbul ignore next */ []));
|
|
647
|
+
scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
|
|
648
|
+
// ===== COMPUTED =====
|
|
649
|
+
visibleRange = computed(() => {
|
|
650
|
+
const height = this.viewportHeight();
|
|
651
|
+
const scroll = this.scrollTop();
|
|
652
|
+
const rh = this.rowHeight();
|
|
653
|
+
const os = this.overscan();
|
|
654
|
+
const total = this.totalRows();
|
|
655
|
+
if (rh <= 0 || total === 0) {
|
|
656
|
+
return { start: 0, end: 0 };
|
|
657
|
+
}
|
|
658
|
+
const visibleCount = Math.ceil(height / rh);
|
|
659
|
+
const firstVisible = Math.floor(scroll / rh);
|
|
660
|
+
const start = Math.max(0, firstVisible - os);
|
|
661
|
+
const end = Math.min(total, firstVisible + visibleCount + os);
|
|
662
|
+
return { start, end };
|
|
663
|
+
}, ...(ngDevMode ? [{ debugName: "visibleRange" }] : /* istanbul ignore next */ []));
|
|
664
|
+
totalHeight = computed(() => {
|
|
665
|
+
return this.totalRows() * this.rowHeight();
|
|
666
|
+
}, ...(ngDevMode ? [{ debugName: "totalHeight" }] : /* istanbul ignore next */ []));
|
|
667
|
+
offsetY = computed(() => {
|
|
668
|
+
return this.visibleRange().start * this.rowHeight();
|
|
669
|
+
}, ...(ngDevMode ? [{ debugName: "offsetY" }] : /* istanbul ignore next */ []));
|
|
670
|
+
visibleCount = computed(() => {
|
|
671
|
+
const range = this.visibleRange();
|
|
672
|
+
return range.end - range.start;
|
|
673
|
+
}, ...(ngDevMode ? [{ debugName: "visibleCount" }] : /* istanbul ignore next */ []));
|
|
674
|
+
// ===== ACTIONS =====
|
|
675
|
+
onScroll(scrollTop) {
|
|
676
|
+
this.scrollTop.set(scrollTop);
|
|
677
|
+
}
|
|
678
|
+
onViewportResize(height) {
|
|
679
|
+
this.viewportHeight.set(height);
|
|
680
|
+
}
|
|
681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VirtualizationEngineService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
682
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VirtualizationEngineService });
|
|
683
|
+
}
|
|
684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VirtualizationEngineService, decorators: [{
|
|
685
|
+
type: Injectable
|
|
686
|
+
}] });
|
|
687
|
+
|
|
688
|
+
class DataTableComponent {
|
|
689
|
+
// ===== BASE PROPS =====
|
|
690
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
691
|
+
id = input(null, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
692
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
693
|
+
// ===== DATA =====
|
|
694
|
+
/** Column definitions that describe the table schema, headers, and cell rendering. */
|
|
695
|
+
columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
696
|
+
/** Array of row data objects rendered by the table. */
|
|
697
|
+
rows = input([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
698
|
+
/** Partial table configuration merged with the defaults. */
|
|
699
|
+
config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
700
|
+
/** When true, shows the loading overlay and suppresses the empty state. */
|
|
701
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
702
|
+
// ===== CONTROLLED STATE =====
|
|
703
|
+
/** Controlled sort state; pass null to let the table manage sorting internally. */
|
|
704
|
+
sortState = input(null, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
|
|
705
|
+
/** Controlled filter state; pass null to let the table manage filtering internally. */
|
|
706
|
+
filterState = input(null, ...(ngDevMode ? [{ debugName: "filterState" }] : /* istanbul ignore next */ []));
|
|
707
|
+
/** Controlled page state; pass null to let the table manage pagination internally. */
|
|
708
|
+
pageState = input(null, ...(ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []));
|
|
709
|
+
// ===== SELECTION =====
|
|
710
|
+
// Opt-in via `rowSelection`. When the parent passes a `selectedKeys` Set the
|
|
711
|
+
// table runs in controlled mode (parent owns state, table emits a new Set on
|
|
712
|
+
// every change). When `selectedKeys` is null the table manages selection
|
|
713
|
+
// internally — useful for Storybook and simple cases.
|
|
714
|
+
//
|
|
715
|
+
// `getRowId` is REQUIRED when `rowSelection` is true. Without a stable
|
|
716
|
+
// row identity selection silently breaks across pagination. The constructor
|
|
717
|
+
// logs a console error if it's missing in dev.
|
|
718
|
+
/** Opt-in to row selection; requires `getRowId` for controlled mode across pagination. */
|
|
719
|
+
rowSelection = input(false, ...(ngDevMode ? [{ debugName: "rowSelection" }] : /* istanbul ignore next */ []));
|
|
720
|
+
/** Controlled set of selected row keys; null lets the table manage selection internally. */
|
|
721
|
+
selectedKeys = input(null, ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
722
|
+
/** Function that returns a stable unique key for each row, required when `rowSelection` is true. */
|
|
723
|
+
getRowId = input(null, ...(ngDevMode ? [{ debugName: "getRowId" }] : /* istanbul ignore next */ []));
|
|
724
|
+
/** Predicate (or static boolean) that disables selection for matching rows. */
|
|
725
|
+
selectionDisabled = input(false, ...(ngDevMode ? [{ debugName: "selectionDisabled" }] : /* istanbul ignore next */ []));
|
|
726
|
+
/** Whether the checkbox column appears at the start or end of the table. */
|
|
727
|
+
selectionColumnPosition = input('start', ...(ngDevMode ? [{ debugName: "selectionColumnPosition" }] : /* istanbul ignore next */ []));
|
|
728
|
+
/** Accessible label for each row's selection checkbox. */
|
|
729
|
+
selectionLabel = input('Select row', ...(ngDevMode ? [{ debugName: "selectionLabel" }] : /* istanbul ignore next */ []));
|
|
730
|
+
// ===== EXPANSION =====
|
|
731
|
+
// Opt-in via `rowDetailTemplate`. When present the table auto-injects
|
|
732
|
+
// a chevron column at start (default) or end and renders the projected
|
|
733
|
+
// template as a detail row beneath each expanded row.
|
|
734
|
+
//
|
|
735
|
+
// `expandedKeys` mirrors the selection contract: pass a Set to run in
|
|
736
|
+
// controlled mode (parent owns state, table emits the new Set on every
|
|
737
|
+
// toggle) or omit to let the engine manage state internally.
|
|
738
|
+
//
|
|
739
|
+
// `expandableRow` lets a host predicate-disable expansion per row —
|
|
740
|
+
// the chevron renders as an inert spacer so columns stay aligned.
|
|
741
|
+
/** Template rendered as the expansion detail row; presence enables the feature. */
|
|
742
|
+
rowDetailTemplate = input(null, ...(ngDevMode ? [{ debugName: "rowDetailTemplate" }] : /* istanbul ignore next */ []));
|
|
743
|
+
/** Whether the chevron column appears at the start or end of the table. */
|
|
744
|
+
expansionColumnPosition = input('start', ...(ngDevMode ? [{ debugName: "expansionColumnPosition" }] : /* istanbul ignore next */ []));
|
|
745
|
+
/** Controlled set of expanded row keys; null lets the engine manage state internally. */
|
|
746
|
+
expandedKeys = input(null, ...(ngDevMode ? [{ debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
|
|
747
|
+
/** Predicate that disables expansion for matching rows; default: every row is expandable. */
|
|
748
|
+
expandableRow = input(null, ...(ngDevMode ? [{ debugName: "expandableRow" }] : /* istanbul ignore next */ []));
|
|
749
|
+
/** Accessible label for each row's expansion chevron. */
|
|
750
|
+
expansionLabel = input('Toggle row details', ...(ngDevMode ? [{ debugName: "expansionLabel" }] : /* istanbul ignore next */ []));
|
|
751
|
+
// ===== OUTPUTS =====
|
|
752
|
+
/** Fires when the user clicks a sortable column header, emitting the new sort state. */
|
|
753
|
+
sortChange = output();
|
|
754
|
+
/** Fires when a filter changes, emitting the updated filter state. */
|
|
755
|
+
filterChange = output();
|
|
756
|
+
/** Fires when the user navigates to a different page or changes page size. */
|
|
757
|
+
pageChange = output();
|
|
758
|
+
/** Fires when the user clicks a table row, emitting the row context. */
|
|
759
|
+
rowClick = output();
|
|
760
|
+
/** Fires when the selection set changes, emitting the full new set of selected keys. */
|
|
761
|
+
selectionChange = output();
|
|
762
|
+
/** Fires when the expansion set changes, emitting the full new set of expanded keys. */
|
|
763
|
+
expansionChange = output();
|
|
764
|
+
/** Fires when the user finishes resizing a column, emitting the column id and new width. */
|
|
765
|
+
columnResize = output();
|
|
766
|
+
// ===== ENGINE =====
|
|
767
|
+
engine;
|
|
768
|
+
virtEngine;
|
|
769
|
+
responsive = inject((ResponsiveStrategyService));
|
|
770
|
+
// ===== VIEW CHILDREN =====
|
|
771
|
+
virtualViewport = viewChild('virtualViewport', ...(ngDevMode ? [{ debugName: "virtualViewport" }] : /* istanbul ignore next */ []));
|
|
772
|
+
// ===== COMPUTED =====
|
|
773
|
+
mergedConfig = computed(() => {
|
|
774
|
+
return { ...DEFAULT_TABLE_CONFIG, ...this.config() };
|
|
775
|
+
}, ...(ngDevMode ? [{ debugName: "mergedConfig" }] : /* istanbul ignore next */ []));
|
|
776
|
+
visibleColumns = computed(() => {
|
|
777
|
+
const responsiveCols = this.responsive.responsiveColumns();
|
|
778
|
+
return responsiveCols.filter((col) => col.visible !== false);
|
|
779
|
+
}, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
|
|
780
|
+
activeMode = computed(() => {
|
|
781
|
+
return this.responsive.activeMode();
|
|
782
|
+
}, ...(ngDevMode ? [{ debugName: "activeMode" }] : /* istanbul ignore next */ []));
|
|
783
|
+
useVirtualization = computed(() => {
|
|
784
|
+
const config = this.mergedConfig();
|
|
785
|
+
const rowCount = this.rows().length;
|
|
786
|
+
if (config.virtualization === true) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
if (config.virtualization === false) {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
return rowCount > config.virtualizationThreshold;
|
|
793
|
+
}, ...(ngDevMode ? [{ debugName: "useVirtualization" }] : /* istanbul ignore next */ []));
|
|
794
|
+
virtualRows = computed(() => {
|
|
795
|
+
const rows = this.engine.processedRows();
|
|
796
|
+
const range = this.virtEngine.visibleRange();
|
|
797
|
+
return rows.slice(range.start, range.end);
|
|
798
|
+
}, ...(ngDevMode ? [{ debugName: "virtualRows" }] : /* istanbul ignore next */ []));
|
|
799
|
+
virtualStartIndex = computed(() => {
|
|
800
|
+
return this.virtEngine.visibleRange().start;
|
|
801
|
+
}, ...(ngDevMode ? [{ debugName: "virtualStartIndex" }] : /* istanbul ignore next */ []));
|
|
802
|
+
isEmpty = computed(() => {
|
|
803
|
+
return this.engine.processedRows().length === 0 && !this.loading();
|
|
804
|
+
}, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
805
|
+
classes = computed(() => {
|
|
806
|
+
return [
|
|
807
|
+
'fk-data-table',
|
|
808
|
+
this.loading() ? 'fk-data-table--loading' : '',
|
|
809
|
+
this.isEmpty() ? 'fk-data-table--empty' : '',
|
|
810
|
+
this.mergedConfig().stickyHeader ? 'fk-data-table--sticky-header' : '',
|
|
811
|
+
this.useVirtualization() ? 'fk-data-table--virtual' : '',
|
|
812
|
+
this.activeMode() === 'stack' ? 'fk-data-table--stack' : '',
|
|
813
|
+
this.className(),
|
|
814
|
+
]
|
|
815
|
+
.filter(Boolean)
|
|
816
|
+
.join(' ');
|
|
817
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
818
|
+
get hostClass() {
|
|
819
|
+
return this.classes();
|
|
820
|
+
}
|
|
821
|
+
get hostId() {
|
|
822
|
+
return this.id();
|
|
823
|
+
}
|
|
824
|
+
ngZone = inject(NgZone);
|
|
825
|
+
/**
|
|
826
|
+
* Optional group coordinator picked up via DI when this table is
|
|
827
|
+
* wrapped in `<fk-data-table-group>`. Presence flips expansion to
|
|
828
|
+
* single-open mode; absence keeps multi-expand behavior (the
|
|
829
|
+
* default).
|
|
830
|
+
*/
|
|
831
|
+
groupCoordinator = inject(DataTableGroupCoordinator, {
|
|
832
|
+
optional: true,
|
|
833
|
+
});
|
|
834
|
+
constructor() {
|
|
835
|
+
this.engine = new TableEngineService();
|
|
836
|
+
this.virtEngine = new VirtualizationEngineService();
|
|
837
|
+
// Clean up the coordinator's active-pointer when this table is torn
|
|
838
|
+
// down so a sibling expansion doesn't try to collapse a stale ref.
|
|
839
|
+
inject(DestroyRef).onDestroy(() => {
|
|
840
|
+
this.groupCoordinator?.unregister(this);
|
|
841
|
+
});
|
|
842
|
+
effect(() => {
|
|
843
|
+
this.engine.rows.set(this.rows());
|
|
844
|
+
});
|
|
845
|
+
effect(() => {
|
|
846
|
+
this.engine.columns.set(this.columns());
|
|
847
|
+
});
|
|
848
|
+
effect(() => {
|
|
849
|
+
const sort = this.sortState();
|
|
850
|
+
this.engine.controlledSortState.set(sort === null ? undefined : sort);
|
|
851
|
+
});
|
|
852
|
+
effect(() => {
|
|
853
|
+
const filter = this.filterState();
|
|
854
|
+
this.engine.controlledFilterState.set(filter === null ? undefined : filter);
|
|
855
|
+
});
|
|
856
|
+
effect(() => {
|
|
857
|
+
const page = this.pageState();
|
|
858
|
+
this.engine.controlledPageState.set(page === null ? undefined : page);
|
|
859
|
+
});
|
|
860
|
+
// Dev-mode: warn once per misconfiguration when selection is enabled
|
|
861
|
+
// but no row id resolver is provided. Without it `getSelectionKey`
|
|
862
|
+
// falls back to row reference which silently breaks across pagination
|
|
863
|
+
// since each fetch returns brand-new objects.
|
|
864
|
+
let warnedNoRowId = false;
|
|
865
|
+
effect(() => {
|
|
866
|
+
if (this.rowSelection() && this.getRowId() === null && !warnedNoRowId) {
|
|
867
|
+
warnedNoRowId = true;
|
|
868
|
+
console.error('[fk-data-table] `rowSelection` is true but `getRowId` was not ' +
|
|
869
|
+
'provided. Selection requires a stable row identifier — set ' +
|
|
870
|
+
"`[getRowId]` to a function returning each row's unique key.");
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
// Sync virtualization config
|
|
874
|
+
effect(() => {
|
|
875
|
+
const config = this.mergedConfig();
|
|
876
|
+
this.virtEngine.rowHeight.set(config.rowHeight);
|
|
877
|
+
this.virtEngine.overscan.set(config.overscan);
|
|
878
|
+
});
|
|
879
|
+
// Sync total row count into virtualization engine
|
|
880
|
+
effect(() => {
|
|
881
|
+
this.virtEngine.totalRows.set(this.engine.processedRows().length);
|
|
882
|
+
});
|
|
883
|
+
// Sync responsive strategy
|
|
884
|
+
effect(() => {
|
|
885
|
+
this.responsive.columns.set(this.columns());
|
|
886
|
+
});
|
|
887
|
+
effect(() => {
|
|
888
|
+
this.responsive.config.set(this.mergedConfig());
|
|
889
|
+
});
|
|
890
|
+
// Initialize breakpoint observer
|
|
891
|
+
effect(() => {
|
|
892
|
+
const config = this.mergedConfig();
|
|
893
|
+
this.responsive.init(config.breakpoints);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
// ===== TEMPLATE HELPERS =====
|
|
897
|
+
/** Resolves the display value for a cell given its column definition and row data. */
|
|
898
|
+
getCellValue(column, row) {
|
|
899
|
+
if (typeof column.accessor === 'function') {
|
|
900
|
+
return column.accessor(row);
|
|
901
|
+
}
|
|
902
|
+
return row[column.accessor];
|
|
903
|
+
}
|
|
904
|
+
/** Returns the trackBy function from the merged config for use in `*ngFor`. */
|
|
905
|
+
getTrackBy() {
|
|
906
|
+
return this.mergedConfig().trackBy;
|
|
907
|
+
}
|
|
908
|
+
// ===== SELECTION HELPERS =====
|
|
909
|
+
/**
|
|
910
|
+
* Source of truth for which keys are currently selected. Reads the
|
|
911
|
+
* controlled `selectedKeys` input when the parent supplied one;
|
|
912
|
+
* otherwise falls back to the engine's internal state (uncontrolled
|
|
913
|
+
* mode). Computed so changes flow through Angular's signal graph
|
|
914
|
+
* without effect-write timing surprises.
|
|
915
|
+
*/
|
|
916
|
+
effectiveSelectedKeys = computed(() => {
|
|
917
|
+
const controlled = this.selectedKeys();
|
|
918
|
+
if (controlled !== null) {
|
|
919
|
+
return controlled;
|
|
920
|
+
}
|
|
921
|
+
return this.engine.selectedKeys();
|
|
922
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveSelectedKeys" }] : /* istanbul ignore next */ []));
|
|
923
|
+
/**
|
|
924
|
+
* Public alias of `effectiveSelectedKeys` for the stack (mobile) child
|
|
925
|
+
* binding. The private field stays private to discourage callers from
|
|
926
|
+
* reaching past the API.
|
|
927
|
+
*/
|
|
928
|
+
effectiveSelectedKeysForTemplate = this.effectiveSelectedKeys;
|
|
929
|
+
/**
|
|
930
|
+
* Resolve a row's selection key. Uses the explicit `getRowId` input
|
|
931
|
+
* when provided; otherwise falls back to the row reference itself
|
|
932
|
+
* (which only works if rows are object-stable across renders — fine
|
|
933
|
+
* for uncontrolled-mode demos, broken across server-side pagination).
|
|
934
|
+
*/
|
|
935
|
+
/** Resolves a stable selection key for the given row using `getRowId` or falling back to the row reference. */
|
|
936
|
+
getSelectionKey(row) {
|
|
937
|
+
const idFn = this.getRowId();
|
|
938
|
+
if (idFn) {
|
|
939
|
+
return idFn(row);
|
|
940
|
+
}
|
|
941
|
+
return row;
|
|
942
|
+
}
|
|
943
|
+
/** Returns true if the given row is currently in the selected keys set. */
|
|
944
|
+
isRowSelected(row) {
|
|
945
|
+
return this.effectiveSelectedKeys().has(this.getSelectionKey(row));
|
|
946
|
+
}
|
|
947
|
+
/** Returns true if selection is disabled for the given row per the `selectionDisabled` input. */
|
|
948
|
+
isRowSelectionDisabled(row) {
|
|
949
|
+
const disabled = this.selectionDisabled();
|
|
950
|
+
if (typeof disabled === 'function') {
|
|
951
|
+
return disabled(row);
|
|
952
|
+
}
|
|
953
|
+
return disabled;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Selection state of the header checkbox, derived from visible rows ∩
|
|
957
|
+
* `effectiveSelectedKeys`. Disabled rows are excluded — they can't
|
|
958
|
+
* contribute to the all-selected check.
|
|
959
|
+
*/
|
|
960
|
+
headerSelectionState = computed(() => {
|
|
961
|
+
if (!this.rowSelection()) {
|
|
962
|
+
return 'unchecked';
|
|
963
|
+
}
|
|
964
|
+
const selected = this.effectiveSelectedKeys();
|
|
965
|
+
const visibleEligible = this.visibleSelectableRows();
|
|
966
|
+
if (visibleEligible.length === 0) {
|
|
967
|
+
return 'unchecked';
|
|
968
|
+
}
|
|
969
|
+
let hits = 0;
|
|
970
|
+
for (const row of visibleEligible) {
|
|
971
|
+
if (selected.has(this.getSelectionKey(row))) {
|
|
972
|
+
hits++;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (hits === 0) {
|
|
976
|
+
return 'unchecked';
|
|
977
|
+
}
|
|
978
|
+
if (hits === visibleEligible.length) {
|
|
979
|
+
return 'checked';
|
|
980
|
+
}
|
|
981
|
+
return 'indeterminate';
|
|
982
|
+
}, ...(ngDevMode ? [{ debugName: "headerSelectionState" }] : /* istanbul ignore next */ []));
|
|
983
|
+
/**
|
|
984
|
+
* Rows currently visible in the body (post-pagination) excluding any
|
|
985
|
+
* marked as selection-disabled. The header checkbox toggles only over
|
|
986
|
+
* this set.
|
|
987
|
+
*/
|
|
988
|
+
visibleSelectableRows = computed(() => {
|
|
989
|
+
return this.engine
|
|
990
|
+
.processedRows()
|
|
991
|
+
.filter((row) => !this.isRowSelectionDisabled(row));
|
|
992
|
+
}, ...(ngDevMode ? [{ debugName: "visibleSelectableRows" }] : /* istanbul ignore next */ []));
|
|
993
|
+
// ===== SELECTION ACTIONS =====
|
|
994
|
+
onToggleRow(row, event) {
|
|
995
|
+
event.stopPropagation();
|
|
996
|
+
if (this.isRowSelectionDisabled(row)) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
const key = this.getSelectionKey(row);
|
|
1000
|
+
const current = this.effectiveSelectedKeys();
|
|
1001
|
+
const next = new Set(current);
|
|
1002
|
+
if (next.has(key)) {
|
|
1003
|
+
next.delete(key);
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
next.add(key);
|
|
1007
|
+
}
|
|
1008
|
+
if (this.selectedKeys() === null) {
|
|
1009
|
+
this.engine.setSelectedKeys(next);
|
|
1010
|
+
}
|
|
1011
|
+
this.selectionChange.emit(next);
|
|
1012
|
+
}
|
|
1013
|
+
onToggleAllVisible(event) {
|
|
1014
|
+
event.stopPropagation();
|
|
1015
|
+
const visible = this.visibleSelectableRows();
|
|
1016
|
+
const visibleKeys = visible.map((row) => this.getSelectionKey(row));
|
|
1017
|
+
const current = this.effectiveSelectedKeys();
|
|
1018
|
+
const allSelected = visibleKeys.length > 0 && visibleKeys.every((key) => current.has(key));
|
|
1019
|
+
const next = new Set(current);
|
|
1020
|
+
if (allSelected) {
|
|
1021
|
+
for (const key of visibleKeys) {
|
|
1022
|
+
next.delete(key);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
for (const key of visibleKeys) {
|
|
1027
|
+
next.add(key);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (this.selectedKeys() === null) {
|
|
1031
|
+
this.engine.setSelectedKeys(next);
|
|
1032
|
+
}
|
|
1033
|
+
this.selectionChange.emit(next);
|
|
1034
|
+
}
|
|
1035
|
+
// ===== EXPANSION HELPERS =====
|
|
1036
|
+
/**
|
|
1037
|
+
* Source of truth for which row keys are currently expanded. Reads
|
|
1038
|
+
* the controlled `expandedKeys` input when the parent supplied one,
|
|
1039
|
+
* otherwise falls back to the engine's internal set.
|
|
1040
|
+
*/
|
|
1041
|
+
effectiveExpandedKeys = computed(() => {
|
|
1042
|
+
const controlled = this.expandedKeys();
|
|
1043
|
+
if (controlled !== null) {
|
|
1044
|
+
return controlled;
|
|
1045
|
+
}
|
|
1046
|
+
return this.engine.expandedKeys();
|
|
1047
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveExpandedKeys" }] : /* istanbul ignore next */ []));
|
|
1048
|
+
/** True when `rowDetailTemplate` is set — the feature is opt-in. */
|
|
1049
|
+
hasExpansion = computed(() => this.rowDetailTemplate() !== null, ...(ngDevMode ? [{ debugName: "hasExpansion" }] : /* istanbul ignore next */ []));
|
|
1050
|
+
/**
|
|
1051
|
+
* Whether the expand chevron column appears at the start. When the
|
|
1052
|
+
* expansion feature is off this returns false so the column doesn't
|
|
1053
|
+
* render. Selection takes priority when both are pinned to "start"
|
|
1054
|
+
* (selection comes first, then expand).
|
|
1055
|
+
*/
|
|
1056
|
+
hasExpansionStart = computed(() => this.hasExpansion() && this.expansionColumnPosition() === 'start', ...(ngDevMode ? [{ debugName: "hasExpansionStart" }] : /* istanbul ignore next */ []));
|
|
1057
|
+
hasExpansionEnd = computed(() => this.hasExpansion() && this.expansionColumnPosition() === 'end', ...(ngDevMode ? [{ debugName: "hasExpansionEnd" }] : /* istanbul ignore next */ []));
|
|
1058
|
+
/** Returns true if the given row is currently expanded. */
|
|
1059
|
+
isRowExpanded(row) {
|
|
1060
|
+
return this.effectiveExpandedKeys().has(this.getSelectionKey(row));
|
|
1061
|
+
}
|
|
1062
|
+
/** Returns true unless an `expandableRow` predicate explicitly excludes the row. */
|
|
1063
|
+
isRowExpandable(row) {
|
|
1064
|
+
const predicate = this.expandableRow();
|
|
1065
|
+
if (predicate) {
|
|
1066
|
+
return predicate(row);
|
|
1067
|
+
}
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Total columns rendered per row (data columns + selection col + expand col).
|
|
1072
|
+
* Used as the colspan for the detail `<td>` so it spans the full row width.
|
|
1073
|
+
*/
|
|
1074
|
+
totalColumnCount = computed(() => {
|
|
1075
|
+
let count = this.visibleColumns().length;
|
|
1076
|
+
if (this.rowSelection()) {
|
|
1077
|
+
count += 1;
|
|
1078
|
+
}
|
|
1079
|
+
if (this.hasExpansion()) {
|
|
1080
|
+
count += 1;
|
|
1081
|
+
}
|
|
1082
|
+
return count;
|
|
1083
|
+
}, ...(ngDevMode ? [{ debugName: "totalColumnCount" }] : /* istanbul ignore next */ []));
|
|
1084
|
+
/** Toggle a single row's expansion via chevron click or keyboard. */
|
|
1085
|
+
onToggleExpand(row, event) {
|
|
1086
|
+
event.stopPropagation();
|
|
1087
|
+
if (!this.isRowExpandable(row)) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const key = this.getSelectionKey(row);
|
|
1091
|
+
const currentSet = this.effectiveExpandedKeys();
|
|
1092
|
+
const wasExpanded = currentSet.has(key);
|
|
1093
|
+
const nextExpanded = !wasExpanded;
|
|
1094
|
+
if (this.groupCoordinator) {
|
|
1095
|
+
// Single-expand mode — route through the coordinator so a
|
|
1096
|
+
// sibling row (or sibling table) gets collapsed atomically.
|
|
1097
|
+
const { keysForThisTable, siblingTableToClear } = this.groupCoordinator.resolveToggle(this, key, nextExpanded, currentSet);
|
|
1098
|
+
this.commitExpansion(keysForThisTable);
|
|
1099
|
+
if (siblingTableToClear) {
|
|
1100
|
+
siblingTableToClear.clearExpansion();
|
|
1101
|
+
}
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
// Multi-expand mode — default. Add or remove this key from the set.
|
|
1105
|
+
const next = new Set(currentSet);
|
|
1106
|
+
if (wasExpanded) {
|
|
1107
|
+
next.delete(key);
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
next.add(key);
|
|
1111
|
+
}
|
|
1112
|
+
this.commitExpansion(next);
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Public hook used by the group coordinator to force-clear this
|
|
1116
|
+
* table's expansion when a sibling table opens a row. Routes through
|
|
1117
|
+
* the same commit path so controlled-mode parents still see the
|
|
1118
|
+
* emission.
|
|
1119
|
+
*/
|
|
1120
|
+
clearExpansion() {
|
|
1121
|
+
if (this.effectiveExpandedKeys().size === 0) {
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
this.commitExpansion(new Set());
|
|
1125
|
+
}
|
|
1126
|
+
commitExpansion(next) {
|
|
1127
|
+
if (this.expandedKeys() === null) {
|
|
1128
|
+
this.engine.setExpandedKeys(next);
|
|
1129
|
+
}
|
|
1130
|
+
this.expansionChange.emit(next);
|
|
1131
|
+
}
|
|
1132
|
+
// ===== ACTIONS =====
|
|
1133
|
+
onHeaderClick(column) {
|
|
1134
|
+
if (!column.sortable) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const next = this.engine.toggleSort(column.id);
|
|
1138
|
+
this.sortChange.emit(next);
|
|
1139
|
+
}
|
|
1140
|
+
/** Returns an inline style object for the row if a `rowStyleFn` is configured, otherwise null. */
|
|
1141
|
+
getRowStyle(row, index) {
|
|
1142
|
+
const fn = this.mergedConfig().rowStyleFn;
|
|
1143
|
+
if (!fn) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
return fn(row, index);
|
|
1147
|
+
}
|
|
1148
|
+
onRowClick(row, index) {
|
|
1149
|
+
this.rowClick.emit({
|
|
1150
|
+
row,
|
|
1151
|
+
index,
|
|
1152
|
+
selected: false,
|
|
1153
|
+
expanded: false,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
onVirtualScroll(event) {
|
|
1157
|
+
const target = event.target;
|
|
1158
|
+
this.virtEngine.onScroll(target.scrollTop);
|
|
1159
|
+
}
|
|
1160
|
+
onViewportInit(el) {
|
|
1161
|
+
this.virtEngine.onViewportResize(el.clientHeight);
|
|
1162
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
1163
|
+
const observer = new ResizeObserver((entries) => {
|
|
1164
|
+
for (const entry of entries) {
|
|
1165
|
+
this.ngZone.run(() => {
|
|
1166
|
+
this.virtEngine.onViewportResize(entry.contentRect.height);
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
observer.observe(el);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
getPinOffset(column) {
|
|
1174
|
+
if (!column.pinned) {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
const cols = this.visibleColumns();
|
|
1178
|
+
const side = column.pinned;
|
|
1179
|
+
let offset = 0;
|
|
1180
|
+
if (side === 'start') {
|
|
1181
|
+
for (const col of cols) {
|
|
1182
|
+
if (col.id === column.id) {
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
if (col.pinned === 'start') {
|
|
1186
|
+
offset += parseInt(col.width ?? col.minWidth ?? '0', 10) || 0;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
for (let i = cols.length - 1; i >= 0; i--) {
|
|
1192
|
+
if (cols[i].id === column.id) {
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
if (cols[i].pinned === 'end') {
|
|
1196
|
+
offset += parseInt(cols[i].width ?? cols[i].minWidth ?? '0', 10) || 0;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return `${offset}px`;
|
|
1201
|
+
}
|
|
1202
|
+
onColumnResize(event) {
|
|
1203
|
+
this.columnResize.emit(event);
|
|
1204
|
+
}
|
|
1205
|
+
/** Returns the current sort direction for the given column id, or null if not sorted. */
|
|
1206
|
+
getSortDirection(columnId) {
|
|
1207
|
+
const sort = this.engine.sortState();
|
|
1208
|
+
if (sort?.columnId !== columnId) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
return sort.direction;
|
|
1212
|
+
}
|
|
1213
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1214
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DataTableComponent, isStandalone: true, selector: "fk-data-table", inputs: { className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, sortState: { classPropertyName: "sortState", publicName: "sortState", isSignal: true, isRequired: false, transformFunction: null }, filterState: { classPropertyName: "filterState", publicName: "filterState", isSignal: true, isRequired: false, transformFunction: null }, pageState: { classPropertyName: "pageState", publicName: "pageState", isSignal: true, isRequired: false, transformFunction: null }, rowSelection: { classPropertyName: "rowSelection", publicName: "rowSelection", isSignal: true, isRequired: false, transformFunction: null }, selectedKeys: { classPropertyName: "selectedKeys", publicName: "selectedKeys", isSignal: true, isRequired: false, transformFunction: null }, getRowId: { classPropertyName: "getRowId", publicName: "getRowId", isSignal: true, isRequired: false, transformFunction: null }, selectionDisabled: { classPropertyName: "selectionDisabled", publicName: "selectionDisabled", isSignal: true, isRequired: false, transformFunction: null }, selectionColumnPosition: { classPropertyName: "selectionColumnPosition", publicName: "selectionColumnPosition", isSignal: true, isRequired: false, transformFunction: null }, selectionLabel: { classPropertyName: "selectionLabel", publicName: "selectionLabel", isSignal: true, isRequired: false, transformFunction: null }, rowDetailTemplate: { classPropertyName: "rowDetailTemplate", publicName: "rowDetailTemplate", isSignal: true, isRequired: false, transformFunction: null }, expansionColumnPosition: { classPropertyName: "expansionColumnPosition", publicName: "expansionColumnPosition", isSignal: true, isRequired: false, transformFunction: null }, expandedKeys: { classPropertyName: "expandedKeys", publicName: "expandedKeys", isSignal: true, isRequired: false, transformFunction: null }, expandableRow: { classPropertyName: "expandableRow", publicName: "expandableRow", isSignal: true, isRequired: false, transformFunction: null }, expansionLabel: { classPropertyName: "expansionLabel", publicName: "expansionLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", filterChange: "filterChange", pageChange: "pageChange", rowClick: "rowClick", selectionChange: "selectionChange", expansionChange: "expansionChange", columnResize: "columnResize" }, host: { properties: { "class": "this.hostClass", "attr.id": "this.hostId" } }, providers: [
|
|
1215
|
+
TableEngineService,
|
|
1216
|
+
VirtualizationEngineService,
|
|
1217
|
+
ResponsiveStrategyService,
|
|
1218
|
+
], viewQueries: [{ propertyName: "virtualViewport", first: true, predicate: ["virtualViewport"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (activeMode() === 'stack') {\n <!-- Stack path: mobile card view -->\n <fk-data-table-stack\n [rows]=\"engine.processedRows()\"\n [columns]=\"columns()\"\n [config]=\"mergedConfig()\"\n [trackByFn]=\"mergedConfig().trackBy\"\n [rowSelection]=\"rowSelection()\"\n [selectedKeys]=\"effectiveSelectedKeysForTemplate()\"\n [getRowId]=\"getRowId()\"\n [selectionDisabled]=\"selectionDisabled()\"\n [selectionLabel]=\"selectionLabel()\"\n (rowClick)=\"rowClick.emit($event)\"\n (toggleRow)=\"onToggleRow($event.row, $event.event)\"\n />\n} @else if (useVirtualization()) {\n <!-- Virtual path: div/grid-based with virtual scrolling -->\n <div role=\"table\" [attr.aria-label]=\"ariaLabel()\">\n @if (mergedConfig().showHeader) {\n <div class=\"fk-data-table__head\" role=\"rowgroup\">\n <div class=\"fk-data-table__head-row\" role=\"row\">\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <div\n class=\"fk-data-table__th fk-data-table__selection-cell\"\n role=\"columnheader\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </div>\n }\n @for (column of visibleColumns(); track column.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-data-table__th\"\n role=\"columnheader\"\n [fkColumnResize]=\"column.id\"\n [enabled]=\"column.resizable === true\"\n (columnResize)=\"onColumnResize($event)\"\n [class.fk-data-table__th--sortable]=\"column.sortable\"\n [class.fk-data-table__th--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__th--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__th--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__th--pinned-end]=\"column.pinned === 'end'\"\n [style.width]=\"column.width ?? null\"\n [style.min-width]=\"column.minWidth ?? null\"\n [style.max-width]=\"column.maxWidth ?? null\"\n [style.flex]=\"column.flex ?? null\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n [attr.aria-sort]=\"\n getSortDirection(column.id) === 'asc'\n ? 'ascending'\n : getSortDirection(column.id) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(column)\"\n >\n @if (column.headerRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.headerRenderer\"\n [ngTemplateOutletContext]=\"{ $implicit: column }\"\n />\n } @else {\n {{ column.header }}\n }\n\n @if (column.sortable) {\n <span\n class=\"fk-data-table__sort-indicator\"\n [class.fk-data-table__sort-indicator--asc]=\"\n getSortDirection(column.id) === 'asc'\n \"\n [class.fk-data-table__sort-indicator--desc]=\"\n getSortDirection(column.id) === 'desc'\n \"\n aria-hidden=\"true\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M5 2L8 6H2L5 2Z\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n </div>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <div\n class=\"fk-data-table__th fk-data-table__selection-cell\"\n role=\"columnheader\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </div>\n }\n </div>\n </div>\n }\n\n <div\n #virtualViewport\n class=\"fk-data-table__viewport\"\n role=\"rowgroup\"\n (scroll)=\"onVirtualScroll($event)\"\n >\n <div\n class=\"fk-data-table__sentinel\"\n [style.height.px]=\"virtEngine.totalHeight()\"\n ></div>\n\n <div\n class=\"fk-data-table__virtual-body\"\n [style.transform]=\"'translateY(' + virtEngine.offsetY() + 'px)'\"\n >\n @for (\n row of virtualRows();\n track mergedConfig().trackBy(virtualStartIndex() + $index, row);\n let i = $index\n ) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-data-table__row\"\n role=\"row\"\n [class.fk-data-table__row--selected]=\"isRowSelected(row)\"\n [style.height.px]=\"mergedConfig().rowHeight\"\n [ngStyle]=\"getRowStyle(row, virtualStartIndex() + i)\"\n (click)=\"onRowClick(row, virtualStartIndex() + i)\"\n >\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <div\n class=\"fk-data-table__td fk-data-table__selection-cell\"\n role=\"cell\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </div>\n }\n @for (column of visibleColumns(); track column.id) {\n <div\n class=\"fk-data-table__td\"\n role=\"cell\"\n [class.fk-data-table__td--truncate]=\"column.truncate\"\n [class.fk-data-table__td--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__td--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__td--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__td--pinned-end]=\"column.pinned === 'end'\"\n [style.width]=\"column.width ?? null\"\n [style.min-width]=\"column.minWidth ?? null\"\n [style.max-width]=\"column.maxWidth ?? null\"\n [style.flex]=\"column.flex ?? null\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n >\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </div>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <div\n class=\"fk-data-table__td fk-data-table__selection-cell\"\n role=\"cell\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (isEmpty()) {\n <div class=\"fk-data-table__empty\" role=\"row\">\n <div role=\"cell\">{{ mergedConfig().emptyMessage }}</div>\n </div>\n }\n </div>\n} @else {\n <!-- Standard path: semantic table -->\n <div class=\"fk-data-table__scroll\">\n <table class=\"fk-data-table__table\" [attr.aria-label]=\"ariaLabel()\">\n @if (mergedConfig().showHeader) {\n <thead class=\"fk-data-table__head\">\n <tr class=\"fk-data-table__head-row\">\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <th class=\"fk-data-table__th fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </th>\n }\n @if (hasExpansionStart()) {\n <th\n class=\"fk-data-table__th fk-data-table__expand-cell\"\n aria-hidden=\"true\"\n ></th>\n }\n @for (column of visibleColumns(); track column.id) {\n <th\n class=\"fk-data-table__th\"\n [fkColumnResize]=\"column.id\"\n [enabled]=\"column.resizable === true\"\n (columnResize)=\"onColumnResize($event)\"\n [class.fk-data-table__th--sortable]=\"column.sortable\"\n [class.fk-data-table__th--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__th--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__th--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__th--pinned-end]=\"column.pinned === 'end'\"\n [style.width]=\"column.width ?? null\"\n [style.min-width]=\"column.minWidth ?? null\"\n [style.max-width]=\"column.maxWidth ?? null\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n [attr.aria-sort]=\"\n getSortDirection(column.id) === 'asc'\n ? 'ascending'\n : getSortDirection(column.id) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(column)\"\n >\n @if (column.headerRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.headerRenderer\"\n [ngTemplateOutletContext]=\"{ $implicit: column }\"\n />\n } @else {\n {{ column.header }}\n }\n\n @if (column.sortable) {\n <span\n class=\"fk-data-table__sort-indicator\"\n [class.fk-data-table__sort-indicator--asc]=\"\n getSortDirection(column.id) === 'asc'\n \"\n [class.fk-data-table__sort-indicator--desc]=\"\n getSortDirection(column.id) === 'desc'\n \"\n aria-hidden=\"true\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M5 2L8 6H2L5 2Z\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n </th>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <th class=\"fk-data-table__th fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </th>\n }\n @if (hasExpansionEnd()) {\n <th\n class=\"fk-data-table__th fk-data-table__expand-cell\"\n aria-hidden=\"true\"\n ></th>\n }\n </tr>\n </thead>\n }\n\n <tbody class=\"fk-data-table__body\">\n @for (\n row of engine.processedRows();\n track mergedConfig().trackBy($index, row);\n let i = $index\n ) {\n <tr\n class=\"fk-data-table__row\"\n [class.fk-data-table__row--selected]=\"isRowSelected(row)\"\n [class.fk-data-table__row--expanded]=\"\n hasExpansion() && isRowExpanded(row)\n \"\n [ngStyle]=\"getRowStyle(row, i)\"\n (click)=\"onRowClick(row, i)\"\n >\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <td class=\"fk-data-table__td fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </td>\n }\n @if (hasExpansionStart()) {\n <td class=\"fk-data-table__td fk-data-table__expand-cell\">\n @if (isRowExpandable(row)) {\n <button\n type=\"button\"\n class=\"fk-data-table__expand-button\"\n [class.fk-data-table__expand-button--expanded]=\"\n isRowExpanded(row)\n \"\n [attr.aria-label]=\"expansionLabel()\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n (click)=\"onToggleExpand(row, $event)\"\n >\n <svg\n class=\"fk-data-table__expand-chevron\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M3 1.5L6.5 5L3 8.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n }\n </td>\n }\n @for (column of visibleColumns(); track column.id) {\n <td\n class=\"fk-data-table__td\"\n [class.fk-data-table__td--truncate]=\"column.truncate\"\n [class.fk-data-table__td--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__td--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__td--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__td--pinned-end]=\"column.pinned === 'end'\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n >\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </td>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <td class=\"fk-data-table__td fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </td>\n }\n @if (hasExpansionEnd()) {\n <td class=\"fk-data-table__td fk-data-table__expand-cell\">\n @if (isRowExpandable(row)) {\n <button\n type=\"button\"\n class=\"fk-data-table__expand-button\"\n [class.fk-data-table__expand-button--expanded]=\"\n isRowExpanded(row)\n \"\n [attr.aria-label]=\"expansionLabel()\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n (click)=\"onToggleExpand(row, $event)\"\n >\n <svg\n class=\"fk-data-table__expand-chevron\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M3 1.5L6.5 5L3 8.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n }\n </td>\n }\n </tr>\n @if (hasExpansion() && isRowExpanded(row)) {\n <tr class=\"fk-data-table__detail-row\">\n <td\n class=\"fk-data-table__detail-cell\"\n [attr.colspan]=\"totalColumnCount()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowDetailTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: row,\n row: row,\n index: i,\n }\"\n />\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n\n @if (isEmpty()) {\n <div class=\"fk-data-table__empty\">\n {{ mergedConfig().emptyMessage }}\n </div>\n }\n </div>\n}\n\n@if (loading()) {\n <div class=\"fk-data-table__loading-overlay\" aria-live=\"polite\">\n <span class=\"fk-data-table__loading-text\">Loading\u2026</span>\n </div>\n}\n", styles: [":host{display:block;position:relative;border:var(--fk-data-table-border-width, 1px) solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-border-radius, var(--fk-radius-md, .375rem));overflow:hidden}.fk-data-table__scroll{overflow-x:auto;width:100%}.fk-data-table__table{width:100%;border-collapse:collapse;border-spacing:0}.fk-data-table__head{background:var(--fk-data-table-head-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table__head-row{display:flex}.fk-data-table__table .fk-data-table__head-row{display:table-row}.fk-data-table__th{position:relative;padding:var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem));text-align:start;font-weight:var(--fk-data-table-head-font-weight, var(--fk-font-weight-semibold, 600));font-size:var(--fk-data-table-head-font-size, var(--fk-typography-small-font-size, .875rem));color:var(--fk-data-table-head-color, var(--fk-color-text-muted, #6b7a8d));border-bottom:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));white-space:nowrap;-webkit-user-select:none;user-select:none;flex:1}.fk-data-table__th--sortable{cursor:pointer}.fk-data-table__th--sortable:hover{color:var(--fk-data-table-head-hover-color, var(--fk-color-text, #1f2d3d))}.fk-data-table__th--align-center{text-align:center}.fk-data-table__th--align-end{text-align:end}.fk-data-table__table .fk-data-table__th{flex:none}.fk-data-table__sort-indicator{display:inline-flex;margin-inline-start:var(--fk-rhythm-1, .25rem);opacity:.3;transition:transform .15s ease,opacity .15s ease}.fk-data-table__sort-indicator--asc{opacity:1;transform:rotate(0)}.fk-data-table__sort-indicator--desc{opacity:1;transform:rotate(180deg)}.fk-data-table__viewport{overflow-y:auto;position:relative;max-height:var(--fk-data-table-viewport-height, 600px)}.fk-data-table__sentinel{position:absolute;top:0;left:0;width:1px;visibility:hidden}.fk-data-table__virtual-body{position:relative;will-change:transform}.fk-data-table__body{background:var(--fk-data-table-body-bg, var(--fk-color-surface, #ffffff))}.fk-data-table__row{display:flex;border-bottom:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));cursor:pointer}.fk-data-table__row:hover{background:var(--fk-data-table-row-hover-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table__row:last-child{border-bottom:none}.fk-data-table__table .fk-data-table__row{display:table-row}.fk-data-table__td{padding:var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem));font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem));color:var(--fk-data-table-body-color, var(--fk-color-text, #1f2d3d));vertical-align:middle;flex:1}.fk-data-table__td--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fk-data-table__td--align-center{text-align:center}.fk-data-table__td--align-end{text-align:end}.fk-data-table__table .fk-data-table__td{flex:none}.fk-data-table__empty{padding:var(--fk-data-table-empty-padding, var(--fk-rhythm-8, 2rem));text-align:center;color:var(--fk-data-table-empty-color, var(--fk-color-text-muted, #6b7a8d));font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem))}.fk-data-table__loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--fk-data-table-loading-bg, var(--fk-color-surface-overlay, rgba(255, 255, 255, .7)));z-index:1}.fk-data-table__loading-text{font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem));color:var(--fk-data-table-loading-color, var(--fk-color-text-muted, #6b7a8d))}.fk-data-table__resize-handle{position:absolute;top:0;right:0;bottom:0;width:var(--fk-data-table-resize-handle-width, 4px);cursor:col-resize;background:transparent;z-index:1}.fk-data-table__resize-handle:hover,.fk-data-table__resize-handle:active{background:var(--fk-data-table-resize-handle-color, var(--fk-color-primary, #3b82f6))}.fk-data-table__th--pinned-start,.fk-data-table__td--pinned-start{position:sticky;left:var(--fk-data-table-pin-offset, 0);z-index:2;background:inherit}.fk-data-table__th--pinned-end,.fk-data-table__td--pinned-end{position:sticky;right:var(--fk-data-table-pin-offset, 0);z-index:2;background:inherit}.fk-data-table__selection-cell{flex:none;width:var(--fk-data-table-selection-cell-width, 2.75rem);min-width:var(--fk-data-table-selection-cell-width, 2.75rem);max-width:var(--fk-data-table-selection-cell-width, 2.75rem);text-align:center;cursor:default}.fk-data-table__selection-cell:hover{color:inherit}.fk-data-table__row--selected{background:var(--fk-data-table-row-selected-bg, var(--fk-color-primary-light, #eaf4ff))}.fk-data-table__row--selected:hover{background:var(--fk-data-table-row-selected-hover-bg, var(--fk-color-primary-light, #dbe9ff))}.fk-data-table__expand-cell{flex:none;width:var(--fk-data-table-expand-cell-width, 2.5rem);min-width:var(--fk-data-table-expand-cell-width, 2.5rem);max-width:var(--fk-data-table-expand-cell-width, 2.5rem);text-align:center;cursor:default;padding-inline:var(--fk-rhythm-1, .25rem)}.fk-data-table__expand-button{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:var(--fk-data-table-expand-button-size, 1.5rem);height:var(--fk-data-table-expand-button-size, 1.5rem);padding:0;background:transparent;border:1px solid transparent;border-radius:var(--fk-data-table-expand-button-radius, .25rem);color:var(--fk-data-table-expand-color, var(--fk-color-text-muted, #5d6b80));cursor:pointer;transition:background-color .15s ease,color .15s ease,transform .15s ease}.fk-data-table__expand-button:hover{background:var(--fk-data-table-expand-button-hover-bg, var(--fk-color-surface-muted, #f1f4f8));color:var(--fk-data-table-expand-color-hover, var(--fk-color-text, #1c2433))}.fk-data-table__expand-button:focus-visible{outline:2px solid var(--fk-data-table-expand-focus-ring, var(--fk-color-primary, #0066cc));outline-offset:2px}.fk-data-table__expand-button--expanded{color:var(--fk-data-table-expand-color-active, var(--fk-color-primary, #0066cc))}.fk-data-table__expand-button--expanded .fk-data-table__expand-chevron{transform:rotate(90deg)}.fk-data-table__expand-chevron{transition:transform .15s ease}.fk-data-table__row--expanded{background:var(--fk-data-table-row-expanded-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table__detail-row{background:var(--fk-data-table-detail-row-bg, var(--fk-color-surface, #ffffff))}.fk-data-table__detail-cell{padding:var(--fk-data-table-detail-cell-padding, var(--fk-rhythm-4, 1rem));border-top:1px solid var(--fk-data-table-detail-border-color, var(--fk-color-border, #d9e2ee));border-bottom:1px solid var(--fk-data-table-detail-border-color, var(--fk-color-border, #d9e2ee))}.fk-data-table__checkbox{appearance:none;width:var(--fk-data-table-checkbox-size, 1rem);height:var(--fk-data-table-checkbox-size, 1rem);margin:0;border:1px solid var(--fk-data-table-checkbox-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-checkbox-radius, .25rem);background:var(--fk-data-table-checkbox-bg, var(--fk-color-surface, #ffffff));cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease;position:relative;vertical-align:middle}.fk-data-table__checkbox:focus-visible{outline:none;box-shadow:var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18))}.fk-data-table__checkbox:hover:not(:disabled){border-color:var(--fk-data-table-checkbox-hover-border-color, var(--fk-color-primary, #0a84ff))}.fk-data-table__checkbox:checked,.fk-data-table__checkbox:indeterminate{background:var(--fk-data-table-checkbox-checked-bg, var(--fk-color-primary, #0a84ff));border-color:var(--fk-data-table-checkbox-checked-border-color, var(--fk-color-primary, #0a84ff))}.fk-data-table__checkbox:checked:after{content:\"\";position:absolute;inset:0;background:var(--fk-data-table-checkbox-icon-color, var(--fk-white, #ffffff));mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat;-webkit-mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat}.fk-data-table__checkbox:indeterminate:after{content:\"\";position:absolute;inset:0;background:var(--fk-data-table-checkbox-icon-color, var(--fk-white, #ffffff));mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' rx='1' fill='currentColor'/></svg>\") center/75% 75% no-repeat;-webkit-mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' rx='1' fill='currentColor'/></svg>\") center/75% 75% no-repeat}.fk-data-table__checkbox:disabled{cursor:not-allowed;opacity:.5}.fk-data-table--resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fk-data-table--sticky-header .fk-data-table__head{position:sticky;top:0;z-index:1}.fk-data-table--sticky-header .fk-data-table__table .fk-data-table__th{position:sticky;top:0;z-index:1;background:var(--fk-data-table-head-bg, var(--fk-color-surface-muted, #f7f9fb))}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DataTableStackComponent, selector: "fk-data-table-stack", inputs: ["rows", "columns", "config", "trackByFn", "rowSelection", "selectedKeys", "getRowId", "selectionDisabled", "selectionLabel"], outputs: ["rowClick", "toggleRow"] }, { kind: "directive", type: ColumnResizeDirective, selector: "[fkColumnResize]", inputs: ["fkColumnResize", "enabled", "minWidth", "maxWidth"], outputs: ["columnResize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1219
|
+
}
|
|
1220
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableComponent, decorators: [{
|
|
1221
|
+
type: Component,
|
|
1222
|
+
args: [{ selector: 'fk-data-table', standalone: true, imports: [
|
|
1223
|
+
NgStyle,
|
|
1224
|
+
NgTemplateOutlet,
|
|
1225
|
+
DataTableStackComponent,
|
|
1226
|
+
ColumnResizeDirective,
|
|
1227
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
1228
|
+
TableEngineService,
|
|
1229
|
+
VirtualizationEngineService,
|
|
1230
|
+
ResponsiveStrategyService,
|
|
1231
|
+
], template: "@if (activeMode() === 'stack') {\n <!-- Stack path: mobile card view -->\n <fk-data-table-stack\n [rows]=\"engine.processedRows()\"\n [columns]=\"columns()\"\n [config]=\"mergedConfig()\"\n [trackByFn]=\"mergedConfig().trackBy\"\n [rowSelection]=\"rowSelection()\"\n [selectedKeys]=\"effectiveSelectedKeysForTemplate()\"\n [getRowId]=\"getRowId()\"\n [selectionDisabled]=\"selectionDisabled()\"\n [selectionLabel]=\"selectionLabel()\"\n (rowClick)=\"rowClick.emit($event)\"\n (toggleRow)=\"onToggleRow($event.row, $event.event)\"\n />\n} @else if (useVirtualization()) {\n <!-- Virtual path: div/grid-based with virtual scrolling -->\n <div role=\"table\" [attr.aria-label]=\"ariaLabel()\">\n @if (mergedConfig().showHeader) {\n <div class=\"fk-data-table__head\" role=\"rowgroup\">\n <div class=\"fk-data-table__head-row\" role=\"row\">\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <div\n class=\"fk-data-table__th fk-data-table__selection-cell\"\n role=\"columnheader\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </div>\n }\n @for (column of visibleColumns(); track column.id) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-data-table__th\"\n role=\"columnheader\"\n [fkColumnResize]=\"column.id\"\n [enabled]=\"column.resizable === true\"\n (columnResize)=\"onColumnResize($event)\"\n [class.fk-data-table__th--sortable]=\"column.sortable\"\n [class.fk-data-table__th--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__th--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__th--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__th--pinned-end]=\"column.pinned === 'end'\"\n [style.width]=\"column.width ?? null\"\n [style.min-width]=\"column.minWidth ?? null\"\n [style.max-width]=\"column.maxWidth ?? null\"\n [style.flex]=\"column.flex ?? null\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n [attr.aria-sort]=\"\n getSortDirection(column.id) === 'asc'\n ? 'ascending'\n : getSortDirection(column.id) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(column)\"\n >\n @if (column.headerRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.headerRenderer\"\n [ngTemplateOutletContext]=\"{ $implicit: column }\"\n />\n } @else {\n {{ column.header }}\n }\n\n @if (column.sortable) {\n <span\n class=\"fk-data-table__sort-indicator\"\n [class.fk-data-table__sort-indicator--asc]=\"\n getSortDirection(column.id) === 'asc'\n \"\n [class.fk-data-table__sort-indicator--desc]=\"\n getSortDirection(column.id) === 'desc'\n \"\n aria-hidden=\"true\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M5 2L8 6H2L5 2Z\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n </div>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <div\n class=\"fk-data-table__th fk-data-table__selection-cell\"\n role=\"columnheader\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </div>\n }\n </div>\n </div>\n }\n\n <div\n #virtualViewport\n class=\"fk-data-table__viewport\"\n role=\"rowgroup\"\n (scroll)=\"onVirtualScroll($event)\"\n >\n <div\n class=\"fk-data-table__sentinel\"\n [style.height.px]=\"virtEngine.totalHeight()\"\n ></div>\n\n <div\n class=\"fk-data-table__virtual-body\"\n [style.transform]=\"'translateY(' + virtEngine.offsetY() + 'px)'\"\n >\n @for (\n row of virtualRows();\n track mergedConfig().trackBy(virtualStartIndex() + $index, row);\n let i = $index\n ) {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"fk-data-table__row\"\n role=\"row\"\n [class.fk-data-table__row--selected]=\"isRowSelected(row)\"\n [style.height.px]=\"mergedConfig().rowHeight\"\n [ngStyle]=\"getRowStyle(row, virtualStartIndex() + i)\"\n (click)=\"onRowClick(row, virtualStartIndex() + i)\"\n >\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <div\n class=\"fk-data-table__td fk-data-table__selection-cell\"\n role=\"cell\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </div>\n }\n @for (column of visibleColumns(); track column.id) {\n <div\n class=\"fk-data-table__td\"\n role=\"cell\"\n [class.fk-data-table__td--truncate]=\"column.truncate\"\n [class.fk-data-table__td--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__td--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__td--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__td--pinned-end]=\"column.pinned === 'end'\"\n [style.width]=\"column.width ?? null\"\n [style.min-width]=\"column.minWidth ?? null\"\n [style.max-width]=\"column.maxWidth ?? null\"\n [style.flex]=\"column.flex ?? null\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n >\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </div>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <div\n class=\"fk-data-table__td fk-data-table__selection-cell\"\n role=\"cell\"\n >\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n @if (isEmpty()) {\n <div class=\"fk-data-table__empty\" role=\"row\">\n <div role=\"cell\">{{ mergedConfig().emptyMessage }}</div>\n </div>\n }\n </div>\n} @else {\n <!-- Standard path: semantic table -->\n <div class=\"fk-data-table__scroll\">\n <table class=\"fk-data-table__table\" [attr.aria-label]=\"ariaLabel()\">\n @if (mergedConfig().showHeader) {\n <thead class=\"fk-data-table__head\">\n <tr class=\"fk-data-table__head-row\">\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <th class=\"fk-data-table__th fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </th>\n }\n @if (hasExpansionStart()) {\n <th\n class=\"fk-data-table__th fk-data-table__expand-cell\"\n aria-hidden=\"true\"\n ></th>\n }\n @for (column of visibleColumns(); track column.id) {\n <th\n class=\"fk-data-table__th\"\n [fkColumnResize]=\"column.id\"\n [enabled]=\"column.resizable === true\"\n (columnResize)=\"onColumnResize($event)\"\n [class.fk-data-table__th--sortable]=\"column.sortable\"\n [class.fk-data-table__th--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__th--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__th--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__th--pinned-end]=\"column.pinned === 'end'\"\n [style.width]=\"column.width ?? null\"\n [style.min-width]=\"column.minWidth ?? null\"\n [style.max-width]=\"column.maxWidth ?? null\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n [attr.aria-sort]=\"\n getSortDirection(column.id) === 'asc'\n ? 'ascending'\n : getSortDirection(column.id) === 'desc'\n ? 'descending'\n : null\n \"\n (click)=\"onHeaderClick(column)\"\n >\n @if (column.headerRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.headerRenderer\"\n [ngTemplateOutletContext]=\"{ $implicit: column }\"\n />\n } @else {\n {{ column.header }}\n }\n\n @if (column.sortable) {\n <span\n class=\"fk-data-table__sort-indicator\"\n [class.fk-data-table__sort-indicator--asc]=\"\n getSortDirection(column.id) === 'asc'\n \"\n [class.fk-data-table__sort-indicator--desc]=\"\n getSortDirection(column.id) === 'desc'\n \"\n aria-hidden=\"true\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M5 2L8 6H2L5 2Z\" fill=\"currentColor\" />\n </svg>\n </span>\n }\n </th>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <th class=\"fk-data-table__th fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"\n headerSelectionState() === 'checked'\n ? 'Deselect all on page'\n : 'Select all on page'\n \"\n [checked]=\"headerSelectionState() === 'checked'\"\n [indeterminate]=\"headerSelectionState() === 'indeterminate'\"\n (click)=\"onToggleAllVisible($event)\"\n />\n </th>\n }\n @if (hasExpansionEnd()) {\n <th\n class=\"fk-data-table__th fk-data-table__expand-cell\"\n aria-hidden=\"true\"\n ></th>\n }\n </tr>\n </thead>\n }\n\n <tbody class=\"fk-data-table__body\">\n @for (\n row of engine.processedRows();\n track mergedConfig().trackBy($index, row);\n let i = $index\n ) {\n <tr\n class=\"fk-data-table__row\"\n [class.fk-data-table__row--selected]=\"isRowSelected(row)\"\n [class.fk-data-table__row--expanded]=\"\n hasExpansion() && isRowExpanded(row)\n \"\n [ngStyle]=\"getRowStyle(row, i)\"\n (click)=\"onRowClick(row, i)\"\n >\n @if (rowSelection() && selectionColumnPosition() === 'start') {\n <td class=\"fk-data-table__td fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </td>\n }\n @if (hasExpansionStart()) {\n <td class=\"fk-data-table__td fk-data-table__expand-cell\">\n @if (isRowExpandable(row)) {\n <button\n type=\"button\"\n class=\"fk-data-table__expand-button\"\n [class.fk-data-table__expand-button--expanded]=\"\n isRowExpanded(row)\n \"\n [attr.aria-label]=\"expansionLabel()\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n (click)=\"onToggleExpand(row, $event)\"\n >\n <svg\n class=\"fk-data-table__expand-chevron\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M3 1.5L6.5 5L3 8.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n }\n </td>\n }\n @for (column of visibleColumns(); track column.id) {\n <td\n class=\"fk-data-table__td\"\n [class.fk-data-table__td--truncate]=\"column.truncate\"\n [class.fk-data-table__td--align-center]=\"\n column.align === 'center'\n \"\n [class.fk-data-table__td--align-end]=\"column.align === 'end'\"\n [class.fk-data-table__td--pinned-start]=\"\n column.pinned === 'start'\n \"\n [class.fk-data-table__td--pinned-end]=\"column.pinned === 'end'\"\n [style.--fk-data-table-pin-offset]=\"getPinOffset(column)\"\n >\n @if (column.cellRenderer) {\n <ng-container\n [ngTemplateOutlet]=\"column.cellRenderer\"\n [ngTemplateOutletContext]=\"{\n $implicit: getCellValue(column, row),\n row: row,\n }\"\n />\n } @else {\n {{ getCellValue(column, row) }}\n }\n </td>\n }\n @if (rowSelection() && selectionColumnPosition() === 'end') {\n <td class=\"fk-data-table__td fk-data-table__selection-cell\">\n <input\n type=\"checkbox\"\n class=\"fk-data-table__checkbox\"\n [attr.aria-label]=\"selectionLabel()\"\n [checked]=\"isRowSelected(row)\"\n [disabled]=\"isRowSelectionDisabled(row)\"\n (click)=\"onToggleRow(row, $event)\"\n />\n </td>\n }\n @if (hasExpansionEnd()) {\n <td class=\"fk-data-table__td fk-data-table__expand-cell\">\n @if (isRowExpandable(row)) {\n <button\n type=\"button\"\n class=\"fk-data-table__expand-button\"\n [class.fk-data-table__expand-button--expanded]=\"\n isRowExpanded(row)\n \"\n [attr.aria-label]=\"expansionLabel()\"\n [attr.aria-expanded]=\"isRowExpanded(row)\"\n (click)=\"onToggleExpand(row, $event)\"\n >\n <svg\n class=\"fk-data-table__expand-chevron\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M3 1.5L6.5 5L3 8.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n }\n </td>\n }\n </tr>\n @if (hasExpansion() && isRowExpanded(row)) {\n <tr class=\"fk-data-table__detail-row\">\n <td\n class=\"fk-data-table__detail-cell\"\n [attr.colspan]=\"totalColumnCount()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowDetailTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: row,\n row: row,\n index: i,\n }\"\n />\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n\n @if (isEmpty()) {\n <div class=\"fk-data-table__empty\">\n {{ mergedConfig().emptyMessage }}\n </div>\n }\n </div>\n}\n\n@if (loading()) {\n <div class=\"fk-data-table__loading-overlay\" aria-live=\"polite\">\n <span class=\"fk-data-table__loading-text\">Loading\u2026</span>\n </div>\n}\n", styles: [":host{display:block;position:relative;border:var(--fk-data-table-border-width, 1px) solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-border-radius, var(--fk-radius-md, .375rem));overflow:hidden}.fk-data-table__scroll{overflow-x:auto;width:100%}.fk-data-table__table{width:100%;border-collapse:collapse;border-spacing:0}.fk-data-table__head{background:var(--fk-data-table-head-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table__head-row{display:flex}.fk-data-table__table .fk-data-table__head-row{display:table-row}.fk-data-table__th{position:relative;padding:var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem));text-align:start;font-weight:var(--fk-data-table-head-font-weight, var(--fk-font-weight-semibold, 600));font-size:var(--fk-data-table-head-font-size, var(--fk-typography-small-font-size, .875rem));color:var(--fk-data-table-head-color, var(--fk-color-text-muted, #6b7a8d));border-bottom:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));white-space:nowrap;-webkit-user-select:none;user-select:none;flex:1}.fk-data-table__th--sortable{cursor:pointer}.fk-data-table__th--sortable:hover{color:var(--fk-data-table-head-hover-color, var(--fk-color-text, #1f2d3d))}.fk-data-table__th--align-center{text-align:center}.fk-data-table__th--align-end{text-align:end}.fk-data-table__table .fk-data-table__th{flex:none}.fk-data-table__sort-indicator{display:inline-flex;margin-inline-start:var(--fk-rhythm-1, .25rem);opacity:.3;transition:transform .15s ease,opacity .15s ease}.fk-data-table__sort-indicator--asc{opacity:1;transform:rotate(0)}.fk-data-table__sort-indicator--desc{opacity:1;transform:rotate(180deg)}.fk-data-table__viewport{overflow-y:auto;position:relative;max-height:var(--fk-data-table-viewport-height, 600px)}.fk-data-table__sentinel{position:absolute;top:0;left:0;width:1px;visibility:hidden}.fk-data-table__virtual-body{position:relative;will-change:transform}.fk-data-table__body{background:var(--fk-data-table-body-bg, var(--fk-color-surface, #ffffff))}.fk-data-table__row{display:flex;border-bottom:1px solid var(--fk-data-table-border-color, var(--fk-color-border, #d9e2ee));cursor:pointer}.fk-data-table__row:hover{background:var(--fk-data-table-row-hover-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table__row:last-child{border-bottom:none}.fk-data-table__table .fk-data-table__row{display:table-row}.fk-data-table__td{padding:var(--fk-data-table-cell-padding, var(--fk-rhythm-3, .75rem));font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem));color:var(--fk-data-table-body-color, var(--fk-color-text, #1f2d3d));vertical-align:middle;flex:1}.fk-data-table__td--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fk-data-table__td--align-center{text-align:center}.fk-data-table__td--align-end{text-align:end}.fk-data-table__table .fk-data-table__td{flex:none}.fk-data-table__empty{padding:var(--fk-data-table-empty-padding, var(--fk-rhythm-8, 2rem));text-align:center;color:var(--fk-data-table-empty-color, var(--fk-color-text-muted, #6b7a8d));font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem))}.fk-data-table__loading-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--fk-data-table-loading-bg, var(--fk-color-surface-overlay, rgba(255, 255, 255, .7)));z-index:1}.fk-data-table__loading-text{font-size:var(--fk-data-table-body-font-size, var(--fk-typography-body-font-size, .875rem));color:var(--fk-data-table-loading-color, var(--fk-color-text-muted, #6b7a8d))}.fk-data-table__resize-handle{position:absolute;top:0;right:0;bottom:0;width:var(--fk-data-table-resize-handle-width, 4px);cursor:col-resize;background:transparent;z-index:1}.fk-data-table__resize-handle:hover,.fk-data-table__resize-handle:active{background:var(--fk-data-table-resize-handle-color, var(--fk-color-primary, #3b82f6))}.fk-data-table__th--pinned-start,.fk-data-table__td--pinned-start{position:sticky;left:var(--fk-data-table-pin-offset, 0);z-index:2;background:inherit}.fk-data-table__th--pinned-end,.fk-data-table__td--pinned-end{position:sticky;right:var(--fk-data-table-pin-offset, 0);z-index:2;background:inherit}.fk-data-table__selection-cell{flex:none;width:var(--fk-data-table-selection-cell-width, 2.75rem);min-width:var(--fk-data-table-selection-cell-width, 2.75rem);max-width:var(--fk-data-table-selection-cell-width, 2.75rem);text-align:center;cursor:default}.fk-data-table__selection-cell:hover{color:inherit}.fk-data-table__row--selected{background:var(--fk-data-table-row-selected-bg, var(--fk-color-primary-light, #eaf4ff))}.fk-data-table__row--selected:hover{background:var(--fk-data-table-row-selected-hover-bg, var(--fk-color-primary-light, #dbe9ff))}.fk-data-table__expand-cell{flex:none;width:var(--fk-data-table-expand-cell-width, 2.5rem);min-width:var(--fk-data-table-expand-cell-width, 2.5rem);max-width:var(--fk-data-table-expand-cell-width, 2.5rem);text-align:center;cursor:default;padding-inline:var(--fk-rhythm-1, .25rem)}.fk-data-table__expand-button{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:var(--fk-data-table-expand-button-size, 1.5rem);height:var(--fk-data-table-expand-button-size, 1.5rem);padding:0;background:transparent;border:1px solid transparent;border-radius:var(--fk-data-table-expand-button-radius, .25rem);color:var(--fk-data-table-expand-color, var(--fk-color-text-muted, #5d6b80));cursor:pointer;transition:background-color .15s ease,color .15s ease,transform .15s ease}.fk-data-table__expand-button:hover{background:var(--fk-data-table-expand-button-hover-bg, var(--fk-color-surface-muted, #f1f4f8));color:var(--fk-data-table-expand-color-hover, var(--fk-color-text, #1c2433))}.fk-data-table__expand-button:focus-visible{outline:2px solid var(--fk-data-table-expand-focus-ring, var(--fk-color-primary, #0066cc));outline-offset:2px}.fk-data-table__expand-button--expanded{color:var(--fk-data-table-expand-color-active, var(--fk-color-primary, #0066cc))}.fk-data-table__expand-button--expanded .fk-data-table__expand-chevron{transform:rotate(90deg)}.fk-data-table__expand-chevron{transition:transform .15s ease}.fk-data-table__row--expanded{background:var(--fk-data-table-row-expanded-bg, var(--fk-color-surface-muted, #f7f9fb))}.fk-data-table__detail-row{background:var(--fk-data-table-detail-row-bg, var(--fk-color-surface, #ffffff))}.fk-data-table__detail-cell{padding:var(--fk-data-table-detail-cell-padding, var(--fk-rhythm-4, 1rem));border-top:1px solid var(--fk-data-table-detail-border-color, var(--fk-color-border, #d9e2ee));border-bottom:1px solid var(--fk-data-table-detail-border-color, var(--fk-color-border, #d9e2ee))}.fk-data-table__checkbox{appearance:none;width:var(--fk-data-table-checkbox-size, 1rem);height:var(--fk-data-table-checkbox-size, 1rem);margin:0;border:1px solid var(--fk-data-table-checkbox-border-color, var(--fk-color-border, #d9e2ee));border-radius:var(--fk-data-table-checkbox-radius, .25rem);background:var(--fk-data-table-checkbox-bg, var(--fk-color-surface, #ffffff));cursor:pointer;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease;position:relative;vertical-align:middle}.fk-data-table__checkbox:focus-visible{outline:none;box-shadow:var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, .18))}.fk-data-table__checkbox:hover:not(:disabled){border-color:var(--fk-data-table-checkbox-hover-border-color, var(--fk-color-primary, #0a84ff))}.fk-data-table__checkbox:checked,.fk-data-table__checkbox:indeterminate{background:var(--fk-data-table-checkbox-checked-bg, var(--fk-color-primary, #0a84ff));border-color:var(--fk-data-table-checkbox-checked-border-color, var(--fk-color-primary, #0a84ff))}.fk-data-table__checkbox:checked:after{content:\"\";position:absolute;inset:0;background:var(--fk-data-table-checkbox-icon-color, var(--fk-white, #ffffff));mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat;-webkit-mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='currentColor' d='M6.5 11L3 7.5l1-1L6.5 9 12 3.5l1 1z'/></svg>\") center/75% 75% no-repeat}.fk-data-table__checkbox:indeterminate:after{content:\"\";position:absolute;inset:0;background:var(--fk-data-table-checkbox-icon-color, var(--fk-white, #ffffff));mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' rx='1' fill='currentColor'/></svg>\") center/75% 75% no-repeat;-webkit-mask:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect x='3' y='7' width='10' height='2' rx='1' fill='currentColor'/></svg>\") center/75% 75% no-repeat}.fk-data-table__checkbox:disabled{cursor:not-allowed;opacity:.5}.fk-data-table--resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fk-data-table--sticky-header .fk-data-table__head{position:sticky;top:0;z-index:1}.fk-data-table--sticky-header .fk-data-table__table .fk-data-table__th{position:sticky;top:0;z-index:1;background:var(--fk-data-table-head-bg, var(--fk-color-surface-muted, #f7f9fb))}\n"] }]
|
|
1232
|
+
}], ctorParameters: () => [], propDecorators: { className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], sortState: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortState", required: false }] }], filterState: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterState", required: false }] }], pageState: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageState", required: false }] }], rowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowSelection", required: false }] }], selectedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedKeys", required: false }] }], getRowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "getRowId", required: false }] }], selectionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionDisabled", required: false }] }], selectionColumnPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionColumnPosition", required: false }] }], selectionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionLabel", required: false }] }], rowDetailTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowDetailTemplate", required: false }] }], expansionColumnPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "expansionColumnPosition", required: false }] }], expandedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedKeys", required: false }] }], expandableRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandableRow", required: false }] }], expansionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expansionLabel", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], expansionChange: [{ type: i0.Output, args: ["expansionChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], virtualViewport: [{ type: i0.ViewChild, args: ['virtualViewport', { isSignal: true }] }], hostClass: [{
|
|
1233
|
+
type: HostBinding,
|
|
1234
|
+
args: ['class']
|
|
1235
|
+
}], hostId: [{
|
|
1236
|
+
type: HostBinding,
|
|
1237
|
+
args: ['attr.id']
|
|
1238
|
+
}] } });
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Single-expand wrapper for `<fk-data-table>`. Mirror of
|
|
1242
|
+
* `<fk-accordion-group>`: provides a coordinator scoped to itself, any
|
|
1243
|
+
* descendant data-table picks it up via DI and routes every expansion
|
|
1244
|
+
* toggle through it. The coordinator collapses the previously-active
|
|
1245
|
+
* row when a new one expands so only one detail panel is open.
|
|
1246
|
+
*
|
|
1247
|
+
* Wrap a data-table when you want exclusive expansion:
|
|
1248
|
+
*
|
|
1249
|
+
* ```html
|
|
1250
|
+
* <fk-data-table-group>
|
|
1251
|
+
* <fk-data-table [rowDetailTemplate]="detail" ... />
|
|
1252
|
+
* </fk-data-table-group>
|
|
1253
|
+
* ```
|
|
1254
|
+
*
|
|
1255
|
+
* Omit the wrapper for multi-expand (the default).
|
|
1256
|
+
*/
|
|
1257
|
+
class DataTableGroupComponent {
|
|
1258
|
+
className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
|
|
1259
|
+
classes = computed(() => {
|
|
1260
|
+
return ['fk-data-table-group', this.className()].filter(Boolean).join(' ');
|
|
1261
|
+
}, ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
1262
|
+
get hostClass() {
|
|
1263
|
+
return this.classes();
|
|
1264
|
+
}
|
|
1265
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1266
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: DataTableGroupComponent, isStandalone: true, selector: "fk-data-table-group", inputs: { className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, providers: [DataTableGroupCoordinator], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1267
|
+
}
|
|
1268
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DataTableGroupComponent, decorators: [{
|
|
1269
|
+
type: Component,
|
|
1270
|
+
args: [{
|
|
1271
|
+
selector: 'fk-data-table-group',
|
|
1272
|
+
standalone: true,
|
|
1273
|
+
imports: [],
|
|
1274
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1275
|
+
providers: [DataTableGroupCoordinator],
|
|
1276
|
+
template: `<ng-content />`,
|
|
1277
|
+
}]
|
|
1278
|
+
}], propDecorators: { className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hostClass: [{
|
|
1279
|
+
type: HostBinding,
|
|
1280
|
+
args: ['class']
|
|
1281
|
+
}] } });
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Generated bundle index. Do not edit.
|
|
1285
|
+
*/
|
|
1286
|
+
|
|
1287
|
+
export { ColumnResizeDirective, DEFAULT_TABLE_CONFIG, DataTableComponent, DataTableGroupComponent, DataTableGroupCoordinator, DataTableStackComponent, ResponsiveStrategyService, TableEngineService, VirtualizationEngineService };
|
|
1288
|
+
//# sourceMappingURL=frame-kit-ui-ng-ui-data-table.mjs.map
|