@tailng-ui/primitives 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/lib/layout/index.d.ts +1 -0
- package/src/lib/layout/index.d.ts.map +1 -1
- package/src/lib/layout/index.js +1 -0
- package/src/lib/layout/index.js.map +1 -1
- package/src/lib/layout/table/__tests__/tng-table.test-harness.d.ts +300 -0
- package/src/lib/layout/table/__tests__/tng-table.test-harness.d.ts.map +1 -0
- package/src/lib/layout/table/__tests__/tng-table.test-harness.js +2492 -0
- package/src/lib/layout/table/__tests__/tng-table.test-harness.js.map +1 -0
- package/src/lib/layout/table/index.d.ts +6 -0
- package/src/lib/layout/table/index.d.ts.map +1 -0
- package/src/lib/layout/table/index.js +6 -0
- package/src/lib/layout/table/index.js.map +1 -0
- package/src/lib/layout/table/tng-table-render.d.ts +175 -0
- package/src/lib/layout/table/tng-table-render.d.ts.map +1 -0
- package/src/lib/layout/table/tng-table-render.js +304 -0
- package/src/lib/layout/table/tng-table-render.js.map +1 -0
- package/src/lib/layout/table/tng-table-sizing.d.ts +75 -0
- package/src/lib/layout/table/tng-table-sizing.d.ts.map +1 -0
- package/src/lib/layout/table/tng-table-sizing.js +361 -0
- package/src/lib/layout/table/tng-table-sizing.js.map +1 -0
- package/src/lib/layout/table/tng-table-sort.d.ts +53 -0
- package/src/lib/layout/table/tng-table-sort.d.ts.map +1 -0
- package/src/lib/layout/table/tng-table-sort.js +167 -0
- package/src/lib/layout/table/tng-table-sort.js.map +1 -0
- package/src/lib/layout/table/tng-table-virtual.d.ts +55 -0
- package/src/lib/layout/table/tng-table-virtual.d.ts.map +1 -0
- package/src/lib/layout/table/tng-table-virtual.js +250 -0
- package/src/lib/layout/table/tng-table-virtual.js.map +1 -0
- package/src/lib/layout/table/tng-table.d.ts +362 -0
- package/src/lib/layout/table/tng-table.d.ts.map +1 -0
- package/src/lib/layout/table/tng-table.js +1490 -0
- package/src/lib/layout/table/tng-table.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tng-table-sort.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/layout/table/tng-table-sort.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n HostBinding,\n HostListener,\n booleanAttribute,\n inject,\n input,\n output,\n} from '@angular/core';\nimport type { OnDestroy, OnInit } from '@angular/core';\nimport {\n createTngSortController,\n type TngTableSortController,\n type TngTableAriaSort,\n type TngTableSortDirection,\n type TngTableSortState,\n} from '@tailng-ui/cdk';\n\nexport type TngTableSortChange = Readonly<{\n activeColumnId: string | null;\n direction: TngTableSortDirection | null;\n}>;\n\ntype TngSortHeaderKeydownEvent = Readonly<Pick<KeyboardEvent, 'key' | 'preventDefault'>>;\n\nfunction normalizeSortDirection(value: unknown): TngTableSortDirection | null {\n if (value === 'asc' || value === 'desc') {\n return value;\n }\n\n return null;\n}\n\n@Directive({\n selector: '[tngTableSort]',\n exportAs: 'tngTableSort',\n})\nexport class TngTableSort {\n private readonly registeredHeaderIds = new Set<string>();\n private uncontrolledActiveColumnId: string | null = null;\n private uncontrolledDirection: TngTableSortDirection | null = null;\n\n public readonly activeColumnId = input<string | null | undefined>(undefined, {\n alias: 'tngTableSortActive',\n });\n public readonly direction = input<TngTableSortDirection | null | undefined, unknown>(undefined, {\n alias: 'tngTableSortDirection',\n transform: normalizeSortDirection,\n });\n public readonly disableClear = input<boolean, boolean | string>(false, {\n alias: 'tngTableSortDisableClear',\n transform: booleanAttribute,\n });\n\n public readonly sortChange = output<TngTableSortChange>();\n\n @HostBinding('attr.data-sortable')\n protected readonly dataSortable = '' as const;\n\n @HostBinding('attr.data-sort-column')\n protected get dataSortColumnAttr(): string | null {\n return this.getState().activeColumnId;\n }\n\n @HostBinding('attr.data-sort-direction')\n protected get dataSortDirectionAttr(): TngTableSortDirection | null {\n return this.getState().direction;\n }\n\n public getAriaSort(columnId: string): TngTableAriaSort {\n return this.createController().getAriaSort(columnId);\n }\n\n public getDirectionFor(columnId: string): TngTableSortDirection | null {\n const state = this.getState();\n if (state.activeColumnId !== columnId) {\n return null;\n }\n\n return state.direction;\n }\n\n public getState(): TngTableSortState<string> {\n return Object.freeze({\n activeColumnId: this.activeColumnId() ?? this.uncontrolledActiveColumnId,\n direction: this.direction() ?? this.uncontrolledDirection,\n disableClear: this.disableClear(),\n });\n }\n\n public hasHeader(columnId: string): boolean {\n return this.registeredHeaderIds.has(columnId);\n }\n\n public registerHeader(columnId: string): void {\n this.registeredHeaderIds.add(columnId);\n }\n\n public set(\n activeColumnId: string | null,\n direction: TngTableSortDirection | null,\n ): TngTableSortState<string> {\n return this.commit(this.createController().set(activeColumnId, direction));\n }\n\n public toggle(columnId: string): TngTableSortState<string> {\n return this.commit(this.createController().toggle(columnId));\n }\n\n public unregisterHeader(columnId: string): void {\n this.registeredHeaderIds.delete(columnId);\n }\n\n private commit(nextState: TngTableSortState<string>): TngTableSortState<string> {\n if (this.activeColumnId() === undefined) {\n this.uncontrolledActiveColumnId = nextState.activeColumnId;\n }\n\n if (this.direction() === undefined) {\n this.uncontrolledDirection = nextState.direction;\n }\n\n this.sortChange.emit({\n activeColumnId: nextState.activeColumnId,\n direction: nextState.direction,\n });\n\n return this.getState();\n }\n\n private createController(): TngTableSortController<unknown, string> {\n const state = this.getState();\n\n return createTngSortController<unknown>({\n activeColumnId: state.activeColumnId,\n direction: state.direction,\n disableClear: state.disableClear,\n });\n }\n}\n\n@Directive({\n selector: 'th[tngSortHeader]',\n exportAs: 'tngSortHeader',\n})\nexport class TngSortHeader implements OnDestroy, OnInit {\n private readonly sort = inject(TngTableSort, { optional: true });\n\n public readonly columnId = input.required<string>({\n alias: 'tngSortHeader',\n });\n public readonly disabled = input<boolean, boolean | string>(false, {\n alias: 'tngSortHeaderDisabled',\n transform: booleanAttribute,\n });\n\n @HostBinding('attr.aria-sort')\n protected get ariaSortAttr(): TngTableAriaSort | null {\n if (this.sort === null) {\n return null;\n }\n\n return this.sort.getAriaSort(this.columnId());\n }\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'table-sort-header' as const;\n\n @HostBinding('attr.data-sort-active')\n protected get dataSortActiveAttr(): '' | null {\n return this.sort?.getDirectionFor(this.columnId()) !== null ? '' : null;\n }\n\n @HostBinding('attr.data-sort-direction')\n protected get dataSortDirectionAttr(): TngTableSortDirection | null {\n return this.sort?.getDirectionFor(this.columnId()) ?? null;\n }\n\n public ngOnDestroy(): void {\n this.sort?.unregisterHeader(this.columnId());\n }\n\n public ngOnInit(): void {\n this.sort?.registerHeader(this.columnId());\n }\n\n @HostListener('click')\n protected onClick(): void {\n this.toggle();\n }\n\n @HostListener('keydown', ['$event'])\n protected onKeydown(event: TngSortHeaderKeydownEvent): void {\n if (event.key !== 'Enter' && event.key !== ' ') {\n return;\n }\n\n event.preventDefault();\n this.toggle();\n }\n\n private toggle(): void {\n if (this.disabled() || this.sort === null) {\n return;\n }\n\n this.sort.toggle(this.columnId());\n }\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ElementRef } from '@angular/core';
|
|
2
|
+
import type { OnDestroy } from '@angular/core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export type TngTableVirtualRange = Readonly<{
|
|
5
|
+
end: number;
|
|
6
|
+
start: number;
|
|
7
|
+
}>;
|
|
8
|
+
export declare class TngTableVirtual implements OnDestroy {
|
|
9
|
+
private readonly hostRef;
|
|
10
|
+
private lastEmittedRange;
|
|
11
|
+
private pendingRangeEmissionFrame;
|
|
12
|
+
private pendingRangeEmissionTimeout;
|
|
13
|
+
constructor(hostRef: ElementRef<HTMLElement>);
|
|
14
|
+
readonly itemCount: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
15
|
+
readonly itemSize: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
16
|
+
readonly overscan: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
17
|
+
readonly viewportHeight: import("@angular/core").InputSignalWithTransform<number | null, unknown>;
|
|
18
|
+
readonly rangeChange: import("@angular/core").OutputEmitterRef<Readonly<{
|
|
19
|
+
end: number;
|
|
20
|
+
start: number;
|
|
21
|
+
}>>;
|
|
22
|
+
protected get dataVirtualEndAttr(): string;
|
|
23
|
+
protected get dataVirtualStartAttr(): string;
|
|
24
|
+
protected readonly dataVirtualized: "";
|
|
25
|
+
protected get maxHeightAttr(): string | null;
|
|
26
|
+
afterSize(): number;
|
|
27
|
+
beforeSize(): number;
|
|
28
|
+
end(): number;
|
|
29
|
+
range(): TngTableVirtualRange;
|
|
30
|
+
scrollToIndex(index: number): void;
|
|
31
|
+
slice<TValue>(items: readonly TValue[]): readonly TValue[];
|
|
32
|
+
start(): number;
|
|
33
|
+
totalSize(): number;
|
|
34
|
+
ngOnDestroy(): void;
|
|
35
|
+
protected onScroll(): void;
|
|
36
|
+
private emitRangeIfChanged;
|
|
37
|
+
private cancelScheduledRangeEmission;
|
|
38
|
+
private resolveRange;
|
|
39
|
+
private resolveViewportHeight;
|
|
40
|
+
private scheduleRangeEmission;
|
|
41
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TngTableVirtual, never>;
|
|
42
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<TngTableVirtual, "[tngTableVirtual]", ["tngTableVirtual"], { "itemCount": { "alias": "tngTableVirtualItemCount"; "required": false; "isSignal": true; }; "itemSize": { "alias": "tngTableVirtualItemSize"; "required": false; "isSignal": true; }; "overscan": { "alias": "tngTableVirtualOverscan"; "required": false; "isSignal": true; }; "viewportHeight": { "alias": "tngTableVirtualViewportHeight"; "required": false; "isSignal": true; }; }, { "rangeChange": "rangeChange"; }, never, never, true, never>;
|
|
43
|
+
}
|
|
44
|
+
export declare class TngTableVirtualSpacer {
|
|
45
|
+
readonly size: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
46
|
+
readonly colspan: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
47
|
+
protected readonly ariaHidden: "true";
|
|
48
|
+
protected get dataSizeAttr(): string;
|
|
49
|
+
protected readonly dataSlot: "table-virtual-spacer";
|
|
50
|
+
protected readonly role: "presentation";
|
|
51
|
+
sizeCss(): string;
|
|
52
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TngTableVirtualSpacer, never>;
|
|
53
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<TngTableVirtualSpacer, "tr[tngTableVirtualSpacer]", ["tngTableVirtualSpacer"], { "size": { "alias": "tngTableVirtualSpacerSize"; "required": false; "isSignal": true; }; "colspan": { "alias": "tngTableVirtualSpacerColspan"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=tng-table-virtual.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tng-table-virtual.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/layout/table/tng-table-virtual.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAMX,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;;AAE/C,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAmDH,qBAIa,eAAgB,YAAW,SAAS;IAQR,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP/D,OAAO,CAAC,gBAAgB,CAGrB;IACH,OAAO,CAAC,yBAAyB,CAAuB;IACxD,OAAO,CAAC,2BAA2B,CAAuB;gBAEF,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC;IAExF,SAAgB,SAAS,oEAGtB;IACH,SAAgB,QAAQ,oEAGrB;IACH,SAAgB,QAAQ,oEAGrB;IACH,SAAgB,cAAc,2EAS3B;IAEH,SAAgB,WAAW;aA1FtB,MAAM;eACJ,MAAM;QAyFgD;IAG7D,SAAS,KAAK,kBAAkB,IAAI,MAAM,CAEzC;IAGD,SAAS,KAAK,oBAAoB,IAAI,MAAM,CAE3C;IAGD,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAG,EAAE,CAAU;IAGjD,SAAS,KAAK,aAAa,IAAI,MAAM,GAAG,IAAI,CAE3C;IAEM,SAAS,IAAI,MAAM;IAKnB,UAAU,IAAI,MAAM;IAIpB,GAAG,IAAI,MAAM;IAIb,KAAK,IAAI,oBAAoB;IAI7B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMlC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE;IAK1D,KAAK,IAAI,MAAM;IAIf,SAAS,IAAI,MAAM;IAInB,WAAW,IAAI,IAAI;IAK1B,SAAS,CAAC,QAAQ,IAAI,IAAI;IAI1B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,4BAA4B;IAYpC,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,qBAAqB;yCA5JlB,eAAe;2CAAf,eAAe;CA8K3B;AAED,qBAYa,qBAAqB;IAChC,SAAgB,IAAI,oEAGjB;IACH,SAAgB,OAAO,oEAGpB;IAGH,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAG,MAAM,CAAU;IAGhD,SAAS,KAAK,YAAY,IAAI,MAAM,CAEnC;IAGD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,sBAAsB,CAAU;IAG9D,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IAE3C,OAAO,IAAI,MAAM;yCAxBb,qBAAqB;2CAArB,qBAAqB;CA2BjC"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { Component, Directive, ElementRef, HostBinding, HostListener, Inject, input, output, } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
function clamp(value, min, max) {
|
|
4
|
+
if (value < min) {
|
|
5
|
+
return min;
|
|
6
|
+
}
|
|
7
|
+
if (value > max) {
|
|
8
|
+
return max;
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function normalizeCount(value) {
|
|
13
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
14
|
+
return Math.max(0, Math.trunc(value));
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
17
|
+
const parsed = Number(value);
|
|
18
|
+
if (Number.isFinite(parsed)) {
|
|
19
|
+
return Math.max(0, Math.trunc(parsed));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
function normalizeItemSize(value) {
|
|
25
|
+
const normalized = normalizeCount(value);
|
|
26
|
+
return normalized > 0 ? normalized : 1;
|
|
27
|
+
}
|
|
28
|
+
function normalizeCssLength(value) {
|
|
29
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
30
|
+
return `${value}px`;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value !== 'string') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
37
|
+
}
|
|
38
|
+
function rangeChanged(a, b) {
|
|
39
|
+
return a.start !== b.start || a.end !== b.end;
|
|
40
|
+
}
|
|
41
|
+
export class TngTableVirtual {
|
|
42
|
+
hostRef;
|
|
43
|
+
lastEmittedRange = Object.freeze({
|
|
44
|
+
end: 0,
|
|
45
|
+
start: 0,
|
|
46
|
+
});
|
|
47
|
+
pendingRangeEmissionFrame = null;
|
|
48
|
+
pendingRangeEmissionTimeout = null;
|
|
49
|
+
constructor(hostRef) {
|
|
50
|
+
this.hostRef = hostRef;
|
|
51
|
+
}
|
|
52
|
+
itemCount = input(0, { ...(ngDevMode ? { debugName: "itemCount" } : {}), alias: 'tngTableVirtualItemCount',
|
|
53
|
+
transform: normalizeCount });
|
|
54
|
+
itemSize = input(1, { ...(ngDevMode ? { debugName: "itemSize" } : {}), alias: 'tngTableVirtualItemSize',
|
|
55
|
+
transform: normalizeItemSize });
|
|
56
|
+
overscan = input(0, { ...(ngDevMode ? { debugName: "overscan" } : {}), alias: 'tngTableVirtualOverscan',
|
|
57
|
+
transform: normalizeCount });
|
|
58
|
+
viewportHeight = input(null, { ...(ngDevMode ? { debugName: "viewportHeight" } : {}), alias: 'tngTableVirtualViewportHeight',
|
|
59
|
+
transform: (value) => {
|
|
60
|
+
if (value === null || value === undefined || value === '') {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return normalizeItemSize(value);
|
|
64
|
+
} });
|
|
65
|
+
rangeChange = output();
|
|
66
|
+
get dataVirtualEndAttr() {
|
|
67
|
+
return String(this.end());
|
|
68
|
+
}
|
|
69
|
+
get dataVirtualStartAttr() {
|
|
70
|
+
return String(this.start());
|
|
71
|
+
}
|
|
72
|
+
dataVirtualized = '';
|
|
73
|
+
get maxHeightAttr() {
|
|
74
|
+
return normalizeCssLength(this.viewportHeight());
|
|
75
|
+
}
|
|
76
|
+
afterSize() {
|
|
77
|
+
const range = this.resolveRange();
|
|
78
|
+
return Math.max(0, this.totalSize() - range.end * this.itemSize());
|
|
79
|
+
}
|
|
80
|
+
beforeSize() {
|
|
81
|
+
return this.start() * this.itemSize();
|
|
82
|
+
}
|
|
83
|
+
end() {
|
|
84
|
+
return this.resolveRange().end;
|
|
85
|
+
}
|
|
86
|
+
range() {
|
|
87
|
+
return this.resolveRange();
|
|
88
|
+
}
|
|
89
|
+
scrollToIndex(index) {
|
|
90
|
+
const maxIndex = Math.max(0, this.itemCount() - 1);
|
|
91
|
+
this.hostRef.nativeElement.scrollTop = clamp(Math.trunc(index), 0, maxIndex) * this.itemSize();
|
|
92
|
+
this.emitRangeIfChanged();
|
|
93
|
+
}
|
|
94
|
+
slice(items) {
|
|
95
|
+
const range = this.resolveRange();
|
|
96
|
+
return items.slice(range.start, range.end);
|
|
97
|
+
}
|
|
98
|
+
start() {
|
|
99
|
+
return this.resolveRange().start;
|
|
100
|
+
}
|
|
101
|
+
totalSize() {
|
|
102
|
+
return this.itemCount() * this.itemSize();
|
|
103
|
+
}
|
|
104
|
+
ngOnDestroy() {
|
|
105
|
+
this.cancelScheduledRangeEmission();
|
|
106
|
+
}
|
|
107
|
+
onScroll() {
|
|
108
|
+
this.scheduleRangeEmission();
|
|
109
|
+
}
|
|
110
|
+
emitRangeIfChanged() {
|
|
111
|
+
const nextRange = this.resolveRange();
|
|
112
|
+
if (!rangeChanged(this.lastEmittedRange, nextRange)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.lastEmittedRange = nextRange;
|
|
116
|
+
this.rangeChange.emit(nextRange);
|
|
117
|
+
}
|
|
118
|
+
cancelScheduledRangeEmission() {
|
|
119
|
+
if (this.pendingRangeEmissionFrame !== null && typeof cancelAnimationFrame === 'function') {
|
|
120
|
+
cancelAnimationFrame(this.pendingRangeEmissionFrame);
|
|
121
|
+
this.pendingRangeEmissionFrame = null;
|
|
122
|
+
}
|
|
123
|
+
if (this.pendingRangeEmissionTimeout !== null) {
|
|
124
|
+
clearTimeout(this.pendingRangeEmissionTimeout);
|
|
125
|
+
this.pendingRangeEmissionTimeout = null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
resolveRange() {
|
|
129
|
+
const itemCount = this.itemCount();
|
|
130
|
+
if (itemCount === 0) {
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
end: 0,
|
|
133
|
+
start: 0,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const itemSize = this.itemSize();
|
|
137
|
+
const viewportHeight = this.resolveViewportHeight();
|
|
138
|
+
const visibleCount = Math.max(1, Math.ceil(viewportHeight / itemSize));
|
|
139
|
+
const firstVisibleIndex = clamp(Math.floor(this.hostRef.nativeElement.scrollTop / itemSize), 0, Math.max(0, itemCount - 1));
|
|
140
|
+
const start = Math.max(0, firstVisibleIndex - this.overscan());
|
|
141
|
+
const end = Math.min(itemCount, firstVisibleIndex + visibleCount + this.overscan());
|
|
142
|
+
return Object.freeze({
|
|
143
|
+
end,
|
|
144
|
+
start,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
resolveViewportHeight() {
|
|
148
|
+
const explicitHeight = this.viewportHeight();
|
|
149
|
+
if (explicitHeight !== null) {
|
|
150
|
+
return explicitHeight;
|
|
151
|
+
}
|
|
152
|
+
const clientHeight = this.hostRef.nativeElement.clientHeight;
|
|
153
|
+
return clientHeight > 0 ? clientHeight : this.itemSize();
|
|
154
|
+
}
|
|
155
|
+
scheduleRangeEmission() {
|
|
156
|
+
if (this.pendingRangeEmissionFrame !== null || this.pendingRangeEmissionTimeout !== null) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
160
|
+
this.pendingRangeEmissionFrame = requestAnimationFrame(() => {
|
|
161
|
+
this.pendingRangeEmissionFrame = null;
|
|
162
|
+
this.emitRangeIfChanged();
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.pendingRangeEmissionTimeout = globalThis.setTimeout(() => {
|
|
167
|
+
this.pendingRangeEmissionTimeout = null;
|
|
168
|
+
this.emitRangeIfChanged();
|
|
169
|
+
}, 0);
|
|
170
|
+
}
|
|
171
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngTableVirtual, deps: [{ token: ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
172
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: TngTableVirtual, isStandalone: true, selector: "[tngTableVirtual]", inputs: { itemCount: { classPropertyName: "itemCount", publicName: "tngTableVirtualItemCount", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "tngTableVirtualItemSize", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "tngTableVirtualOverscan", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "tngTableVirtualViewportHeight", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rangeChange: "rangeChange" }, host: { listeners: { "scroll": "onScroll()" }, properties: { "attr.data-virtual-end": "this.dataVirtualEndAttr", "attr.data-virtual-start": "this.dataVirtualStartAttr", "attr.data-virtualized": "this.dataVirtualized", "style.max-height": "this.maxHeightAttr" } }, exportAs: ["tngTableVirtual"], ngImport: i0 });
|
|
173
|
+
}
|
|
174
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngTableVirtual, decorators: [{
|
|
175
|
+
type: Directive,
|
|
176
|
+
args: [{
|
|
177
|
+
selector: '[tngTableVirtual]',
|
|
178
|
+
exportAs: 'tngTableVirtual',
|
|
179
|
+
}]
|
|
180
|
+
}], ctorParameters: () => [{ type: i0.ElementRef, decorators: [{
|
|
181
|
+
type: Inject,
|
|
182
|
+
args: [ElementRef]
|
|
183
|
+
}] }], propDecorators: { itemCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngTableVirtualItemCount", required: false }] }], itemSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngTableVirtualItemSize", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngTableVirtualOverscan", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngTableVirtualViewportHeight", required: false }] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], dataVirtualEndAttr: [{
|
|
184
|
+
type: HostBinding,
|
|
185
|
+
args: ['attr.data-virtual-end']
|
|
186
|
+
}], dataVirtualStartAttr: [{
|
|
187
|
+
type: HostBinding,
|
|
188
|
+
args: ['attr.data-virtual-start']
|
|
189
|
+
}], dataVirtualized: [{
|
|
190
|
+
type: HostBinding,
|
|
191
|
+
args: ['attr.data-virtualized']
|
|
192
|
+
}], maxHeightAttr: [{
|
|
193
|
+
type: HostBinding,
|
|
194
|
+
args: ['style.max-height']
|
|
195
|
+
}], onScroll: [{
|
|
196
|
+
type: HostListener,
|
|
197
|
+
args: ['scroll']
|
|
198
|
+
}] } });
|
|
199
|
+
export class TngTableVirtualSpacer {
|
|
200
|
+
size = input(0, { ...(ngDevMode ? { debugName: "size" } : {}), alias: 'tngTableVirtualSpacerSize',
|
|
201
|
+
transform: normalizeCount });
|
|
202
|
+
colspan = input(1, { ...(ngDevMode ? { debugName: "colspan" } : {}), alias: 'tngTableVirtualSpacerColspan',
|
|
203
|
+
transform: normalizeItemSize });
|
|
204
|
+
ariaHidden = 'true';
|
|
205
|
+
get dataSizeAttr() {
|
|
206
|
+
return String(this.size());
|
|
207
|
+
}
|
|
208
|
+
dataSlot = 'table-virtual-spacer';
|
|
209
|
+
role = 'presentation';
|
|
210
|
+
sizeCss() {
|
|
211
|
+
return `${this.size()}px`;
|
|
212
|
+
}
|
|
213
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngTableVirtualSpacer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
214
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.1", type: TngTableVirtualSpacer, isStandalone: true, selector: "tr[tngTableVirtualSpacer]", inputs: { size: { classPropertyName: "size", publicName: "tngTableVirtualSpacerSize", isSignal: true, isRequired: false, transformFunction: null }, colspan: { classPropertyName: "colspan", publicName: "tngTableVirtualSpacerColspan", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "this.ariaHidden", "attr.data-size": "this.dataSizeAttr", "attr.data-slot": "this.dataSlot", "attr.role": "this.role" } }, exportAs: ["tngTableVirtualSpacer"], ngImport: i0, template: `
|
|
215
|
+
<td
|
|
216
|
+
[attr.colspan]="colspan()"
|
|
217
|
+
[style.border]="0"
|
|
218
|
+
[style.height]="sizeCss()"
|
|
219
|
+
[style.padding]="0"
|
|
220
|
+
></td>
|
|
221
|
+
`, isInline: true });
|
|
222
|
+
}
|
|
223
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TngTableVirtualSpacer, decorators: [{
|
|
224
|
+
type: Component,
|
|
225
|
+
args: [{
|
|
226
|
+
selector: 'tr[tngTableVirtualSpacer]',
|
|
227
|
+
exportAs: 'tngTableVirtualSpacer',
|
|
228
|
+
template: `
|
|
229
|
+
<td
|
|
230
|
+
[attr.colspan]="colspan()"
|
|
231
|
+
[style.border]="0"
|
|
232
|
+
[style.height]="sizeCss()"
|
|
233
|
+
[style.padding]="0"
|
|
234
|
+
></td>
|
|
235
|
+
`,
|
|
236
|
+
}]
|
|
237
|
+
}], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngTableVirtualSpacerSize", required: false }] }], colspan: [{ type: i0.Input, args: [{ isSignal: true, alias: "tngTableVirtualSpacerColspan", required: false }] }], ariaHidden: [{
|
|
238
|
+
type: HostBinding,
|
|
239
|
+
args: ['attr.aria-hidden']
|
|
240
|
+
}], dataSizeAttr: [{
|
|
241
|
+
type: HostBinding,
|
|
242
|
+
args: ['attr.data-size']
|
|
243
|
+
}], dataSlot: [{
|
|
244
|
+
type: HostBinding,
|
|
245
|
+
args: ['attr.data-slot']
|
|
246
|
+
}], role: [{
|
|
247
|
+
type: HostBinding,
|
|
248
|
+
args: ['attr.role']
|
|
249
|
+
}] } });
|
|
250
|
+
//# sourceMappingURL=tng-table-virtual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tng-table-virtual.js","sourceRoot":"","sources":["../../../../../../../../libs/tailng-ui/primitives/src/lib/layout/table/tng-table-virtual.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Directive,\n ElementRef,\n HostBinding,\n HostListener,\n Inject,\n input,\n output,\n} from '@angular/core';\nimport type { OnDestroy } from '@angular/core';\n\nexport type TngTableVirtualRange = Readonly<{\n end: number;\n start: number;\n}>;\n\nfunction clamp(value: number, min: number, max: number): number {\n if (value < min) {\n return min;\n }\n\n if (value > max) {\n return max;\n }\n\n return value;\n}\n\nfunction normalizeCount(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return Math.max(0, Math.trunc(value));\n }\n\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) {\n return Math.max(0, Math.trunc(parsed));\n }\n }\n\n return 0;\n}\n\nfunction normalizeItemSize(value: unknown): number {\n const normalized = normalizeCount(value);\n return normalized > 0 ? normalized : 1;\n}\n\nfunction normalizeCssLength(value: unknown): string | null {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return `${value}px`;\n }\n\n if (typeof value !== 'string') {\n return null;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction rangeChanged(a: TngTableVirtualRange, b: TngTableVirtualRange): boolean {\n return a.start !== b.start || a.end !== b.end;\n}\n\n@Directive({\n selector: '[tngTableVirtual]',\n exportAs: 'tngTableVirtual',\n})\nexport class TngTableVirtual implements OnDestroy {\n private lastEmittedRange: TngTableVirtualRange = Object.freeze({\n end: 0,\n start: 0,\n });\n private pendingRangeEmissionFrame: number | null = null;\n private pendingRangeEmissionTimeout: number | null = null;\n\n public constructor(@Inject(ElementRef) private readonly hostRef: ElementRef<HTMLElement>) {}\n\n public readonly itemCount = input<number, unknown>(0, {\n alias: 'tngTableVirtualItemCount',\n transform: normalizeCount,\n });\n public readonly itemSize = input<number, unknown>(1, {\n alias: 'tngTableVirtualItemSize',\n transform: normalizeItemSize,\n });\n public readonly overscan = input<number, unknown>(0, {\n alias: 'tngTableVirtualOverscan',\n transform: normalizeCount,\n });\n public readonly viewportHeight = input<number | null, unknown>(null, {\n alias: 'tngTableVirtualViewportHeight',\n transform: (value: unknown): number | null => {\n if (value === null || value === undefined || value === '') {\n return null;\n }\n\n return normalizeItemSize(value);\n },\n });\n\n public readonly rangeChange = output<TngTableVirtualRange>();\n\n @HostBinding('attr.data-virtual-end')\n protected get dataVirtualEndAttr(): string {\n return String(this.end());\n }\n\n @HostBinding('attr.data-virtual-start')\n protected get dataVirtualStartAttr(): string {\n return String(this.start());\n }\n\n @HostBinding('attr.data-virtualized')\n protected readonly dataVirtualized = '' as const;\n\n @HostBinding('style.max-height')\n protected get maxHeightAttr(): string | null {\n return normalizeCssLength(this.viewportHeight());\n }\n\n public afterSize(): number {\n const range = this.resolveRange();\n return Math.max(0, this.totalSize() - range.end * this.itemSize());\n }\n\n public beforeSize(): number {\n return this.start() * this.itemSize();\n }\n\n public end(): number {\n return this.resolveRange().end;\n }\n\n public range(): TngTableVirtualRange {\n return this.resolveRange();\n }\n\n public scrollToIndex(index: number): void {\n const maxIndex = Math.max(0, this.itemCount() - 1);\n this.hostRef.nativeElement.scrollTop = clamp(Math.trunc(index), 0, maxIndex) * this.itemSize();\n this.emitRangeIfChanged();\n }\n\n public slice<TValue>(items: readonly TValue[]): readonly TValue[] {\n const range = this.resolveRange();\n return items.slice(range.start, range.end);\n }\n\n public start(): number {\n return this.resolveRange().start;\n }\n\n public totalSize(): number {\n return this.itemCount() * this.itemSize();\n }\n\n public ngOnDestroy(): void {\n this.cancelScheduledRangeEmission();\n }\n\n @HostListener('scroll')\n protected onScroll(): void {\n this.scheduleRangeEmission();\n }\n\n private emitRangeIfChanged(): void {\n const nextRange = this.resolveRange();\n if (!rangeChanged(this.lastEmittedRange, nextRange)) {\n return;\n }\n\n this.lastEmittedRange = nextRange;\n this.rangeChange.emit(nextRange);\n }\n\n private cancelScheduledRangeEmission(): void {\n if (this.pendingRangeEmissionFrame !== null && typeof cancelAnimationFrame === 'function') {\n cancelAnimationFrame(this.pendingRangeEmissionFrame);\n this.pendingRangeEmissionFrame = null;\n }\n\n if (this.pendingRangeEmissionTimeout !== null) {\n clearTimeout(this.pendingRangeEmissionTimeout);\n this.pendingRangeEmissionTimeout = null;\n }\n }\n\n private resolveRange(): TngTableVirtualRange {\n const itemCount = this.itemCount();\n if (itemCount === 0) {\n return Object.freeze({\n end: 0,\n start: 0,\n });\n }\n\n const itemSize = this.itemSize();\n const viewportHeight = this.resolveViewportHeight();\n const visibleCount = Math.max(1, Math.ceil(viewportHeight / itemSize));\n const firstVisibleIndex = clamp(\n Math.floor(this.hostRef.nativeElement.scrollTop / itemSize),\n 0,\n Math.max(0, itemCount - 1),\n );\n const start = Math.max(0, firstVisibleIndex - this.overscan());\n const end = Math.min(itemCount, firstVisibleIndex + visibleCount + this.overscan());\n\n return Object.freeze({\n end,\n start,\n });\n }\n\n private resolveViewportHeight(): number {\n const explicitHeight = this.viewportHeight();\n if (explicitHeight !== null) {\n return explicitHeight;\n }\n\n const clientHeight = this.hostRef.nativeElement.clientHeight;\n return clientHeight > 0 ? clientHeight : this.itemSize();\n }\n\n private scheduleRangeEmission(): void {\n if (this.pendingRangeEmissionFrame !== null || this.pendingRangeEmissionTimeout !== null) {\n return;\n }\n\n if (typeof requestAnimationFrame === 'function') {\n this.pendingRangeEmissionFrame = requestAnimationFrame(() => {\n this.pendingRangeEmissionFrame = null;\n this.emitRangeIfChanged();\n });\n return;\n }\n\n this.pendingRangeEmissionTimeout = globalThis.setTimeout(() => {\n this.pendingRangeEmissionTimeout = null;\n this.emitRangeIfChanged();\n }, 0);\n }\n}\n\n@Component({\n selector: 'tr[tngTableVirtualSpacer]',\n exportAs: 'tngTableVirtualSpacer',\n template: `\n <td\n [attr.colspan]=\"colspan()\"\n [style.border]=\"0\"\n [style.height]=\"sizeCss()\"\n [style.padding]=\"0\"\n ></td>\n `,\n})\nexport class TngTableVirtualSpacer {\n public readonly size = input<number, unknown>(0, {\n alias: 'tngTableVirtualSpacerSize',\n transform: normalizeCount,\n });\n public readonly colspan = input<number, unknown>(1, {\n alias: 'tngTableVirtualSpacerColspan',\n transform: normalizeItemSize,\n });\n\n @HostBinding('attr.aria-hidden')\n protected readonly ariaHidden = 'true' as const;\n\n @HostBinding('attr.data-size')\n protected get dataSizeAttr(): string {\n return String(this.size());\n }\n\n @HostBinding('attr.data-slot')\n protected readonly dataSlot = 'table-virtual-spacer' as const;\n\n @HostBinding('attr.role')\n protected readonly role = 'presentation' as const;\n\n public sizeCss(): string {\n return `${this.size()}px`;\n }\n}\n"]}
|