@tolle_/tolle-ui 0.0.19-beta → 0.0.22-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.
@@ -1,7 +1,7 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import * as i0 from '@angular/core';
4
- import { Component, Input, forwardRef, Injectable, Optional, HostListener, ViewChild, ContentChildren, EventEmitter, Output, Directive, inject, PLATFORM_ID, Inject, InjectionToken, APP_INITIALIZER, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector, HostBinding } from '@angular/core';
4
+ import { Component, Input, forwardRef, ViewChild, Injectable, Optional, HostListener, ContentChildren, EventEmitter, Output, Directive, inject, PLATFORM_ID, Inject, InjectionToken, APP_INITIALIZER, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector, HostBinding } from '@angular/core';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, isPlatformBrowser, DOCUMENT, NgIf, NgTemplateOutlet } from '@angular/common';
7
7
  import { cva } from 'class-variance-authority';
@@ -18,15 +18,15 @@ function cn(...inputs) {
18
18
  return twMerge(clsx(inputs));
19
19
  }
20
20
 
21
- const buttonVariants = cva("inline-flex items-center justify-center rounded-md text-sm font-medium transition-all 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 active:scale-[0.98]", {
21
+ const buttonVariants = cva("tolle-button-base inline-flex items-center justify-center rounded-md text-sm font-medium transition-all 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 active:scale-[0.98]", {
22
22
  variants: {
23
23
  variant: {
24
24
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
25
25
  destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
26
- outline: "border border-input hover:bg-accent hover:text-accent-foreground",
26
+ outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
27
27
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
28
28
  ghost: "hover:bg-accent hover:text-accent-foreground",
29
- link: "underline-offset-4 hover:underline text-primary",
29
+ link: "text-primary underline-offset-4 hover:underline",
30
30
  },
31
31
  size: {
32
32
  default: "h-10 px-4 py-2",
@@ -38,7 +38,7 @@ const buttonVariants = cva("inline-flex items-center justify-center rounded-md t
38
38
  icon: "h-10 w-10",
39
39
  "icon-lg": "h-11 w-11",
40
40
  },
41
- busy: { true: "relative !cursor-wait !pointer-events-none" }
41
+ busy: { true: "tolle-button--busy relative !cursor-wait !pointer-events-none" }
42
42
  },
43
43
  defaultVariants: {
44
44
  variant: "default",
@@ -51,16 +51,15 @@ class ButtonComponent {
51
51
  size = 'default';
52
52
  disabled = false;
53
53
  busy = false;
54
- readonly = false;
55
54
  get computedClass() {
56
55
  return cn(buttonVariants({
57
56
  variant: this.variant,
58
57
  size: this.size,
59
58
  busy: this.busy
60
- }), 'size-' + this.size, this.class);
59
+ }), this.class);
61
60
  }
62
61
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
63
- 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", disabled: "disabled", busy: "busy", readonly: "readonly" }, ngImport: i0, template: `
62
+ 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", disabled: "disabled", busy: "busy" }, host: { classAttribute: "tolle-button-wrapper inline-block align-middle" }, ngImport: i0, template: `
64
63
  <button
65
64
  [class]="computedClass"
66
65
  [disabled]="disabled || busy"
@@ -77,11 +76,15 @@ class ButtonComponent {
77
76
  <ng-content></ng-content>
78
77
  </span>
79
78
  </button>
80
- `, isInline: true, styles: [":host{display:inline-block;vertical-align:middle}:host ::ng-deep i{display:inline-flex;align-items:center;justify-content:center;line-height:1}:host-context(.size-xs) ::ng-deep i,:host-context(.size-icon-xs) ::ng-deep i,:host-context(.size-sm) ::ng-deep i,:host-context(.size-icon-sm) ::ng-deep i{font-size:1rem}:host-context(.size-default) ::ng-deep i,:host-context(.size-icon) ::ng-deep i,:host-context(.size-lg) ::ng-deep i,:host-context(.size-icon-lg) ::ng-deep i{font-size:1.2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
79
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
81
80
  }
82
81
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, decorators: [{
83
82
  type: Component,
84
- args: [{ selector: 'tolle-button', standalone: true, imports: [CommonModule], template: `
83
+ args: [{
84
+ selector: 'tolle-button',
85
+ standalone: true,
86
+ imports: [CommonModule],
87
+ template: `
85
88
  <button
86
89
  [class]="computedClass"
87
90
  [disabled]="disabled || busy"
@@ -98,7 +101,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
98
101
  <ng-content></ng-content>
99
102
  </span>
100
103
  </button>
101
- `, styles: [":host{display:inline-block;vertical-align:middle}:host ::ng-deep i{display:inline-flex;align-items:center;justify-content:center;line-height:1}:host-context(.size-xs) ::ng-deep i,:host-context(.size-icon-xs) ::ng-deep i,:host-context(.size-sm) ::ng-deep i,:host-context(.size-icon-sm) ::ng-deep i{font-size:1rem}:host-context(.size-default) ::ng-deep i,:host-context(.size-icon) ::ng-deep i,:host-context(.size-lg) ::ng-deep i,:host-context(.size-icon-lg) ::ng-deep i{font-size:1.2rem}\n"] }]
104
+ `,
105
+ host: {
106
+ 'class': 'tolle-button-wrapper inline-block align-middle'
107
+ }
108
+ }]
102
109
  }], propDecorators: { class: [{
103
110
  type: Input
104
111
  }], variant: [{
@@ -109,12 +116,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
109
116
  type: Input
110
117
  }], busy: [{
111
118
  type: Input
112
- }], readonly: [{
113
- type: Input
114
119
  }] } });
115
120
 
116
121
  class InputComponent {
117
122
  cdr;
123
+ inputElement;
118
124
  id = `input-${Math.random().toString(36).substr(2, 9)}`;
119
125
  label = '';
120
126
  hint = '';
@@ -128,12 +134,20 @@ class InputComponent {
128
134
  disabled = false;
129
135
  readonly = false;
130
136
  error = false;
137
+ // Focus behavior
138
+ hideHintOnFocus = true;
131
139
  value = '';
132
140
  onChange = () => { };
133
141
  onTouched = () => { };
142
+ isFocused = false;
134
143
  constructor(cdr) {
135
144
  this.cdr = cdr;
136
145
  }
146
+ ngAfterViewInit() {
147
+ if (this.inputElement?.nativeElement.hasAttribute('autofocus')) {
148
+ setTimeout(() => this.inputElement.nativeElement.focus());
149
+ }
150
+ }
137
151
  writeValue(value) {
138
152
  this.value = value;
139
153
  this.cdr.markForCheck();
@@ -151,66 +165,132 @@ class InputComponent {
151
165
  this.value = val;
152
166
  this.onChange(val);
153
167
  }
168
+ onFocus() {
169
+ this.isFocused = true;
170
+ }
171
+ onBlur() {
172
+ this.isFocused = false;
173
+ this.onTouched();
174
+ }
175
+ focusInput() {
176
+ if (!this.disabled && this.inputElement) {
177
+ this.inputElement.nativeElement.focus();
178
+ }
179
+ }
154
180
  cn = cn;
181
+ get computedLabelClass() {
182
+ return cn("text-sm font-medium text-foreground leading-none transition-opacity duration-200", this.disabled && "opacity-50");
183
+ }
155
184
  get computedContainerClass() {
156
- return cn("group relative flex items-center w-full rounded-md border transition-all shadow-sm", "bg-background ring-offset-background",
185
+ return cn(
186
+ // Base styles
187
+ "group relative flex items-center w-full rounded-md border transition-all duration-200", "bg-background",
188
+ // Border and shadow
189
+ "border-input shadow-sm",
157
190
  // Sizing
158
- this.size === 'xs' && "h-8 px-2 gap-1.5", this.size === 'sm' && "h-9 px-3 gap-2", this.size === 'default' && "h-10 px-3 gap-2", this.size === 'lg' && "h-11 px-4 gap-3",
159
- // Interaction States (Focus ring disabled for Readonly/Disabled)
160
- !(this.readonly || this.disabled) && "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1",
161
- // Colors & Borders
162
- this.error ? "border-destructive focus-within:ring-destructive" : "border-input",
163
- // Disabled vs Readonly styling
164
- this.disabled && "cursor-not-allowed opacity-50", this.readonly && "cursor-default border-dashed focus-within:ring-0", this.containerClass);
191
+ this.size === 'xs' && "h-8 px-2 gap-1.5 text-xs", this.size === 'sm' && "h-9 px-3 gap-2 text-sm", this.size === 'default' && "h-10 px-3 gap-2 text-sm", this.size === 'lg' && "h-11 px-4 gap-3 text-base",
192
+ // Focus state - SIMPLE AND ELEGANT LIKE ZARDUI
193
+ // The magic happens in CSS: border darkens automatically on focus
194
+ !(this.readonly || this.disabled) && [
195
+ "focus-within:border-primary/80",
196
+ "focus-within:ring-4",
197
+ "focus-within:ring-ring/30",
198
+ "focus-within:ring-offset-0",
199
+ "focus-within:shadow-none",
200
+ ],
201
+ // Error state
202
+ this.error && [
203
+ "border-destructive",
204
+ !(this.readonly || this.disabled) && [
205
+ "focus-within:border-destructive/80",
206
+ "focus-within:ring-destructive/30"
207
+ ]
208
+ ],
209
+ // Disabled state
210
+ this.disabled && [
211
+ "cursor-not-allowed opacity-50",
212
+ "border-opacity-50"
213
+ ],
214
+ // Readonly state
215
+ this.readonly && [
216
+ "cursor-default",
217
+ "border-dashed",
218
+ !this.disabled && "focus-within:ring-0 focus-within:border-opacity-100"
219
+ ], this.containerClass);
165
220
  }
166
221
  get computedInputClass() {
167
- return cn("flex-1 bg-transparent border-none p-0 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-0", this.size === 'xs' && "text-xs", this.size === 'lg' && "text-base", this.disabled && "cursor-not-allowed", this.readonly && "cursor-default", this.class);
222
+ return cn(
223
+ // Base styles
224
+ "flex-1 bg-transparent border-none p-0", "placeholder:text-muted-foreground",
225
+ // Remove all default focus styles
226
+ "focus:outline-none focus:ring-0 focus:shadow-none",
227
+ // Text sizing
228
+ this.size === 'xs' && "text-xs", this.size === 'sm' && "text-sm", this.size === 'default' && "text-sm", this.size === 'lg' && "text-base",
229
+ // Cursor states
230
+ this.disabled && "cursor-not-allowed", this.readonly && "cursor-default",
231
+ // Text color
232
+ "text-foreground",
233
+ // Selection color
234
+ "selection:bg-primary/20 selection:text-foreground", this.class);
168
235
  }
169
236
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
170
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: InputComponent, isStandalone: true, selector: "tolle-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", type: "type", placeholder: "placeholder", size: "size", containerClass: "containerClass", class: "class", disabled: "disabled", readonly: "readonly", error: "error" }, providers: [
237
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: InputComponent, isStandalone: true, selector: "tolle-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", type: "type", placeholder: "placeholder", size: "size", containerClass: "containerClass", class: "class", disabled: "disabled", readonly: "readonly", error: "error", hideHintOnFocus: "hideHintOnFocus" }, providers: [
171
238
  {
172
239
  provide: NG_VALUE_ACCESSOR,
173
240
  useExisting: forwardRef(() => InputComponent),
174
241
  multi: true
175
242
  }
176
- ], ngImport: i0, template: `
243
+ ], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["inputElement"], descendants: true }], ngImport: i0, template: `
177
244
  <div class="flex flex-col gap-1.5 w-full">
178
245
  <label
179
246
  *ngIf="label"
180
247
  [for]="id"
181
- [class.opacity-50]="disabled"
182
- class="text-sm font-medium text-foreground leading-none transition-opacity"
183
- >
248
+ [class]="computedLabelClass">
184
249
  {{ label }}
185
250
  </label>
186
251
 
187
- <div [class]="computedContainerClass">
188
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
252
+ <div
253
+ [class]="computedContainerClass"
254
+ (click)="focusInput()"
255
+ >
256
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
189
257
  <ng-content select="[prefix]"></ng-content>
190
258
  </div>
191
259
 
192
260
  <input
261
+ #inputElement
193
262
  [id]="id"
194
263
  [type]="type"
195
264
  [placeholder]="placeholder"
196
265
  [disabled]="disabled"
197
266
  [readOnly]="readonly"
198
267
  [(ngModel)]="value"
199
- (blur)="onTouched()"
268
+ (blur)="onBlur()"
269
+ (focus)="onFocus()"
200
270
  (input)="onInputChange($event)"
201
271
  [class]="computedInputClass"
272
+ [attr.aria-invalid]="error"
273
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
202
274
  />
203
275
 
204
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
276
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
205
277
  <ng-content select="[suffix]"></ng-content>
206
278
  </div>
207
279
  </div>
208
280
 
209
281
  <ng-container *ngIf="!disabled">
210
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
282
+ <p
283
+ *ngIf="hint && !error"
284
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
285
+ [class.opacity-0]="isFocused && hideHintOnFocus"
286
+ >
211
287
  {{ hint }}
212
288
  </p>
213
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
289
+ <p
290
+ *ngIf="error && errorMessage"
291
+ [id]="id + '-error'"
292
+ class="text-xs text-destructive px-1"
293
+ >
214
294
  {{ errorMessage }}
215
295
  </p>
216
296
  </ng-container>
@@ -235,46 +315,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
235
315
  <label
236
316
  *ngIf="label"
237
317
  [for]="id"
238
- [class.opacity-50]="disabled"
239
- class="text-sm font-medium text-foreground leading-none transition-opacity"
240
- >
318
+ [class]="computedLabelClass">
241
319
  {{ label }}
242
320
  </label>
243
321
 
244
- <div [class]="computedContainerClass">
245
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
322
+ <div
323
+ [class]="computedContainerClass"
324
+ (click)="focusInput()"
325
+ >
326
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
246
327
  <ng-content select="[prefix]"></ng-content>
247
328
  </div>
248
329
 
249
330
  <input
331
+ #inputElement
250
332
  [id]="id"
251
333
  [type]="type"
252
334
  [placeholder]="placeholder"
253
335
  [disabled]="disabled"
254
336
  [readOnly]="readonly"
255
337
  [(ngModel)]="value"
256
- (blur)="onTouched()"
338
+ (blur)="onBlur()"
339
+ (focus)="onFocus()"
257
340
  (input)="onInputChange($event)"
258
341
  [class]="computedInputClass"
342
+ [attr.aria-invalid]="error"
343
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
259
344
  />
260
345
 
261
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
346
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
262
347
  <ng-content select="[suffix]"></ng-content>
263
348
  </div>
264
349
  </div>
265
350
 
266
351
  <ng-container *ngIf="!disabled">
267
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
352
+ <p
353
+ *ngIf="hint && !error"
354
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
355
+ [class.opacity-0]="isFocused && hideHintOnFocus"
356
+ >
268
357
  {{ hint }}
269
358
  </p>
270
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
359
+ <p
360
+ *ngIf="error && errorMessage"
361
+ [id]="id + '-error'"
362
+ class="text-xs text-destructive px-1"
363
+ >
271
364
  {{ errorMessage }}
272
365
  </p>
273
366
  </ng-container>
274
367
  </div>
275
368
  `,
276
369
  }]
277
- }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { id: [{
370
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { inputElement: [{
371
+ type: ViewChild,
372
+ args: ['inputElement']
373
+ }], id: [{
278
374
  type: Input
279
375
  }], label: [{
280
376
  type: Input
@@ -298,6 +394,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
298
394
  type: Input
299
395
  }], error: [{
300
396
  type: Input
397
+ }], hideHintOnFocus: [{
398
+ type: Input
301
399
  }] } });
302
400
 
303
401
  class CardComponent {
@@ -305,7 +403,7 @@ class CardComponent {
305
403
  cn = cn;
306
404
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
307
405
  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: `
308
- <div [class]="cn('rounded-xl border border-border bg-card text-card-foreground shadow', class)">
406
+ <div [class]="cn('rounded-md border border-border bg-card text-card-foreground shadow', class)">
309
407
  <ng-content></ng-content>
310
408
  </div>
311
409
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
@@ -317,7 +415,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
317
415
  standalone: true,
318
416
  imports: [CommonModule],
319
417
  template: `
320
- <div [class]="cn('rounded-xl border border-border bg-card text-card-foreground shadow', class)">
418
+ <div [class]="cn('rounded-md border border-border bg-card text-card-foreground shadow', class)">
321
419
  <ng-content></ng-content>
322
420
  </div>
323
421
  `,
@@ -496,6 +594,7 @@ class SelectComponent {
496
594
  readonly = false;
497
595
  trigger;
498
596
  popover;
597
+ container;
499
598
  items;
500
599
  sub = new Subscription();
501
600
  searchQuery = '';
@@ -519,15 +618,38 @@ class SelectComponent {
519
618
  this.close();
520
619
  }));
521
620
  }
522
- // UPDATED: Centralized sizing logic for the trigger
621
+ // SIMPLIFIED: Zardui-inspired trigger styling
523
622
  get computedTriggerClass() {
524
- return cn('flex w-full items-center justify-between rounded-md border transition-all shadow-sm', 'bg-background ring-offset-background placeholder:text-muted-foreground',
623
+ return cn(
624
+ // Base styles
625
+ 'flex w-full items-center justify-between rounded-md border transition-all duration-200', 'bg-background text-foreground',
626
+ // Border and shadow
627
+ 'border-input shadow-sm',
525
628
  // Sizing
526
629
  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',
527
- // Interaction States (Focus ring disabled for Readonly/Disabled)
528
- !(this.readonly || this.disabled) && 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
529
- // Colors & Borders (Matching your Hex-based theme logic)
530
- this.disabled && 'cursor-not-allowed opacity-50 bg-muted/30 border-input', this.readonly && 'cursor-default bg-muted/10 border-dashed border-input focus:ring-0', !this.disabled && !this.readonly && 'border-input hover:border-accent cursor-pointer', this.class);
630
+ // Focus state - SIMPLE LIKE ZARDUI
631
+ !(this.readonly || this.disabled) && [
632
+ 'focus:outline-none',
633
+ 'focus:ring-4',
634
+ 'focus:ring-ring/30',
635
+ 'focus:ring-offset-0',
636
+ 'focus:shadow-none',
637
+ // Border darkens on focus automatically
638
+ 'focus:border-primary/80'
639
+ ],
640
+ // Hover state
641
+ !(this.readonly || this.disabled) && 'hover:border-accent',
642
+ // Disabled state
643
+ this.disabled && [
644
+ 'cursor-not-allowed opacity-50',
645
+ 'border-opacity-50'
646
+ ],
647
+ // Readonly state
648
+ this.readonly && [
649
+ 'cursor-default',
650
+ 'border-dashed',
651
+ !this.disabled && 'focus:ring-0 focus:border-opacity-100'
652
+ ], this.class);
531
653
  }
532
654
  // UPDATED: Dynamic icon sizing relative to the trigger size
533
655
  get iconClass() {
@@ -553,6 +675,8 @@ class SelectComponent {
553
675
  }
554
676
  open() {
555
677
  this.isOpen = true;
678
+ // Trigger focus on the button for focus styling
679
+ this.trigger.nativeElement.focus();
556
680
  // Tick to ensure DOM is rendered before positioning
557
681
  setTimeout(() => this.updatePosition());
558
682
  }
@@ -621,7 +745,7 @@ class SelectComponent {
621
745
  useExisting: forwardRef(() => SelectComponent),
622
746
  multi: true
623
747
  }
624
- ], 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: `
748
+ ], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }, { propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
625
749
  <div [class]="cn('w-full', 'size-' + size)" #container>
626
750
  <button
627
751
  type="button"
@@ -661,7 +785,7 @@ class SelectComponent {
661
785
  </div>
662
786
  </div>
663
787
  </div>
664
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { 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: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
788
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { 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: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
665
789
  }
666
790
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectComponent, decorators: [{
667
791
  type: Component,
@@ -737,6 +861,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
737
861
  }], popover: [{
738
862
  type: ViewChild,
739
863
  args: ['popover']
864
+ }], container: [{
865
+ type: ViewChild,
866
+ args: ['container']
740
867
  }], items: [{
741
868
  type: ContentChildren,
742
869
  args: [SelectItemComponent, { descendants: true }]
@@ -929,7 +1056,7 @@ class BadgeComponent {
929
1056
  get computedClass() {
930
1057
  return cn(
931
1058
  // Base styles - Pills are always rounded-full
932
- 'inline-flex items-center justify-center rounded-full border px-2 py-0.5 font-medium transition-colors gap-1',
1059
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 font-medium transition-colors gap-1',
933
1060
  // Variants (Google Dark Mode theme)
934
1061
  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',
935
1062
  // Sizing
@@ -947,7 +1074,7 @@ class BadgeComponent {
947
1074
  <button
948
1075
  *ngIf="removable"
949
1076
  (click)="onRemove.emit($event)"
950
- class="ml-1 -mr-1 rounded-full p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
1077
+ class="ml-1 -mr-1 rounded-md p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
951
1078
  >
952
1079
  <i class="ri-close-line text-[1.1em]"></i>
953
1080
  </button>
@@ -971,7 +1098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
971
1098
  <button
972
1099
  *ngIf="removable"
973
1100
  (click)="onRemove.emit($event)"
974
- class="ml-1 -mr-1 rounded-full p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
1101
+ class="ml-1 -mr-1 rounded-md p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
975
1102
  >
976
1103
  <i class="ri-close-line text-[1.1em]"></i>
977
1104
  </button>
@@ -1343,116 +1470,255 @@ class ThemeService {
1343
1470
  initializeTheme() {
1344
1471
  if (!isPlatformBrowser(this.platformId))
1345
1472
  return;
1346
- // 1. Determine Initial Mode (Dark/Light)
1473
+ // LOGIC: User Saved Preferences > Config Defaults > System Preference
1347
1474
  const savedTheme = localStorage.getItem('tolle-theme');
1475
+ const savedPrimary = localStorage.getItem('tolle-primary-color');
1476
+ const savedRadius = localStorage.getItem('tolle-radius');
1348
1477
  const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1349
- // Logic: Saved Preference > Config Default > System Preference
1350
- const shouldBeDark = savedTheme
1351
- ? savedTheme === 'dark'
1352
- : (this.config?.darkByDefault ?? systemPrefersDark);
1478
+ console.log('Theme initialization:', {
1479
+ savedTheme,
1480
+ savedPrimary,
1481
+ savedRadius,
1482
+ config: this.config,
1483
+ systemPrefersDark
1484
+ });
1485
+ // 1. Determine Dark/Light Mode
1486
+ // Priority: Saved Theme > Config Default > System Preference
1487
+ let shouldBeDark = systemPrefersDark; // Start with system
1488
+ if (savedTheme) {
1489
+ // User has saved preference
1490
+ shouldBeDark = savedTheme === 'dark';
1491
+ }
1492
+ else if (this.config?.darkByDefault !== undefined) {
1493
+ // Use config default if no user preference
1494
+ shouldBeDark = this.config.darkByDefault;
1495
+ }
1496
+ // Apply theme mode
1353
1497
  if (shouldBeDark) {
1354
- this.enableDarkMode();
1498
+ this.enableDarkMode(false); // Don't save, we'll save after checking all preferences
1355
1499
  }
1356
1500
  else {
1357
- this.disableDarkMode();
1501
+ this.disableDarkMode(false);
1358
1502
  }
1359
- const savedPrimary = localStorage.getItem('tolle-primary-color');
1503
+ // 2. Apply Primary Color
1504
+ // Priority: Saved Color > Config Color
1360
1505
  if (savedPrimary) {
1361
- this.setPrimaryColor(savedPrimary, false);
1506
+ // User has saved color preference
1507
+ this.setPrimaryColor(savedPrimary, false); // Don't save again
1362
1508
  }
1363
1509
  else if (this.config?.primaryColor) {
1364
- this.setPrimaryColor(this.config.primaryColor, false);
1510
+ // Use config default if no user preference
1511
+ this.setPrimaryColor(this.config.primaryColor, true); // Save this as user preference
1512
+ }
1513
+ // 3. Apply Radius
1514
+ // Priority: Saved Radius > Config Radius
1515
+ if (savedRadius) {
1516
+ // User has saved radius preference
1517
+ this.setRadius(savedRadius, false);
1365
1518
  }
1366
- if (this.config?.radius) {
1367
- this.renderer.setStyle(this.document.documentElement, '--radius', this.config.radius);
1519
+ else if (this.config?.radius) {
1520
+ // Use config default if no user preference
1521
+ this.setRadius(this.config.radius, true);
1368
1522
  }
1369
- // 2. Apply Brand Config - This will generate full palette
1370
- if (this.config) {
1371
- this.applyBrandConfig(this.config);
1523
+ // Save theme mode preference if it came from config or system
1524
+ if (!savedTheme) {
1525
+ localStorage.setItem('tolle-theme', shouldBeDark ? 'dark' : 'light');
1372
1526
  }
1373
1527
  }
1374
1528
  /**
1375
- * Applies the brand identity variables with full shade palette
1529
+ * Sets the border radius for all components
1376
1530
  */
1377
- applyBrandConfig(config) {
1531
+ setRadius(radius, persist = true) {
1378
1532
  if (!isPlatformBrowser(this.platformId))
1379
1533
  return;
1380
1534
  const root = this.document.documentElement;
1381
- // Set primary color if provided
1382
- if (config.primaryColor) {
1383
- this.renderer.setStyle(root, '--primary', config.primaryColor);
1384
- this.generatePrimaryShades(config.primaryColor);
1535
+ // Set the CSS variable
1536
+ this.renderer.setStyle(root, '--radius', radius);
1537
+ // Also update the dynamic styles to include radius calculations
1538
+ this.updateRadiusInDynamicStyles(radius);
1539
+ // Persist if needed
1540
+ if (persist) {
1541
+ localStorage.setItem('tolle-radius', radius);
1385
1542
  }
1386
- // Set border radius if provided
1387
- if (config.radius) {
1388
- this.renderer.setStyle(root, '--radius', config.radius);
1543
+ }
1544
+ /**
1545
+ * Updates the radius calculations in dynamic styles
1546
+ */
1547
+ updateRadiusInDynamicStyles(radius) {
1548
+ const existingStyle = this.document.getElementById(this.styleId);
1549
+ if (existingStyle) {
1550
+ let css = existingStyle.textContent || '';
1551
+ // Update or add radius calculations
1552
+ if (css.includes('--radius:')) {
1553
+ // Replace existing radius declarations
1554
+ css = css.replace(/--radius:[^;]+;/g, `--radius: ${radius};`);
1555
+ }
1556
+ else {
1557
+ // Add radius to the beginning of :root
1558
+ css = css.replace(/:root\s*{/, `:root {\n --radius: ${radius};`);
1559
+ }
1560
+ // Update the calculated radius values in the CSS
1561
+ const radiusCalcRegex = /calc\(var\(--radius[^)]+\)/g;
1562
+ css = css.replace(radiusCalcRegex, (match) => {
1563
+ if (match.includes('- 2px')) {
1564
+ return `calc(${radius} - 2px)`;
1565
+ }
1566
+ else if (match.includes('- 4px')) {
1567
+ return `calc(${radius} - 4px)`;
1568
+ }
1569
+ return match;
1570
+ });
1571
+ existingStyle.textContent = css;
1389
1572
  }
1390
1573
  }
1391
1574
  /**
1392
1575
  * Generates full primary color palette (50-900) based on base color
1393
- * Uses color-mix() for consistency with your existing approach
1394
1576
  */
1395
1577
  generatePrimaryShades(baseColor) {
1578
+ // Convert hex to RGB
1579
+ const rgb = this.hexToRgb(baseColor);
1580
+ const rgbString = rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '37 99 235';
1581
+ // Create lighter ring colors in RGB
1582
+ const ringLight = this.lightenColor(baseColor, 40);
1583
+ const ringLightRgb = this.hexToRgb(ringLight);
1584
+ const ringLightRgbString = ringLightRgb ? `${ringLightRgb.r} ${ringLightRgb.g} ${ringLightRgb.b}` : '96 165 250';
1585
+ const ringDark = this.lightenColor(baseColor, 20);
1586
+ const ringDarkRgb = this.hexToRgb(ringDark);
1587
+ const ringDarkRgbString = ringDarkRgb ? `${ringDarkRgb.r} ${ringDarkRgb.g} ${ringDarkRgb.b}` : '147 197 253';
1588
+ // Get current radius or use default
1589
+ const root = this.document.documentElement;
1590
+ const currentRadius = getComputedStyle(root).getPropertyValue('--radius').trim() || '0.5rem';
1396
1591
  const css = `
1592
+ /* Override primary colors - this needs to come AFTER your main CSS */
1397
1593
  :root {
1398
- /* Primary Shades */
1399
- --primary-50: color-mix(in srgb, ${baseColor}, white 90%);
1400
- --primary-100: color-mix(in srgb, ${baseColor}, white 80%);
1401
- --primary-200: color-mix(in srgb, ${baseColor}, white 60%);
1402
- --primary-300: color-mix(in srgb, ${baseColor}, white 40%);
1403
- --primary-400: color-mix(in srgb, ${baseColor}, white 20%);
1404
- --primary-500: ${baseColor};
1405
- --primary-600: color-mix(in srgb, ${baseColor}, black 20%);
1406
- --primary-700: color-mix(in srgb, ${baseColor}, black 40%);
1407
- --primary-800: color-mix(in srgb, ${baseColor}, black 60%);
1408
- --primary-900: color-mix(in srgb, ${baseColor}, black 80%);
1409
-
1410
- /* Your existing derived colors - updated to use new shades */
1411
- --primary-foreground: white;
1412
- --secondary: color-mix(in srgb, var(--primary-200), transparent 40%);
1413
- --secondary-foreground: var(--primary-900);
1414
- --muted: color-mix(in srgb, var(--primary-50), #f3f4f6 50%);
1415
- --muted-foreground: color-mix(in srgb, var(--primary-400), #4b5563 60%);
1416
- --accent: color-mix(in srgb, var(--primary-100), transparent 70%);
1417
- --accent-foreground: var(--primary-700);
1418
- --ring: color-mix(in srgb, var(--primary-300), transparent 50%);
1594
+ /* Primary in RGB format for Tailwind opacity support */
1595
+ --primary: ${rgbString};
1596
+ --primary-foreground: ${this.getContrastColorRgb(baseColor)};
1597
+
1598
+ /* Radius */
1599
+ --radius: ${currentRadius};
1600
+
1601
+ /* Primary shades for light mode */
1602
+ --primary-50: ${this.hexToRgbString(this.lightenColor(baseColor, 90))};
1603
+ --primary-100: ${this.hexToRgbString(this.lightenColor(baseColor, 80))};
1604
+ --primary-200: ${this.hexToRgbString(this.lightenColor(baseColor, 60))};
1605
+ --primary-300: ${this.hexToRgbString(this.lightenColor(baseColor, 40))};
1606
+ --primary-400: ${this.hexToRgbString(this.lightenColor(baseColor, 20))};
1607
+ --primary-500: ${rgbString};
1608
+ --primary-600: ${this.hexToRgbString(this.darkenColor(baseColor, 20))};
1609
+ --primary-700: ${this.hexToRgbString(this.darkenColor(baseColor, 40))};
1610
+ --primary-800: ${this.hexToRgbString(this.darkenColor(baseColor, 60))};
1611
+ --primary-900: ${this.hexToRgbString(this.darkenColor(baseColor, 80))};
1612
+
1613
+ /* Update ring color to be lighter */
1614
+ --ring: ${ringLightRgbString};
1419
1615
  }
1420
1616
 
1421
1617
  .dark {
1422
- /* Dark mode variants */
1423
- --primary-50: color-mix(in srgb, ${baseColor}, black 85%);
1424
- --primary-100: color-mix(in srgb, ${baseColor}, black 75%);
1425
- --primary-200: color-mix(in srgb, ${baseColor}, black 65%);
1426
- --primary-300: color-mix(in srgb, ${baseColor}, black 55%);
1427
- --primary-400: color-mix(in srgb, ${baseColor}, black 45%);
1428
- --primary-500: ${baseColor};
1429
- --primary-600: color-mix(in srgb, ${baseColor}, white 20%);
1430
- --primary-700: color-mix(in srgb, ${baseColor}, white 35%);
1431
- --primary-800: color-mix(in srgb, ${baseColor}, white 50%);
1432
- --primary-900: color-mix(in srgb, ${baseColor}, white 65%);
1433
-
1434
- /* Dark mode derived colors */
1435
- --primary-foreground: color-mix(in srgb, ${baseColor}, white 90%);
1436
- --secondary: color-mix(in srgb, var(--primary-900), transparent 70%);
1437
- --secondary-foreground: var(--primary-100);
1438
- --muted: color-mix(in srgb, var(--primary-950), #1f2937 50%);
1439
- --muted-foreground: color-mix(in srgb, var(--primary-300), #9ca3af 40%);
1440
- --accent: color-mix(in srgb, var(--primary-800), transparent 80%);
1441
- --accent-foreground: var(--primary-200);
1442
- --ring: color-mix(in srgb, var(--primary-600), transparent 60%);
1618
+ /* For dark mode, we keep the primary color but adjust shades */
1619
+ --primary: ${rgbString};
1620
+ --primary-foreground: ${this.getContrastColorRgb(baseColor)};
1621
+
1622
+ /* Dark mode shades */
1623
+ --primary-50: ${this.hexToRgbString(this.darkenColor(baseColor, 85))};
1624
+ --primary-100: ${this.hexToRgbString(this.darkenColor(baseColor, 75))};
1625
+ --primary-200: ${this.hexToRgbString(this.darkenColor(baseColor, 65))};
1626
+ --primary-300: ${this.hexToRgbString(this.darkenColor(baseColor, 55))};
1627
+ --primary-400: ${this.hexToRgbString(this.darkenColor(baseColor, 45))};
1628
+ --primary-500: ${rgbString};
1629
+ --primary-600: ${this.hexToRgbString(this.lightenColor(baseColor, 20))};
1630
+ --primary-700: ${this.hexToRgbString(this.lightenColor(baseColor, 35))};
1631
+ --primary-800: ${this.hexToRgbString(this.lightenColor(baseColor, 50))};
1632
+ --primary-900: ${this.hexToRgbString(this.lightenColor(baseColor, 65))};
1633
+
1634
+ /* Update ring color for dark mode - lighter variant */
1635
+ --ring: ${ringDarkRgbString};
1443
1636
  }
1444
1637
  `;
1445
1638
  this.injectDynamicStyles(css);
1446
1639
  }
1640
+ /**
1641
+ * Convert hex color to RGB object
1642
+ */
1643
+ hexToRgb(hex) {
1644
+ // Remove # if present
1645
+ const cleanedHex = hex.replace('#', '');
1646
+ let r = 0, g = 0, b = 0;
1647
+ if (cleanedHex.length === 3) {
1648
+ r = parseInt(cleanedHex[0] + cleanedHex[0], 16);
1649
+ g = parseInt(cleanedHex[1] + cleanedHex[1], 16);
1650
+ b = parseInt(cleanedHex[2] + cleanedHex[2], 16);
1651
+ return { r, g, b };
1652
+ }
1653
+ if (cleanedHex.length === 6) {
1654
+ r = parseInt(cleanedHex.substring(0, 2), 16);
1655
+ g = parseInt(cleanedHex.substring(2, 4), 16);
1656
+ b = parseInt(cleanedHex.substring(4, 6), 16);
1657
+ return { r, g, b };
1658
+ }
1659
+ return null;
1660
+ }
1661
+ /**
1662
+ * Convert hex to RGB string format for CSS (space-separated)
1663
+ */
1664
+ hexToRgbString(hexColor) {
1665
+ const rgb = this.hexToRgb(hexColor);
1666
+ return rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '37 99 235';
1667
+ }
1668
+ /**
1669
+ * Get contrast color in RGB format
1670
+ */
1671
+ getContrastColorRgb(hexColor) {
1672
+ const contrast = this.getContrastColor(hexColor);
1673
+ const rgb = this.hexToRgb(contrast);
1674
+ return rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '255 255 255';
1675
+ }
1676
+ /**
1677
+ * Lighten a hex color by a percentage
1678
+ */
1679
+ lightenColor(color, percent) {
1680
+ const rgb = this.hexToRgb(color);
1681
+ if (!rgb)
1682
+ return color;
1683
+ // Lighten by percentage
1684
+ const r = Math.min(255, Math.floor(rgb.r + (255 - rgb.r) * (percent / 100)));
1685
+ const g = Math.min(255, Math.floor(rgb.g + (255 - rgb.g) * (percent / 100)));
1686
+ const b = Math.min(255, Math.floor(rgb.b + (255 - rgb.b) * (percent / 100)));
1687
+ // Convert back to hex
1688
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
1689
+ }
1690
+ /**
1691
+ * Darken a hex color by a percentage
1692
+ */
1693
+ darkenColor(color, percent) {
1694
+ const rgb = this.hexToRgb(color);
1695
+ if (!rgb)
1696
+ return color;
1697
+ const factor = 1 - (percent / 100);
1698
+ const r = Math.max(0, Math.floor(rgb.r * factor));
1699
+ const g = Math.max(0, Math.floor(rgb.g * factor));
1700
+ const b = Math.max(0, Math.floor(rgb.b * factor));
1701
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
1702
+ }
1703
+ /**
1704
+ * Calculate contrast color (black or white) based on background color
1705
+ */
1706
+ getContrastColor(hexColor) {
1707
+ const rgb = this.hexToRgb(hexColor);
1708
+ if (!rgb)
1709
+ return '#ffffff';
1710
+ // Calculate relative luminance
1711
+ const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
1712
+ // Return black for light colors, white for dark colors
1713
+ return luminance > 0.5 ? '#000000' : '#ffffff';
1714
+ }
1447
1715
  injectDynamicStyles(css) {
1448
1716
  if (!isPlatformBrowser(this.platformId))
1449
1717
  return;
1450
- // Remove existing dynamic styles
1451
1718
  const existingStyle = this.document.getElementById(this.styleId);
1452
1719
  if (existingStyle) {
1453
1720
  existingStyle.remove();
1454
1721
  }
1455
- // Create and inject new styles
1456
1722
  const styleElement = this.document.createElement('style');
1457
1723
  styleElement.id = this.styleId;
1458
1724
  styleElement.textContent = css;
@@ -1462,23 +1728,29 @@ class ThemeService {
1462
1728
  const isCurrentlyDark = this.document.documentElement.classList.contains('dark');
1463
1729
  isCurrentlyDark ? this.disableDarkMode() : this.enableDarkMode();
1464
1730
  }
1465
- enableDarkMode() {
1731
+ enableDarkMode(saveToStorage = true) {
1466
1732
  this.renderer.addClass(this.document.documentElement, 'dark');
1467
- localStorage.setItem('tolle-theme', 'dark');
1733
+ if (saveToStorage) {
1734
+ localStorage.setItem('tolle-theme', 'dark');
1735
+ }
1468
1736
  this.isDarkSubject.next(true);
1469
1737
  }
1470
- disableDarkMode() {
1738
+ disableDarkMode(saveToStorage = true) {
1471
1739
  this.renderer.removeClass(this.document.documentElement, 'dark');
1472
- localStorage.setItem('tolle-theme', 'light');
1740
+ if (saveToStorage) {
1741
+ localStorage.setItem('tolle-theme', 'light');
1742
+ }
1473
1743
  this.isDarkSubject.next(false);
1474
1744
  }
1475
1745
  setPrimaryColor(color, persist = true) {
1476
1746
  if (!isPlatformBrowser(this.platformId))
1477
1747
  return;
1478
- // Update CSS variables + palette
1479
- this.renderer.setStyle(this.document.documentElement, '--primary', color);
1480
1748
  this.generatePrimaryShades(color);
1481
- // Persist user preference
1749
+ // Also set inline for immediate update
1750
+ const rgb = this.hexToRgb(color);
1751
+ if (rgb) {
1752
+ this.renderer.setStyle(this.document.documentElement, '--primary', `${rgb.r} ${rgb.g} ${rgb.b}`);
1753
+ }
1482
1754
  if (persist) {
1483
1755
  localStorage.setItem('tolle-primary-color', color);
1484
1756
  }
@@ -1487,8 +1759,71 @@ class ThemeService {
1487
1759
  return this.isDarkSubject.value ? 'dark' : 'light';
1488
1760
  }
1489
1761
  get primaryColor() {
1762
+ if (!isPlatformBrowser(this.platformId))
1763
+ return null;
1764
+ const root = this.document.documentElement;
1765
+ const cssValue = getComputedStyle(root).getPropertyValue('--primary').trim();
1766
+ if (cssValue && cssValue !== '') {
1767
+ // Convert RGB string back to hex for external use
1768
+ const rgbParts = cssValue.split(' ').map(Number);
1769
+ if (rgbParts.length === 3) {
1770
+ return `#${rgbParts[0].toString(16).padStart(2, '0')}${rgbParts[1].toString(16).padStart(2, '0')}${rgbParts[2].toString(16).padStart(2, '0')}`;
1771
+ }
1772
+ }
1490
1773
  return localStorage.getItem('tolle-primary-color');
1491
1774
  }
1775
+ /**
1776
+ * Reset to config defaults (clears user preferences)
1777
+ */
1778
+ resetToConfigDefaults() {
1779
+ if (!isPlatformBrowser(this.platformId))
1780
+ return;
1781
+ // Clear user preferences
1782
+ localStorage.removeItem('tolle-theme');
1783
+ localStorage.removeItem('tolle-primary-color');
1784
+ localStorage.removeItem('tolle-radius');
1785
+ // Re-initialize with config defaults
1786
+ this.initializeTheme();
1787
+ }
1788
+ /**
1789
+ * Get current user preferences
1790
+ */
1791
+ getUserPreferences() {
1792
+ if (!isPlatformBrowser(this.platformId))
1793
+ return null;
1794
+ return {
1795
+ theme: localStorage.getItem('tolle-theme'),
1796
+ primaryColor: localStorage.getItem('tolle-primary-color'),
1797
+ radius: localStorage.getItem('tolle-radius')
1798
+ };
1799
+ }
1800
+ /**
1801
+ * Clear all user preferences
1802
+ */
1803
+ clearUserPreferences() {
1804
+ if (!isPlatformBrowser(this.platformId))
1805
+ return;
1806
+ localStorage.removeItem('tolle-theme');
1807
+ localStorage.removeItem('tolle-primary-color');
1808
+ localStorage.removeItem('tolle-radius');
1809
+ // Reset to system defaults (not config defaults)
1810
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1811
+ if (systemPrefersDark) {
1812
+ this.enableDarkMode(false);
1813
+ }
1814
+ else {
1815
+ this.disableDarkMode(false);
1816
+ }
1817
+ // Remove CSS variables
1818
+ const root = this.document.documentElement;
1819
+ this.renderer.removeStyle(root, '--primary');
1820
+ this.renderer.removeStyle(root, '--radius');
1821
+ // Clear dynamic styles
1822
+ const existingStyle = this.document.getElementById(this.styleId);
1823
+ if (existingStyle) {
1824
+ existingStyle.remove();
1825
+ }
1826
+ }
1492
1827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }, { token: TOLLE_CONFIG, optional: true }, { token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable });
1493
1828
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, providedIn: 'root' });
1494
1829
  }
@@ -1670,7 +2005,7 @@ class MultiSelectComponent {
1670
2005
  </div>
1671
2006
  </div>
1672
2007
  </div>
1673
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.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, selector: "tolle-badge", inputs: ["variant", "size", "removable", "class"], outputs: ["onRemove"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
2008
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.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, selector: "tolle-badge", inputs: ["variant", "size", "removable", "class"], outputs: ["onRemove"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
1674
2009
  }
1675
2010
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiSelectComponent, decorators: [{
1676
2011
  type: Component,
@@ -2067,10 +2402,12 @@ class MaskedInputComponent {
2067
2402
  error = false;
2068
2403
  size = 'default';
2069
2404
  returnRaw = false;
2405
+ hideHintOnFocus = true;
2070
2406
  inputEl;
2071
2407
  hasPrefix = false;
2072
2408
  hasSuffix = false;
2073
2409
  displayValue = '';
2410
+ isFocused = false;
2074
2411
  tokens = {
2075
2412
  '0': /\d/, '9': /\d/, 'a': /[a-z]/i, 'A': /[a-z]/i, '*': /[a-z0-9]/i
2076
2413
  };
@@ -2089,19 +2426,71 @@ class MaskedInputComponent {
2089
2426
  this.cdr.detectChanges();
2090
2427
  }
2091
2428
  }
2429
+ get computedLabelClass() {
2430
+ return cn("text-sm font-medium text-foreground leading-none transition-opacity duration-200", this.disabled && "opacity-50");
2431
+ }
2092
2432
  get computedContainerClass() {
2093
- return cn("group relative flex items-center w-full rounded-md border transition-all shadow-sm", "bg-background ring-offset-background",
2433
+ return cn(
2434
+ // Base styles
2435
+ "group relative flex items-center w-full rounded-md border transition-all duration-200", "bg-background",
2436
+ // Border and shadow
2437
+ "border-input shadow-sm",
2094
2438
  // Sizing
2095
- this.size === 'xs' && "h-8 px-2 gap-1.5", this.size === 'sm' && "h-9 px-3 gap-2", this.size === 'default' && "h-10 px-3 gap-2", this.size === 'lg' && "h-11 px-4 gap-3",
2096
- // Interaction States (Inherited from Input focus style)
2097
- !(this.readonly || this.disabled) && "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1",
2098
- // Colors & Borders
2099
- this.error ? "border-destructive focus-within:ring-destructive" : "border-input",
2100
- // Disabled vs Readonly styling
2101
- this.disabled && "cursor-not-allowed opacity-50", this.readonly && "cursor-default border-dashed focus-within:ring-0", this.containerClass);
2439
+ this.size === 'xs' && "h-8 px-2 gap-1.5 text-xs", this.size === 'sm' && "h-9 px-3 gap-2 text-sm", this.size === 'default' && "h-10 px-3 gap-2 text-sm", this.size === 'lg' && "h-11 px-4 gap-3 text-base",
2440
+ // Focus state - SIMPLE LIKE ZARDUI
2441
+ !(this.readonly || this.disabled) && [
2442
+ "focus-within:border-primary/80",
2443
+ "focus-within:ring-4",
2444
+ "focus-within:ring-ring/30",
2445
+ "focus-within:ring-offset-0",
2446
+ "focus-within:shadow-none",
2447
+ ],
2448
+ // Error state
2449
+ this.error && [
2450
+ "border-destructive",
2451
+ !(this.readonly || this.disabled) && [
2452
+ "focus-within:border-destructive/80",
2453
+ "focus-within:ring-destructive/30"
2454
+ ]
2455
+ ],
2456
+ // Disabled state
2457
+ this.disabled && [
2458
+ "cursor-not-allowed opacity-50",
2459
+ "border-opacity-50"
2460
+ ],
2461
+ // Readonly state
2462
+ this.readonly && [
2463
+ "cursor-default",
2464
+ "border-dashed",
2465
+ !this.disabled && "focus-within:ring-0 focus-within:border-opacity-100"
2466
+ ], this.containerClass);
2102
2467
  }
2103
2468
  get computedInputClass() {
2104
- return cn("flex-1 bg-transparent border-none p-0 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-0", this.size === 'xs' && "text-xs", this.size === 'lg' && "text-base", this.disabled && "cursor-not-allowed", this.readonly && "cursor-default", this.class);
2469
+ return cn(
2470
+ // Base styles
2471
+ "flex-1 bg-transparent border-none p-0", "placeholder:text-muted-foreground",
2472
+ // Remove all default focus styles
2473
+ "focus:outline-none focus:ring-0 focus:shadow-none",
2474
+ // Text sizing
2475
+ this.size === 'xs' && "text-xs", this.size === 'sm' && "text-sm", this.size === 'default' && "text-sm", this.size === 'lg' && "text-base",
2476
+ // Cursor states
2477
+ this.disabled && "cursor-not-allowed", this.readonly && "cursor-default",
2478
+ // Text color
2479
+ "text-foreground",
2480
+ // Selection color
2481
+ "selection:bg-primary/20 selection:text-foreground", this.class);
2482
+ }
2483
+ focusInput() {
2484
+ if (!this.disabled && this.inputEl) {
2485
+ this.inputEl.nativeElement.focus();
2486
+ }
2487
+ }
2488
+ onFocus() {
2489
+ this.isFocused = true;
2490
+ }
2491
+ onBlur() {
2492
+ this.isFocused = false;
2493
+ this.onTouched();
2105
2494
  }
2106
2495
  // --- Masking Logic ---
2107
2496
  onInput(event) {
@@ -2140,20 +2529,26 @@ class MaskedInputComponent {
2140
2529
  }
2141
2530
  return formatted;
2142
2531
  }
2143
- unmask(val) { return val.replace(/[^a-zA-Z0-9]/g, ''); }
2532
+ unmask(val) {
2533
+ return val.replace(/[^a-zA-Z0-9]/g, '');
2534
+ }
2144
2535
  writeValue(value) {
2145
2536
  this.displayValue = value ? this.applyMask(this.unmask(value.toString())) : '';
2146
2537
  this.cdr.markForCheck();
2147
2538
  }
2148
- registerOnChange(fn) { this.onChange = fn; }
2149
- registerOnTouched(fn) { this.onTouched = fn; }
2539
+ registerOnChange(fn) {
2540
+ this.onChange = fn;
2541
+ }
2542
+ registerOnTouched(fn) {
2543
+ this.onTouched = fn;
2544
+ }
2150
2545
  setDisabledState(isDisabled) {
2151
2546
  this.disabled = isDisabled;
2152
2547
  this.cdr.markForCheck();
2153
2548
  }
2154
2549
  cn = cn;
2155
2550
  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 });
2156
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MaskedInputComponent, isStandalone: true, selector: "tolle-masked-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", mask: "mask", placeholder: "placeholder", type: "type", disabled: "disabled", readonly: "readonly", class: "class", containerClass: "containerClass", error: "error", size: "size", returnRaw: "returnRaw" }, providers: [
2551
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MaskedInputComponent, isStandalone: true, selector: "tolle-masked-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", mask: "mask", placeholder: "placeholder", type: "type", disabled: "disabled", readonly: "readonly", class: "class", containerClass: "containerClass", error: "error", size: "size", returnRaw: "returnRaw", hideHintOnFocus: "hideHintOnFocus" }, providers: [
2157
2552
  {
2158
2553
  provide: NG_VALUE_ACCESSOR,
2159
2554
  useExisting: forwardRef(() => MaskedInputComponent),
@@ -2164,15 +2559,17 @@ class MaskedInputComponent {
2164
2559
  <label
2165
2560
  *ngIf="label"
2166
2561
  [for]="id"
2167
- [class.opacity-50]="disabled"
2168
- class="text-sm font-medium text-foreground leading-none transition-opacity"
2562
+ [class]="computedLabelClass"
2169
2563
  >
2170
2564
  {{ label }}
2171
2565
  </label>
2172
2566
 
2173
- <div [class]="computedContainerClass">
2567
+ <div
2568
+ [class]="computedContainerClass"
2569
+ (click)="focusInput()"
2570
+ >
2174
2571
  <!-- Prefix Icon -->
2175
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2572
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2176
2573
  <ng-content select="[prefix]"></ng-content>
2177
2574
  </div>
2178
2575
 
@@ -2185,21 +2582,32 @@ class MaskedInputComponent {
2185
2582
  [readOnly]="readonly"
2186
2583
  [value]="displayValue"
2187
2584
  (input)="onInput($event)"
2188
- (blur)="onTouched()"
2585
+ (blur)="onBlur()"
2586
+ (focus)="onFocus()"
2189
2587
  [class]="computedInputClass"
2588
+ [attr.aria-invalid]="error"
2589
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
2190
2590
  />
2191
2591
 
2192
2592
  <!-- Suffix Icon -->
2193
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2593
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2194
2594
  <ng-content select="[suffix]"></ng-content>
2195
2595
  </div>
2196
2596
  </div>
2197
2597
 
2198
2598
  <ng-container *ngIf="!disabled">
2199
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
2599
+ <p
2600
+ *ngIf="hint && !error"
2601
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
2602
+ [class.opacity-0]="isFocused && hideHintOnFocus"
2603
+ >
2200
2604
  {{ hint }}
2201
2605
  </p>
2202
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
2606
+ <p
2607
+ *ngIf="error && errorMessage"
2608
+ [id]="id + '-error'"
2609
+ class="text-xs text-destructive px-1"
2610
+ >
2203
2611
  {{ errorMessage }}
2204
2612
  </p>
2205
2613
  </ng-container>
@@ -2224,15 +2632,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2224
2632
  <label
2225
2633
  *ngIf="label"
2226
2634
  [for]="id"
2227
- [class.opacity-50]="disabled"
2228
- class="text-sm font-medium text-foreground leading-none transition-opacity"
2635
+ [class]="computedLabelClass"
2229
2636
  >
2230
2637
  {{ label }}
2231
2638
  </label>
2232
2639
 
2233
- <div [class]="computedContainerClass">
2640
+ <div
2641
+ [class]="computedContainerClass"
2642
+ (click)="focusInput()"
2643
+ >
2234
2644
  <!-- Prefix Icon -->
2235
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2645
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2236
2646
  <ng-content select="[prefix]"></ng-content>
2237
2647
  </div>
2238
2648
 
@@ -2245,21 +2655,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2245
2655
  [readOnly]="readonly"
2246
2656
  [value]="displayValue"
2247
2657
  (input)="onInput($event)"
2248
- (blur)="onTouched()"
2658
+ (blur)="onBlur()"
2659
+ (focus)="onFocus()"
2249
2660
  [class]="computedInputClass"
2661
+ [attr.aria-invalid]="error"
2662
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
2250
2663
  />
2251
2664
 
2252
2665
  <!-- Suffix Icon -->
2253
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2666
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2254
2667
  <ng-content select="[suffix]"></ng-content>
2255
2668
  </div>
2256
2669
  </div>
2257
2670
 
2258
2671
  <ng-container *ngIf="!disabled">
2259
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
2672
+ <p
2673
+ *ngIf="hint && !error"
2674
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
2675
+ [class.opacity-0]="isFocused && hideHintOnFocus"
2676
+ >
2260
2677
  {{ hint }}
2261
2678
  </p>
2262
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
2679
+ <p
2680
+ *ngIf="error && errorMessage"
2681
+ [id]="id + '-error'"
2682
+ class="text-xs text-destructive px-1"
2683
+ >
2263
2684
  {{ errorMessage }}
2264
2685
  </p>
2265
2686
  </ng-container>
@@ -2294,6 +2715,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2294
2715
  type: Input
2295
2716
  }], returnRaw: [{
2296
2717
  type: Input
2718
+ }], hideHintOnFocus: [{
2719
+ type: Input
2297
2720
  }], inputEl: [{
2298
2721
  type: ViewChild,
2299
2722
  args: ['inputEl', { static: true }]
@@ -2446,7 +2869,7 @@ class DatePickerComponent {
2446
2869
  ></tolle-calendar>
2447
2870
  </div>
2448
2871
  </div>
2449
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.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: ["id", "label", "hint", "errorMessage", "mask", "placeholder", "type", "disabled", "readonly", "class", "containerClass", "error", "size", "returnRaw"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "disablePastDates"] }] });
2872
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.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: ["id", "label", "hint", "errorMessage", "mask", "placeholder", "type", "disabled", "readonly", "class", "containerClass", "error", "size", "returnRaw", "hideHintOnFocus"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "disablePastDates"] }] });
2450
2873
  }
2451
2874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
2452
2875
  type: Component,
@@ -3038,7 +3461,7 @@ class DataTableComponent {
3038
3461
  (onPageSizeChange)="updatePage()"
3039
3462
  ></tolle-pagination>
3040
3463
  </div>
3041
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.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, selector: "tolle-pagination", inputs: ["class", "showPageLinks", "showPageOptions", "showCurrentPageInfo", "currentPageInfoTemplate", "totalRecords", "currentPageSize", "currentPage", "pageSizeOptions"], outputs: ["onPageNumberChange", "onPageSizeChange"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
3464
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.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, selector: "tolle-pagination", inputs: ["class", "showPageLinks", "showPageOptions", "showCurrentPageInfo", "currentPageInfoTemplate", "totalRecords", "currentPageSize", "currentPage", "pageSizeOptions"], outputs: ["onPageNumberChange", "onPageSizeChange"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
3042
3465
  }
3043
3466
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataTableComponent, decorators: [{
3044
3467
  type: Component,
@@ -3948,7 +4371,7 @@ class DateRangePickerComponent {
3948
4371
  ></tolle-range-calendar>
3949
4372
  </div>
3950
4373
  </div>
3951
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.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, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
4374
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.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, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
3952
4375
  }
3953
4376
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DateRangePickerComponent, decorators: [{
3954
4377
  type: Component,
@@ -4166,30 +4589,69 @@ class TextareaComponent {
4166
4589
  label = '';
4167
4590
  placeholder = '';
4168
4591
  hint = '';
4592
+ errorMessage = '';
4169
4593
  rows = 3;
4170
4594
  maxLength;
4171
4595
  showCharacterCount = false;
4172
4596
  autoGrow = false;
4173
4597
  error = false;
4174
4598
  className = '';
4599
+ // Focus behavior
4600
+ hideHintOnFocus = true;
4601
+ hideCharacterCountOnFocus = false;
4175
4602
  // New States
4176
4603
  disabled = false;
4177
4604
  readonly = false;
4178
4605
  value = '';
4606
+ isFocused = false;
4179
4607
  onChange = () => { };
4180
4608
  onTouched = () => { };
4181
4609
  ngAfterViewInit() {
4182
4610
  if (this.autoGrow)
4183
4611
  this.resize();
4184
4612
  }
4613
+ get computedLabelClass() {
4614
+ return cn("text-sm font-medium text-foreground leading-none transition-opacity duration-200", this.disabled && "opacity-50");
4615
+ }
4185
4616
  get textareaClasses() {
4186
- return cn('flex w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background transition-all duration-200 shadow-sm', 'placeholder:text-muted-foreground focus-visible:outline-none',
4187
- // Focus States (Disabled only when readonly or disabled is true)
4188
- !(this.readonly || this.disabled) && 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
4189
- // Border colors
4190
- this.error ? 'border-destructive' : 'border-input',
4191
- // Disabled vs Readonly styles
4192
- this.disabled && 'cursor-not-allowed opacity-50', this.readonly && 'cursor-default border-dashed focus-visible:ring-0', 'scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent', 'min-h-[80px]', this.className);
4617
+ return cn(
4618
+ // Base styles
4619
+ 'flex w-full rounded-md border bg-background px-3 py-2 text-sm', 'ring-offset-background transition-all duration-200', 'placeholder:text-muted-foreground',
4620
+ // Border and shadow
4621
+ 'border-input shadow-sm',
4622
+ // Minimum height
4623
+ 'min-h-[80px]',
4624
+ // Focus state - SIMPLE LIKE ZARDUI
4625
+ !(this.readonly || this.disabled) && [
4626
+ 'focus:outline-none',
4627
+ 'focus:ring-4',
4628
+ 'focus:ring-ring/30',
4629
+ 'focus:ring-offset-0',
4630
+ 'focus:shadow-none',
4631
+ // Border darkens on focus automatically
4632
+ 'focus:border-primary/80'
4633
+ ],
4634
+ // Error state
4635
+ this.error && [
4636
+ 'border-destructive',
4637
+ !(this.readonly || this.disabled) && [
4638
+ 'focus:border-destructive/80',
4639
+ 'focus:ring-destructive/30'
4640
+ ]
4641
+ ],
4642
+ // Disabled state
4643
+ this.disabled && [
4644
+ 'cursor-not-allowed opacity-50',
4645
+ 'border-opacity-50'
4646
+ ],
4647
+ // Readonly state
4648
+ this.readonly && [
4649
+ 'cursor-default',
4650
+ 'border-dashed',
4651
+ !this.disabled && 'focus:ring-0 focus:border-opacity-100'
4652
+ ],
4653
+ // Scrollbar styling
4654
+ 'scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent', this.className);
4193
4655
  }
4194
4656
  handleInput(event) {
4195
4657
  if (this.readonly || this.disabled)
@@ -4200,6 +4662,13 @@ class TextareaComponent {
4200
4662
  if (this.autoGrow)
4201
4663
  this.resize();
4202
4664
  }
4665
+ onFocus() {
4666
+ this.isFocused = true;
4667
+ }
4668
+ onBlur() {
4669
+ this.isFocused = false;
4670
+ this.onTouched();
4671
+ }
4203
4672
  resize() {
4204
4673
  const textarea = this.textareaElement.nativeElement;
4205
4674
  textarea.style.height = 'auto';
@@ -4210,11 +4679,17 @@ class TextareaComponent {
4210
4679
  if (this.autoGrow)
4211
4680
  setTimeout(() => this.resize());
4212
4681
  }
4213
- registerOnChange(fn) { this.onChange = fn; }
4214
- registerOnTouched(fn) { this.onTouched = fn; }
4215
- setDisabledState(isDisabled) { this.disabled = isDisabled; }
4682
+ registerOnChange(fn) {
4683
+ this.onChange = fn;
4684
+ }
4685
+ registerOnTouched(fn) {
4686
+ this.onTouched = fn;
4687
+ }
4688
+ setDisabledState(isDisabled) {
4689
+ this.disabled = isDisabled;
4690
+ }
4216
4691
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4217
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TextareaComponent, isStandalone: true, selector: "tolle-textarea", inputs: { id: "id", label: "label", placeholder: "placeholder", hint: "hint", rows: "rows", maxLength: "maxLength", showCharacterCount: "showCharacterCount", autoGrow: "autoGrow", error: "error", className: "className", disabled: "disabled", readonly: "readonly" }, providers: [
4692
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TextareaComponent, isStandalone: true, selector: "tolle-textarea", inputs: { id: "id", label: "label", placeholder: "placeholder", hint: "hint", errorMessage: "errorMessage", rows: "rows", maxLength: "maxLength", showCharacterCount: "showCharacterCount", autoGrow: "autoGrow", error: "error", className: "className", hideHintOnFocus: "hideHintOnFocus", hideCharacterCountOnFocus: "hideCharacterCountOnFocus", disabled: "disabled", readonly: "readonly" }, providers: [
4218
4693
  {
4219
4694
  provide: NG_VALUE_ACCESSOR,
4220
4695
  useExisting: forwardRef(() => TextareaComponent),
@@ -4222,9 +4697,11 @@ class TextareaComponent {
4222
4697
  }
4223
4698
  ], viewQueries: [{ propertyName: "textareaElement", first: true, predicate: ["textareaElement"], descendants: true }], ngImport: i0, template: `
4224
4699
  <div class="flex flex-col gap-1.5 w-full">
4225
- <label *ngIf="label" [for]="id"
4226
- [class.opacity-50]="disabled"
4227
- class="text-sm font-medium text-foreground leading-none transition-opacity">
4700
+ <label
4701
+ *ngIf="label"
4702
+ [for]="id"
4703
+ [class]="computedLabelClass"
4704
+ >
4228
4705
  {{ label }}
4229
4706
  </label>
4230
4707
 
@@ -4238,16 +4715,37 @@ class TextareaComponent {
4238
4715
  [rows]="rows"
4239
4716
  [(ngModel)]="value"
4240
4717
  (input)="handleInput($event)"
4241
- (blur)="onTouched()"
4718
+ (blur)="onBlur()"
4719
+ (focus)="onFocus()"
4242
4720
  [class]="textareaClasses"
4243
4721
  [style.resize]="(autoGrow || readonly || disabled) ? 'none' : 'vertical'"
4244
4722
  [style.overflow]="autoGrow ? 'hidden' : 'auto'"
4723
+ [attr.aria-invalid]="error"
4724
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
4725
+ [attr.maxlength]="maxLength"
4245
4726
  ></textarea>
4246
4727
  </div>
4247
4728
 
4248
- <div *ngIf="(showCharacterCount || hint) && !disabled" class="flex justify-between items-center px-1">
4249
- <p *ngIf="hint" class="text-xs text-muted-foreground">{{ hint }}</p>
4250
- <p *ngIf="showCharacterCount" class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium">
4729
+ <div *ngIf="(showCharacterCount || hint || errorMessage) && !disabled" class="flex justify-between items-center px-1">
4730
+ <p
4731
+ *ngIf="hint && !error"
4732
+ class="text-xs text-muted-foreground transition-opacity duration-200"
4733
+ [class.opacity-0]="isFocused && hideHintOnFocus"
4734
+ >
4735
+ {{ hint }}
4736
+ </p>
4737
+ <p
4738
+ *ngIf="error && errorMessage"
4739
+ [id]="id + '-error'"
4740
+ class="text-xs text-destructive"
4741
+ >
4742
+ {{ errorMessage }}
4743
+ </p>
4744
+ <p
4745
+ *ngIf="showCharacterCount"
4746
+ class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium transition-opacity duration-200"
4747
+ [class.opacity-0]="isFocused && hideCharacterCountOnFocus"
4748
+ >
4251
4749
  {{ value.length || 0 }}{{ maxLength ? ' / ' + maxLength : '' }}
4252
4750
  </p>
4253
4751
  </div>
@@ -4269,9 +4767,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4269
4767
  ],
4270
4768
  template: `
4271
4769
  <div class="flex flex-col gap-1.5 w-full">
4272
- <label *ngIf="label" [for]="id"
4273
- [class.opacity-50]="disabled"
4274
- class="text-sm font-medium text-foreground leading-none transition-opacity">
4770
+ <label
4771
+ *ngIf="label"
4772
+ [for]="id"
4773
+ [class]="computedLabelClass"
4774
+ >
4275
4775
  {{ label }}
4276
4776
  </label>
4277
4777
 
@@ -4285,16 +4785,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4285
4785
  [rows]="rows"
4286
4786
  [(ngModel)]="value"
4287
4787
  (input)="handleInput($event)"
4288
- (blur)="onTouched()"
4788
+ (blur)="onBlur()"
4789
+ (focus)="onFocus()"
4289
4790
  [class]="textareaClasses"
4290
4791
  [style.resize]="(autoGrow || readonly || disabled) ? 'none' : 'vertical'"
4291
4792
  [style.overflow]="autoGrow ? 'hidden' : 'auto'"
4793
+ [attr.aria-invalid]="error"
4794
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
4795
+ [attr.maxlength]="maxLength"
4292
4796
  ></textarea>
4293
4797
  </div>
4294
4798
 
4295
- <div *ngIf="(showCharacterCount || hint) && !disabled" class="flex justify-between items-center px-1">
4296
- <p *ngIf="hint" class="text-xs text-muted-foreground">{{ hint }}</p>
4297
- <p *ngIf="showCharacterCount" class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium">
4799
+ <div *ngIf="(showCharacterCount || hint || errorMessage) && !disabled" class="flex justify-between items-center px-1">
4800
+ <p
4801
+ *ngIf="hint && !error"
4802
+ class="text-xs text-muted-foreground transition-opacity duration-200"
4803
+ [class.opacity-0]="isFocused && hideHintOnFocus"
4804
+ >
4805
+ {{ hint }}
4806
+ </p>
4807
+ <p
4808
+ *ngIf="error && errorMessage"
4809
+ [id]="id + '-error'"
4810
+ class="text-xs text-destructive"
4811
+ >
4812
+ {{ errorMessage }}
4813
+ </p>
4814
+ <p
4815
+ *ngIf="showCharacterCount"
4816
+ class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium transition-opacity duration-200"
4817
+ [class.opacity-0]="isFocused && hideCharacterCountOnFocus"
4818
+ >
4298
4819
  {{ value.length || 0 }}{{ maxLength ? ' / ' + maxLength : '' }}
4299
4820
  </p>
4300
4821
  </div>
@@ -4312,6 +4833,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4312
4833
  type: Input
4313
4834
  }], hint: [{
4314
4835
  type: Input
4836
+ }], errorMessage: [{
4837
+ type: Input
4315
4838
  }], rows: [{
4316
4839
  type: Input
4317
4840
  }], maxLength: [{
@@ -4324,6 +4847,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4324
4847
  type: Input
4325
4848
  }], className: [{
4326
4849
  type: Input
4850
+ }], hideHintOnFocus: [{
4851
+ type: Input
4852
+ }], hideCharacterCountOnFocus: [{
4853
+ type: Input
4327
4854
  }], disabled: [{
4328
4855
  type: Input
4329
4856
  }], readonly: [{
@@ -4887,23 +5414,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4887
5414
  class OtpSlotComponent {
4888
5415
  char = '';
4889
5416
  isActive = false;
4890
- isFirst = false; // New Input
4891
- isLast = false; // New Input
5417
+ isFirst = false;
5418
+ isLast = false;
4892
5419
  class = '';
4893
5420
  cn = cn;
5421
+ get computedClass() {
5422
+ return cn(
5423
+ // Base styles (matching input container)
5424
+ 'relative flex h-10 w-10 items-center justify-center', 'transition-all duration-200', 'text-center font-medium select-none',
5425
+ // Border styling - exactly like input
5426
+ 'border border-input shadow-sm bg-background',
5427
+ // FOCUS STYLING - EXACTLY LIKE INPUT COMPONENT
5428
+ this.isActive && [
5429
+ // Darker border on focus
5430
+ 'border-primary/80',
5431
+ // Remove shadow on focus
5432
+ 'shadow-none',
5433
+ // Focus ring with same styling as input
5434
+ 'ring-4',
5435
+ 'ring-ring/30',
5436
+ 'ring-offset-0',
5437
+ // Ensure it's above other slots
5438
+ 'z-10'
5439
+ ],
5440
+ // Filled state
5441
+ this.char && !this.isActive && 'border-primary/60',
5442
+ // Border radius
5443
+ this.isFirst && 'rounded-l-md', this.isLast && 'rounded-r-md',
5444
+ // Remove left border for all but first slot
5445
+ !this.isFirst && '-ml-px', this.class);
5446
+ }
4894
5447
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OtpSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4895
5448
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: OtpSlotComponent, isStandalone: true, selector: "tolle-otp-slot", inputs: { char: "char", isActive: "isActive", isFirst: "isFirst", isLast: "isLast", class: "class" }, ngImport: i0, template: `
4896
- <div [class]="cn(
4897
- 'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all',
4898
- 'bg-background',
4899
- isFirst ? 'rounded-l-md border-l' : '',
4900
- isLast ? 'rounded-r-md' : '',
4901
- isActive ? 'z-10 ring-2 ring-ring ring-offset-background' : '',
4902
- class
4903
- )">
4904
- {{ char || '' }}
5449
+ <div [class]="computedClass">
5450
+ <span class="text-lg font-medium">{{ char || '' }}</span>
4905
5451
  <div *ngIf="isActive && !char" class="pointer-events-none absolute inset-0 flex items-center justify-center">
4906
- <div class="h-4 w-px animate-caret-blink bg-foreground duration-1000"></div>
5452
+ <div class="h-6 w-0.5 animate-caret-blink bg-foreground"></div>
4907
5453
  </div>
4908
5454
  </div>
4909
5455
  `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
@@ -4915,17 +5461,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4915
5461
  standalone: true,
4916
5462
  imports: [NgIf],
4917
5463
  template: `
4918
- <div [class]="cn(
4919
- 'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all',
4920
- 'bg-background',
4921
- isFirst ? 'rounded-l-md border-l' : '',
4922
- isLast ? 'rounded-r-md' : '',
4923
- isActive ? 'z-10 ring-2 ring-ring ring-offset-background' : '',
4924
- class
4925
- )">
4926
- {{ char || '' }}
5464
+ <div [class]="computedClass">
5465
+ <span class="text-lg font-medium">{{ char || '' }}</span>
4927
5466
  <div *ngIf="isActive && !char" class="pointer-events-none absolute inset-0 flex items-center justify-center">
4928
- <div class="h-4 w-px animate-caret-blink bg-foreground duration-1000"></div>
5467
+ <div class="h-6 w-0.5 animate-caret-blink bg-foreground"></div>
4929
5468
  </div>
4930
5469
  </div>
4931
5470
  `