@tolle_/tolle-ui 0.0.1-beta
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/README.md +35 -0
- package/esm2022/lib/accordion-item.component.mjs +78 -0
- package/esm2022/lib/accordion.component.mjs +60 -0
- package/esm2022/lib/badge.component.mjs +76 -0
- package/esm2022/lib/button-group.component.mjs +25 -0
- package/esm2022/lib/button.component.mjs +70 -0
- package/esm2022/lib/calendar.component.mjs +315 -0
- package/esm2022/lib/card.component.mjs +94 -0
- package/esm2022/lib/checkbox.component.mjs +100 -0
- package/esm2022/lib/data-table.component.mjs +332 -0
- package/esm2022/lib/date-picker.component.mjs +232 -0
- package/esm2022/lib/date-range-picker.component.mjs +208 -0
- package/esm2022/lib/input.component.mjs +134 -0
- package/esm2022/lib/masked-input.component.mjs +179 -0
- package/esm2022/lib/modal-ref.mjs +31 -0
- package/esm2022/lib/modal-stack.service.mjs +26 -0
- package/esm2022/lib/modal.component.mjs +98 -0
- package/esm2022/lib/modal.mjs +27 -0
- package/esm2022/lib/modal.service.mjs +65 -0
- package/esm2022/lib/multi-select.component.mjs +231 -0
- package/esm2022/lib/pagination.component.mjs +279 -0
- package/esm2022/lib/range-calendar.component.mjs +285 -0
- package/esm2022/lib/select-group.component.mjs +28 -0
- package/esm2022/lib/select-item.component.mjs +84 -0
- package/esm2022/lib/select-separator.component.mjs +24 -0
- package/esm2022/lib/select.component.mjs +261 -0
- package/esm2022/lib/select.service.mjs +21 -0
- package/esm2022/lib/skeleton.component.mjs +34 -0
- package/esm2022/lib/switch.component.mjs +133 -0
- package/esm2022/lib/toast.service.mjs +59 -0
- package/esm2022/lib/tolle-cell.directive.mjs +22 -0
- package/esm2022/lib/tolle-config.mjs +11 -0
- package/esm2022/lib/tooltip.directive.mjs +71 -0
- package/esm2022/lib/types/date-range.mjs +2 -0
- package/esm2022/lib/utils/cn.mjs +6 -0
- package/esm2022/public-api.mjs +36 -0
- package/esm2022/tolle_-tolle-ui.mjs +5 -0
- package/fesm2022/tolle_-tolle-ui.mjs +3553 -0
- package/fesm2022/tolle_-tolle-ui.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/accordion-item.component.d.ts +13 -0
- package/lib/accordion.component.d.ts +14 -0
- package/lib/badge.component.d.ts +14 -0
- package/lib/button-group.component.d.ts +8 -0
- package/lib/button.component.d.ts +16 -0
- package/lib/calendar.component.d.ts +35 -0
- package/lib/card.component.d.ts +32 -0
- package/lib/checkbox.component.d.ts +23 -0
- package/lib/data-table.component.d.ts +45 -0
- package/lib/date-picker.component.d.ts +35 -0
- package/lib/date-range-picker.component.d.ts +36 -0
- package/lib/input.component.d.ts +27 -0
- package/lib/masked-input.component.d.ts +36 -0
- package/lib/modal-ref.d.ts +16 -0
- package/lib/modal-stack.service.d.ts +12 -0
- package/lib/modal.component.d.ts +19 -0
- package/lib/modal.d.ts +29 -0
- package/lib/modal.service.d.ts +18 -0
- package/lib/multi-select.component.d.ts +47 -0
- package/lib/pagination.component.d.ts +36 -0
- package/lib/range-calendar.component.d.ts +37 -0
- package/lib/select-group.component.d.ts +8 -0
- package/lib/select-item.component.d.ts +18 -0
- package/lib/select-separator.component.d.ts +8 -0
- package/lib/select.component.d.ts +45 -0
- package/lib/select.service.d.ts +10 -0
- package/lib/skeleton.component.d.ts +10 -0
- package/lib/switch.component.d.ts +39 -0
- package/lib/toast.service.d.ts +24 -0
- package/lib/tolle-cell.directive.d.ts +9 -0
- package/lib/tolle-config.d.ts +9 -0
- package/lib/tooltip.directive.d.ts +15 -0
- package/lib/types/date-range.d.ts +4 -0
- package/lib/utils/cn.d.ts +2 -0
- package/package.json +32 -0
- package/public-api.d.ts +32 -0
- package/theme.css +211 -0
|
@@ -0,0 +1,3553 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { Component, Input, forwardRef, Optional, HostListener, Injectable, ViewChild, ContentChildren, EventEmitter, Output, Directive, InjectionToken, inject, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector } from '@angular/core';
|
|
5
|
+
import * as i1$1 from '@angular/common';
|
|
6
|
+
import { CommonModule } from '@angular/common';
|
|
7
|
+
import { cva } from 'class-variance-authority';
|
|
8
|
+
import * as i2 from '@angular/forms';
|
|
9
|
+
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
10
|
+
import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
|
|
11
|
+
import * as i1 from '@tolle/ui/select.service';
|
|
12
|
+
import { Subject, Subscription } from 'rxjs';
|
|
13
|
+
import { BadgeComponent as BadgeComponent$1 } from '@tolle/ui/badge.component';
|
|
14
|
+
import { InputComponent as InputComponent$1 } from '@tolle/ui/input.component';
|
|
15
|
+
import { startOfWeek, startOfMonth, endOfWeek, endOfMonth, eachDayOfInterval, subMonths, subYears, addMonths, addYears, isSameMonth, setMonth, setYear, isSameDay, isToday, isBefore, startOfDay, parse, isValid, format, isWithinInterval } from 'date-fns';
|
|
16
|
+
import { SelectComponent as SelectComponent$1, SelectItemComponent as SelectItemComponent$1 } from '@tolle/ui';
|
|
17
|
+
import { PaginationComponent as PaginationComponent$1 } from '@tolle/ui/pagination.component';
|
|
18
|
+
import { TolleCellDirective as TolleCellDirective$1 } from '@tolle/ui/tolle-cell.directive';
|
|
19
|
+
import * as i1$2 from '@angular/cdk/overlay';
|
|
20
|
+
import { OverlayConfig } from '@angular/cdk/overlay';
|
|
21
|
+
import { ComponentPortal } from '@angular/cdk/portal';
|
|
22
|
+
|
|
23
|
+
function cn(...inputs) {
|
|
24
|
+
return twMerge(clsx(inputs));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 1. Define Component Variants (The "Recipe")
|
|
28
|
+
const buttonVariants = cva(
|
|
29
|
+
// Base styles applied to ALL buttons
|
|
30
|
+
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", {
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
34
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
35
|
+
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
|
36
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
37
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
38
|
+
link: "underline-offset-4 hover:underline text-primary",
|
|
39
|
+
},
|
|
40
|
+
size: {
|
|
41
|
+
default: "h-10 px-4 py-2",
|
|
42
|
+
xs: "h-8 px-2 py-1 text-xs",
|
|
43
|
+
sm: "h-9 rounded-md px-3",
|
|
44
|
+
lg: "h-11 rounded-md px-8",
|
|
45
|
+
"icon-xs": "h-8 w-8",
|
|
46
|
+
"icon-sm": "h-9 w-9",
|
|
47
|
+
icon: "h-10 w-10",
|
|
48
|
+
"icon-lg": "h-11 w-11",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
defaultVariants: {
|
|
52
|
+
variant: "default",
|
|
53
|
+
size: "default",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
class ButtonComponent {
|
|
57
|
+
// Allow consumers to pass a custom class to override styles
|
|
58
|
+
class = '';
|
|
59
|
+
// Expose the variants as Inputs
|
|
60
|
+
variant = 'default';
|
|
61
|
+
size = 'default';
|
|
62
|
+
// Calculate the final string of classes
|
|
63
|
+
get computedClass() {
|
|
64
|
+
return cn(buttonVariants({ variant: this.variant, size: this.size }), this.class);
|
|
65
|
+
}
|
|
66
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
67
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "tolle-button", inputs: { class: "class", variant: "variant", size: "size" }, ngImport: i0, template: `
|
|
68
|
+
<button [class]="computedClass">
|
|
69
|
+
<span class="flex items-center justify-center gap-2 w-full h-full">
|
|
70
|
+
<ng-content></ng-content>
|
|
71
|
+
</span>
|
|
72
|
+
</button>
|
|
73
|
+
`, isInline: true, styles: [":host ::ng-deep i{display:inline-flex;align-items:center;line-height:1;font-size:1.25em}:host ::ng-deep i:not(:only-child){margin-right:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
74
|
+
}
|
|
75
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
76
|
+
type: Component,
|
|
77
|
+
args: [{ selector: 'tolle-button', standalone: true, imports: [CommonModule], template: `
|
|
78
|
+
<button [class]="computedClass">
|
|
79
|
+
<span class="flex items-center justify-center gap-2 w-full h-full">
|
|
80
|
+
<ng-content></ng-content>
|
|
81
|
+
</span>
|
|
82
|
+
</button>
|
|
83
|
+
`, styles: [":host ::ng-deep i{display:inline-flex;align-items:center;line-height:1;font-size:1.25em}:host ::ng-deep i:not(:only-child){margin-right:.5rem}\n"] }]
|
|
84
|
+
}], propDecorators: { class: [{
|
|
85
|
+
type: Input
|
|
86
|
+
}], variant: [{
|
|
87
|
+
type: Input
|
|
88
|
+
}], size: [{
|
|
89
|
+
type: Input
|
|
90
|
+
}] } });
|
|
91
|
+
|
|
92
|
+
class InputComponent {
|
|
93
|
+
cdr;
|
|
94
|
+
type = 'text';
|
|
95
|
+
placeholder = '';
|
|
96
|
+
disabled = false;
|
|
97
|
+
error = false;
|
|
98
|
+
size = 'default';
|
|
99
|
+
containerClass = '';
|
|
100
|
+
class = '';
|
|
101
|
+
// Internal State
|
|
102
|
+
value = '';
|
|
103
|
+
// CVA Callbacks
|
|
104
|
+
onChange = () => { };
|
|
105
|
+
onTouched = () => { };
|
|
106
|
+
constructor(cdr) {
|
|
107
|
+
this.cdr = cdr;
|
|
108
|
+
}
|
|
109
|
+
// --- ControlValueAccessor Implementation ---
|
|
110
|
+
writeValue(value) {
|
|
111
|
+
this.value = value;
|
|
112
|
+
this.cdr.markForCheck();
|
|
113
|
+
}
|
|
114
|
+
registerOnChange(fn) {
|
|
115
|
+
this.onChange = fn;
|
|
116
|
+
}
|
|
117
|
+
registerOnTouched(fn) {
|
|
118
|
+
this.onTouched = fn;
|
|
119
|
+
}
|
|
120
|
+
setDisabledState(isDisabled) {
|
|
121
|
+
this.disabled = isDisabled;
|
|
122
|
+
this.cdr.markForCheck();
|
|
123
|
+
}
|
|
124
|
+
onInputChange(event) {
|
|
125
|
+
const val = event.target.value;
|
|
126
|
+
this.value = val;
|
|
127
|
+
this.onChange(val);
|
|
128
|
+
}
|
|
129
|
+
// --- Styling Logic ---
|
|
130
|
+
cn = cn;
|
|
131
|
+
get computedInputClass() {
|
|
132
|
+
return cn("flex w-full rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 transition-all", 'disabled:opacity-50 shadow-sm transition-shadow', this.size === 'xs' && "h-8 text-xs px-2", this.size === 'sm' && "h-9 px-3", this.size === 'default' && "h-10 px-3", this.size === 'lg' && "h-11 px-4 text-base", "group-has-[[prefix]]:pl-10 group-has-[[suffix]]:pr-10", this.size === 'xs' && "group-has-[[prefix]]:pl-8 group-has-[[suffix]]:pr-8", this.error && "border-destructive focus-visible:ring-destructive", this.class);
|
|
133
|
+
}
|
|
134
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
135
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: InputComponent, isStandalone: true, selector: "tolle-input", inputs: { type: "type", placeholder: "placeholder", disabled: "disabled", error: "error", size: "size", containerClass: "containerClass", class: "class" }, providers: [
|
|
136
|
+
{
|
|
137
|
+
provide: NG_VALUE_ACCESSOR,
|
|
138
|
+
useExisting: forwardRef(() => InputComponent),
|
|
139
|
+
multi: true
|
|
140
|
+
}
|
|
141
|
+
], ngImport: i0, template: `
|
|
142
|
+
<div [class]="cn('relative flex items-center w-full group', 'size-' + size, containerClass)">
|
|
143
|
+
|
|
144
|
+
<div class="absolute left-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
145
|
+
[class.left-2.5]="size === 'xs'">
|
|
146
|
+
<ng-content select="[prefix]"></ng-content>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<input
|
|
150
|
+
[type]="type"
|
|
151
|
+
[placeholder]="placeholder"
|
|
152
|
+
[disabled]="disabled"
|
|
153
|
+
[(ngModel)]="value"
|
|
154
|
+
(blur)="onTouched()"
|
|
155
|
+
(input)="onInputChange($event)"
|
|
156
|
+
[class]="computedInputClass"
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
<div class="absolute right-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
160
|
+
[class.right-2.5]="size === 'xs'">
|
|
161
|
+
<ng-content select="[suffix]"></ng-content>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
165
|
+
}
|
|
166
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InputComponent, decorators: [{
|
|
167
|
+
type: Component,
|
|
168
|
+
args: [{
|
|
169
|
+
selector: 'tolle-input',
|
|
170
|
+
standalone: true,
|
|
171
|
+
imports: [CommonModule, FormsModule],
|
|
172
|
+
providers: [
|
|
173
|
+
{
|
|
174
|
+
provide: NG_VALUE_ACCESSOR,
|
|
175
|
+
useExisting: forwardRef(() => InputComponent),
|
|
176
|
+
multi: true
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
template: `
|
|
180
|
+
<div [class]="cn('relative flex items-center w-full group', 'size-' + size, containerClass)">
|
|
181
|
+
|
|
182
|
+
<div class="absolute left-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
183
|
+
[class.left-2.5]="size === 'xs'">
|
|
184
|
+
<ng-content select="[prefix]"></ng-content>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<input
|
|
188
|
+
[type]="type"
|
|
189
|
+
[placeholder]="placeholder"
|
|
190
|
+
[disabled]="disabled"
|
|
191
|
+
[(ngModel)]="value"
|
|
192
|
+
(blur)="onTouched()"
|
|
193
|
+
(input)="onInputChange($event)"
|
|
194
|
+
[class]="computedInputClass"
|
|
195
|
+
/>
|
|
196
|
+
|
|
197
|
+
<div class="absolute right-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
198
|
+
[class.right-2.5]="size === 'xs'">
|
|
199
|
+
<ng-content select="[suffix]"></ng-content>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
`,
|
|
203
|
+
}]
|
|
204
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { type: [{
|
|
205
|
+
type: Input
|
|
206
|
+
}], placeholder: [{
|
|
207
|
+
type: Input
|
|
208
|
+
}], disabled: [{
|
|
209
|
+
type: Input
|
|
210
|
+
}], error: [{
|
|
211
|
+
type: Input
|
|
212
|
+
}], size: [{
|
|
213
|
+
type: Input
|
|
214
|
+
}], containerClass: [{
|
|
215
|
+
type: Input
|
|
216
|
+
}], class: [{
|
|
217
|
+
type: Input
|
|
218
|
+
}] } });
|
|
219
|
+
|
|
220
|
+
class CardComponent {
|
|
221
|
+
class = '';
|
|
222
|
+
cn = cn;
|
|
223
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
224
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardComponent, isStandalone: true, selector: "tolle-card", inputs: { class: "class" }, ngImport: i0, template: `
|
|
225
|
+
<div [class]="cn('rounded-xl border border-border bg-card text-card-foreground shadow', class)">
|
|
226
|
+
<ng-content></ng-content>
|
|
227
|
+
</div>
|
|
228
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
229
|
+
}
|
|
230
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, decorators: [{
|
|
231
|
+
type: Component,
|
|
232
|
+
args: [{
|
|
233
|
+
selector: 'tolle-card',
|
|
234
|
+
standalone: true,
|
|
235
|
+
imports: [CommonModule],
|
|
236
|
+
template: `
|
|
237
|
+
<div [class]="cn('rounded-xl border border-border bg-card text-card-foreground shadow', class)">
|
|
238
|
+
<ng-content></ng-content>
|
|
239
|
+
</div>
|
|
240
|
+
`,
|
|
241
|
+
}]
|
|
242
|
+
}], propDecorators: { class: [{
|
|
243
|
+
type: Input
|
|
244
|
+
}] } });
|
|
245
|
+
class CardHeaderComponent {
|
|
246
|
+
class = '';
|
|
247
|
+
cn = cn;
|
|
248
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
249
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardHeaderComponent, isStandalone: true, selector: "tolle-card-header", inputs: { class: "class" }, ngImport: i0, template: `<div [class]="cn('flex flex-col space-y-1.5 p-6', class)"><ng-content></ng-content></div>`, isInline: true });
|
|
250
|
+
}
|
|
251
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardHeaderComponent, decorators: [{
|
|
252
|
+
type: Component,
|
|
253
|
+
args: [{
|
|
254
|
+
selector: 'tolle-card-header',
|
|
255
|
+
standalone: true,
|
|
256
|
+
template: `<div [class]="cn('flex flex-col space-y-1.5 p-6', class)"><ng-content></ng-content></div>`,
|
|
257
|
+
}]
|
|
258
|
+
}], propDecorators: { class: [{
|
|
259
|
+
type: Input
|
|
260
|
+
}] } });
|
|
261
|
+
class CardTitleComponent {
|
|
262
|
+
class = '';
|
|
263
|
+
cn = cn;
|
|
264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardTitleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardTitleComponent, isStandalone: true, selector: "tolle-card-title", inputs: { class: "class" }, ngImport: i0, template: `<h3 [class]="cn('font-semibold leading-none tracking-tight', class)"><ng-content></ng-content></h3>`, isInline: true });
|
|
266
|
+
}
|
|
267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardTitleComponent, decorators: [{
|
|
268
|
+
type: Component,
|
|
269
|
+
args: [{
|
|
270
|
+
selector: 'tolle-card-title',
|
|
271
|
+
standalone: true,
|
|
272
|
+
template: `<h3 [class]="cn('font-semibold leading-none tracking-tight', class)"><ng-content></ng-content></h3>`,
|
|
273
|
+
}]
|
|
274
|
+
}], propDecorators: { class: [{
|
|
275
|
+
type: Input
|
|
276
|
+
}] } });
|
|
277
|
+
class CardContentComponent {
|
|
278
|
+
class = '';
|
|
279
|
+
cn = cn;
|
|
280
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
281
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardContentComponent, isStandalone: true, selector: "tolle-card-content", inputs: { class: "class" }, ngImport: i0, template: `<div [class]="cn('p-6 pt-0', class)"><ng-content></ng-content></div>`, isInline: true });
|
|
282
|
+
}
|
|
283
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardContentComponent, decorators: [{
|
|
284
|
+
type: Component,
|
|
285
|
+
args: [{
|
|
286
|
+
selector: 'tolle-card-content',
|
|
287
|
+
standalone: true,
|
|
288
|
+
template: `<div [class]="cn('p-6 pt-0', class)"><ng-content></ng-content></div>`,
|
|
289
|
+
}]
|
|
290
|
+
}], propDecorators: { class: [{
|
|
291
|
+
type: Input
|
|
292
|
+
}] } });
|
|
293
|
+
class CardFooterComponent {
|
|
294
|
+
class = '';
|
|
295
|
+
cn = cn;
|
|
296
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
297
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardFooterComponent, isStandalone: true, selector: "tolle-card-footer", inputs: { class: "class" }, ngImport: i0, template: `<div [class]="cn('flex items-center p-6 pt-0', class)"><ng-content></ng-content></div>`, isInline: true });
|
|
298
|
+
}
|
|
299
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardFooterComponent, decorators: [{
|
|
300
|
+
type: Component,
|
|
301
|
+
args: [{
|
|
302
|
+
selector: 'tolle-card-footer',
|
|
303
|
+
standalone: true,
|
|
304
|
+
template: `<div [class]="cn('flex items-center p-6 pt-0', class)"><ng-content></ng-content></div>`,
|
|
305
|
+
}]
|
|
306
|
+
}], propDecorators: { class: [{
|
|
307
|
+
type: Input
|
|
308
|
+
}] } });
|
|
309
|
+
|
|
310
|
+
class SelectItemComponent {
|
|
311
|
+
selectService;
|
|
312
|
+
el;
|
|
313
|
+
value;
|
|
314
|
+
class = '';
|
|
315
|
+
selected = false;
|
|
316
|
+
hidden = false;
|
|
317
|
+
constructor(selectService, el) {
|
|
318
|
+
this.selectService = selectService;
|
|
319
|
+
this.el = el;
|
|
320
|
+
}
|
|
321
|
+
// Helper method for the parent to get the searchable text
|
|
322
|
+
getLabel() {
|
|
323
|
+
return this.el.nativeElement.innerText || '';
|
|
324
|
+
}
|
|
325
|
+
onClick(event) {
|
|
326
|
+
if (this.hidden)
|
|
327
|
+
return;
|
|
328
|
+
event.stopPropagation();
|
|
329
|
+
if (this.selectService) {
|
|
330
|
+
// Get the text content to show in the trigger button
|
|
331
|
+
const label = this.el.nativeElement.innerText.trim();
|
|
332
|
+
this.selectService.registerClick(this.value, label);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
cn = cn;
|
|
336
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectItemComponent, deps: [{ token: i1.SelectService, optional: true }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
337
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SelectItemComponent, isStandalone: true, selector: "tolle-select-item", inputs: { value: "value", class: "class", selected: "selected" }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0, template: `
|
|
338
|
+
<div
|
|
339
|
+
*ngIf="!hidden"
|
|
340
|
+
[class]="cn(
|
|
341
|
+
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors',
|
|
342
|
+
selected ? 'bg-accent text-accent-foreground' : '',
|
|
343
|
+
class
|
|
344
|
+
)"
|
|
345
|
+
>
|
|
346
|
+
<span *ngIf="selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
347
|
+
<i class="ri-check-line text-primary"></i>
|
|
348
|
+
</span>
|
|
349
|
+
<ng-content></ng-content>
|
|
350
|
+
</div>
|
|
351
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
352
|
+
}
|
|
353
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectItemComponent, decorators: [{
|
|
354
|
+
type: Component,
|
|
355
|
+
args: [{
|
|
356
|
+
selector: 'tolle-select-item',
|
|
357
|
+
standalone: true,
|
|
358
|
+
imports: [CommonModule],
|
|
359
|
+
template: `
|
|
360
|
+
<div
|
|
361
|
+
*ngIf="!hidden"
|
|
362
|
+
[class]="cn(
|
|
363
|
+
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors',
|
|
364
|
+
selected ? 'bg-accent text-accent-foreground' : '',
|
|
365
|
+
class
|
|
366
|
+
)"
|
|
367
|
+
>
|
|
368
|
+
<span *ngIf="selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
369
|
+
<i class="ri-check-line text-primary"></i>
|
|
370
|
+
</span>
|
|
371
|
+
<ng-content></ng-content>
|
|
372
|
+
</div>
|
|
373
|
+
`,
|
|
374
|
+
}]
|
|
375
|
+
}], ctorParameters: () => [{ type: i1.SelectService, decorators: [{
|
|
376
|
+
type: Optional
|
|
377
|
+
}] }, { type: i0.ElementRef }], propDecorators: { value: [{
|
|
378
|
+
type: Input
|
|
379
|
+
}], class: [{
|
|
380
|
+
type: Input
|
|
381
|
+
}], selected: [{
|
|
382
|
+
type: Input
|
|
383
|
+
}], onClick: [{
|
|
384
|
+
type: HostListener,
|
|
385
|
+
args: ['click', ['$event']]
|
|
386
|
+
}] } });
|
|
387
|
+
|
|
388
|
+
class SelectService {
|
|
389
|
+
// Emits the value of the clicked item
|
|
390
|
+
selectedValueSource = new Subject();
|
|
391
|
+
selectedValue$ = this.selectedValueSource.asObservable();
|
|
392
|
+
// Emits the label/text of the clicked item
|
|
393
|
+
selectedLabelSource = new Subject();
|
|
394
|
+
selectedLabel$ = this.selectedLabelSource.asObservable();
|
|
395
|
+
registerClick(value, label) {
|
|
396
|
+
this.selectedValueSource.next(value);
|
|
397
|
+
this.selectedLabelSource.next(label);
|
|
398
|
+
}
|
|
399
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
400
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectService });
|
|
401
|
+
}
|
|
402
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectService, decorators: [{
|
|
403
|
+
type: Injectable
|
|
404
|
+
}] });
|
|
405
|
+
|
|
406
|
+
class SelectComponent {
|
|
407
|
+
selectService;
|
|
408
|
+
placeholder = 'Select an option';
|
|
409
|
+
class = '';
|
|
410
|
+
disabled = false;
|
|
411
|
+
searchable = false;
|
|
412
|
+
size = 'default';
|
|
413
|
+
trigger;
|
|
414
|
+
popover;
|
|
415
|
+
items;
|
|
416
|
+
sub = new Subscription();
|
|
417
|
+
searchQuery = '';
|
|
418
|
+
noResults = false;
|
|
419
|
+
isOpen = false;
|
|
420
|
+
value = null;
|
|
421
|
+
selectedLabel = '';
|
|
422
|
+
cleanupAutoUpdate;
|
|
423
|
+
onChange = () => { };
|
|
424
|
+
onTouched = () => { };
|
|
425
|
+
cn = cn;
|
|
426
|
+
constructor(selectService) {
|
|
427
|
+
this.selectService = selectService;
|
|
428
|
+
this.sub.add(this.selectService.selectedValue$.subscribe(val => {
|
|
429
|
+
this.value = val;
|
|
430
|
+
this.onChange(val);
|
|
431
|
+
this.updateItemSelection();
|
|
432
|
+
}));
|
|
433
|
+
this.sub.add(this.selectService.selectedLabel$.subscribe(label => {
|
|
434
|
+
this.selectedLabel = label;
|
|
435
|
+
this.close();
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
// UPDATED: Centralized sizing logic for the trigger
|
|
439
|
+
get computedTriggerClass() {
|
|
440
|
+
return cn('flex w-full items-center justify-between rounded-md border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring focus:ring-ring focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 transition-all', 'disabled:opacity-50 shadow-sm transition-shadow', this.size === 'xs' && 'h-8 px-2 text-xs', this.size === 'sm' && 'h-9 px-3 text-sm', this.size === 'default' && 'h-10 px-3 text-sm', this.size === 'lg' && 'h-11 px-4 text-base', this.class);
|
|
441
|
+
}
|
|
442
|
+
// UPDATED: Dynamic icon sizing relative to the trigger size
|
|
443
|
+
get iconClass() {
|
|
444
|
+
return cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform duration-200', this.isOpen ? 'rotate-180' : '', (this.size === 'xs' || this.size === 'sm') ? 'text-[14px]' : 'text-[18px]');
|
|
445
|
+
}
|
|
446
|
+
ngAfterContentInit() {
|
|
447
|
+
this.updateItemSelection();
|
|
448
|
+
this.items.changes.subscribe(() => this.updateItemSelection());
|
|
449
|
+
}
|
|
450
|
+
updateItemSelection() {
|
|
451
|
+
if (this.items) {
|
|
452
|
+
this.items.forEach(item => {
|
|
453
|
+
item.selected = item.value === this.value;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
toggle() {
|
|
458
|
+
if (this.disabled)
|
|
459
|
+
return;
|
|
460
|
+
this.isOpen ? this.close() : this.open();
|
|
461
|
+
}
|
|
462
|
+
open() {
|
|
463
|
+
this.isOpen = true;
|
|
464
|
+
// Tick to ensure DOM is rendered before positioning
|
|
465
|
+
setTimeout(() => this.updatePosition());
|
|
466
|
+
}
|
|
467
|
+
close() {
|
|
468
|
+
this.isOpen = false;
|
|
469
|
+
this.searchQuery = '';
|
|
470
|
+
this.onSearchChange('');
|
|
471
|
+
if (this.cleanupAutoUpdate)
|
|
472
|
+
this.cleanupAutoUpdate();
|
|
473
|
+
}
|
|
474
|
+
updatePosition() {
|
|
475
|
+
if (!this.trigger || !this.popover)
|
|
476
|
+
return;
|
|
477
|
+
this.cleanupAutoUpdate = autoUpdate(this.trigger.nativeElement, this.popover.nativeElement, () => {
|
|
478
|
+
computePosition(this.trigger.nativeElement, this.popover.nativeElement, {
|
|
479
|
+
placement: 'bottom-start',
|
|
480
|
+
middleware: [offset(4), flip(), shift({ padding: 8 })],
|
|
481
|
+
}).then(({ x, y }) => {
|
|
482
|
+
Object.assign(this.popover.nativeElement.style, {
|
|
483
|
+
left: `${x}px`,
|
|
484
|
+
top: `${y}px`,
|
|
485
|
+
visibility: 'visible',
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
onSearchChange(query) {
|
|
491
|
+
const filter = (query || '').toLowerCase().trim();
|
|
492
|
+
let visibleCount = 0;
|
|
493
|
+
this.items.forEach(item => {
|
|
494
|
+
const text = item.getLabel().toLowerCase();
|
|
495
|
+
const isVisible = text.includes(filter);
|
|
496
|
+
item.hidden = !isVisible;
|
|
497
|
+
if (isVisible)
|
|
498
|
+
visibleCount++;
|
|
499
|
+
});
|
|
500
|
+
this.noResults = visibleCount === 0 && filter !== '';
|
|
501
|
+
}
|
|
502
|
+
onDocumentClick(event) {
|
|
503
|
+
if (this.isOpen && !this.trigger.nativeElement.contains(event.target) && !this.popover.nativeElement.contains(event.target)) {
|
|
504
|
+
this.close();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
writeValue(value) {
|
|
508
|
+
this.value = value;
|
|
509
|
+
this.updateItemSelection();
|
|
510
|
+
if (this.items) {
|
|
511
|
+
const found = this.items.find(i => i.value === value);
|
|
512
|
+
if (found)
|
|
513
|
+
this.selectedLabel = found.getLabel();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
517
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
518
|
+
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
519
|
+
ngOnDestroy() {
|
|
520
|
+
this.sub.unsubscribe();
|
|
521
|
+
if (this.cleanupAutoUpdate)
|
|
522
|
+
this.cleanupAutoUpdate();
|
|
523
|
+
}
|
|
524
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectComponent, deps: [{ token: SelectService }], target: i0.ɵɵFactoryTarget.Component });
|
|
525
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SelectComponent, isStandalone: true, selector: "tolle-select", inputs: { placeholder: "placeholder", class: "class", disabled: "disabled", searchable: "searchable", size: "size" }, host: { listeners: { "document:mousedown": "onDocumentClick($event)" } }, providers: [
|
|
526
|
+
SelectService,
|
|
527
|
+
{
|
|
528
|
+
provide: NG_VALUE_ACCESSOR,
|
|
529
|
+
useExisting: forwardRef(() => SelectComponent),
|
|
530
|
+
multi: true
|
|
531
|
+
}
|
|
532
|
+
], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
|
|
533
|
+
<div [class]="cn('relative w-full', 'size-' + size)" #container>
|
|
534
|
+
<button
|
|
535
|
+
type="button"
|
|
536
|
+
#trigger
|
|
537
|
+
(click)="toggle()"
|
|
538
|
+
[disabled]="disabled"
|
|
539
|
+
[class]="computedTriggerClass"
|
|
540
|
+
>
|
|
541
|
+
<span class="truncate" [class.text-muted-foreground]="!selectedLabel">
|
|
542
|
+
{{ selectedLabel || placeholder }}
|
|
543
|
+
</span>
|
|
544
|
+
<i [class]="iconClass"></i>
|
|
545
|
+
</button>
|
|
546
|
+
|
|
547
|
+
<div
|
|
548
|
+
#popover
|
|
549
|
+
*ngIf="isOpen"
|
|
550
|
+
class="absolute bg-popover z-50 min-w-full overflow-hidden rounded-md border border-border text-popover-foreground bg-background shadow-md"
|
|
551
|
+
style="visibility: hidden; top: 0; left: 0;"
|
|
552
|
+
>
|
|
553
|
+
<div *ngIf="searchable" class="p-2 border-b border-border bg-popover">
|
|
554
|
+
<tolle-input
|
|
555
|
+
size="xs"
|
|
556
|
+
placeholder="Search..."
|
|
557
|
+
[(ngModel)]="searchQuery"
|
|
558
|
+
(ngModelChange)="onSearchChange($event)"
|
|
559
|
+
class="w-full">
|
|
560
|
+
<i prefix class="ri-search-line"></i>
|
|
561
|
+
</tolle-input>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div class="p-1 max-h-60 overflow-y-auto">
|
|
565
|
+
<ng-content></ng-content>
|
|
566
|
+
<div *ngIf="noResults" class="py-6 text-center text-sm text-muted-foreground">
|
|
567
|
+
No results found.
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["type", "placeholder", "disabled", "error", "size", "containerClass", "class"] }] });
|
|
573
|
+
}
|
|
574
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectComponent, decorators: [{
|
|
575
|
+
type: Component,
|
|
576
|
+
args: [{
|
|
577
|
+
selector: 'tolle-select',
|
|
578
|
+
standalone: true,
|
|
579
|
+
imports: [CommonModule, FormsModule, InputComponent],
|
|
580
|
+
providers: [
|
|
581
|
+
SelectService,
|
|
582
|
+
{
|
|
583
|
+
provide: NG_VALUE_ACCESSOR,
|
|
584
|
+
useExisting: forwardRef(() => SelectComponent),
|
|
585
|
+
multi: true
|
|
586
|
+
}
|
|
587
|
+
],
|
|
588
|
+
template: `
|
|
589
|
+
<div [class]="cn('relative w-full', 'size-' + size)" #container>
|
|
590
|
+
<button
|
|
591
|
+
type="button"
|
|
592
|
+
#trigger
|
|
593
|
+
(click)="toggle()"
|
|
594
|
+
[disabled]="disabled"
|
|
595
|
+
[class]="computedTriggerClass"
|
|
596
|
+
>
|
|
597
|
+
<span class="truncate" [class.text-muted-foreground]="!selectedLabel">
|
|
598
|
+
{{ selectedLabel || placeholder }}
|
|
599
|
+
</span>
|
|
600
|
+
<i [class]="iconClass"></i>
|
|
601
|
+
</button>
|
|
602
|
+
|
|
603
|
+
<div
|
|
604
|
+
#popover
|
|
605
|
+
*ngIf="isOpen"
|
|
606
|
+
class="absolute bg-popover z-50 min-w-full overflow-hidden rounded-md border border-border text-popover-foreground bg-background shadow-md"
|
|
607
|
+
style="visibility: hidden; top: 0; left: 0;"
|
|
608
|
+
>
|
|
609
|
+
<div *ngIf="searchable" class="p-2 border-b border-border bg-popover">
|
|
610
|
+
<tolle-input
|
|
611
|
+
size="xs"
|
|
612
|
+
placeholder="Search..."
|
|
613
|
+
[(ngModel)]="searchQuery"
|
|
614
|
+
(ngModelChange)="onSearchChange($event)"
|
|
615
|
+
class="w-full">
|
|
616
|
+
<i prefix class="ri-search-line"></i>
|
|
617
|
+
</tolle-input>
|
|
618
|
+
</div>
|
|
619
|
+
|
|
620
|
+
<div class="p-1 max-h-60 overflow-y-auto">
|
|
621
|
+
<ng-content></ng-content>
|
|
622
|
+
<div *ngIf="noResults" class="py-6 text-center text-sm text-muted-foreground">
|
|
623
|
+
No results found.
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
`,
|
|
629
|
+
}]
|
|
630
|
+
}], ctorParameters: () => [{ type: SelectService }], propDecorators: { placeholder: [{
|
|
631
|
+
type: Input
|
|
632
|
+
}], class: [{
|
|
633
|
+
type: Input
|
|
634
|
+
}], disabled: [{
|
|
635
|
+
type: Input
|
|
636
|
+
}], searchable: [{
|
|
637
|
+
type: Input
|
|
638
|
+
}], size: [{
|
|
639
|
+
type: Input
|
|
640
|
+
}], trigger: [{
|
|
641
|
+
type: ViewChild,
|
|
642
|
+
args: ['trigger']
|
|
643
|
+
}], popover: [{
|
|
644
|
+
type: ViewChild,
|
|
645
|
+
args: ['popover']
|
|
646
|
+
}], items: [{
|
|
647
|
+
type: ContentChildren,
|
|
648
|
+
args: [SelectItemComponent, { descendants: true }]
|
|
649
|
+
}], onDocumentClick: [{
|
|
650
|
+
type: HostListener,
|
|
651
|
+
args: ['document:mousedown', ['$event']]
|
|
652
|
+
}] } });
|
|
653
|
+
|
|
654
|
+
class SelectGroupComponent {
|
|
655
|
+
class = '';
|
|
656
|
+
cn = cn;
|
|
657
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
658
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SelectGroupComponent, isStandalone: true, selector: "tolle-select-group", inputs: { class: "class" }, ngImport: i0, template: `
|
|
659
|
+
<div [class]="cn('px-2 py-1.5 text-sm font-semibold text-muted-foreground', class)">
|
|
660
|
+
<ng-content></ng-content>
|
|
661
|
+
</div>
|
|
662
|
+
`, isInline: true });
|
|
663
|
+
}
|
|
664
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectGroupComponent, decorators: [{
|
|
665
|
+
type: Component,
|
|
666
|
+
args: [{
|
|
667
|
+
selector: 'tolle-select-group',
|
|
668
|
+
standalone: true,
|
|
669
|
+
template: `
|
|
670
|
+
<div [class]="cn('px-2 py-1.5 text-sm font-semibold text-muted-foreground', class)">
|
|
671
|
+
<ng-content></ng-content>
|
|
672
|
+
</div>
|
|
673
|
+
`,
|
|
674
|
+
}]
|
|
675
|
+
}], propDecorators: { class: [{
|
|
676
|
+
type: Input
|
|
677
|
+
}] } });
|
|
678
|
+
|
|
679
|
+
class SelectSeparatorComponent {
|
|
680
|
+
class = '';
|
|
681
|
+
cn = cn;
|
|
682
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectSeparatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
683
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SelectSeparatorComponent, isStandalone: true, selector: "tolle-select-separator", inputs: { class: "class" }, ngImport: i0, template: `
|
|
684
|
+
<div [class]="cn('-mx-1 my-1 h-px bg-border', class)"></div>
|
|
685
|
+
`, isInline: true });
|
|
686
|
+
}
|
|
687
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectSeparatorComponent, decorators: [{
|
|
688
|
+
type: Component,
|
|
689
|
+
args: [{
|
|
690
|
+
selector: 'tolle-select-separator',
|
|
691
|
+
standalone: true,
|
|
692
|
+
template: `
|
|
693
|
+
<div [class]="cn('-mx-1 my-1 h-px bg-border', class)"></div>
|
|
694
|
+
`,
|
|
695
|
+
}]
|
|
696
|
+
}], propDecorators: { class: [{
|
|
697
|
+
type: Input
|
|
698
|
+
}] } });
|
|
699
|
+
|
|
700
|
+
class SwitchComponent {
|
|
701
|
+
cdr;
|
|
702
|
+
class = '';
|
|
703
|
+
disabled = false;
|
|
704
|
+
size = 'default';
|
|
705
|
+
checked = false;
|
|
706
|
+
onChange = () => { };
|
|
707
|
+
onTouched = () => { };
|
|
708
|
+
constructor(cdr) {
|
|
709
|
+
this.cdr = cdr;
|
|
710
|
+
}
|
|
711
|
+
get sizeClasses() {
|
|
712
|
+
const sizes = {
|
|
713
|
+
xs: {
|
|
714
|
+
track: 'h-4 w-7',
|
|
715
|
+
thumb: 'h-3 w-3',
|
|
716
|
+
translate: 'translate-x-3'
|
|
717
|
+
},
|
|
718
|
+
sm: {
|
|
719
|
+
track: 'h-5 w-9',
|
|
720
|
+
thumb: 'h-4 w-4',
|
|
721
|
+
translate: 'translate-x-4'
|
|
722
|
+
},
|
|
723
|
+
default: {
|
|
724
|
+
track: 'h-6 w-11',
|
|
725
|
+
thumb: 'h-5 w-5',
|
|
726
|
+
translate: 'translate-x-5'
|
|
727
|
+
},
|
|
728
|
+
lg: {
|
|
729
|
+
track: 'h-7 w-[3.25rem]',
|
|
730
|
+
thumb: 'h-6 w-6',
|
|
731
|
+
translate: 'translate-x-6'
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
return sizes[this.size];
|
|
735
|
+
}
|
|
736
|
+
cn = cn;
|
|
737
|
+
toggle() {
|
|
738
|
+
if (this.disabled)
|
|
739
|
+
return;
|
|
740
|
+
this.checked = !this.checked;
|
|
741
|
+
this.onChange(this.checked);
|
|
742
|
+
this.onTouched();
|
|
743
|
+
}
|
|
744
|
+
// --- ControlValueAccessor ---
|
|
745
|
+
writeValue(value) {
|
|
746
|
+
this.checked = value;
|
|
747
|
+
this.cdr.markForCheck();
|
|
748
|
+
}
|
|
749
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
750
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
751
|
+
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
752
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SwitchComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
753
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SwitchComponent, isStandalone: true, selector: "tolle-switch", inputs: { class: "class", disabled: "disabled", size: "size" }, providers: [
|
|
754
|
+
{
|
|
755
|
+
provide: NG_VALUE_ACCESSOR,
|
|
756
|
+
useExisting: forwardRef(() => SwitchComponent),
|
|
757
|
+
multi: true
|
|
758
|
+
}
|
|
759
|
+
], ngImport: i0, template: `
|
|
760
|
+
<button
|
|
761
|
+
type="button"
|
|
762
|
+
role="switch"
|
|
763
|
+
[attr.aria-checked]="checked"
|
|
764
|
+
[disabled]="disabled"
|
|
765
|
+
(click)="toggle()"
|
|
766
|
+
[class]="cn(
|
|
767
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',
|
|
768
|
+
sizeClasses.track,
|
|
769
|
+
checked ? 'bg-primary' : 'bg-input',
|
|
770
|
+
class
|
|
771
|
+
)"
|
|
772
|
+
>
|
|
773
|
+
<span
|
|
774
|
+
[class]="cn(
|
|
775
|
+
'pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform',
|
|
776
|
+
sizeClasses.thumb,
|
|
777
|
+
checked ? sizeClasses.translate : 'translate-x-0'
|
|
778
|
+
)"
|
|
779
|
+
></span>
|
|
780
|
+
</button>
|
|
781
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
782
|
+
}
|
|
783
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SwitchComponent, decorators: [{
|
|
784
|
+
type: Component,
|
|
785
|
+
args: [{
|
|
786
|
+
selector: 'tolle-switch',
|
|
787
|
+
standalone: true,
|
|
788
|
+
imports: [CommonModule],
|
|
789
|
+
providers: [
|
|
790
|
+
{
|
|
791
|
+
provide: NG_VALUE_ACCESSOR,
|
|
792
|
+
useExisting: forwardRef(() => SwitchComponent),
|
|
793
|
+
multi: true
|
|
794
|
+
}
|
|
795
|
+
],
|
|
796
|
+
template: `
|
|
797
|
+
<button
|
|
798
|
+
type="button"
|
|
799
|
+
role="switch"
|
|
800
|
+
[attr.aria-checked]="checked"
|
|
801
|
+
[disabled]="disabled"
|
|
802
|
+
(click)="toggle()"
|
|
803
|
+
[class]="cn(
|
|
804
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',
|
|
805
|
+
sizeClasses.track,
|
|
806
|
+
checked ? 'bg-primary' : 'bg-input',
|
|
807
|
+
class
|
|
808
|
+
)"
|
|
809
|
+
>
|
|
810
|
+
<span
|
|
811
|
+
[class]="cn(
|
|
812
|
+
'pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform',
|
|
813
|
+
sizeClasses.thumb,
|
|
814
|
+
checked ? sizeClasses.translate : 'translate-x-0'
|
|
815
|
+
)"
|
|
816
|
+
></span>
|
|
817
|
+
</button>
|
|
818
|
+
`
|
|
819
|
+
}]
|
|
820
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { class: [{
|
|
821
|
+
type: Input
|
|
822
|
+
}], disabled: [{
|
|
823
|
+
type: Input
|
|
824
|
+
}], size: [{
|
|
825
|
+
type: Input
|
|
826
|
+
}] } });
|
|
827
|
+
|
|
828
|
+
class BadgeComponent {
|
|
829
|
+
variant = 'default';
|
|
830
|
+
size = 'default';
|
|
831
|
+
removable = false; // Toggle the "Pill" remove icon
|
|
832
|
+
class = '';
|
|
833
|
+
onRemove = new EventEmitter();
|
|
834
|
+
cn = cn;
|
|
835
|
+
get computedClass() {
|
|
836
|
+
return cn(
|
|
837
|
+
// Base styles - Pills are always rounded-full
|
|
838
|
+
'inline-flex items-center justify-center rounded-full border px-2 py-0.5 font-medium transition-colors gap-1',
|
|
839
|
+
// Variants (Google Dark Mode theme)
|
|
840
|
+
this.variant === 'default' && 'border-transparent bg-primary text-primary-foreground', this.variant === 'secondary' && 'border-transparent bg-secondary text-secondary-foreground', this.variant === 'outline' && 'text-foreground border-border bg-transparent', this.variant === 'destructive' && 'border-transparent bg-destructive text-destructive-foreground',
|
|
841
|
+
// Sizing
|
|
842
|
+
this.size === 'xs' && 'px-1.5 py-0 text-[10px]', this.size === 'sm' && 'px-2 py-0 text-[11px]', this.size === 'default' && 'text-xs', this.class);
|
|
843
|
+
}
|
|
844
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
845
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: BadgeComponent, isStandalone: true, selector: "tolle-badge", inputs: { variant: "variant", size: "size", removable: "removable", class: "class" }, outputs: { onRemove: "onRemove" }, ngImport: i0, template: `
|
|
846
|
+
<div [class]="computedClass">
|
|
847
|
+
<ng-content select="[prefix]"></ng-content>
|
|
848
|
+
|
|
849
|
+
<span class="truncate">
|
|
850
|
+
<ng-content></ng-content>
|
|
851
|
+
</span>
|
|
852
|
+
|
|
853
|
+
<button
|
|
854
|
+
*ngIf="removable"
|
|
855
|
+
(click)="onRemove.emit($event)"
|
|
856
|
+
class="ml-1 -mr-1 rounded-full p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
|
|
857
|
+
>
|
|
858
|
+
<i class="ri-close-line text-[1.1em]"></i>
|
|
859
|
+
</button>
|
|
860
|
+
</div>
|
|
861
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
862
|
+
}
|
|
863
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BadgeComponent, decorators: [{
|
|
864
|
+
type: Component,
|
|
865
|
+
args: [{
|
|
866
|
+
selector: 'tolle-badge',
|
|
867
|
+
standalone: true,
|
|
868
|
+
imports: [CommonModule],
|
|
869
|
+
template: `
|
|
870
|
+
<div [class]="computedClass">
|
|
871
|
+
<ng-content select="[prefix]"></ng-content>
|
|
872
|
+
|
|
873
|
+
<span class="truncate">
|
|
874
|
+
<ng-content></ng-content>
|
|
875
|
+
</span>
|
|
876
|
+
|
|
877
|
+
<button
|
|
878
|
+
*ngIf="removable"
|
|
879
|
+
(click)="onRemove.emit($event)"
|
|
880
|
+
class="ml-1 -mr-1 rounded-full p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
|
|
881
|
+
>
|
|
882
|
+
<i class="ri-close-line text-[1.1em]"></i>
|
|
883
|
+
</button>
|
|
884
|
+
</div>
|
|
885
|
+
`,
|
|
886
|
+
}]
|
|
887
|
+
}], propDecorators: { variant: [{
|
|
888
|
+
type: Input
|
|
889
|
+
}], size: [{
|
|
890
|
+
type: Input
|
|
891
|
+
}], removable: [{
|
|
892
|
+
type: Input
|
|
893
|
+
}], class: [{
|
|
894
|
+
type: Input
|
|
895
|
+
}], onRemove: [{
|
|
896
|
+
type: Output
|
|
897
|
+
}] } });
|
|
898
|
+
|
|
899
|
+
class SkeletonComponent {
|
|
900
|
+
variant = 'rect';
|
|
901
|
+
class = '';
|
|
902
|
+
cn = cn;
|
|
903
|
+
get computedClass() {
|
|
904
|
+
return cn(
|
|
905
|
+
// The background matches the Google Dark Mode "Muted" color
|
|
906
|
+
'animate-pulse bg-muted rounded-md', this.variant === 'circle' && 'rounded-full', this.variant === 'pill' && 'rounded-full', this.class);
|
|
907
|
+
}
|
|
908
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
909
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SkeletonComponent, isStandalone: true, selector: "tolle-skeleton", inputs: { variant: "variant", class: "class" }, ngImport: i0, template: `
|
|
910
|
+
<div [class]="computedClass"></div>
|
|
911
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
912
|
+
}
|
|
913
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SkeletonComponent, decorators: [{
|
|
914
|
+
type: Component,
|
|
915
|
+
args: [{
|
|
916
|
+
selector: 'tolle-skeleton',
|
|
917
|
+
standalone: true,
|
|
918
|
+
imports: [CommonModule],
|
|
919
|
+
template: `
|
|
920
|
+
<div [class]="computedClass"></div>
|
|
921
|
+
`,
|
|
922
|
+
}]
|
|
923
|
+
}], propDecorators: { variant: [{
|
|
924
|
+
type: Input
|
|
925
|
+
}], class: [{
|
|
926
|
+
type: Input
|
|
927
|
+
}] } });
|
|
928
|
+
|
|
929
|
+
class CheckboxComponent {
|
|
930
|
+
cdr;
|
|
931
|
+
class = '';
|
|
932
|
+
disabled = false;
|
|
933
|
+
size = 'default';
|
|
934
|
+
checked = false;
|
|
935
|
+
onChange = () => { };
|
|
936
|
+
onTouched = () => { };
|
|
937
|
+
constructor(cdr) {
|
|
938
|
+
this.cdr = cdr;
|
|
939
|
+
}
|
|
940
|
+
cn = cn;
|
|
941
|
+
get sizeClasses() {
|
|
942
|
+
return cn(this.size === 'xs' && 'h-3.5 w-3.5', this.size === 'sm' && 'h-4 w-4', this.size === 'default' && 'h-5 w-5', this.size === 'lg' && 'h-6 w-6');
|
|
943
|
+
}
|
|
944
|
+
toggle() {
|
|
945
|
+
if (this.disabled)
|
|
946
|
+
return;
|
|
947
|
+
this.checked = !this.checked;
|
|
948
|
+
this.onChange(this.checked);
|
|
949
|
+
this.onTouched();
|
|
950
|
+
}
|
|
951
|
+
// --- ControlValueAccessor ---
|
|
952
|
+
writeValue(value) {
|
|
953
|
+
this.checked = value;
|
|
954
|
+
this.cdr.markForCheck();
|
|
955
|
+
}
|
|
956
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
957
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
958
|
+
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
959
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CheckboxComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
960
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CheckboxComponent, isStandalone: true, selector: "tolle-checkbox", inputs: { class: "class", disabled: "disabled", size: "size" }, providers: [
|
|
961
|
+
{
|
|
962
|
+
provide: NG_VALUE_ACCESSOR,
|
|
963
|
+
useExisting: forwardRef(() => CheckboxComponent),
|
|
964
|
+
multi: true
|
|
965
|
+
}
|
|
966
|
+
], ngImport: i0, template: `
|
|
967
|
+
<div
|
|
968
|
+
(click)="toggle()"
|
|
969
|
+
[class]="cn(
|
|
970
|
+
'group flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border border-primary ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
971
|
+
checked ? 'bg-primary text-primary-foreground' : 'bg-transparent',
|
|
972
|
+
sizeClasses,
|
|
973
|
+
class
|
|
974
|
+
)"
|
|
975
|
+
>
|
|
976
|
+
<i
|
|
977
|
+
*ngIf="checked"
|
|
978
|
+
class="ri-check-line font-bold"
|
|
979
|
+
[class]="size === 'xs' ? 'text-[10px]' : 'text-[14px]'"
|
|
980
|
+
></i>
|
|
981
|
+
</div>
|
|
982
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
983
|
+
}
|
|
984
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CheckboxComponent, decorators: [{
|
|
985
|
+
type: Component,
|
|
986
|
+
args: [{
|
|
987
|
+
selector: 'tolle-checkbox',
|
|
988
|
+
standalone: true,
|
|
989
|
+
imports: [CommonModule],
|
|
990
|
+
providers: [
|
|
991
|
+
{
|
|
992
|
+
provide: NG_VALUE_ACCESSOR,
|
|
993
|
+
useExisting: forwardRef(() => CheckboxComponent),
|
|
994
|
+
multi: true
|
|
995
|
+
}
|
|
996
|
+
],
|
|
997
|
+
template: `
|
|
998
|
+
<div
|
|
999
|
+
(click)="toggle()"
|
|
1000
|
+
[class]="cn(
|
|
1001
|
+
'group flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border border-primary ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
1002
|
+
checked ? 'bg-primary text-primary-foreground' : 'bg-transparent',
|
|
1003
|
+
sizeClasses,
|
|
1004
|
+
class
|
|
1005
|
+
)"
|
|
1006
|
+
>
|
|
1007
|
+
<i
|
|
1008
|
+
*ngIf="checked"
|
|
1009
|
+
class="ri-check-line font-bold"
|
|
1010
|
+
[class]="size === 'xs' ? 'text-[10px]' : 'text-[14px]'"
|
|
1011
|
+
></i>
|
|
1012
|
+
</div>
|
|
1013
|
+
`
|
|
1014
|
+
}]
|
|
1015
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { class: [{
|
|
1016
|
+
type: Input
|
|
1017
|
+
}], disabled: [{
|
|
1018
|
+
type: Input
|
|
1019
|
+
}], size: [{
|
|
1020
|
+
type: Input
|
|
1021
|
+
}] } });
|
|
1022
|
+
|
|
1023
|
+
class TooltipDirective {
|
|
1024
|
+
el;
|
|
1025
|
+
content = '';
|
|
1026
|
+
placement = 'top';
|
|
1027
|
+
tooltipEl = null;
|
|
1028
|
+
cleanup;
|
|
1029
|
+
constructor(el) {
|
|
1030
|
+
this.el = el;
|
|
1031
|
+
}
|
|
1032
|
+
show() {
|
|
1033
|
+
if (!this.content)
|
|
1034
|
+
return;
|
|
1035
|
+
// 1. Create Tooltip Element
|
|
1036
|
+
this.tooltipEl = document.createElement('div');
|
|
1037
|
+
this.tooltipEl.className = cn('absolute z-[100] px-2 py-1 text-xs font-medium text-white bg-black rounded shadow-md pointer-events-none');
|
|
1038
|
+
this.tooltipEl.innerText = this.content;
|
|
1039
|
+
document.body.appendChild(this.tooltipEl);
|
|
1040
|
+
// 2. Position it using Floating UI
|
|
1041
|
+
this.cleanup = autoUpdate(this.el.nativeElement, this.tooltipEl, () => {
|
|
1042
|
+
computePosition(this.el.nativeElement, this.tooltipEl, {
|
|
1043
|
+
placement: this.placement,
|
|
1044
|
+
middleware: [offset(8), flip(), shift({ padding: 5 })],
|
|
1045
|
+
}).then(({ x, y }) => {
|
|
1046
|
+
Object.assign(this.tooltipEl.style, {
|
|
1047
|
+
left: `${x}px`,
|
|
1048
|
+
top: `${y}px`,
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
hide() {
|
|
1054
|
+
if (this.tooltipEl) {
|
|
1055
|
+
this.tooltipEl.remove();
|
|
1056
|
+
this.tooltipEl = null;
|
|
1057
|
+
}
|
|
1058
|
+
if (this.cleanup) {
|
|
1059
|
+
this.cleanup();
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
ngOnDestroy() {
|
|
1063
|
+
this.hide();
|
|
1064
|
+
}
|
|
1065
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TooltipDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
1066
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: TooltipDirective, isStandalone: true, selector: "[tolleTooltip]", inputs: { content: ["tolleTooltip", "content"], placement: "placement" }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()", "focusout": "hide()" } }, ngImport: i0 });
|
|
1067
|
+
}
|
|
1068
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TooltipDirective, decorators: [{
|
|
1069
|
+
type: Directive,
|
|
1070
|
+
args: [{
|
|
1071
|
+
selector: '[tolleTooltip]',
|
|
1072
|
+
standalone: true
|
|
1073
|
+
}]
|
|
1074
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { content: [{
|
|
1075
|
+
type: Input,
|
|
1076
|
+
args: ['tolleTooltip']
|
|
1077
|
+
}], placement: [{
|
|
1078
|
+
type: Input
|
|
1079
|
+
}], show: [{
|
|
1080
|
+
type: HostListener,
|
|
1081
|
+
args: ['mouseenter']
|
|
1082
|
+
}], hide: [{
|
|
1083
|
+
type: HostListener,
|
|
1084
|
+
args: ['mouseleave']
|
|
1085
|
+
}, {
|
|
1086
|
+
type: HostListener,
|
|
1087
|
+
args: ['focusout']
|
|
1088
|
+
}] } });
|
|
1089
|
+
|
|
1090
|
+
// projects/tolle/src/lib/toast.service.ts
|
|
1091
|
+
class ToastService {
|
|
1092
|
+
toasts = [];
|
|
1093
|
+
toastSubject = new Subject();
|
|
1094
|
+
toasts$ = this.toastSubject.asObservable();
|
|
1095
|
+
constructor() {
|
|
1096
|
+
setInterval(() => this.tick(), 100);
|
|
1097
|
+
}
|
|
1098
|
+
show(toast) {
|
|
1099
|
+
const duration = toast.duration || 3000;
|
|
1100
|
+
const newToast = {
|
|
1101
|
+
...toast,
|
|
1102
|
+
id: Date.now(),
|
|
1103
|
+
duration,
|
|
1104
|
+
remainingTime: duration,
|
|
1105
|
+
isPaused: false
|
|
1106
|
+
};
|
|
1107
|
+
this.toasts.push(newToast);
|
|
1108
|
+
this.notify();
|
|
1109
|
+
}
|
|
1110
|
+
tick() {
|
|
1111
|
+
let changed = false;
|
|
1112
|
+
this.toasts.forEach(t => {
|
|
1113
|
+
if (!t.isPaused) {
|
|
1114
|
+
t.remainingTime -= 100;
|
|
1115
|
+
changed = true;
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
const initialCount = this.toasts.length;
|
|
1119
|
+
this.toasts = this.toasts.filter(t => t.remainingTime > 0);
|
|
1120
|
+
if (changed || this.toasts.length !== initialCount) {
|
|
1121
|
+
this.notify();
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
setPaused(id, paused) {
|
|
1125
|
+
const toast = this.toasts.find(t => t.id === id);
|
|
1126
|
+
if (toast) {
|
|
1127
|
+
toast.isPaused = paused;
|
|
1128
|
+
this.notify();
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
remove(id) {
|
|
1132
|
+
this.toasts = this.toasts.filter(t => t.id !== id);
|
|
1133
|
+
this.notify();
|
|
1134
|
+
}
|
|
1135
|
+
notify() {
|
|
1136
|
+
this.toastSubject.next([...this.toasts]);
|
|
1137
|
+
}
|
|
1138
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1139
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastService, providedIn: 'root' });
|
|
1140
|
+
}
|
|
1141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ToastService, decorators: [{
|
|
1142
|
+
type: Injectable,
|
|
1143
|
+
args: [{ providedIn: 'root' }]
|
|
1144
|
+
}], ctorParameters: () => [] });
|
|
1145
|
+
|
|
1146
|
+
const TOLLE_CONFIG = new InjectionToken('TolleConfig');
|
|
1147
|
+
function provideTolleConfig(config) {
|
|
1148
|
+
return [
|
|
1149
|
+
{
|
|
1150
|
+
provide: TOLLE_CONFIG,
|
|
1151
|
+
useValue: config
|
|
1152
|
+
}
|
|
1153
|
+
];
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
class MultiSelectComponent {
|
|
1157
|
+
selectService;
|
|
1158
|
+
placeholder = 'Select options...';
|
|
1159
|
+
size = 'default';
|
|
1160
|
+
searchable = false;
|
|
1161
|
+
disabled = false;
|
|
1162
|
+
class = '';
|
|
1163
|
+
trigger;
|
|
1164
|
+
popover;
|
|
1165
|
+
items;
|
|
1166
|
+
value = [];
|
|
1167
|
+
selectedItems = [];
|
|
1168
|
+
isOpen = false;
|
|
1169
|
+
searchQuery = '';
|
|
1170
|
+
noResults = false;
|
|
1171
|
+
cleanup;
|
|
1172
|
+
constructor(selectService) {
|
|
1173
|
+
this.selectService = selectService;
|
|
1174
|
+
this.selectService.selectedValue$.subscribe(val => {
|
|
1175
|
+
if (val !== undefined && this.isOpen)
|
|
1176
|
+
this.toggleValue(val);
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
ngAfterContentInit() {
|
|
1180
|
+
this.syncItems();
|
|
1181
|
+
this.items.changes.subscribe(() => this.syncItems());
|
|
1182
|
+
}
|
|
1183
|
+
toggle() {
|
|
1184
|
+
if (this.disabled)
|
|
1185
|
+
return;
|
|
1186
|
+
this.isOpen ? this.close() : this.open();
|
|
1187
|
+
}
|
|
1188
|
+
open() {
|
|
1189
|
+
this.isOpen = true;
|
|
1190
|
+
setTimeout(() => this.updatePosition());
|
|
1191
|
+
}
|
|
1192
|
+
close() {
|
|
1193
|
+
this.isOpen = false;
|
|
1194
|
+
if (this.cleanup)
|
|
1195
|
+
this.cleanup();
|
|
1196
|
+
}
|
|
1197
|
+
updatePosition() {
|
|
1198
|
+
this.cleanup = autoUpdate(this.trigger.nativeElement, this.popover.nativeElement, () => {
|
|
1199
|
+
computePosition(this.trigger.nativeElement, this.popover.nativeElement, {
|
|
1200
|
+
placement: 'bottom-start',
|
|
1201
|
+
middleware: [offset(4), flip(), shift({ padding: 8 })],
|
|
1202
|
+
}).then(({ x, y }) => {
|
|
1203
|
+
Object.assign(this.popover.nativeElement.style, {
|
|
1204
|
+
left: `${x}px`,
|
|
1205
|
+
top: `${y}px`,
|
|
1206
|
+
visibility: 'visible',
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
toggleValue(val) {
|
|
1212
|
+
const index = this.value.indexOf(val);
|
|
1213
|
+
index > -1 ? this.value.splice(index, 1) : this.value.push(val);
|
|
1214
|
+
this.syncItems();
|
|
1215
|
+
this.onChange([...this.value]); // Return new array reference for Change Detection
|
|
1216
|
+
}
|
|
1217
|
+
selectAll() {
|
|
1218
|
+
this.value = this.items.map(i => i.value);
|
|
1219
|
+
this.syncItems();
|
|
1220
|
+
this.onChange([...this.value]);
|
|
1221
|
+
}
|
|
1222
|
+
clearAll() {
|
|
1223
|
+
this.value = [];
|
|
1224
|
+
this.syncItems();
|
|
1225
|
+
this.onChange([]);
|
|
1226
|
+
}
|
|
1227
|
+
removeValue(event, val) {
|
|
1228
|
+
event.stopPropagation();
|
|
1229
|
+
this.toggleValue(val);
|
|
1230
|
+
}
|
|
1231
|
+
syncItems() {
|
|
1232
|
+
if (!this.items)
|
|
1233
|
+
return;
|
|
1234
|
+
this.selectedItems = [];
|
|
1235
|
+
this.items.forEach(item => {
|
|
1236
|
+
item.selected = this.value.includes(item.value);
|
|
1237
|
+
if (item.selected)
|
|
1238
|
+
this.selectedItems.push({ label: item.getLabel(), value: item.value });
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
onSearchChange(q) {
|
|
1242
|
+
const filter = (q || '').toLowerCase();
|
|
1243
|
+
let visibleCount = 0;
|
|
1244
|
+
this.items.forEach(item => {
|
|
1245
|
+
const isVisible = item.getLabel().toLowerCase().includes(filter);
|
|
1246
|
+
item.hidden = !isVisible;
|
|
1247
|
+
if (isVisible)
|
|
1248
|
+
visibleCount++;
|
|
1249
|
+
});
|
|
1250
|
+
this.noResults = visibleCount === 0 && filter !== '';
|
|
1251
|
+
}
|
|
1252
|
+
onClickOutside(event) {
|
|
1253
|
+
if (this.isOpen && !this.trigger.nativeElement.contains(event.target) && !this.popover?.nativeElement.contains(event.target)) {
|
|
1254
|
+
this.close();
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// ControlValueAccessor
|
|
1258
|
+
onChange = () => { };
|
|
1259
|
+
onTouched = () => { };
|
|
1260
|
+
writeValue(v) { this.value = Array.isArray(v) ? v : []; this.syncItems(); }
|
|
1261
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
1262
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
1263
|
+
cn = cn;
|
|
1264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiSelectComponent, deps: [{ token: SelectService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MultiSelectComponent, isStandalone: true, selector: "tolle-multi-select", inputs: { placeholder: "placeholder", size: "size", searchable: "searchable", disabled: "disabled", class: "class" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
|
|
1266
|
+
SelectService,
|
|
1267
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: MultiSelectComponent, multi: true }
|
|
1268
|
+
], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
|
|
1269
|
+
<div [class]="cn('relative w-full', 'size-' + size)" #container>
|
|
1270
|
+
<button #trigger type="button" (click)="toggle()" [disabled]="disabled"
|
|
1271
|
+
[class]="cn('flex min-h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-ring focus:ring-offset-2 transition-all h-auto', class)">
|
|
1272
|
+
<div class="flex flex-wrap gap-1 items-center max-w-[95%]">
|
|
1273
|
+
<ng-container *ngIf="value?.length; else placeholderTpl">
|
|
1274
|
+
<tolle-badge *ngFor="let item of selectedItems" size="xs" variant="secondary" [removable]="true" (onRemove)="removeValue($event, item.value)">
|
|
1275
|
+
{{ item.label }}
|
|
1276
|
+
</tolle-badge>
|
|
1277
|
+
</ng-container>
|
|
1278
|
+
<ng-template #placeholderTpl><span class="text-muted-foreground">{{ placeholder }}</span></ng-template>
|
|
1279
|
+
</div>
|
|
1280
|
+
<i [class]="cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform', isOpen ? 'rotate-180' : '')"></i>
|
|
1281
|
+
</button>
|
|
1282
|
+
|
|
1283
|
+
<div #popover *ngIf="isOpen" class="absolute bg-popover z-50 min-w-full rounded-md border border-border shadow-md overflow-hidden" style="visibility: hidden;">
|
|
1284
|
+
|
|
1285
|
+
<div class="p-2 border-b border-border space-y-2 bg-popover">
|
|
1286
|
+
<tolle-input *ngIf="searchable" size="xs" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChange($event)">
|
|
1287
|
+
<i prefix class="ri-search-line"></i>
|
|
1288
|
+
</tolle-input>
|
|
1289
|
+
|
|
1290
|
+
<div class="flex items-center justify-between px-1">
|
|
1291
|
+
<button type="button" (click)="selectAll()" class="text-[10px] font-bold uppercase text-primary hover:underline">Select All</button>
|
|
1292
|
+
<button type="button" (click)="clearAll()" class="text-[10px] font-bold uppercase text-muted-foreground hover:underline">Clear</button>
|
|
1293
|
+
</div>
|
|
1294
|
+
</div>
|
|
1295
|
+
|
|
1296
|
+
<div class="p-1 max-h-60 overflow-y-auto">
|
|
1297
|
+
<ng-content></ng-content>
|
|
1298
|
+
<div *ngIf="noResults" class="py-4 text-center text-xs text-muted-foreground">No results found for "{{searchQuery}}"</div>
|
|
1299
|
+
</div>
|
|
1300
|
+
</div>
|
|
1301
|
+
</div>
|
|
1302
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BadgeComponent$1, selector: "tolle-badge", inputs: ["variant", "size", "removable", "class"], outputs: ["onRemove"] }, { kind: "component", type: InputComponent$1, selector: "tolle-input", inputs: ["type", "placeholder", "disabled", "error", "size", "containerClass", "class"] }] });
|
|
1303
|
+
}
|
|
1304
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiSelectComponent, decorators: [{
|
|
1305
|
+
type: Component,
|
|
1306
|
+
args: [{
|
|
1307
|
+
selector: 'tolle-multi-select',
|
|
1308
|
+
standalone: true,
|
|
1309
|
+
imports: [CommonModule, FormsModule, BadgeComponent$1, InputComponent$1],
|
|
1310
|
+
providers: [
|
|
1311
|
+
SelectService,
|
|
1312
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: MultiSelectComponent, multi: true }
|
|
1313
|
+
],
|
|
1314
|
+
template: `
|
|
1315
|
+
<div [class]="cn('relative w-full', 'size-' + size)" #container>
|
|
1316
|
+
<button #trigger type="button" (click)="toggle()" [disabled]="disabled"
|
|
1317
|
+
[class]="cn('flex min-h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-ring focus:ring-offset-2 transition-all h-auto', class)">
|
|
1318
|
+
<div class="flex flex-wrap gap-1 items-center max-w-[95%]">
|
|
1319
|
+
<ng-container *ngIf="value?.length; else placeholderTpl">
|
|
1320
|
+
<tolle-badge *ngFor="let item of selectedItems" size="xs" variant="secondary" [removable]="true" (onRemove)="removeValue($event, item.value)">
|
|
1321
|
+
{{ item.label }}
|
|
1322
|
+
</tolle-badge>
|
|
1323
|
+
</ng-container>
|
|
1324
|
+
<ng-template #placeholderTpl><span class="text-muted-foreground">{{ placeholder }}</span></ng-template>
|
|
1325
|
+
</div>
|
|
1326
|
+
<i [class]="cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform', isOpen ? 'rotate-180' : '')"></i>
|
|
1327
|
+
</button>
|
|
1328
|
+
|
|
1329
|
+
<div #popover *ngIf="isOpen" class="absolute bg-popover z-50 min-w-full rounded-md border border-border shadow-md overflow-hidden" style="visibility: hidden;">
|
|
1330
|
+
|
|
1331
|
+
<div class="p-2 border-b border-border space-y-2 bg-popover">
|
|
1332
|
+
<tolle-input *ngIf="searchable" size="xs" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChange($event)">
|
|
1333
|
+
<i prefix class="ri-search-line"></i>
|
|
1334
|
+
</tolle-input>
|
|
1335
|
+
|
|
1336
|
+
<div class="flex items-center justify-between px-1">
|
|
1337
|
+
<button type="button" (click)="selectAll()" class="text-[10px] font-bold uppercase text-primary hover:underline">Select All</button>
|
|
1338
|
+
<button type="button" (click)="clearAll()" class="text-[10px] font-bold uppercase text-muted-foreground hover:underline">Clear</button>
|
|
1339
|
+
</div>
|
|
1340
|
+
</div>
|
|
1341
|
+
|
|
1342
|
+
<div class="p-1 max-h-60 overflow-y-auto">
|
|
1343
|
+
<ng-content></ng-content>
|
|
1344
|
+
<div *ngIf="noResults" class="py-4 text-center text-xs text-muted-foreground">No results found for "{{searchQuery}}"</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
</div>
|
|
1348
|
+
`
|
|
1349
|
+
}]
|
|
1350
|
+
}], ctorParameters: () => [{ type: SelectService }], propDecorators: { placeholder: [{
|
|
1351
|
+
type: Input
|
|
1352
|
+
}], size: [{
|
|
1353
|
+
type: Input
|
|
1354
|
+
}], searchable: [{
|
|
1355
|
+
type: Input
|
|
1356
|
+
}], disabled: [{
|
|
1357
|
+
type: Input
|
|
1358
|
+
}], class: [{
|
|
1359
|
+
type: Input
|
|
1360
|
+
}], trigger: [{
|
|
1361
|
+
type: ViewChild,
|
|
1362
|
+
args: ['trigger']
|
|
1363
|
+
}], popover: [{
|
|
1364
|
+
type: ViewChild,
|
|
1365
|
+
args: ['popover']
|
|
1366
|
+
}], items: [{
|
|
1367
|
+
type: ContentChildren,
|
|
1368
|
+
args: [SelectItemComponent, { descendants: true }]
|
|
1369
|
+
}], onClickOutside: [{
|
|
1370
|
+
type: HostListener,
|
|
1371
|
+
args: ['document:mousedown', ['$event']]
|
|
1372
|
+
}] } });
|
|
1373
|
+
|
|
1374
|
+
class CalendarComponent {
|
|
1375
|
+
class = '';
|
|
1376
|
+
disablePastDates = false;
|
|
1377
|
+
currentView = 'date';
|
|
1378
|
+
viewDate = new Date();
|
|
1379
|
+
selectedDate = null;
|
|
1380
|
+
weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
1381
|
+
daysInMonth = [];
|
|
1382
|
+
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
1383
|
+
years = [];
|
|
1384
|
+
navBtnClass = cn('h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 border border-input rounded-md flex items-center justify-center hover:bg-accent hover:text-accent-foreground transition-all');
|
|
1385
|
+
onTouched = () => { };
|
|
1386
|
+
onChange = () => { };
|
|
1387
|
+
cn = cn;
|
|
1388
|
+
ngOnInit() {
|
|
1389
|
+
this.generateDays();
|
|
1390
|
+
this.generateYears();
|
|
1391
|
+
}
|
|
1392
|
+
generateDays() {
|
|
1393
|
+
const start = startOfWeek(startOfMonth(this.viewDate));
|
|
1394
|
+
const end = endOfWeek(endOfMonth(this.viewDate));
|
|
1395
|
+
this.daysInMonth = eachDayOfInterval({ start, end });
|
|
1396
|
+
}
|
|
1397
|
+
generateYears() {
|
|
1398
|
+
const currentYear = this.viewDate.getFullYear();
|
|
1399
|
+
// Generates a 16-year window centered roughly on current view
|
|
1400
|
+
this.years = Array.from({ length: 16 }, (_, i) => currentYear - 6 + i);
|
|
1401
|
+
}
|
|
1402
|
+
setView(view) {
|
|
1403
|
+
this.currentView = view;
|
|
1404
|
+
// If switching to year view, ensure the year grid is centered on current view year
|
|
1405
|
+
if (view === 'year') {
|
|
1406
|
+
this.generateYears();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
prev() {
|
|
1410
|
+
if (this.currentView === 'date') {
|
|
1411
|
+
this.viewDate = subMonths(this.viewDate, 1);
|
|
1412
|
+
this.generateDays();
|
|
1413
|
+
}
|
|
1414
|
+
else if (this.currentView === 'year') {
|
|
1415
|
+
this.viewDate = subYears(this.viewDate, 16);
|
|
1416
|
+
this.generateYears();
|
|
1417
|
+
}
|
|
1418
|
+
else if (this.currentView === 'month') {
|
|
1419
|
+
this.viewDate = subYears(this.viewDate, 1);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
next() {
|
|
1423
|
+
if (this.currentView === 'date') {
|
|
1424
|
+
this.viewDate = addMonths(this.viewDate, 1);
|
|
1425
|
+
this.generateDays();
|
|
1426
|
+
}
|
|
1427
|
+
else if (this.currentView === 'year') {
|
|
1428
|
+
this.viewDate = addYears(this.viewDate, 16);
|
|
1429
|
+
this.generateYears();
|
|
1430
|
+
}
|
|
1431
|
+
else if (this.currentView === 'month') {
|
|
1432
|
+
this.viewDate = addYears(this.viewDate, 1);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
selectDate(date) {
|
|
1436
|
+
if (this.isDateDisabled(date))
|
|
1437
|
+
return;
|
|
1438
|
+
this.selectedDate = date;
|
|
1439
|
+
if (!isSameMonth(date, this.viewDate)) {
|
|
1440
|
+
this.viewDate = date;
|
|
1441
|
+
this.generateDays();
|
|
1442
|
+
}
|
|
1443
|
+
this.onChange(date);
|
|
1444
|
+
this.onTouched();
|
|
1445
|
+
}
|
|
1446
|
+
selectMonth(monthIndex) {
|
|
1447
|
+
this.viewDate = setMonth(this.viewDate, monthIndex);
|
|
1448
|
+
this.currentView = 'date';
|
|
1449
|
+
this.generateDays();
|
|
1450
|
+
}
|
|
1451
|
+
selectYear(year) {
|
|
1452
|
+
this.viewDate = setYear(this.viewDate, year);
|
|
1453
|
+
this.currentView = 'date'; // Jump straight back to date view for efficiency
|
|
1454
|
+
this.generateDays();
|
|
1455
|
+
}
|
|
1456
|
+
getDayClass(date) {
|
|
1457
|
+
const isSelected = this.selectedDate && isSameDay(date, this.selectedDate);
|
|
1458
|
+
const isTodayDate = isToday(date);
|
|
1459
|
+
const isOutside = !isSameMonth(date, this.viewDate);
|
|
1460
|
+
const isDisabled = this.isDateDisabled(date);
|
|
1461
|
+
return cn('h-9 w-9 p-0 font-normal text-sm rounded-md transition-all flex items-center justify-center', !isSelected && !isDisabled && 'hover:bg-accent hover:text-accent-foreground', isSelected && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground', !isSelected && isTodayDate && 'bg-accent text-accent-foreground', (isOutside || isDisabled) && 'text-muted-foreground opacity-50', isDisabled && 'cursor-not-allowed');
|
|
1462
|
+
}
|
|
1463
|
+
isDateDisabled(date) {
|
|
1464
|
+
return this.disablePastDates ? isBefore(date, startOfDay(new Date())) : false;
|
|
1465
|
+
}
|
|
1466
|
+
// CVA Implementation
|
|
1467
|
+
writeValue(obj) {
|
|
1468
|
+
if (obj) {
|
|
1469
|
+
const date = new Date(obj);
|
|
1470
|
+
if (!isNaN(date.getTime())) {
|
|
1471
|
+
this.selectedDate = date;
|
|
1472
|
+
this.viewDate = date;
|
|
1473
|
+
this.generateDays();
|
|
1474
|
+
this.generateYears();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
1479
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
1480
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1481
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CalendarComponent, isStandalone: true, selector: "tolle-calendar", inputs: { class: "class", disablePastDates: "disablePastDates" }, providers: [
|
|
1482
|
+
{
|
|
1483
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1484
|
+
useExisting: forwardRef(() => CalendarComponent),
|
|
1485
|
+
multi: true
|
|
1486
|
+
}
|
|
1487
|
+
], ngImport: i0, template: `
|
|
1488
|
+
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block w-fit', class)">
|
|
1489
|
+
|
|
1490
|
+
<div class="flex items-center justify-between pt-1 pb-4 gap-2">
|
|
1491
|
+
|
|
1492
|
+
<div class="flex items-center gap-1">
|
|
1493
|
+
<button
|
|
1494
|
+
type="button"
|
|
1495
|
+
(click)="setView('month')"
|
|
1496
|
+
[class]="cn(
|
|
1497
|
+
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
1498
|
+
currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
1499
|
+
)"
|
|
1500
|
+
>
|
|
1501
|
+
{{ viewDate | date: 'MMMM' }}
|
|
1502
|
+
</button>
|
|
1503
|
+
|
|
1504
|
+
<button
|
|
1505
|
+
type="button"
|
|
1506
|
+
(click)="setView('year')"
|
|
1507
|
+
[class]="cn(
|
|
1508
|
+
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
1509
|
+
currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
1510
|
+
)"
|
|
1511
|
+
>
|
|
1512
|
+
{{ viewDate | date: 'yyyy' }}
|
|
1513
|
+
</button>
|
|
1514
|
+
</div>
|
|
1515
|
+
|
|
1516
|
+
<div class="flex items-center space-x-1">
|
|
1517
|
+
<button type="button" (click)="prev()" [class]="navBtnClass">
|
|
1518
|
+
<i class="ri-arrow-left-s-line text-lg"></i>
|
|
1519
|
+
</button>
|
|
1520
|
+
<button type="button" (click)="next()" [class]="navBtnClass">
|
|
1521
|
+
<i class="ri-arrow-right-s-line text-lg"></i>
|
|
1522
|
+
</button>
|
|
1523
|
+
</div>
|
|
1524
|
+
</div>
|
|
1525
|
+
|
|
1526
|
+
<div *ngIf="currentView === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
|
|
1527
|
+
<div class="grid grid-cols-7 gap-1 w-full">
|
|
1528
|
+
<span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
|
|
1529
|
+
{{ day }}
|
|
1530
|
+
</span>
|
|
1531
|
+
</div>
|
|
1532
|
+
<div class="grid grid-cols-7 gap-1 w-full">
|
|
1533
|
+
<button
|
|
1534
|
+
*ngFor="let date of daysInMonth"
|
|
1535
|
+
type="button"
|
|
1536
|
+
(click)="selectDate(date)"
|
|
1537
|
+
[disabled]="isDateDisabled(date)"
|
|
1538
|
+
[class]="getDayClass(date)"
|
|
1539
|
+
>
|
|
1540
|
+
{{ date | date: 'd' }}
|
|
1541
|
+
</button>
|
|
1542
|
+
</div>
|
|
1543
|
+
</div>
|
|
1544
|
+
|
|
1545
|
+
<div *ngIf="currentView === 'month'" class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
1546
|
+
<button
|
|
1547
|
+
*ngFor="let month of months; let i = index"
|
|
1548
|
+
type="button"
|
|
1549
|
+
(click)="selectMonth(i)"
|
|
1550
|
+
[class]="cn(
|
|
1551
|
+
'text-sm py-2.5 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
|
|
1552
|
+
i === viewDate.getMonth() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
|
|
1553
|
+
)"
|
|
1554
|
+
>
|
|
1555
|
+
{{ month }}
|
|
1556
|
+
</button>
|
|
1557
|
+
</div>
|
|
1558
|
+
|
|
1559
|
+
<div *ngIf="currentView === 'year'" class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
1560
|
+
<button
|
|
1561
|
+
*ngFor="let year of years"
|
|
1562
|
+
type="button"
|
|
1563
|
+
(click)="selectYear(year)"
|
|
1564
|
+
[class]="cn(
|
|
1565
|
+
'text-sm py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
|
|
1566
|
+
year === viewDate.getFullYear() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
|
|
1567
|
+
)"
|
|
1568
|
+
>
|
|
1569
|
+
{{ year }}
|
|
1570
|
+
</button>
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] });
|
|
1574
|
+
}
|
|
1575
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarComponent, decorators: [{
|
|
1576
|
+
type: Component,
|
|
1577
|
+
args: [{
|
|
1578
|
+
selector: 'tolle-calendar',
|
|
1579
|
+
standalone: true,
|
|
1580
|
+
imports: [CommonModule],
|
|
1581
|
+
providers: [
|
|
1582
|
+
{
|
|
1583
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1584
|
+
useExisting: forwardRef(() => CalendarComponent),
|
|
1585
|
+
multi: true
|
|
1586
|
+
}
|
|
1587
|
+
],
|
|
1588
|
+
template: `
|
|
1589
|
+
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block w-fit', class)">
|
|
1590
|
+
|
|
1591
|
+
<div class="flex items-center justify-between pt-1 pb-4 gap-2">
|
|
1592
|
+
|
|
1593
|
+
<div class="flex items-center gap-1">
|
|
1594
|
+
<button
|
|
1595
|
+
type="button"
|
|
1596
|
+
(click)="setView('month')"
|
|
1597
|
+
[class]="cn(
|
|
1598
|
+
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
1599
|
+
currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
1600
|
+
)"
|
|
1601
|
+
>
|
|
1602
|
+
{{ viewDate | date: 'MMMM' }}
|
|
1603
|
+
</button>
|
|
1604
|
+
|
|
1605
|
+
<button
|
|
1606
|
+
type="button"
|
|
1607
|
+
(click)="setView('year')"
|
|
1608
|
+
[class]="cn(
|
|
1609
|
+
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
1610
|
+
currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
1611
|
+
)"
|
|
1612
|
+
>
|
|
1613
|
+
{{ viewDate | date: 'yyyy' }}
|
|
1614
|
+
</button>
|
|
1615
|
+
</div>
|
|
1616
|
+
|
|
1617
|
+
<div class="flex items-center space-x-1">
|
|
1618
|
+
<button type="button" (click)="prev()" [class]="navBtnClass">
|
|
1619
|
+
<i class="ri-arrow-left-s-line text-lg"></i>
|
|
1620
|
+
</button>
|
|
1621
|
+
<button type="button" (click)="next()" [class]="navBtnClass">
|
|
1622
|
+
<i class="ri-arrow-right-s-line text-lg"></i>
|
|
1623
|
+
</button>
|
|
1624
|
+
</div>
|
|
1625
|
+
</div>
|
|
1626
|
+
|
|
1627
|
+
<div *ngIf="currentView === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
|
|
1628
|
+
<div class="grid grid-cols-7 gap-1 w-full">
|
|
1629
|
+
<span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
|
|
1630
|
+
{{ day }}
|
|
1631
|
+
</span>
|
|
1632
|
+
</div>
|
|
1633
|
+
<div class="grid grid-cols-7 gap-1 w-full">
|
|
1634
|
+
<button
|
|
1635
|
+
*ngFor="let date of daysInMonth"
|
|
1636
|
+
type="button"
|
|
1637
|
+
(click)="selectDate(date)"
|
|
1638
|
+
[disabled]="isDateDisabled(date)"
|
|
1639
|
+
[class]="getDayClass(date)"
|
|
1640
|
+
>
|
|
1641
|
+
{{ date | date: 'd' }}
|
|
1642
|
+
</button>
|
|
1643
|
+
</div>
|
|
1644
|
+
</div>
|
|
1645
|
+
|
|
1646
|
+
<div *ngIf="currentView === 'month'" class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
1647
|
+
<button
|
|
1648
|
+
*ngFor="let month of months; let i = index"
|
|
1649
|
+
type="button"
|
|
1650
|
+
(click)="selectMonth(i)"
|
|
1651
|
+
[class]="cn(
|
|
1652
|
+
'text-sm py-2.5 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
|
|
1653
|
+
i === viewDate.getMonth() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
|
|
1654
|
+
)"
|
|
1655
|
+
>
|
|
1656
|
+
{{ month }}
|
|
1657
|
+
</button>
|
|
1658
|
+
</div>
|
|
1659
|
+
|
|
1660
|
+
<div *ngIf="currentView === 'year'" class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
1661
|
+
<button
|
|
1662
|
+
*ngFor="let year of years"
|
|
1663
|
+
type="button"
|
|
1664
|
+
(click)="selectYear(year)"
|
|
1665
|
+
[class]="cn(
|
|
1666
|
+
'text-sm py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
|
|
1667
|
+
year === viewDate.getFullYear() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
|
|
1668
|
+
)"
|
|
1669
|
+
>
|
|
1670
|
+
{{ year }}
|
|
1671
|
+
</button>
|
|
1672
|
+
</div>
|
|
1673
|
+
</div>
|
|
1674
|
+
`
|
|
1675
|
+
}]
|
|
1676
|
+
}], propDecorators: { class: [{
|
|
1677
|
+
type: Input
|
|
1678
|
+
}], disablePastDates: [{
|
|
1679
|
+
type: Input
|
|
1680
|
+
}] } });
|
|
1681
|
+
|
|
1682
|
+
class MaskedInputComponent {
|
|
1683
|
+
el;
|
|
1684
|
+
cdr;
|
|
1685
|
+
mask = '';
|
|
1686
|
+
placeholder = '';
|
|
1687
|
+
type = 'text';
|
|
1688
|
+
disabled = false;
|
|
1689
|
+
class = '';
|
|
1690
|
+
error = false;
|
|
1691
|
+
size = 'default';
|
|
1692
|
+
returnRaw = false;
|
|
1693
|
+
inputEl;
|
|
1694
|
+
hasPrefix = false;
|
|
1695
|
+
hasSuffix = false;
|
|
1696
|
+
displayValue = '';
|
|
1697
|
+
tokens = {
|
|
1698
|
+
'0': /\d/, '9': /\d/, 'a': /[a-z]/i, 'A': /[a-z]/i, '*': /[a-z0-9]/i
|
|
1699
|
+
};
|
|
1700
|
+
onChange = () => { };
|
|
1701
|
+
onTouched = () => { };
|
|
1702
|
+
constructor(el, cdr) {
|
|
1703
|
+
this.el = el;
|
|
1704
|
+
this.cdr = cdr;
|
|
1705
|
+
}
|
|
1706
|
+
// FIXED DETECTION: Check the actual DOM nodes projected into the component
|
|
1707
|
+
ngAfterContentChecked() {
|
|
1708
|
+
const prefix = this.el.nativeElement.querySelector('[prefix]');
|
|
1709
|
+
const suffix = this.el.nativeElement.querySelector('[suffix]');
|
|
1710
|
+
if (this.hasPrefix !== !!prefix || this.hasSuffix !== !!suffix) {
|
|
1711
|
+
this.hasPrefix = !!prefix;
|
|
1712
|
+
this.hasSuffix = !!suffix;
|
|
1713
|
+
this.cdr.detectChanges();
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
get computedInputClass() {
|
|
1717
|
+
return cn("flex w-full rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 transition-all", 'disabled:opacity-50 shadow-sm transition-shadow', this.size === 'xs' && "h-8 text-xs px-2", this.size === 'sm' && "h-9 px-3", this.size === 'default' && "h-10 px-3", this.size === 'lg' && "h-11 px-4 text-base", "group-has-[[prefix]]:pl-10 group-has-[[suffix]]:pr-10", this.size === 'xs' && "group-has-[[prefix]]:pl-8 group-has-[[suffix]]:pr-8", this.error && "border-destructive focus-visible:ring-destructive", this.class);
|
|
1718
|
+
}
|
|
1719
|
+
// --- Masking Logic ---
|
|
1720
|
+
onInput(event) {
|
|
1721
|
+
const input = event.target;
|
|
1722
|
+
const raw = this.unmask(input.value);
|
|
1723
|
+
const masked = this.applyMask(raw);
|
|
1724
|
+
this.displayValue = masked;
|
|
1725
|
+
input.value = masked;
|
|
1726
|
+
this.returnRaw ? this.onChange(raw) : this.onChange(masked);
|
|
1727
|
+
}
|
|
1728
|
+
applyMask(rawValue) {
|
|
1729
|
+
let rawIndex = 0;
|
|
1730
|
+
let formatted = '';
|
|
1731
|
+
for (let i = 0; i < this.mask.length; i++) {
|
|
1732
|
+
if (rawIndex >= rawValue.length)
|
|
1733
|
+
break;
|
|
1734
|
+
const maskChar = this.mask[i];
|
|
1735
|
+
const rawChar = rawValue[rawIndex];
|
|
1736
|
+
if (this.tokens[maskChar]) {
|
|
1737
|
+
if (this.tokens[maskChar].test(rawChar)) {
|
|
1738
|
+
formatted += rawChar;
|
|
1739
|
+
rawIndex++;
|
|
1740
|
+
}
|
|
1741
|
+
else {
|
|
1742
|
+
rawIndex++;
|
|
1743
|
+
i--;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
else {
|
|
1747
|
+
formatted += maskChar;
|
|
1748
|
+
if (rawChar === maskChar)
|
|
1749
|
+
rawIndex++;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return formatted;
|
|
1753
|
+
}
|
|
1754
|
+
unmask(val) { return val.replace(/[^a-zA-Z0-9]/g, ''); }
|
|
1755
|
+
writeValue(value) {
|
|
1756
|
+
this.displayValue = value ? this.applyMask(this.unmask(value.toString())) : '';
|
|
1757
|
+
this.cdr.markForCheck();
|
|
1758
|
+
}
|
|
1759
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
1760
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
1761
|
+
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
1762
|
+
cn = cn;
|
|
1763
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MaskedInputComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1764
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MaskedInputComponent, isStandalone: true, selector: "tolle-masked-input", inputs: { mask: "mask", placeholder: "placeholder", type: "type", disabled: "disabled", class: "class", error: "error", size: "size", returnRaw: "returnRaw" }, providers: [
|
|
1765
|
+
{
|
|
1766
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1767
|
+
useExisting: forwardRef(() => MaskedInputComponent),
|
|
1768
|
+
multi: true
|
|
1769
|
+
}
|
|
1770
|
+
], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }], ngImport: i0, template: `
|
|
1771
|
+
<div [class]="cn('relative flex items-center w-full group', 'size-' + size, class)">
|
|
1772
|
+
|
|
1773
|
+
<div class="absolute left-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
1774
|
+
[class.left-2.5]="size === 'xs'">
|
|
1775
|
+
<ng-content select="[prefix]"></ng-content>
|
|
1776
|
+
</div>
|
|
1777
|
+
|
|
1778
|
+
<input
|
|
1779
|
+
#inputEl
|
|
1780
|
+
[type]="type"
|
|
1781
|
+
[placeholder]="placeholder"
|
|
1782
|
+
[disabled]="disabled"
|
|
1783
|
+
[value]="displayValue"
|
|
1784
|
+
(input)="onInput($event)"
|
|
1785
|
+
(blur)="onTouched()"
|
|
1786
|
+
[class]="computedInputClass"
|
|
1787
|
+
/>
|
|
1788
|
+
|
|
1789
|
+
<div class="absolute right-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
1790
|
+
[class.right-2.5]="size === 'xs'">
|
|
1791
|
+
<ng-content select="[suffix]"></ng-content>
|
|
1792
|
+
</div>
|
|
1793
|
+
</div>
|
|
1794
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
|
|
1795
|
+
}
|
|
1796
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MaskedInputComponent, decorators: [{
|
|
1797
|
+
type: Component,
|
|
1798
|
+
args: [{
|
|
1799
|
+
selector: 'tolle-masked-input',
|
|
1800
|
+
standalone: true,
|
|
1801
|
+
imports: [CommonModule, FormsModule],
|
|
1802
|
+
providers: [
|
|
1803
|
+
{
|
|
1804
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1805
|
+
useExisting: forwardRef(() => MaskedInputComponent),
|
|
1806
|
+
multi: true
|
|
1807
|
+
}
|
|
1808
|
+
],
|
|
1809
|
+
template: `
|
|
1810
|
+
<div [class]="cn('relative flex items-center w-full group', 'size-' + size, class)">
|
|
1811
|
+
|
|
1812
|
+
<div class="absolute left-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
1813
|
+
[class.left-2.5]="size === 'xs'">
|
|
1814
|
+
<ng-content select="[prefix]"></ng-content>
|
|
1815
|
+
</div>
|
|
1816
|
+
|
|
1817
|
+
<input
|
|
1818
|
+
#inputEl
|
|
1819
|
+
[type]="type"
|
|
1820
|
+
[placeholder]="placeholder"
|
|
1821
|
+
[disabled]="disabled"
|
|
1822
|
+
[value]="displayValue"
|
|
1823
|
+
(input)="onInput($event)"
|
|
1824
|
+
(blur)="onTouched()"
|
|
1825
|
+
[class]="computedInputClass"
|
|
1826
|
+
/>
|
|
1827
|
+
|
|
1828
|
+
<div class="absolute right-3 flex items-center justify-center text-muted-foreground group-focus-within:text-primary transition-colors"
|
|
1829
|
+
[class.right-2.5]="size === 'xs'">
|
|
1830
|
+
<ng-content select="[suffix]"></ng-content>
|
|
1831
|
+
</div>
|
|
1832
|
+
</div>
|
|
1833
|
+
`
|
|
1834
|
+
}]
|
|
1835
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { mask: [{
|
|
1836
|
+
type: Input
|
|
1837
|
+
}], placeholder: [{
|
|
1838
|
+
type: Input
|
|
1839
|
+
}], type: [{
|
|
1840
|
+
type: Input
|
|
1841
|
+
}], disabled: [{
|
|
1842
|
+
type: Input
|
|
1843
|
+
}], class: [{
|
|
1844
|
+
type: Input
|
|
1845
|
+
}], error: [{
|
|
1846
|
+
type: Input
|
|
1847
|
+
}], size: [{
|
|
1848
|
+
type: Input
|
|
1849
|
+
}], returnRaw: [{
|
|
1850
|
+
type: Input
|
|
1851
|
+
}], inputEl: [{
|
|
1852
|
+
type: ViewChild,
|
|
1853
|
+
args: ['inputEl', { static: true }]
|
|
1854
|
+
}] } });
|
|
1855
|
+
|
|
1856
|
+
class DatePickerComponent {
|
|
1857
|
+
cdr;
|
|
1858
|
+
placeholder = 'MM/DD/YYYY';
|
|
1859
|
+
disabled = false;
|
|
1860
|
+
class = '';
|
|
1861
|
+
disablePastDates = false;
|
|
1862
|
+
triggerContainer;
|
|
1863
|
+
popover;
|
|
1864
|
+
value = null;
|
|
1865
|
+
inputValue = '';
|
|
1866
|
+
isOpen = false;
|
|
1867
|
+
cleanupAutoUpdate;
|
|
1868
|
+
constructor(cdr) {
|
|
1869
|
+
this.cdr = cdr;
|
|
1870
|
+
}
|
|
1871
|
+
// --- Logic ---
|
|
1872
|
+
onInputChange(str) {
|
|
1873
|
+
if (str?.length === 10) {
|
|
1874
|
+
const parsed = parse(str, 'MM/dd/yyyy', new Date());
|
|
1875
|
+
if (isValid(parsed)) {
|
|
1876
|
+
this.value = startOfDay(parsed);
|
|
1877
|
+
this.onChange(this.value);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
else if (!str) {
|
|
1881
|
+
this.value = null;
|
|
1882
|
+
this.onChange(null);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
onCalendarChange(date) {
|
|
1886
|
+
this.value = date;
|
|
1887
|
+
this.inputValue = format(date, 'MM/dd/yyyy');
|
|
1888
|
+
this.onChange(this.value);
|
|
1889
|
+
this.close();
|
|
1890
|
+
}
|
|
1891
|
+
togglePopover(event) {
|
|
1892
|
+
event.stopPropagation(); // Prevent bubbling to document
|
|
1893
|
+
if (this.disabled)
|
|
1894
|
+
return;
|
|
1895
|
+
this.isOpen ? this.close() : this.open();
|
|
1896
|
+
}
|
|
1897
|
+
open() {
|
|
1898
|
+
this.isOpen = true;
|
|
1899
|
+
setTimeout(() => this.updatePosition());
|
|
1900
|
+
}
|
|
1901
|
+
close() {
|
|
1902
|
+
this.isOpen = false;
|
|
1903
|
+
if (this.cleanupAutoUpdate)
|
|
1904
|
+
this.cleanupAutoUpdate();
|
|
1905
|
+
}
|
|
1906
|
+
clear(event) {
|
|
1907
|
+
event.stopPropagation(); // CRITICAL: Stop the calendar from opening
|
|
1908
|
+
this.value = null;
|
|
1909
|
+
this.inputValue = '';
|
|
1910
|
+
this.onChange(null);
|
|
1911
|
+
this.cdr.markForCheck();
|
|
1912
|
+
}
|
|
1913
|
+
// --- Positioning ---
|
|
1914
|
+
updatePosition() {
|
|
1915
|
+
if (!this.triggerContainer || !this.popover)
|
|
1916
|
+
return;
|
|
1917
|
+
this.cleanupAutoUpdate = autoUpdate(this.triggerContainer.nativeElement, this.popover.nativeElement, () => {
|
|
1918
|
+
computePosition(this.triggerContainer.nativeElement, this.popover.nativeElement, {
|
|
1919
|
+
placement: 'bottom-end', // Aligned to the right where the icon is
|
|
1920
|
+
middleware: [offset(4), flip(), shift({ padding: 8 })],
|
|
1921
|
+
}).then(({ x, y }) => {
|
|
1922
|
+
Object.assign(this.popover.nativeElement.style, {
|
|
1923
|
+
left: `${x}px`,
|
|
1924
|
+
top: `${y}px`,
|
|
1925
|
+
visibility: 'visible',
|
|
1926
|
+
});
|
|
1927
|
+
});
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
onClickOutside(event) {
|
|
1931
|
+
if (this.isOpen &&
|
|
1932
|
+
!this.triggerContainer.nativeElement.contains(event.target) &&
|
|
1933
|
+
!this.popover.nativeElement.contains(event.target)) {
|
|
1934
|
+
this.close();
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
// --- CVA ---
|
|
1938
|
+
onChange = () => { };
|
|
1939
|
+
onTouched = () => { };
|
|
1940
|
+
writeValue(val) {
|
|
1941
|
+
if (val) {
|
|
1942
|
+
const date = new Date(val);
|
|
1943
|
+
if (isValid(date)) {
|
|
1944
|
+
this.value = startOfDay(date);
|
|
1945
|
+
this.inputValue = format(this.value, 'MM/dd/yyyy');
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
this.value = null;
|
|
1950
|
+
this.inputValue = '';
|
|
1951
|
+
}
|
|
1952
|
+
this.cdr.markForCheck();
|
|
1953
|
+
}
|
|
1954
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
1955
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
1956
|
+
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
1957
|
+
cn = cn;
|
|
1958
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1959
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatePickerComponent, isStandalone: true, selector: "tolle-date-picker", inputs: { placeholder: "placeholder", disabled: "disabled", class: "class", disablePastDates: "disablePastDates" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
|
|
1960
|
+
{
|
|
1961
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1962
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
1963
|
+
multi: true
|
|
1964
|
+
}
|
|
1965
|
+
], viewQueries: [{ propertyName: "triggerContainer", first: true, predicate: ["triggerContainer"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
|
|
1966
|
+
<div class="relative w-full" #triggerContainer>
|
|
1967
|
+
<tolle-masked-input
|
|
1968
|
+
#maskInput
|
|
1969
|
+
[mask]="'00/00/0000'"
|
|
1970
|
+
[placeholder]="placeholder"
|
|
1971
|
+
[disabled]="disabled"
|
|
1972
|
+
[(ngModel)]="inputValue"
|
|
1973
|
+
(ngModelChange)="onInputChange($event)"
|
|
1974
|
+
[class]="cn(class)"
|
|
1975
|
+
>
|
|
1976
|
+
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
1977
|
+
<i
|
|
1978
|
+
*ngIf="value && !disabled"
|
|
1979
|
+
(click)="clear($event)"
|
|
1980
|
+
class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
|
1981
|
+
></i>
|
|
1982
|
+
|
|
1983
|
+
<i
|
|
1984
|
+
(click)="togglePopover($event)"
|
|
1985
|
+
class="ri-calendar-line cursor-pointer text-muted-foreground hover:text-primary transition-colors"
|
|
1986
|
+
></i>
|
|
1987
|
+
</div>
|
|
1988
|
+
</tolle-masked-input>
|
|
1989
|
+
|
|
1990
|
+
<div
|
|
1991
|
+
#popover
|
|
1992
|
+
*ngIf="isOpen"
|
|
1993
|
+
class="absolute bg-popover z-50 max-w-max left-0 right-0 overflow-hidden rounded-md border border-border text-popover-foreground bg-background shadow-md"
|
|
1994
|
+
style="visibility: hidden; top: 0; left: 0;"
|
|
1995
|
+
>
|
|
1996
|
+
<tolle-calendar
|
|
1997
|
+
[(ngModel)]="value"
|
|
1998
|
+
(ngModelChange)="onCalendarChange($event)"
|
|
1999
|
+
[disablePastDates]="disablePastDates"
|
|
2000
|
+
></tolle-calendar>
|
|
2001
|
+
</div>
|
|
2002
|
+
</div>
|
|
2003
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MaskedInputComponent, selector: "tolle-masked-input", inputs: ["mask", "placeholder", "type", "disabled", "class", "error", "size", "returnRaw"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "disablePastDates"] }] });
|
|
2004
|
+
}
|
|
2005
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
|
|
2006
|
+
type: Component,
|
|
2007
|
+
args: [{
|
|
2008
|
+
selector: 'tolle-date-picker',
|
|
2009
|
+
standalone: true,
|
|
2010
|
+
imports: [CommonModule, FormsModule, MaskedInputComponent, CalendarComponent],
|
|
2011
|
+
providers: [
|
|
2012
|
+
{
|
|
2013
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2014
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
2015
|
+
multi: true
|
|
2016
|
+
}
|
|
2017
|
+
],
|
|
2018
|
+
template: `
|
|
2019
|
+
<div class="relative w-full" #triggerContainer>
|
|
2020
|
+
<tolle-masked-input
|
|
2021
|
+
#maskInput
|
|
2022
|
+
[mask]="'00/00/0000'"
|
|
2023
|
+
[placeholder]="placeholder"
|
|
2024
|
+
[disabled]="disabled"
|
|
2025
|
+
[(ngModel)]="inputValue"
|
|
2026
|
+
(ngModelChange)="onInputChange($event)"
|
|
2027
|
+
[class]="cn(class)"
|
|
2028
|
+
>
|
|
2029
|
+
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
2030
|
+
<i
|
|
2031
|
+
*ngIf="value && !disabled"
|
|
2032
|
+
(click)="clear($event)"
|
|
2033
|
+
class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
|
2034
|
+
></i>
|
|
2035
|
+
|
|
2036
|
+
<i
|
|
2037
|
+
(click)="togglePopover($event)"
|
|
2038
|
+
class="ri-calendar-line cursor-pointer text-muted-foreground hover:text-primary transition-colors"
|
|
2039
|
+
></i>
|
|
2040
|
+
</div>
|
|
2041
|
+
</tolle-masked-input>
|
|
2042
|
+
|
|
2043
|
+
<div
|
|
2044
|
+
#popover
|
|
2045
|
+
*ngIf="isOpen"
|
|
2046
|
+
class="absolute bg-popover z-50 max-w-max left-0 right-0 overflow-hidden rounded-md border border-border text-popover-foreground bg-background shadow-md"
|
|
2047
|
+
style="visibility: hidden; top: 0; left: 0;"
|
|
2048
|
+
>
|
|
2049
|
+
<tolle-calendar
|
|
2050
|
+
[(ngModel)]="value"
|
|
2051
|
+
(ngModelChange)="onCalendarChange($event)"
|
|
2052
|
+
[disablePastDates]="disablePastDates"
|
|
2053
|
+
></tolle-calendar>
|
|
2054
|
+
</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
`
|
|
2057
|
+
}]
|
|
2058
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { placeholder: [{
|
|
2059
|
+
type: Input
|
|
2060
|
+
}], disabled: [{
|
|
2061
|
+
type: Input
|
|
2062
|
+
}], class: [{
|
|
2063
|
+
type: Input
|
|
2064
|
+
}], disablePastDates: [{
|
|
2065
|
+
type: Input
|
|
2066
|
+
}], triggerContainer: [{
|
|
2067
|
+
type: ViewChild,
|
|
2068
|
+
args: ['triggerContainer']
|
|
2069
|
+
}], popover: [{
|
|
2070
|
+
type: ViewChild,
|
|
2071
|
+
args: ['popover']
|
|
2072
|
+
}], onClickOutside: [{
|
|
2073
|
+
type: HostListener,
|
|
2074
|
+
args: ['document:mousedown', ['$event']]
|
|
2075
|
+
}] } });
|
|
2076
|
+
|
|
2077
|
+
class PaginationComponent {
|
|
2078
|
+
class = '';
|
|
2079
|
+
showPageLinks = true;
|
|
2080
|
+
showPageOptions = true;
|
|
2081
|
+
showCurrentPageInfo = true;
|
|
2082
|
+
currentPageInfoTemplate;
|
|
2083
|
+
totalRecords = 0;
|
|
2084
|
+
currentPageSize = 10;
|
|
2085
|
+
currentPage = 1;
|
|
2086
|
+
pageSizeOptions = [10, 20, 30, 50];
|
|
2087
|
+
onPageNumberChange = new EventEmitter();
|
|
2088
|
+
onPageSizeChange = new EventEmitter();
|
|
2089
|
+
totalPages = 0;
|
|
2090
|
+
first = 0;
|
|
2091
|
+
last = 0;
|
|
2092
|
+
displayPageIndex = [];
|
|
2093
|
+
pageReport = '';
|
|
2094
|
+
initialized = false;
|
|
2095
|
+
cd = inject(ChangeDetectorRef);
|
|
2096
|
+
cn = cn;
|
|
2097
|
+
navBtnClass = 'h-8 w-8 p-0 flex items-center justify-center rounded-md border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:cursor-not-allowed';
|
|
2098
|
+
ngOnInit() {
|
|
2099
|
+
this.initializePagination();
|
|
2100
|
+
}
|
|
2101
|
+
ngOnChanges(changes) {
|
|
2102
|
+
// Only re-init if meaningful data changes
|
|
2103
|
+
if (changes['totalRecords'] || changes['currentPage'] || changes['currentPageSize']) {
|
|
2104
|
+
this.initializePagination();
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
initializePagination() {
|
|
2108
|
+
this.calcPagination();
|
|
2109
|
+
if (!this.initialized) {
|
|
2110
|
+
this.initialized = true;
|
|
2111
|
+
}
|
|
2112
|
+
this.cd.detectChanges();
|
|
2113
|
+
}
|
|
2114
|
+
nextPage() {
|
|
2115
|
+
if (this.totalPages > this.currentPage) {
|
|
2116
|
+
this.currentPage++;
|
|
2117
|
+
this.emitChange();
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
previousPage() {
|
|
2121
|
+
if (this.currentPage > 1) {
|
|
2122
|
+
this.currentPage--;
|
|
2123
|
+
this.emitChange();
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
selectPage(page) {
|
|
2127
|
+
if (this.currentPage === page)
|
|
2128
|
+
return;
|
|
2129
|
+
this.currentPage = page;
|
|
2130
|
+
this.emitChange();
|
|
2131
|
+
}
|
|
2132
|
+
sizeChange(size) {
|
|
2133
|
+
this.currentPageSize = size;
|
|
2134
|
+
this.currentPage = 1; // Reset to page 1 on size change
|
|
2135
|
+
this.emitChange();
|
|
2136
|
+
}
|
|
2137
|
+
emitChange() {
|
|
2138
|
+
this.calcPagination();
|
|
2139
|
+
this.onPageNumberChange.emit(this.currentPage);
|
|
2140
|
+
this.onPageSizeChange.emit(this.currentPageSize);
|
|
2141
|
+
this.cd.detectChanges();
|
|
2142
|
+
}
|
|
2143
|
+
calcPagination() {
|
|
2144
|
+
this.totalPages = Math.ceil(this.totalRecords / this.currentPageSize) || 0;
|
|
2145
|
+
// Bounds check
|
|
2146
|
+
if (this.currentPage > this.totalPages && this.totalPages > 0) {
|
|
2147
|
+
this.currentPage = this.totalPages;
|
|
2148
|
+
}
|
|
2149
|
+
this.first = this.totalRecords === 0 ? 0 : (this.currentPage - 1) * this.currentPageSize + 1;
|
|
2150
|
+
this.last = Math.min(this.totalRecords, this.currentPage * this.currentPageSize);
|
|
2151
|
+
// Calculate Sliding Window for Page Numbers (Max 5 visible)
|
|
2152
|
+
const pages = Array.from({ length: this.totalPages }, (_, i) => i + 1);
|
|
2153
|
+
if (this.totalPages <= 5) {
|
|
2154
|
+
this.displayPageIndex = pages;
|
|
2155
|
+
}
|
|
2156
|
+
else {
|
|
2157
|
+
let start = Math.max(0, this.currentPage - 3);
|
|
2158
|
+
let end = start + 5;
|
|
2159
|
+
if (end > this.totalPages) {
|
|
2160
|
+
end = this.totalPages;
|
|
2161
|
+
start = end - 5;
|
|
2162
|
+
}
|
|
2163
|
+
this.displayPageIndex = pages.slice(start, end);
|
|
2164
|
+
}
|
|
2165
|
+
// Template Parsing
|
|
2166
|
+
if (this.currentPageInfoTemplate) {
|
|
2167
|
+
this.pageReport = this.currentPageInfoTemplate.replace(/{first}|{last}|{totalRecords}|{currentPage}|{currentPageSize}|{totalPages}/g, (match) => {
|
|
2168
|
+
switch (match) {
|
|
2169
|
+
case '{first}': return `${this.first}`;
|
|
2170
|
+
case '{last}': return `${this.last}`;
|
|
2171
|
+
case '{totalRecords}': return `${this.totalRecords}`;
|
|
2172
|
+
case '{totalPages}': return `${this.totalPages}`;
|
|
2173
|
+
case '{currentPage}': return `${this.currentPage}`;
|
|
2174
|
+
case '{currentPageSize}': return `${this.currentPageSize}`;
|
|
2175
|
+
default: return match;
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2181
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: PaginationComponent, isStandalone: true, selector: "tolle-pagination", inputs: { class: "class", showPageLinks: "showPageLinks", showPageOptions: "showPageOptions", showCurrentPageInfo: "showCurrentPageInfo", currentPageInfoTemplate: "currentPageInfoTemplate", totalRecords: "totalRecords", currentPageSize: "currentPageSize", currentPage: "currentPage", pageSizeOptions: "pageSizeOptions" }, outputs: { onPageNumberChange: "onPageNumberChange", onPageSizeChange: "onPageSizeChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2182
|
+
<div [class]="cn('flex items-center justify-between px-2 py-4', class)">
|
|
2183
|
+
|
|
2184
|
+
<div *ngIf="showCurrentPageInfo" class="text-sm text-muted-foreground">
|
|
2185
|
+
<ng-container *ngIf="currentPageInfoTemplate; else defaultReport">
|
|
2186
|
+
{{ pageReport }}
|
|
2187
|
+
</ng-container>
|
|
2188
|
+
<ng-template #defaultReport>
|
|
2189
|
+
Showing {{ first }} to {{ last }} of {{ totalRecords }} entries
|
|
2190
|
+
</ng-template>
|
|
2191
|
+
</div>
|
|
2192
|
+
|
|
2193
|
+
<div class="flex items-center space-x-6 lg:space-x-8">
|
|
2194
|
+
|
|
2195
|
+
<div *ngIf="showPageOptions" class="flex items-center space-x-2">
|
|
2196
|
+
<p class="text-sm font-medium">Rows per page</p>
|
|
2197
|
+
<tolle-select
|
|
2198
|
+
class="w-[70px]"
|
|
2199
|
+
size="sm"
|
|
2200
|
+
[ngModel]="currentPageSize"
|
|
2201
|
+
(ngModelChange)="sizeChange($event)"
|
|
2202
|
+
>
|
|
2203
|
+
<tolle-select-item *ngFor="let opt of pageSizeOptions" [value]="opt">
|
|
2204
|
+
{{ opt }}
|
|
2205
|
+
</tolle-select-item>
|
|
2206
|
+
</tolle-select>
|
|
2207
|
+
</div>
|
|
2208
|
+
|
|
2209
|
+
<div class="flex items-center space-x-2">
|
|
2210
|
+
<div *ngIf="!showPageLinks" class="flex w-[100px] items-center justify-center text-sm font-medium">
|
|
2211
|
+
Page {{ currentPage }} of {{ totalPages }}
|
|
2212
|
+
</div>
|
|
2213
|
+
|
|
2214
|
+
<div *ngIf="showPageLinks" class="flex items-center space-x-1">
|
|
2215
|
+
<button
|
|
2216
|
+
(click)="previousPage()"
|
|
2217
|
+
[disabled]="currentPage === 1"
|
|
2218
|
+
[class]="navBtnClass"
|
|
2219
|
+
>
|
|
2220
|
+
<i class="ri-arrow-left-s-line"></i>
|
|
2221
|
+
</button>
|
|
2222
|
+
|
|
2223
|
+
<button
|
|
2224
|
+
*ngFor="let page of displayPageIndex"
|
|
2225
|
+
(click)="selectPage(page)"
|
|
2226
|
+
[class]="cn(
|
|
2227
|
+
'h-8 w-8 text-sm rounded-md flex items-center justify-center transition-colors',
|
|
2228
|
+
currentPage === page
|
|
2229
|
+
? 'bg-primary text-primary-foreground font-medium'
|
|
2230
|
+
: 'hover:bg-accent hover:text-accent-foreground'
|
|
2231
|
+
)"
|
|
2232
|
+
>
|
|
2233
|
+
{{ page }}
|
|
2234
|
+
</button>
|
|
2235
|
+
|
|
2236
|
+
<button
|
|
2237
|
+
(click)="nextPage()"
|
|
2238
|
+
[disabled]="currentPage === totalPages || totalPages === 0"
|
|
2239
|
+
[class]="navBtnClass"
|
|
2240
|
+
>
|
|
2241
|
+
<i class="ri-arrow-right-s-line"></i>
|
|
2242
|
+
</button>
|
|
2243
|
+
</div>
|
|
2244
|
+
</div>
|
|
2245
|
+
</div>
|
|
2246
|
+
</div>
|
|
2247
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: SelectComponent$1, selector: "tolle-select", inputs: ["placeholder", "class", "disabled", "searchable", "size"] }, { kind: "component", type: SelectItemComponent$1, selector: "tolle-select-item", inputs: ["value", "class", "selected"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2248
|
+
}
|
|
2249
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PaginationComponent, decorators: [{
|
|
2250
|
+
type: Component,
|
|
2251
|
+
args: [{
|
|
2252
|
+
selector: 'tolle-pagination',
|
|
2253
|
+
standalone: true,
|
|
2254
|
+
imports: [CommonModule, SelectComponent$1, SelectItemComponent$1, FormsModule],
|
|
2255
|
+
template: `
|
|
2256
|
+
<div [class]="cn('flex items-center justify-between px-2 py-4', class)">
|
|
2257
|
+
|
|
2258
|
+
<div *ngIf="showCurrentPageInfo" class="text-sm text-muted-foreground">
|
|
2259
|
+
<ng-container *ngIf="currentPageInfoTemplate; else defaultReport">
|
|
2260
|
+
{{ pageReport }}
|
|
2261
|
+
</ng-container>
|
|
2262
|
+
<ng-template #defaultReport>
|
|
2263
|
+
Showing {{ first }} to {{ last }} of {{ totalRecords }} entries
|
|
2264
|
+
</ng-template>
|
|
2265
|
+
</div>
|
|
2266
|
+
|
|
2267
|
+
<div class="flex items-center space-x-6 lg:space-x-8">
|
|
2268
|
+
|
|
2269
|
+
<div *ngIf="showPageOptions" class="flex items-center space-x-2">
|
|
2270
|
+
<p class="text-sm font-medium">Rows per page</p>
|
|
2271
|
+
<tolle-select
|
|
2272
|
+
class="w-[70px]"
|
|
2273
|
+
size="sm"
|
|
2274
|
+
[ngModel]="currentPageSize"
|
|
2275
|
+
(ngModelChange)="sizeChange($event)"
|
|
2276
|
+
>
|
|
2277
|
+
<tolle-select-item *ngFor="let opt of pageSizeOptions" [value]="opt">
|
|
2278
|
+
{{ opt }}
|
|
2279
|
+
</tolle-select-item>
|
|
2280
|
+
</tolle-select>
|
|
2281
|
+
</div>
|
|
2282
|
+
|
|
2283
|
+
<div class="flex items-center space-x-2">
|
|
2284
|
+
<div *ngIf="!showPageLinks" class="flex w-[100px] items-center justify-center text-sm font-medium">
|
|
2285
|
+
Page {{ currentPage }} of {{ totalPages }}
|
|
2286
|
+
</div>
|
|
2287
|
+
|
|
2288
|
+
<div *ngIf="showPageLinks" class="flex items-center space-x-1">
|
|
2289
|
+
<button
|
|
2290
|
+
(click)="previousPage()"
|
|
2291
|
+
[disabled]="currentPage === 1"
|
|
2292
|
+
[class]="navBtnClass"
|
|
2293
|
+
>
|
|
2294
|
+
<i class="ri-arrow-left-s-line"></i>
|
|
2295
|
+
</button>
|
|
2296
|
+
|
|
2297
|
+
<button
|
|
2298
|
+
*ngFor="let page of displayPageIndex"
|
|
2299
|
+
(click)="selectPage(page)"
|
|
2300
|
+
[class]="cn(
|
|
2301
|
+
'h-8 w-8 text-sm rounded-md flex items-center justify-center transition-colors',
|
|
2302
|
+
currentPage === page
|
|
2303
|
+
? 'bg-primary text-primary-foreground font-medium'
|
|
2304
|
+
: 'hover:bg-accent hover:text-accent-foreground'
|
|
2305
|
+
)"
|
|
2306
|
+
>
|
|
2307
|
+
{{ page }}
|
|
2308
|
+
</button>
|
|
2309
|
+
|
|
2310
|
+
<button
|
|
2311
|
+
(click)="nextPage()"
|
|
2312
|
+
[disabled]="currentPage === totalPages || totalPages === 0"
|
|
2313
|
+
[class]="navBtnClass"
|
|
2314
|
+
>
|
|
2315
|
+
<i class="ri-arrow-right-s-line"></i>
|
|
2316
|
+
</button>
|
|
2317
|
+
</div>
|
|
2318
|
+
</div>
|
|
2319
|
+
</div>
|
|
2320
|
+
</div>
|
|
2321
|
+
`,
|
|
2322
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
2323
|
+
}]
|
|
2324
|
+
}], propDecorators: { class: [{
|
|
2325
|
+
type: Input
|
|
2326
|
+
}], showPageLinks: [{
|
|
2327
|
+
type: Input
|
|
2328
|
+
}], showPageOptions: [{
|
|
2329
|
+
type: Input
|
|
2330
|
+
}], showCurrentPageInfo: [{
|
|
2331
|
+
type: Input
|
|
2332
|
+
}], currentPageInfoTemplate: [{
|
|
2333
|
+
type: Input
|
|
2334
|
+
}], totalRecords: [{
|
|
2335
|
+
type: Input
|
|
2336
|
+
}], currentPageSize: [{
|
|
2337
|
+
type: Input
|
|
2338
|
+
}], currentPage: [{
|
|
2339
|
+
type: Input
|
|
2340
|
+
}], pageSizeOptions: [{
|
|
2341
|
+
type: Input
|
|
2342
|
+
}], onPageNumberChange: [{
|
|
2343
|
+
type: Output
|
|
2344
|
+
}], onPageSizeChange: [{
|
|
2345
|
+
type: Output
|
|
2346
|
+
}] } });
|
|
2347
|
+
|
|
2348
|
+
class DataTableComponent {
|
|
2349
|
+
data = [];
|
|
2350
|
+
columns = [];
|
|
2351
|
+
searchable = true;
|
|
2352
|
+
paginate = true;
|
|
2353
|
+
pageSize = 10;
|
|
2354
|
+
expandable = false;
|
|
2355
|
+
size = 'default';
|
|
2356
|
+
// Track which rows are open
|
|
2357
|
+
expandedRows = new Set();
|
|
2358
|
+
// Use ContentChildren to grab the tolleCell templates from the user's HTML
|
|
2359
|
+
cellTemplates;
|
|
2360
|
+
// Keep this as an Input for the main expansion slot
|
|
2361
|
+
expandedTemplate;
|
|
2362
|
+
filteredData = [];
|
|
2363
|
+
pagedData = [];
|
|
2364
|
+
searchTerm = '';
|
|
2365
|
+
currentPage = 1;
|
|
2366
|
+
sortKey = '';
|
|
2367
|
+
sortDir = null;
|
|
2368
|
+
// 2. Map Size to Padding for Cells
|
|
2369
|
+
get cellPaddingClass() {
|
|
2370
|
+
switch (this.size) {
|
|
2371
|
+
case 'xs': return 'p-1 px-4'; // Ultra-compact
|
|
2372
|
+
case 'sm': return 'p-2 px-4'; // Dense
|
|
2373
|
+
case 'lg': return 'p-6 px-4'; // Spacious
|
|
2374
|
+
default: return 'p-4'; // Standard (16px)
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
// 3. Map Size to Padding for Header (usually slightly shorter than cells)
|
|
2378
|
+
get headerPaddingClass() {
|
|
2379
|
+
switch (this.size) {
|
|
2380
|
+
case 'xs': return 'h-7 px-4';
|
|
2381
|
+
case 'sm': return 'h-9 px-4';
|
|
2382
|
+
case 'lg': return 'h-14 px-4';
|
|
2383
|
+
default: return 'h-12 px-4';
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
// 4. Map Size to Font Sizes
|
|
2387
|
+
get fontSizeClass() {
|
|
2388
|
+
switch (this.size) {
|
|
2389
|
+
case 'xs': return 'text-[11px]';
|
|
2390
|
+
case 'sm': return 'text-xs';
|
|
2391
|
+
case 'lg': return 'text-base';
|
|
2392
|
+
default: return 'text-sm';
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
cn = cn;
|
|
2396
|
+
ngOnInit() { this.refreshTable(); }
|
|
2397
|
+
ngOnChanges(changes) {
|
|
2398
|
+
if (changes['data']) {
|
|
2399
|
+
this.refreshTable();
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
// --- Search & Sort & Page Logic ---
|
|
2403
|
+
// (Your existing implementation of applySearch, applySort, and updatePage goes here)
|
|
2404
|
+
refreshTable() { this.applySearch(); this.applySort(); this.updatePage(); }
|
|
2405
|
+
onSearch() { this.currentPage = 1; this.refreshTable(); }
|
|
2406
|
+
applySearch() {
|
|
2407
|
+
if (!this.searchTerm) {
|
|
2408
|
+
this.filteredData = [...this.data];
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
const q = this.searchTerm.toLowerCase();
|
|
2412
|
+
this.filteredData = this.data.filter(row => Object.values(row).some(val => String(val).toLowerCase().includes(q)));
|
|
2413
|
+
}
|
|
2414
|
+
applySort() {
|
|
2415
|
+
if (!this.sortKey || !this.sortDir)
|
|
2416
|
+
return;
|
|
2417
|
+
this.filteredData.sort((a, b) => {
|
|
2418
|
+
const valA = a[this.sortKey];
|
|
2419
|
+
const valB = b[this.sortKey];
|
|
2420
|
+
if (valA < valB)
|
|
2421
|
+
return this.sortDir === 'asc' ? -1 : 1;
|
|
2422
|
+
if (valA > valB)
|
|
2423
|
+
return this.sortDir === 'asc' ? 1 : -1;
|
|
2424
|
+
return 0;
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
updatePage() {
|
|
2428
|
+
if (!this.paginate) {
|
|
2429
|
+
this.pagedData = this.filteredData;
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
2433
|
+
const end = start + this.pageSize;
|
|
2434
|
+
this.pagedData = this.filteredData.slice(start, end);
|
|
2435
|
+
}
|
|
2436
|
+
// --- Helpers ---
|
|
2437
|
+
toggleSort(key) {
|
|
2438
|
+
if (this.sortKey === key) {
|
|
2439
|
+
this.sortDir = this.sortDir === 'asc' ? 'desc' : this.sortDir === 'desc' ? null : 'asc';
|
|
2440
|
+
}
|
|
2441
|
+
else {
|
|
2442
|
+
this.sortKey = key;
|
|
2443
|
+
this.sortDir = 'asc';
|
|
2444
|
+
}
|
|
2445
|
+
this.refreshTable();
|
|
2446
|
+
}
|
|
2447
|
+
getSortIcon(key) {
|
|
2448
|
+
if (this.sortKey !== key || !this.sortDir)
|
|
2449
|
+
return 'ri-arrow-up-down-line opacity-30';
|
|
2450
|
+
return this.sortDir === 'asc' ? 'ri-arrow-up-line' : 'ri-arrow-down-line';
|
|
2451
|
+
}
|
|
2452
|
+
toggleRow(index) {
|
|
2453
|
+
if (this.expandedRows.has(index))
|
|
2454
|
+
this.expandedRows.delete(index);
|
|
2455
|
+
else
|
|
2456
|
+
this.expandedRows.add(index);
|
|
2457
|
+
}
|
|
2458
|
+
// Helper to find the right cell template
|
|
2459
|
+
getTemplate(key) {
|
|
2460
|
+
return this.cellTemplates?.find(t => t.name === key);
|
|
2461
|
+
}
|
|
2462
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2463
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DataTableComponent, isStandalone: true, selector: "tolle-data-table", inputs: { data: "data", columns: "columns", searchable: "searchable", paginate: "paginate", pageSize: "pageSize", expandable: "expandable", size: "size", expandedTemplate: "expandedTemplate" }, queries: [{ propertyName: "cellTemplates", predicate: TolleCellDirective$1 }], usesOnChanges: true, ngImport: i0, template: `
|
|
2464
|
+
<div class="space-y-4">
|
|
2465
|
+
<div *ngIf="searchable" class="flex items-center py-2">
|
|
2466
|
+
<tolle-input
|
|
2467
|
+
[size]="size === 'lg' ? 'default' : 'sm'"
|
|
2468
|
+
class="max-w-sm"
|
|
2469
|
+
placeholder="Filter records..."
|
|
2470
|
+
[(ngModel)]="searchTerm"
|
|
2471
|
+
(ngModelChange)="onSearch()">
|
|
2472
|
+
<i prefix class="ri-search-line"></i>
|
|
2473
|
+
</tolle-input>
|
|
2474
|
+
</div>
|
|
2475
|
+
|
|
2476
|
+
<div class="rounded-md border border-border bg-background overflow-hidden shadow-sm">
|
|
2477
|
+
<table class="w-full text-sm">
|
|
2478
|
+
<thead class="border-b bg-muted/30">
|
|
2479
|
+
<tr>
|
|
2480
|
+
<th *ngIf="expandable" [class]="cn('px-4', size === 'xs' ? 'w-[32px]' : 'w-[48px]')"></th>
|
|
2481
|
+
<th *ngFor="let col of columns"
|
|
2482
|
+
[class]="cn(
|
|
2483
|
+
'font-medium text-muted-foreground transition-all',
|
|
2484
|
+
headerPaddingClass,
|
|
2485
|
+
fontSizeClass,
|
|
2486
|
+
col.class
|
|
2487
|
+
)">
|
|
2488
|
+
<div *ngIf="col.sortable; else simpleHeader" (click)="toggleSort(col.key)" class="flex items-center gap-1 cursor-pointer hover:text-foreground">
|
|
2489
|
+
{{ col.label }}
|
|
2490
|
+
<i [class]="getSortIcon(col.key)"></i>
|
|
2491
|
+
</div>
|
|
2492
|
+
<ng-template #simpleHeader>{{ col.label }}</ng-template>
|
|
2493
|
+
</th>
|
|
2494
|
+
</tr>
|
|
2495
|
+
</thead>
|
|
2496
|
+
<tbody class="divide-y divide-border">
|
|
2497
|
+
<ng-container *ngFor="let row of pagedData; let i = index">
|
|
2498
|
+
<tr class="hover:bg-muted/50 transition-colors">
|
|
2499
|
+
<td *ngIf="expandable" class="px-4">
|
|
2500
|
+
<button (click)="toggleRow(i)"
|
|
2501
|
+
[class]="cn(
|
|
2502
|
+
'flex items-center justify-center rounded-md hover:bg-accent text-muted-foreground hover:text-foreground',
|
|
2503
|
+
size === 'xs' ? 'h-6 w-6' : 'h-8 w-8'
|
|
2504
|
+
)">
|
|
2505
|
+
<i [class]="expandedRows.has(i) ? 'ri-arrow-down-s-line' : 'ri-arrow-right-s-line'"></i>
|
|
2506
|
+
</button>
|
|
2507
|
+
</td>
|
|
2508
|
+
|
|
2509
|
+
<td *ngFor="let col of columns"
|
|
2510
|
+
[class]="cn(
|
|
2511
|
+
'align-middle transition-all',
|
|
2512
|
+
cellPaddingClass,
|
|
2513
|
+
fontSizeClass,
|
|
2514
|
+
col.class
|
|
2515
|
+
)">
|
|
2516
|
+
<ng-container *ngIf="getTemplate(col.key) as cell; else defaultValue">
|
|
2517
|
+
<ng-container *ngTemplateOutlet="cell.template; context: { $implicit: row[col.key], row: row }"></ng-container>
|
|
2518
|
+
</ng-container>
|
|
2519
|
+
<ng-template #defaultValue>
|
|
2520
|
+
<span class="text-foreground">{{ row[col.key] }}</span>
|
|
2521
|
+
</ng-template>
|
|
2522
|
+
</td>
|
|
2523
|
+
</tr>
|
|
2524
|
+
|
|
2525
|
+
<tr *ngIf="expandedRows.has(i)" class="bg-muted/10">
|
|
2526
|
+
<td [attr.colspan]="columns.length + (expandable ? 1 : 0)" class="p-0">
|
|
2527
|
+
<div class="p-6 border-b border-dashed border-border">
|
|
2528
|
+
<ng-container *ngIf="expandedTemplate; else defaultExpanded">
|
|
2529
|
+
<ng-container *ngTemplateOutlet="expandedTemplate; context: { row: row }"></ng-container>
|
|
2530
|
+
</ng-container>
|
|
2531
|
+
<ng-template #defaultExpanded>
|
|
2532
|
+
<div class="text-xs text-muted-foreground italic">No details available.</div>
|
|
2533
|
+
</ng-template>
|
|
2534
|
+
</div>
|
|
2535
|
+
</td>
|
|
2536
|
+
</tr>
|
|
2537
|
+
</ng-container>
|
|
2538
|
+
</tbody>
|
|
2539
|
+
</table>
|
|
2540
|
+
</div>
|
|
2541
|
+
|
|
2542
|
+
<tolle-pagination
|
|
2543
|
+
*ngIf="paginate"
|
|
2544
|
+
[totalRecords]="filteredData.length"
|
|
2545
|
+
[currentPage]="currentPage"
|
|
2546
|
+
[currentPageSize]="pageSize"
|
|
2547
|
+
(onPageNumberChange)="updatePage()"
|
|
2548
|
+
(onPageSizeChange)="updatePage()"
|
|
2549
|
+
></tolle-pagination>
|
|
2550
|
+
</div>
|
|
2551
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PaginationComponent$1, selector: "tolle-pagination", inputs: ["class", "showPageLinks", "showPageOptions", "showCurrentPageInfo", "currentPageInfoTemplate", "totalRecords", "currentPageSize", "currentPage", "pageSizeOptions"], outputs: ["onPageNumberChange", "onPageSizeChange"] }, { kind: "component", type: InputComponent$1, selector: "tolle-input", inputs: ["type", "placeholder", "disabled", "error", "size", "containerClass", "class"] }] });
|
|
2552
|
+
}
|
|
2553
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataTableComponent, decorators: [{
|
|
2554
|
+
type: Component,
|
|
2555
|
+
args: [{
|
|
2556
|
+
selector: 'tolle-data-table',
|
|
2557
|
+
standalone: true,
|
|
2558
|
+
imports: [CommonModule, FormsModule, PaginationComponent$1, InputComponent$1],
|
|
2559
|
+
template: `
|
|
2560
|
+
<div class="space-y-4">
|
|
2561
|
+
<div *ngIf="searchable" class="flex items-center py-2">
|
|
2562
|
+
<tolle-input
|
|
2563
|
+
[size]="size === 'lg' ? 'default' : 'sm'"
|
|
2564
|
+
class="max-w-sm"
|
|
2565
|
+
placeholder="Filter records..."
|
|
2566
|
+
[(ngModel)]="searchTerm"
|
|
2567
|
+
(ngModelChange)="onSearch()">
|
|
2568
|
+
<i prefix class="ri-search-line"></i>
|
|
2569
|
+
</tolle-input>
|
|
2570
|
+
</div>
|
|
2571
|
+
|
|
2572
|
+
<div class="rounded-md border border-border bg-background overflow-hidden shadow-sm">
|
|
2573
|
+
<table class="w-full text-sm">
|
|
2574
|
+
<thead class="border-b bg-muted/30">
|
|
2575
|
+
<tr>
|
|
2576
|
+
<th *ngIf="expandable" [class]="cn('px-4', size === 'xs' ? 'w-[32px]' : 'w-[48px]')"></th>
|
|
2577
|
+
<th *ngFor="let col of columns"
|
|
2578
|
+
[class]="cn(
|
|
2579
|
+
'font-medium text-muted-foreground transition-all',
|
|
2580
|
+
headerPaddingClass,
|
|
2581
|
+
fontSizeClass,
|
|
2582
|
+
col.class
|
|
2583
|
+
)">
|
|
2584
|
+
<div *ngIf="col.sortable; else simpleHeader" (click)="toggleSort(col.key)" class="flex items-center gap-1 cursor-pointer hover:text-foreground">
|
|
2585
|
+
{{ col.label }}
|
|
2586
|
+
<i [class]="getSortIcon(col.key)"></i>
|
|
2587
|
+
</div>
|
|
2588
|
+
<ng-template #simpleHeader>{{ col.label }}</ng-template>
|
|
2589
|
+
</th>
|
|
2590
|
+
</tr>
|
|
2591
|
+
</thead>
|
|
2592
|
+
<tbody class="divide-y divide-border">
|
|
2593
|
+
<ng-container *ngFor="let row of pagedData; let i = index">
|
|
2594
|
+
<tr class="hover:bg-muted/50 transition-colors">
|
|
2595
|
+
<td *ngIf="expandable" class="px-4">
|
|
2596
|
+
<button (click)="toggleRow(i)"
|
|
2597
|
+
[class]="cn(
|
|
2598
|
+
'flex items-center justify-center rounded-md hover:bg-accent text-muted-foreground hover:text-foreground',
|
|
2599
|
+
size === 'xs' ? 'h-6 w-6' : 'h-8 w-8'
|
|
2600
|
+
)">
|
|
2601
|
+
<i [class]="expandedRows.has(i) ? 'ri-arrow-down-s-line' : 'ri-arrow-right-s-line'"></i>
|
|
2602
|
+
</button>
|
|
2603
|
+
</td>
|
|
2604
|
+
|
|
2605
|
+
<td *ngFor="let col of columns"
|
|
2606
|
+
[class]="cn(
|
|
2607
|
+
'align-middle transition-all',
|
|
2608
|
+
cellPaddingClass,
|
|
2609
|
+
fontSizeClass,
|
|
2610
|
+
col.class
|
|
2611
|
+
)">
|
|
2612
|
+
<ng-container *ngIf="getTemplate(col.key) as cell; else defaultValue">
|
|
2613
|
+
<ng-container *ngTemplateOutlet="cell.template; context: { $implicit: row[col.key], row: row }"></ng-container>
|
|
2614
|
+
</ng-container>
|
|
2615
|
+
<ng-template #defaultValue>
|
|
2616
|
+
<span class="text-foreground">{{ row[col.key] }}</span>
|
|
2617
|
+
</ng-template>
|
|
2618
|
+
</td>
|
|
2619
|
+
</tr>
|
|
2620
|
+
|
|
2621
|
+
<tr *ngIf="expandedRows.has(i)" class="bg-muted/10">
|
|
2622
|
+
<td [attr.colspan]="columns.length + (expandable ? 1 : 0)" class="p-0">
|
|
2623
|
+
<div class="p-6 border-b border-dashed border-border">
|
|
2624
|
+
<ng-container *ngIf="expandedTemplate; else defaultExpanded">
|
|
2625
|
+
<ng-container *ngTemplateOutlet="expandedTemplate; context: { row: row }"></ng-container>
|
|
2626
|
+
</ng-container>
|
|
2627
|
+
<ng-template #defaultExpanded>
|
|
2628
|
+
<div class="text-xs text-muted-foreground italic">No details available.</div>
|
|
2629
|
+
</ng-template>
|
|
2630
|
+
</div>
|
|
2631
|
+
</td>
|
|
2632
|
+
</tr>
|
|
2633
|
+
</ng-container>
|
|
2634
|
+
</tbody>
|
|
2635
|
+
</table>
|
|
2636
|
+
</div>
|
|
2637
|
+
|
|
2638
|
+
<tolle-pagination
|
|
2639
|
+
*ngIf="paginate"
|
|
2640
|
+
[totalRecords]="filteredData.length"
|
|
2641
|
+
[currentPage]="currentPage"
|
|
2642
|
+
[currentPageSize]="pageSize"
|
|
2643
|
+
(onPageNumberChange)="updatePage()"
|
|
2644
|
+
(onPageSizeChange)="updatePage()"
|
|
2645
|
+
></tolle-pagination>
|
|
2646
|
+
</div>
|
|
2647
|
+
`
|
|
2648
|
+
}]
|
|
2649
|
+
}], propDecorators: { data: [{
|
|
2650
|
+
type: Input
|
|
2651
|
+
}], columns: [{
|
|
2652
|
+
type: Input
|
|
2653
|
+
}], searchable: [{
|
|
2654
|
+
type: Input
|
|
2655
|
+
}], paginate: [{
|
|
2656
|
+
type: Input
|
|
2657
|
+
}], pageSize: [{
|
|
2658
|
+
type: Input
|
|
2659
|
+
}], expandable: [{
|
|
2660
|
+
type: Input
|
|
2661
|
+
}], size: [{
|
|
2662
|
+
type: Input
|
|
2663
|
+
}], cellTemplates: [{
|
|
2664
|
+
type: ContentChildren,
|
|
2665
|
+
args: [TolleCellDirective$1]
|
|
2666
|
+
}], expandedTemplate: [{
|
|
2667
|
+
type: Input
|
|
2668
|
+
}] } });
|
|
2669
|
+
|
|
2670
|
+
class TolleCellDirective {
|
|
2671
|
+
template;
|
|
2672
|
+
name; // The column key this template belongs to
|
|
2673
|
+
constructor(template) {
|
|
2674
|
+
this.template = template;
|
|
2675
|
+
}
|
|
2676
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TolleCellDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2677
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: TolleCellDirective, isStandalone: true, selector: "[tolleCell]", inputs: { name: ["tolleCell", "name"] }, ngImport: i0 });
|
|
2678
|
+
}
|
|
2679
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TolleCellDirective, decorators: [{
|
|
2680
|
+
type: Directive,
|
|
2681
|
+
args: [{
|
|
2682
|
+
selector: '[tolleCell]',
|
|
2683
|
+
standalone: true
|
|
2684
|
+
}]
|
|
2685
|
+
}], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{
|
|
2686
|
+
type: Input,
|
|
2687
|
+
args: ['tolleCell']
|
|
2688
|
+
}] } });
|
|
2689
|
+
|
|
2690
|
+
class AccordionItemComponent {
|
|
2691
|
+
title = '';
|
|
2692
|
+
class = '';
|
|
2693
|
+
id;
|
|
2694
|
+
isOpen = false;
|
|
2695
|
+
// This will be set by the parent Accordion component
|
|
2696
|
+
onToggle;
|
|
2697
|
+
toggle() {
|
|
2698
|
+
if (this.onToggle) {
|
|
2699
|
+
this.onToggle(this.id);
|
|
2700
|
+
}
|
|
2701
|
+
else {
|
|
2702
|
+
this.isOpen = !this.isOpen;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
cn = cn;
|
|
2706
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AccordionItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2707
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: AccordionItemComponent, isStandalone: true, selector: "tolle-accordion-item", inputs: { title: "title", class: "class", id: "id" }, ngImport: i0, template: `
|
|
2708
|
+
<div [class]="cn('flex flex-col border-b border-border', class)">
|
|
2709
|
+
<button
|
|
2710
|
+
type="button"
|
|
2711
|
+
(click)="toggle()"
|
|
2712
|
+
class="flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>i]:rotate-180"
|
|
2713
|
+
[attr.data-state]="isOpen ? 'open' : 'closed'"
|
|
2714
|
+
>
|
|
2715
|
+
<span class="text-left">{{ title }}</span>
|
|
2716
|
+
<i class="ri-arrow-down-s-line text-muted-foreground transition-transform"></i>
|
|
2717
|
+
</button>
|
|
2718
|
+
|
|
2719
|
+
<div
|
|
2720
|
+
*ngIf="isOpen"
|
|
2721
|
+
class="pb-4 text-sm text-muted-foreground overflow-hidden"
|
|
2722
|
+
>
|
|
2723
|
+
<ng-content></ng-content>
|
|
2724
|
+
</div>
|
|
2725
|
+
</div>
|
|
2726
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
2727
|
+
}
|
|
2728
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AccordionItemComponent, decorators: [{
|
|
2729
|
+
type: Component,
|
|
2730
|
+
args: [{
|
|
2731
|
+
selector: 'tolle-accordion-item',
|
|
2732
|
+
standalone: true,
|
|
2733
|
+
imports: [CommonModule],
|
|
2734
|
+
template: `
|
|
2735
|
+
<div [class]="cn('flex flex-col border-b border-border', class)">
|
|
2736
|
+
<button
|
|
2737
|
+
type="button"
|
|
2738
|
+
(click)="toggle()"
|
|
2739
|
+
class="flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>i]:rotate-180"
|
|
2740
|
+
[attr.data-state]="isOpen ? 'open' : 'closed'"
|
|
2741
|
+
>
|
|
2742
|
+
<span class="text-left">{{ title }}</span>
|
|
2743
|
+
<i class="ri-arrow-down-s-line text-muted-foreground transition-transform"></i>
|
|
2744
|
+
</button>
|
|
2745
|
+
|
|
2746
|
+
<div
|
|
2747
|
+
*ngIf="isOpen"
|
|
2748
|
+
class="pb-4 text-sm text-muted-foreground overflow-hidden"
|
|
2749
|
+
>
|
|
2750
|
+
<ng-content></ng-content>
|
|
2751
|
+
</div>
|
|
2752
|
+
</div>
|
|
2753
|
+
`
|
|
2754
|
+
}]
|
|
2755
|
+
}], propDecorators: { title: [{
|
|
2756
|
+
type: Input
|
|
2757
|
+
}], class: [{
|
|
2758
|
+
type: Input
|
|
2759
|
+
}], id: [{
|
|
2760
|
+
type: Input
|
|
2761
|
+
}] } });
|
|
2762
|
+
|
|
2763
|
+
class AccordionComponent {
|
|
2764
|
+
type = 'single';
|
|
2765
|
+
class = '';
|
|
2766
|
+
items;
|
|
2767
|
+
ngAfterContentInit() {
|
|
2768
|
+
this.items.forEach((item, index) => {
|
|
2769
|
+
// Assign unique ID if none provided
|
|
2770
|
+
if (item.id === undefined)
|
|
2771
|
+
item.id = index;
|
|
2772
|
+
// Hook into the item's toggle event
|
|
2773
|
+
item.onToggle = (id) => this.handleToggle(id);
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
handleToggle(selectedId) {
|
|
2777
|
+
this.items.forEach(item => {
|
|
2778
|
+
if (item.id === selectedId) {
|
|
2779
|
+
item.isOpen = !item.isOpen;
|
|
2780
|
+
}
|
|
2781
|
+
else {
|
|
2782
|
+
// If type is 'single', close all other items
|
|
2783
|
+
if (this.type === 'single') {
|
|
2784
|
+
item.isOpen = false;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
cn = cn;
|
|
2790
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AccordionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2791
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: AccordionComponent, isStandalone: true, selector: "tolle-accordion", inputs: { type: "type", class: "class" }, queries: [{ propertyName: "items", predicate: AccordionItemComponent }], ngImport: i0, template: `
|
|
2792
|
+
<div [class]="cn('w-full border-t border-border', class)">
|
|
2793
|
+
<ng-content></ng-content>
|
|
2794
|
+
</div>
|
|
2795
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
2796
|
+
}
|
|
2797
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AccordionComponent, decorators: [{
|
|
2798
|
+
type: Component,
|
|
2799
|
+
args: [{
|
|
2800
|
+
selector: 'tolle-accordion',
|
|
2801
|
+
standalone: true,
|
|
2802
|
+
imports: [CommonModule],
|
|
2803
|
+
template: `
|
|
2804
|
+
<div [class]="cn('w-full border-t border-border', class)">
|
|
2805
|
+
<ng-content></ng-content>
|
|
2806
|
+
</div>
|
|
2807
|
+
`
|
|
2808
|
+
}]
|
|
2809
|
+
}], propDecorators: { type: [{
|
|
2810
|
+
type: Input
|
|
2811
|
+
}], class: [{
|
|
2812
|
+
type: Input
|
|
2813
|
+
}], items: [{
|
|
2814
|
+
type: ContentChildren,
|
|
2815
|
+
args: [AccordionItemComponent]
|
|
2816
|
+
}] } });
|
|
2817
|
+
|
|
2818
|
+
class ModalRef {
|
|
2819
|
+
overlay;
|
|
2820
|
+
modal;
|
|
2821
|
+
stack;
|
|
2822
|
+
_afterClosed$ = new Subject();
|
|
2823
|
+
afterClosed$ = this._afterClosed$.asObservable();
|
|
2824
|
+
constructor(overlay, modal, stack) {
|
|
2825
|
+
this.overlay = overlay;
|
|
2826
|
+
this.modal = modal;
|
|
2827
|
+
this.stack = stack;
|
|
2828
|
+
this.stack.register(this);
|
|
2829
|
+
// Handle Backdrop Click
|
|
2830
|
+
this.overlay.backdropClick().subscribe(() => {
|
|
2831
|
+
if (this.modal.backdropClose) {
|
|
2832
|
+
this.close();
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Closes the modal instantly.
|
|
2838
|
+
* @param result Data to pass back to the caller
|
|
2839
|
+
*/
|
|
2840
|
+
close(result) {
|
|
2841
|
+
this._afterClosed$.next(result);
|
|
2842
|
+
this._afterClosed$.complete();
|
|
2843
|
+
this.overlay.dispose(); // Instant disposal (No animation timer)
|
|
2844
|
+
this.stack.unregister(this);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
class ModalComponent {
|
|
2849
|
+
ref;
|
|
2850
|
+
contentType = 'string';
|
|
2851
|
+
content;
|
|
2852
|
+
modalClasses = '';
|
|
2853
|
+
constructor(ref) {
|
|
2854
|
+
this.ref = ref;
|
|
2855
|
+
}
|
|
2856
|
+
ngOnInit() {
|
|
2857
|
+
this.content = this.ref.modal.content;
|
|
2858
|
+
this.modalClasses = this.getModalSizeCss();
|
|
2859
|
+
this.determineContentType();
|
|
2860
|
+
}
|
|
2861
|
+
getModalSizeCss() {
|
|
2862
|
+
const { size } = this.ref.modal;
|
|
2863
|
+
return cn(
|
|
2864
|
+
// Base classes: Added 'w-full' and 'mx-auto'
|
|
2865
|
+
'bg-background border border-border shadow-lg relative flex flex-col w-full mx-auto overflow-hidden', size === 'fullscreen' ? 'h-screen w-screen rounded-none' : 'rounded-lg',
|
|
2866
|
+
// Sizing scale with explicit max-widths
|
|
2867
|
+
size === 'xs' && 'max-w-[320px]', size === 'sm' && 'max-w-[425px]', size === 'default' && 'max-w-[544px]', size === 'lg' && 'max-w-[1024px]');
|
|
2868
|
+
}
|
|
2869
|
+
determineContentType() {
|
|
2870
|
+
if (typeof this.content === 'string')
|
|
2871
|
+
this.contentType = 'string';
|
|
2872
|
+
else if (this.content instanceof TemplateRef)
|
|
2873
|
+
this.contentType = 'template';
|
|
2874
|
+
else
|
|
2875
|
+
this.contentType = 'component';
|
|
2876
|
+
}
|
|
2877
|
+
get asTemplate() { return this.content; }
|
|
2878
|
+
get asComponent() { return this.content; }
|
|
2879
|
+
cn = cn;
|
|
2880
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalComponent, deps: [{ token: ModalRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2881
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ModalComponent, isStandalone: true, selector: "tolle-modal", ngImport: i0, template: `
|
|
2882
|
+
<div [class]="modalClasses" class="pointer-events-auto" (mousedown)="$event.stopPropagation()">
|
|
2883
|
+
|
|
2884
|
+
<div class="flex items-center justify-between p-6 border-b border-border">
|
|
2885
|
+
<h3 *ngIf="ref.modal.title" class="text-lg font-semibold text-foreground tracking-tight">
|
|
2886
|
+
{{ ref.modal.title }}
|
|
2887
|
+
</h3>
|
|
2888
|
+
<button (click)="ref.close()" class="ml-auto p-1 text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors">
|
|
2889
|
+
<i class="ri-close-line text-2xl"></i>
|
|
2890
|
+
</button>
|
|
2891
|
+
</div>
|
|
2892
|
+
|
|
2893
|
+
<div class="p-6 overflow-y-auto max-h-[80vh] text-foreground">
|
|
2894
|
+
<ng-container [ngSwitch]="contentType">
|
|
2895
|
+
<div *ngSwitchCase="'string'">{{ content }}</div>
|
|
2896
|
+
|
|
2897
|
+
<ng-container *ngSwitchCase="'template'">
|
|
2898
|
+
<ng-container *ngTemplateOutlet="asTemplate; context: ref.modal.context"></ng-container>
|
|
2899
|
+
</ng-container>
|
|
2900
|
+
|
|
2901
|
+
<ng-container *ngSwitchCase="'component'">
|
|
2902
|
+
<ng-container *ngComponentOutlet="asComponent"></ng-container>
|
|
2903
|
+
</ng-container>
|
|
2904
|
+
</ng-container>
|
|
2905
|
+
</div>
|
|
2906
|
+
</div>
|
|
2907
|
+
`, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }] });
|
|
2908
|
+
}
|
|
2909
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalComponent, decorators: [{
|
|
2910
|
+
type: Component,
|
|
2911
|
+
args: [{ selector: 'tolle-modal', standalone: true, imports: [CommonModule], template: `
|
|
2912
|
+
<div [class]="modalClasses" class="pointer-events-auto" (mousedown)="$event.stopPropagation()">
|
|
2913
|
+
|
|
2914
|
+
<div class="flex items-center justify-between p-6 border-b border-border">
|
|
2915
|
+
<h3 *ngIf="ref.modal.title" class="text-lg font-semibold text-foreground tracking-tight">
|
|
2916
|
+
{{ ref.modal.title }}
|
|
2917
|
+
</h3>
|
|
2918
|
+
<button (click)="ref.close()" class="ml-auto p-1 text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors">
|
|
2919
|
+
<i class="ri-close-line text-2xl"></i>
|
|
2920
|
+
</button>
|
|
2921
|
+
</div>
|
|
2922
|
+
|
|
2923
|
+
<div class="p-6 overflow-y-auto max-h-[80vh] text-foreground">
|
|
2924
|
+
<ng-container [ngSwitch]="contentType">
|
|
2925
|
+
<div *ngSwitchCase="'string'">{{ content }}</div>
|
|
2926
|
+
|
|
2927
|
+
<ng-container *ngSwitchCase="'template'">
|
|
2928
|
+
<ng-container *ngTemplateOutlet="asTemplate; context: ref.modal.context"></ng-container>
|
|
2929
|
+
</ng-container>
|
|
2930
|
+
|
|
2931
|
+
<ng-container *ngSwitchCase="'component'">
|
|
2932
|
+
<ng-container *ngComponentOutlet="asComponent"></ng-container>
|
|
2933
|
+
</ng-container>
|
|
2934
|
+
</ng-container>
|
|
2935
|
+
</div>
|
|
2936
|
+
</div>
|
|
2937
|
+
`, styles: [":host{display:contents}\n"] }]
|
|
2938
|
+
}], ctorParameters: () => [{ type: ModalRef }] });
|
|
2939
|
+
|
|
2940
|
+
class ModalStackService {
|
|
2941
|
+
_stack = new Set();
|
|
2942
|
+
register(ref) {
|
|
2943
|
+
this._stack.add(ref);
|
|
2944
|
+
}
|
|
2945
|
+
unregister(ref) {
|
|
2946
|
+
this._stack.delete(ref);
|
|
2947
|
+
}
|
|
2948
|
+
/** Instantly closes all open modals */
|
|
2949
|
+
closeAll() {
|
|
2950
|
+
this._stack.forEach(ref => ref.close());
|
|
2951
|
+
this._stack.clear();
|
|
2952
|
+
}
|
|
2953
|
+
get activeCount() {
|
|
2954
|
+
return this._stack.size;
|
|
2955
|
+
}
|
|
2956
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalStackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2957
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalStackService, providedIn: 'root' });
|
|
2958
|
+
}
|
|
2959
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalStackService, decorators: [{
|
|
2960
|
+
type: Injectable,
|
|
2961
|
+
args: [{ providedIn: 'root' }]
|
|
2962
|
+
}] });
|
|
2963
|
+
|
|
2964
|
+
class ModalService {
|
|
2965
|
+
overlay;
|
|
2966
|
+
injector;
|
|
2967
|
+
stack;
|
|
2968
|
+
constructor(overlay, injector, stack) {
|
|
2969
|
+
this.overlay = overlay;
|
|
2970
|
+
this.injector = injector;
|
|
2971
|
+
this.stack = stack;
|
|
2972
|
+
}
|
|
2973
|
+
open(config) {
|
|
2974
|
+
// 1. Create the Overlay (DOM placeholder)
|
|
2975
|
+
const overlayRef = this.createOverlay();
|
|
2976
|
+
// 2. Create the Controller (Ref)
|
|
2977
|
+
const modalRef = new ModalRef(overlayRef, config, this.stack);
|
|
2978
|
+
// 3. Create Injector to allow the Component to access the Ref
|
|
2979
|
+
const injector = Injector.create({
|
|
2980
|
+
parent: this.injector,
|
|
2981
|
+
providers: [{ provide: ModalRef, useValue: modalRef }]
|
|
2982
|
+
});
|
|
2983
|
+
// 4. Attach the UI Component to the Overlay
|
|
2984
|
+
const portal = new ComponentPortal(ModalComponent, null, injector);
|
|
2985
|
+
overlayRef.attach(portal);
|
|
2986
|
+
return modalRef;
|
|
2987
|
+
}
|
|
2988
|
+
/** Global helper to close everything */
|
|
2989
|
+
closeAll() {
|
|
2990
|
+
this.stack.closeAll();
|
|
2991
|
+
}
|
|
2992
|
+
// modal.service.ts
|
|
2993
|
+
createOverlay() {
|
|
2994
|
+
const config = new OverlayConfig({
|
|
2995
|
+
hasBackdrop: true,
|
|
2996
|
+
backdropClass: ['cdk-overlay-backdrop', 'bg-black/80', 'backdrop-blur-sm'],
|
|
2997
|
+
panelClass: [
|
|
2998
|
+
'w-full',
|
|
2999
|
+
'h-full',
|
|
3000
|
+
'flex',
|
|
3001
|
+
'items-center',
|
|
3002
|
+
'justify-center',
|
|
3003
|
+
'pointer-events-none'
|
|
3004
|
+
],
|
|
3005
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
3006
|
+
positionStrategy: this.overlay.position()
|
|
3007
|
+
.global()
|
|
3008
|
+
.centerHorizontally()
|
|
3009
|
+
.centerVertically()
|
|
3010
|
+
});
|
|
3011
|
+
return this.overlay.create(config);
|
|
3012
|
+
}
|
|
3013
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalService, deps: [{ token: i1$2.Overlay }, { token: i0.Injector }, { token: ModalStackService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3014
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalService, providedIn: 'root' });
|
|
3015
|
+
}
|
|
3016
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ModalService, decorators: [{
|
|
3017
|
+
type: Injectable,
|
|
3018
|
+
args: [{ providedIn: 'root' }]
|
|
3019
|
+
}], ctorParameters: () => [{ type: i1$2.Overlay }, { type: i0.Injector }, { type: ModalStackService }] });
|
|
3020
|
+
|
|
3021
|
+
class Modal {
|
|
3022
|
+
/** The content to display (String, Component, or Template) */
|
|
3023
|
+
content;
|
|
3024
|
+
/** Optional title for the standard header */
|
|
3025
|
+
title;
|
|
3026
|
+
/** * Predefined size scale.
|
|
3027
|
+
* - xs: 320px (Mobile alerts)
|
|
3028
|
+
* - sm: 425px (Standard dialogs)
|
|
3029
|
+
* - default: 544px (Forms)
|
|
3030
|
+
* - lg: 90% / 1024px (Data tables)
|
|
3031
|
+
* - fullscreen: 100vw/100vh (Complex workflows)
|
|
3032
|
+
*/
|
|
3033
|
+
size = 'default';
|
|
3034
|
+
/** * If true (default), clicking the backdrop closes the modal.
|
|
3035
|
+
* Set to false for "blocking" modals (e.g., Terms of Service).
|
|
3036
|
+
*/
|
|
3037
|
+
backdropClose = true;
|
|
3038
|
+
/** * Data to pass to a Component content.
|
|
3039
|
+
* Accessed via @Input() in the child component.
|
|
3040
|
+
*/
|
|
3041
|
+
data;
|
|
3042
|
+
/** * Context to pass to a TemplateRef content.
|
|
3043
|
+
* Accessed via `let-val` in the HTML.
|
|
3044
|
+
*/
|
|
3045
|
+
context;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
class ButtonGroupComponent {
|
|
3049
|
+
class = '';
|
|
3050
|
+
cn = cn;
|
|
3051
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3052
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonGroupComponent, isStandalone: true, selector: "tolle-button-group", inputs: { class: "class" }, ngImport: i0, template: `
|
|
3053
|
+
<div [class]="cn('inline-flex items-center -space-x-px rounded-md shadow-sm', class)">
|
|
3054
|
+
<ng-content></ng-content>
|
|
3055
|
+
</div>
|
|
3056
|
+
`, isInline: true, styles: [":host{display:inline-block}:host ::ng-deep tolle-button:first-child button{@apply rounded-r-none;}:host ::ng-deep tolle-button:not(:first-child):not(:last-child) button{@apply rounded-none border-l-0;}:host ::ng-deep tolle-button:last-child:not(:first-child) button{@apply rounded-l-none border-l-0;}:host ::ng-deep tolle-button button:hover,:host ::ng-deep tolle-button button:focus{@apply z-10 relative;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
3057
|
+
}
|
|
3058
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonGroupComponent, decorators: [{
|
|
3059
|
+
type: Component,
|
|
3060
|
+
args: [{ selector: 'tolle-button-group', standalone: true, imports: [CommonModule], template: `
|
|
3061
|
+
<div [class]="cn('inline-flex items-center -space-x-px rounded-md shadow-sm', class)">
|
|
3062
|
+
<ng-content></ng-content>
|
|
3063
|
+
</div>
|
|
3064
|
+
`, styles: [":host{display:inline-block}:host ::ng-deep tolle-button:first-child button{@apply rounded-r-none;}:host ::ng-deep tolle-button:not(:first-child):not(:last-child) button{@apply rounded-none border-l-0;}:host ::ng-deep tolle-button:last-child:not(:first-child) button{@apply rounded-l-none border-l-0;}:host ::ng-deep tolle-button button:hover,:host ::ng-deep tolle-button button:focus{@apply z-10 relative;}\n"] }]
|
|
3065
|
+
}], propDecorators: { class: [{
|
|
3066
|
+
type: Input
|
|
3067
|
+
}] } });
|
|
3068
|
+
|
|
3069
|
+
class RangeCalendarComponent {
|
|
3070
|
+
class = '';
|
|
3071
|
+
disablePastDates = false;
|
|
3072
|
+
rangeSelect = new EventEmitter(); // Emits whenever selection changes
|
|
3073
|
+
currentView = 'date';
|
|
3074
|
+
viewDate = new Date();
|
|
3075
|
+
// The Range Value
|
|
3076
|
+
value = { start: null, end: null };
|
|
3077
|
+
weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
3078
|
+
daysInMonth = [];
|
|
3079
|
+
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
3080
|
+
years = [];
|
|
3081
|
+
navBtnClass = cn('h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 border border-input rounded-md flex items-center justify-center hover:bg-accent hover:text-accent-foreground transition-all');
|
|
3082
|
+
onTouched = () => { };
|
|
3083
|
+
onChange = () => { };
|
|
3084
|
+
cn = cn;
|
|
3085
|
+
ngOnInit() {
|
|
3086
|
+
this.generateDays();
|
|
3087
|
+
this.generateYears();
|
|
3088
|
+
}
|
|
3089
|
+
// --- Date Generation Logic (Same as Calendar) ---
|
|
3090
|
+
generateDays() {
|
|
3091
|
+
const start = startOfWeek(startOfMonth(this.viewDate));
|
|
3092
|
+
const end = endOfWeek(endOfMonth(this.viewDate));
|
|
3093
|
+
this.daysInMonth = eachDayOfInterval({ start, end });
|
|
3094
|
+
}
|
|
3095
|
+
generateYears() {
|
|
3096
|
+
const currentYear = this.viewDate.getFullYear();
|
|
3097
|
+
this.years = Array.from({ length: 16 }, (_, i) => currentYear - 6 + i);
|
|
3098
|
+
}
|
|
3099
|
+
setView(view) {
|
|
3100
|
+
this.currentView = view;
|
|
3101
|
+
if (view === 'year')
|
|
3102
|
+
this.generateYears();
|
|
3103
|
+
}
|
|
3104
|
+
prev() {
|
|
3105
|
+
if (this.currentView === 'date') {
|
|
3106
|
+
this.viewDate = subMonths(this.viewDate, 1);
|
|
3107
|
+
this.generateDays();
|
|
3108
|
+
}
|
|
3109
|
+
else if (this.currentView === 'year') {
|
|
3110
|
+
this.viewDate = subYears(this.viewDate, 16);
|
|
3111
|
+
this.generateYears();
|
|
3112
|
+
}
|
|
3113
|
+
else if (this.currentView === 'month') {
|
|
3114
|
+
this.viewDate = subYears(this.viewDate, 1);
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
next() {
|
|
3118
|
+
if (this.currentView === 'date') {
|
|
3119
|
+
this.viewDate = addMonths(this.viewDate, 1);
|
|
3120
|
+
this.generateDays();
|
|
3121
|
+
}
|
|
3122
|
+
else if (this.currentView === 'year') {
|
|
3123
|
+
this.viewDate = addYears(this.viewDate, 16);
|
|
3124
|
+
this.generateYears();
|
|
3125
|
+
}
|
|
3126
|
+
else if (this.currentView === 'month') {
|
|
3127
|
+
this.viewDate = addYears(this.viewDate, 1);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
// --- Range Selection Logic ---
|
|
3131
|
+
selectDate(date) {
|
|
3132
|
+
if (this.isDateDisabled(date))
|
|
3133
|
+
return;
|
|
3134
|
+
const { start, end } = this.value;
|
|
3135
|
+
// 1. If start exists but end doesn't
|
|
3136
|
+
if (start && !end) {
|
|
3137
|
+
if (isBefore(date, start)) {
|
|
3138
|
+
// User clicked earlier date -> Reset start
|
|
3139
|
+
this.value = { start: date, end: null };
|
|
3140
|
+
}
|
|
3141
|
+
else {
|
|
3142
|
+
// User clicked later date -> Complete range
|
|
3143
|
+
this.value = { start, end: date };
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
// 2. If neither exist OR both exist (reset)
|
|
3147
|
+
else {
|
|
3148
|
+
this.value = { start: date, end: null };
|
|
3149
|
+
}
|
|
3150
|
+
if (!isSameMonth(date, this.viewDate)) {
|
|
3151
|
+
this.viewDate = date;
|
|
3152
|
+
this.generateDays();
|
|
3153
|
+
}
|
|
3154
|
+
this.onChange(this.value);
|
|
3155
|
+
this.rangeSelect.emit(this.value);
|
|
3156
|
+
this.onTouched();
|
|
3157
|
+
}
|
|
3158
|
+
selectMonth(monthIndex) {
|
|
3159
|
+
this.viewDate = setMonth(this.viewDate, monthIndex);
|
|
3160
|
+
this.currentView = 'date';
|
|
3161
|
+
this.generateDays();
|
|
3162
|
+
}
|
|
3163
|
+
selectYear(year) {
|
|
3164
|
+
this.viewDate = setYear(this.viewDate, year);
|
|
3165
|
+
this.currentView = 'date';
|
|
3166
|
+
this.generateDays();
|
|
3167
|
+
}
|
|
3168
|
+
// --- Visual Styling for Range ---
|
|
3169
|
+
getDayClass(date) {
|
|
3170
|
+
const { start, end } = this.value;
|
|
3171
|
+
const isOutside = !isSameMonth(date, this.viewDate);
|
|
3172
|
+
const isDisabled = this.isDateDisabled(date);
|
|
3173
|
+
// Range Checks
|
|
3174
|
+
const isStart = start && isSameDay(date, start);
|
|
3175
|
+
const isEnd = end && isSameDay(date, end);
|
|
3176
|
+
const isInside = start && end && isWithinInterval(date, { start, end });
|
|
3177
|
+
const isTodayDate = isToday(date);
|
|
3178
|
+
return cn(
|
|
3179
|
+
// Base: h-9 w-9, but we remove margins/rounding for the 'strip' effect
|
|
3180
|
+
'h-9 w-9 p-0 font-normal text-sm transition-all flex items-center justify-center relative z-10',
|
|
3181
|
+
// Default State (Not selected, Not disabled)
|
|
3182
|
+
!isInside && !isStart && !isEnd && !isDisabled && 'hover:bg-accent hover:text-accent-foreground rounded-md',
|
|
3183
|
+
// The "Caps": Start and End
|
|
3184
|
+
(isStart || isEnd) && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
|
|
3185
|
+
// The "Strip": Dates between start and end
|
|
3186
|
+
isInside && !isStart && !isEnd && 'bg-accent text-accent-foreground rounded-none',
|
|
3187
|
+
// Connecting the Caps to the Strip (Rectangular inside edges)
|
|
3188
|
+
isStart && end && 'rounded-l-md rounded-r-none', isEnd && start && 'rounded-r-md rounded-l-none',
|
|
3189
|
+
// If no end date yet, Start should be fully rounded
|
|
3190
|
+
isStart && !end && 'rounded-md',
|
|
3191
|
+
// Muted/Disabled logic
|
|
3192
|
+
!isInside && isTodayDate && !isStart && !isEnd && 'bg-accent/50 text-accent-foreground rounded-md', (isOutside || isDisabled) && 'text-muted-foreground opacity-50', isDisabled && 'cursor-not-allowed');
|
|
3193
|
+
}
|
|
3194
|
+
isDateDisabled(date) {
|
|
3195
|
+
return this.disablePastDates ? isBefore(date, startOfDay(new Date())) : false;
|
|
3196
|
+
}
|
|
3197
|
+
// --- CVA Implementation ---
|
|
3198
|
+
writeValue(val) {
|
|
3199
|
+
if (val) {
|
|
3200
|
+
this.value = val;
|
|
3201
|
+
if (val.start)
|
|
3202
|
+
this.viewDate = val.start;
|
|
3203
|
+
this.generateDays();
|
|
3204
|
+
this.generateYears();
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
3208
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
3209
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RangeCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3210
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: RangeCalendarComponent, isStandalone: true, selector: "tolle-range-calendar", inputs: { class: "class", disablePastDates: "disablePastDates" }, outputs: { rangeSelect: "rangeSelect" }, providers: [
|
|
3211
|
+
{
|
|
3212
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3213
|
+
useExisting: forwardRef(() => RangeCalendarComponent),
|
|
3214
|
+
multi: true
|
|
3215
|
+
}
|
|
3216
|
+
], ngImport: i0, template: `
|
|
3217
|
+
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block min-w-fit', class)">
|
|
3218
|
+
|
|
3219
|
+
<div class="flex items-center justify-between pt-1 pb-4 gap-2">
|
|
3220
|
+
<div class="flex items-center gap-1">
|
|
3221
|
+
<button type="button" (click)="setView('month')"
|
|
3222
|
+
[class]="cn('text-sm font-semibold px-2 py-1 rounded transition-colors', currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground')">
|
|
3223
|
+
{{ viewDate | date: 'MMMM' }}
|
|
3224
|
+
</button>
|
|
3225
|
+
<button type="button" (click)="setView('year')"
|
|
3226
|
+
[class]="cn('text-sm font-semibold px-2 py-1 rounded transition-colors', currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground')">
|
|
3227
|
+
{{ viewDate | date: 'yyyy' }}
|
|
3228
|
+
</button>
|
|
3229
|
+
</div>
|
|
3230
|
+
<div class="flex items-center space-x-1">
|
|
3231
|
+
<button type="button" (click)="prev()" [class]="navBtnClass"><i class="ri-arrow-left-s-line text-lg"></i></button>
|
|
3232
|
+
<button type="button" (click)="next()" [class]="navBtnClass"><i class="ri-arrow-right-s-line text-lg"></i></button>
|
|
3233
|
+
</div>
|
|
3234
|
+
</div>
|
|
3235
|
+
|
|
3236
|
+
<div *ngIf="currentView === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
|
|
3237
|
+
<div class="grid grid-cols-7 gap-y-1 w-full">
|
|
3238
|
+
<span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
|
|
3239
|
+
{{ day }}
|
|
3240
|
+
</span>
|
|
3241
|
+
</div>
|
|
3242
|
+
<div class="grid grid-cols-7 gap-y-1 w-full">
|
|
3243
|
+
<button
|
|
3244
|
+
*ngFor="let date of daysInMonth"
|
|
3245
|
+
type="button"
|
|
3246
|
+
(click)="selectDate(date)"
|
|
3247
|
+
[disabled]="isDateDisabled(date)"
|
|
3248
|
+
[class]="getDayClass(date)"
|
|
3249
|
+
>
|
|
3250
|
+
{{ date | date: 'd' }}
|
|
3251
|
+
</button>
|
|
3252
|
+
</div>
|
|
3253
|
+
</div>
|
|
3254
|
+
|
|
3255
|
+
<div *ngIf="currentView === 'month'" class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
3256
|
+
<button *ngFor="let month of months; let i = index" type="button" (click)="selectMonth(i)"
|
|
3257
|
+
[class]="cn('text-sm py-2.5 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors', i === viewDate.getMonth() ? 'bg-primary text-primary-foreground' : '')">
|
|
3258
|
+
{{ month }}
|
|
3259
|
+
</button>
|
|
3260
|
+
</div>
|
|
3261
|
+
|
|
3262
|
+
<div *ngIf="currentView === 'year'" class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
3263
|
+
<button *ngFor="let year of years" type="button" (click)="selectYear(year)"
|
|
3264
|
+
[class]="cn('text-sm py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors', year === viewDate.getFullYear() ? 'bg-primary text-primary-foreground' : '')">
|
|
3265
|
+
{{ year }}
|
|
3266
|
+
</button>
|
|
3267
|
+
</div>
|
|
3268
|
+
</div>
|
|
3269
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] });
|
|
3270
|
+
}
|
|
3271
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RangeCalendarComponent, decorators: [{
|
|
3272
|
+
type: Component,
|
|
3273
|
+
args: [{
|
|
3274
|
+
selector: 'tolle-range-calendar',
|
|
3275
|
+
standalone: true,
|
|
3276
|
+
imports: [CommonModule],
|
|
3277
|
+
providers: [
|
|
3278
|
+
{
|
|
3279
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3280
|
+
useExisting: forwardRef(() => RangeCalendarComponent),
|
|
3281
|
+
multi: true
|
|
3282
|
+
}
|
|
3283
|
+
],
|
|
3284
|
+
template: `
|
|
3285
|
+
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block min-w-fit', class)">
|
|
3286
|
+
|
|
3287
|
+
<div class="flex items-center justify-between pt-1 pb-4 gap-2">
|
|
3288
|
+
<div class="flex items-center gap-1">
|
|
3289
|
+
<button type="button" (click)="setView('month')"
|
|
3290
|
+
[class]="cn('text-sm font-semibold px-2 py-1 rounded transition-colors', currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground')">
|
|
3291
|
+
{{ viewDate | date: 'MMMM' }}
|
|
3292
|
+
</button>
|
|
3293
|
+
<button type="button" (click)="setView('year')"
|
|
3294
|
+
[class]="cn('text-sm font-semibold px-2 py-1 rounded transition-colors', currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground')">
|
|
3295
|
+
{{ viewDate | date: 'yyyy' }}
|
|
3296
|
+
</button>
|
|
3297
|
+
</div>
|
|
3298
|
+
<div class="flex items-center space-x-1">
|
|
3299
|
+
<button type="button" (click)="prev()" [class]="navBtnClass"><i class="ri-arrow-left-s-line text-lg"></i></button>
|
|
3300
|
+
<button type="button" (click)="next()" [class]="navBtnClass"><i class="ri-arrow-right-s-line text-lg"></i></button>
|
|
3301
|
+
</div>
|
|
3302
|
+
</div>
|
|
3303
|
+
|
|
3304
|
+
<div *ngIf="currentView === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
|
|
3305
|
+
<div class="grid grid-cols-7 gap-y-1 w-full">
|
|
3306
|
+
<span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
|
|
3307
|
+
{{ day }}
|
|
3308
|
+
</span>
|
|
3309
|
+
</div>
|
|
3310
|
+
<div class="grid grid-cols-7 gap-y-1 w-full">
|
|
3311
|
+
<button
|
|
3312
|
+
*ngFor="let date of daysInMonth"
|
|
3313
|
+
type="button"
|
|
3314
|
+
(click)="selectDate(date)"
|
|
3315
|
+
[disabled]="isDateDisabled(date)"
|
|
3316
|
+
[class]="getDayClass(date)"
|
|
3317
|
+
>
|
|
3318
|
+
{{ date | date: 'd' }}
|
|
3319
|
+
</button>
|
|
3320
|
+
</div>
|
|
3321
|
+
</div>
|
|
3322
|
+
|
|
3323
|
+
<div *ngIf="currentView === 'month'" class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
3324
|
+
<button *ngFor="let month of months; let i = index" type="button" (click)="selectMonth(i)"
|
|
3325
|
+
[class]="cn('text-sm py-2.5 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors', i === viewDate.getMonth() ? 'bg-primary text-primary-foreground' : '')">
|
|
3326
|
+
{{ month }}
|
|
3327
|
+
</button>
|
|
3328
|
+
</div>
|
|
3329
|
+
|
|
3330
|
+
<div *ngIf="currentView === 'year'" class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
3331
|
+
<button *ngFor="let year of years" type="button" (click)="selectYear(year)"
|
|
3332
|
+
[class]="cn('text-sm py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors', year === viewDate.getFullYear() ? 'bg-primary text-primary-foreground' : '')">
|
|
3333
|
+
{{ year }}
|
|
3334
|
+
</button>
|
|
3335
|
+
</div>
|
|
3336
|
+
</div>
|
|
3337
|
+
`
|
|
3338
|
+
}]
|
|
3339
|
+
}], propDecorators: { class: [{
|
|
3340
|
+
type: Input
|
|
3341
|
+
}], disablePastDates: [{
|
|
3342
|
+
type: Input
|
|
3343
|
+
}], rangeSelect: [{
|
|
3344
|
+
type: Output
|
|
3345
|
+
}] } });
|
|
3346
|
+
|
|
3347
|
+
class DateRangePickerComponent {
|
|
3348
|
+
cdr;
|
|
3349
|
+
disabled = false;
|
|
3350
|
+
placeholder = 'Pick a date range';
|
|
3351
|
+
class = '';
|
|
3352
|
+
disablePastDates = false;
|
|
3353
|
+
// Standardized Sizes
|
|
3354
|
+
size = 'default';
|
|
3355
|
+
triggerContainer;
|
|
3356
|
+
popover;
|
|
3357
|
+
value = { start: null, end: null };
|
|
3358
|
+
isOpen = false;
|
|
3359
|
+
cleanupAutoUpdate;
|
|
3360
|
+
constructor(cdr) {
|
|
3361
|
+
this.cdr = cdr;
|
|
3362
|
+
}
|
|
3363
|
+
get displayValue() {
|
|
3364
|
+
if (!this.value.start)
|
|
3365
|
+
return '';
|
|
3366
|
+
const startStr = format(this.value.start, 'MMM dd, yyyy'); // Using date-fns format
|
|
3367
|
+
if (!this.value.end)
|
|
3368
|
+
return startStr;
|
|
3369
|
+
const endStr = format(this.value.end, 'MMM dd, yyyy');
|
|
3370
|
+
return `${startStr} - ${endStr}`;
|
|
3371
|
+
}
|
|
3372
|
+
onCalendarSelect(range) {
|
|
3373
|
+
this.value = range;
|
|
3374
|
+
this.onChange(this.value);
|
|
3375
|
+
// Close only if range is complete
|
|
3376
|
+
if (range.start && range.end) {
|
|
3377
|
+
this.onChange(this.value);
|
|
3378
|
+
// Small delay for UX
|
|
3379
|
+
setTimeout(() => this.close(), 150);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
togglePopover(event) {
|
|
3383
|
+
if (this.disabled)
|
|
3384
|
+
return;
|
|
3385
|
+
this.isOpen ? this.close() : this.open();
|
|
3386
|
+
}
|
|
3387
|
+
open() {
|
|
3388
|
+
this.isOpen = true;
|
|
3389
|
+
setTimeout(() => this.updatePosition());
|
|
3390
|
+
}
|
|
3391
|
+
close() {
|
|
3392
|
+
this.isOpen = false;
|
|
3393
|
+
if (this.cleanupAutoUpdate)
|
|
3394
|
+
this.cleanupAutoUpdate();
|
|
3395
|
+
}
|
|
3396
|
+
clear(event) {
|
|
3397
|
+
event.stopPropagation(); // Stop button click
|
|
3398
|
+
this.value = { start: null, end: null };
|
|
3399
|
+
this.onChange(this.value);
|
|
3400
|
+
}
|
|
3401
|
+
// --- Floating UI Positioning ---
|
|
3402
|
+
updatePosition() {
|
|
3403
|
+
if (!this.triggerContainer || !this.popover)
|
|
3404
|
+
return;
|
|
3405
|
+
this.cleanupAutoUpdate = autoUpdate(this.triggerContainer.nativeElement, this.popover.nativeElement, () => {
|
|
3406
|
+
computePosition(this.triggerContainer.nativeElement, this.popover.nativeElement, {
|
|
3407
|
+
placement: 'bottom-start', // Aligned to the right where the icon is
|
|
3408
|
+
middleware: [offset(4), flip(), shift({ padding: 8 })],
|
|
3409
|
+
}).then(({ x, y }) => {
|
|
3410
|
+
Object.assign(this.popover.nativeElement.style, {
|
|
3411
|
+
left: `${x}px`,
|
|
3412
|
+
top: `${y}px`,
|
|
3413
|
+
visibility: 'visible',
|
|
3414
|
+
});
|
|
3415
|
+
});
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
onClickOutside(event) {
|
|
3419
|
+
if (this.isOpen &&
|
|
3420
|
+
!this.triggerContainer.nativeElement.contains(event.target) &&
|
|
3421
|
+
!this.popover.nativeElement.contains(event.target)) {
|
|
3422
|
+
this.close();
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
// CVA
|
|
3426
|
+
onChange = () => { };
|
|
3427
|
+
onTouched = () => { };
|
|
3428
|
+
writeValue(val) {
|
|
3429
|
+
if (val) {
|
|
3430
|
+
this.value = { ...val };
|
|
3431
|
+
}
|
|
3432
|
+
else {
|
|
3433
|
+
this.value = { start: null, end: null };
|
|
3434
|
+
}
|
|
3435
|
+
this.cdr.markForCheck();
|
|
3436
|
+
}
|
|
3437
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
3438
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
3439
|
+
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
3440
|
+
cn = cn;
|
|
3441
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DateRangePickerComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3442
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DateRangePickerComponent, isStandalone: true, selector: "tolle-date-range-picker", inputs: { disabled: "disabled", placeholder: "placeholder", class: "class", disablePastDates: "disablePastDates", size: "size" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
|
|
3443
|
+
{
|
|
3444
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3445
|
+
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
3446
|
+
multi: true
|
|
3447
|
+
}
|
|
3448
|
+
], viewQueries: [{ propertyName: "triggerContainer", first: true, predicate: ["triggerContainer"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
|
|
3449
|
+
<div class="relative w-full" #triggerContainer>
|
|
3450
|
+
<tolle-input [placeholder]="placeholder" [disabled]="disabled" [ngModel]="displayValue">
|
|
3451
|
+
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
3452
|
+
<i
|
|
3453
|
+
*ngIf="(value.start || value.end) && !disabled"
|
|
3454
|
+
(click)="clear($event)"
|
|
3455
|
+
class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
|
3456
|
+
></i>
|
|
3457
|
+
|
|
3458
|
+
<i
|
|
3459
|
+
(click)="togglePopover($event)"
|
|
3460
|
+
class="ri-calendar-line cursor-pointer text-muted-foreground hover:text-primary transition-colors"
|
|
3461
|
+
></i>
|
|
3462
|
+
</div>
|
|
3463
|
+
</tolle-input>
|
|
3464
|
+
<div
|
|
3465
|
+
#popover
|
|
3466
|
+
*ngIf="isOpen"
|
|
3467
|
+
class="absolute z-50 min-w-72"
|
|
3468
|
+
style="visibility: hidden; top: 0; left: 0;"
|
|
3469
|
+
>
|
|
3470
|
+
<tolle-range-calendar
|
|
3471
|
+
[ngModel]="value"
|
|
3472
|
+
(rangeSelect)="onCalendarSelect($event)"
|
|
3473
|
+
[disablePastDates]="disablePastDates"
|
|
3474
|
+
></tolle-range-calendar>
|
|
3475
|
+
</div>
|
|
3476
|
+
</div>
|
|
3477
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: RangeCalendarComponent, selector: "tolle-range-calendar", inputs: ["class", "disablePastDates"], outputs: ["rangeSelect"] }, { kind: "component", type: InputComponent$1, selector: "tolle-input", inputs: ["type", "placeholder", "disabled", "error", "size", "containerClass", "class"] }] });
|
|
3478
|
+
}
|
|
3479
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DateRangePickerComponent, decorators: [{
|
|
3480
|
+
type: Component,
|
|
3481
|
+
args: [{
|
|
3482
|
+
selector: 'tolle-date-range-picker',
|
|
3483
|
+
standalone: true,
|
|
3484
|
+
imports: [CommonModule, FormsModule, RangeCalendarComponent, InputComponent$1],
|
|
3485
|
+
providers: [
|
|
3486
|
+
{
|
|
3487
|
+
provide: NG_VALUE_ACCESSOR,
|
|
3488
|
+
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
3489
|
+
multi: true
|
|
3490
|
+
}
|
|
3491
|
+
],
|
|
3492
|
+
template: `
|
|
3493
|
+
<div class="relative w-full" #triggerContainer>
|
|
3494
|
+
<tolle-input [placeholder]="placeholder" [disabled]="disabled" [ngModel]="displayValue">
|
|
3495
|
+
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
3496
|
+
<i
|
|
3497
|
+
*ngIf="(value.start || value.end) && !disabled"
|
|
3498
|
+
(click)="clear($event)"
|
|
3499
|
+
class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
|
3500
|
+
></i>
|
|
3501
|
+
|
|
3502
|
+
<i
|
|
3503
|
+
(click)="togglePopover($event)"
|
|
3504
|
+
class="ri-calendar-line cursor-pointer text-muted-foreground hover:text-primary transition-colors"
|
|
3505
|
+
></i>
|
|
3506
|
+
</div>
|
|
3507
|
+
</tolle-input>
|
|
3508
|
+
<div
|
|
3509
|
+
#popover
|
|
3510
|
+
*ngIf="isOpen"
|
|
3511
|
+
class="absolute z-50 min-w-72"
|
|
3512
|
+
style="visibility: hidden; top: 0; left: 0;"
|
|
3513
|
+
>
|
|
3514
|
+
<tolle-range-calendar
|
|
3515
|
+
[ngModel]="value"
|
|
3516
|
+
(rangeSelect)="onCalendarSelect($event)"
|
|
3517
|
+
[disablePastDates]="disablePastDates"
|
|
3518
|
+
></tolle-range-calendar>
|
|
3519
|
+
</div>
|
|
3520
|
+
</div>
|
|
3521
|
+
`
|
|
3522
|
+
}]
|
|
3523
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { disabled: [{
|
|
3524
|
+
type: Input
|
|
3525
|
+
}], placeholder: [{
|
|
3526
|
+
type: Input
|
|
3527
|
+
}], class: [{
|
|
3528
|
+
type: Input
|
|
3529
|
+
}], disablePastDates: [{
|
|
3530
|
+
type: Input
|
|
3531
|
+
}], size: [{
|
|
3532
|
+
type: Input
|
|
3533
|
+
}], triggerContainer: [{
|
|
3534
|
+
type: ViewChild,
|
|
3535
|
+
args: ['triggerContainer']
|
|
3536
|
+
}], popover: [{
|
|
3537
|
+
type: ViewChild,
|
|
3538
|
+
args: ['popover']
|
|
3539
|
+
}], onClickOutside: [{
|
|
3540
|
+
type: HostListener,
|
|
3541
|
+
args: ['document:mousedown', ['$event']]
|
|
3542
|
+
}] } });
|
|
3543
|
+
|
|
3544
|
+
/*
|
|
3545
|
+
* Public API Surface of tolle
|
|
3546
|
+
*/
|
|
3547
|
+
|
|
3548
|
+
/**
|
|
3549
|
+
* Generated bundle index. Do not edit.
|
|
3550
|
+
*/
|
|
3551
|
+
|
|
3552
|
+
export { AccordionComponent, AccordionItemComponent, BadgeComponent, ButtonComponent, ButtonGroupComponent, CalendarComponent, CardComponent, CardContentComponent, CardFooterComponent, CardHeaderComponent, CardTitleComponent, CheckboxComponent, DataTableComponent, DatePickerComponent, DateRangePickerComponent, InputComponent, MaskedInputComponent, Modal, ModalComponent, ModalRef, ModalService, ModalStackService, MultiSelectComponent, PaginationComponent, RangeCalendarComponent, SelectComponent, SelectGroupComponent, SelectItemComponent, SelectSeparatorComponent, SkeletonComponent, SwitchComponent, TOLLE_CONFIG, ToastService, TolleCellDirective, TooltipDirective, cn, provideTolleConfig };
|
|
3553
|
+
//# sourceMappingURL=tolle_-tolle-ui.mjs.map
|