@tociva/tailng-ui 0.12.0 → 0.17.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/fesm2022/tociva-tailng-ui-buttons-indicators.mjs +497 -16
- package/fesm2022/tociva-tailng-ui-buttons-indicators.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui-data-table-structure.mjs +802 -17
- package/fesm2022/tociva-tailng-ui-data-table-structure.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui-form-controls.mjs +3096 -17
- package/fesm2022/tociva-tailng-ui-form-controls.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui-layout.mjs +438 -17
- package/fesm2022/tociva-tailng-ui-layout.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui-navigation.mjs +780 -17
- package/fesm2022/tociva-tailng-ui-navigation.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui-popups-overlays.mjs +1115 -17
- package/fesm2022/tociva-tailng-ui-popups-overlays.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui-utilities.mjs +129 -16
- package/fesm2022/tociva-tailng-ui-utilities.mjs.map +1 -1
- package/fesm2022/tociva-tailng-ui.mjs +15 -17
- package/fesm2022/tociva-tailng-ui.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1,22 +1,785 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, Component, output, signal, effect, TemplateRef, ViewChild, ContentChild, Directive, inject, HostListener, ElementRef, booleanAttribute, HostBinding, numberAttribute, contentChildren } from '@angular/core';
|
|
3
|
+
import { RouterLink } from '@angular/router';
|
|
4
|
+
import { TngFocusTrap } from '@tociva/tailng-cdk/a11y';
|
|
5
|
+
import * as i1 from '@angular/common';
|
|
6
|
+
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
7
|
+
import { TngConnectedOverlay, TngOverlayPanel, TngOverlayRef } from '@tociva/tailng-ui/popups-overlays';
|
|
8
|
+
|
|
9
|
+
class TngBreadcrumbs {
|
|
10
|
+
/** Items */
|
|
11
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
12
|
+
/** Optional Home crumb (prepended) */
|
|
13
|
+
home = input(null, ...(ngDevMode ? [{ debugName: "home" }] : []));
|
|
14
|
+
/** Separator text (if you later want icons, change template to project) */
|
|
15
|
+
separator = input('/', ...(ngDevMode ? [{ debugName: "separator" }] : []));
|
|
16
|
+
/** a11y label */
|
|
17
|
+
ariaLabel = input('Breadcrumb', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
18
|
+
/* =====================
|
|
19
|
+
* Klass inputs (Tailng style)
|
|
20
|
+
* ===================== */
|
|
21
|
+
rootKlass = input('flex items-center text-sm text-muted-foreground', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
|
|
22
|
+
listKlass = input('flex items-center flex-wrap gap-1', ...(ngDevMode ? [{ debugName: "listKlass" }] : []));
|
|
23
|
+
itemKlass = input('inline-flex items-center', ...(ngDevMode ? [{ debugName: "itemKlass" }] : []));
|
|
24
|
+
linkKlass = input('text-primary hover:underline', ...(ngDevMode ? [{ debugName: "linkKlass" }] : []));
|
|
25
|
+
currentKlass = input('text-foreground font-medium', ...(ngDevMode ? [{ debugName: "currentKlass" }] : []));
|
|
26
|
+
disabledKlass = input('opacity-60 pointer-events-none', ...(ngDevMode ? [{ debugName: "disabledKlass" }] : []));
|
|
27
|
+
separatorKlass = input('mx-2 text-slate-400', ...(ngDevMode ? [{ debugName: "separatorKlass" }] : []));
|
|
28
|
+
/* =====================
|
|
29
|
+
* Derived
|
|
30
|
+
* ===================== */
|
|
31
|
+
resolvedItems = computed(() => {
|
|
32
|
+
const home = this.home();
|
|
33
|
+
const items = this.items() ?? [];
|
|
34
|
+
return home ? [home, ...items] : items;
|
|
35
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedItems" }] : []));
|
|
36
|
+
currentIndex = computed(() => {
|
|
37
|
+
const items = this.resolvedItems();
|
|
38
|
+
const explicit = items.findIndex((x) => !!x.current);
|
|
39
|
+
return explicit >= 0 ? explicit : Math.max(0, items.length - 1);
|
|
40
|
+
}, ...(ngDevMode ? [{ debugName: "currentIndex" }] : []));
|
|
41
|
+
isCurrent(i) {
|
|
42
|
+
return i === this.currentIndex();
|
|
43
|
+
}
|
|
44
|
+
isClickable(item, i) {
|
|
45
|
+
if (item.disabled)
|
|
46
|
+
return false;
|
|
47
|
+
if (this.isCurrent(i))
|
|
48
|
+
return false;
|
|
49
|
+
return !!item.route || !!item.href;
|
|
50
|
+
}
|
|
51
|
+
itemClasses(item, i) {
|
|
52
|
+
const base = this.itemKlass();
|
|
53
|
+
const disabled = item.disabled ? ` ${this.disabledKlass()}` : '';
|
|
54
|
+
return `${base}${disabled}`.trim();
|
|
55
|
+
}
|
|
56
|
+
labelClasses(item, i) {
|
|
57
|
+
const isCurrent = this.isCurrent(i);
|
|
58
|
+
return (isCurrent ? this.currentKlass() : this.linkKlass()).trim();
|
|
59
|
+
}
|
|
60
|
+
relFor(item) {
|
|
61
|
+
if (!item.href)
|
|
62
|
+
return null;
|
|
63
|
+
if (item.rel)
|
|
64
|
+
return item.rel;
|
|
65
|
+
return item.target === '_blank' ? 'noopener noreferrer' : null;
|
|
66
|
+
}
|
|
67
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngBreadcrumbs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
68
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngBreadcrumbs, isStandalone: true, selector: "tng-breadcrumbs", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, home: { classPropertyName: "home", publicName: "home", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, listKlass: { classPropertyName: "listKlass", publicName: "listKlass", isSignal: true, isRequired: false, transformFunction: null }, itemKlass: { classPropertyName: "itemKlass", publicName: "itemKlass", isSignal: true, isRequired: false, transformFunction: null }, linkKlass: { classPropertyName: "linkKlass", publicName: "linkKlass", isSignal: true, isRequired: false, transformFunction: null }, currentKlass: { classPropertyName: "currentKlass", publicName: "currentKlass", isSignal: true, isRequired: false, transformFunction: null }, disabledKlass: { classPropertyName: "disabledKlass", publicName: "disabledKlass", isSignal: true, isRequired: false, transformFunction: null }, separatorKlass: { classPropertyName: "separatorKlass", publicName: "separatorKlass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<nav [attr.aria-label]=\"ariaLabel()\" [class]=\"rootKlass()\">\n <ol [class]=\"listKlass()\">\n @for (item of resolvedItems(); track $index; let i = $index; let last = $last) {\n <li [class]=\"itemClasses(item, i)\">\n <!-- Internal route -->\n @if (item.route && isClickable(item, i)) {\n <a\n [routerLink]=\"item.route\"\n [class]=\"labelClasses(item, i)\"\n >\n {{ item.label }}\n </a>\n }\n <!-- External link -->\n @else if (item.href && isClickable(item, i)) {\n <a\n [href]=\"item.href\"\n [target]=\"item.target ?? null\"\n [attr.rel]=\"relFor(item)\"\n [class]=\"labelClasses(item, i)\"\n >\n {{ item.label }}\n </a>\n }\n <!-- Current/disabled/plain -->\n @else {\n <span\n [class]=\"labelClasses(item, i)\"\n [attr.aria-current]=\"isCurrent(i) ? 'page' : null\"\n >\n {{ item.label }}\n </span>\n }\n\n @if (!last) {\n <span [class]=\"separatorKlass()\">{{ separator() }}</span>\n }\n </li>\n }\n </ol>\n</nav>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
|
|
69
|
+
}
|
|
70
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngBreadcrumbs, decorators: [{
|
|
71
|
+
type: Component,
|
|
72
|
+
args: [{ selector: 'tng-breadcrumbs', standalone: true, imports: [RouterLink], template: "<nav [attr.aria-label]=\"ariaLabel()\" [class]=\"rootKlass()\">\n <ol [class]=\"listKlass()\">\n @for (item of resolvedItems(); track $index; let i = $index; let last = $last) {\n <li [class]=\"itemClasses(item, i)\">\n <!-- Internal route -->\n @if (item.route && isClickable(item, i)) {\n <a\n [routerLink]=\"item.route\"\n [class]=\"labelClasses(item, i)\"\n >\n {{ item.label }}\n </a>\n }\n <!-- External link -->\n @else if (item.href && isClickable(item, i)) {\n <a\n [href]=\"item.href\"\n [target]=\"item.target ?? null\"\n [attr.rel]=\"relFor(item)\"\n [class]=\"labelClasses(item, i)\"\n >\n {{ item.label }}\n </a>\n }\n <!-- Current/disabled/plain -->\n @else {\n <span\n [class]=\"labelClasses(item, i)\"\n [attr.aria-current]=\"isCurrent(i) ? 'page' : null\"\n >\n {{ item.label }}\n </span>\n }\n\n @if (!last) {\n <span [class]=\"separatorKlass()\">{{ separator() }}</span>\n }\n </li>\n }\n </ol>\n</nav>\n" }]
|
|
73
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], home: [{ type: i0.Input, args: [{ isSignal: true, alias: "home", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], listKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "listKlass", required: false }] }], itemKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemKlass", required: false }] }], linkKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkKlass", required: false }] }], currentKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentKlass", required: false }] }], disabledKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledKlass", required: false }] }], separatorKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "separatorKlass", required: false }] }] } });
|
|
74
|
+
|
|
75
|
+
class TngDrawer {
|
|
76
|
+
/* =====================
|
|
77
|
+
* Projected content
|
|
78
|
+
* ===================== */
|
|
79
|
+
drawerTpl;
|
|
80
|
+
anchorEl;
|
|
81
|
+
/* =====================
|
|
82
|
+
* Inputs / Outputs
|
|
83
|
+
* ===================== */
|
|
84
|
+
open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
85
|
+
placement = input('left', ...(ngDevMode ? [{ debugName: "placement" }] : []));
|
|
86
|
+
closeOnBackdropClick = input(true, ...(ngDevMode ? [{ debugName: "closeOnBackdropClick" }] : []));
|
|
87
|
+
closeOnEscape = input(true, ...(ngDevMode ? [{ debugName: "closeOnEscape" }] : []));
|
|
88
|
+
/** Focus trap (a11y) */
|
|
89
|
+
trapFocus = input(true, ...(ngDevMode ? [{ debugName: "trapFocus" }] : []));
|
|
90
|
+
restoreFocus = input(true, ...(ngDevMode ? [{ debugName: "restoreFocus" }] : []));
|
|
91
|
+
autoCapture = input(true, ...(ngDevMode ? [{ debugName: "autoCapture" }] : []));
|
|
92
|
+
deferCaptureElements = input(false, ...(ngDevMode ? [{ debugName: "deferCaptureElements" }] : []));
|
|
93
|
+
opened = output();
|
|
94
|
+
closed = output();
|
|
95
|
+
/* =====================
|
|
96
|
+
* Styling (klass-first)
|
|
97
|
+
* ===================== */
|
|
98
|
+
backdropKlass = input('fixed inset-0 bg-black/40 backdrop-blur-[1px]', ...(ngDevMode ? [{ debugName: "backdropKlass" }] : []));
|
|
99
|
+
panelKlass = input('bg-bg shadow-xl outline-none', ...(ngDevMode ? [{ debugName: "panelKlass" }] : []));
|
|
100
|
+
sizeKlass = input('w-80', ...(ngDevMode ? [{ debugName: "sizeKlass" }] : [])); // for left/right
|
|
101
|
+
heightKlass = input('h-80', ...(ngDevMode ? [{ debugName: "heightKlass" }] : [])); // for top/bottom
|
|
102
|
+
/* =====================
|
|
103
|
+
* Internal
|
|
104
|
+
* ===================== */
|
|
105
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
106
|
+
overlayPlacement = computed(() => {
|
|
107
|
+
switch (this.placement()) {
|
|
108
|
+
case 'left':
|
|
109
|
+
return 'bottom-start';
|
|
110
|
+
case 'right':
|
|
111
|
+
return 'bottom-end';
|
|
112
|
+
case 'top':
|
|
113
|
+
return 'top-start';
|
|
114
|
+
case 'bottom':
|
|
115
|
+
return 'bottom-start';
|
|
116
|
+
}
|
|
117
|
+
}, ...(ngDevMode ? [{ debugName: "overlayPlacement" }] : []));
|
|
118
|
+
slideClasses = computed(() => {
|
|
119
|
+
const base = 'fixed transition-transform duration-200 ease-in-out will-change-transform';
|
|
120
|
+
switch (this.placement()) {
|
|
121
|
+
case 'left':
|
|
122
|
+
return `${base} left-0 top-0 h-full ${this.isOpen() ? 'translate-x-0' : '-translate-x-full'}`;
|
|
123
|
+
case 'right':
|
|
124
|
+
return `${base} right-0 top-0 h-full ${this.isOpen() ? 'translate-x-0' : 'translate-x-full'}`;
|
|
125
|
+
case 'top':
|
|
126
|
+
return `${base} top-0 left-0 w-full ${this.isOpen() ? 'translate-y-0' : '-translate-y-full'}`;
|
|
127
|
+
case 'bottom':
|
|
128
|
+
return `${base} bottom-0 left-0 w-full ${this.isOpen() ? 'translate-y-0' : 'translate-y-full'}`;
|
|
129
|
+
}
|
|
130
|
+
}, ...(ngDevMode ? [{ debugName: "slideClasses" }] : []));
|
|
131
|
+
constructor() {
|
|
132
|
+
effect(() => {
|
|
133
|
+
if (this.open()) {
|
|
134
|
+
this.isOpen.set(true);
|
|
135
|
+
this.opened.emit();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.isOpen.set(false);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
onOverlayClosed(reason) {
|
|
143
|
+
this.closed.emit(reason);
|
|
144
|
+
}
|
|
145
|
+
onBackdropClick() {
|
|
146
|
+
if (!this.closeOnBackdropClick())
|
|
147
|
+
return;
|
|
148
|
+
this.closed.emit('outside-click');
|
|
149
|
+
}
|
|
150
|
+
/** Keep escape handling scoped to drawer (instead of document listener) */
|
|
151
|
+
onPanelKeydown(ev) {
|
|
152
|
+
if (!this.open())
|
|
153
|
+
return;
|
|
154
|
+
if (!this.closeOnEscape())
|
|
155
|
+
return;
|
|
156
|
+
if (ev.defaultPrevented)
|
|
157
|
+
return;
|
|
158
|
+
if (ev.key === 'Escape') {
|
|
159
|
+
ev.preventDefault();
|
|
160
|
+
this.closed.emit('escape');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
164
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngDrawer, isStandalone: true, selector: "tng-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, trapFocus: { classPropertyName: "trapFocus", publicName: "trapFocus", isSignal: true, isRequired: false, transformFunction: null }, restoreFocus: { classPropertyName: "restoreFocus", publicName: "restoreFocus", isSignal: true, isRequired: false, transformFunction: null }, autoCapture: { classPropertyName: "autoCapture", publicName: "autoCapture", isSignal: true, isRequired: false, transformFunction: null }, deferCaptureElements: { classPropertyName: "deferCaptureElements", publicName: "deferCaptureElements", isSignal: true, isRequired: false, transformFunction: null }, backdropKlass: { classPropertyName: "backdropKlass", publicName: "backdropKlass", isSignal: true, isRequired: false, transformFunction: null }, panelKlass: { classPropertyName: "panelKlass", publicName: "panelKlass", isSignal: true, isRequired: false, transformFunction: null }, sizeKlass: { classPropertyName: "sizeKlass", publicName: "sizeKlass", isSignal: true, isRequired: false, transformFunction: null }, heightKlass: { classPropertyName: "heightKlass", publicName: "heightKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed" }, queries: [{ propertyName: "drawerTpl", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "anchorEl", first: true, predicate: ["anchorEl"], descendants: true, static: true }], ngImport: i0, template: "@if (open()) {\n <!-- Backdrop (below panel) -->\n <div\n [class]=\"backdropKlass()\"\n class=\"z-[1000]\"\n (click)=\"onBackdropClick()\"\n ></div>\n\n <!-- Drawer panel (above backdrop) -->\n <div\n [class]=\"slideClasses() + ' ' + panelKlass()\"\n class=\"z-[1001]\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onPanelKeydown($event)\"\n [tngFocusTrap]=\"open() && trapFocus()\"\n [restoreFocus]=\"restoreFocus()\"\n [autoCapture]=\"autoCapture()\"\n [deferCaptureElements]=\"deferCaptureElements()\"\n >\n <ng-content />\n </div>\n}\n", dependencies: [{ kind: "directive", type: TngFocusTrap, selector: "[tngFocusTrap]", inputs: ["tngFocusTrap", "deferCaptureElements", "autoCapture", "restoreFocus"] }] });
|
|
165
|
+
}
|
|
166
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngDrawer, decorators: [{
|
|
167
|
+
type: Component,
|
|
168
|
+
args: [{ selector: 'tng-drawer', standalone: true, imports: [TngFocusTrap], template: "@if (open()) {\n <!-- Backdrop (below panel) -->\n <div\n [class]=\"backdropKlass()\"\n class=\"z-[1000]\"\n (click)=\"onBackdropClick()\"\n ></div>\n\n <!-- Drawer panel (above backdrop) -->\n <div\n [class]=\"slideClasses() + ' ' + panelKlass()\"\n class=\"z-[1001]\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onPanelKeydown($event)\"\n [tngFocusTrap]=\"open() && trapFocus()\"\n [restoreFocus]=\"restoreFocus()\"\n [autoCapture]=\"autoCapture()\"\n [deferCaptureElements]=\"deferCaptureElements()\"\n >\n <ng-content />\n </div>\n}\n" }]
|
|
169
|
+
}], ctorParameters: () => [], propDecorators: { drawerTpl: [{
|
|
170
|
+
type: ContentChild,
|
|
171
|
+
args: [TemplateRef, { descendants: true }]
|
|
172
|
+
}], anchorEl: [{
|
|
173
|
+
type: ViewChild,
|
|
174
|
+
args: ['anchorEl', { static: true }]
|
|
175
|
+
}], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], trapFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "trapFocus", required: false }] }], restoreFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "restoreFocus", required: false }] }], autoCapture: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCapture", required: false }] }], deferCaptureElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "deferCaptureElements", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], backdropKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropKlass", required: false }] }], panelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelKlass", required: false }] }], sizeKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeKlass", required: false }] }], heightKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "heightKlass", required: false }] }] } });
|
|
176
|
+
|
|
177
|
+
class TngMenuTemplate {
|
|
178
|
+
tpl;
|
|
179
|
+
constructor(tpl) {
|
|
180
|
+
this.tpl = tpl;
|
|
181
|
+
}
|
|
182
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngMenuTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
183
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TngMenuTemplate, isStandalone: true, selector: "ng-template[tngMenuTemplate]", ngImport: i0 });
|
|
184
|
+
}
|
|
185
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngMenuTemplate, decorators: [{
|
|
186
|
+
type: Directive,
|
|
187
|
+
args: [{
|
|
188
|
+
selector: 'ng-template[tngMenuTemplate]',
|
|
189
|
+
standalone: true,
|
|
190
|
+
}]
|
|
191
|
+
}], ctorParameters: () => [{ type: i0.TemplateRef }] });
|
|
192
|
+
|
|
193
|
+
// menu.component.ts
|
|
194
|
+
class TngMenu {
|
|
195
|
+
tplDir;
|
|
196
|
+
get menuTemplate() {
|
|
197
|
+
return this.tplDir?.tpl;
|
|
198
|
+
}
|
|
199
|
+
triggerEl;
|
|
200
|
+
// Modal mode (backdrop semantics)
|
|
201
|
+
modal = input(false, ...(ngDevMode ? [{ debugName: "modal" }] : []));
|
|
202
|
+
placement = input('bottom-start', ...(ngDevMode ? [{ debugName: "placement" }] : []));
|
|
203
|
+
offset = input(6, ...(ngDevMode ? [{ debugName: "offset" }] : []));
|
|
204
|
+
width = input('anchor', ...(ngDevMode ? [{ debugName: "width" }] : []));
|
|
205
|
+
closeOnOutsideClick = input(true, ...(ngDevMode ? [{ debugName: "closeOnOutsideClick" }] : []));
|
|
206
|
+
closeOnEscape = input(true, ...(ngDevMode ? [{ debugName: "closeOnEscape" }] : []));
|
|
207
|
+
closeOnItemClick = input(true, ...(ngDevMode ? [{ debugName: "closeOnItemClick" }] : []));
|
|
208
|
+
rootKlass = input('relative inline-block', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
|
|
209
|
+
triggerKlass = input('inline-flex', ...(ngDevMode ? [{ debugName: "triggerKlass" }] : []));
|
|
210
|
+
panelKlass = input('p-1', ...(ngDevMode ? [{ debugName: "panelKlass" }] : []));
|
|
211
|
+
backdropKlass = input('fixed inset-0 bg-black/40 z-[999]', ...(ngDevMode ? [{ debugName: "backdropKlass" }] : []));
|
|
212
|
+
opened = output();
|
|
213
|
+
closed = output();
|
|
214
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
215
|
+
// Simple stable id for aria-controls (unique enough per instance)
|
|
216
|
+
uid = Math.random().toString(36).slice(2);
|
|
217
|
+
menuId = computed(() => `tng-menu-${this.uid}`, ...(ngDevMode ? [{ debugName: "menuId" }] : []));
|
|
218
|
+
/** Modal forces predictable close behavior */
|
|
219
|
+
effectiveCloseOnOutsideClick = computed(() => this.modal() ? true : this.closeOnOutsideClick(), ...(ngDevMode ? [{ debugName: "effectiveCloseOnOutsideClick" }] : []));
|
|
220
|
+
effectiveCloseOnEscape = computed(() => this.modal() ? true : this.closeOnEscape(), ...(ngDevMode ? [{ debugName: "effectiveCloseOnEscape" }] : []));
|
|
221
|
+
open() {
|
|
222
|
+
this.isOpen.set(true);
|
|
223
|
+
}
|
|
224
|
+
onOverlayOpened() {
|
|
225
|
+
this.opened.emit();
|
|
226
|
+
}
|
|
227
|
+
close(reason) {
|
|
228
|
+
if (!this.isOpen())
|
|
229
|
+
return;
|
|
230
|
+
this.isOpen.set(false);
|
|
231
|
+
this.closed.emit(reason);
|
|
232
|
+
queueMicrotask(() => this.triggerEl?.nativeElement?.focus());
|
|
233
|
+
}
|
|
234
|
+
onOverlayOpenChange(open) {
|
|
235
|
+
if (open)
|
|
236
|
+
this.isOpen.set(true);
|
|
237
|
+
}
|
|
238
|
+
onOverlayClosed(reason) {
|
|
239
|
+
this.isOpen.set(false);
|
|
240
|
+
this.closed.emit(reason);
|
|
241
|
+
queueMicrotask(() => this.triggerEl?.nativeElement?.focus());
|
|
242
|
+
}
|
|
243
|
+
onTriggerClick() {
|
|
244
|
+
this.isOpen() ? this.close('programmatic') : this.open();
|
|
245
|
+
}
|
|
246
|
+
requestCloseOnSelection() {
|
|
247
|
+
if (!this.closeOnItemClick())
|
|
248
|
+
return;
|
|
249
|
+
this.close('selection');
|
|
250
|
+
}
|
|
251
|
+
onItemSelected() {
|
|
252
|
+
this.requestCloseOnSelection();
|
|
253
|
+
}
|
|
254
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
255
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngMenu, isStandalone: true, selector: "tng-menu", inputs: { modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnOutsideClick: { classPropertyName: "closeOnOutsideClick", publicName: "closeOnOutsideClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, closeOnItemClick: { classPropertyName: "closeOnItemClick", publicName: "closeOnItemClick", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, triggerKlass: { classPropertyName: "triggerKlass", publicName: "triggerKlass", isSignal: true, isRequired: false, transformFunction: null }, panelKlass: { classPropertyName: "panelKlass", publicName: "panelKlass", isSignal: true, isRequired: false, transformFunction: null }, backdropKlass: { classPropertyName: "backdropKlass", publicName: "backdropKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed" }, queries: [{ propertyName: "tplDir", first: true, predicate: TngMenuTemplate, descendants: true }], viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["triggerEl"], descendants: true, static: true }], ngImport: i0, template: "\n<div [class]=\"rootKlass()\">\n <!-- Trigger -->\n <button\n #triggerEl\n type=\"button\"\n [class]=\"triggerKlass()\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? menuId() : null\"\n (click)=\"onTriggerClick()\"\n >\n <ng-content select=\"[tngMenuTrigger]\"></ng-content>\n </button>\n\n <!-- Overlay -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (opened)=\"onOverlayOpened()\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"triggerEl\"\n [placement]=\"placement()\"\n [offset]=\"offset()\"\n [width]=\"width()\"\n [hasBackdrop]=\"modal()\"\n [backdropClass]=\"backdropKlass()\"\n [closeOnOutsideClick]=\"effectiveCloseOnOutsideClick()\"\n [closeOnEscape]=\"effectiveCloseOnEscape()\"\n [closeOnInsideClick]=\"false\"\n (closed)=\"overlayRef.close($event)\"\n\n >\n <tng-overlay-panel [klass]=\"panelKlass()\" [modal]=\"modal()\">\n <div [id]=\"menuId()\" role=\"menu\">\n @if (menuTemplate) {\n <ng-container [ngTemplateOutlet]=\"menuTemplate!\" />\n } @else {\n <ng-content select=\"[tngMenuContent]\"></ng-content>\n }\n </div>\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TngConnectedOverlay, selector: "tng-connected-overlay", inputs: ["open", "anchor", "placement", "offset", "width", "closeOnOutsideClick", "closeOnInsideClick", "closeOnEscape", "hasBackdrop", "backdropClass"], outputs: ["opened", "closed", "backdropClick"] }, { kind: "component", type: TngOverlayPanel, selector: "tng-overlay-panel", inputs: ["klass", "modal", "role", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "restoreFocus", "autoCapture", "deferCaptureElements"] }, { kind: "component", type: TngOverlayRef, selector: "tng-overlay-ref", inputs: ["open"], outputs: ["openChange", "opened", "closed"], exportAs: ["tngOverlayRef"] }] });
|
|
256
|
+
}
|
|
257
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngMenu, decorators: [{
|
|
258
|
+
type: Component,
|
|
259
|
+
args: [{ selector: 'tng-menu', standalone: true, imports: [
|
|
260
|
+
CommonModule,
|
|
261
|
+
NgTemplateOutlet,
|
|
262
|
+
TngConnectedOverlay,
|
|
263
|
+
TngOverlayPanel,
|
|
264
|
+
TngOverlayRef,
|
|
265
|
+
], template: "\n<div [class]=\"rootKlass()\">\n <!-- Trigger -->\n <button\n #triggerEl\n type=\"button\"\n [class]=\"triggerKlass()\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? menuId() : null\"\n (click)=\"onTriggerClick()\"\n >\n <ng-content select=\"[tngMenuTrigger]\"></ng-content>\n </button>\n\n <!-- Overlay -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (opened)=\"onOverlayOpened()\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"triggerEl\"\n [placement]=\"placement()\"\n [offset]=\"offset()\"\n [width]=\"width()\"\n [hasBackdrop]=\"modal()\"\n [backdropClass]=\"backdropKlass()\"\n [closeOnOutsideClick]=\"effectiveCloseOnOutsideClick()\"\n [closeOnEscape]=\"effectiveCloseOnEscape()\"\n [closeOnInsideClick]=\"false\"\n (closed)=\"overlayRef.close($event)\"\n\n >\n <tng-overlay-panel [klass]=\"panelKlass()\" [modal]=\"modal()\">\n <div [id]=\"menuId()\" role=\"menu\">\n @if (menuTemplate) {\n <ng-container [ngTemplateOutlet]=\"menuTemplate!\" />\n } @else {\n <ng-content select=\"[tngMenuContent]\"></ng-content>\n }\n </div>\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n" }]
|
|
266
|
+
}], propDecorators: { tplDir: [{
|
|
267
|
+
type: ContentChild,
|
|
268
|
+
args: [TngMenuTemplate]
|
|
269
|
+
}], triggerEl: [{
|
|
270
|
+
type: ViewChild,
|
|
271
|
+
args: ['triggerEl', { static: true }]
|
|
272
|
+
}], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closeOnOutsideClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnOutsideClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], closeOnItemClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnItemClick", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], triggerKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerKlass", required: false }] }], panelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelKlass", required: false }] }], backdropKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropKlass", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
273
|
+
|
|
274
|
+
class TngMenuItem {
|
|
275
|
+
menu = inject(TngMenu, { optional: true });
|
|
276
|
+
onClick() {
|
|
277
|
+
this.menu?.requestCloseOnSelection();
|
|
278
|
+
}
|
|
279
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
280
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TngMenuItem, isStandalone: true, selector: "[tngMenuItem]", host: { listeners: { "click": "onClick()" } }, ngImport: i0 });
|
|
281
|
+
}
|
|
282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngMenuItem, decorators: [{
|
|
283
|
+
type: Directive,
|
|
284
|
+
args: [{
|
|
285
|
+
selector: '[tngMenuItem]',
|
|
286
|
+
standalone: true,
|
|
287
|
+
}]
|
|
288
|
+
}], propDecorators: { onClick: [{
|
|
289
|
+
type: HostListener,
|
|
290
|
+
args: ['click']
|
|
291
|
+
}] } });
|
|
292
|
+
|
|
293
|
+
class TngPaginator {
|
|
294
|
+
/* =====================
|
|
295
|
+
* Inputs
|
|
296
|
+
* ===================== */
|
|
297
|
+
/** Total items count */
|
|
298
|
+
count = input(0, ...(ngDevMode ? [{ debugName: "count" }] : []));
|
|
299
|
+
/** Current page (1-based) */
|
|
300
|
+
page = input(1, ...(ngDevMode ? [{ debugName: "page" }] : []));
|
|
301
|
+
/** Items per page */
|
|
302
|
+
pageSize = input(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
303
|
+
/** Page size options */
|
|
304
|
+
pageSizeOptions = input([10, 20, 50, 100], ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : []));
|
|
305
|
+
/** Hide page size selector */
|
|
306
|
+
hidePageSize = input(false, ...(ngDevMode ? [{ debugName: "hidePageSize" }] : []));
|
|
307
|
+
/** Max visible page buttons (window size) */
|
|
308
|
+
maxPages = input(7, ...(ngDevMode ? [{ debugName: "maxPages" }] : []));
|
|
309
|
+
/* =====================
|
|
310
|
+
* Klass inputs (Tailng)
|
|
311
|
+
* ===================== */
|
|
312
|
+
rootKlass = input('flex flex-wrap items-center justify-between gap-3 text-sm', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
|
|
313
|
+
leftKlass = input('text-muted-foreground', ...(ngDevMode ? [{ debugName: "leftKlass" }] : []));
|
|
314
|
+
rightKlass = input('flex flex-wrap items-center gap-2', ...(ngDevMode ? [{ debugName: "rightKlass" }] : []));
|
|
315
|
+
buttonKlass = input('rounded-md border border-border bg-bg px-2.5 py-1.5 text-sm hover:bg-slate-50 disabled:opacity-50 disabled:pointer-events-none', ...(ngDevMode ? [{ debugName: "buttonKlass" }] : []));
|
|
316
|
+
activePageKlass = input('bg-primary text-on-primary border-primary hover:bg-primary', ...(ngDevMode ? [{ debugName: "activePageKlass" }] : []));
|
|
317
|
+
pageKlass = input('rounded-md border border-border bg-bg px-2.5 py-1.5 text-sm hover:bg-slate-50', ...(ngDevMode ? [{ debugName: "pageKlass" }] : []));
|
|
318
|
+
selectKlass = input('rounded-md border border-border bg-bg px-2 py-1.5 text-sm', ...(ngDevMode ? [{ debugName: "selectKlass" }] : []));
|
|
319
|
+
separatorKlass = input('mx-2 text-slate-400', ...(ngDevMode ? [{ debugName: "separatorKlass" }] : []));
|
|
320
|
+
/* =====================
|
|
321
|
+
* Outputs
|
|
322
|
+
* ===================== */
|
|
323
|
+
pageChange = output();
|
|
324
|
+
pageSizeChange = output();
|
|
325
|
+
change = output();
|
|
326
|
+
/* =====================
|
|
327
|
+
* Derived
|
|
328
|
+
* ===================== */
|
|
329
|
+
totalPages = computed(() => {
|
|
330
|
+
const total = Math.max(0, this.count());
|
|
331
|
+
const size = Math.max(1, this.pageSize());
|
|
332
|
+
return Math.max(1, Math.ceil(total / size));
|
|
333
|
+
}, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
|
|
334
|
+
clampedPage = computed(() => {
|
|
335
|
+
const p = this.page();
|
|
336
|
+
return Math.min(Math.max(1, p), this.totalPages());
|
|
337
|
+
}, ...(ngDevMode ? [{ debugName: "clampedPage" }] : []));
|
|
338
|
+
skip = computed(() => (this.clampedPage() - 1) * this.pageSize(), ...(ngDevMode ? [{ debugName: "skip" }] : []));
|
|
339
|
+
rangeStart = computed(() => {
|
|
340
|
+
const total = this.count();
|
|
341
|
+
if (total <= 0)
|
|
342
|
+
return 0;
|
|
343
|
+
return this.skip() + 1;
|
|
344
|
+
}, ...(ngDevMode ? [{ debugName: "rangeStart" }] : []));
|
|
345
|
+
rangeEnd = computed(() => {
|
|
346
|
+
const total = this.count();
|
|
347
|
+
if (total <= 0)
|
|
348
|
+
return 0;
|
|
349
|
+
return Math.min(total, this.skip() + this.pageSize());
|
|
350
|
+
}, ...(ngDevMode ? [{ debugName: "rangeEnd" }] : []));
|
|
351
|
+
pages = computed(() => {
|
|
352
|
+
const total = this.totalPages();
|
|
353
|
+
const max = Math.max(5, this.maxPages()); // keep sane min
|
|
354
|
+
const current = this.clampedPage();
|
|
355
|
+
if (total <= max) {
|
|
356
|
+
return Array.from({ length: total }, (_, i) => i + 1);
|
|
357
|
+
}
|
|
358
|
+
const half = Math.floor(max / 2);
|
|
359
|
+
let start = Math.max(1, current - half);
|
|
360
|
+
let end = Math.min(total, start + max - 1);
|
|
361
|
+
// adjust if we hit end
|
|
362
|
+
start = Math.max(1, end - max + 1);
|
|
363
|
+
const result = [];
|
|
364
|
+
if (start > 1) {
|
|
365
|
+
result.push(1);
|
|
366
|
+
if (start > 2)
|
|
367
|
+
result.push('…');
|
|
368
|
+
}
|
|
369
|
+
for (let p = start; p <= end; p++)
|
|
370
|
+
result.push(p);
|
|
371
|
+
if (end < total) {
|
|
372
|
+
if (end < total - 1)
|
|
373
|
+
result.push('…');
|
|
374
|
+
result.push(total);
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
}, ...(ngDevMode ? [{ debugName: "pages" }] : []));
|
|
378
|
+
/* =====================
|
|
379
|
+
* Actions
|
|
380
|
+
* ===================== */
|
|
381
|
+
emitChange(nextPage, nextSize) {
|
|
382
|
+
this.pageChange.emit(nextPage);
|
|
383
|
+
this.pageSizeChange.emit(nextSize);
|
|
384
|
+
this.change.emit({
|
|
385
|
+
page: nextPage,
|
|
386
|
+
pageSize: nextSize,
|
|
387
|
+
skip: (nextPage - 1) * nextSize,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
goTo(page) {
|
|
391
|
+
const next = Math.min(Math.max(1, page), this.totalPages());
|
|
392
|
+
if (next === this.clampedPage())
|
|
393
|
+
return;
|
|
394
|
+
// keep size unchanged
|
|
395
|
+
this.pageChange.emit(next);
|
|
396
|
+
this.change.emit({ page: next, pageSize: this.pageSize(), skip: (next - 1) * this.pageSize() });
|
|
397
|
+
}
|
|
398
|
+
prev() {
|
|
399
|
+
this.goTo(this.clampedPage() - 1);
|
|
400
|
+
}
|
|
401
|
+
next() {
|
|
402
|
+
this.goTo(this.clampedPage() + 1);
|
|
403
|
+
}
|
|
404
|
+
first() {
|
|
405
|
+
this.goTo(1);
|
|
406
|
+
}
|
|
407
|
+
last() {
|
|
408
|
+
this.goTo(this.totalPages());
|
|
409
|
+
}
|
|
410
|
+
onPageSizeSelect(value) {
|
|
411
|
+
const size = Math.max(1, Number(value) || this.pageSize());
|
|
412
|
+
if (size === this.pageSize())
|
|
413
|
+
return;
|
|
414
|
+
// keep user near same position: recompute page from previous skip
|
|
415
|
+
const currentSkip = (this.clampedPage() - 1) * this.pageSize();
|
|
416
|
+
const nextPage = Math.floor(currentSkip / size) + 1;
|
|
417
|
+
this.emitChange(nextPage, size);
|
|
418
|
+
}
|
|
419
|
+
isActive(p) {
|
|
420
|
+
return p === this.clampedPage();
|
|
421
|
+
}
|
|
422
|
+
pageBtnClasses(p) {
|
|
423
|
+
const base = this.pageKlass();
|
|
424
|
+
const active = this.isActive(p) ? ` ${this.activePageKlass()}` : '';
|
|
425
|
+
return (base + active).trim();
|
|
426
|
+
}
|
|
427
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngPaginator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
428
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngPaginator, isStandalone: true, selector: "tng-paginator", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, hidePageSize: { classPropertyName: "hidePageSize", publicName: "hidePageSize", isSignal: true, isRequired: false, transformFunction: null }, maxPages: { classPropertyName: "maxPages", publicName: "maxPages", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, leftKlass: { classPropertyName: "leftKlass", publicName: "leftKlass", isSignal: true, isRequired: false, transformFunction: null }, rightKlass: { classPropertyName: "rightKlass", publicName: "rightKlass", isSignal: true, isRequired: false, transformFunction: null }, buttonKlass: { classPropertyName: "buttonKlass", publicName: "buttonKlass", isSignal: true, isRequired: false, transformFunction: null }, activePageKlass: { classPropertyName: "activePageKlass", publicName: "activePageKlass", isSignal: true, isRequired: false, transformFunction: null }, pageKlass: { classPropertyName: "pageKlass", publicName: "pageKlass", isSignal: true, isRequired: false, transformFunction: null }, selectKlass: { classPropertyName: "selectKlass", publicName: "selectKlass", isSignal: true, isRequired: false, transformFunction: null }, separatorKlass: { classPropertyName: "separatorKlass", publicName: "separatorKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", change: "change" }, ngImport: i0, template: "<div [class]=\"rootKlass()\">\n <!-- Left: range -->\n <div [class]=\"leftKlass()\">\n @if (count() > 0) {\n Showing <span class=\"text-foreground\">{{ rangeStart() }}</span>\u2013<span class=\"text-foreground\">{{ rangeEnd() }}</span>\n of <span class=\"text-foreground\">{{ count() }}</span>\n } @else {\n Showing 0 of 0\n }\n </div>\n\n <!-- Right: controls -->\n <div [class]=\"rightKlass()\">\n @if (!hidePageSize()) {\n <div class=\"flex items-center gap-2\">\n <span class=\"text-xs text-muted-foreground\">Rows</span>\n <select\n [class]=\"selectKlass()\"\n [value]=\"pageSize()\"\n (change)=\"onPageSizeSelect($any($event.target).value)\"\n >\n @for (s of pageSizeOptions(); track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n }\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"first()\" [disabled]=\"clampedPage() <= 1\">\n \u00AB\n </button>\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"prev()\" [disabled]=\"clampedPage() <= 1\">\n \u2039\n </button>\n\n <!-- Page numbers -->\n <div class=\"flex items-center gap-1\">\n @for (p of pages(); track $index) {\n @if (p === '\u2026') {\n <span class=\"px-2 text-muted-foreground\">\u2026</span>\n } @else {\n <button\n type=\"button\"\n [class]=\"pageBtnClasses(p)\"\n (click)=\"goTo(p)\"\n [attr.aria-current]=\"isActive(p) ? 'page' : null\"\n >\n {{ p }}\n </button>\n }\n }\n </div>\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"next()\" [disabled]=\"clampedPage() >= totalPages()\">\n \u203A\n </button>\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"last()\" [disabled]=\"clampedPage() >= totalPages()\">\n \u00BB\n </button>\n </div>\n</div>\n" });
|
|
429
|
+
}
|
|
430
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngPaginator, decorators: [{
|
|
431
|
+
type: Component,
|
|
432
|
+
args: [{ selector: 'tng-paginator', standalone: true, template: "<div [class]=\"rootKlass()\">\n <!-- Left: range -->\n <div [class]=\"leftKlass()\">\n @if (count() > 0) {\n Showing <span class=\"text-foreground\">{{ rangeStart() }}</span>\u2013<span class=\"text-foreground\">{{ rangeEnd() }}</span>\n of <span class=\"text-foreground\">{{ count() }}</span>\n } @else {\n Showing 0 of 0\n }\n </div>\n\n <!-- Right: controls -->\n <div [class]=\"rightKlass()\">\n @if (!hidePageSize()) {\n <div class=\"flex items-center gap-2\">\n <span class=\"text-xs text-muted-foreground\">Rows</span>\n <select\n [class]=\"selectKlass()\"\n [value]=\"pageSize()\"\n (change)=\"onPageSizeSelect($any($event.target).value)\"\n >\n @for (s of pageSizeOptions(); track s) {\n <option [value]=\"s\">{{ s }}</option>\n }\n </select>\n </div>\n }\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"first()\" [disabled]=\"clampedPage() <= 1\">\n \u00AB\n </button>\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"prev()\" [disabled]=\"clampedPage() <= 1\">\n \u2039\n </button>\n\n <!-- Page numbers -->\n <div class=\"flex items-center gap-1\">\n @for (p of pages(); track $index) {\n @if (p === '\u2026') {\n <span class=\"px-2 text-muted-foreground\">\u2026</span>\n } @else {\n <button\n type=\"button\"\n [class]=\"pageBtnClasses(p)\"\n (click)=\"goTo(p)\"\n [attr.aria-current]=\"isActive(p) ? 'page' : null\"\n >\n {{ p }}\n </button>\n }\n }\n </div>\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"next()\" [disabled]=\"clampedPage() >= totalPages()\">\n \u203A\n </button>\n\n <button type=\"button\" [class]=\"buttonKlass()\" (click)=\"last()\" [disabled]=\"clampedPage() >= totalPages()\">\n \u00BB\n </button>\n </div>\n</div>\n" }]
|
|
433
|
+
}], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], hidePageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePageSize", required: false }] }], maxPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPages", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], leftKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftKlass", required: false }] }], rightKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rightKlass", required: false }] }], buttonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonKlass", required: false }] }], activePageKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "activePageKlass", required: false }] }], pageKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageKlass", required: false }] }], selectKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectKlass", required: false }] }], separatorKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "separatorKlass", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], change: [{ type: i0.Output, args: ["change"] }] } });
|
|
434
|
+
|
|
435
|
+
class TngSidenav {
|
|
436
|
+
/* =====================
|
|
437
|
+
* State
|
|
438
|
+
* ===================== */
|
|
439
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
440
|
+
/* =====================
|
|
441
|
+
* Tailwind class inputs
|
|
442
|
+
* ===================== */
|
|
443
|
+
rootKlass = input('group h-full bg-bg border-r border-border flex flex-col ' +
|
|
444
|
+
'transition-[width] duration-200 ease-in-out will-change-[width]', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
|
|
445
|
+
expandedKlass = input('w-64', ...(ngDevMode ? [{ debugName: "expandedKlass" }] : []));
|
|
446
|
+
collapsedKlass = input('w-16', ...(ngDevMode ? [{ debugName: "collapsedKlass" }] : []));
|
|
447
|
+
contentKlass = input('flex-1 overflow-auto', ...(ngDevMode ? [{ debugName: "contentKlass" }] : []));
|
|
448
|
+
footerKlass = input('border-t border-border', ...(ngDevMode ? [{ debugName: "footerKlass" }] : []));
|
|
449
|
+
/* =====================
|
|
450
|
+
* Computed
|
|
451
|
+
* ===================== */
|
|
452
|
+
classes = computed(() => [this.rootKlass(), this.collapsed() ? this.collapsedKlass() : this.expandedKlass()].join(' '), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
453
|
+
/**
|
|
454
|
+
* Expose state as attribute for Tailwind selectors:
|
|
455
|
+
* `data-[collapsed=true]:...`
|
|
456
|
+
*/
|
|
457
|
+
dataCollapsed = computed(() => (this.collapsed() ? 'true' : 'false'), ...(ngDevMode ? [{ debugName: "dataCollapsed" }] : []));
|
|
458
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSidenav, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
459
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngSidenav, isStandalone: true, selector: "tng-sidenav", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, expandedKlass: { classPropertyName: "expandedKlass", publicName: "expandedKlass", isSignal: true, isRequired: false, transformFunction: null }, collapsedKlass: { classPropertyName: "collapsedKlass", publicName: "collapsedKlass", isSignal: true, isRequired: false, transformFunction: null }, contentKlass: { classPropertyName: "contentKlass", publicName: "contentKlass", isSignal: true, isRequired: false, transformFunction: null }, footerKlass: { classPropertyName: "footerKlass", publicName: "footerKlass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<nav\n [class]=\"classes()\"\n [attr.data-collapsed]=\"dataCollapsed()\"\n aria-label=\"Primary navigation\"\n>\n <!-- Header slot -->\n <div class=\"shrink-0\">\n <ng-content select=\"[tngSidenavHeader]\"></ng-content>\n </div>\n\n <!-- Main navigation -->\n <div [class]=\"contentKlass()\">\n <ng-content></ng-content>\n </div>\n\n <!-- Footer slot -->\n <div [class]=\"footerKlass()\">\n <ng-content select=\"[tngSidenavFooter]\"></ng-content>\n </div>\n</nav>\n" });
|
|
460
|
+
}
|
|
461
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSidenav, decorators: [{
|
|
462
|
+
type: Component,
|
|
463
|
+
args: [{ selector: 'tng-sidenav', standalone: true, template: "<nav\n [class]=\"classes()\"\n [attr.data-collapsed]=\"dataCollapsed()\"\n aria-label=\"Primary navigation\"\n>\n <!-- Header slot -->\n <div class=\"shrink-0\">\n <ng-content select=\"[tngSidenavHeader]\"></ng-content>\n </div>\n\n <!-- Main navigation -->\n <div [class]=\"contentKlass()\">\n <ng-content></ng-content>\n </div>\n\n <!-- Footer slot -->\n <div [class]=\"footerKlass()\">\n <ng-content select=\"[tngSidenavFooter]\"></ng-content>\n </div>\n</nav>\n" }]
|
|
464
|
+
}], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], expandedKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedKlass", required: false }] }], collapsedKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedKlass", required: false }] }], contentKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentKlass", required: false }] }], footerKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerKlass", required: false }] }] } });
|
|
465
|
+
|
|
466
|
+
class TngSidenavHeaderSlot {
|
|
467
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSidenavHeaderSlot, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
468
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TngSidenavHeaderSlot, isStandalone: true, selector: "[tngSidenavHeader]", ngImport: i0 });
|
|
469
|
+
}
|
|
470
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSidenavHeaderSlot, decorators: [{
|
|
471
|
+
type: Directive,
|
|
472
|
+
args: [{ selector: '[tngSidenavHeader]', standalone: true }]
|
|
473
|
+
}] });
|
|
474
|
+
class TngSidenavFooterSlot {
|
|
475
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSidenavFooterSlot, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
476
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TngSidenavFooterSlot, isStandalone: true, selector: "[tngSidenavFooter]", ngImport: i0 });
|
|
477
|
+
}
|
|
478
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSidenavFooterSlot, decorators: [{
|
|
479
|
+
type: Directive,
|
|
480
|
+
args: [{ selector: '[tngSidenavFooter]', standalone: true }]
|
|
481
|
+
}] });
|
|
482
|
+
|
|
483
|
+
class TngStep {
|
|
484
|
+
stepper = inject(TngStepper);
|
|
485
|
+
el = inject((ElementRef));
|
|
486
|
+
/* =====================
|
|
487
|
+
* Inputs
|
|
488
|
+
* ===================== */
|
|
489
|
+
/** Optional label (you can also project content) */
|
|
490
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
491
|
+
/** Disable this step */
|
|
492
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), transform: booleanAttribute });
|
|
493
|
+
/** Mark as complete (used by linear mode to allow forward navigation) */
|
|
494
|
+
complete = input(false, { ...(ngDevMode ? { debugName: "complete" } : {}), transform: booleanAttribute });
|
|
495
|
+
/* =====================
|
|
496
|
+
* Klass hooks
|
|
497
|
+
* ===================== */
|
|
498
|
+
stepKlass = input('inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium border border-transparent', ...(ngDevMode ? [{ debugName: "stepKlass" }] : []));
|
|
499
|
+
activeKlass = input('bg-primary text-on-primary', ...(ngDevMode ? [{ debugName: "activeKlass" }] : []));
|
|
500
|
+
inactiveKlass = input('bg-bg text-muted-foreground hover:text-foreground hover:bg-slate-50', ...(ngDevMode ? [{ debugName: "inactiveKlass" }] : []));
|
|
501
|
+
disabledKlass = input('opacity-50 cursor-not-allowed', ...(ngDevMode ? [{ debugName: "disabledKlass" }] : []));
|
|
502
|
+
/* =====================
|
|
503
|
+
* Internal
|
|
504
|
+
* ===================== */
|
|
505
|
+
focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
|
|
506
|
+
isComplete() {
|
|
507
|
+
return this.complete();
|
|
508
|
+
}
|
|
509
|
+
isFocused() {
|
|
510
|
+
return this.focused();
|
|
511
|
+
}
|
|
512
|
+
focus() {
|
|
513
|
+
this.el.nativeElement.focus();
|
|
514
|
+
}
|
|
515
|
+
/* =====================
|
|
516
|
+
* Host bindings
|
|
517
|
+
* ===================== */
|
|
518
|
+
role = 'tab';
|
|
519
|
+
get tabindex() {
|
|
520
|
+
// roving tabindex: active is focusable by default, others -1
|
|
521
|
+
return this.stepper.isActive(this.index()) ? 0 : -1;
|
|
522
|
+
}
|
|
523
|
+
get selected() {
|
|
524
|
+
return this.stepper.isActive(this.index());
|
|
525
|
+
}
|
|
526
|
+
get ariaDisabled() {
|
|
527
|
+
return this.disabled() ? 'true' : null;
|
|
528
|
+
}
|
|
529
|
+
get klass() {
|
|
530
|
+
if (this.disabled())
|
|
531
|
+
return `${this.stepKlass()} ${this.disabledKlass()}`.trim();
|
|
532
|
+
return this.stepper.isActive(this.index())
|
|
533
|
+
? `${this.stepKlass()} ${this.activeKlass()}`.trim()
|
|
534
|
+
: `${this.stepKlass()} ${this.inactiveKlass()}`.trim();
|
|
535
|
+
}
|
|
536
|
+
/* =====================
|
|
537
|
+
* Index resolution
|
|
538
|
+
* ===================== */
|
|
539
|
+
index = computed(() => {
|
|
540
|
+
const steps = this.stepper.steps();
|
|
541
|
+
return steps.indexOf(this);
|
|
542
|
+
}, ...(ngDevMode ? [{ debugName: "index" }] : []));
|
|
543
|
+
/* =====================
|
|
544
|
+
* Events
|
|
545
|
+
* ===================== */
|
|
546
|
+
onClick() {
|
|
547
|
+
if (this.disabled())
|
|
548
|
+
return;
|
|
549
|
+
this.stepper.setIndex(this.index());
|
|
550
|
+
}
|
|
551
|
+
onFocus() {
|
|
552
|
+
this.focused.set(true);
|
|
553
|
+
}
|
|
554
|
+
onBlur() {
|
|
555
|
+
this.focused.set(false);
|
|
556
|
+
}
|
|
557
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngStep, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
558
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngStep, isStandalone: true, selector: "tng-step", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, complete: { classPropertyName: "complete", publicName: "complete", isSignal: true, isRequired: false, transformFunction: null }, stepKlass: { classPropertyName: "stepKlass", publicName: "stepKlass", isSignal: true, isRequired: false, transformFunction: null }, activeKlass: { classPropertyName: "activeKlass", publicName: "activeKlass", isSignal: true, isRequired: false, transformFunction: null }, inactiveKlass: { classPropertyName: "inactiveKlass", publicName: "inactiveKlass", isSignal: true, isRequired: false, transformFunction: null }, disabledKlass: { classPropertyName: "disabledKlass", publicName: "disabledKlass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()", "focus": "onFocus()", "blur": "onBlur()" }, properties: { "attr.role": "this.role", "attr.tabindex": "this.tabindex", "attr.aria-selected": "this.selected", "attr.aria-disabled": "this.ariaDisabled", "class": "this.klass" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
|
|
559
|
+
}
|
|
560
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngStep, decorators: [{
|
|
561
|
+
type: Component,
|
|
562
|
+
args: [{
|
|
563
|
+
selector: 'tng-step',
|
|
564
|
+
standalone: true,
|
|
565
|
+
template: `<ng-content />`,
|
|
566
|
+
}]
|
|
567
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], complete: [{ type: i0.Input, args: [{ isSignal: true, alias: "complete", required: false }] }], stepKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepKlass", required: false }] }], activeKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKlass", required: false }] }], inactiveKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inactiveKlass", required: false }] }], disabledKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledKlass", required: false }] }], role: [{
|
|
568
|
+
type: HostBinding,
|
|
569
|
+
args: ['attr.role']
|
|
570
|
+
}], tabindex: [{
|
|
571
|
+
type: HostBinding,
|
|
572
|
+
args: ['attr.tabindex']
|
|
573
|
+
}], selected: [{
|
|
574
|
+
type: HostBinding,
|
|
575
|
+
args: ['attr.aria-selected']
|
|
576
|
+
}], ariaDisabled: [{
|
|
577
|
+
type: HostBinding,
|
|
578
|
+
args: ['attr.aria-disabled']
|
|
579
|
+
}], klass: [{
|
|
580
|
+
type: HostBinding,
|
|
581
|
+
args: ['class']
|
|
582
|
+
}], onClick: [{
|
|
583
|
+
type: HostListener,
|
|
584
|
+
args: ['click']
|
|
585
|
+
}], onFocus: [{
|
|
586
|
+
type: HostListener,
|
|
587
|
+
args: ['focus']
|
|
588
|
+
}], onBlur: [{
|
|
589
|
+
type: HostListener,
|
|
590
|
+
args: ['blur']
|
|
591
|
+
}] } });
|
|
592
|
+
|
|
593
|
+
class TngStepper {
|
|
594
|
+
/* =====================
|
|
595
|
+
* Inputs
|
|
596
|
+
* ===================== */
|
|
597
|
+
/**
|
|
598
|
+
* Controlled active index.
|
|
599
|
+
* If not null, stepper behaves controlled.
|
|
600
|
+
* NOTE: No transform here (Angular typing limitation with null initial).
|
|
601
|
+
*/
|
|
602
|
+
activeIndex = input(null, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
|
|
603
|
+
/** Uncontrolled initial index */
|
|
604
|
+
defaultIndex = input(0, { ...(ngDevMode ? { debugName: "defaultIndex" } : {}), transform: numberAttribute });
|
|
605
|
+
/** Linear mode: prevent jumping forward beyond completed steps */
|
|
606
|
+
linear = input(false, { ...(ngDevMode ? { debugName: "linear" } : {}), transform: booleanAttribute });
|
|
607
|
+
/** Orientation */
|
|
608
|
+
orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : []));
|
|
609
|
+
/* =====================
|
|
610
|
+
* Outputs
|
|
611
|
+
* ===================== */
|
|
612
|
+
activeIndexChange = output();
|
|
613
|
+
/* =====================
|
|
614
|
+
* Klass hooks
|
|
615
|
+
* ===================== */
|
|
616
|
+
rootKlass = input('w-full', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
|
|
617
|
+
headerKlass = input('flex gap-2', ...(ngDevMode ? [{ debugName: "headerKlass" }] : []));
|
|
618
|
+
headerVerticalKlass = input('flex flex-col gap-2', ...(ngDevMode ? [{ debugName: "headerVerticalKlass" }] : []));
|
|
619
|
+
panelWrapKlass = input('pt-4', ...(ngDevMode ? [{ debugName: "panelWrapKlass" }] : []));
|
|
620
|
+
/* =====================
|
|
621
|
+
* Children
|
|
622
|
+
* ===================== */
|
|
623
|
+
steps = contentChildren(TngStep, { ...(ngDevMode ? { debugName: "steps" } : {}), descendants: true });
|
|
624
|
+
/* =====================
|
|
625
|
+
* State
|
|
626
|
+
* ===================== */
|
|
627
|
+
_index = signal(0, ...(ngDevMode ? [{ debugName: "_index" }] : []));
|
|
628
|
+
index = this._index.asReadonly();
|
|
629
|
+
constructor() {
|
|
630
|
+
// Controlled/uncontrolled sync
|
|
631
|
+
effect(() => {
|
|
632
|
+
const steps = this.steps(); // track for clamping
|
|
633
|
+
const n = steps.length;
|
|
634
|
+
const controlled = this.activeIndex();
|
|
635
|
+
if (controlled !== null) {
|
|
636
|
+
this._index.set(this.clamp(Number(controlled), n));
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
// uncontrolled init (only when nothing set yet)
|
|
640
|
+
const current = this._index();
|
|
641
|
+
if (current === 0 && this.defaultIndex() !== 0) {
|
|
642
|
+
this._index.set(this.clamp(this.defaultIndex(), n));
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// always keep clamped if steps count changes
|
|
646
|
+
this._index.set(this.clamp(current, n));
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/* =====================
|
|
651
|
+
* Public API
|
|
652
|
+
* ===================== */
|
|
653
|
+
setIndex(next) {
|
|
654
|
+
const clamped = this.clamp(next, this.steps().length);
|
|
655
|
+
if (!this.canActivate(clamped))
|
|
656
|
+
return;
|
|
657
|
+
this._index.set(clamped);
|
|
658
|
+
this.activeIndexChange.emit(clamped);
|
|
659
|
+
}
|
|
660
|
+
next() {
|
|
661
|
+
this.setIndex(this._index() + 1);
|
|
662
|
+
}
|
|
663
|
+
prev() {
|
|
664
|
+
this.setIndex(this._index() - 1);
|
|
665
|
+
}
|
|
666
|
+
isActive(i) {
|
|
667
|
+
return this._index() === i;
|
|
668
|
+
}
|
|
669
|
+
/* =====================
|
|
670
|
+
* Keyboard navigation (roving focus)
|
|
671
|
+
* ===================== */
|
|
672
|
+
onKeydown(ev) {
|
|
673
|
+
const steps = this.steps();
|
|
674
|
+
if (!steps.length)
|
|
675
|
+
return;
|
|
676
|
+
const key = ev.key;
|
|
677
|
+
const horizontal = this.orientation() === 'horizontal';
|
|
678
|
+
const prevKey = horizontal ? 'ArrowLeft' : 'ArrowUp';
|
|
679
|
+
const nextKey = horizontal ? 'ArrowRight' : 'ArrowDown';
|
|
680
|
+
if (key === prevKey) {
|
|
681
|
+
ev.preventDefault();
|
|
682
|
+
this.focusStep(this._index() - 1);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (key === nextKey) {
|
|
686
|
+
ev.preventDefault();
|
|
687
|
+
this.focusStep(this._index() + 1);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (key === 'Home') {
|
|
691
|
+
ev.preventDefault();
|
|
692
|
+
this.focusStep(0);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (key === 'End') {
|
|
696
|
+
ev.preventDefault();
|
|
697
|
+
this.focusStep(steps.length - 1);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (key === 'Enter' || key === ' ') {
|
|
701
|
+
const focusedIndex = steps.findIndex((s) => s.isFocused());
|
|
702
|
+
if (focusedIndex >= 0) {
|
|
703
|
+
ev.preventDefault();
|
|
704
|
+
this.setIndex(focusedIndex);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
focusStep(i) {
|
|
709
|
+
const steps = this.steps();
|
|
710
|
+
const clamped = this.clamp(i, steps.length);
|
|
711
|
+
steps[clamped]?.focus();
|
|
712
|
+
}
|
|
713
|
+
/* =====================
|
|
714
|
+
* Rules
|
|
715
|
+
* ===================== */
|
|
716
|
+
clamp(i, n) {
|
|
717
|
+
if (n <= 0)
|
|
718
|
+
return 0;
|
|
719
|
+
if (!Number.isFinite(i))
|
|
720
|
+
return 0;
|
|
721
|
+
return Math.max(0, Math.min(n - 1, i));
|
|
722
|
+
}
|
|
723
|
+
canActivate(target) {
|
|
724
|
+
const steps = this.steps();
|
|
725
|
+
const current = this._index();
|
|
726
|
+
if (!this.linear())
|
|
727
|
+
return true;
|
|
728
|
+
if (target <= current)
|
|
729
|
+
return true;
|
|
730
|
+
for (let i = 0; i < target; i++) {
|
|
731
|
+
const s = steps[i];
|
|
732
|
+
if (!s)
|
|
733
|
+
return false;
|
|
734
|
+
if (!s.isComplete())
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
/* =====================
|
|
740
|
+
* Computed
|
|
741
|
+
* ===================== */
|
|
742
|
+
headerResolvedKlass = computed(() => this.orientation() === 'vertical' ? this.headerVerticalKlass() : this.headerKlass(), ...(ngDevMode ? [{ debugName: "headerResolvedKlass" }] : []));
|
|
743
|
+
orientationAttr = computed(() => this.orientation() === 'vertical' ? 'vertical' : 'horizontal', ...(ngDevMode ? [{ debugName: "orientationAttr" }] : []));
|
|
744
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngStepper, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
745
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.6", type: TngStepper, isStandalone: true, selector: "tng-stepper", inputs: { activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, defaultIndex: { classPropertyName: "defaultIndex", publicName: "defaultIndex", isSignal: true, isRequired: false, transformFunction: null }, linear: { classPropertyName: "linear", publicName: "linear", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, headerKlass: { classPropertyName: "headerKlass", publicName: "headerKlass", isSignal: true, isRequired: false, transformFunction: null }, headerVerticalKlass: { classPropertyName: "headerVerticalKlass", publicName: "headerVerticalKlass", isSignal: true, isRequired: false, transformFunction: null }, panelWrapKlass: { classPropertyName: "panelWrapKlass", publicName: "panelWrapKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeIndexChange: "activeIndexChange" }, queries: [{ propertyName: "steps", predicate: TngStep, descendants: true, isSignal: true }], ngImport: i0, template: "<div [class]=\"rootKlass()\">\n <!-- Step headers -->\n <div\n role=\"tablist\"\n [attr.aria-orientation]=\"orientationAttr()\"\n [class]=\"headerResolvedKlass()\"\n (keydown)=\"onKeydown($event)\"\n >\n <ng-content select=\"tng-step\"></ng-content>\n </div>\n\n <!-- Panels -->\n <div [class]=\"panelWrapKlass()\">\n <ng-content select=\"tng-step-panel\"></ng-content>\n </div>\n</div>\n" });
|
|
746
|
+
}
|
|
747
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngStepper, decorators: [{
|
|
748
|
+
type: Component,
|
|
749
|
+
args: [{ selector: 'tng-stepper', standalone: true, template: "<div [class]=\"rootKlass()\">\n <!-- Step headers -->\n <div\n role=\"tablist\"\n [attr.aria-orientation]=\"orientationAttr()\"\n [class]=\"headerResolvedKlass()\"\n (keydown)=\"onKeydown($event)\"\n >\n <ng-content select=\"tng-step\"></ng-content>\n </div>\n\n <!-- Panels -->\n <div [class]=\"panelWrapKlass()\">\n <ng-content select=\"tng-step-panel\"></ng-content>\n </div>\n</div>\n" }]
|
|
750
|
+
}], ctorParameters: () => [], propDecorators: { activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }], defaultIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultIndex", required: false }] }], linear: [{ type: i0.Input, args: [{ isSignal: true, alias: "linear", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], activeIndexChange: [{ type: i0.Output, args: ["activeIndexChange"] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], headerKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerKlass", required: false }] }], headerVerticalKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerVerticalKlass", required: false }] }], panelWrapKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWrapKlass", required: false }] }], steps: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TngStep), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
751
|
+
|
|
752
|
+
class TngStepPanel {
|
|
753
|
+
stepper = inject(TngStepper);
|
|
754
|
+
/** Panel index this content belongs to */
|
|
755
|
+
index = input.required(...(ngDevMode ? [{ debugName: "index" }] : []));
|
|
756
|
+
role = 'tabpanel';
|
|
757
|
+
get hidden() {
|
|
758
|
+
return !this.stepper.isActive(this.index());
|
|
759
|
+
}
|
|
760
|
+
// Optional: you can use this for debugging or conditional content
|
|
761
|
+
isActive = computed(() => this.stepper.isActive(this.index()), ...(ngDevMode ? [{ debugName: "isActive" }] : []));
|
|
762
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngStepPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
763
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngStepPanel, isStandalone: true, selector: "tng-step-panel", inputs: { index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.role": "this.role", "hidden": "this.hidden" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
|
|
764
|
+
}
|
|
765
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngStepPanel, decorators: [{
|
|
766
|
+
type: Component,
|
|
767
|
+
args: [{
|
|
768
|
+
selector: 'tng-step-panel',
|
|
769
|
+
standalone: true,
|
|
770
|
+
template: `<ng-content />`,
|
|
771
|
+
}]
|
|
772
|
+
}], propDecorators: { index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], role: [{
|
|
773
|
+
type: HostBinding,
|
|
774
|
+
args: ['attr.role']
|
|
775
|
+
}], hidden: [{
|
|
776
|
+
type: HostBinding,
|
|
777
|
+
args: ['hidden']
|
|
778
|
+
}] } });
|
|
18
779
|
|
|
19
780
|
/**
|
|
20
781
|
* Generated bundle index. Do not edit.
|
|
21
782
|
*/
|
|
783
|
+
|
|
784
|
+
export { TngBreadcrumbs, TngDrawer, TngMenu, TngMenuItem, TngMenuTemplate, TngPaginator, TngSidenav, TngSidenavFooterSlot, TngSidenavHeaderSlot, TngStep, TngStepPanel, TngStepper };
|
|
22
785
|
//# sourceMappingURL=tociva-tailng-ui-navigation.mjs.map
|