@ngneers/controls 0.0.1-pre5 → 0.0.1-pre7
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/fesm2022/ngneers-controls-accordion.mjs +33 -33
- package/fesm2022/ngneers-controls-accordion.mjs.map +1 -1
- package/fesm2022/ngneers-controls-api-ng.mjs +22 -19
- package/fesm2022/ngneers-controls-api-ng.mjs.map +1 -1
- package/fesm2022/ngneers-controls-api-resize.mjs +706 -0
- package/fesm2022/ngneers-controls-api-resize.mjs.map +1 -0
- package/fesm2022/ngneers-controls-avatar.mjs +13 -13
- package/fesm2022/ngneers-controls-avatar.mjs.map +1 -1
- package/fesm2022/ngneers-controls-base.mjs +89 -64
- package/fesm2022/ngneers-controls-base.mjs.map +1 -1
- package/fesm2022/ngneers-controls-breadcrumb.mjs +19 -19
- package/fesm2022/ngneers-controls-breadcrumb.mjs.map +1 -1
- package/fesm2022/ngneers-controls-button-group.mjs +8 -8
- package/fesm2022/ngneers-controls-button-group.mjs.map +1 -1
- package/fesm2022/ngneers-controls-button.mjs +15 -10
- package/fesm2022/ngneers-controls-button.mjs.map +1 -1
- package/fesm2022/{ngneers-controls-calendar-time-BEeJIqIX.mjs → ngneers-controls-calendar-time-CKAqRgOP.mjs} +9 -9
- package/fesm2022/{ngneers-controls-calendar-time-BEeJIqIX.mjs.map → ngneers-controls-calendar-time-CKAqRgOP.mjs.map} +1 -1
- package/fesm2022/ngneers-controls-calendar.mjs +56 -56
- package/fesm2022/ngneers-controls-calendar.mjs.map +1 -1
- package/fesm2022/ngneers-controls-checkbox.mjs +9 -9
- package/fesm2022/ngneers-controls-checkbox.mjs.map +1 -1
- package/fesm2022/ngneers-controls-chip.mjs +7 -7
- package/fesm2022/ngneers-controls-chip.mjs.map +1 -1
- package/fesm2022/ngneers-controls-default-icons.mjs +61 -0
- package/fesm2022/ngneers-controls-default-icons.mjs.map +1 -0
- package/fesm2022/ngneers-controls-defer.mjs +10 -10
- package/fesm2022/ngneers-controls-defer.mjs.map +1 -1
- package/fesm2022/ngneers-controls-dialog.mjs +38 -40
- package/fesm2022/ngneers-controls-dialog.mjs.map +1 -1
- package/fesm2022/ngneers-controls-directives.mjs +45 -39
- package/fesm2022/ngneers-controls-directives.mjs.map +1 -1
- package/fesm2022/ngneers-controls-drawer.mjs +29 -29
- package/fesm2022/ngneers-controls-drawer.mjs.map +1 -1
- package/fesm2022/ngneers-controls-edit-inplace.mjs +16 -16
- package/fesm2022/ngneers-controls-edit-inplace.mjs.map +1 -1
- package/fesm2022/ngneers-controls-filter.mjs +192 -44
- package/fesm2022/ngneers-controls-filter.mjs.map +1 -1
- package/fesm2022/{ngneers-controls-i18n-de-CdTPeBiR.mjs → ngneers-controls-i18n-de-CF0xTWEX.mjs} +9 -5
- package/fesm2022/ngneers-controls-i18n-de-CF0xTWEX.mjs.map +1 -0
- package/fesm2022/{ngneers-controls-i18n-en-Cfp2pOLY.mjs → ngneers-controls-i18n-en-8B7xAjH2.mjs} +9 -5
- package/fesm2022/ngneers-controls-i18n-en-8B7xAjH2.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n.mjs +5 -5
- package/fesm2022/ngneers-controls-i18n.mjs.map +1 -1
- package/fesm2022/ngneers-controls-icon.mjs +111 -47
- package/fesm2022/ngneers-controls-icon.mjs.map +1 -1
- package/fesm2022/ngneers-controls-inplace.mjs +18 -18
- package/fesm2022/ngneers-controls-inplace.mjs.map +1 -1
- package/fesm2022/ngneers-controls-input-field.mjs +16 -16
- package/fesm2022/ngneers-controls-input-field.mjs.map +1 -1
- package/fesm2022/ngneers-controls-input-mask.mjs +8 -8
- package/fesm2022/ngneers-controls-input-mask.mjs.map +1 -1
- package/fesm2022/ngneers-controls-input.mjs +6 -6
- package/fesm2022/ngneers-controls-input.mjs.map +1 -1
- package/fesm2022/ngneers-controls-item-view.mjs +33 -33
- package/fesm2022/ngneers-controls-item-view.mjs.map +1 -1
- package/fesm2022/ngneers-controls-list-box.mjs +31 -31
- package/fesm2022/ngneers-controls-list-box.mjs.map +1 -1
- package/fesm2022/ngneers-controls-menu.mjs +33 -33
- package/fesm2022/ngneers-controls-menu.mjs.map +1 -1
- package/fesm2022/ngneers-controls-message.mjs +4 -4
- package/fesm2022/ngneers-controls-message.mjs.map +1 -1
- package/fesm2022/ngneers-controls-paginator.mjs +13 -13
- package/fesm2022/ngneers-controls-paginator.mjs.map +1 -1
- package/fesm2022/ngneers-controls-popover.mjs +21 -16
- package/fesm2022/ngneers-controls-popover.mjs.map +1 -1
- package/fesm2022/ngneers-controls-progress.mjs +16 -16
- package/fesm2022/ngneers-controls-progress.mjs.map +1 -1
- package/fesm2022/ngneers-controls-scroll-shadow.mjs +54 -0
- package/fesm2022/ngneers-controls-scroll-shadow.mjs.map +1 -0
- package/fesm2022/ngneers-controls-scroller.mjs +74 -39
- package/fesm2022/ngneers-controls-scroller.mjs.map +1 -1
- package/fesm2022/ngneers-controls-select-button.mjs +7 -7
- package/fesm2022/ngneers-controls-select-button.mjs.map +1 -1
- package/fesm2022/ngneers-controls-select.mjs +48 -47
- package/fesm2022/ngneers-controls-select.mjs.map +1 -1
- package/fesm2022/ngneers-controls-slider.mjs +11 -11
- package/fesm2022/ngneers-controls-slider.mjs.map +1 -1
- package/fesm2022/ngneers-controls-spinner.mjs +7 -7
- package/fesm2022/ngneers-controls-spinner.mjs.map +1 -1
- package/fesm2022/ngneers-controls-splitter.mjs +141 -328
- package/fesm2022/ngneers-controls-splitter.mjs.map +1 -1
- package/fesm2022/ngneers-controls-switch.mjs +3 -3
- package/fesm2022/ngneers-controls-switch.mjs.map +1 -1
- package/fesm2022/ngneers-controls-table.mjs +1456 -82
- package/fesm2022/ngneers-controls-table.mjs.map +1 -1
- package/fesm2022/ngneers-controls-tabs.mjs +41 -40
- package/fesm2022/ngneers-controls-tabs.mjs.map +1 -1
- package/fesm2022/ngneers-controls-tag.mjs +4 -4
- package/fesm2022/ngneers-controls-tag.mjs.map +1 -1
- package/fesm2022/ngneers-controls-toast.mjs +19 -19
- package/fesm2022/ngneers-controls-toast.mjs.map +1 -1
- package/fesm2022/ngneers-controls-toggle-button.mjs +10 -10
- package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -1
- package/fesm2022/ngneers-controls-tooltip.mjs +74 -38
- package/fesm2022/ngneers-controls-tooltip.mjs.map +1 -1
- package/fesm2022/ngneers-controls-utils-ng.mjs +9 -9
- package/fesm2022/ngneers-controls-utils-ng.mjs.map +1 -1
- package/fesm2022/ngneers-controls-utils.mjs +1 -1
- package/fesm2022/ngneers-controls-utils.mjs.map +1 -1
- package/package.json +34 -12
- package/types/ngneers-controls-accordion.d.ts +6 -4
- package/types/ngneers-controls-api-ng.d.ts +7 -2
- package/types/ngneers-controls-api-resize.d.ts +318 -0
- package/types/ngneers-controls-api.d.ts +2 -2
- package/types/ngneers-controls-base.d.ts +8 -0
- package/types/ngneers-controls-breadcrumb.d.ts +6 -4
- package/types/ngneers-controls-button.d.ts +13 -8
- package/types/ngneers-controls-calendar.d.ts +5 -1
- package/types/ngneers-controls-checkbox.d.ts +6 -4
- package/types/ngneers-controls-chip.d.ts +3 -1
- package/types/ngneers-controls-default-icons.d.ts +5 -0
- package/types/ngneers-controls-directives.d.ts +7 -1
- package/types/ngneers-controls-drawer.d.ts +7 -1
- package/types/ngneers-controls-edit-inplace.d.ts +4 -0
- package/types/ngneers-controls-filter.d.ts +52 -3
- package/types/ngneers-controls-i18n.d.ts +4 -0
- package/types/ngneers-controls-icon.d.ts +23 -40
- package/types/ngneers-controls-input-field.d.ts +7 -1
- package/types/ngneers-controls-item-view.d.ts +4 -2
- package/types/ngneers-controls-list-box.d.ts +5 -1
- package/types/ngneers-controls-menu.d.ts +4 -2
- package/types/ngneers-controls-message.d.ts +3 -1
- package/types/ngneers-controls-paginator.d.ts +4 -0
- package/types/ngneers-controls-popover.d.ts +2 -1
- package/types/ngneers-controls-scroll-shadow.d.ts +16 -0
- package/types/ngneers-controls-scroller.d.ts +12 -2
- package/types/ngneers-controls-select.d.ts +11 -12
- package/types/ngneers-controls-spinner.d.ts +1 -1
- package/types/ngneers-controls-splitter.d.ts +29 -59
- package/types/ngneers-controls-table.d.ts +380 -24
- package/types/ngneers-controls-tabs.d.ts +4 -2
- package/types/ngneers-controls-tag.d.ts +3 -1
- package/types/ngneers-controls-toast.d.ts +5 -3
- package/types/ngneers-controls-toggle-button.d.ts +5 -3
- package/types/ngneers-controls-tooltip.d.ts +7 -1
- package/fesm2022/ngneers-controls-i18n-de-CdTPeBiR.mjs.map +0 -1
- package/fesm2022/ngneers-controls-i18n-en-Cfp2pOLY.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-calendar-z5xA9wTT.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-calendar-z5xA9wTT.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-check-DdAQ9k-T.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-check-DdAQ9k-T.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-chevron_down-HO-z-xOr.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-chevron_down-HO-z-xOr.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-chevron_left-CC5psi16.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-chevron_left-CC5psi16.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-chevron_right-BRxlzx4G.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-chevron_right-BRxlzx4G.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-chevron_up-cqhPO7g2.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-chevron_up-cqhPO7g2.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-ellipsis-DXIGA1sk.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-ellipsis-DXIGA1sk.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-filter-oU7E6Xgr.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-filter-oU7E6Xgr.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-filter_solid-CqIKuR4N.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-filter_solid-CqIKuR4N.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-magnifying_glass-T4Hzbkig.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-magnifying_glass-T4Hzbkig.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-minus-oGDEFO4E.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-minus-oGDEFO4E.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-sort-CRD4tafz.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-sort-CRD4tafz.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-sort_down-DiFB1P6R.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-sort_down-DiFB1P6R.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-sort_up-Bq_q3hIi.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-sort_up-Bq_q3hIi.mjs.map +0 -1
- package/fesm2022/ngneers-controls-icon-xmark-BZKZORiu.mjs +0 -4
- package/fesm2022/ngneers-controls-icon-xmark-BZKZORiu.mjs.map +0 -1
|
@@ -1,56 +1,215 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { contentChild, ChangeDetectionStrategy, Component, signal,
|
|
2
|
+
import { input, inject, ElementRef, effect, Directive, contentChild, ChangeDetectionStrategy, Component, signal, viewChild, model, booleanAttribute, computed, afterRenderEffect, afterNextRender, Renderer2, EnvironmentInjector, ApplicationRef, createComponent, NgModule, ViewContainerRef } from '@angular/core';
|
|
3
3
|
import { NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
import { executeMultiFilter } from '@ngneers/controls/api';
|
|
5
|
-
import { templateTypesFn,
|
|
5
|
+
import { injectThemeTemplate, templateTypesFn, elementSizeSignal, NgnTemplate, setComponentInput } from '@ngneers/controls/api/ng';
|
|
6
|
+
import { ResizeEngine, getResizeLimitInPx } from '@ngneers/controls/api/resize';
|
|
6
7
|
import { NgnBase, NgnPt, provideSelf, getNearestNgnInstance, getNearestNgnInstanceSig } from '@ngneers/controls/base';
|
|
8
|
+
import { NgnIcon } from '@ngneers/controls/icon';
|
|
7
9
|
import { NgnPaginator } from '@ngneers/controls/paginator';
|
|
8
|
-
import {
|
|
10
|
+
import { NgnScrollShadow } from '@ngneers/controls/scroll-shadow';
|
|
11
|
+
import { NgnScrollerItem, NgnScroller } from '@ngneers/controls/scroller';
|
|
9
12
|
import { tableControlTemplate } from '@ngneers/controls-themes/templates/table';
|
|
10
13
|
import { toggleClass, NgnError } from '@ngneers/controls/utils';
|
|
11
14
|
import { setInputSignalValue } from '@ngneers/controls/utils-ng';
|
|
15
|
+
import { NgnCheckbox } from '@ngneers/controls/checkbox';
|
|
12
16
|
import { NgnActionButton } from '@ngneers/controls/button';
|
|
13
17
|
import { NgnFilter } from '@ngneers/controls/filter';
|
|
14
|
-
|
|
18
|
+
|
|
19
|
+
class NgnTableGroupHeaderTr extends NgnScrollerItem {
|
|
20
|
+
ngnTableGroupHeaderTr = input.required(...(ngDevMode ? [{ debugName: "ngnTableGroupHeaderTr" }] : /* istanbul ignore next */ []));
|
|
21
|
+
ngnScrollerItem = input({}, ...(ngDevMode ? [{ debugName: "ngnScrollerItem" }] : /* istanbul ignore next */ []));
|
|
22
|
+
_element = inject((ElementRef));
|
|
23
|
+
theme = injectThemeTemplate(tableControlTemplate);
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
effect(() => {
|
|
27
|
+
const row = this.ngnTableGroupHeaderTr();
|
|
28
|
+
setInputSignalValue(this.ngnScrollerItem, row);
|
|
29
|
+
});
|
|
30
|
+
this.prepareDom();
|
|
31
|
+
}
|
|
32
|
+
prepareDom() {
|
|
33
|
+
toggleClass(this._element.nativeElement, this.theme.class('row'), true);
|
|
34
|
+
toggleClass(this._element.nativeElement, this.theme.class('group-header-row'), true);
|
|
35
|
+
}
|
|
36
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableGroupHeaderTr, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
37
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.10", type: NgnTableGroupHeaderTr, isStandalone: true, selector: "[ngnTableGroupHeaderTr]", inputs: { ngnTableGroupHeaderTr: { classPropertyName: "ngnTableGroupHeaderTr", publicName: "ngnTableGroupHeaderTr", isSignal: true, isRequired: true, transformFunction: null }, ngnScrollerItem: { classPropertyName: "ngnScrollerItem", publicName: "ngnScrollerItem", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-rowindex": "ngnTableGroupHeaderTr().index + 2", "style.--ngn-table-row-index": "ngnTableGroupHeaderTr().index + 2" } }, usesInheritance: true, ngImport: i0 });
|
|
38
|
+
}
|
|
39
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableGroupHeaderTr, decorators: [{
|
|
40
|
+
type: Directive,
|
|
41
|
+
args: [{
|
|
42
|
+
selector: '[ngnTableGroupHeaderTr]',
|
|
43
|
+
host: {
|
|
44
|
+
'[attr.aria-rowindex]': 'ngnTableGroupHeaderTr().index + 2',
|
|
45
|
+
'[style.--ngn-table-row-index]': 'ngnTableGroupHeaderTr().index + 2',
|
|
46
|
+
},
|
|
47
|
+
}]
|
|
48
|
+
}], ctorParameters: () => [], propDecorators: { ngnTableGroupHeaderTr: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableGroupHeaderTr", required: true }] }], ngnScrollerItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnScrollerItem", required: false }] }] } });
|
|
15
49
|
|
|
16
50
|
class NgnTableTemplates extends NgnBase {
|
|
17
|
-
_headerTemplate = contentChild('header', ...(ngDevMode ? [{ debugName: "_headerTemplate" }] : []));
|
|
18
|
-
_bodyTemplate = contentChild('body', ...(ngDevMode ? [{ debugName: "_bodyTemplate" }] : []));
|
|
51
|
+
_headerTemplate = contentChild('header', ...(ngDevMode ? [{ debugName: "_headerTemplate" }] : /* istanbul ignore next */ []));
|
|
52
|
+
_bodyTemplate = contentChild('body', ...(ngDevMode ? [{ debugName: "_bodyTemplate" }] : /* istanbul ignore next */ []));
|
|
53
|
+
_groupHeaderTemplate = contentChild('groupHeader', ...(ngDevMode ? [{ debugName: "_groupHeaderTemplate" }] : /* istanbul ignore next */ []));
|
|
19
54
|
headerTemplate = this._headerTemplate;
|
|
20
55
|
bodyTemplate = this._bodyTemplate;
|
|
56
|
+
groupHeaderTemplate = this._groupHeaderTemplate;
|
|
21
57
|
/**
|
|
22
|
-
* Types for the
|
|
58
|
+
* Types for the table templates.
|
|
23
59
|
*/
|
|
24
60
|
templateTypes = templateTypesFn();
|
|
25
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
26
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.
|
|
61
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableTemplates, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
62
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.10", type: NgnTableTemplates, isStandalone: true, selector: "ng-component", queries: [{ propertyName: "_headerTemplate", first: true, predicate: ["header"], descendants: true, isSignal: true }, { propertyName: "_bodyTemplate", first: true, predicate: ["body"], descendants: true, isSignal: true }, { propertyName: "_groupHeaderTemplate", first: true, predicate: ["groupHeader"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
27
63
|
}
|
|
28
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
64
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableTemplates, decorators: [{
|
|
29
65
|
type: Component,
|
|
30
66
|
args: [{
|
|
31
67
|
template: '',
|
|
32
68
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
33
69
|
}]
|
|
34
|
-
}], propDecorators: { _headerTemplate: [{ type: i0.ContentChild, args: ['header', { isSignal: true }] }], _bodyTemplate: [{ type: i0.ContentChild, args: ['body', { isSignal: true }] }] } });
|
|
70
|
+
}], propDecorators: { _headerTemplate: [{ type: i0.ContentChild, args: ['header', { isSignal: true }] }], _bodyTemplate: [{ type: i0.ContentChild, args: ['body', { isSignal: true }] }], _groupHeaderTemplate: [{ type: i0.ContentChild, args: ['groupHeader', { isSignal: true }] }] } });
|
|
35
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Default minimum column width in pixels for table columns.
|
|
74
|
+
* Enforced as an absolute floor even when a relative min-size (e.g. `5%`) resolves smaller.
|
|
75
|
+
*/
|
|
76
|
+
const TABLE_MIN_COLUMN_WIDTH_PX = 50;
|
|
77
|
+
/** Splits a CSS grid-template-columns string into individual track values, handling minmax(). */
|
|
78
|
+
const GRID_TRACK_RE = /(?:minmax\([^)]+\)|[^\s]+)/g;
|
|
79
|
+
/**
|
|
80
|
+
* @category control
|
|
81
|
+
*/
|
|
36
82
|
class NgnTable extends NgnTableTemplates {
|
|
37
83
|
theme = this.injectThemeTemplate(tableControlTemplate, {
|
|
38
84
|
root: true,
|
|
39
85
|
virtual: () => this.virtual(),
|
|
86
|
+
resizing: () => this._resizeEngine?.isDragging() ?? false,
|
|
87
|
+
selectable: () => !!this.selectionMode(),
|
|
88
|
+
reorderable: () => this.reorderable(),
|
|
89
|
+
reordering: () => this._isReordering(),
|
|
40
90
|
});
|
|
41
|
-
_registeredHeaderCells = signal([], ...(ngDevMode ? [{ debugName: "_registeredHeaderCells" }] : []));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
91
|
+
_registeredHeaderCells = signal([], ...(ngDevMode ? [{ debugName: "_registeredHeaderCells" }] : /* istanbul ignore next */ []));
|
|
92
|
+
_stickyColumns = signal(new Map(), ...(ngDevMode ? [{ debugName: "_stickyColumns" }] : /* istanbul ignore next */ []));
|
|
93
|
+
_scroller = viewChild.required(NgnScroller);
|
|
94
|
+
rows = input.required(...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
95
|
+
rowHeight = input(...(ngDevMode ? [undefined, { debugName: "rowHeight" }] : /* istanbul ignore next */ []));
|
|
96
|
+
fieldId = input.required(...(ngDevMode ? [{ debugName: "fieldId" }] : /* istanbul ignore next */ []));
|
|
97
|
+
virtual = input(false, ...(ngDevMode ? [{ debugName: "virtual" }] : /* istanbul ignore next */ []));
|
|
98
|
+
virtualPadding = input(2, ...(ngDevMode ? [{ debugName: "virtualPadding" }] : /* istanbul ignore next */ []));
|
|
99
|
+
striped = input(false, ...(ngDevMode ? [{ debugName: "striped" }] : /* istanbul ignore next */ []));
|
|
100
|
+
paginator = input(false, ...(ngDevMode ? [{ debugName: "paginator" }] : /* istanbul ignore next */ []));
|
|
101
|
+
/**
|
|
102
|
+
* The selection mode for the table.
|
|
103
|
+
* - `'single'`: Only one row can be selected at a time.
|
|
104
|
+
* - `'multi'`: Multiple rows can be selected (with Ctrl/Shift click or checkboxes).
|
|
105
|
+
* - `null`: Selection is disabled.
|
|
106
|
+
* @default null
|
|
107
|
+
*/
|
|
108
|
+
selectionMode = input(null, ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
|
|
109
|
+
/**
|
|
110
|
+
* The selected row IDs. Two-way bindable via `[(selection)]`.
|
|
111
|
+
* @default []
|
|
112
|
+
*/
|
|
113
|
+
selection = model([], ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
114
|
+
/**
|
|
115
|
+
* Whether a selection column directive is present in the template.
|
|
116
|
+
* Set automatically by `NgnTableSelectionColumn` — do not set manually.
|
|
117
|
+
*/
|
|
118
|
+
_hasSelectionColumn = signal(false, ...(ngDevMode ? [{ debugName: "_hasSelectionColumn" }] : /* istanbul ignore next */ []));
|
|
119
|
+
/**
|
|
120
|
+
* Column key to group rows by. Rows with the same value in this column
|
|
121
|
+
* are collected under a collapsible group header.
|
|
122
|
+
* @default null (no grouping)
|
|
123
|
+
*/
|
|
124
|
+
groupBy = input(null, ...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
|
|
125
|
+
/**
|
|
126
|
+
* Two-way model tracking which groups are currently expanded, identified
|
|
127
|
+
* by their column value. Defaults to `[]` (all collapsed).
|
|
128
|
+
* Pass all group values to start with all groups expanded.
|
|
129
|
+
*
|
|
130
|
+
* Type-safe: when `groupBy` is set, this is typed as `T[G][]` where G
|
|
131
|
+
* is the groupBy column key (inferred by Angular's template type checker).
|
|
132
|
+
*/
|
|
133
|
+
expandedGroups = model([], ...(ngDevMode ? [{ debugName: "expandedGroups" }] : /* istanbul ignore next */ []));
|
|
134
|
+
/**
|
|
135
|
+
* Whether column resizing is enabled.
|
|
136
|
+
* @default false
|
|
137
|
+
*/
|
|
138
|
+
resizable = input(false, { ...(ngDevMode ? { debugName: "resizable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
139
|
+
/**
|
|
140
|
+
* The resize distribution mode.
|
|
141
|
+
* - `'adjacent'`: Only columns adjacent to the resize handle change. Total width stays the same.
|
|
142
|
+
* - `'push'`: Column grows/shrinks independently, total width changes (enables horizontal scrolling).
|
|
143
|
+
* @default 'adjacent'
|
|
144
|
+
*/
|
|
145
|
+
resizeMode = input('adjacent', ...(ngDevMode ? [{ debugName: "resizeMode" }] : /* istanbul ignore next */ []));
|
|
146
|
+
/**
|
|
147
|
+
* Whether to lock affected columns to fixed `px` values after a resize completes.
|
|
148
|
+
*
|
|
149
|
+
* - **`proportional` mode**: when `true`, the resized column stays as `px` (locked);
|
|
150
|
+
* when `false`, it is converted back to `fr` and continues to flex with the container.
|
|
151
|
+
* - **`adjacent` / `push` modes**: when `true`, columns whose size changed are converted
|
|
152
|
+
* to `px`; when `false`, columns keep their original units.
|
|
153
|
+
*
|
|
154
|
+
* @default false
|
|
155
|
+
*/
|
|
156
|
+
lockSizes = input(false, ...(ngDevMode ? [{ debugName: "lockSizes" }] : /* istanbul ignore next */ []));
|
|
157
|
+
/**
|
|
158
|
+
* Whether column reordering is enabled.
|
|
159
|
+
* @default false
|
|
160
|
+
*/
|
|
161
|
+
reorderable = input(false, { ...(ngDevMode ? { debugName: "reorderable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
162
|
+
/**
|
|
163
|
+
* The current column order as an array of column identifiers.
|
|
164
|
+
* When empty, the natural registration order (DOM order) is used.
|
|
165
|
+
* Supports two-way binding for persistence.
|
|
166
|
+
*/
|
|
167
|
+
columnOrder = model([], ...(ngDevMode ? [{ debugName: "columnOrder" }] : /* istanbul ignore next */ []));
|
|
49
168
|
trackById = (item) => item[this.fieldId()];
|
|
50
|
-
sort = model(null, ...(ngDevMode ? [{ debugName: "sort" }] : []));
|
|
51
|
-
filters = model(null, ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
52
|
-
pageState = signal(null, ...(ngDevMode ? [{ debugName: "pageState" }] : []));
|
|
169
|
+
sort = model(null, ...(ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []));
|
|
170
|
+
filters = model(null, ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
|
|
171
|
+
pageState = signal(null, ...(ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []));
|
|
172
|
+
// --- Selection state ---
|
|
173
|
+
showCheckboxes = this._hasSelectionColumn.asReadonly();
|
|
174
|
+
/**
|
|
175
|
+
* Set of selected row IDs for O(1) lookup.
|
|
176
|
+
* Uses a content-aware equality check to avoid invalidating dependents
|
|
177
|
+
* (50+ per-row computeds) when the selection hasn't actually changed.
|
|
178
|
+
*/
|
|
179
|
+
selectionSet = computed(() => new Set(this.selection()), { ...(ngDevMode ? { debugName: "selectionSet" } : /* istanbul ignore next */ {}), equal: (a, b) => a.size === b.size && [...a].every(v => b.has(v)) });
|
|
180
|
+
/**
|
|
181
|
+
* Anchor index for Shift+click range selection (index in formattedRows).
|
|
182
|
+
*/
|
|
183
|
+
_selectionAnchor = signal(null, ...(ngDevMode ? [{ debugName: "_selectionAnchor" }] : /* istanbul ignore next */ []));
|
|
184
|
+
/**
|
|
185
|
+
* Currently focused row index for keyboard navigation (index in formattedRows).
|
|
186
|
+
*/
|
|
187
|
+
focusedRowIndex = signal(null, ...(ngDevMode ? [{ debugName: "focusedRowIndex" }] : /* istanbul ignore next */ []));
|
|
188
|
+
isAllSelected = computed(() => {
|
|
189
|
+
const rows = this.formattedRows();
|
|
190
|
+
const selSet = this.selectionSet();
|
|
191
|
+
return rows.length > 0 && rows.every(row => selSet.has(row.id));
|
|
192
|
+
}, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : /* istanbul ignore next */ []));
|
|
193
|
+
isIndeterminate = computed(() => {
|
|
194
|
+
const selSet = this.selectionSet();
|
|
195
|
+
return selSet.size > 0 && !this.isAllSelected();
|
|
196
|
+
}, ...(ngDevMode ? [{ debugName: "isIndeterminate" }] : /* istanbul ignore next */ []));
|
|
197
|
+
headerCheckboxValue = computed(() => {
|
|
198
|
+
if (this.isAllSelected())
|
|
199
|
+
return true;
|
|
200
|
+
if (this.isIndeterminate())
|
|
201
|
+
return null;
|
|
202
|
+
return false;
|
|
203
|
+
}, ...(ngDevMode ? [{ debugName: "headerCheckboxValue" }] : /* istanbul ignore next */ []));
|
|
204
|
+
/**
|
|
205
|
+
* Total column count including the selection checkbox column.
|
|
206
|
+
*/
|
|
207
|
+
totalColumnCount = computed(() => this.columnCount() + (this.showCheckboxes() ? 1 : 0), ...(ngDevMode ? [{ debugName: "totalColumnCount" }] : /* istanbul ignore next */ []));
|
|
53
208
|
formattedRows = computed(() => {
|
|
209
|
+
const groupBy = this.groupBy();
|
|
210
|
+
if (groupBy) {
|
|
211
|
+
return this._groupedRows();
|
|
212
|
+
}
|
|
54
213
|
const rows = this._sortedRows();
|
|
55
214
|
return rows.map((data, index) => ({
|
|
56
215
|
kind: 'data',
|
|
@@ -58,7 +217,51 @@ class NgnTable extends NgnTableTemplates {
|
|
|
58
217
|
data,
|
|
59
218
|
index,
|
|
60
219
|
}));
|
|
61
|
-
}, ...(ngDevMode ? [{ debugName: "formattedRows" }] : []));
|
|
220
|
+
}, ...(ngDevMode ? [{ debugName: "formattedRows" }] : /* istanbul ignore next */ []));
|
|
221
|
+
_groupedRows = computed(() => {
|
|
222
|
+
const groupBy = this.groupBy();
|
|
223
|
+
if (!groupBy)
|
|
224
|
+
return [];
|
|
225
|
+
const rows = this._sortedRows();
|
|
226
|
+
const expandedSet = new Set(this.expandedGroups());
|
|
227
|
+
const fieldId = this.fieldId();
|
|
228
|
+
const groupMap = new Map();
|
|
229
|
+
for (const row of rows) {
|
|
230
|
+
const key = row[groupBy];
|
|
231
|
+
let group = groupMap.get(key);
|
|
232
|
+
if (!group) {
|
|
233
|
+
group = [];
|
|
234
|
+
groupMap.set(key, group);
|
|
235
|
+
}
|
|
236
|
+
group.push(row);
|
|
237
|
+
}
|
|
238
|
+
// Build flat list with group headers interleaved
|
|
239
|
+
const result = [];
|
|
240
|
+
let index = 0;
|
|
241
|
+
for (const [groupKey, groupRows] of groupMap) {
|
|
242
|
+
const expanded = expandedSet.has(groupKey);
|
|
243
|
+
result.push({
|
|
244
|
+
kind: 'group-header',
|
|
245
|
+
id: `group-${String(groupKey)}`,
|
|
246
|
+
groupKey,
|
|
247
|
+
groupValue: groupKey,
|
|
248
|
+
count: groupRows.length,
|
|
249
|
+
expanded,
|
|
250
|
+
index: index++,
|
|
251
|
+
});
|
|
252
|
+
if (expanded) {
|
|
253
|
+
for (const data of groupRows) {
|
|
254
|
+
result.push({
|
|
255
|
+
kind: 'data',
|
|
256
|
+
id: data[fieldId],
|
|
257
|
+
data,
|
|
258
|
+
index: index++,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}, ...(ngDevMode ? [{ debugName: "_groupedRows" }] : /* istanbul ignore next */ []));
|
|
62
265
|
pagedRows = computed(() => {
|
|
63
266
|
const paginator = this.paginator();
|
|
64
267
|
if (!paginator) {
|
|
@@ -69,7 +272,7 @@ class NgnTable extends NgnTableTemplates {
|
|
|
69
272
|
return [];
|
|
70
273
|
}
|
|
71
274
|
return this.formattedRows().slice(pageState.slice.skip, pageState.slice.take + pageState.slice.skip);
|
|
72
|
-
}, ...(ngDevMode ? [{ debugName: "pagedRows" }] : []));
|
|
275
|
+
}, ...(ngDevMode ? [{ debugName: "pagedRows" }] : /* istanbul ignore next */ []));
|
|
73
276
|
_filteredRows = computed(() => {
|
|
74
277
|
const filters = this.filters();
|
|
75
278
|
const rows = this.rows();
|
|
@@ -77,7 +280,7 @@ class NgnTable extends NgnTableTemplates {
|
|
|
77
280
|
return rows;
|
|
78
281
|
}
|
|
79
282
|
return executeMultiFilter(rows, filters);
|
|
80
|
-
}, ...(ngDevMode ? [{ debugName: "_filteredRows" }] : []));
|
|
283
|
+
}, ...(ngDevMode ? [{ debugName: "_filteredRows" }] : /* istanbul ignore next */ []));
|
|
81
284
|
_sortedRows = computed(() => {
|
|
82
285
|
const rows = this._filteredRows();
|
|
83
286
|
const sort = this.sort();
|
|
@@ -106,69 +309,798 @@ class NgnTable extends NgnTableTemplates {
|
|
|
106
309
|
? String(aValue).localeCompare(String(bValue))
|
|
107
310
|
: String(bValue).localeCompare(String(aValue));
|
|
108
311
|
});
|
|
109
|
-
}, ...(ngDevMode ? [{ debugName: "_sortedRows" }] : []));
|
|
110
|
-
columnCount = computed(() => this._registeredHeaderCells().length, ...(ngDevMode ? [{ debugName: "columnCount" }] : []));
|
|
312
|
+
}, ...(ngDevMode ? [{ debugName: "_sortedRows" }] : /* istanbul ignore next */ []));
|
|
313
|
+
columnCount = computed(() => this._registeredHeaderCells().length, ...(ngDevMode ? [{ debugName: "columnCount" }] : /* istanbul ignore next */ []));
|
|
314
|
+
// --- Column reorder state ---
|
|
315
|
+
_isReordering = signal(false, ...(ngDevMode ? [{ debugName: "_isReordering" }] : /* istanbul ignore next */ []));
|
|
316
|
+
_reorderSourceColumnId = signal(null, ...(ngDevMode ? [{ debugName: "_reorderSourceColumnId" }] : /* istanbul ignore next */ []));
|
|
317
|
+
_dropIndicatorState = signal(null, ...(ngDevMode ? [{ debugName: "_dropIndicatorState" }] : /* istanbul ignore next */ []));
|
|
318
|
+
_reorderTargetIndex = -1;
|
|
319
|
+
/**
|
|
320
|
+
* Effective column order: merges user-provided `columnOrder` with registered header cells.
|
|
321
|
+
* If `columnOrder` is empty, falls back to registration order.
|
|
322
|
+
* Unknown keys are filtered; new columns not in the order are appended.
|
|
323
|
+
*/
|
|
324
|
+
_effectiveColumnOrder = computed(() => {
|
|
325
|
+
const cells = this._registeredHeaderCells();
|
|
326
|
+
const cellIds = cells.map(c => c.ngnTableTh());
|
|
327
|
+
const userOrder = this.columnOrder();
|
|
328
|
+
if (!userOrder.length) {
|
|
329
|
+
return cellIds;
|
|
330
|
+
}
|
|
331
|
+
// Filter to valid keys and append any new columns not in the order
|
|
332
|
+
const validOrder = userOrder.filter(id => cellIds.includes(id));
|
|
333
|
+
const missing = cellIds.filter(id => !validOrder.includes(id));
|
|
334
|
+
return [...validOrder, ...missing];
|
|
335
|
+
}, ...(ngDevMode ? [{ debugName: "_effectiveColumnOrder" }] : /* istanbul ignore next */ []));
|
|
336
|
+
_stickyStartColumns = computed(() => {
|
|
337
|
+
const stickyMap = this._stickyColumns();
|
|
338
|
+
const order = this._effectiveColumnOrder();
|
|
339
|
+
const contiguous = [];
|
|
340
|
+
for (const id of order) {
|
|
341
|
+
if (stickyMap.get(id) === 'start') {
|
|
342
|
+
contiguous.push(id);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return contiguous;
|
|
349
|
+
}, ...(ngDevMode ? [{ debugName: "_stickyStartColumns" }] : /* istanbul ignore next */ []));
|
|
350
|
+
_stickyEndColumns = computed(() => {
|
|
351
|
+
const stickyMap = this._stickyColumns();
|
|
352
|
+
const order = this._effectiveColumnOrder();
|
|
353
|
+
const contiguous = [];
|
|
354
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
355
|
+
if (stickyMap.get(order[i]) === 'end') {
|
|
356
|
+
contiguous.unshift(order[i]);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return contiguous;
|
|
363
|
+
}, ...(ngDevMode ? [{ debugName: "_stickyEndColumns" }] : /* istanbul ignore next */ []));
|
|
364
|
+
/**
|
|
365
|
+
* Maps column ID → 1-based visual position.
|
|
366
|
+
*/
|
|
367
|
+
columnOrderMap = computed(() => {
|
|
368
|
+
const order = this._effectiveColumnOrder();
|
|
369
|
+
const map = new Map();
|
|
370
|
+
for (let i = 0; i < order.length; i++) {
|
|
371
|
+
map.set(order[i], i + 1);
|
|
372
|
+
}
|
|
373
|
+
return map;
|
|
374
|
+
}, ...(ngDevMode ? [{ debugName: "columnOrderMap" }] : /* istanbul ignore next */ []));
|
|
375
|
+
/**
|
|
376
|
+
* Returns the 1-based visual column index for a given 0-based logical (DOM) index.
|
|
377
|
+
*/
|
|
378
|
+
getVisualColumnIndex(logicalIndex) {
|
|
379
|
+
const cells = this._registeredHeaderCells();
|
|
380
|
+
const cell = cells[logicalIndex];
|
|
381
|
+
if (!cell)
|
|
382
|
+
return logicalIndex + 1;
|
|
383
|
+
return this.columnOrderMap().get(cell.ngnTableTh()) ?? logicalIndex + 1;
|
|
384
|
+
}
|
|
385
|
+
// --- Resize engine ---
|
|
386
|
+
_tableElementSize = elementSizeSignal(this.element);
|
|
387
|
+
_resizeEngine;
|
|
388
|
+
/**
|
|
389
|
+
* Grid template columns computed from the resize engine when resizable,
|
|
390
|
+
* otherwise falls back to the standard equal-width repeat.
|
|
391
|
+
*/
|
|
392
|
+
gridTemplateColumns;
|
|
393
|
+
constructor() {
|
|
394
|
+
super();
|
|
395
|
+
// Create engine eagerly in constructor (outside reactive context) to avoid NG0602
|
|
396
|
+
this._resizeEngine = new ResizeEngine({
|
|
397
|
+
items: computed(() => this._registeredHeaderCells()),
|
|
398
|
+
containerSize: computed(() => {
|
|
399
|
+
// Subscribe to the size signal for reactivity, but use clientWidth
|
|
400
|
+
// to exclude vertical scrollbar width and avoid horizontal overflow.
|
|
401
|
+
this._tableElementSize();
|
|
402
|
+
const tableEl = this.element.nativeElement.querySelector('table');
|
|
403
|
+
const total = tableEl?.clientWidth ?? 0;
|
|
404
|
+
if (!this._hasSelectionColumn())
|
|
405
|
+
return total;
|
|
406
|
+
// Subtract the selection column width so the resize engine only distributes
|
|
407
|
+
// the remaining space among data columns.
|
|
408
|
+
const selCol = this.element.nativeElement.querySelector(`.${this.theme.class('selection-column')}`);
|
|
409
|
+
return total - (selCol?.getBoundingClientRect().width ?? 0);
|
|
410
|
+
}),
|
|
411
|
+
gapSizes: signal([]),
|
|
412
|
+
distributionMode: computed(() => this.resizeMode()),
|
|
413
|
+
containerConstrained: computed(() => this.resizeMode() !== 'push'),
|
|
414
|
+
lockSizes: computed(() => this.lockSizes()),
|
|
415
|
+
minItemSizePx: TABLE_MIN_COLUMN_WIDTH_PX,
|
|
416
|
+
resolveItemSizes: () => this._registeredHeaderCells().map(cell => cell.element.nativeElement.getBoundingClientRect().width),
|
|
417
|
+
});
|
|
418
|
+
this.gridTemplateColumns = computed(() => {
|
|
419
|
+
const checkboxCol = this._hasSelectionColumn() ? 'min-content ' : '';
|
|
420
|
+
if (this.resizable()) {
|
|
421
|
+
const rawSizes = this._resizeEngine.gridTemplateSizes();
|
|
422
|
+
// When reorderable, permute grid track sizes to match visual column order
|
|
423
|
+
if (this.reorderable()) {
|
|
424
|
+
return `${checkboxCol}${this._permuteGridSizes(rawSizes)}`;
|
|
425
|
+
}
|
|
426
|
+
return `${checkboxCol}${rawSizes}`;
|
|
427
|
+
}
|
|
428
|
+
return `${checkboxCol}repeat(${this.columnCount()}, 1fr)`;
|
|
429
|
+
}, ...(ngDevMode ? [{ debugName: "gridTemplateColumns" }] : /* istanbul ignore next */ []));
|
|
430
|
+
afterRenderEffect(() => {
|
|
431
|
+
const startCols = this._stickyStartColumns();
|
|
432
|
+
const endCols = this._stickyEndColumns();
|
|
433
|
+
if (startCols.length === 0 && endCols.length === 0)
|
|
434
|
+
return;
|
|
435
|
+
if (this._resizeEngine.isDragging())
|
|
436
|
+
return;
|
|
437
|
+
this._tableElementSize();
|
|
438
|
+
const tableEl = this.element.nativeElement.querySelector(':scope > table');
|
|
439
|
+
if (!tableEl)
|
|
440
|
+
return;
|
|
441
|
+
const cells = this._registeredHeaderCells();
|
|
442
|
+
let selectionWidth = 0;
|
|
443
|
+
if (this._hasSelectionColumn()) {
|
|
444
|
+
const selCol = tableEl.querySelector(`.${this.theme.class('selection-column')}`);
|
|
445
|
+
selectionWidth = selCol?.getBoundingClientRect().width ?? 0;
|
|
446
|
+
}
|
|
447
|
+
let cumulativeLeft = selectionWidth;
|
|
448
|
+
for (let i = 0; i < startCols.length; i++) {
|
|
449
|
+
tableEl.style.setProperty(`--ngn-sticky-start-offset-${i}`, `${cumulativeLeft}px`);
|
|
450
|
+
const cell = cells.find(c => c.ngnTableTh() === startCols[i]);
|
|
451
|
+
cumulativeLeft += cell?.element.nativeElement.getBoundingClientRect().width ?? 0;
|
|
452
|
+
}
|
|
453
|
+
let cumulativeRight = 0;
|
|
454
|
+
for (let i = endCols.length - 1; i >= 0; i--) {
|
|
455
|
+
tableEl.style.setProperty(`--ngn-sticky-end-offset-${i}`, `${cumulativeRight}px`);
|
|
456
|
+
const cell = cells.find(c => c.ngnTableTh() === endCols[i]);
|
|
457
|
+
cumulativeRight += cell?.element.nativeElement.getBoundingClientRect().width ?? 0;
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
111
461
|
pageChanged(event) {
|
|
112
462
|
this.pageState.set(event);
|
|
113
463
|
}
|
|
114
464
|
column(column) {
|
|
115
465
|
return column;
|
|
116
466
|
}
|
|
467
|
+
registerSelectionColumn() {
|
|
468
|
+
this._hasSelectionColumn.set(true);
|
|
469
|
+
}
|
|
470
|
+
unregisterSelectionColumn() {
|
|
471
|
+
this._hasSelectionColumn.set(false);
|
|
472
|
+
}
|
|
117
473
|
registerHeaderCell(cell) {
|
|
118
474
|
this._registeredHeaderCells.update(cells => [...cells, cell]);
|
|
119
475
|
}
|
|
120
476
|
unregisterHeaderCell(cell) {
|
|
121
477
|
this._registeredHeaderCells.update(cells => cells.filter(c => c !== cell));
|
|
122
478
|
}
|
|
123
|
-
|
|
124
|
-
|
|
479
|
+
getRegisteredHeaderCells() {
|
|
480
|
+
return this._registeredHeaderCells();
|
|
481
|
+
}
|
|
482
|
+
registerStickyColumn(columnId, side) {
|
|
483
|
+
this._stickyColumns.update(map => {
|
|
484
|
+
const next = new Map(map);
|
|
485
|
+
next.set(columnId, side);
|
|
486
|
+
return next;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
unregisterStickyColumn(columnId) {
|
|
490
|
+
this._stickyColumns.update(map => {
|
|
491
|
+
const next = new Map(map);
|
|
492
|
+
next.delete(columnId);
|
|
493
|
+
return next;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
getStickyInfo(columnId) {
|
|
497
|
+
const startCols = this._stickyStartColumns();
|
|
498
|
+
const endCols = this._stickyEndColumns();
|
|
499
|
+
const startIndex = startCols.indexOf(columnId);
|
|
500
|
+
if (startIndex >= 0) {
|
|
501
|
+
return { side: 'start', index: startIndex, isEdge: startIndex === startCols.length - 1 };
|
|
502
|
+
}
|
|
503
|
+
const endIndex = endCols.indexOf(columnId);
|
|
504
|
+
if (endIndex >= 0) {
|
|
505
|
+
return { side: 'end', index: endIndex, isEdge: endIndex === 0 };
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
// --- Resize operations (called by NgnTableTh) ---
|
|
510
|
+
startColumnResize(columnIndex, event) {
|
|
511
|
+
if (!this.resizable())
|
|
512
|
+
return;
|
|
513
|
+
this._resizeEngine.startDrag(columnIndex, event.clientX);
|
|
514
|
+
}
|
|
515
|
+
dragColumnResize(columnIndex, event) {
|
|
516
|
+
if (!this.resizable())
|
|
517
|
+
return;
|
|
518
|
+
this._resizeEngine.drag(columnIndex, event.clientX);
|
|
519
|
+
}
|
|
520
|
+
endColumnResize(columnIndex, cancel) {
|
|
521
|
+
if (!this.resizable())
|
|
522
|
+
return;
|
|
523
|
+
this._resizeEngine.endDrag(columnIndex, cancel);
|
|
524
|
+
}
|
|
525
|
+
// --- Selection operations ---
|
|
526
|
+
isRowSelected(id) {
|
|
527
|
+
return this.selectionSet().has(id);
|
|
528
|
+
}
|
|
529
|
+
handleRowClick(row, event) {
|
|
530
|
+
const mode = this.selectionMode();
|
|
531
|
+
if (!mode)
|
|
532
|
+
return;
|
|
533
|
+
const id = row.id;
|
|
534
|
+
const rowIndex = this.formattedRows().findIndex(r => r.id === row.id);
|
|
535
|
+
if (mode === 'single') {
|
|
536
|
+
this.selection.set([id]);
|
|
537
|
+
this._selectionAnchor.set(rowIndex);
|
|
538
|
+
this.focusedRowIndex.set(null);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
// Multi mode
|
|
542
|
+
if (event.shiftKey && this._selectionAnchor() !== null) {
|
|
543
|
+
this._selectRange(this._selectionAnchor(), rowIndex);
|
|
544
|
+
}
|
|
545
|
+
else if (event.ctrlKey || event.metaKey) {
|
|
546
|
+
this._toggleRowInSelection(id);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
this._toggleRowInSelection(id);
|
|
550
|
+
}
|
|
551
|
+
this._selectionAnchor.set(rowIndex);
|
|
552
|
+
this.focusedRowIndex.set(null);
|
|
553
|
+
}
|
|
554
|
+
handleCheckboxChange(row) {
|
|
555
|
+
const id = row.id;
|
|
556
|
+
this._toggleRowInSelection(id);
|
|
557
|
+
const rowIndex = this.formattedRows().findIndex(r => r.id === row.id);
|
|
558
|
+
this._selectionAnchor.set(rowIndex);
|
|
559
|
+
this.focusedRowIndex.set(null);
|
|
560
|
+
}
|
|
561
|
+
toggleSelectAll() {
|
|
562
|
+
if (this.isAllSelected()) {
|
|
563
|
+
this.selection.set([]);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
const dataRows = this.formattedRows().filter((r) => r.kind === 'data');
|
|
567
|
+
this.selection.set(dataRows.map(r => r.id));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
_toggleRowInSelection(id) {
|
|
571
|
+
const current = this.selection();
|
|
572
|
+
if (this.selectionSet().has(id)) {
|
|
573
|
+
this.selection.set(current.filter(v => v !== id));
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
this.selection.set([...current, id]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
_selectRange(fromIndex, toIndex) {
|
|
580
|
+
const rows = this.formattedRows();
|
|
581
|
+
const start = Math.min(fromIndex, toIndex);
|
|
582
|
+
const end = Math.max(fromIndex, toIndex);
|
|
583
|
+
const rangeIds = rows
|
|
584
|
+
.slice(start, end + 1)
|
|
585
|
+
.filter((r) => r.kind === 'data')
|
|
586
|
+
.map(r => r.id);
|
|
587
|
+
// Merge with existing selection (union)
|
|
588
|
+
const currentSet = new Set(this.selection());
|
|
589
|
+
for (const id of rangeIds) {
|
|
590
|
+
currentSet.add(id);
|
|
591
|
+
}
|
|
592
|
+
this.selection.set([...currentSet]);
|
|
593
|
+
}
|
|
594
|
+
// --- Keyboard navigation ---
|
|
595
|
+
onKeyDown(event) {
|
|
596
|
+
const mode = this.selectionMode();
|
|
597
|
+
if (!mode)
|
|
598
|
+
return;
|
|
599
|
+
const rows = this.formattedRows();
|
|
600
|
+
if (rows.length === 0)
|
|
601
|
+
return;
|
|
602
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
603
|
+
event.preventDefault();
|
|
604
|
+
event.stopPropagation();
|
|
605
|
+
// Start from current focus, or current selection in single mode, or -1
|
|
606
|
+
let currentIndex = this.focusedRowIndex();
|
|
607
|
+
if (currentIndex === null && mode === 'single') {
|
|
608
|
+
const sel = this.selection();
|
|
609
|
+
if (sel.length > 0) {
|
|
610
|
+
currentIndex = rows.findIndex(r => r.id === sel[0]);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
currentIndex ??= -1;
|
|
614
|
+
const nextIndex = event.key === 'ArrowDown'
|
|
615
|
+
? Math.min(currentIndex + 1, rows.length - 1)
|
|
616
|
+
: Math.max(currentIndex - 1, 0);
|
|
617
|
+
this.focusedRowIndex.set(nextIndex);
|
|
618
|
+
this._scroller().scrollToIndex(nextIndex);
|
|
619
|
+
if (mode === 'single') {
|
|
620
|
+
const id = rows[nextIndex].id;
|
|
621
|
+
this.selection.set([id]);
|
|
622
|
+
this._selectionAnchor.set(nextIndex);
|
|
623
|
+
}
|
|
624
|
+
else if (event.shiftKey) {
|
|
625
|
+
// Extend range selection with Shift+Arrow
|
|
626
|
+
const anchor = this._selectionAnchor() ?? nextIndex;
|
|
627
|
+
this._selectRange(anchor, nextIndex);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
else if (event.key === ' ' || event.key === 'Enter') {
|
|
631
|
+
if (mode === 'multi') {
|
|
632
|
+
const focusIdx = this.focusedRowIndex();
|
|
633
|
+
if (focusIdx !== null && rows[focusIdx]) {
|
|
634
|
+
event.preventDefault();
|
|
635
|
+
event.stopPropagation();
|
|
636
|
+
const id = rows[focusIdx].id;
|
|
637
|
+
this._toggleRowInSelection(id);
|
|
638
|
+
this._selectionAnchor.set(focusIdx);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// --- Row grouping ---
|
|
644
|
+
/**
|
|
645
|
+
* Toggle the expanded state of a group identified by its column value.
|
|
646
|
+
*/
|
|
647
|
+
toggleGroup(groupKey) {
|
|
648
|
+
const current = this.expandedGroups();
|
|
649
|
+
if (current.includes(groupKey)) {
|
|
650
|
+
this.expandedGroups.set(current.filter(k => k !== groupKey));
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
this.expandedGroups.set([...current, groupKey]);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Toggle a group from a group-header row. Used in the template where
|
|
658
|
+
* the scroller item type cannot carry the `G` generic.
|
|
659
|
+
* @internal
|
|
660
|
+
*/
|
|
661
|
+
toggleGroupFromRow(row) {
|
|
662
|
+
this.toggleGroup(row.groupKey);
|
|
663
|
+
}
|
|
664
|
+
// --- Reorder operations (called by NgnTableReorderableColumn) ---
|
|
665
|
+
getReorderBounds(columnId) {
|
|
666
|
+
const order = this._effectiveColumnOrder();
|
|
667
|
+
const startCols = this._stickyStartColumns();
|
|
668
|
+
const endCols = this._stickyEndColumns();
|
|
669
|
+
const side = this._stickyColumns().get(columnId);
|
|
670
|
+
if (side === 'start') {
|
|
671
|
+
return { min: 0, max: startCols.length };
|
|
672
|
+
}
|
|
673
|
+
else if (side === 'end') {
|
|
674
|
+
return { min: order.length - endCols.length, max: order.length };
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
return { min: startCols.length, max: order.length - endCols.length };
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
startColumnReorder(columnId) {
|
|
681
|
+
if (!this.reorderable())
|
|
682
|
+
return;
|
|
683
|
+
this._isReordering.set(true);
|
|
684
|
+
this._reorderSourceColumnId.set(columnId);
|
|
685
|
+
this._reorderTargetIndex = -1;
|
|
686
|
+
}
|
|
687
|
+
dragColumnReorder(event) {
|
|
688
|
+
if (!this.reorderable() || !this._isReordering())
|
|
689
|
+
return;
|
|
690
|
+
const cells = this._registeredHeaderCells();
|
|
691
|
+
const effectiveOrder = this._effectiveColumnOrder();
|
|
692
|
+
const sourceId = this._reorderSourceColumnId();
|
|
693
|
+
if (!sourceId)
|
|
694
|
+
return;
|
|
695
|
+
// Find drop target by comparing cursor X to header cell midpoints (in visual order)
|
|
696
|
+
const cursorX = event.clientX;
|
|
697
|
+
let targetIndex = effectiveOrder.length;
|
|
698
|
+
// Build visual-order cells with their bounding boxes
|
|
699
|
+
const visualCells = effectiveOrder
|
|
700
|
+
.map(id => cells.find(c => c.ngnTableTh() === id))
|
|
701
|
+
.filter((c) => !!c);
|
|
702
|
+
for (let i = 0; i < visualCells.length; i++) {
|
|
703
|
+
const rect = visualCells[i].element.nativeElement.getBoundingClientRect();
|
|
704
|
+
const midX = rect.left + rect.width / 2;
|
|
705
|
+
if (cursorX < midX) {
|
|
706
|
+
targetIndex = i;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const bounds = this.getReorderBounds(sourceId);
|
|
711
|
+
targetIndex = Math.max(bounds.min, Math.min(bounds.max, targetIndex));
|
|
712
|
+
this._reorderTargetIndex = targetIndex;
|
|
713
|
+
// Compute indicator position relative to the table element
|
|
714
|
+
const tableEl = this.element.nativeElement.querySelector(':scope > table');
|
|
715
|
+
if (!tableEl)
|
|
716
|
+
return;
|
|
717
|
+
const tableRect = tableEl.getBoundingClientRect();
|
|
718
|
+
// Use the first header cell's height for the indicator
|
|
719
|
+
const firstCellRect = visualCells[0]?.element.nativeElement.getBoundingClientRect();
|
|
720
|
+
const indicatorHeight = firstCellRect?.height ?? 40;
|
|
721
|
+
let indicatorLeftPx;
|
|
722
|
+
if (targetIndex === 0) {
|
|
723
|
+
const firstCell = visualCells[0];
|
|
724
|
+
if (!firstCell)
|
|
725
|
+
return;
|
|
726
|
+
const rect = firstCell.element.nativeElement.getBoundingClientRect();
|
|
727
|
+
indicatorLeftPx = rect.left - tableRect.left;
|
|
728
|
+
}
|
|
729
|
+
else if (targetIndex >= visualCells.length) {
|
|
730
|
+
const lastCell = visualCells[visualCells.length - 1];
|
|
731
|
+
if (!lastCell)
|
|
732
|
+
return;
|
|
733
|
+
const rect = lastCell.element.nativeElement.getBoundingClientRect();
|
|
734
|
+
indicatorLeftPx = rect.right - tableRect.left;
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
const prevCell = visualCells[targetIndex - 1];
|
|
738
|
+
const nextCell = visualCells[targetIndex];
|
|
739
|
+
const prevRect = prevCell.element.nativeElement.getBoundingClientRect();
|
|
740
|
+
const nextRect = nextCell.element.nativeElement.getBoundingClientRect();
|
|
741
|
+
indicatorLeftPx = (prevRect.right + nextRect.left) / 2 - tableRect.left;
|
|
742
|
+
}
|
|
743
|
+
// Clamp so the 3px-wide indicator stays within the visible content area
|
|
744
|
+
// (use clientWidth to exclude scrollbar width from the boundary)
|
|
745
|
+
const maxLeft = tableEl.clientWidth - 3;
|
|
746
|
+
indicatorLeftPx = Math.max(0, Math.min(indicatorLeftPx, maxLeft));
|
|
747
|
+
this._dropIndicatorState.set({
|
|
748
|
+
leftPx: indicatorLeftPx,
|
|
749
|
+
topPx: tableEl.scrollTop,
|
|
750
|
+
heightPx: indicatorHeight,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
endColumnReorder(cancel) {
|
|
754
|
+
if (!this.reorderable())
|
|
755
|
+
return;
|
|
756
|
+
if (!cancel && this._reorderTargetIndex >= 0) {
|
|
757
|
+
const sourceId = this._reorderSourceColumnId();
|
|
758
|
+
if (sourceId) {
|
|
759
|
+
const effectiveOrder = [...this._effectiveColumnOrder()];
|
|
760
|
+
const sourceIndex = effectiveOrder.indexOf(sourceId);
|
|
761
|
+
if (sourceIndex >= 0) {
|
|
762
|
+
// Remove source from its current position
|
|
763
|
+
effectiveOrder.splice(sourceIndex, 1);
|
|
764
|
+
// Adjust target index if source was before target
|
|
765
|
+
let insertAt = this._reorderTargetIndex;
|
|
766
|
+
if (sourceIndex < this._reorderTargetIndex) {
|
|
767
|
+
insertAt--;
|
|
768
|
+
}
|
|
769
|
+
// Insert at new position
|
|
770
|
+
effectiveOrder.splice(insertAt, 0, sourceId);
|
|
771
|
+
this.columnOrder.set(effectiveOrder);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
this._isReordering.set(false);
|
|
776
|
+
this._reorderSourceColumnId.set(null);
|
|
777
|
+
this._dropIndicatorState.set(null);
|
|
778
|
+
this._reorderTargetIndex = -1;
|
|
779
|
+
}
|
|
780
|
+
getReorderSourceColumnId() {
|
|
781
|
+
return this._reorderSourceColumnId();
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Permutes grid template sizes from logical (registration) order to visual (column order) order.
|
|
785
|
+
*/
|
|
786
|
+
_permuteGridSizes(rawSizes) {
|
|
787
|
+
const tracks = rawSizes.match(GRID_TRACK_RE) ?? [];
|
|
788
|
+
const cells = this._registeredHeaderCells();
|
|
789
|
+
const effectiveOrder = this._effectiveColumnOrder();
|
|
790
|
+
if (tracks.length !== cells.length) {
|
|
791
|
+
return rawSizes; // Mismatch — don't permute
|
|
792
|
+
}
|
|
793
|
+
// Build map: column ID → track size (logical order)
|
|
794
|
+
const sizeByColumnId = new Map();
|
|
795
|
+
for (let i = 0; i < cells.length; i++) {
|
|
796
|
+
sizeByColumnId.set(cells[i].ngnTableTh(), tracks[i]);
|
|
797
|
+
}
|
|
798
|
+
// Emit sizes in visual order
|
|
799
|
+
return effectiveOrder.map(id => sizeByColumnId.get(id) ?? '1fr').join(' ');
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Auto-sizes the column at `columnIndex` to fit its visible content.
|
|
803
|
+
* Uses `Canvas.measureText()` for fast, reflow-free text measurement since
|
|
804
|
+
* grid-constrained cells (`min-width: 0`) don't report natural content width
|
|
805
|
+
* via `scrollWidth`.
|
|
806
|
+
*/
|
|
807
|
+
autoSizeColumn(columnIndex) {
|
|
808
|
+
if (!this.resizable())
|
|
809
|
+
return;
|
|
810
|
+
const cells = this._registeredHeaderCells();
|
|
811
|
+
const headerCell = cells[columnIndex];
|
|
812
|
+
if (!headerCell)
|
|
813
|
+
return;
|
|
814
|
+
const tableEl = this.element.nativeElement.querySelector('table');
|
|
815
|
+
if (!tableEl)
|
|
816
|
+
return;
|
|
817
|
+
const headerEl = headerCell.element.nativeElement;
|
|
818
|
+
const ctx = autoSizeCanvasCtx();
|
|
819
|
+
// Measure header: text width + non-text siblings (icons, controls) + flex gap.
|
|
820
|
+
// The header cell is a flex container with: cell-text, spacer, filter/sort icons, resize handle.
|
|
821
|
+
const cellTextClass = this.theme.class('cell-text');
|
|
822
|
+
const headerTextEl = headerEl.querySelector(`.${cellTextClass}`);
|
|
823
|
+
let headerRequiredWidth = 0;
|
|
824
|
+
if (headerTextEl) {
|
|
825
|
+
ctx.font = getCssFont(headerEl);
|
|
826
|
+
const text = headerTextEl.textContent ?? '';
|
|
827
|
+
headerRequiredWidth = Math.ceil(ctx.measureText(text).width);
|
|
828
|
+
}
|
|
829
|
+
// Add width of visible non-text siblings (filter icon, sort icon, etc.)
|
|
830
|
+
const headerStyle = getComputedStyle(headerEl);
|
|
831
|
+
const headerGap = parseFloat(headerStyle.gap) || 0;
|
|
832
|
+
let visibleSiblingCount = 0;
|
|
833
|
+
for (let i = 0; i < headerEl.children.length; i++) {
|
|
834
|
+
const child = headerEl.children[i];
|
|
835
|
+
if (child === headerTextEl)
|
|
836
|
+
continue;
|
|
837
|
+
// Skip spacer (zero intrinsic width) and hidden resize handle
|
|
838
|
+
if (child.offsetWidth > 0 && getComputedStyle(child).flexGrow === '0') {
|
|
839
|
+
headerRequiredWidth += child.offsetWidth;
|
|
840
|
+
visibleSiblingCount++;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
// Add flex gaps between visible items
|
|
844
|
+
if (visibleSiblingCount > 0) {
|
|
845
|
+
headerRequiredWidth += headerGap * visibleSiblingCount;
|
|
846
|
+
}
|
|
847
|
+
let maxContentWidth = headerRequiredWidth;
|
|
848
|
+
// Measure visible body cells (nth-child is 1-indexed, offset by selection column)
|
|
849
|
+
const selectionOffset = this._hasSelectionColumn() ? 1 : 0;
|
|
850
|
+
const bodyCells = Array.from(tableEl.querySelectorAll(`tbody tr td:nth-child(${columnIndex + selectionOffset + 1})`));
|
|
851
|
+
if (bodyCells.length > 0) {
|
|
852
|
+
ctx.font = getCssFont(bodyCells[0]);
|
|
853
|
+
for (let i = 0; i < bodyCells.length; i++) {
|
|
854
|
+
const text = bodyCells[i].textContent ?? '';
|
|
855
|
+
maxContentWidth = Math.max(maxContentWidth, Math.ceil(ctx.measureText(text).width));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
// Add cell horizontal padding
|
|
859
|
+
const paddingLeft = parseFloat(headerStyle.paddingLeft) || 0;
|
|
860
|
+
const paddingRight = parseFloat(headerStyle.paddingRight) || 0;
|
|
861
|
+
const totalWidth = maxContentWidth + paddingLeft + paddingRight;
|
|
862
|
+
// Clamp to min/max constraints
|
|
863
|
+
const containerSize = this._tableElementSize().width;
|
|
864
|
+
const minPx = Math.max(TABLE_MIN_COLUMN_WIDTH_PX, getResizeLimitInPx(headerCell.minSize(), containerSize));
|
|
865
|
+
const maxPx = getResizeLimitInPx(headerCell.maxSize(), containerSize);
|
|
866
|
+
const clampedWidth = Math.max(minPx, Math.min(maxPx, totalWidth));
|
|
867
|
+
this._resizeEngine.setItemSize(headerCell, `${clampedWidth}px`);
|
|
868
|
+
this._resizeEngine.hasBeenResized.set(true);
|
|
869
|
+
}
|
|
870
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
871
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: NgnTable, isStandalone: true, selector: "ngn-table", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, fieldId: { classPropertyName: "fieldId", publicName: "fieldId", isSignal: true, isRequired: true, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, virtualPadding: { classPropertyName: "virtualPadding", publicName: "virtualPadding", isSignal: true, isRequired: false, transformFunction: null }, striped: { classPropertyName: "striped", publicName: "striped", isSignal: true, isRequired: false, transformFunction: null }, paginator: { classPropertyName: "paginator", publicName: "paginator", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, expandedGroups: { classPropertyName: "expandedGroups", publicName: "expandedGroups", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, resizeMode: { classPropertyName: "resizeMode", publicName: "resizeMode", isSignal: true, isRequired: false, transformFunction: null }, lockSizes: { classPropertyName: "lockSizes", publicName: "lockSizes", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, columnOrder: { classPropertyName: "columnOrder", publicName: "columnOrder", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", expandedGroups: "expandedGroupsChange", columnOrder: "columnOrderChange", sort: "sortChange", filters: "filtersChange" }, host: { attributes: { "tabindex": "0" }, listeners: { "keydown": "onKeyDown($event)" }, properties: { "attr.aria-multiselectable": "selectionMode() === \"multi\" ? true : null" } }, providers: [provideSelf(NgnTable)], viewQueries: [{ propertyName: "_scroller", first: true, predicate: NgnScroller, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<table\r\n #scrollContainer\r\n ngnScrollShadow=\"horizontal\"\r\n [ptInt]=\"this\"\r\n [ptClass]=\"{ table: true, striped: striped() }\"\r\n [attr.aria-rowcount]=\"formattedRows().length\"\r\n [style.--ngn-table-column-count]=\"totalColumnCount()\"\r\n [style.--ngn-table-selection-offset]=\"showCheckboxes() ? 1 : 0\"\r\n [style.grid-template-columns]=\"gridTemplateColumns()\"\r\n [style.--ngn-table-row-height.px]=\"rowHeight()\"\r\n>\r\n <thead [ptInt]=\"this\" [ptClass]=\"'head'\">\r\n <ng-container [ngTemplateOutlet]=\"headerTemplate()\"></ng-container>\r\n </thead>\r\n @if (_dropIndicatorState()) {\r\n <div\r\n [ptInt]=\"this\"\r\n [ptClass]=\"'drop-indicator'\"\r\n [style.left.px]=\"_dropIndicatorState()!.leftPx\"\r\n [style.top.px]=\"_dropIndicatorState()!.topPx\"\r\n [style.height.px]=\"_dropIndicatorState()!.heightPx\"\r\n ></div>\r\n }\r\n\r\n <tbody\r\n [ptInt]=\"this\"\r\n [ptClass]=\"'body'\"\r\n ngn-scroller\r\n #scroller\r\n [scrollContainer]=\"scrollContainer\"\r\n [virtual]=\"virtual()\"\r\n [items]=\"pagedRows()\"\r\n [fieldId]=\"'id'\"\r\n [itemHeight]=\"rowHeight() || 0\"\r\n [virtualPadding]=\"virtualPadding()\"\r\n [style.--ngn-table-item-start-index]=\"scroller.itemStartIndex()\"\r\n >\r\n <ng-template #item [ngnTemplate]=\"scroller.templateTypes.item\" let-item let-index=\"index\">\r\n @if (item.kind === 'group-header') {\r\n @if (groupHeaderTemplate(); as customGroupHeader) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"customGroupHeader\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: {\r\n groupKey: item.groupKey,\r\n groupValue: item.groupValue,\r\n count: item.count,\r\n expanded: item.expanded,\r\n },\r\n }\"\r\n ></ng-container>\r\n } @else {\r\n <tr\r\n [ngnTableGroupHeaderTr]=\"item\"\r\n [class]=\"theme.classes({ 'group-expanded': item.expanded })\"\r\n [attr.aria-expanded]=\"item.expanded\"\r\n role=\"row\"\r\n (click)=\"toggleGroupFromRow(item)\"\r\n >\r\n <td [class]=\"theme.class('group-header-cell')\" role=\"gridcell\">\r\n <ngn-icon\r\n [defaultIcon]=\"'table-group-toggle'\"\r\n [class]=\"theme.class('group-toggle')\"\r\n />\r\n <span>{{ item.groupValue }}</span>\r\n <span>({{ item.count }})</span>\r\n </td>\r\n </tr>\r\n }\r\n } @else {\r\n <ng-container\r\n [ngTemplateOutlet]=\"bodyTemplate()\"\r\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\r\n ></ng-container>\r\n }\r\n </ng-template>\r\n </tbody>\r\n</table>\r\n@if (paginator()) {\r\n <ngn-paginator [totalItems]=\"rows().length\" (value)=\"pageChanged($event)\" />\r\n}\r\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgnScroller, selector: "ngn-scroller, [ngn-scroller]", inputs: ["items", "fieldId", "virtual", "itemHeight", "virtualPadding", "focusable", "fieldSticky"] }, { kind: "component", type: NgnPaginator, selector: "ngn-paginator", inputs: ["totalItems", "pageSize", "possiblePageSizes", "fixedPageSize", "page"], outputs: ["pageSizeChange", "value", "pageChange"] }, { kind: "directive", type: NgnTemplate, selector: "ng-template[ngnTemplate]", inputs: ["ngnTemplate"] }, { kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }, { kind: "directive", type: NgnTableGroupHeaderTr, selector: "[ngnTableGroupHeaderTr]", inputs: ["ngnTableGroupHeaderTr", "ngnScrollerItem"] }, { kind: "directive", type: NgnScrollShadow, selector: "[ngnScrollShadow]", inputs: ["ngnScrollShadow", "scrollShadowTarget"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
125
872
|
}
|
|
126
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
873
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTable, decorators: [{
|
|
127
874
|
type: Component,
|
|
128
|
-
args: [{ selector: 'ngn-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
875
|
+
args: [{ selector: 'ngn-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
876
|
+
NgTemplateOutlet,
|
|
877
|
+
NgnScroller,
|
|
878
|
+
NgnPaginator,
|
|
879
|
+
NgnTemplate,
|
|
880
|
+
NgnPt,
|
|
881
|
+
NgnIcon,
|
|
882
|
+
NgnTableGroupHeaderTr,
|
|
883
|
+
NgnScrollShadow,
|
|
884
|
+
], providers: [provideSelf(NgnTable)], host: {
|
|
129
885
|
tabindex: '0',
|
|
130
|
-
|
|
131
|
-
|
|
886
|
+
'[attr.aria-multiselectable]': 'selectionMode() === "multi" ? true : null',
|
|
887
|
+
'(keydown)': 'onKeyDown($event)',
|
|
888
|
+
}, template: "<table\r\n #scrollContainer\r\n ngnScrollShadow=\"horizontal\"\r\n [ptInt]=\"this\"\r\n [ptClass]=\"{ table: true, striped: striped() }\"\r\n [attr.aria-rowcount]=\"formattedRows().length\"\r\n [style.--ngn-table-column-count]=\"totalColumnCount()\"\r\n [style.--ngn-table-selection-offset]=\"showCheckboxes() ? 1 : 0\"\r\n [style.grid-template-columns]=\"gridTemplateColumns()\"\r\n [style.--ngn-table-row-height.px]=\"rowHeight()\"\r\n>\r\n <thead [ptInt]=\"this\" [ptClass]=\"'head'\">\r\n <ng-container [ngTemplateOutlet]=\"headerTemplate()\"></ng-container>\r\n </thead>\r\n @if (_dropIndicatorState()) {\r\n <div\r\n [ptInt]=\"this\"\r\n [ptClass]=\"'drop-indicator'\"\r\n [style.left.px]=\"_dropIndicatorState()!.leftPx\"\r\n [style.top.px]=\"_dropIndicatorState()!.topPx\"\r\n [style.height.px]=\"_dropIndicatorState()!.heightPx\"\r\n ></div>\r\n }\r\n\r\n <tbody\r\n [ptInt]=\"this\"\r\n [ptClass]=\"'body'\"\r\n ngn-scroller\r\n #scroller\r\n [scrollContainer]=\"scrollContainer\"\r\n [virtual]=\"virtual()\"\r\n [items]=\"pagedRows()\"\r\n [fieldId]=\"'id'\"\r\n [itemHeight]=\"rowHeight() || 0\"\r\n [virtualPadding]=\"virtualPadding()\"\r\n [style.--ngn-table-item-start-index]=\"scroller.itemStartIndex()\"\r\n >\r\n <ng-template #item [ngnTemplate]=\"scroller.templateTypes.item\" let-item let-index=\"index\">\r\n @if (item.kind === 'group-header') {\r\n @if (groupHeaderTemplate(); as customGroupHeader) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"customGroupHeader\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: {\r\n groupKey: item.groupKey,\r\n groupValue: item.groupValue,\r\n count: item.count,\r\n expanded: item.expanded,\r\n },\r\n }\"\r\n ></ng-container>\r\n } @else {\r\n <tr\r\n [ngnTableGroupHeaderTr]=\"item\"\r\n [class]=\"theme.classes({ 'group-expanded': item.expanded })\"\r\n [attr.aria-expanded]=\"item.expanded\"\r\n role=\"row\"\r\n (click)=\"toggleGroupFromRow(item)\"\r\n >\r\n <td [class]=\"theme.class('group-header-cell')\" role=\"gridcell\">\r\n <ngn-icon\r\n [defaultIcon]=\"'table-group-toggle'\"\r\n [class]=\"theme.class('group-toggle')\"\r\n />\r\n <span>{{ item.groupValue }}</span>\r\n <span>({{ item.count }})</span>\r\n </td>\r\n </tr>\r\n }\r\n } @else {\r\n <ng-container\r\n [ngTemplateOutlet]=\"bodyTemplate()\"\r\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\r\n ></ng-container>\r\n }\r\n </ng-template>\r\n </tbody>\r\n</table>\r\n@if (paginator()) {\r\n <ngn-paginator [totalItems]=\"rows().length\" (value)=\"pageChanged($event)\" />\r\n}\r\n" }]
|
|
889
|
+
}], ctorParameters: () => [], propDecorators: { _scroller: [{ type: i0.ViewChild, args: [i0.forwardRef(() => NgnScroller), { isSignal: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], fieldId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldId", required: true }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], virtualPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualPadding", required: false }] }], striped: [{ type: i0.Input, args: [{ isSignal: true, alias: "striped", required: false }] }], paginator: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginator", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], expandedGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedGroups", required: false }] }, { type: i0.Output, args: ["expandedGroupsChange"] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], resizeMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeMode", required: false }] }], lockSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockSizes", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], columnOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnOrder", required: false }] }, { type: i0.Output, args: ["columnOrderChange"] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }, { type: i0.Output, args: ["sortChange"] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }, { type: i0.Output, args: ["filtersChange"] }] } });
|
|
890
|
+
// --- Auto-size helpers ---
|
|
891
|
+
/** Lazily created canvas context for text measurement (no DOM reflows). */
|
|
892
|
+
let _autoSizeCtx = null;
|
|
893
|
+
function autoSizeCanvasCtx() {
|
|
894
|
+
if (!_autoSizeCtx) {
|
|
895
|
+
_autoSizeCtx = document.createElement('canvas').getContext('2d');
|
|
896
|
+
}
|
|
897
|
+
return _autoSizeCtx;
|
|
898
|
+
}
|
|
899
|
+
/** Builds the CSS font shorthand from an element's computed style. */
|
|
900
|
+
function getCssFont(el) {
|
|
901
|
+
const s = getComputedStyle(el);
|
|
902
|
+
return `${s.fontStyle} ${s.fontWeight} ${s.fontSize} ${s.fontFamily}`;
|
|
903
|
+
}
|
|
132
904
|
|
|
133
905
|
class NgnTableTd extends NgnBase {
|
|
134
906
|
theme = this.injectThemeTemplate(tableControlTemplate);
|
|
135
|
-
|
|
907
|
+
_table = signal(null, ...(ngDevMode ? [{ debugName: "_table" }] : /* istanbul ignore next */ []));
|
|
908
|
+
/** 0-based logical (DOM) index of this cell within its row. */
|
|
909
|
+
_logicalIndex = signal(0, ...(ngDevMode ? [{ debugName: "_logicalIndex" }] : /* istanbul ignore next */ []));
|
|
910
|
+
/** 1-based visual column index, remapped through the table's column order. */
|
|
911
|
+
_visualColumnIndex = computed(() => {
|
|
912
|
+
const logicalIndex = this._logicalIndex();
|
|
913
|
+
const table = this._table();
|
|
914
|
+
if (table) {
|
|
915
|
+
// Subtract the selection column offset so DOM index aligns with registered header cells.
|
|
916
|
+
const offset = table.showCheckboxes() ? 1 : 0;
|
|
917
|
+
return table.getVisualColumnIndex(logicalIndex - offset);
|
|
918
|
+
}
|
|
919
|
+
return logicalIndex + 1;
|
|
920
|
+
}, ...(ngDevMode ? [{ debugName: "_visualColumnIndex" }] : /* istanbul ignore next */ []));
|
|
921
|
+
_columnId = computed(() => {
|
|
922
|
+
const table = this._table();
|
|
923
|
+
if (!table)
|
|
924
|
+
return null;
|
|
925
|
+
const offset = table.showCheckboxes() ? 1 : 0;
|
|
926
|
+
const logicalIndex = this._logicalIndex() - offset;
|
|
927
|
+
const cells = table.getRegisteredHeaderCells();
|
|
928
|
+
return cells[logicalIndex]?.ngnTableTh() ?? null;
|
|
929
|
+
}, ...(ngDevMode ? [{ debugName: "_columnId" }] : /* istanbul ignore next */ []));
|
|
930
|
+
_stickyInfo = computed(() => {
|
|
931
|
+
const table = this._table();
|
|
932
|
+
const columnId = this._columnId();
|
|
933
|
+
if (!table || !columnId)
|
|
934
|
+
return null;
|
|
935
|
+
return table.getStickyInfo(columnId);
|
|
936
|
+
}, ...(ngDevMode ? [{ debugName: "_stickyInfo" }] : /* istanbul ignore next */ []));
|
|
136
937
|
constructor() {
|
|
137
938
|
super();
|
|
138
939
|
this.prepareDom();
|
|
139
940
|
const parent = this.element.nativeElement.parentElement?.children;
|
|
140
941
|
if (parent) {
|
|
141
942
|
const index = Array.from(parent).findIndex(child => child === this.element.nativeElement);
|
|
142
|
-
this.
|
|
943
|
+
this._logicalIndex.set(index);
|
|
143
944
|
}
|
|
945
|
+
afterNextRender(() => {
|
|
946
|
+
const table = getNearestNgnInstance(this.element.nativeElement, NgnTable);
|
|
947
|
+
this._table.set(table);
|
|
948
|
+
});
|
|
949
|
+
afterRenderEffect(() => {
|
|
950
|
+
const info = this._stickyInfo();
|
|
951
|
+
const el = this.element.nativeElement;
|
|
952
|
+
toggleClass(el, this.theme.class('sticky-start'), info?.side === 'start');
|
|
953
|
+
toggleClass(el, this.theme.class('sticky-end'), info?.side === 'end');
|
|
954
|
+
toggleClass(el, this.theme.class('sticky-start-edge'), info?.side === 'start' && info.isEdge);
|
|
955
|
+
toggleClass(el, this.theme.class('sticky-end-edge'), info?.side === 'end' && info.isEdge);
|
|
956
|
+
if (info) {
|
|
957
|
+
el.style.position = 'sticky';
|
|
958
|
+
if (info.side === 'start') {
|
|
959
|
+
el.style.left = `var(--ngn-sticky-start-offset-${info.index})`;
|
|
960
|
+
el.style.removeProperty('right');
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
el.style.right = `var(--ngn-sticky-end-offset-${info.index})`;
|
|
964
|
+
el.style.removeProperty('left');
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
el.style.removeProperty('position');
|
|
969
|
+
el.style.removeProperty('left');
|
|
970
|
+
el.style.removeProperty('right');
|
|
971
|
+
}
|
|
972
|
+
});
|
|
144
973
|
}
|
|
145
974
|
prepareDom() {
|
|
146
975
|
toggleClass(this.element.nativeElement, this.theme.class('cell'), true);
|
|
147
976
|
}
|
|
148
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
149
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.
|
|
977
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableTd, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
978
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.10", type: NgnTableTd, isStandalone: true, selector: "[ngnTableTd]", host: { properties: { "style.--ngn-table-column-index": "_visualColumnIndex()" } }, usesInheritance: true, ngImport: i0 });
|
|
150
979
|
}
|
|
151
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
980
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableTd, decorators: [{
|
|
152
981
|
type: Directive,
|
|
153
982
|
args: [{
|
|
154
983
|
selector: '[ngnTableTd]',
|
|
155
|
-
host: { '[style.--ngn-table-column-index]': '
|
|
984
|
+
host: { '[style.--ngn-table-column-index]': '_visualColumnIndex()' },
|
|
156
985
|
}]
|
|
157
986
|
}], ctorParameters: () => [] });
|
|
158
987
|
|
|
159
988
|
class NgnTableTh extends NgnBase {
|
|
160
989
|
theme = this.injectThemeTemplate(tableControlTemplate);
|
|
161
990
|
_table;
|
|
162
|
-
|
|
991
|
+
_tableSignal = signal(null, ...(ngDevMode ? [{ debugName: "_tableSignal" }] : /* istanbul ignore next */ []));
|
|
992
|
+
_resizeHandle;
|
|
993
|
+
ngnTableTh = input.required(...(ngDevMode ? [{ debugName: "ngnTableTh" }] : /* istanbul ignore next */ []));
|
|
994
|
+
/**
|
|
995
|
+
* The size of this column (e.g. '1fr', '200px', '25%').
|
|
996
|
+
* @default '1fr'
|
|
997
|
+
*/
|
|
998
|
+
size = model('1fr', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
999
|
+
/**
|
|
1000
|
+
* The minimum size constraint for this column.
|
|
1001
|
+
* @default '50px'
|
|
1002
|
+
*/
|
|
1003
|
+
minSize = input('50px', ...(ngDevMode ? [{ debugName: "minSize" }] : /* istanbul ignore next */ []));
|
|
1004
|
+
/**
|
|
1005
|
+
* The maximum size constraint for this column.
|
|
1006
|
+
* @default '100%'
|
|
1007
|
+
*/
|
|
1008
|
+
maxSize = input('100%', ...(ngDevMode ? [{ debugName: "maxSize" }] : /* istanbul ignore next */ []));
|
|
1009
|
+
/**
|
|
1010
|
+
* Whether this column is resizable. Computed from the parent table's `resizable` input.
|
|
1011
|
+
*/
|
|
1012
|
+
resizable = computed(() => this._table?.resizable() ?? false, ...(ngDevMode ? [{ debugName: "resizable" }] : /* istanbul ignore next */ []));
|
|
1013
|
+
/**
|
|
1014
|
+
* Whether the resize handle should be shown. Hidden for the last column in adjacent
|
|
1015
|
+
* and proportional modes since it is implicitly resized via the previous column's handle.
|
|
1016
|
+
* Push mode shows handles on all columns since each column grows independently.
|
|
1017
|
+
*/
|
|
1018
|
+
_showHandle = computed(() => {
|
|
1019
|
+
if (!this._table?.resizable())
|
|
1020
|
+
return false;
|
|
1021
|
+
if (this._table.resizeMode() === 'push')
|
|
1022
|
+
return true;
|
|
1023
|
+
// Adjacent & proportional: hide handle on last column
|
|
1024
|
+
const cells = this._table.getRegisteredHeaderCells();
|
|
1025
|
+
return cells.indexOf(this) < cells.length - 1;
|
|
1026
|
+
}, ...(ngDevMode ? [{ debugName: "_showHandle" }] : /* istanbul ignore next */ []));
|
|
1027
|
+
/**
|
|
1028
|
+
* 1-based visual column index, derived from the table's column order map.
|
|
1029
|
+
* Falls back to registration index + 1 when no reordering is active.
|
|
1030
|
+
*/
|
|
1031
|
+
_visualColumnIndex = computed(() => {
|
|
1032
|
+
if (!this._table)
|
|
1033
|
+
return undefined;
|
|
1034
|
+
const map = this._table.columnOrderMap();
|
|
1035
|
+
return map.get(this.ngnTableTh()) ?? this.getColumnIndex() + 1;
|
|
1036
|
+
}, ...(ngDevMode ? [{ debugName: "_visualColumnIndex" }] : /* istanbul ignore next */ []));
|
|
163
1037
|
constructor() {
|
|
164
1038
|
super();
|
|
165
1039
|
this.prepareDom();
|
|
166
1040
|
const renderer = inject(Renderer2);
|
|
167
1041
|
const elRef = inject(ElementRef);
|
|
168
1042
|
afterNextRender(() => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
1043
|
+
// Wrap existing content in a text container for ellipsis support
|
|
1044
|
+
const textWrapper = renderer.createElement('span');
|
|
1045
|
+
textWrapper.classList.add(this.theme.class('cell-text'));
|
|
1046
|
+
const el = elRef.nativeElement;
|
|
1047
|
+
while (el.firstChild) {
|
|
1048
|
+
textWrapper.appendChild(el.firstChild);
|
|
1049
|
+
}
|
|
1050
|
+
el.appendChild(textWrapper);
|
|
1051
|
+
// Move dynamically-created controls back out of the text wrapper
|
|
1052
|
+
// so they participate in the header cell's flex layout (order: 2, 3)
|
|
1053
|
+
const filterControl = textWrapper.querySelector(`.${this.theme.class('filter-control')}`);
|
|
1054
|
+
if (filterControl)
|
|
1055
|
+
el.appendChild(filterControl);
|
|
1056
|
+
const sortControl = textWrapper.querySelector(`.${this.theme.class('sort-control')}`);
|
|
1057
|
+
if (sortControl)
|
|
1058
|
+
el.appendChild(sortControl);
|
|
1059
|
+
// Spacer div for flex layout
|
|
1060
|
+
const spacer = renderer.createElement('div');
|
|
1061
|
+
spacer.classList.add(this.theme.class('spacer'));
|
|
1062
|
+
elRef.nativeElement.appendChild(spacer);
|
|
1063
|
+
// Resize handle
|
|
1064
|
+
this._resizeHandle = renderer.createElement('div');
|
|
1065
|
+
this._resizeHandle.classList.add(this.theme.class('resize-handle'));
|
|
1066
|
+
this._resizeHandle.addEventListener('pointerdown', this.onResizePointerDown);
|
|
1067
|
+
this._resizeHandle.addEventListener('dblclick', this.onResizeDblClick);
|
|
1068
|
+
elRef.nativeElement.appendChild(this._resizeHandle);
|
|
1069
|
+
});
|
|
1070
|
+
// Toggle handle visibility based on mode and column position
|
|
1071
|
+
afterRenderEffect(() => {
|
|
1072
|
+
const show = this._showHandle();
|
|
1073
|
+
if (this._resizeHandle) {
|
|
1074
|
+
this._resizeHandle.style.display = show ? '' : 'none';
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
// Sticky column classes and offsets
|
|
1078
|
+
afterRenderEffect(() => {
|
|
1079
|
+
const table = this._tableSignal();
|
|
1080
|
+
if (!table)
|
|
1081
|
+
return;
|
|
1082
|
+
const info = table.getStickyInfo(this.ngnTableTh());
|
|
1083
|
+
const el = this.element.nativeElement;
|
|
1084
|
+
toggleClass(el, this.theme.class('sticky-start'), info?.side === 'start');
|
|
1085
|
+
toggleClass(el, this.theme.class('sticky-end'), info?.side === 'end');
|
|
1086
|
+
toggleClass(el, this.theme.class('sticky-start-edge'), info?.side === 'start' && info.isEdge);
|
|
1087
|
+
toggleClass(el, this.theme.class('sticky-end-edge'), info?.side === 'end' && info.isEdge);
|
|
1088
|
+
if (info) {
|
|
1089
|
+
el.style.position = 'sticky';
|
|
1090
|
+
if (info.side === 'start') {
|
|
1091
|
+
el.style.left = `var(--ngn-sticky-start-offset-${info.index})`;
|
|
1092
|
+
el.style.removeProperty('right');
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
el.style.right = `var(--ngn-sticky-end-offset-${info.index})`;
|
|
1096
|
+
el.style.removeProperty('left');
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
el.style.removeProperty('position');
|
|
1101
|
+
el.style.removeProperty('left');
|
|
1102
|
+
el.style.removeProperty('right');
|
|
1103
|
+
}
|
|
172
1104
|
});
|
|
173
1105
|
}
|
|
174
1106
|
ngOnInit() {
|
|
@@ -177,44 +1109,248 @@ class NgnTableTh extends NgnBase {
|
|
|
177
1109
|
throw new NgnError('ngnTableTh', 'ngnTableTh must be used within an NgnTable component');
|
|
178
1110
|
}
|
|
179
1111
|
this._table = table;
|
|
1112
|
+
this._tableSignal.set(table);
|
|
180
1113
|
this._table.registerHeaderCell(this);
|
|
181
1114
|
}
|
|
182
1115
|
ngOnDestroy() {
|
|
183
1116
|
this._table?.unregisterHeaderCell(this);
|
|
1117
|
+
this._resizeHandle?.removeEventListener('pointerdown', this.onResizePointerDown);
|
|
1118
|
+
this._resizeHandle?.removeEventListener('dblclick', this.onResizeDblClick);
|
|
184
1119
|
}
|
|
185
1120
|
prepareDom() {
|
|
186
1121
|
toggleClass(this.element.nativeElement, this.theme.class('cell'), true);
|
|
1122
|
+
toggleClass(this.element.nativeElement, this.theme.class('resizable'), true);
|
|
1123
|
+
}
|
|
1124
|
+
onResizeDblClick = (event) => {
|
|
1125
|
+
if (!this._table?.resizable())
|
|
1126
|
+
return;
|
|
1127
|
+
event.preventDefault();
|
|
1128
|
+
event.stopPropagation();
|
|
1129
|
+
const columnIndex = this.getColumnIndex();
|
|
1130
|
+
if (columnIndex < 0)
|
|
1131
|
+
return;
|
|
1132
|
+
this._table.autoSizeColumn(columnIndex);
|
|
1133
|
+
};
|
|
1134
|
+
onResizePointerDown = (event) => {
|
|
1135
|
+
if (!this._table?.resizable())
|
|
1136
|
+
return;
|
|
1137
|
+
event.preventDefault();
|
|
1138
|
+
event.stopPropagation();
|
|
1139
|
+
const target = event.target;
|
|
1140
|
+
try {
|
|
1141
|
+
target.setPointerCapture(event.pointerId);
|
|
1142
|
+
}
|
|
1143
|
+
catch {
|
|
1144
|
+
// Ignore
|
|
1145
|
+
}
|
|
1146
|
+
const columnIndex = this.getColumnIndex();
|
|
1147
|
+
if (columnIndex < 0)
|
|
1148
|
+
return;
|
|
1149
|
+
this._table.startColumnResize(columnIndex, event);
|
|
1150
|
+
const onPointerMove = (e) => {
|
|
1151
|
+
this._table.dragColumnResize(columnIndex, e);
|
|
1152
|
+
};
|
|
1153
|
+
const cleanup = (cancel) => {
|
|
1154
|
+
this._table.endColumnResize(columnIndex, cancel);
|
|
1155
|
+
target.removeEventListener('pointermove', onPointerMove);
|
|
1156
|
+
target.removeEventListener('pointerup', onPointerUp);
|
|
1157
|
+
target.removeEventListener('pointercancel', onPointerCancel);
|
|
1158
|
+
};
|
|
1159
|
+
const onPointerUp = () => cleanup(false);
|
|
1160
|
+
const onPointerCancel = () => cleanup(true);
|
|
1161
|
+
target.addEventListener('pointermove', onPointerMove);
|
|
1162
|
+
target.addEventListener('pointerup', onPointerUp);
|
|
1163
|
+
target.addEventListener('pointercancel', onPointerCancel);
|
|
1164
|
+
};
|
|
1165
|
+
getColumnIndex() {
|
|
1166
|
+
if (!this._table)
|
|
1167
|
+
return -1;
|
|
1168
|
+
const cells = this._table.getRegisteredHeaderCells();
|
|
1169
|
+
return cells.indexOf(this);
|
|
187
1170
|
}
|
|
188
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
189
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.
|
|
1171
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableTh, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1172
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.10", type: NgnTableTh, isStandalone: true, selector: "[ngnTableTh]", inputs: { ngnTableTh: { classPropertyName: "ngnTableTh", publicName: "ngnTableTh", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, minSize: { classPropertyName: "minSize", publicName: "minSize", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { size: "sizeChange" }, host: { properties: { "style.--ngn-table-column-index": "_visualColumnIndex()" } }, usesInheritance: true, ngImport: i0 });
|
|
190
1173
|
}
|
|
191
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1174
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableTh, decorators: [{
|
|
192
1175
|
type: Directive,
|
|
193
|
-
args: [{
|
|
194
|
-
|
|
1176
|
+
args: [{
|
|
1177
|
+
selector: '[ngnTableTh]',
|
|
1178
|
+
host: {
|
|
1179
|
+
'[style.--ngn-table-column-index]': '_visualColumnIndex()',
|
|
1180
|
+
},
|
|
1181
|
+
}]
|
|
1182
|
+
}], ctorParameters: () => [], propDecorators: { ngnTableTh: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableTh", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }, { type: i0.Output, args: ["sizeChange"] }], minSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSize", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }] } });
|
|
195
1183
|
|
|
196
1184
|
class NgnTableHeadTr extends NgnBase {
|
|
197
1185
|
theme = this.injectThemeTemplate(tableControlTemplate);
|
|
1186
|
+
_element = inject((ElementRef));
|
|
198
1187
|
constructor() {
|
|
199
1188
|
super();
|
|
200
1189
|
this.prepareDom();
|
|
201
1190
|
}
|
|
202
1191
|
prepareDom() {
|
|
203
|
-
toggleClass(this.
|
|
1192
|
+
toggleClass(this._element.nativeElement, this.theme.class('row'), true);
|
|
204
1193
|
}
|
|
205
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
206
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.
|
|
1194
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableHeadTr, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1195
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.10", type: NgnTableHeadTr, isStandalone: true, selector: "[ngnTableHeadTr]", host: { properties: { "attr.aria-rowindex": "1" } }, usesInheritance: true, ngImport: i0 });
|
|
207
1196
|
}
|
|
208
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1197
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableHeadTr, decorators: [{
|
|
209
1198
|
type: Directive,
|
|
210
1199
|
args: [{ selector: '[ngnTableHeadTr]', host: { '[attr.aria-rowindex]': '1' } }]
|
|
211
1200
|
}], ctorParameters: () => [] });
|
|
212
1201
|
|
|
1202
|
+
/** Minimum distance in pixels before a drag is initiated. */
|
|
1203
|
+
const REORDER_DEAD_ZONE_PX = 5;
|
|
1204
|
+
/**
|
|
1205
|
+
* @category directive
|
|
1206
|
+
*/
|
|
1207
|
+
class NgnTableReorderableColumn {
|
|
1208
|
+
theme = injectThemeTemplate(tableControlTemplate);
|
|
1209
|
+
_element = inject(ElementRef);
|
|
1210
|
+
_headerCell = inject(NgnTableTh);
|
|
1211
|
+
_columnId = this._headerCell.ngnTableTh;
|
|
1212
|
+
_table = getNearestNgnInstanceSig(this._element.nativeElement, NgnTable);
|
|
1213
|
+
_isDragging = false;
|
|
1214
|
+
_startX = 0;
|
|
1215
|
+
_startY = 0;
|
|
1216
|
+
_pointerId = null;
|
|
1217
|
+
_boundOnPointerMove = null;
|
|
1218
|
+
_boundOnPointerUp = null;
|
|
1219
|
+
_boundOnPointerCancel = null;
|
|
1220
|
+
_boundOnKeyDown = null;
|
|
1221
|
+
constructor() {
|
|
1222
|
+
afterNextRender(() => {
|
|
1223
|
+
const el = this._element.nativeElement;
|
|
1224
|
+
el.addEventListener('pointerdown', this.onPointerDown);
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
ngOnDestroy() {
|
|
1228
|
+
this._element.nativeElement.removeEventListener('pointerdown', this.onPointerDown);
|
|
1229
|
+
this.cleanup();
|
|
1230
|
+
}
|
|
1231
|
+
onPointerDown = (event) => {
|
|
1232
|
+
const table = this._table();
|
|
1233
|
+
if (!table?.reorderable())
|
|
1234
|
+
return;
|
|
1235
|
+
// Don't intercept resize handle clicks
|
|
1236
|
+
const target = event.target;
|
|
1237
|
+
if (target.classList.contains(this.theme.class('resize-handle')))
|
|
1238
|
+
return;
|
|
1239
|
+
event.preventDefault();
|
|
1240
|
+
this._startX = event.clientX;
|
|
1241
|
+
this._startY = event.clientY;
|
|
1242
|
+
this._isDragging = false;
|
|
1243
|
+
this._pointerId = event.pointerId;
|
|
1244
|
+
try {
|
|
1245
|
+
this._element.nativeElement.setPointerCapture(event.pointerId);
|
|
1246
|
+
}
|
|
1247
|
+
catch {
|
|
1248
|
+
// Ignore
|
|
1249
|
+
}
|
|
1250
|
+
this._boundOnPointerMove = (e) => this.onPointerMove(e);
|
|
1251
|
+
this._boundOnPointerUp = () => this.onPointerUp();
|
|
1252
|
+
this._boundOnPointerCancel = () => this.onPointerCancel();
|
|
1253
|
+
this._boundOnKeyDown = (e) => this.onKeyDown(e);
|
|
1254
|
+
this._element.nativeElement.addEventListener('pointermove', this._boundOnPointerMove);
|
|
1255
|
+
this._element.nativeElement.addEventListener('pointerup', this._boundOnPointerUp);
|
|
1256
|
+
this._element.nativeElement.addEventListener('pointercancel', this._boundOnPointerCancel);
|
|
1257
|
+
document.addEventListener('keydown', this._boundOnKeyDown);
|
|
1258
|
+
};
|
|
1259
|
+
onPointerMove(event) {
|
|
1260
|
+
if (!this._isDragging) {
|
|
1261
|
+
const deltaX = event.clientX - this._startX;
|
|
1262
|
+
const deltaY = event.clientY - this._startY;
|
|
1263
|
+
const distance = Math.sqrt(deltaX ** 2 + deltaY ** 2);
|
|
1264
|
+
if (distance < REORDER_DEAD_ZONE_PX)
|
|
1265
|
+
return;
|
|
1266
|
+
// Start dragging
|
|
1267
|
+
this._isDragging = true;
|
|
1268
|
+
const table = this._table();
|
|
1269
|
+
table?.startColumnReorder(this._columnId());
|
|
1270
|
+
toggleClass(this._element.nativeElement, this.theme.class('drag-source'), true);
|
|
1271
|
+
}
|
|
1272
|
+
this._table()?.dragColumnReorder(event);
|
|
1273
|
+
}
|
|
1274
|
+
onPointerUp() {
|
|
1275
|
+
if (this._isDragging) {
|
|
1276
|
+
this._table()?.endColumnReorder(false);
|
|
1277
|
+
toggleClass(this._element.nativeElement, this.theme.class('drag-source'), false);
|
|
1278
|
+
}
|
|
1279
|
+
this.cleanup();
|
|
1280
|
+
}
|
|
1281
|
+
onPointerCancel() {
|
|
1282
|
+
if (this._isDragging) {
|
|
1283
|
+
this._table()?.endColumnReorder(true);
|
|
1284
|
+
toggleClass(this._element.nativeElement, this.theme.class('drag-source'), false);
|
|
1285
|
+
}
|
|
1286
|
+
this.cleanup();
|
|
1287
|
+
}
|
|
1288
|
+
onKeyDown(event) {
|
|
1289
|
+
if (event.key === 'Escape' && this._isDragging) {
|
|
1290
|
+
this._table()?.endColumnReorder(true);
|
|
1291
|
+
toggleClass(this._element.nativeElement, this.theme.class('drag-source'), false);
|
|
1292
|
+
this.cleanup();
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
cleanup() {
|
|
1296
|
+
this._isDragging = false;
|
|
1297
|
+
if (this._pointerId != null) {
|
|
1298
|
+
try {
|
|
1299
|
+
this._element.nativeElement.releasePointerCapture(this._pointerId);
|
|
1300
|
+
}
|
|
1301
|
+
catch {
|
|
1302
|
+
// Ignore — capture may already be released
|
|
1303
|
+
}
|
|
1304
|
+
this._pointerId = null;
|
|
1305
|
+
}
|
|
1306
|
+
if (this._boundOnPointerMove) {
|
|
1307
|
+
this._element.nativeElement.removeEventListener('pointermove', this._boundOnPointerMove);
|
|
1308
|
+
this._boundOnPointerMove = null;
|
|
1309
|
+
}
|
|
1310
|
+
if (this._boundOnPointerUp) {
|
|
1311
|
+
this._element.nativeElement.removeEventListener('pointerup', this._boundOnPointerUp);
|
|
1312
|
+
this._boundOnPointerUp = null;
|
|
1313
|
+
}
|
|
1314
|
+
if (this._boundOnPointerCancel) {
|
|
1315
|
+
this._element.nativeElement.removeEventListener('pointercancel', this._boundOnPointerCancel);
|
|
1316
|
+
this._boundOnPointerCancel = null;
|
|
1317
|
+
}
|
|
1318
|
+
if (this._boundOnKeyDown) {
|
|
1319
|
+
document.removeEventListener('keydown', this._boundOnKeyDown);
|
|
1320
|
+
this._boundOnKeyDown = null;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableReorderableColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1324
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.10", type: NgnTableReorderableColumn, isStandalone: true, selector: "[ngnTableReorderableColumn]", ngImport: i0 });
|
|
1325
|
+
}
|
|
1326
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableReorderableColumn, decorators: [{
|
|
1327
|
+
type: Directive,
|
|
1328
|
+
args: [{
|
|
1329
|
+
selector: '[ngnTableReorderableColumn]',
|
|
1330
|
+
}]
|
|
1331
|
+
}], ctorParameters: () => [] });
|
|
1332
|
+
|
|
213
1333
|
class NgnTableBodyTr extends NgnScrollerItem {
|
|
214
|
-
ngnTableBodyTr = input.required(...(ngDevMode ? [{ debugName: "ngnTableBodyTr" }] : []));
|
|
215
|
-
ngnScrollerItem = input({}, ...(ngDevMode ? [{ debugName: "ngnScrollerItem" }] : []));
|
|
1334
|
+
ngnTableBodyTr = input.required(...(ngDevMode ? [{ debugName: "ngnTableBodyTr" }] : /* istanbul ignore next */ []));
|
|
1335
|
+
ngnScrollerItem = input({}, ...(ngDevMode ? [{ debugName: "ngnScrollerItem" }] : /* istanbul ignore next */ []));
|
|
216
1336
|
_element = inject((ElementRef));
|
|
217
1337
|
theme = injectThemeTemplate(tableControlTemplate);
|
|
1338
|
+
_table = getNearestNgnInstanceSig(this._element.nativeElement, NgnTable);
|
|
1339
|
+
selectable = computed(() => !!this._table()?.selectionMode(), ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
|
|
1340
|
+
selected = computed(() => {
|
|
1341
|
+
const table = this._table();
|
|
1342
|
+
if (!table || !table.selectionMode())
|
|
1343
|
+
return false;
|
|
1344
|
+
const row = this.ngnTableBodyTr();
|
|
1345
|
+
return table.isRowSelected(row.id);
|
|
1346
|
+
}, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
1347
|
+
focused = computed(() => {
|
|
1348
|
+
const table = this._table();
|
|
1349
|
+
if (!table || !table.selectionMode())
|
|
1350
|
+
return false;
|
|
1351
|
+
const row = this.ngnTableBodyTr();
|
|
1352
|
+
return table.focusedRowIndex() === row.index;
|
|
1353
|
+
}, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
218
1354
|
constructor() {
|
|
219
1355
|
super();
|
|
220
1356
|
effect(() => {
|
|
@@ -226,51 +1362,279 @@ class NgnTableBodyTr extends NgnScrollerItem {
|
|
|
226
1362
|
prepareDom() {
|
|
227
1363
|
toggleClass(this._element.nativeElement, this.theme.class('row'), true);
|
|
228
1364
|
}
|
|
229
|
-
|
|
230
|
-
|
|
1365
|
+
onRowClick(event) {
|
|
1366
|
+
const table = this._table();
|
|
1367
|
+
if (!table || !table.selectionMode())
|
|
1368
|
+
return;
|
|
1369
|
+
table.handleRowClick(this.ngnTableBodyTr(), event);
|
|
1370
|
+
}
|
|
1371
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableBodyTr, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1372
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.10", type: NgnTableBodyTr, isStandalone: true, selector: "[ngnTableBodyTr]", inputs: { ngnTableBodyTr: { classPropertyName: "ngnTableBodyTr", publicName: "ngnTableBodyTr", isSignal: true, isRequired: true, transformFunction: null }, ngnScrollerItem: { classPropertyName: "ngnScrollerItem", publicName: "ngnScrollerItem", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "row" }, listeners: { "click": "onRowClick($event)" }, properties: { "attr.aria-rowindex": "ngnTableBodyTr().index + 2", "style.--ngn-table-row-index": "ngnTableBodyTr().index + 2", "class": "theme.classes({\n 'even': ngnTableBodyTr().index % 2 === 0,\n 'selected-row': selected(),\n 'focused-row': focused()\n })", "attr.aria-selected": "selectable() ? selected() : null" } }, usesInheritance: true, ngImport: i0 });
|
|
231
1373
|
}
|
|
232
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1374
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableBodyTr, decorators: [{
|
|
233
1375
|
type: Directive,
|
|
234
1376
|
args: [{
|
|
235
1377
|
selector: '[ngnTableBodyTr]',
|
|
236
1378
|
host: {
|
|
237
1379
|
'[attr.aria-rowindex]': 'ngnTableBodyTr().index + 2',
|
|
238
1380
|
'[style.--ngn-table-row-index]': 'ngnTableBodyTr().index + 2',
|
|
239
|
-
'[class]': `theme.classes({
|
|
1381
|
+
'[class]': `theme.classes({
|
|
1382
|
+
'even': ngnTableBodyTr().index % 2 === 0,
|
|
1383
|
+
'selected-row': selected(),
|
|
1384
|
+
'focused-row': focused()
|
|
1385
|
+
})`,
|
|
1386
|
+
'[attr.aria-selected]': 'selectable() ? selected() : null',
|
|
1387
|
+
role: 'row',
|
|
1388
|
+
'(click)': 'onRowClick($event)',
|
|
240
1389
|
},
|
|
241
1390
|
}]
|
|
242
1391
|
}], ctorParameters: () => [], propDecorators: { ngnTableBodyTr: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableBodyTr", required: true }] }], ngnScrollerItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnScrollerItem", required: false }] }] } });
|
|
243
1392
|
|
|
1393
|
+
/**
|
|
1394
|
+
* Directive that turns a `<th>` or `<td>` into a selection checkbox column.
|
|
1395
|
+
*
|
|
1396
|
+
* **Header usage** — renders a "select all / none" checkbox:
|
|
1397
|
+
* ```html
|
|
1398
|
+
* <th ngnTableSelectionColumn></th>
|
|
1399
|
+
* ```
|
|
1400
|
+
*
|
|
1401
|
+
* **Body usage** — renders a per-row checkbox (must be inside a `[ngnTableBodyTr]`):
|
|
1402
|
+
* ```html
|
|
1403
|
+
* <td ngnTableSelectionColumn></td>
|
|
1404
|
+
* ```
|
|
1405
|
+
*
|
|
1406
|
+
* @category directive
|
|
1407
|
+
*/
|
|
1408
|
+
class NgnTableSelectionColumn {
|
|
1409
|
+
theme = injectThemeTemplate(tableControlTemplate);
|
|
1410
|
+
_element = inject(ElementRef);
|
|
1411
|
+
_envInjector = inject(EnvironmentInjector);
|
|
1412
|
+
_appRef = inject(ApplicationRef);
|
|
1413
|
+
_table = getNearestNgnInstanceSig(this._element.nativeElement, NgnTable);
|
|
1414
|
+
/** Non-null only when used inside a body row (`<td>`). */
|
|
1415
|
+
_bodyTr = inject(NgnTableBodyTr, { optional: true });
|
|
1416
|
+
_isHeader = this._element.nativeElement.tagName === 'TH';
|
|
1417
|
+
/**
|
|
1418
|
+
* NgnCheckbox created at the application level via `createComponent` + `EnvironmentInjector`.
|
|
1419
|
+
* This keeps it outside Angular's view tree so @for reconciliation cannot destroy it.
|
|
1420
|
+
*/
|
|
1421
|
+
_checkboxRef;
|
|
1422
|
+
selected = computed(() => {
|
|
1423
|
+
if (this._isHeader)
|
|
1424
|
+
return false;
|
|
1425
|
+
const table = this._table();
|
|
1426
|
+
const row = this._bodyTr?.ngnTableBodyTr();
|
|
1427
|
+
if (!table || !row)
|
|
1428
|
+
return false;
|
|
1429
|
+
return table.isRowSelected(row.id);
|
|
1430
|
+
}, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
1431
|
+
focused = computed(() => {
|
|
1432
|
+
if (this._isHeader)
|
|
1433
|
+
return false;
|
|
1434
|
+
const table = this._table();
|
|
1435
|
+
const row = this._bodyTr?.ngnTableBodyTr();
|
|
1436
|
+
if (!table || !row)
|
|
1437
|
+
return false;
|
|
1438
|
+
return table.focusedRowIndex() === row.index;
|
|
1439
|
+
}, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
1440
|
+
constructor() {
|
|
1441
|
+
this._prepareDom();
|
|
1442
|
+
this._createCheckbox();
|
|
1443
|
+
if (this._isHeader) {
|
|
1444
|
+
this._setupHeaderEffects();
|
|
1445
|
+
// Register the selection column once the table signal resolves
|
|
1446
|
+
effect(() => {
|
|
1447
|
+
const table = this._table();
|
|
1448
|
+
if (table) {
|
|
1449
|
+
table.registerSelectionColumn();
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
this._setupBodyEffects();
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
ngOnDestroy() {
|
|
1458
|
+
if (this._isHeader) {
|
|
1459
|
+
this._table()?.unregisterSelectionColumn();
|
|
1460
|
+
}
|
|
1461
|
+
this._appRef.detachView(this._checkboxRef.hostView);
|
|
1462
|
+
this._checkboxRef.destroy();
|
|
1463
|
+
}
|
|
1464
|
+
_prepareDom() {
|
|
1465
|
+
const el = this._element.nativeElement;
|
|
1466
|
+
toggleClass(el, this.theme.class('cell'), true);
|
|
1467
|
+
toggleClass(el, this.theme.class('selection-column'), true);
|
|
1468
|
+
if (this._isHeader) {
|
|
1469
|
+
el.setAttribute('aria-label', 'Select all rows');
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Creates an NgnCheckbox via `createComponent` + `EnvironmentInjector`.
|
|
1474
|
+
* The component is attached to `ApplicationRef` (not a ViewContainerRef),
|
|
1475
|
+
* so Angular's @for block reconciliation cannot destroy it.
|
|
1476
|
+
*/
|
|
1477
|
+
_createCheckbox() {
|
|
1478
|
+
this._checkboxRef = createComponent(NgnCheckbox, {
|
|
1479
|
+
environmentInjector: this._envInjector,
|
|
1480
|
+
});
|
|
1481
|
+
this._appRef.attachView(this._checkboxRef.hostView);
|
|
1482
|
+
const cbEl = this._checkboxRef.location.nativeElement;
|
|
1483
|
+
cbEl.classList.add(this.theme.class('selection-checkbox'));
|
|
1484
|
+
if (this._isHeader) {
|
|
1485
|
+
setComponentInput(this._checkboxRef, 'allowIndeterminate', true);
|
|
1486
|
+
}
|
|
1487
|
+
this._element.nativeElement.appendChild(cbEl);
|
|
1488
|
+
}
|
|
1489
|
+
// --- Header (select-all) ---
|
|
1490
|
+
_setupHeaderEffects() {
|
|
1491
|
+
effect(() => {
|
|
1492
|
+
const table = this._table();
|
|
1493
|
+
if (!table)
|
|
1494
|
+
return;
|
|
1495
|
+
setComponentInput(this._checkboxRef, 'value', table.headerCheckboxValue());
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
// --- Body (per-row) ---
|
|
1499
|
+
_setupBodyEffects() {
|
|
1500
|
+
effect(() => {
|
|
1501
|
+
const isSelected = this.selected();
|
|
1502
|
+
const isFocused = this.focused();
|
|
1503
|
+
const row = this._bodyTr?.ngnTableBodyTr();
|
|
1504
|
+
const el = this._element.nativeElement;
|
|
1505
|
+
setComponentInput(this._checkboxRef, 'value', isSelected);
|
|
1506
|
+
if (row) {
|
|
1507
|
+
el.style.setProperty('--ngn-table-row-index', String(row.index + 2));
|
|
1508
|
+
}
|
|
1509
|
+
toggleClass(el, this.theme.class('selected-row-cell'), isSelected);
|
|
1510
|
+
toggleClass(el, this.theme.class('focused-row-cell'), isFocused);
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
// --- Click handling ---
|
|
1514
|
+
onClick(event) {
|
|
1515
|
+
event.stopPropagation();
|
|
1516
|
+
const table = this._table();
|
|
1517
|
+
if (!table)
|
|
1518
|
+
return;
|
|
1519
|
+
if (this._isHeader) {
|
|
1520
|
+
table.toggleSelectAll();
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
const row = this._bodyTr?.ngnTableBodyTr();
|
|
1524
|
+
if (row) {
|
|
1525
|
+
table.handleCheckboxChange(row);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableSelectionColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1530
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.10", type: NgnTableSelectionColumn, isStandalone: true, selector: "[ngnTableSelectionColumn]", host: { listeners: { "click": "onClick($event)" } }, ngImport: i0 });
|
|
1531
|
+
}
|
|
1532
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableSelectionColumn, decorators: [{
|
|
1533
|
+
type: Directive,
|
|
1534
|
+
args: [{
|
|
1535
|
+
selector: '[ngnTableSelectionColumn]',
|
|
1536
|
+
host: {
|
|
1537
|
+
'(click)': 'onClick($event)',
|
|
1538
|
+
},
|
|
1539
|
+
}]
|
|
1540
|
+
}], ctorParameters: () => [] });
|
|
1541
|
+
|
|
1542
|
+
class NgnTableStickyColumn {
|
|
1543
|
+
_element = inject(ElementRef);
|
|
1544
|
+
_headerCell = inject(NgnTableTh);
|
|
1545
|
+
_columnId = this._headerCell.ngnTableTh;
|
|
1546
|
+
ngnTableStickyColumn = input.required(...(ngDevMode ? [{ debugName: "ngnTableStickyColumn" }] : /* istanbul ignore next */ []));
|
|
1547
|
+
_table = getNearestNgnInstanceSig(this._element.nativeElement, NgnTable);
|
|
1548
|
+
constructor() {
|
|
1549
|
+
afterNextRender(() => {
|
|
1550
|
+
const table = this._table();
|
|
1551
|
+
if (table) {
|
|
1552
|
+
table.registerStickyColumn(this._columnId(), this.ngnTableStickyColumn());
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
ngOnDestroy() {
|
|
1557
|
+
this._table()?.unregisterStickyColumn(this._columnId());
|
|
1558
|
+
}
|
|
1559
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableStickyColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1560
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.10", type: NgnTableStickyColumn, isStandalone: true, selector: "[ngnTableStickyColumn]", inputs: { ngnTableStickyColumn: { classPropertyName: "ngnTableStickyColumn", publicName: "ngnTableStickyColumn", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
1561
|
+
}
|
|
1562
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableStickyColumn, decorators: [{
|
|
1563
|
+
type: Directive,
|
|
1564
|
+
args: [{
|
|
1565
|
+
selector: '[ngnTableStickyColumn]',
|
|
1566
|
+
}]
|
|
1567
|
+
}], ctorParameters: () => [], propDecorators: { ngnTableStickyColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableStickyColumn", required: true }] }] } });
|
|
1568
|
+
|
|
244
1569
|
class NgnTableModule {
|
|
245
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
246
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.
|
|
247
|
-
|
|
1570
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1571
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.10", ngImport: i0, type: NgnTableModule, imports: [NgnTable,
|
|
1572
|
+
NgnTableBodyTr,
|
|
1573
|
+
NgnTableTd,
|
|
1574
|
+
NgnTableTh,
|
|
1575
|
+
NgnTableHeadTr,
|
|
1576
|
+
NgnTableGroupHeaderTr,
|
|
1577
|
+
NgnTableReorderableColumn,
|
|
1578
|
+
NgnTableSelectionColumn,
|
|
1579
|
+
NgnTableStickyColumn], exports: [NgnTable,
|
|
1580
|
+
NgnTableBodyTr,
|
|
1581
|
+
NgnTableTd,
|
|
1582
|
+
NgnTableTh,
|
|
1583
|
+
NgnTableHeadTr,
|
|
1584
|
+
NgnTableGroupHeaderTr,
|
|
1585
|
+
NgnTableReorderableColumn,
|
|
1586
|
+
NgnTableSelectionColumn,
|
|
1587
|
+
NgnTableStickyColumn] });
|
|
1588
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableModule, imports: [NgnTable] });
|
|
248
1589
|
}
|
|
249
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1590
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableModule, decorators: [{
|
|
250
1591
|
type: NgModule,
|
|
251
1592
|
args: [{
|
|
252
|
-
imports: [
|
|
253
|
-
|
|
1593
|
+
imports: [
|
|
1594
|
+
NgnTable,
|
|
1595
|
+
NgnTableBodyTr,
|
|
1596
|
+
NgnTableTd,
|
|
1597
|
+
NgnTableTh,
|
|
1598
|
+
NgnTableHeadTr,
|
|
1599
|
+
NgnTableGroupHeaderTr,
|
|
1600
|
+
NgnTableReorderableColumn,
|
|
1601
|
+
NgnTableSelectionColumn,
|
|
1602
|
+
NgnTableStickyColumn,
|
|
1603
|
+
],
|
|
1604
|
+
exports: [
|
|
1605
|
+
NgnTable,
|
|
1606
|
+
NgnTableBodyTr,
|
|
1607
|
+
NgnTableTd,
|
|
1608
|
+
NgnTableTh,
|
|
1609
|
+
NgnTableHeadTr,
|
|
1610
|
+
NgnTableGroupHeaderTr,
|
|
1611
|
+
NgnTableReorderableColumn,
|
|
1612
|
+
NgnTableSelectionColumn,
|
|
1613
|
+
NgnTableStickyColumn,
|
|
1614
|
+
],
|
|
254
1615
|
}]
|
|
255
1616
|
}] });
|
|
256
1617
|
|
|
1618
|
+
/**
|
|
1619
|
+
* @category directive
|
|
1620
|
+
*/
|
|
257
1621
|
class NgnTableFilterableColumn {
|
|
258
1622
|
theme = injectThemeTemplate(tableControlTemplate);
|
|
259
1623
|
_element = inject(ElementRef);
|
|
260
1624
|
_columnId = inject(NgnTableTh).ngnTableTh;
|
|
261
|
-
ngnTableFilterableColumn = input(...(ngDevMode ? [undefined, { debugName: "ngnTableFilterableColumn" }] : []));
|
|
1625
|
+
ngnTableFilterableColumn = input(...(ngDevMode ? [undefined, { debugName: "ngnTableFilterableColumn" }] : /* istanbul ignore next */ []));
|
|
262
1626
|
_ngnActionButton;
|
|
263
1627
|
_ngnFilter;
|
|
264
1628
|
_table = getNearestNgnInstanceSig(this._element.nativeElement, NgnTable);
|
|
265
1629
|
_rows = computed(() => {
|
|
266
1630
|
return this._table()?.rows() || [];
|
|
267
|
-
}, ...(ngDevMode ? [{ debugName: "_rows" }] : []));
|
|
1631
|
+
}, ...(ngDevMode ? [{ debugName: "_rows" }] : /* istanbul ignore next */ []));
|
|
268
1632
|
filter = computed(() => {
|
|
269
1633
|
const tableFilter = this._table()?.filters()?.[this._columnId()];
|
|
270
1634
|
return tableFilter;
|
|
271
|
-
}, ...(ngDevMode ? [{ debugName: "filter" }] : []));
|
|
272
|
-
ngnTableFilterableColumnType = input.required(...(ngDevMode ? [{ debugName: "ngnTableFilterableColumnType" }] : []));
|
|
273
|
-
ngnTableFilterableColumnItems = input(...(ngDevMode ? [undefined, { debugName: "ngnTableFilterableColumnItems" }] : []));
|
|
1635
|
+
}, ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
1636
|
+
ngnTableFilterableColumnType = input.required(...(ngDevMode ? [{ debugName: "ngnTableFilterableColumnType" }] : /* istanbul ignore next */ []));
|
|
1637
|
+
ngnTableFilterableColumnItems = input(...(ngDevMode ? [undefined, { debugName: "ngnTableFilterableColumnItems" }] : /* istanbul ignore next */ []));
|
|
274
1638
|
constructor() {
|
|
275
1639
|
this._ngnActionButton = inject(ViewContainerRef).createComponent((NgnActionButton));
|
|
276
1640
|
this._ngnFilter = inject(ViewContainerRef).createComponent(NgnFilter);
|
|
@@ -278,6 +1642,7 @@ class NgnTableFilterableColumn {
|
|
|
278
1642
|
this._element.nativeElement.appendChild(this._ngnFilter.location.nativeElement);
|
|
279
1643
|
this._ngnActionButton.location.nativeElement.classList.add(this.theme.class('filter-control'));
|
|
280
1644
|
setComponentInput(this._ngnActionButton, 'kind', 'icon');
|
|
1645
|
+
setComponentInput(this._ngnActionButton, 'inline', true);
|
|
281
1646
|
setComponentInput(this._ngnFilter, 'anchor', this._ngnActionButton.location.nativeElement);
|
|
282
1647
|
setComponentInput(this._ngnFilter, 'mode', 'headless');
|
|
283
1648
|
setComponentInput(this._ngnFilter, 'allowMultiple', true);
|
|
@@ -285,12 +1650,12 @@ class NgnTableFilterableColumn {
|
|
|
285
1650
|
label: 'Filter',
|
|
286
1651
|
value: null,
|
|
287
1652
|
kind: 'icon',
|
|
288
|
-
defaultIcon: this.filter() ? '
|
|
1653
|
+
defaultIcon: this.filter() ? 'filter-active' : 'filter-inactive',
|
|
289
1654
|
action: event => {
|
|
290
1655
|
event.stopPropagation();
|
|
291
1656
|
this._ngnFilter.instance.show();
|
|
292
1657
|
},
|
|
293
|
-
}), ...(ngDevMode ? [{ debugName: "cfg" }] : []));
|
|
1658
|
+
}), ...(ngDevMode ? [{ debugName: "cfg" }] : /* istanbul ignore next */ []));
|
|
294
1659
|
effect(() => {
|
|
295
1660
|
setComponentInput(this._ngnActionButton, 'config', cfg());
|
|
296
1661
|
});
|
|
@@ -313,10 +1678,10 @@ class NgnTableFilterableColumn {
|
|
|
313
1678
|
this._ngnActionButton.destroy();
|
|
314
1679
|
this._ngnFilter.destroy();
|
|
315
1680
|
}
|
|
316
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
317
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.
|
|
1681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableFilterableColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1682
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.10", type: NgnTableFilterableColumn, isStandalone: true, selector: "[ngnTableFilterableColumn]", inputs: { ngnTableFilterableColumn: { classPropertyName: "ngnTableFilterableColumn", publicName: "ngnTableFilterableColumn", isSignal: true, isRequired: false, transformFunction: null }, ngnTableFilterableColumnType: { classPropertyName: "ngnTableFilterableColumnType", publicName: "ngnTableFilterableColumnType", isSignal: true, isRequired: true, transformFunction: null }, ngnTableFilterableColumnItems: { classPropertyName: "ngnTableFilterableColumnItems", publicName: "ngnTableFilterableColumnItems", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "theme.classes({'filterable-column': true, 'filtered-column': !!filter() })" } }, ngImport: i0 });
|
|
318
1683
|
}
|
|
319
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableFilterableColumn, decorators: [{
|
|
320
1685
|
type: Directive,
|
|
321
1686
|
args: [{
|
|
322
1687
|
selector: '[ngnTableFilterableColumn]',
|
|
@@ -326,10 +1691,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
326
1691
|
}]
|
|
327
1692
|
}], ctorParameters: () => [], propDecorators: { ngnTableFilterableColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableFilterableColumn", required: false }] }], ngnTableFilterableColumnType: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableFilterableColumnType", required: true }] }], ngnTableFilterableColumnItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableFilterableColumnItems", required: false }] }] } });
|
|
328
1693
|
|
|
1694
|
+
/**
|
|
1695
|
+
* @category directive
|
|
1696
|
+
*/
|
|
329
1697
|
class NgnTableSortableColumn {
|
|
330
1698
|
theme = injectThemeTemplate(tableControlTemplate);
|
|
331
1699
|
_element = inject(ElementRef);
|
|
332
|
-
ngnTableSortableColumn = input(...(ngDevMode ? [undefined, { debugName: "ngnTableSortableColumn" }] : []));
|
|
1700
|
+
ngnTableSortableColumn = input(...(ngDevMode ? [undefined, { debugName: "ngnTableSortableColumn" }] : /* istanbul ignore next */ []));
|
|
333
1701
|
_table = getNearestNgnInstanceSig(this._element.nativeElement, NgnTable);
|
|
334
1702
|
sort = computed(() => {
|
|
335
1703
|
const tableSort = this._table()?.sort();
|
|
@@ -337,7 +1705,7 @@ class NgnTableSortableColumn {
|
|
|
337
1705
|
return tableSort.direction;
|
|
338
1706
|
}
|
|
339
1707
|
return null;
|
|
340
|
-
}, ...(ngDevMode ? [{ debugName: "sort" }] : []));
|
|
1708
|
+
}, ...(ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []));
|
|
341
1709
|
columnId = inject(NgnTableTh).ngnTableTh;
|
|
342
1710
|
_ngnIcon;
|
|
343
1711
|
constructor() {
|
|
@@ -346,12 +1714,18 @@ class NgnTableSortableColumn {
|
|
|
346
1714
|
this._ngnIcon.location.nativeElement.classList.add(this.theme.class('sort-control'));
|
|
347
1715
|
effect(() => {
|
|
348
1716
|
const sort = this.sort();
|
|
349
|
-
setComponentInput(this._ngnIcon, 'defaultIcon', sort === 'asc' ? '
|
|
1717
|
+
setComponentInput(this._ngnIcon, 'defaultIcon', sort === 'asc' ? 'sort-ascending' : sort === 'desc' ? 'sort-descending' : 'sort-neutral');
|
|
350
1718
|
});
|
|
351
1719
|
}
|
|
352
1720
|
ngOnDestroy() {
|
|
353
1721
|
this._ngnIcon.destroy();
|
|
354
1722
|
}
|
|
1723
|
+
onHostClick(event) {
|
|
1724
|
+
const target = event.target;
|
|
1725
|
+
if (target.classList.contains(this.theme.class('resize-handle')))
|
|
1726
|
+
return;
|
|
1727
|
+
this.toggleSort();
|
|
1728
|
+
}
|
|
355
1729
|
toggleSort() {
|
|
356
1730
|
const currentSort = this.sort();
|
|
357
1731
|
const table = this._table();
|
|
@@ -361,16 +1735,16 @@ class NgnTableSortableColumn {
|
|
|
361
1735
|
? { column: this.columnId(), direction: 'desc' }
|
|
362
1736
|
: null);
|
|
363
1737
|
}
|
|
364
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
365
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.
|
|
1738
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableSortableColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1739
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.10", type: NgnTableSortableColumn, isStandalone: true, selector: "[ngnTableSortableColumn]", inputs: { ngnTableSortableColumn: { classPropertyName: "ngnTableSortableColumn", publicName: "ngnTableSortableColumn", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onHostClick($event)" }, properties: { "class": "theme.classes({'sortable-column': true, 'sorted-column': !!sort() })" } }, ngImport: i0 });
|
|
366
1740
|
}
|
|
367
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1741
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgnTableSortableColumn, decorators: [{
|
|
368
1742
|
type: Directive,
|
|
369
1743
|
args: [{
|
|
370
1744
|
selector: '[ngnTableSortableColumn]',
|
|
371
1745
|
host: {
|
|
372
1746
|
'[class]': `theme.classes({'sortable-column': true, 'sorted-column': !!sort() })`,
|
|
373
|
-
'(click)': `
|
|
1747
|
+
'(click)': `onHostClick($event)`,
|
|
374
1748
|
},
|
|
375
1749
|
}]
|
|
376
1750
|
}], ctorParameters: () => [], propDecorators: { ngnTableSortableColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTableSortableColumn", required: false }] }] } });
|
|
@@ -379,5 +1753,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
379
1753
|
* Generated bundle index. Do not edit.
|
|
380
1754
|
*/
|
|
381
1755
|
|
|
382
|
-
export { NgnTable, NgnTableBodyTr, NgnTableFilterableColumn, NgnTableHeadTr, NgnTableModule, NgnTableSortableColumn, NgnTableTd, NgnTableTh };
|
|
1756
|
+
export { NgnTable, NgnTableBodyTr, NgnTableFilterableColumn, NgnTableGroupHeaderTr, NgnTableHeadTr, NgnTableModule, NgnTableReorderableColumn, NgnTableSelectionColumn, NgnTableSortableColumn, NgnTableStickyColumn, NgnTableTd, NgnTableTemplates, NgnTableTh };
|
|
383
1757
|
//# sourceMappingURL=ngneers-controls-table.mjs.map
|