@tociva/tailng-ui 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,3101 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports$1) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./lib"), exports);
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { input, output, signal, computed, effect, TemplateRef, forwardRef, ViewChild, ContentChild, Component, Injectable, InjectionToken, inject, ChangeDetectionStrategy, HostListener, Directive, contentChild, DestroyRef } from '@angular/core';
4
+ import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
5
+ import { TngConnectedOverlay, TngOverlayPanel, TngOptionList, TngOverlayRef } from '@tociva/tailng-ui/popups-overlays';
6
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
+ import { merge, of } from 'rxjs';
8
+
9
+ // autocomplete.component.ts
10
+ class TngAutocomplete {
11
+ /* =====================
12
+ * Projected templates
13
+ * ===================== */
14
+ // Dropdown option template (list rows)
15
+ optionTpl;
16
+ // Selected value template (shown inside input area when not typing)
17
+ inputTpl;
18
+ inputEl;
19
+ // We call optionList.onKeydown(ev) from the INPUT keydown handler
20
+ optionList;
21
+ /* =====================
22
+ * Inputs / Outputs
23
+ * ===================== */
24
+ inputKlass = input('', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
25
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
26
+ placeholder = input('Start typing…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
27
+ /** External disabled input (read-only InputSignal) */
28
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
29
+ /** String representation (used for actual input.value + fallback) */
30
+ displayWith = input((v) => String(v), ...(ngDevMode ? [{ debugName: "displayWith" }] : []));
31
+ /** Raw text for filtering / API search (not the form value) */
32
+ search = output();
33
+ /** Optional: non-form usage hook */
34
+ selected = output();
35
+ /** Emits whenever overlay closes (selection/escape/outside-click/blur/programmatic) */
36
+ closed = output();
37
+ /* =====================
38
+ * Internal State
39
+ * ===================== */
40
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : []));
41
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
42
+ focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
43
+ /** eslint-safe + template-safe internal disabled state */
44
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
45
+ /** Selected item (for inputTpl rendering) */
46
+ selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "selectedValue" }] : []));
47
+ /**
48
+ * Whether to show rich selected template over the input.
49
+ * Show only when:
50
+ * - inputTpl exists
51
+ * - we have a selected value
52
+ * - overlay is closed (so typing/search UX stays normal)
53
+ */
54
+ showSelectedTpl = computed(() => !!this.inputTpl && this.selectedValue() != null && !this.isOpen(), ...(ngDevMode ? [{ debugName: "showSelectedTpl" }] : []));
55
+ inputKlassFinal = computed(() => ['relative z-0 w-full border rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary bg-bg text-fg', this.inputKlass()].join(' '), ...(ngDevMode ? [{ debugName: "inputKlassFinal" }] : []));
56
+ /** Form value (selected item) */
57
+ value = null;
58
+ /* =====================
59
+ * ControlValueAccessor
60
+ * ===================== */
61
+ onChange = () => { };
62
+ onTouched = () => { };
63
+ constructor() {
64
+ // Sync external [disabled] -> internal state
65
+ effect(() => {
66
+ this.isDisabled.set(this.disabled());
67
+ if (this.isDisabled())
68
+ this.close('programmatic');
69
+ });
70
+ }
71
+ /* =====================
72
+ * ControlValueAccessor API
73
+ * ===================== */
74
+ writeValue(value) {
75
+ this.value = value;
76
+ this.selectedValue.set(value);
77
+ if (value == null) {
78
+ this.inputValue.set('');
79
+ this.focusedIndex.set(-1);
80
+ return;
81
+ }
82
+ // keep text value for fallback + accessibility
83
+ this.inputValue.set(this.displayWith()(value));
84
+ this.focusedIndex.set(-1);
85
+ }
86
+ registerOnChange(fn) {
87
+ this.onChange = fn;
88
+ }
89
+ registerOnTouched(fn) {
90
+ this.onTouched = fn;
91
+ }
92
+ setDisabledState(isDisabled) {
93
+ this.isDisabled.set(isDisabled);
94
+ if (isDisabled)
95
+ this.close('programmatic');
96
+ }
97
+ /* =====================
98
+ * Display helper
99
+ * ===================== */
100
+ display(item) {
101
+ return this.displayWith()(item);
102
+ }
103
+ /* =====================
104
+ * State Transitions
105
+ * ===================== */
106
+ open(_reason) {
107
+ if (this.isDisabled())
108
+ return;
109
+ this.isOpen.set(true);
110
+ if (this.options().length) {
111
+ const current = this.focusedIndex();
112
+ if (current < 0)
113
+ this.focusedIndex.set(0);
114
+ }
115
+ else {
116
+ this.focusedIndex.set(-1);
117
+ }
118
+ }
119
+ close(reason) {
120
+ if (!this.isOpen())
121
+ return;
122
+ this.isOpen.set(false);
123
+ this.focusedIndex.set(-1);
124
+ this.closed.emit(reason);
125
+ }
126
+ onOverlayClosed(reason) {
127
+ this.close(reason);
128
+ }
129
+ onOverlayOpenChange(open) {
130
+ if (this.isDisabled()) {
131
+ this.isOpen.set(false);
132
+ return;
133
+ }
134
+ if (open)
135
+ this.open('programmatic');
136
+ else
137
+ this.close('programmatic');
138
+ }
139
+ /* =====================
140
+ * UI Events
141
+ * ===================== */
142
+ onInput(ev) {
143
+ if (this.isDisabled())
144
+ return;
145
+ const text = ev.target.value ?? '';
146
+ this.inputValue.set(text);
147
+ // typing clears selection (so inputTpl disappears)
148
+ this.value = null;
149
+ this.selectedValue.set(null);
150
+ this.onChange(null);
151
+ this.search.emit(text);
152
+ this.open('programmatic');
153
+ }
154
+ onBlur() {
155
+ this.onTouched();
156
+ queueMicrotask(() => {
157
+ if (document.activeElement === this.inputEl.nativeElement)
158
+ return;
159
+ this.close('blur');
160
+ });
161
+ }
162
+ /**
163
+ * Key handling updated to match OptionList architecture:
164
+ * - Escape is handled here (OptionList doesn't close overlays)
165
+ * - Navigation/Enter are delegated to optionList.onKeydown(ev)
166
+ * - Printable keys are NOT delegated (input typing controls filtering)
167
+ */
168
+ onKeydown(ev) {
169
+ if (this.isDisabled())
170
+ return;
171
+ // Close on escape
172
+ if (ev.key === 'Escape' && this.isOpen()) {
173
+ ev.preventDefault();
174
+ ev.stopPropagation();
175
+ this.close('escape');
176
+ return;
177
+ }
178
+ // If closed: ArrowDown/Up should open + delegate once overlay is painted
179
+ if (!this.isOpen() && (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')) {
180
+ ev.preventDefault();
181
+ ev.stopPropagation();
182
+ this.open('programmatic');
183
+ // wait for overlay + optionList to exist
184
+ requestAnimationFrame(() => this.optionList?.onKeydown(ev));
185
+ return;
186
+ }
187
+ if (!this.isOpen())
188
+ return;
189
+ // Delegate only list-navigation keys
190
+ if (!this.isListNavigationKey(ev))
191
+ return;
192
+ ev.stopPropagation(); // helps if parent/global handlers exist
193
+ this.optionList?.onKeydown(ev);
194
+ }
195
+ isListNavigationKey(ev) {
196
+ // Match the keys handled by OptionList (excluding typeahead)
197
+ switch (ev.key) {
198
+ case 'ArrowDown':
199
+ case 'ArrowUp':
200
+ case 'Home':
201
+ case 'End':
202
+ case 'PageDown':
203
+ case 'PageUp':
204
+ case 'Enter':
205
+ return true;
206
+ default:
207
+ return false;
208
+ }
209
+ }
210
+ /* =====================
211
+ * OptionList wiring
212
+ * ===================== */
213
+ onFocusedIndexChange(i) {
214
+ this.focusedIndex.set(i);
215
+ }
216
+ requestSelectActive() {
217
+ const i = this.focusedIndex();
218
+ const item = this.options()[i];
219
+ if (item !== undefined)
220
+ this.select(item);
221
+ }
222
+ /* =====================
223
+ * Selection
224
+ * ===================== */
225
+ select(item) {
226
+ if (this.isDisabled())
227
+ return;
228
+ this.value = item;
229
+ this.selectedValue.set(item);
230
+ // keep input value as text fallback
231
+ this.inputValue.set(this.displayWith()(item));
232
+ this.onChange(item);
233
+ this.onTouched();
234
+ this.selected.emit(item);
235
+ this.close('selection');
236
+ queueMicrotask(() => {
237
+ this.inputEl.nativeElement.focus();
238
+ });
239
+ }
240
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngAutocomplete, deps: [], target: i0.ɵɵFactoryTarget.Component });
241
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngAutocomplete, isStandalone: true, selector: "tng-autocomplete", inputs: { inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search", selected: "selected", closed: "closed" }, providers: [
242
+ {
243
+ provide: NG_VALUE_ACCESSOR,
244
+ useExisting: forwardRef(() => TngAutocomplete),
245
+ multi: true,
246
+ },
247
+ ], queries: [{ propertyName: "optionTpl", first: true, predicate: ["optionTpl"], descendants: true, read: TemplateRef }, { propertyName: "inputTpl", first: true, predicate: ["inputTpl"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }, { propertyName: "optionList", first: true, predicate: TngOptionList, descendants: true }], ngImport: i0, template: "<!-- autocomplete.component.html -->\n<div class=\"relative\">\n <!-- Selected-value rich display overlay (inside input) -->\n @if (showSelectedTpl()) {\n <div\n class=\"pointer-events-none absolute inset-y-0 left-0 right-0 z-10 flex items-center px-3\"\n aria-hidden=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"inputTpl!\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedValue()! }\"\n />\n </div>\n }\n\n <div [class.opacity-60]=\"isDisabled()\" [class.pointer-events-none]=\"isDisabled()\">\n <input\n #inputEl\n type=\"text\"\n [class]=\"inputKlassFinal()\"\n [class.text-transparent]=\"showSelectedTpl()\"\n [class.caret-slate-900]=\"showSelectedTpl()\"\n [class.bg-transparent]=\"showSelectedTpl()\"\n [value]=\"inputValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"overlayRef.requestOpen()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n />\n </div>\n\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"inputEl\"\n placement=\"bottom-start\"\n width=\"anchor\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel>\n <tng-option-list\n [items]=\"options()\"\n [activeIndex]=\"focusedIndex()\"\n [displayWith]=\"displayWith()\"\n [optionTemplate]=\"optionTpl ?? null\"\n [loop]=\"false\"\n [typeahead]=\"false\"\n (activeIndexChange)=\"onFocusedIndexChange($event)\"\n (requestSelectActive)=\"requestSelectActive()\"\n (optionMouseDown)=\"select($event.item)\"\n (optionHover)=\"focusedIndex.set($event)\"\n />\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TngConnectedOverlay, selector: "tng-connected-overlay", inputs: ["open", "anchor", "placement", "offset", "width", "closeOnOutsideClick", "closeOnInsideClick", "closeOnEscape", "hasBackdrop", "backdropClass"], outputs: ["opened", "closed", "backdropClick"] }, { kind: "component", type: TngOverlayPanel, selector: "tng-overlay-panel", inputs: ["klass", "modal", "role", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "restoreFocus", "autoCapture", "deferCaptureElements"] }, { kind: "component", type: TngOptionList, selector: "tng-option-list", inputs: ["modal", "panelKlass", "restoreFocus", "autoCapture", "deferCaptureElements", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "tabindex", "optionTemplate", "items", "activeIndex", "displayWith", "emptyText", "containerKlass", "optionKlass", "optionActiveKlass", "optionInactiveKlass", "emptyKlass", "propagateKeys", "keyboard", "loop", "selectOnEnter", "typeahead", "typeaheadMode", "typeaheadResetMs", "pageJumpSize"], outputs: ["optionMouseDown", "optionHover", "activeIndexChange", "requestSelectActive", "requestTypeaheadMatch", "keyStroke"] }, { kind: "component", type: TngOverlayRef, selector: "tng-overlay-ref", inputs: ["open"], outputs: ["openChange", "opened", "closed"], exportAs: ["tngOverlayRef"] }] });
248
+ }
249
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngAutocomplete, decorators: [{
250
+ type: Component,
251
+ args: [{ selector: 'tng-autocomplete', standalone: true, imports: [
252
+ NgTemplateOutlet,
253
+ TngConnectedOverlay,
254
+ TngOverlayPanel,
255
+ TngOptionList,
256
+ TngOverlayRef,
257
+ ], providers: [
258
+ {
259
+ provide: NG_VALUE_ACCESSOR,
260
+ useExisting: forwardRef(() => TngAutocomplete),
261
+ multi: true,
262
+ },
263
+ ], template: "<!-- autocomplete.component.html -->\n<div class=\"relative\">\n <!-- Selected-value rich display overlay (inside input) -->\n @if (showSelectedTpl()) {\n <div\n class=\"pointer-events-none absolute inset-y-0 left-0 right-0 z-10 flex items-center px-3\"\n aria-hidden=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"inputTpl!\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedValue()! }\"\n />\n </div>\n }\n\n <div [class.opacity-60]=\"isDisabled()\" [class.pointer-events-none]=\"isDisabled()\">\n <input\n #inputEl\n type=\"text\"\n [class]=\"inputKlassFinal()\"\n [class.text-transparent]=\"showSelectedTpl()\"\n [class.caret-slate-900]=\"showSelectedTpl()\"\n [class.bg-transparent]=\"showSelectedTpl()\"\n [value]=\"inputValue()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"overlayRef.requestOpen()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n />\n </div>\n\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"inputEl\"\n placement=\"bottom-start\"\n width=\"anchor\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel>\n <tng-option-list\n [items]=\"options()\"\n [activeIndex]=\"focusedIndex()\"\n [displayWith]=\"displayWith()\"\n [optionTemplate]=\"optionTpl ?? null\"\n [loop]=\"false\"\n [typeahead]=\"false\"\n (activeIndexChange)=\"onFocusedIndexChange($event)\"\n (requestSelectActive)=\"requestSelectActive()\"\n (optionMouseDown)=\"select($event.item)\"\n (optionHover)=\"focusedIndex.set($event)\"\n />\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n" }]
264
+ }], ctorParameters: () => [], propDecorators: { optionTpl: [{
265
+ type: ContentChild,
266
+ args: ['optionTpl', { read: TemplateRef }]
267
+ }], inputTpl: [{
268
+ type: ContentChild,
269
+ args: ['inputTpl', { read: TemplateRef }]
270
+ }], inputEl: [{
271
+ type: ViewChild,
272
+ args: ['inputEl', { static: true }]
273
+ }], optionList: [{
274
+ type: ViewChild,
275
+ args: [TngOptionList]
276
+ }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], selected: [{ type: i0.Output, args: ["selected"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
277
+
278
+ class TngButtonToggle {
279
+ /* =====================
280
+ * Inputs / Outputs
281
+ * ===================== */
282
+ /** Options for the segmented control */
283
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
284
+ /**
285
+ * Direct mode value (optional).
286
+ * In forms mode, CVA is source of truth.
287
+ */
288
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
289
+ valueChange = output();
290
+ /** External disabled input (read-only) */
291
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
292
+ /** Material-like: single (default) or multiple selection */
293
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
294
+ /**
295
+ * Whether selection can be cleared:
296
+ * - single: clicking active option -> null
297
+ * - multiple: allows empty array
298
+ */
299
+ allowDeselect = input(false, ...(ngDevMode ? [{ debugName: "allowDeselect" }] : []));
300
+ /* =====================
301
+ * Theming / class hooks (section-wise)
302
+ * ===================== */
303
+ /** Root wrapper */
304
+ rootKlass = input('block w-full', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
305
+ /** Group container */
306
+ groupKlass = input('', ...(ngDevMode ? [{ debugName: "groupKlass" }] : []));
307
+ /** Button base */
308
+ buttonKlass = input('', ...(ngDevMode ? [{ debugName: "buttonKlass" }] : []));
309
+ /** Active option button */
310
+ activeButtonKlass = input('', ...(ngDevMode ? [{ debugName: "activeButtonKlass" }] : []));
311
+ /** Inactive option button */
312
+ inactiveButtonKlass = input('', ...(ngDevMode ? [{ debugName: "inactiveButtonKlass" }] : []));
313
+ /** Disabled option button */
314
+ disabledButtonKlass = input('', ...(ngDevMode ? [{ debugName: "disabledButtonKlass" }] : []));
315
+ /* =====================
316
+ * Internal state
317
+ * ===================== */
318
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
319
+ /** Authoritative selected value inside component */
320
+ selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "selectedValue" }] : []));
321
+ /** When true, CVA owns the value (forms mode) */
322
+ usingCva = false;
323
+ /* =====================
324
+ * ControlValueAccessor
325
+ * ===================== */
326
+ onChange = () => { };
327
+ onTouched = () => { };
328
+ constructor() {
329
+ // sync external disabled -> internal
330
+ effect(() => {
331
+ this.isDisabled.set(this.disabled());
332
+ });
333
+ // sync external [value] -> internal only when NOT using CVA
334
+ effect(() => {
335
+ const v = this.value();
336
+ if (this.usingCva)
337
+ return;
338
+ this.selectedValue.set(this.normalizeIncoming(v));
339
+ });
340
+ // ensure internal value shape matches mode when `multiple` changes
341
+ effect(() => {
342
+ const isMulti = this.multiple();
343
+ const cur = this.selectedValue();
344
+ const normalized = isMulti ? this.toArray(cur) : this.toSingle(cur);
345
+ if (!this.shallowEqual(cur, normalized)) {
346
+ this.selectedValue.set(normalized);
347
+ // NOTE: we DO NOT emit onChange here; mode switch is a "configuration change"
348
+ }
349
+ });
350
+ }
351
+ writeValue(value) {
352
+ this.usingCva = true;
353
+ this.selectedValue.set(this.normalizeIncoming(value));
354
+ }
355
+ registerOnChange(fn) {
356
+ this.usingCva = true;
357
+ this.onChange = fn;
358
+ }
359
+ registerOnTouched(fn) {
360
+ this.onTouched = fn;
361
+ }
362
+ setDisabledState(isDisabled) {
363
+ this.isDisabled.set(isDisabled);
364
+ }
365
+ /* =====================
366
+ * Helpers
367
+ * ===================== */
368
+ currentValue = computed(() => this.selectedValue(), ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
369
+ isOptionDisabled(opt) {
370
+ return this.isDisabled() || !!opt.disabled;
371
+ }
372
+ isSelected(opt) {
373
+ const v = this.selectedValue();
374
+ if (Array.isArray(v))
375
+ return v.includes(opt.value);
376
+ return v === opt.value;
377
+ }
378
+ select(opt) {
379
+ if (this.isOptionDisabled(opt))
380
+ return;
381
+ const cur = this.selectedValue();
382
+ let next;
383
+ if (this.multiple()) {
384
+ const currArr = this.toArray(cur);
385
+ const toggled = this.toggleInArray(currArr, opt.value);
386
+ // if deselect not allowed, prevent empty
387
+ if (!this.allowDeselect() && toggled.length === 0) {
388
+ next = currArr;
389
+ }
390
+ else {
391
+ next = toggled;
392
+ }
393
+ }
394
+ else {
395
+ const currSingle = this.toSingle(cur);
396
+ next =
397
+ this.allowDeselect() && currSingle === opt.value
398
+ ? null
399
+ : opt.value;
400
+ }
401
+ this.selectedValue.set(next);
402
+ // CVA
403
+ this.onChange(next);
404
+ this.onTouched();
405
+ // direct mode output
406
+ this.valueChange.emit(next);
407
+ }
408
+ /* =====================
409
+ * Keyboard support
410
+ * ===================== */
411
+ onKeydown(ev) {
412
+ if (this.isDisabled())
413
+ return;
414
+ const opts = this.options();
415
+ if (!opts.length)
416
+ return;
417
+ const enabledIndexes = opts
418
+ .map((o, i) => ({ o, i }))
419
+ .filter(({ o }) => !this.isOptionDisabled(o))
420
+ .map(({ i }) => i);
421
+ if (!enabledIndexes.length)
422
+ return;
423
+ // for keyboard navigation we keep a "current index" concept:
424
+ // - single: selected option index
425
+ // - multi: first selected index, otherwise first enabled
426
+ const cur = this.selectedValue();
427
+ const currentIndex = (() => {
428
+ if (Array.isArray(cur)) {
429
+ const first = opts.findIndex((o) => cur.includes(o.value));
430
+ return first;
431
+ }
432
+ return opts.findIndex((o) => o.value === cur);
433
+ })();
434
+ const move = (dir) => {
435
+ ev.preventDefault();
436
+ const start = currentIndex >= 0 ? currentIndex : enabledIndexes[0];
437
+ let idx = start;
438
+ for (let step = 0; step < opts.length; step++) {
439
+ idx = (idx + dir + opts.length) % opts.length;
440
+ if (enabledIndexes.includes(idx)) {
441
+ this.select(opts[idx]);
442
+ break;
443
+ }
444
+ }
445
+ };
446
+ switch (ev.key) {
447
+ case 'ArrowRight':
448
+ case 'ArrowDown':
449
+ move(1);
450
+ break;
451
+ case 'ArrowLeft':
452
+ case 'ArrowUp':
453
+ move(-1);
454
+ break;
455
+ case 'Enter':
456
+ case ' ':
457
+ ev.preventDefault();
458
+ if (currentIndex >= 0)
459
+ this.select(opts[currentIndex]);
460
+ break;
461
+ default:
462
+ break;
463
+ }
464
+ }
465
+ /* =====================
466
+ * Classes
467
+ * ===================== */
468
+ /** Group container classes */
469
+ groupClasses = computed(() => ('flex w-full overflow-hidden rounded-md border border-border ' +
470
+ 'bg-bg ' +
471
+ (this.isDisabled() ? 'opacity-60 pointer-events-none ' : '') +
472
+ this.groupKlass()).trim(), ...(ngDevMode ? [{ debugName: "groupClasses" }] : []));
473
+ /** Button base classes */
474
+ baseBtn = computed(() => ('flex-1 px-3 py-2 text-sm font-medium text-center ' +
475
+ 'transition-colors select-none ' +
476
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ' +
477
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-background ' +
478
+ 'border-r border-border last:border-r-0 ' +
479
+ this.buttonKlass()).trim(), ...(ngDevMode ? [{ debugName: "baseBtn" }] : []));
480
+ buttonClasses(opt) {
481
+ const active = this.isSelected(opt);
482
+ const disabled = this.isOptionDisabled(opt);
483
+ const state = disabled
484
+ ? 'text-disable bg-bg ' + this.disabledButtonKlass()
485
+ : active
486
+ ? 'bg-primary text-on-primary ' + this.activeButtonKlass()
487
+ : 'bg-on-primary text-fg hover:bg-alternate-background ' +
488
+ this.inactiveButtonKlass();
489
+ return `${this.baseBtn()} ${state}`.trim();
490
+ }
491
+ /* =====================
492
+ * Normalization utilities
493
+ * ===================== */
494
+ normalizeIncoming(value) {
495
+ return this.multiple() ? this.toArray(value) : this.toSingle(value);
496
+ }
497
+ toArray(value) {
498
+ if (Array.isArray(value))
499
+ return value;
500
+ if (value === null || value === undefined)
501
+ return [];
502
+ return [value];
503
+ }
504
+ toSingle(value) {
505
+ if (Array.isArray(value))
506
+ return value[0] ?? null;
507
+ return value ?? null;
508
+ }
509
+ toggleInArray(arr, value) {
510
+ return arr.includes(value) ? arr.filter((x) => x !== value) : [...arr, value];
511
+ }
512
+ shallowEqual(a, b) {
513
+ if (a === b)
514
+ return true;
515
+ const aArr = Array.isArray(a);
516
+ const bArr = Array.isArray(b);
517
+ if (aArr !== bArr)
518
+ return false;
519
+ if (!aArr || !bArr)
520
+ return false;
521
+ // array shallow equality (order-sensitive)
522
+ if (a.length !== b.length)
523
+ return false;
524
+ for (let i = 0; i < a.length; i++) {
525
+ if (a[i] !== b[i])
526
+ return false;
527
+ }
528
+ return true;
529
+ }
530
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngButtonToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
531
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngButtonToggle, isStandalone: true, selector: "tng-button-toggle", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, allowDeselect: { classPropertyName: "allowDeselect", publicName: "allowDeselect", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, groupKlass: { classPropertyName: "groupKlass", publicName: "groupKlass", isSignal: true, isRequired: false, transformFunction: null }, buttonKlass: { classPropertyName: "buttonKlass", publicName: "buttonKlass", isSignal: true, isRequired: false, transformFunction: null }, activeButtonKlass: { classPropertyName: "activeButtonKlass", publicName: "activeButtonKlass", isSignal: true, isRequired: false, transformFunction: null }, inactiveButtonKlass: { classPropertyName: "inactiveButtonKlass", publicName: "inactiveButtonKlass", isSignal: true, isRequired: false, transformFunction: null }, disabledButtonKlass: { classPropertyName: "disabledButtonKlass", publicName: "disabledButtonKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, providers: [
532
+ {
533
+ provide: NG_VALUE_ACCESSOR,
534
+ useExisting: forwardRef(() => TngButtonToggle),
535
+ multi: true,
536
+ },
537
+ ], ngImport: i0, template: "<div [class]=\"rootKlass()\">\n <div\n role=\"group\"\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\n [class]=\"groupClasses()\"\n (keydown)=\"onKeydown($event)\"\n >\n @for (opt of options(); let i = $index; track i) {\n <button\n type=\"button\"\n role=\"button\"\n [attr.aria-pressed]=\"isSelected(opt)\"\n [attr.aria-disabled]=\"isOptionDisabled(opt) ? true : null\"\n [disabled]=\"isOptionDisabled(opt)\"\n [class]=\"buttonClasses(opt)\"\n (click)=\"select(opt)\"\n >\n {{ opt.label }}\n </button>\n }\n </div>\n</div>\n" });
538
+ }
539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngButtonToggle, decorators: [{
540
+ type: Component,
541
+ args: [{ selector: 'tng-button-toggle', standalone: true, providers: [
542
+ {
543
+ provide: NG_VALUE_ACCESSOR,
544
+ useExisting: forwardRef(() => TngButtonToggle),
545
+ multi: true,
546
+ },
547
+ ], template: "<div [class]=\"rootKlass()\">\n <div\n role=\"group\"\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\n [class]=\"groupClasses()\"\n (keydown)=\"onKeydown($event)\"\n >\n @for (opt of options(); let i = $index; track i) {\n <button\n type=\"button\"\n role=\"button\"\n [attr.aria-pressed]=\"isSelected(opt)\"\n [attr.aria-disabled]=\"isOptionDisabled(opt) ? true : null\"\n [disabled]=\"isOptionDisabled(opt)\"\n [class]=\"buttonClasses(opt)\"\n (click)=\"select(opt)\"\n >\n {{ opt.label }}\n </button>\n }\n </div>\n</div>\n" }]
548
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], allowDeselect: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDeselect", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], groupKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupKlass", required: false }] }], buttonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonKlass", required: false }] }], activeButtonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeButtonKlass", required: false }] }], inactiveButtonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inactiveButtonKlass", required: false }] }], disabledButtonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledButtonKlass", required: false }] }] } });
549
+
550
+ class TngCheckbox {
551
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
552
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
553
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
554
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
555
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
556
+ /* =====================
557
+ * Theming / class hooks (section-wise)
558
+ * ===================== */
559
+ /** Root <label> */
560
+ rootKlass = input('inline-flex items-center gap-2 cursor-pointer select-none', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
561
+ /** <input type="checkbox"> */
562
+ inputKlass = input('', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
563
+ /** Label <span> */
564
+ labelKlass = input('text-sm text-fg', ...(ngDevMode ? [{ debugName: "labelKlass" }] : []));
565
+ _value = signal(false, ...(ngDevMode ? [{ debugName: "_value" }] : []));
566
+ value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : []));
567
+ onChange = () => { };
568
+ onTouched = () => { };
569
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
570
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
571
+ writeValue(value) {
572
+ this._value.set(value ?? false);
573
+ }
574
+ registerOnChange(fn) {
575
+ this.onChange = fn;
576
+ }
577
+ registerOnTouched(fn) {
578
+ this.onTouched = fn;
579
+ }
580
+ setDisabledState(isDisabled) {
581
+ this._formDisabled.set(isDisabled);
582
+ }
583
+ /** Final classes */
584
+ classes = computed(() => (
585
+ // size + shape
586
+ `h-4 w-4 rounded ` +
587
+ // theme tokens
588
+ `border border-border bg-bg ` +
589
+ `accent-primary ` +
590
+ // focus ring
591
+ `focus-visible:outline-none ` +
592
+ `focus-visible:ring-2 focus-visible:ring-primary ` +
593
+ `focus-visible:ring-offset-2 focus-visible:ring-offset-background ` +
594
+ // disabled
595
+ `disabled:opacity-50 disabled:pointer-events-none ` +
596
+ // user override
597
+ this.inputKlass()).trim(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
598
+ onToggle(event) {
599
+ if (this.isDisabled())
600
+ return;
601
+ const checked = event.target.checked;
602
+ this._value.set(checked);
603
+ this.onChange(checked);
604
+ this.onTouched();
605
+ }
606
+ onBlur() {
607
+ this.onTouched();
608
+ }
609
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
610
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngCheckbox, isStandalone: true, selector: "tng-checkbox", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null }, labelKlass: { classPropertyName: "labelKlass", publicName: "labelKlass", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
611
+ {
612
+ provide: NG_VALUE_ACCESSOR,
613
+ useExisting: forwardRef(() => TngCheckbox),
614
+ multi: true,
615
+ },
616
+ ], ngImport: i0, template: "<label [class]=\"rootKlass()\">\n <input\n type=\"checkbox\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [checked]=\"value()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [class]=\"classes()\"\n (change)=\"onToggle($event)\"\n (blur)=\"onBlur()\"\n />\n\n @if (label()) {\n <span [class]=\"labelKlass()\">\n {{ label() }}\n </span>\n }\n</label>\n" });
617
+ }
618
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngCheckbox, decorators: [{
619
+ type: Component,
620
+ args: [{ selector: 'tng-checkbox', standalone: true, providers: [
621
+ {
622
+ provide: NG_VALUE_ACCESSOR,
623
+ useExisting: forwardRef(() => TngCheckbox),
624
+ multi: true,
625
+ },
626
+ ], template: "<label [class]=\"rootKlass()\">\n <input\n type=\"checkbox\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [checked]=\"value()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [class]=\"classes()\"\n (change)=\"onToggle($event)\"\n (blur)=\"onBlur()\"\n />\n\n @if (label()) {\n <span [class]=\"labelKlass()\">\n {{ label() }}\n </span>\n }\n</label>\n" }]
627
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }], labelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKlass", required: false }] }] } });
628
+
629
+ // chips.component.ts
630
+ class TngChips {
631
+ /* =====================
632
+ * Projected templates
633
+ * ===================== */
634
+ chipTpl;
635
+ optionTpl;
636
+ inputEl;
637
+ // Delegate list keys to OptionList
638
+ optionList;
639
+ /* =====================
640
+ * Inputs / Outputs
641
+ * ===================== */
642
+ value = input([], ...(ngDevMode ? [{ debugName: "value" }] : []));
643
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
644
+ placeholder = input('Add…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
645
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
646
+ displayWith = input((v) => String(v), ...(ngDevMode ? [{ debugName: "displayWith" }] : []));
647
+ allowFreeText = input(true, ...(ngDevMode ? [{ debugName: "allowFreeText" }] : []));
648
+ parse = input((raw) => raw, ...(ngDevMode ? [{ debugName: "parse" }] : []));
649
+ normalize = input((raw) => raw.trim(), ...(ngDevMode ? [{ debugName: "normalize" }] : []));
650
+ preventDuplicates = input(true, ...(ngDevMode ? [{ debugName: "preventDuplicates" }] : []));
651
+ search = output();
652
+ valueChange = output();
653
+ chipAdded = output();
654
+ chipRemoved = output();
655
+ closed = output();
656
+ /* =====================
657
+ * Theming
658
+ * ===================== */
659
+ rootKlass = input('relative', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
660
+ containerKlass = input([
661
+ 'w-full min-h-[42px] px-2 py-1 text-sm',
662
+ 'flex flex-wrap items-center gap-2',
663
+ 'border border-border rounded-md',
664
+ 'bg-bg text-fg',
665
+ 'focus-within:ring-2 focus-within:ring-primary',
666
+ ].join(' '), ...(ngDevMode ? [{ debugName: "containerKlass" }] : []));
667
+ chipKlass = input([
668
+ 'inline-flex items-center gap-1 px-2 py-1 rounded-md',
669
+ 'bg-alternate-background border border-border',
670
+ 'text-sm',
671
+ ].join(' '), ...(ngDevMode ? [{ debugName: "chipKlass" }] : []));
672
+ chipLabelKlass = input('truncate max-w-[200px]', ...(ngDevMode ? [{ debugName: "chipLabelKlass" }] : []));
673
+ removeButtonKlass = input('ml-1 text-disable hover:text-fg', ...(ngDevMode ? [{ debugName: "removeButtonKlass" }] : []));
674
+ inputKlass = input('flex-1 min-w-[140px] px-2 py-2 outline-none bg-transparent', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
675
+ /* =====================
676
+ * Internal State
677
+ * ===================== */
678
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : []));
679
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
680
+ focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
681
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
682
+ chipsValue = signal([], ...(ngDevMode ? [{ debugName: "chipsValue" }] : []));
683
+ usingCva = false;
684
+ onChange = () => { };
685
+ onTouched = () => { };
686
+ constructor() {
687
+ effect(() => {
688
+ this.isDisabled.set(this.disabled());
689
+ if (this.isDisabled())
690
+ this.close('programmatic');
691
+ });
692
+ effect(() => {
693
+ const v = this.value();
694
+ if (this.usingCva)
695
+ return;
696
+ this.chipsValue.set(v);
697
+ });
698
+ }
699
+ /* =====================
700
+ * ControlValueAccessor
701
+ * ===================== */
702
+ writeValue(value) {
703
+ this.usingCva = true;
704
+ this.chipsValue.set(value ?? []);
705
+ }
706
+ registerOnChange(fn) {
707
+ this.usingCva = true;
708
+ this.onChange = fn;
709
+ }
710
+ registerOnTouched(fn) {
711
+ this.onTouched = fn;
712
+ }
713
+ setDisabledState(isDisabled) {
714
+ this.isDisabled.set(isDisabled);
715
+ if (isDisabled)
716
+ this.close('programmatic');
717
+ }
718
+ /* =====================
719
+ * Template getters
720
+ * ===================== */
721
+ chips() {
722
+ return this.chipsValue();
723
+ }
724
+ placeholderText = computed(() => (this.chipsValue().length ? '' : this.placeholder()), ...(ngDevMode ? [{ debugName: "placeholderText" }] : []));
725
+ containerClasses = computed(() => (this.containerKlass() + (this.isDisabled() ? ' opacity-60 pointer-events-none' : '')).trim(), ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
726
+ /* =====================
727
+ * Helpers
728
+ * ===================== */
729
+ display(item) {
730
+ return this.displayWith()(item);
731
+ }
732
+ existsAlready(next) {
733
+ if (!this.preventDuplicates())
734
+ return false;
735
+ const nextKey = this.display(next).toLowerCase();
736
+ return this.chipsValue().some((v) => this.display(v).toLowerCase() === nextKey);
737
+ }
738
+ emitValue(next) {
739
+ this.chipsValue.set(next);
740
+ this.onChange(next);
741
+ this.valueChange.emit(next);
742
+ }
743
+ /* =====================
744
+ * Overlay open/close
745
+ * ===================== */
746
+ open(_reason) {
747
+ if (this.isDisabled())
748
+ return;
749
+ this.isOpen.set(true);
750
+ if (this.options().length) {
751
+ const current = this.focusedIndex();
752
+ if (current < 0)
753
+ this.focusedIndex.set(0);
754
+ }
755
+ else {
756
+ this.focusedIndex.set(-1);
757
+ }
758
+ }
759
+ close(reason) {
760
+ if (!this.isOpen())
761
+ return;
762
+ this.isOpen.set(false);
763
+ this.focusedIndex.set(-1);
764
+ this.closed.emit(reason);
765
+ }
766
+ onOverlayOpenChange(open) {
767
+ if (this.isDisabled()) {
768
+ this.isOpen.set(false);
769
+ return;
770
+ }
771
+ if (open)
772
+ this.open('programmatic');
773
+ else
774
+ this.close('programmatic');
775
+ }
776
+ onOverlayClosed(reason) {
777
+ this.close(reason);
778
+ }
779
+ /* =====================
780
+ * UI events
781
+ * ===================== */
782
+ onInput(ev) {
783
+ if (this.isDisabled())
784
+ return;
785
+ const raw = ev.target.value ?? '';
786
+ this.inputValue.set(raw);
787
+ this.search.emit(raw);
788
+ this.open('programmatic');
789
+ }
790
+ onBlur() {
791
+ this.onTouched();
792
+ queueMicrotask(() => {
793
+ if (document.activeElement === this.inputEl.nativeElement)
794
+ return;
795
+ this.close('blur');
796
+ });
797
+ }
798
+ onKeydown(ev) {
799
+ if (this.isDisabled())
800
+ return;
801
+ // Escape closes
802
+ if (ev.key === 'Escape' && this.isOpen()) {
803
+ ev.preventDefault();
804
+ ev.stopPropagation();
805
+ this.close('escape');
806
+ return;
807
+ }
808
+ // Backspace removes last chip when input empty
809
+ if (ev.key === 'Backspace' && !this.inputValue()) {
810
+ const current = this.chipsValue();
811
+ if (current.length) {
812
+ ev.preventDefault();
813
+ ev.stopPropagation();
814
+ this.removeAt(current.length - 1);
815
+ }
816
+ return;
817
+ }
818
+ // Enter
819
+ if (ev.key === 'Enter') {
820
+ // IMPORTANT: if open, DO NOT preventDefault here — OptionList must see a non-prevented event
821
+ if (this.isOpen()) {
822
+ ev.stopPropagation();
823
+ this.optionList?.onKeydown(ev); // OptionList will preventDefault + emit requestSelectActive
824
+ return;
825
+ }
826
+ ev.preventDefault();
827
+ ev.stopPropagation();
828
+ this.addFromInput();
829
+ return;
830
+ }
831
+ // Open on arrows when closed (prevent original cursor move),
832
+ // then delegate a *fresh* event to OptionList after mount.
833
+ if (!this.isOpen() && (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')) {
834
+ ev.preventDefault();
835
+ ev.stopPropagation();
836
+ this.open('programmatic');
837
+ requestAnimationFrame(() => {
838
+ const replay = this.cloneKeyboardEvent(ev);
839
+ this.optionList?.onKeydown(replay);
840
+ });
841
+ return;
842
+ }
843
+ if (!this.isOpen())
844
+ return;
845
+ // Delegate list-navigation keys (no preventDefault here; OptionList does it)
846
+ if (!this.isListNavigationKey(ev))
847
+ return;
848
+ ev.stopPropagation();
849
+ this.optionList?.onKeydown(ev);
850
+ }
851
+ isListNavigationKey(ev) {
852
+ switch (ev.key) {
853
+ case 'ArrowDown':
854
+ case 'ArrowUp':
855
+ case 'Home':
856
+ case 'End':
857
+ case 'PageDown':
858
+ case 'PageUp':
859
+ return true;
860
+ default:
861
+ return false;
862
+ }
863
+ }
864
+ // Used to replay arrow keys after opening (original event already defaultPrevented)
865
+ cloneKeyboardEvent(ev) {
866
+ return new KeyboardEvent(ev.type, {
867
+ key: ev.key,
868
+ code: ev.code,
869
+ location: ev.location,
870
+ repeat: ev.repeat,
871
+ ctrlKey: ev.ctrlKey,
872
+ shiftKey: ev.shiftKey,
873
+ altKey: ev.altKey,
874
+ metaKey: ev.metaKey,
875
+ bubbles: true,
876
+ cancelable: true,
877
+ });
878
+ }
879
+ /* =====================
880
+ * OptionList wiring
881
+ * ===================== */
882
+ requestSelectActive() {
883
+ const i = this.focusedIndex();
884
+ const item = this.options()[i];
885
+ if (item !== undefined)
886
+ this.selectOption(item);
887
+ }
888
+ /* =====================
889
+ * Chip actions
890
+ * ===================== */
891
+ addFromInput() {
892
+ if (this.isDisabled())
893
+ return;
894
+ if (!this.allowFreeText())
895
+ return;
896
+ const normalized = this.normalize()(this.inputValue());
897
+ if (!normalized)
898
+ return;
899
+ const nextChip = this.parse()(normalized);
900
+ if (this.existsAlready(nextChip)) {
901
+ this.inputValue.set('');
902
+ this.close('programmatic');
903
+ return;
904
+ }
905
+ const next = [...this.chipsValue(), nextChip];
906
+ this.emitValue(next);
907
+ this.chipAdded.emit(nextChip);
908
+ this.inputValue.set('');
909
+ this.close('programmatic');
910
+ queueMicrotask(() => this.inputEl.nativeElement.focus());
911
+ }
912
+ selectOption(item) {
913
+ if (this.isDisabled())
914
+ return;
915
+ if (this.existsAlready(item)) {
916
+ this.inputValue.set('');
917
+ this.close('selection');
918
+ queueMicrotask(() => this.inputEl.nativeElement.focus());
919
+ return;
920
+ }
921
+ const next = [...this.chipsValue(), item];
922
+ this.emitValue(next);
923
+ this.chipAdded.emit(item);
924
+ this.inputValue.set('');
925
+ this.close('selection');
926
+ queueMicrotask(() => this.inputEl.nativeElement.focus());
927
+ }
928
+ removeAt(index) {
929
+ if (this.isDisabled())
930
+ return;
931
+ const current = this.chipsValue();
932
+ if (index < 0 || index >= current.length)
933
+ return;
934
+ const removed = current[index];
935
+ const next = current.filter((_, i) => i !== index);
936
+ this.emitValue(next);
937
+ this.chipRemoved.emit(removed);
938
+ }
939
+ clearAll() {
940
+ if (this.isDisabled())
941
+ return;
942
+ const current = this.chipsValue();
943
+ if (!current.length)
944
+ return;
945
+ for (const item of current)
946
+ this.chipRemoved.emit(item);
947
+ this.emitValue([]);
948
+ }
949
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
950
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngChips, isStandalone: true, selector: "tng-chips", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, allowFreeText: { classPropertyName: "allowFreeText", publicName: "allowFreeText", isSignal: true, isRequired: false, transformFunction: null }, parse: { classPropertyName: "parse", publicName: "parse", isSignal: true, isRequired: false, transformFunction: null }, normalize: { classPropertyName: "normalize", publicName: "normalize", isSignal: true, isRequired: false, transformFunction: null }, preventDuplicates: { classPropertyName: "preventDuplicates", publicName: "preventDuplicates", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, containerKlass: { classPropertyName: "containerKlass", publicName: "containerKlass", isSignal: true, isRequired: false, transformFunction: null }, chipKlass: { classPropertyName: "chipKlass", publicName: "chipKlass", isSignal: true, isRequired: false, transformFunction: null }, chipLabelKlass: { classPropertyName: "chipLabelKlass", publicName: "chipLabelKlass", isSignal: true, isRequired: false, transformFunction: null }, removeButtonKlass: { classPropertyName: "removeButtonKlass", publicName: "removeButtonKlass", isSignal: true, isRequired: false, transformFunction: null }, inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search", valueChange: "valueChange", chipAdded: "chipAdded", chipRemoved: "chipRemoved", closed: "closed" }, providers: [
951
+ {
952
+ provide: NG_VALUE_ACCESSOR,
953
+ useExisting: forwardRef(() => TngChips),
954
+ multi: true,
955
+ },
956
+ ], queries: [{ propertyName: "chipTpl", first: true, predicate: ["chipTpl"], descendants: true, read: TemplateRef }, { propertyName: "optionTpl", first: true, predicate: ["optionTpl"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }, { propertyName: "optionList", first: true, predicate: TngOptionList, descendants: true }], ngImport: i0, template: "<!-- chips.component.html -->\n<div [class]=\"rootKlass()\">\n <!-- Chips row + input -->\n <div [class]=\"containerClasses()\">\n @for (chip of chips(); let i = $index; track i) {\n <span [class]=\"chipKlass()\">\n @if (chipTpl) {\n <ng-container\n [ngTemplateOutlet]=\"chipTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: chip, index: i }\"\n />\n } @else {\n <span [class]=\"chipLabelKlass()\">{{ display(chip) }}</span>\n }\n\n <button\n type=\"button\"\n [class]=\"removeButtonKlass()\"\n [disabled]=\"isDisabled()\"\n (click)=\"removeAt(i)\"\n aria-label=\"Remove chip\"\n >\n \u00D7\n </button>\n </span>\n }\n\n <input\n #inputEl\n type=\"text\"\n [class]=\"inputKlass()\"\n [value]=\"inputValue()\"\n [placeholder]=\"placeholderText()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"open('programmatic')\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n />\n </div>\n\n <!-- Suggestions overlay -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"inputEl\"\n placement=\"bottom-start\"\n width=\"anchor\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel>\n <tng-option-list\n [items]=\"options()\"\n [activeIndex]=\"focusedIndex()\"\n [displayWith]=\"displayWith()\"\n [optionTemplate]=\"optionTpl ?? null\"\n [loop]=\"false\"\n [typeahead]=\"false\"\n (activeIndexChange)=\"focusedIndex.set($event)\"\n (requestSelectActive)=\"requestSelectActive()\"\n (optionMouseDown)=\"selectOption($event.item)\"\n (optionHover)=\"focusedIndex.set($event)\"\n />\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TngConnectedOverlay, selector: "tng-connected-overlay", inputs: ["open", "anchor", "placement", "offset", "width", "closeOnOutsideClick", "closeOnInsideClick", "closeOnEscape", "hasBackdrop", "backdropClass"], outputs: ["opened", "closed", "backdropClick"] }, { kind: "component", type: TngOverlayPanel, selector: "tng-overlay-panel", inputs: ["klass", "modal", "role", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "restoreFocus", "autoCapture", "deferCaptureElements"] }, { kind: "component", type: TngOptionList, selector: "tng-option-list", inputs: ["modal", "panelKlass", "restoreFocus", "autoCapture", "deferCaptureElements", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "tabindex", "optionTemplate", "items", "activeIndex", "displayWith", "emptyText", "containerKlass", "optionKlass", "optionActiveKlass", "optionInactiveKlass", "emptyKlass", "propagateKeys", "keyboard", "loop", "selectOnEnter", "typeahead", "typeaheadMode", "typeaheadResetMs", "pageJumpSize"], outputs: ["optionMouseDown", "optionHover", "activeIndexChange", "requestSelectActive", "requestTypeaheadMatch", "keyStroke"] }, { kind: "component", type: TngOverlayRef, selector: "tng-overlay-ref", inputs: ["open"], outputs: ["openChange", "opened", "closed"], exportAs: ["tngOverlayRef"] }] });
957
+ }
958
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngChips, decorators: [{
959
+ type: Component,
960
+ args: [{ selector: 'tng-chips', standalone: true, imports: [
961
+ NgTemplateOutlet,
962
+ TngConnectedOverlay,
963
+ TngOverlayPanel,
964
+ TngOptionList,
965
+ TngOverlayRef,
966
+ ], providers: [
967
+ {
968
+ provide: NG_VALUE_ACCESSOR,
969
+ useExisting: forwardRef(() => TngChips),
970
+ multi: true,
971
+ },
972
+ ], template: "<!-- chips.component.html -->\n<div [class]=\"rootKlass()\">\n <!-- Chips row + input -->\n <div [class]=\"containerClasses()\">\n @for (chip of chips(); let i = $index; track i) {\n <span [class]=\"chipKlass()\">\n @if (chipTpl) {\n <ng-container\n [ngTemplateOutlet]=\"chipTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: chip, index: i }\"\n />\n } @else {\n <span [class]=\"chipLabelKlass()\">{{ display(chip) }}</span>\n }\n\n <button\n type=\"button\"\n [class]=\"removeButtonKlass()\"\n [disabled]=\"isDisabled()\"\n (click)=\"removeAt(i)\"\n aria-label=\"Remove chip\"\n >\n \u00D7\n </button>\n </span>\n }\n\n <input\n #inputEl\n type=\"text\"\n [class]=\"inputKlass()\"\n [value]=\"inputValue()\"\n [placeholder]=\"placeholderText()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"open('programmatic')\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n />\n </div>\n\n <!-- Suggestions overlay -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"inputEl\"\n placement=\"bottom-start\"\n width=\"anchor\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel>\n <tng-option-list\n [items]=\"options()\"\n [activeIndex]=\"focusedIndex()\"\n [displayWith]=\"displayWith()\"\n [optionTemplate]=\"optionTpl ?? null\"\n [loop]=\"false\"\n [typeahead]=\"false\"\n (activeIndexChange)=\"focusedIndex.set($event)\"\n (requestSelectActive)=\"requestSelectActive()\"\n (optionMouseDown)=\"selectOption($event.item)\"\n (optionHover)=\"focusedIndex.set($event)\"\n />\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n" }]
973
+ }], ctorParameters: () => [], propDecorators: { chipTpl: [{
974
+ type: ContentChild,
975
+ args: ['chipTpl', { read: TemplateRef }]
976
+ }], optionTpl: [{
977
+ type: ContentChild,
978
+ args: ['optionTpl', { read: TemplateRef }]
979
+ }], inputEl: [{
980
+ type: ViewChild,
981
+ args: ['inputEl', { static: true }]
982
+ }], optionList: [{
983
+ type: ViewChild,
984
+ args: [TngOptionList]
985
+ }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], allowFreeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowFreeText", required: false }] }], parse: [{ type: i0.Input, args: [{ isSignal: true, alias: "parse", required: false }] }], normalize: [{ type: i0.Input, args: [{ isSignal: true, alias: "normalize", required: false }] }], preventDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventDuplicates", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], chipAdded: [{ type: i0.Output, args: ["chipAdded"] }], chipRemoved: [{ type: i0.Output, args: ["chipRemoved"] }], closed: [{ type: i0.Output, args: ["closed"] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], containerKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerKlass", required: false }] }], chipKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipKlass", required: false }] }], chipLabelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipLabelKlass", required: false }] }], removeButtonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeButtonKlass", required: false }] }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }] } });
986
+
987
+ class TngNativeDateAdapter {
988
+ now() {
989
+ return new Date();
990
+ }
991
+ from(value) {
992
+ const d = new Date(value);
993
+ return this.isValid(d) ? d : null;
994
+ }
995
+ isValid(date) {
996
+ return date instanceof Date && !Number.isNaN(date.getTime());
997
+ }
998
+ startOfDay(date) {
999
+ const d = new Date(date);
1000
+ d.setHours(0, 0, 0, 0);
1001
+ return d;
1002
+ }
1003
+ year(date) { return date.getFullYear(); }
1004
+ month(date) { return date.getMonth(); }
1005
+ date(date) { return date.getDate(); }
1006
+ day(date) { return date.getDay(); }
1007
+ daysInMonth(date) {
1008
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
1009
+ }
1010
+ setYear(date, year) {
1011
+ const d = new Date(date);
1012
+ d.setFullYear(year);
1013
+ return d;
1014
+ }
1015
+ setMonth(date, month0) {
1016
+ const d = new Date(date);
1017
+ d.setMonth(month0);
1018
+ return d;
1019
+ }
1020
+ setDate(date, dayOfMonth) {
1021
+ const d = new Date(date);
1022
+ d.setDate(dayOfMonth);
1023
+ return d;
1024
+ }
1025
+ addDays(date, days) {
1026
+ const d = new Date(date);
1027
+ d.setDate(d.getDate() + days);
1028
+ return d;
1029
+ }
1030
+ addMonths(date, months) {
1031
+ const d = new Date(date);
1032
+ d.setMonth(d.getMonth() + months);
1033
+ return d;
1034
+ }
1035
+ startOfMonth(date) {
1036
+ return this.startOfDay(new Date(this.year(date), this.month(date), 1));
1037
+ }
1038
+ endOfMonth(date) {
1039
+ return this.startOfDay(new Date(this.year(date), this.month(date) + 1, 0));
1040
+ }
1041
+ startOfYear(date) {
1042
+ return this.startOfDay(new Date(this.year(date), 0, 1));
1043
+ }
1044
+ endOfYear(date) {
1045
+ return this.startOfDay(new Date(this.year(date), 11, 31));
1046
+ }
1047
+ isSameDay(a, b) {
1048
+ return this.year(a) === this.year(b) && this.month(a) === this.month(b) && this.date(a) === this.date(b);
1049
+ }
1050
+ isBeforeDay(a, b) {
1051
+ return this.startOfDay(a).getTime() < this.startOfDay(b).getTime();
1052
+ }
1053
+ isAfterDay(a, b) {
1054
+ return this.startOfDay(a).getTime() > this.startOfDay(b).getTime();
1055
+ }
1056
+ format(date, format, _locale) {
1057
+ const dd = String(this.date(date)).padStart(2, '0');
1058
+ const mm = String(this.month(date) + 1).padStart(2, '0');
1059
+ const yyyy = String(this.year(date));
1060
+ if (format === 'DD/MM/YYYY')
1061
+ return `${dd}/${mm}/${yyyy}`;
1062
+ if (format === 'MM/DD/YYYY')
1063
+ return `${mm}/${dd}/${yyyy}`;
1064
+ if (format === 'DD-MM-YYYY')
1065
+ return `${dd}-${mm}-${yyyy}`;
1066
+ if (format === 'MM-DD-YYYY')
1067
+ return `${mm}-${dd}-${yyyy}`;
1068
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1069
+ if (format === 'DD MMM YYYY')
1070
+ return `${dd} ${months[this.month(date)]} ${yyyy}`;
1071
+ return `${dd}/${mm}/${yyyy}`;
1072
+ }
1073
+ parse(text, format, _locale) {
1074
+ const s = text.trim();
1075
+ if (!s)
1076
+ return null;
1077
+ if (format === 'DD/MM/YYYY') {
1078
+ const m = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
1079
+ if (!m)
1080
+ return null;
1081
+ const dd = Number(m[1]);
1082
+ const mm = Number(m[2]);
1083
+ const yyyy = Number(m[3]);
1084
+ const d = new Date(yyyy, mm - 1, dd);
1085
+ if (!this.isValid(d))
1086
+ return null;
1087
+ if (this.month(d) !== mm - 1 || this.date(d) !== dd)
1088
+ return null;
1089
+ return this.startOfDay(d);
1090
+ }
1091
+ if (format === 'MM/DD/YYYY') {
1092
+ const m = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
1093
+ if (!m)
1094
+ return null;
1095
+ const mm = Number(m[1]);
1096
+ const dd = Number(m[2]);
1097
+ const yyyy = Number(m[3]);
1098
+ const d = new Date(yyyy, mm - 1, dd);
1099
+ if (!this.isValid(d))
1100
+ return null;
1101
+ if (this.month(d) !== mm - 1 || this.date(d) !== dd)
1102
+ return null;
1103
+ return this.startOfDay(d);
1104
+ }
1105
+ if (format === 'DD-MM-YYYY') {
1106
+ const m = s.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/);
1107
+ if (!m)
1108
+ return null;
1109
+ const dd = Number(m[1]);
1110
+ const mm = Number(m[2]);
1111
+ const yyyy = Number(m[3]);
1112
+ const d = new Date(yyyy, mm - 1, dd);
1113
+ if (!this.isValid(d))
1114
+ return null;
1115
+ if (this.month(d) !== mm - 1 || this.date(d) !== dd)
1116
+ return null;
1117
+ return this.startOfDay(d);
1118
+ }
1119
+ if (format === 'MM-DD-YYYY') {
1120
+ const m = s.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/);
1121
+ if (!m)
1122
+ return null;
1123
+ const mm = Number(m[1]);
1124
+ const dd = Number(m[2]);
1125
+ const yyyy = Number(m[3]);
1126
+ const d = new Date(yyyy, mm - 1, dd);
1127
+ if (!this.isValid(d))
1128
+ return null;
1129
+ if (this.month(d) !== mm - 1 || this.date(d) !== dd)
1130
+ return null;
1131
+ return this.startOfDay(d);
1132
+ }
1133
+ if (format === 'DD MMM YYYY') {
1134
+ const m = s.match(/^(\d{1,2})\s+([A-Za-z]{3})\s+(\d{4})$/);
1135
+ if (!m)
1136
+ return null;
1137
+ const dd = Number(m[1]);
1138
+ const mon = m[2].toLowerCase();
1139
+ const yyyy = Number(m[3]);
1140
+ const map = {
1141
+ jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5,
1142
+ jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
1143
+ };
1144
+ const month0 = map[mon];
1145
+ if (month0 == null)
1146
+ return null;
1147
+ const d = new Date(yyyy, month0, dd);
1148
+ if (this.month(d) !== month0 || this.date(d) !== dd)
1149
+ return null;
1150
+ return this.startOfDay(d);
1151
+ }
1152
+ return null;
1153
+ }
1154
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngNativeDateAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1155
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngNativeDateAdapter, providedIn: 'root' });
1156
+ }
1157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngNativeDateAdapter, decorators: [{
1158
+ type: Injectable,
1159
+ args: [{ providedIn: 'root' }]
1160
+ }] });
1161
+
1162
+ const TNG_DATE_ADAPTER = new InjectionToken('TNG_DATE_ADAPTER');
1163
+
1164
+ const DATE_TOKENS = ['YYYY', 'YY', 'MMMM', 'MMM', 'MM', 'M', 'DD', 'D'];
1165
+ function tokenizeFormat(fmt) {
1166
+ const parts = [];
1167
+ let i = 0;
1168
+ while (i < fmt.length) {
1169
+ let matched = null;
1170
+ for (const t of DATE_TOKENS) {
1171
+ if (fmt.startsWith(t, i)) {
1172
+ matched = t;
1173
+ break;
1174
+ }
1175
+ }
1176
+ if (matched) {
1177
+ parts.push({ kind: 'token', token: matched });
1178
+ i += matched.length;
1179
+ }
1180
+ else {
1181
+ parts.push({ kind: 'lit', text: fmt[i] });
1182
+ i += 1;
1183
+ }
1184
+ }
1185
+ // merge adjacent literals
1186
+ const merged = [];
1187
+ for (const p of parts) {
1188
+ const last = merged[merged.length - 1];
1189
+ if (p.kind === 'lit' && last?.kind === 'lit')
1190
+ last.text += p.text;
1191
+ else
1192
+ merged.push(p);
1193
+ }
1194
+ return merged;
1195
+ }
1196
+ function normalizeFormat(fmt) {
1197
+ // Keep only date tokens in order, drop literals.
1198
+ return tokenizeFormat(fmt)
1199
+ .filter((p) => p.kind === 'token')
1200
+ .map((p) => p.token)
1201
+ .join('');
1202
+ }
1203
+ function normalizeInputAlphaNum(raw) {
1204
+ return raw.replace(/[^a-zA-Z0-9]/g, '');
1205
+ }
1206
+ function hasLetters(raw) {
1207
+ return /[a-zA-Z]/.test(raw);
1208
+ }
1209
+ function isLikelyPartial(raw) {
1210
+ // Partial if:
1211
+ // - ends with a separator-like char
1212
+ // - contains letters but not enough letters for month names yet
1213
+ // - too short overall
1214
+ const t = raw.trim();
1215
+ if (!t)
1216
+ return true;
1217
+ if (/[-/.\s]$/.test(t))
1218
+ return true;
1219
+ if (hasLetters(t) && t.length < 6)
1220
+ return true;
1221
+ if (normalizeInputAlphaNum(t).length < 6)
1222
+ return true;
1223
+ return false;
1224
+ }
1225
+ /**
1226
+ * Smart parse:
1227
+ * 1) Try strict parse of raw input using displayFormat (supports separators, MMM, etc.)
1228
+ * 2) If fails, try normalized input + normalized format (removes literals),
1229
+ * e.g. "15-May-1981" -> "15May1981" with "DDMMMYYYY"
1230
+ *
1231
+ * Returns partial/invalid/valid.
1232
+ */
1233
+ function parseSmartDate(raw, displayFormat, adapter, locale) {
1234
+ const text = raw.trim();
1235
+ if (!text)
1236
+ return { kind: 'empty' };
1237
+ // 1) strict as-is parse
1238
+ const direct = adapter.parse(text, displayFormat, locale);
1239
+ if (direct && adapter.isValid(direct)) {
1240
+ return { kind: 'valid', date: adapter.startOfDay(direct) };
1241
+ }
1242
+ // If user is still typing, don't mark invalid aggressively
1243
+ if (isLikelyPartial(text))
1244
+ return { kind: 'partial' };
1245
+ // 2) normalized parse (strip literals)
1246
+ const normFmt = normalizeFormat(displayFormat);
1247
+ const normInput = normalizeInputAlphaNum(text);
1248
+ if (!normInput)
1249
+ return { kind: 'empty' };
1250
+ const normalized = adapter.parse(normInput, normFmt, locale);
1251
+ if (normalized && adapter.isValid(normalized)) {
1252
+ return { kind: 'valid', date: adapter.startOfDay(normalized) };
1253
+ }
1254
+ // If contains letters, assume month still being typed (Ma, May, etc.)
1255
+ if (hasLetters(text) && text.length < 10)
1256
+ return { kind: 'partial' };
1257
+ return { kind: 'invalid' };
1258
+ }
1259
+ /**
1260
+ * Format a Date using displayFormat and locale.
1261
+ */
1262
+ function formatDate(date, displayFormat, adapter, locale) {
1263
+ return adapter.format(date, displayFormat, locale);
1264
+ }
1265
+ /**
1266
+ * Caret preservation:
1267
+ * after we programmatically replace input.value, reposition caret to a logical place.
1268
+ *
1269
+ * Strategy:
1270
+ * - Count alphanumeric characters before caret in the "before" string.
1271
+ * - Find the position in "after" string that has the same count of alphanumerics.
1272
+ */
1273
+ function computeNextCaretPos(before, beforeCaret, after) {
1274
+ const countAlphaNum = (s) => (s.match(/[a-zA-Z0-9]/g) ?? []).length;
1275
+ const beforeLeft = before.slice(0, beforeCaret);
1276
+ const targetCount = countAlphaNum(beforeLeft);
1277
+ if (targetCount === 0)
1278
+ return 0;
1279
+ let seen = 0;
1280
+ for (let i = 0; i < after.length; i++) {
1281
+ if (/[a-zA-Z0-9]/.test(after[i]))
1282
+ seen++;
1283
+ if (seen >= targetCount)
1284
+ return i + 1;
1285
+ }
1286
+ return after.length;
1287
+ }
1288
+
1289
+ const MONTHS = [
1290
+ { index: 0, label: 'Jan' },
1291
+ { index: 1, label: 'Feb' },
1292
+ { index: 2, label: 'Mar' },
1293
+ { index: 3, label: 'Apr' },
1294
+ { index: 4, label: 'May' },
1295
+ { index: 5, label: 'Jun' },
1296
+ { index: 6, label: 'Jul' },
1297
+ { index: 7, label: 'Aug' },
1298
+ { index: 8, label: 'Sep' },
1299
+ { index: 9, label: 'Oct' },
1300
+ { index: 10, label: 'Nov' },
1301
+ { index: 11, label: 'Dec' },
1302
+ ];
1303
+ const YEAR_WINDOW_SIZE = 10;
1304
+ const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
1305
+ class TngDatepicker {
1306
+ /* =====================
1307
+ * Inputs
1308
+ * ===================== */
1309
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
1310
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
1311
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1312
+ displayFormat = input('DD/MM/YYYY', ...(ngDevMode ? [{ debugName: "displayFormat" }] : []));
1313
+ previewFormat = input('DD MMM YYYY', ...(ngDevMode ? [{ debugName: "previewFormat" }] : []));
1314
+ /** Optional locale for month names (e.g. 'en', 'fr', 'de', 'ml') */
1315
+ locale = input(null, ...(ngDevMode ? [{ debugName: "locale" }] : []));
1316
+ dateAdapter = input(null, ...(ngDevMode ? [{ debugName: "dateAdapter" }] : []));
1317
+ /** Klass applied to the text input element */
1318
+ inputKlass = input('', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
1319
+ toggleKlass = input('', ...(ngDevMode ? [{ debugName: "toggleKlass" }] : []));
1320
+ hostKlass = input('', ...(ngDevMode ? [{ debugName: "hostKlass" }] : []));
1321
+ disabledKlass = input('', ...(ngDevMode ? [{ debugName: "disabledKlass" }] : []));
1322
+ fieldKlass = input('', ...(ngDevMode ? [{ debugName: "fieldKlass" }] : []));
1323
+ toggleIconKlass = input('', ...(ngDevMode ? [{ debugName: "toggleIconKlass" }] : []));
1324
+ panelKlass = input('', ...(ngDevMode ? [{ debugName: "panelKlass" }] : []));
1325
+ panelFrameKlass = input('', ...(ngDevMode ? [{ debugName: "panelFrameKlass" }] : []));
1326
+ panelLayoutKlass = input('', ...(ngDevMode ? [{ debugName: "panelLayoutKlass" }] : []));
1327
+ monthRailKlass = input('', ...(ngDevMode ? [{ debugName: "monthRailKlass" }] : []));
1328
+ monthListKlass = input('', ...(ngDevMode ? [{ debugName: "monthListKlass" }] : []));
1329
+ monthItemKlass = input('', ...(ngDevMode ? [{ debugName: "monthItemKlass" }] : []));
1330
+ calendarKlass = input('', ...(ngDevMode ? [{ debugName: "calendarKlass" }] : []));
1331
+ titleKlass = input('', ...(ngDevMode ? [{ debugName: "titleKlass" }] : []));
1332
+ weekdayRowKlass = input('', ...(ngDevMode ? [{ debugName: "weekdayRowKlass" }] : []));
1333
+ weekdayCellKlass = input('', ...(ngDevMode ? [{ debugName: "weekdayCellKlass" }] : []));
1334
+ dayGridKlass = input('', ...(ngDevMode ? [{ debugName: "dayGridKlass" }] : []));
1335
+ dayCellKlass = input('', ...(ngDevMode ? [{ debugName: "dayCellKlass" }] : []));
1336
+ previewTextKlass = input('', ...(ngDevMode ? [{ debugName: "previewTextKlass" }] : []));
1337
+ actionBarKlass = input('', ...(ngDevMode ? [{ debugName: "actionBarKlass" }] : []));
1338
+ cancelKlass = input('', ...(ngDevMode ? [{ debugName: "cancelKlass" }] : []));
1339
+ confirmKlass = input('', ...(ngDevMode ? [{ debugName: "confirmKlass" }] : []));
1340
+ yearRailKlass = input('', ...(ngDevMode ? [{ debugName: "yearRailKlass" }] : []));
1341
+ yearNavPrevKlass = input('', ...(ngDevMode ? [{ debugName: "yearNavPrevKlass" }] : []));
1342
+ yearListKlass = input('', ...(ngDevMode ? [{ debugName: "yearListKlass" }] : []));
1343
+ yearItemKlass = input('', ...(ngDevMode ? [{ debugName: "yearItemKlass" }] : []));
1344
+ yearNavNextKlass = input('', ...(ngDevMode ? [{ debugName: "yearNavNextKlass" }] : []));
1345
+ inputEl;
1346
+ /* =====================
1347
+ * Overlay state
1348
+ * ===================== */
1349
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1350
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
1351
+ injectedAdapter = inject(TNG_DATE_ADAPTER, { optional: true });
1352
+ nativeAdapter = inject(TngNativeDateAdapter);
1353
+ adapter = computed(() => this.dateAdapter() ?? this.injectedAdapter ?? this.nativeAdapter, ...(ngDevMode ? [{ debugName: "adapter" }] : []));
1354
+ /* =====================
1355
+ * Klass (input theming)
1356
+ * ===================== */
1357
+ inputKlassFinal = computed(() => this.join('w-full rounded-md border border-border bg-bg px-3 py-2 pr-10 text-sm', 'focus:outline-none focus:ring-2 focus:ring-primary', this.inputKlass()), ...(ngDevMode ? [{ debugName: "inputKlassFinal" }] : []));
1358
+ toggleKlassFinal = computed(() => this.join('absolute inset-y-0 right-0 flex w-10 items-center justify-center', 'rounded-r-md text-fg hover:bg-alternate-background', this.toggleKlass()), ...(ngDevMode ? [{ debugName: "toggleKlassFinal" }] : []));
1359
+ hostKlassFinal = computed(() => this.join('relative', this.hostKlass()), ...(ngDevMode ? [{ debugName: "hostKlassFinal" }] : []));
1360
+ disabledKlassFinal = computed(() => this.join(this.disabledKlass()), ...(ngDevMode ? [{ debugName: "disabledKlassFinal" }] : []));
1361
+ fieldKlassFinal = computed(() => this.join('relative', this.fieldKlass()), ...(ngDevMode ? [{ debugName: "fieldKlassFinal" }] : []));
1362
+ toggleIconKlassFinal = computed(() => this.join('h-5 w-5', this.toggleIconKlass()), ...(ngDevMode ? [{ debugName: "toggleIconKlassFinal" }] : []));
1363
+ panelKlassFinal = computed(() => this.join('w-[372px] h-[322px] max-h-[322px] p-0', this.panelKlass()), ...(ngDevMode ? [{ debugName: "panelKlassFinal" }] : []));
1364
+ panelFrameKlassFinal = computed(() => this.join('w-[370px] h-[320px] overflow-hidden rounded-lg', 'border border-border bg-bg shadow-lg', this.panelFrameKlass()), ...(ngDevMode ? [{ debugName: "panelFrameKlassFinal" }] : []));
1365
+ panelLayoutKlassFinal = computed(() => this.join('grid h-full grid-cols-[64px_1fr_64px]', this.panelLayoutKlass()), ...(ngDevMode ? [{ debugName: "panelLayoutKlassFinal" }] : []));
1366
+ monthRailKlassFinal = computed(() => this.join('bg-bg p-1 text-fg', this.monthRailKlass()), ...(ngDevMode ? [{ debugName: "monthRailKlassFinal" }] : []));
1367
+ monthListKlassFinal = computed(() => this.join('space-y-0.5', this.monthListKlass()), ...(ngDevMode ? [{ debugName: "monthListKlassFinal" }] : []));
1368
+ monthItemKlassFinal = computed(() => this.join('w-full rounded px-1 py-0.5 text-[11px] font-semibold transition', this.monthItemKlass()), ...(ngDevMode ? [{ debugName: "monthItemKlassFinal" }] : []));
1369
+ calendarKlassFinal = computed(() => this.join('flex h-full flex-col p-2 border-r border-l border-border', this.calendarKlass()), ...(ngDevMode ? [{ debugName: "calendarKlassFinal" }] : []));
1370
+ titleKlassFinal = computed(() => this.join('mb-1 text-center text-sm font-semibold', this.titleKlass()), ...(ngDevMode ? [{ debugName: "titleKlassFinal" }] : []));
1371
+ weekdayRowKlassFinal = computed(() => this.join('grid grid-cols-7 gap-0.5 text-[10px] font-semibold text-disable', this.weekdayRowKlass()), ...(ngDevMode ? [{ debugName: "weekdayRowKlassFinal" }] : []));
1372
+ weekdayCellKlassFinal = computed(() => this.join('text-center', this.weekdayCellKlass()), ...(ngDevMode ? [{ debugName: "weekdayCellKlassFinal" }] : []));
1373
+ dayGridKlassFinal = computed(() => this.join('mt-1 grid grid-cols-7 gap-0.5', this.dayGridKlass()), ...(ngDevMode ? [{ debugName: "dayGridKlassFinal" }] : []));
1374
+ dayCellKlassFinal = computed(() => this.join('h-7 rounded text-[11px] transition', this.dayCellKlass()), ...(ngDevMode ? [{ debugName: "dayCellKlassFinal" }] : []));
1375
+ previewTextKlassFinal = computed(() => this.join('pt-2 text-center text-[11px] text-disable', this.previewTextKlass()), ...(ngDevMode ? [{ debugName: "previewTextKlassFinal" }] : []));
1376
+ actionBarKlassFinal = computed(() => this.join('mt-auto flex items-center justify-end gap-2 pt-4', this.actionBarKlass()), ...(ngDevMode ? [{ debugName: "actionBarKlassFinal" }] : []));
1377
+ cancelKlassFinal = computed(() => this.join('rounded-md border border-border bg-bg', 'px-3 py-1.5 text-[11px] font-semibold text-fg', 'shadow-sm hover:bg-alternate-background active:translate-y-[1px]', this.cancelKlass()), ...(ngDevMode ? [{ debugName: "cancelKlassFinal" }] : []));
1378
+ confirmKlassFinal = computed(() => this.join('rounded-md bg-fg', 'px-3 py-1.5 text-[11px] font-semibold text-bg', 'shadow-sm hover:opacity-95 active:translate-y-[1px]', this.confirmKlass()), ...(ngDevMode ? [{ debugName: "confirmKlassFinal" }] : []));
1379
+ yearRailKlassFinal = computed(() => this.join('bg-bg p-1 text-fg flex flex-col', this.yearRailKlass()), ...(ngDevMode ? [{ debugName: "yearRailKlassFinal" }] : []));
1380
+ yearNavPrevKlassFinal = computed(() => this.join('mx-auto mb-1 flex h-6 w-6 items-center justify-center', 'rounded bg-bg/10 text-[12px] hover:bg-bg/15 disabled:opacity-40', this.yearNavPrevKlass()), ...(ngDevMode ? [{ debugName: "yearNavPrevKlassFinal" }] : []));
1381
+ yearListKlassFinal = computed(() => this.join('space-y-0.5', this.yearListKlass()), ...(ngDevMode ? [{ debugName: "yearListKlassFinal" }] : []));
1382
+ yearItemKlassFinal = computed(() => this.join('w-full rounded px-1 py-0.5 text-[11px] font-semibold transition', this.yearItemKlass()), ...(ngDevMode ? [{ debugName: "yearItemKlassFinal" }] : []));
1383
+ yearNavNextKlassFinal = computed(() => this.join('mx-auto mt-1 flex h-6 w-6 items-center justify-center', 'rounded bg-bg/10 text-[12px] hover:bg-bg/15 disabled:opacity-40', this.yearNavNextKlass()), ...(ngDevMode ? [{ debugName: "yearNavNextKlassFinal" }] : []));
1384
+ join(...parts) {
1385
+ return parts.map((p) => (p ?? '').trim()).filter(Boolean).join(' ');
1386
+ }
1387
+ /* =====================
1388
+ * Form values
1389
+ * ===================== */
1390
+ value = null;
1391
+ draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : []));
1392
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : []));
1393
+ onChange = () => { };
1394
+ onTouched = () => { };
1395
+ /* =====================
1396
+ * UI constants
1397
+ * ===================== */
1398
+ months = MONTHS;
1399
+ weekdays = WEEKDAYS;
1400
+ focusedDate = signal(null, ...(ngDevMode ? [{ debugName: "focusedDate" }] : []));
1401
+ /* =====================
1402
+ * Derived bounds
1403
+ * ===================== */
1404
+ minD = computed(() => this.min() ? this.adapter().startOfDay(this.min()) : null, ...(ngDevMode ? [{ debugName: "minD" }] : []));
1405
+ maxD = computed(() => this.max() ? this.adapter().startOfDay(this.max()) : null, ...(ngDevMode ? [{ debugName: "maxD" }] : []));
1406
+ /* =====================
1407
+ * View base
1408
+ * ===================== */
1409
+ view = computed(() => (this.draft() ?? this.value ?? this.adapter().startOfDay(new Date())), ...(ngDevMode ? [{ debugName: "view" }] : []));
1410
+ selectedYear = computed(() => this.adapter().year(this.view()), ...(ngDevMode ? [{ debugName: "selectedYear" }] : []));
1411
+ selectedMonth = computed(() => this.adapter().month(this.view()), ...(ngDevMode ? [{ debugName: "selectedMonth" }] : []));
1412
+ yearBase = signal(Math.floor(this.adapter().year(new Date()) / YEAR_WINDOW_SIZE) * YEAR_WINDOW_SIZE, ...(ngDevMode ? [{ debugName: "yearBase" }] : []));
1413
+ years = computed(() => {
1414
+ const base = this.yearBase();
1415
+ return Array.from({ length: YEAR_WINDOW_SIZE }, (_, i) => base + i);
1416
+ }, ...(ngDevMode ? [{ debugName: "years" }] : []));
1417
+ previewLabel = computed(() => {
1418
+ const d = this.draft() ?? this.value;
1419
+ if (!d)
1420
+ return '—';
1421
+ const loc = this.locale();
1422
+ return loc ? this.adapter().format(d, this.previewFormat(), loc) : this.adapter().format(d, this.previewFormat());
1423
+ }, ...(ngDevMode ? [{ debugName: "previewLabel" }] : []));
1424
+ calendarCells = computed(() => {
1425
+ const v = this.adapter().startOfMonth(this.view());
1426
+ const startDow = this.adapter().day(v);
1427
+ const gridStart = this.adapter().addDays(v, -startDow);
1428
+ const cells = [];
1429
+ for (let i = 0; i < 42; i++) {
1430
+ const d = this.adapter().addDays(gridStart, i);
1431
+ cells.push({
1432
+ date: d,
1433
+ label: String(this.adapter().date(d)),
1434
+ isOutsideMonth: this.adapter().month(d) !== this.adapter().month(v),
1435
+ });
1436
+ }
1437
+ return cells;
1438
+ }, ...(ngDevMode ? [{ debugName: "calendarCells" }] : []));
1439
+ canPrevYearWindow = computed(() => !this.isYearWindowBlocked(this.yearBase() - YEAR_WINDOW_SIZE), ...(ngDevMode ? [{ debugName: "canPrevYearWindow" }] : []));
1440
+ canNextYearWindow = computed(() => !this.isYearWindowBlocked(this.yearBase() + YEAR_WINDOW_SIZE), ...(ngDevMode ? [{ debugName: "canNextYearWindow" }] : []));
1441
+ isYearWindowBlocked(nextBase) {
1442
+ const min = this.minD();
1443
+ const max = this.maxD();
1444
+ if (!min && !max)
1445
+ return false;
1446
+ const years = Array.from({ length: YEAR_WINDOW_SIZE }, (_, i) => nextBase + i);
1447
+ return years.every((y) => this.isYearDisabled(y));
1448
+ }
1449
+ constructor() {
1450
+ effect(() => {
1451
+ this.isDisabled.set(this.disabled());
1452
+ if (this.isDisabled())
1453
+ this.close('programmatic');
1454
+ });
1455
+ }
1456
+ prevYearWindow() {
1457
+ if (this.isDisabled())
1458
+ return;
1459
+ const next = this.yearBase() - YEAR_WINDOW_SIZE;
1460
+ if (this.isYearWindowBlocked(next))
1461
+ return;
1462
+ this.yearBase.set(next);
1463
+ }
1464
+ nextYearWindow() {
1465
+ if (this.isDisabled())
1466
+ return;
1467
+ const next = this.yearBase() + YEAR_WINDOW_SIZE;
1468
+ if (this.isYearWindowBlocked(next))
1469
+ return;
1470
+ this.yearBase.set(next);
1471
+ }
1472
+ /* =====================
1473
+ * CVA
1474
+ * ===================== */
1475
+ writeValue(value) {
1476
+ if (value == null) {
1477
+ this.value = null;
1478
+ this.draft.set(null);
1479
+ this.inputValue.set('');
1480
+ return;
1481
+ }
1482
+ const d = this.adapter().startOfDay(new Date(value));
1483
+ if (!this.adapter().isValid(d))
1484
+ return;
1485
+ const clamped = this.clampToBounds(d);
1486
+ this.value = clamped;
1487
+ this.draft.set(clamped);
1488
+ this.yearBase.set(Math.floor(this.adapter().year(clamped) / YEAR_WINDOW_SIZE) * YEAR_WINDOW_SIZE);
1489
+ const formatted = this.adapter().format(clamped, this.displayFormat(), this.locale() ?? undefined);
1490
+ this.inputValue.set(formatted);
1491
+ }
1492
+ registerOnChange(fn) {
1493
+ this.onChange = fn;
1494
+ }
1495
+ registerOnTouched(fn) {
1496
+ this.onTouched = fn;
1497
+ }
1498
+ setDisabledState(isDisabled) {
1499
+ this.isDisabled.set(isDisabled);
1500
+ if (isDisabled)
1501
+ this.close('programmatic');
1502
+ }
1503
+ /* =====================
1504
+ * Overlay control
1505
+ * ===================== */
1506
+ open(_reason) {
1507
+ if (this.isDisabled())
1508
+ return;
1509
+ this.isOpen.set(true);
1510
+ if (this.draft() == null)
1511
+ this.draft.set(this.value ?? this.adapter().startOfDay(new Date()));
1512
+ const current = (this.draft() ?? this.value ?? this.adapter().startOfDay(new Date()));
1513
+ // year window sync (existing)
1514
+ const y = this.adapter().year(current);
1515
+ this.yearBase.set(Math.floor(y / YEAR_WINDOW_SIZE) * YEAR_WINDOW_SIZE);
1516
+ // keyboard focus starts from current draft/value
1517
+ this.focusedDate.set(current);
1518
+ }
1519
+ close(_reason) {
1520
+ if (!this.isOpen())
1521
+ return;
1522
+ this.isOpen.set(false);
1523
+ }
1524
+ onOverlayOpenChange(open) {
1525
+ if (this.isDisabled()) {
1526
+ this.isOpen.set(false);
1527
+ return;
1528
+ }
1529
+ open ? this.open('programmatic') : this.close('programmatic');
1530
+ }
1531
+ onOverlayClosed(reason) {
1532
+ this.close(reason);
1533
+ }
1534
+ toggleOverlay() {
1535
+ if (this.isDisabled())
1536
+ return;
1537
+ this.isOpen.update((v) => !v);
1538
+ if (this.isOpen())
1539
+ this.open('programmatic');
1540
+ queueMicrotask(() => this.inputEl.nativeElement.focus());
1541
+ }
1542
+ /* =====================
1543
+ * Input handling (smart)
1544
+ * ===================== */
1545
+ onInput(ev) {
1546
+ if (this.isDisabled())
1547
+ return;
1548
+ const input = ev.target;
1549
+ const raw = input.value ?? '';
1550
+ const before = raw;
1551
+ const beforeCaret = input.selectionStart ?? before.length;
1552
+ const res = parseSmartDate(raw, this.displayFormat(), this.adapter(), this.locale() ?? undefined);
1553
+ // Keep what user typed while partial/invalid; don't hard-clear.
1554
+ if (res.kind === 'empty') {
1555
+ this.inputValue.set('');
1556
+ this.draft.set(null);
1557
+ this.onChange(null);
1558
+ this.onTouched();
1559
+ return;
1560
+ }
1561
+ if (res.kind === 'partial') {
1562
+ this.inputValue.set(raw);
1563
+ // do not emit null onChange while user is mid-typing
1564
+ this.onTouched();
1565
+ return;
1566
+ }
1567
+ if (res.kind === 'invalid') {
1568
+ this.inputValue.set(raw);
1569
+ this.onChange(null);
1570
+ this.onTouched();
1571
+ return;
1572
+ }
1573
+ // valid
1574
+ const clamped = this.clampToBounds(res.date);
1575
+ this.draft.set(clamped);
1576
+ const formatted = this.adapter().format(clamped, this.displayFormat(), this.locale() ?? undefined);
1577
+ this.inputValue.set(formatted);
1578
+ // Apply formatting back to input while preserving caret
1579
+ if (formatted !== raw) {
1580
+ input.value = formatted;
1581
+ const nextCaret = computeNextCaretPos(before, beforeCaret, formatted);
1582
+ queueMicrotask(() => input.setSelectionRange(nextCaret, nextCaret));
1583
+ }
1584
+ // don’t commit until day click/confirm (your current UX), but
1585
+ // we can still emit null/valid? You already treat typing as draft.
1586
+ // Keep existing behavior: do NOT call onChange here.
1587
+ this.onTouched();
1588
+ }
1589
+ onBlur() {
1590
+ this.onTouched();
1591
+ }
1592
+ setFocusedDate(next) {
1593
+ const d = this.adapter().startOfDay(next);
1594
+ // clamp to bounds
1595
+ const clamped = this.clampToBounds(d);
1596
+ this.focusedDate.set(clamped);
1597
+ // If user navigates to another month/year, update draft to keep calendar view in sync
1598
+ this.draft.set(clamped);
1599
+ // Keep year window aligned
1600
+ this.yearBase.set(Math.floor(this.adapter().year(clamped) / YEAR_WINDOW_SIZE) * YEAR_WINDOW_SIZE);
1601
+ }
1602
+ moveFocusedByDays(delta) {
1603
+ const base = (this.focusedDate() ?? this.draft() ?? this.value ?? this.adapter().startOfDay(new Date()));
1604
+ this.setFocusedDate(this.adapter().addDays(base, delta));
1605
+ }
1606
+ moveFocusedByMonths(delta) {
1607
+ const base = (this.focusedDate() ?? this.draft() ?? this.value ?? this.adapter().startOfDay(new Date()));
1608
+ this.setFocusedDate(this.adapter().addMonths(base, delta));
1609
+ }
1610
+ moveFocusedToStartOfMonth() {
1611
+ const base = (this.focusedDate() ?? this.draft() ?? this.value ?? this.adapter().startOfDay(new Date()));
1612
+ this.setFocusedDate(this.adapter().startOfMonth(base));
1613
+ }
1614
+ moveFocusedToEndOfMonth() {
1615
+ const base = (this.focusedDate() ?? this.draft() ?? this.value ?? this.adapter().startOfDay(new Date()));
1616
+ this.setFocusedDate(this.adapter().endOfMonth(base));
1617
+ }
1618
+ isCellFocused(cell) {
1619
+ const f = this.focusedDate();
1620
+ if (!f)
1621
+ return false;
1622
+ return this.adapter().isSameDay(cell.date, f);
1623
+ }
1624
+ onKeydown(ev) {
1625
+ if (this.isDisabled())
1626
+ return;
1627
+ // ---- TAB / SHIFT+TAB -> close popup ----
1628
+ if (ev.key === 'Tab' && this.isOpen()) {
1629
+ // Do NOT preventDefault -> allow normal focus navigation
1630
+ this.close('blur');
1631
+ return;
1632
+ }
1633
+ // Escape closes without commit
1634
+ if (ev.key === 'Escape' && this.isOpen()) {
1635
+ ev.preventDefault();
1636
+ this.close('escape');
1637
+ return;
1638
+ }
1639
+ // Open when closed
1640
+ if (!this.isOpen() && (ev.key === 'ArrowDown' || ev.key === 'Enter')) {
1641
+ ev.preventDefault();
1642
+ this.open('programmatic');
1643
+ return;
1644
+ }
1645
+ // Keyboard navigation only when open
1646
+ if (!this.isOpen())
1647
+ return;
1648
+ switch (ev.key) {
1649
+ case 'ArrowLeft':
1650
+ ev.preventDefault();
1651
+ this.moveFocusedByDays(-1);
1652
+ return;
1653
+ case 'ArrowRight':
1654
+ ev.preventDefault();
1655
+ this.moveFocusedByDays(1);
1656
+ return;
1657
+ case 'ArrowUp':
1658
+ ev.preventDefault();
1659
+ this.moveFocusedByDays(-7);
1660
+ return;
1661
+ case 'ArrowDown':
1662
+ ev.preventDefault();
1663
+ this.moveFocusedByDays(7);
1664
+ return;
1665
+ case 'PageUp':
1666
+ ev.preventDefault();
1667
+ this.moveFocusedByMonths(-1);
1668
+ return;
1669
+ case 'PageDown':
1670
+ ev.preventDefault();
1671
+ this.moveFocusedByMonths(1);
1672
+ return;
1673
+ case 'Home':
1674
+ ev.preventDefault();
1675
+ this.moveFocusedToStartOfMonth();
1676
+ return;
1677
+ case 'End':
1678
+ ev.preventDefault();
1679
+ this.moveFocusedToEndOfMonth();
1680
+ return;
1681
+ case 'Enter': {
1682
+ ev.preventDefault();
1683
+ const f = this.focusedDate();
1684
+ if (!f)
1685
+ return;
1686
+ const clamped = this.clampToBounds(f);
1687
+ this.draft.set(clamped);
1688
+ this.value = clamped;
1689
+ this.inputValue.set(this.adapter().format(clamped, this.displayFormat(), this.locale() ?? undefined));
1690
+ this.onChange(clamped);
1691
+ this.onTouched();
1692
+ this.close('selection');
1693
+ return;
1694
+ }
1695
+ }
1696
+ }
1697
+ /* =====================
1698
+ * Calendar actions
1699
+ * ===================== */
1700
+ selectMonth(monthIndex0) {
1701
+ if (this.isDisabled())
1702
+ return;
1703
+ const base = this.draft() ?? this.value ?? this.adapter().startOfDay(new Date());
1704
+ const day = this.adapter().date(base);
1705
+ const m = this.adapter().setMonth(base, monthIndex0);
1706
+ const nextDay = Math.min(day, this.adapter().daysInMonth(m));
1707
+ this.draft.set(this.clampToBounds(this.adapter().setDate(m, nextDay)));
1708
+ }
1709
+ selectYear(year) {
1710
+ if (this.isDisabled())
1711
+ return;
1712
+ const base = this.draft() ?? this.value ?? this.adapter().startOfDay(new Date());
1713
+ const day = this.adapter().date(base);
1714
+ const y = this.adapter().setYear(base, year);
1715
+ const nextDay = Math.min(day, this.adapter().daysInMonth(y));
1716
+ this.draft.set(this.clampToBounds(this.adapter().setDate(y, nextDay)));
1717
+ }
1718
+ // day click commits + closes (as you requested)
1719
+ selectDay(cell) {
1720
+ if (this.isDisabled())
1721
+ return;
1722
+ const d = this.adapter().startOfDay(cell.date);
1723
+ if (!this.isWithinBounds(d))
1724
+ return;
1725
+ const clamped = this.clampToBounds(d);
1726
+ this.draft.set(clamped);
1727
+ this.value = clamped;
1728
+ const formatted = this.adapter().format(clamped, this.displayFormat(), this.locale() ?? undefined);
1729
+ this.inputValue.set(formatted);
1730
+ this.onChange(clamped);
1731
+ this.onTouched();
1732
+ this.close('selection');
1733
+ }
1734
+ cancel() {
1735
+ if (this.isDisabled())
1736
+ return;
1737
+ this.draft.set(this.value);
1738
+ const formatted = this.value
1739
+ ? this.adapter().format(this.value, this.displayFormat(), this.locale() ?? undefined)
1740
+ : '';
1741
+ this.inputValue.set(formatted);
1742
+ this.close('blur');
1743
+ this.onTouched();
1744
+ }
1745
+ confirm() {
1746
+ if (this.isDisabled())
1747
+ return;
1748
+ const d = this.draft();
1749
+ if (!d) {
1750
+ this.value = null;
1751
+ this.inputValue.set('');
1752
+ this.onChange(null);
1753
+ this.onTouched();
1754
+ this.close('selection');
1755
+ return;
1756
+ }
1757
+ const clamped = this.clampToBounds(d);
1758
+ this.value = clamped;
1759
+ const formatted = this.adapter().format(clamped, this.displayFormat(), this.locale() ?? undefined);
1760
+ this.inputValue.set(formatted);
1761
+ this.onChange(clamped);
1762
+ this.onTouched();
1763
+ this.close('selection');
1764
+ }
1765
+ /* =====================
1766
+ * Selection helpers
1767
+ * ===================== */
1768
+ isMonthSelected(monthIndex0) {
1769
+ return this.selectedMonth() === monthIndex0;
1770
+ }
1771
+ isYearSelected(year) {
1772
+ return this.selectedYear() === year;
1773
+ }
1774
+ isCellSelected(cell) {
1775
+ const d = this.draft() ?? this.value;
1776
+ return !!d && this.adapter().isSameDay(cell.date, d);
1777
+ }
1778
+ isCellDisabled(cell) {
1779
+ return !this.isWithinBounds(cell.date);
1780
+ }
1781
+ isMonthDisabled(monthIndex0) {
1782
+ const min = this.minD();
1783
+ const max = this.maxD();
1784
+ if (!min && !max)
1785
+ return false;
1786
+ const y = this.selectedYear();
1787
+ const start = this.adapter().startOfDay(this.adapter().setYear(this.adapter().setMonth(new Date(), monthIndex0), y));
1788
+ const end = this.adapter().endOfMonth(start);
1789
+ if (min && this.adapter().isBeforeDay(end, min))
1790
+ return true;
1791
+ if (max && this.adapter().isAfterDay(start, max))
1792
+ return true;
1793
+ return false;
1794
+ }
1795
+ isYearDisabled(year) {
1796
+ const min = this.minD();
1797
+ const max = this.maxD();
1798
+ if (!min && !max)
1799
+ return false;
1800
+ const start = this.adapter().startOfDay(this.adapter().setYear(this.adapter().setMonth(new Date(), 0), year));
1801
+ const end = this.adapter().endOfYear(start);
1802
+ if (min && this.adapter().isBeforeDay(end, min))
1803
+ return true;
1804
+ if (max && this.adapter().isAfterDay(start, max))
1805
+ return true;
1806
+ return false;
1807
+ }
1808
+ /* =====================
1809
+ * Internals
1810
+ * ===================== */
1811
+ clampToBounds(d) {
1812
+ const min = this.minD();
1813
+ const max = this.maxD();
1814
+ if (min && this.adapter().isBeforeDay(d, min))
1815
+ return min;
1816
+ if (max && this.adapter().isAfterDay(d, max))
1817
+ return max;
1818
+ return d;
1819
+ }
1820
+ isWithinBounds(d) {
1821
+ const min = this.minD();
1822
+ const max = this.maxD();
1823
+ if (min && this.adapter().isBeforeDay(d, min))
1824
+ return false;
1825
+ if (max && this.adapter().isAfterDay(d, max))
1826
+ return false;
1827
+ return true;
1828
+ }
1829
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngDatepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1830
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngDatepicker, isStandalone: true, selector: "tng-datepicker", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, previewFormat: { classPropertyName: "previewFormat", publicName: "previewFormat", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateAdapter: { classPropertyName: "dateAdapter", publicName: "dateAdapter", isSignal: true, isRequired: false, transformFunction: null }, inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null }, toggleKlass: { classPropertyName: "toggleKlass", publicName: "toggleKlass", isSignal: true, isRequired: false, transformFunction: null }, hostKlass: { classPropertyName: "hostKlass", publicName: "hostKlass", isSignal: true, isRequired: false, transformFunction: null }, disabledKlass: { classPropertyName: "disabledKlass", publicName: "disabledKlass", isSignal: true, isRequired: false, transformFunction: null }, fieldKlass: { classPropertyName: "fieldKlass", publicName: "fieldKlass", isSignal: true, isRequired: false, transformFunction: null }, toggleIconKlass: { classPropertyName: "toggleIconKlass", publicName: "toggleIconKlass", isSignal: true, isRequired: false, transformFunction: null }, panelKlass: { classPropertyName: "panelKlass", publicName: "panelKlass", isSignal: true, isRequired: false, transformFunction: null }, panelFrameKlass: { classPropertyName: "panelFrameKlass", publicName: "panelFrameKlass", isSignal: true, isRequired: false, transformFunction: null }, panelLayoutKlass: { classPropertyName: "panelLayoutKlass", publicName: "panelLayoutKlass", isSignal: true, isRequired: false, transformFunction: null }, monthRailKlass: { classPropertyName: "monthRailKlass", publicName: "monthRailKlass", isSignal: true, isRequired: false, transformFunction: null }, monthListKlass: { classPropertyName: "monthListKlass", publicName: "monthListKlass", isSignal: true, isRequired: false, transformFunction: null }, monthItemKlass: { classPropertyName: "monthItemKlass", publicName: "monthItemKlass", isSignal: true, isRequired: false, transformFunction: null }, calendarKlass: { classPropertyName: "calendarKlass", publicName: "calendarKlass", isSignal: true, isRequired: false, transformFunction: null }, titleKlass: { classPropertyName: "titleKlass", publicName: "titleKlass", isSignal: true, isRequired: false, transformFunction: null }, weekdayRowKlass: { classPropertyName: "weekdayRowKlass", publicName: "weekdayRowKlass", isSignal: true, isRequired: false, transformFunction: null }, weekdayCellKlass: { classPropertyName: "weekdayCellKlass", publicName: "weekdayCellKlass", isSignal: true, isRequired: false, transformFunction: null }, dayGridKlass: { classPropertyName: "dayGridKlass", publicName: "dayGridKlass", isSignal: true, isRequired: false, transformFunction: null }, dayCellKlass: { classPropertyName: "dayCellKlass", publicName: "dayCellKlass", isSignal: true, isRequired: false, transformFunction: null }, previewTextKlass: { classPropertyName: "previewTextKlass", publicName: "previewTextKlass", isSignal: true, isRequired: false, transformFunction: null }, actionBarKlass: { classPropertyName: "actionBarKlass", publicName: "actionBarKlass", isSignal: true, isRequired: false, transformFunction: null }, cancelKlass: { classPropertyName: "cancelKlass", publicName: "cancelKlass", isSignal: true, isRequired: false, transformFunction: null }, confirmKlass: { classPropertyName: "confirmKlass", publicName: "confirmKlass", isSignal: true, isRequired: false, transformFunction: null }, yearRailKlass: { classPropertyName: "yearRailKlass", publicName: "yearRailKlass", isSignal: true, isRequired: false, transformFunction: null }, yearNavPrevKlass: { classPropertyName: "yearNavPrevKlass", publicName: "yearNavPrevKlass", isSignal: true, isRequired: false, transformFunction: null }, yearListKlass: { classPropertyName: "yearListKlass", publicName: "yearListKlass", isSignal: true, isRequired: false, transformFunction: null }, yearItemKlass: { classPropertyName: "yearItemKlass", publicName: "yearItemKlass", isSignal: true, isRequired: false, transformFunction: null }, yearNavNextKlass: { classPropertyName: "yearNavNextKlass", publicName: "yearNavNextKlass", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1831
+ {
1832
+ provide: NG_VALUE_ACCESSOR,
1833
+ useExisting: forwardRef(() => TngDatepicker),
1834
+ multi: true,
1835
+ },
1836
+ ], viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }], ngImport: i0, template: "<div [class]=\"hostKlassFinal()\">\n <!-- =====================\n Input + calendar icon\n ===================== -->\n <div [class]=\"disabledKlassFinal()\" [class.opacity-60]=\"isDisabled()\" [class.pointer-events-none]=\"isDisabled()\">\n <div [class]=\"fieldKlassFinal()\">\n <input\n #inputEl\n type=\"text\"\n [class]=\"inputKlassFinal()\"\n [value]=\"inputValue()\"\n [placeholder]=\"displayFormat()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"open('programmatic')\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n />\n\n <!-- Calendar icon -->\n <button\n type=\"button\"\n [class]=\"toggleKlassFinal()\"\n (click)=\"toggleOverlay()\"\n aria-label=\"Open calendar\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n [class]=\"toggleIconKlassFinal()\"\n fill=\"currentColor\"\n >\n <path\n d=\"M7 2a1 1 0 0 1 1 1v1h8V3a1 1 0 1 1 2 0v1h1.5A2.5 2.5 0 0 1 22 6.5v14\n A2.5 2.5 0 0 1 19.5 23h-15A2.5 2.5 0 0 1 2 20.5v-14\n A2.5 2.5 0 0 1 4.5 4H6V3a1 1 0 0 1 1-1Zm12.5 8h-15a.5.5 0 0 0-.5.5v10\n a.5.5 0 0 0 .5.5h15a.5.5 0 0 0 .5-.5v-10a.5.5 0 0 0-.5-.5Z\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n <!-- =====================\n Overlay\n ===================== -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"inputEl\"\n placement=\"bottom-start\"\n width=\"content\"\n [closeOnInsideClick]=\"false\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel [klass]=\"panelKlassFinal()\">\n <!-- Popup frame -->\n <div [class]=\"panelFrameKlassFinal()\">\n <!-- =====================\n Body layout\n ===================== -->\n <div [class]=\"panelLayoutKlassFinal()\">\n\n <!-- ========= Months ========= -->\n <div [class]=\"monthRailKlassFinal()\">\n <div [class]=\"monthListKlassFinal()\">\n @for (m of months; track m.index) {\n <button\n type=\"button\"\n [class]=\"monthItemKlassFinal()\"\n [class.bg-fg]=\"isMonthSelected(m.index)\"\n [class.text-bg]=\"isMonthSelected(m.index)\"\n [class.opacity-40]=\"isMonthDisabled(m.index)\"\n [disabled]=\"isMonthDisabled(m.index)\"\n (click)=\"selectMonth(m.index)\"\n >\n {{ m.label }}\n </button>\n }\n </div>\n </div>\n\n <!-- ========= Calendar ========= -->\n <div [class]=\"calendarKlassFinal()\">\n <div [class]=\"titleKlassFinal()\">\n Select Date\n </div>\n\n <!-- Weekdays -->\n <div [class]=\"weekdayRowKlassFinal()\">\n @for (d of weekdays; track d) {\n <div [class]=\"weekdayCellKlassFinal()\">{{ d }}</div>\n }\n </div>\n\n <!-- Days -->\n <div [class]=\"dayGridKlassFinal()\">\n @for (cell of calendarCells(); track $index) {\n <button\n type=\"button\"\n [class]=\"dayCellKlassFinal()\"\n [class.text-fg]=\"cell.isOutsideMonth\"\n [class.hover:bg-fg]=\"!isCellDisabled(cell) && !isCellSelected(cell)\"\n [class.hover:text-bg]=\"!isCellDisabled(cell) && !isCellSelected(cell)\"\n [class.bg-fg]=\"isCellSelected(cell)\"\n [class.text-bg]=\"isCellSelected(cell)\"\n [class.opacity-40]=\"isCellDisabled(cell)\"\n [class.ring-2]=\"isCellFocused(cell) && !isCellSelected(cell)\"\n [class.ring-border]=\"isCellFocused(cell) && !isCellSelected(cell)\"\n [disabled]=\"isCellDisabled(cell)\"\n (click)=\"selectDay(cell)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n\n <!-- Preview -->\n <div [class]=\"previewTextKlassFinal()\">\n {{ previewLabel() }}\n </div>\n\n <!-- Actions -->\n <div [class]=\"actionBarKlassFinal()\">\n <button\n type=\"button\"\n [class]=\"cancelKlassFinal()\"\n (click)=\"cancel()\"\n >\n Cancel\n </button>\n\n <button\n type=\"button\"\n [class]=\"confirmKlassFinal()\"\n (click)=\"confirm()\"\n >\n Confirm\n </button>\n </div>\n </div>\n\n <!-- ========= Years (10 at a time) ========= -->\n <div [class]=\"yearRailKlassFinal()\">\n\n <!-- Up -->\n <button\n type=\"button\"\n [class]=\"yearNavPrevKlassFinal()\"\n (click)=\"prevYearWindow()\"\n [disabled]=\"!canPrevYearWindow()\"\n aria-label=\"Previous 10 years\"\n >\n \u25B2\n </button>\n\n <!-- Year list -->\n <div [class]=\"yearListKlassFinal()\">\n @for (y of years(); track (yearBase() + ':' + y)) {\n <button\n type=\"button\"\n [class]=\"yearItemKlassFinal()\"\n [class.bg-fg]=\"isYearSelected(y)\"\n [class.text-bg]=\"isYearSelected(y)\"\n [class.opacity-40]=\"isYearDisabled(y)\"\n [disabled]=\"isYearDisabled(y)\"\n (click)=\"selectYear(y)\"\n >\n {{ y }}\n </button>\n }\n </div>\n \n <!-- Down -->\n <button\n type=\"button\"\n [class]=\"yearNavNextKlassFinal()\"\n (click)=\"nextYearWindow()\"\n [disabled]=\"!canNextYearWindow()\"\n aria-label=\"Next 10 years\"\n >\n \u25BC\n </button>\n </div>\n\n </div>\n </div>\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n", dependencies: [{ kind: "component", type: TngOverlayRef, selector: "tng-overlay-ref", inputs: ["open"], outputs: ["openChange", "opened", "closed"], exportAs: ["tngOverlayRef"] }, { kind: "component", type: TngConnectedOverlay, selector: "tng-connected-overlay", inputs: ["open", "anchor", "placement", "offset", "width", "closeOnOutsideClick", "closeOnInsideClick", "closeOnEscape", "hasBackdrop", "backdropClass"], outputs: ["opened", "closed", "backdropClick"] }, { kind: "component", type: TngOverlayPanel, selector: "tng-overlay-panel", inputs: ["klass", "modal", "role", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "restoreFocus", "autoCapture", "deferCaptureElements"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1837
+ }
1838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngDatepicker, decorators: [{
1839
+ type: Component,
1840
+ args: [{ selector: 'tng-datepicker', standalone: true, imports: [
1841
+ TngOverlayRef,
1842
+ TngConnectedOverlay,
1843
+ TngOverlayPanel,
1844
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1845
+ {
1846
+ provide: NG_VALUE_ACCESSOR,
1847
+ useExisting: forwardRef(() => TngDatepicker),
1848
+ multi: true,
1849
+ },
1850
+ ], template: "<div [class]=\"hostKlassFinal()\">\n <!-- =====================\n Input + calendar icon\n ===================== -->\n <div [class]=\"disabledKlassFinal()\" [class.opacity-60]=\"isDisabled()\" [class.pointer-events-none]=\"isDisabled()\">\n <div [class]=\"fieldKlassFinal()\">\n <input\n #inputEl\n type=\"text\"\n [class]=\"inputKlassFinal()\"\n [value]=\"inputValue()\"\n [placeholder]=\"displayFormat()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onInput($event)\"\n (focus)=\"open('programmatic')\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n autocomplete=\"off\"\n />\n\n <!-- Calendar icon -->\n <button\n type=\"button\"\n [class]=\"toggleKlassFinal()\"\n (click)=\"toggleOverlay()\"\n aria-label=\"Open calendar\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n [class]=\"toggleIconKlassFinal()\"\n fill=\"currentColor\"\n >\n <path\n d=\"M7 2a1 1 0 0 1 1 1v1h8V3a1 1 0 1 1 2 0v1h1.5A2.5 2.5 0 0 1 22 6.5v14\n A2.5 2.5 0 0 1 19.5 23h-15A2.5 2.5 0 0 1 2 20.5v-14\n A2.5 2.5 0 0 1 4.5 4H6V3a1 1 0 0 1 1-1Zm12.5 8h-15a.5.5 0 0 0-.5.5v10\n a.5.5 0 0 0 .5.5h15a.5.5 0 0 0 .5-.5v-10a.5.5 0 0 0-.5-.5Z\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n <!-- =====================\n Overlay\n ===================== -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"inputEl\"\n placement=\"bottom-start\"\n width=\"content\"\n [closeOnInsideClick]=\"false\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel [klass]=\"panelKlassFinal()\">\n <!-- Popup frame -->\n <div [class]=\"panelFrameKlassFinal()\">\n <!-- =====================\n Body layout\n ===================== -->\n <div [class]=\"panelLayoutKlassFinal()\">\n\n <!-- ========= Months ========= -->\n <div [class]=\"monthRailKlassFinal()\">\n <div [class]=\"monthListKlassFinal()\">\n @for (m of months; track m.index) {\n <button\n type=\"button\"\n [class]=\"monthItemKlassFinal()\"\n [class.bg-fg]=\"isMonthSelected(m.index)\"\n [class.text-bg]=\"isMonthSelected(m.index)\"\n [class.opacity-40]=\"isMonthDisabled(m.index)\"\n [disabled]=\"isMonthDisabled(m.index)\"\n (click)=\"selectMonth(m.index)\"\n >\n {{ m.label }}\n </button>\n }\n </div>\n </div>\n\n <!-- ========= Calendar ========= -->\n <div [class]=\"calendarKlassFinal()\">\n <div [class]=\"titleKlassFinal()\">\n Select Date\n </div>\n\n <!-- Weekdays -->\n <div [class]=\"weekdayRowKlassFinal()\">\n @for (d of weekdays; track d) {\n <div [class]=\"weekdayCellKlassFinal()\">{{ d }}</div>\n }\n </div>\n\n <!-- Days -->\n <div [class]=\"dayGridKlassFinal()\">\n @for (cell of calendarCells(); track $index) {\n <button\n type=\"button\"\n [class]=\"dayCellKlassFinal()\"\n [class.text-fg]=\"cell.isOutsideMonth\"\n [class.hover:bg-fg]=\"!isCellDisabled(cell) && !isCellSelected(cell)\"\n [class.hover:text-bg]=\"!isCellDisabled(cell) && !isCellSelected(cell)\"\n [class.bg-fg]=\"isCellSelected(cell)\"\n [class.text-bg]=\"isCellSelected(cell)\"\n [class.opacity-40]=\"isCellDisabled(cell)\"\n [class.ring-2]=\"isCellFocused(cell) && !isCellSelected(cell)\"\n [class.ring-border]=\"isCellFocused(cell) && !isCellSelected(cell)\"\n [disabled]=\"isCellDisabled(cell)\"\n (click)=\"selectDay(cell)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n\n <!-- Preview -->\n <div [class]=\"previewTextKlassFinal()\">\n {{ previewLabel() }}\n </div>\n\n <!-- Actions -->\n <div [class]=\"actionBarKlassFinal()\">\n <button\n type=\"button\"\n [class]=\"cancelKlassFinal()\"\n (click)=\"cancel()\"\n >\n Cancel\n </button>\n\n <button\n type=\"button\"\n [class]=\"confirmKlassFinal()\"\n (click)=\"confirm()\"\n >\n Confirm\n </button>\n </div>\n </div>\n\n <!-- ========= Years (10 at a time) ========= -->\n <div [class]=\"yearRailKlassFinal()\">\n\n <!-- Up -->\n <button\n type=\"button\"\n [class]=\"yearNavPrevKlassFinal()\"\n (click)=\"prevYearWindow()\"\n [disabled]=\"!canPrevYearWindow()\"\n aria-label=\"Previous 10 years\"\n >\n \u25B2\n </button>\n\n <!-- Year list -->\n <div [class]=\"yearListKlassFinal()\">\n @for (y of years(); track (yearBase() + ':' + y)) {\n <button\n type=\"button\"\n [class]=\"yearItemKlassFinal()\"\n [class.bg-fg]=\"isYearSelected(y)\"\n [class.text-bg]=\"isYearSelected(y)\"\n [class.opacity-40]=\"isYearDisabled(y)\"\n [disabled]=\"isYearDisabled(y)\"\n (click)=\"selectYear(y)\"\n >\n {{ y }}\n </button>\n }\n </div>\n \n <!-- Down -->\n <button\n type=\"button\"\n [class]=\"yearNavNextKlassFinal()\"\n (click)=\"nextYearWindow()\"\n [disabled]=\"!canNextYearWindow()\"\n aria-label=\"Next 10 years\"\n >\n \u25BC\n </button>\n </div>\n\n </div>\n </div>\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n" }]
1851
+ }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], previewFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewFormat", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateAdapter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateAdapter", required: false }] }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }], toggleKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleKlass", required: false }] }], hostKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hostKlass", required: false }] }], disabledKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledKlass", required: false }] }], fieldKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldKlass", required: false }] }], toggleIconKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleIconKlass", required: false }] }], panelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelKlass", required: false }] }], panelFrameKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelFrameKlass", required: false }] }], panelLayoutKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLayoutKlass", required: false }] }], monthRailKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthRailKlass", required: false }] }], monthListKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthListKlass", required: false }] }], monthItemKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthItemKlass", required: false }] }], calendarKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "calendarKlass", required: false }] }], titleKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKlass", required: false }] }], weekdayRowKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayRowKlass", required: false }] }], weekdayCellKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayCellKlass", required: false }] }], dayGridKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayGridKlass", required: false }] }], dayCellKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayCellKlass", required: false }] }], previewTextKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewTextKlass", required: false }] }], actionBarKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionBarKlass", required: false }] }], cancelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelKlass", required: false }] }], confirmKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmKlass", required: false }] }], yearRailKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearRailKlass", required: false }] }], yearNavPrevKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearNavPrevKlass", required: false }] }], yearListKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearListKlass", required: false }] }], yearItemKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearItemKlass", required: false }] }], yearNavNextKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearNavNextKlass", required: false }] }], inputEl: [{
1852
+ type: ViewChild,
1853
+ args: ['inputEl', { static: true }]
1854
+ }] } });
1855
+
1856
+ class TngFileDropzone {
1857
+ /** Disable drag/drop interactions */
1858
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1859
+ /** Optional: allow dropping multiple files */
1860
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
1861
+ /** Optional: accept filter like input accept attribute (e.g. "image/*,.pdf") */
1862
+ accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
1863
+ /** Emits when valid files dropped */
1864
+ filesDropped = output();
1865
+ /** Emits drag active state changes (optional hook) */
1866
+ dragActiveChange = output();
1867
+ _dragActive = signal(false, ...(ngDevMode ? [{ debugName: "_dragActive" }] : []));
1868
+ dragActive = computed(() => this._dragActive(), ...(ngDevMode ? [{ debugName: "dragActive" }] : []));
1869
+ setActive(next) {
1870
+ if (this._dragActive() === next)
1871
+ return;
1872
+ this._dragActive.set(next);
1873
+ this.dragActiveChange.emit(next);
1874
+ }
1875
+ acceptMatches(file) {
1876
+ const accept = (this.accept() ?? '').trim();
1877
+ if (!accept)
1878
+ return true;
1879
+ // accept can be: "image/*,.pdf,application/json"
1880
+ const parts = accept
1881
+ .split(',')
1882
+ .map((p) => p.trim())
1883
+ .filter(Boolean);
1884
+ if (!parts.length)
1885
+ return true;
1886
+ const name = file.name.toLowerCase();
1887
+ const type = (file.type || '').toLowerCase();
1888
+ return parts.some((p) => {
1889
+ const token = p.toLowerCase();
1890
+ if (token === '*/*')
1891
+ return true;
1892
+ // extension: ".pdf"
1893
+ if (token.startsWith('.'))
1894
+ return name.endsWith(token);
1895
+ // wildcard mime: "image/*"
1896
+ if (token.endsWith('/*')) {
1897
+ const prefix = token.slice(0, token.length - 1); // keep trailing '/'
1898
+ return type.startsWith(prefix);
1899
+ }
1900
+ // exact mime: "application/pdf"
1901
+ return type === token;
1902
+ });
1903
+ }
1904
+ filterFiles(files) {
1905
+ const accepted = files.filter((f) => this.acceptMatches(f));
1906
+ return this.multiple() ? accepted : accepted.slice(0, 1);
1907
+ }
1908
+ extractFiles(dt) {
1909
+ if (!dt)
1910
+ return [];
1911
+ // Prefer items (better in some browsers)
1912
+ const items = dt.items;
1913
+ if (items && items.length) {
1914
+ const picked = [];
1915
+ for (const item of Array.from(items)) {
1916
+ if (item.kind !== 'file')
1917
+ continue;
1918
+ const file = item.getAsFile();
1919
+ if (file)
1920
+ picked.push(file);
1921
+ }
1922
+ return this.filterFiles(picked);
1923
+ }
1924
+ const files = dt.files ? Array.from(dt.files) : [];
1925
+ return this.filterFiles(files);
1926
+ }
1927
+ onDragEnter(ev) {
1928
+ if (this.disabled())
1929
+ return;
1930
+ ev.preventDefault();
1931
+ ev.stopPropagation();
1932
+ this.setActive(true);
1933
+ }
1934
+ onDragOver(ev) {
1935
+ if (this.disabled())
1936
+ return;
1937
+ ev.preventDefault();
1938
+ ev.stopPropagation();
1939
+ this.setActive(true);
1940
+ if (ev.dataTransfer)
1941
+ ev.dataTransfer.dropEffect = 'copy';
1942
+ }
1943
+ onDragLeave(ev) {
1944
+ if (this.disabled())
1945
+ return;
1946
+ ev.preventDefault();
1947
+ ev.stopPropagation();
1948
+ this.setActive(false);
1949
+ }
1950
+ onDrop(ev) {
1951
+ if (this.disabled())
1952
+ return;
1953
+ ev.preventDefault();
1954
+ ev.stopPropagation();
1955
+ this.setActive(false);
1956
+ const files = this.extractFiles(ev.dataTransfer ?? null);
1957
+ if (files.length)
1958
+ this.filesDropped.emit(files);
1959
+ }
1960
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFileDropzone, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1961
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TngFileDropzone, isStandalone: true, selector: "[tngFileDropzone]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesDropped: "filesDropped", dragActiveChange: "dragActiveChange" }, host: { listeners: { "dragenter": "onDragEnter($event)", "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "drop": "onDrop($event)" } }, exportAs: ["tngFileDropzone"], ngImport: i0 });
1962
+ }
1963
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFileDropzone, decorators: [{
1964
+ type: Directive,
1965
+ args: [{
1966
+ selector: '[tngFileDropzone]',
1967
+ standalone: true,
1968
+ exportAs: 'tngFileDropzone',
1969
+ }]
1970
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], filesDropped: [{ type: i0.Output, args: ["filesDropped"] }], dragActiveChange: [{ type: i0.Output, args: ["dragActiveChange"] }], onDragEnter: [{
1971
+ type: HostListener,
1972
+ args: ['dragenter', ['$event']]
1973
+ }], onDragOver: [{
1974
+ type: HostListener,
1975
+ args: ['dragover', ['$event']]
1976
+ }], onDragLeave: [{
1977
+ type: HostListener,
1978
+ args: ['dragleave', ['$event']]
1979
+ }], onDrop: [{
1980
+ type: HostListener,
1981
+ args: ['drop', ['$event']]
1982
+ }] } });
1983
+
1984
+ class TngFileUpload {
1985
+ fileInput;
1986
+ // config
1987
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1988
+ accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
1989
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
1990
+ // texts
1991
+ title = input('Upload files', ...(ngDevMode ? [{ debugName: "title" }] : []));
1992
+ subtitle = input('Drag & drop here or click to browse', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
1993
+ // theming hooks (section-wise)
1994
+ rootKlass = input('w-full', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
1995
+ dropzoneKlass = input('', ...(ngDevMode ? [{ debugName: "dropzoneKlass" }] : [])); // outer box
1996
+ titleKlass = input('text-sm font-medium text-fg', ...(ngDevMode ? [{ debugName: "titleKlass" }] : []));
1997
+ subtitleKlass = input('text-xs text-disable', ...(ngDevMode ? [{ debugName: "subtitleKlass" }] : []));
1998
+ hintKlass = input('text-xs text-disable', ...(ngDevMode ? [{ debugName: "hintKlass" }] : []));
1999
+ fileListKlass = input('mt-2 space-y-1', ...(ngDevMode ? [{ debugName: "fileListKlass" }] : []));
2000
+ fileItemKlass = input('flex items-center justify-between rounded-md border border-border bg-bg px-3 py-2 text-sm text-fg', ...(ngDevMode ? [{ debugName: "fileItemKlass" }] : []));
2001
+ clearButtonKlass = input('text-xs text-danger hover:text-danger-hover', ...(ngDevMode ? [{ debugName: "clearButtonKlass" }] : []));
2002
+ // optional outputs (non-form usage)
2003
+ valueChange = output();
2004
+ _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : []));
2005
+ value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : []));
2006
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
2007
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2008
+ onChange = () => { };
2009
+ onTouched = () => { };
2010
+ // visual state
2011
+ dragActive = signal(false, ...(ngDevMode ? [{ debugName: "dragActive" }] : []));
2012
+ writeValue(value) {
2013
+ this._value.set(value ?? null);
2014
+ }
2015
+ registerOnChange(fn) {
2016
+ this.onChange = fn;
2017
+ }
2018
+ registerOnTouched(fn) {
2019
+ this.onTouched = fn;
2020
+ }
2021
+ setDisabledState(isDisabled) {
2022
+ this._formDisabled.set(isDisabled);
2023
+ }
2024
+ // computed classes
2025
+ dropzoneClasses = computed(() => {
2026
+ const base = 'relative w-full rounded-md border px-4 py-4 transition ' +
2027
+ 'bg-bg text-fg border-border ' +
2028
+ 'focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2 focus-within:ring-offset-background';
2029
+ const disabled = this.isDisabled() ? ' opacity-60 pointer-events-none' : ' cursor-pointer';
2030
+ const active = this.dragActive()
2031
+ ? ' border-primary ring-2 ring-primary ring-offset-2 ring-offset-background'
2032
+ : '';
2033
+ return `${base} ${disabled} ${active} ${this.dropzoneKlass()}`.trim();
2034
+ }, ...(ngDevMode ? [{ debugName: "dropzoneClasses" }] : []));
2035
+ openPicker() {
2036
+ if (this.isDisabled())
2037
+ return;
2038
+ this.fileInput.nativeElement.click();
2039
+ }
2040
+ setFiles(next) {
2041
+ this._value.set(next);
2042
+ this.onChange(next);
2043
+ this.valueChange.emit(next);
2044
+ }
2045
+ onNativeChange(event) {
2046
+ if (this.isDisabled())
2047
+ return;
2048
+ const files = event.target.files;
2049
+ const list = files ? Array.from(files) : [];
2050
+ this.setFiles(list.length ? list : null);
2051
+ }
2052
+ onDropped(files) {
2053
+ if (this.isDisabled())
2054
+ return;
2055
+ this.setFiles(files.length ? files : null);
2056
+ // reset native input so selecting same file again still triggers change
2057
+ this.fileInput.nativeElement.value = '';
2058
+ }
2059
+ clear() {
2060
+ if (this.isDisabled())
2061
+ return;
2062
+ this.setFiles(null);
2063
+ this.fileInput.nativeElement.value = '';
2064
+ }
2065
+ onBlur() {
2066
+ this.onTouched();
2067
+ }
2068
+ onDragActiveChange(active) {
2069
+ this.dragActive.set(active);
2070
+ }
2071
+ trackByName = (_, f) => `${f.name}-${f.size}-${f.lastModified}`;
2072
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFileUpload, deps: [], target: i0.ɵɵFactoryTarget.Component });
2073
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngFileUpload, isStandalone: true, selector: "tng-file-upload", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, dropzoneKlass: { classPropertyName: "dropzoneKlass", publicName: "dropzoneKlass", isSignal: true, isRequired: false, transformFunction: null }, titleKlass: { classPropertyName: "titleKlass", publicName: "titleKlass", isSignal: true, isRequired: false, transformFunction: null }, subtitleKlass: { classPropertyName: "subtitleKlass", publicName: "subtitleKlass", isSignal: true, isRequired: false, transformFunction: null }, hintKlass: { classPropertyName: "hintKlass", publicName: "hintKlass", isSignal: true, isRequired: false, transformFunction: null }, fileListKlass: { classPropertyName: "fileListKlass", publicName: "fileListKlass", isSignal: true, isRequired: false, transformFunction: null }, fileItemKlass: { classPropertyName: "fileItemKlass", publicName: "fileItemKlass", isSignal: true, isRequired: false, transformFunction: null }, clearButtonKlass: { classPropertyName: "clearButtonKlass", publicName: "clearButtonKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, providers: [
2074
+ {
2075
+ provide: NG_VALUE_ACCESSOR,
2076
+ useExisting: forwardRef(() => TngFileUpload),
2077
+ multi: true,
2078
+ },
2079
+ ], viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, static: true }], ngImport: i0, template: "<div [class]=\"rootKlass()\">\n <!-- Hidden native input -->\n <input\n #fileInput\n type=\"file\"\n class=\"sr-only\"\n [disabled]=\"isDisabled()\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (change)=\"onNativeChange($event)\"\n (blur)=\"onBlur()\"\n />\n\n <!-- Dropzone -->\n <div\n tngFileDropzone\n [disabled]=\"isDisabled()\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (filesDropped)=\"onDropped($event)\"\n (dragActiveChange)=\"onDragActiveChange($event)\"\n [class]=\"dropzoneClasses()\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"openPicker()\"\n (keydown.enter)=\"openPicker()\"\n (keydown.space)=\"openPicker()\"\n >\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"space-y-1\">\n <div [class]=\"titleKlass()\">{{ title() }}</div>\n <div [class]=\"subtitleKlass()\">{{ subtitle() }}</div>\n </div>\n\n @if (value()?.length) {\n <button\n type=\"button\"\n [class]=\"clearButtonKlass()\"\n (click)=\"$event.stopPropagation(); clear()\"\n >\n Clear\n </button>\n }\n </div>\n\n @if (accept()) {\n <div class=\"mt-2\" [class]=\"hintKlass()\">\n Accepted: {{ accept() }}\n </div>\n }\n </div>\n\n <!-- File list -->\n @if (value()?.length) {\n <div [class]=\"fileListKlass()\">\n @for (f of value()!; track trackByName($index, f)) {\n <div [class]=\"fileItemKlass()\">\n <span class=\"truncate\">{{ f.name }}</span>\n <span class=\"ml-3 text-xs text-disable\">\n {{ (f.size / 1024).toFixed(1) }} KB\n </span>\n </div>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: TngFileDropzone, selector: "[tngFileDropzone]", inputs: ["disabled", "multiple", "accept"], outputs: ["filesDropped", "dragActiveChange"], exportAs: ["tngFileDropzone"] }] });
2080
+ }
2081
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFileUpload, decorators: [{
2082
+ type: Component,
2083
+ args: [{ selector: 'tng-file-upload', standalone: true, imports: [TngFileDropzone], providers: [
2084
+ {
2085
+ provide: NG_VALUE_ACCESSOR,
2086
+ useExisting: forwardRef(() => TngFileUpload),
2087
+ multi: true,
2088
+ },
2089
+ ], template: "<div [class]=\"rootKlass()\">\n <!-- Hidden native input -->\n <input\n #fileInput\n type=\"file\"\n class=\"sr-only\"\n [disabled]=\"isDisabled()\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (change)=\"onNativeChange($event)\"\n (blur)=\"onBlur()\"\n />\n\n <!-- Dropzone -->\n <div\n tngFileDropzone\n [disabled]=\"isDisabled()\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (filesDropped)=\"onDropped($event)\"\n (dragActiveChange)=\"onDragActiveChange($event)\"\n [class]=\"dropzoneClasses()\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"openPicker()\"\n (keydown.enter)=\"openPicker()\"\n (keydown.space)=\"openPicker()\"\n >\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"space-y-1\">\n <div [class]=\"titleKlass()\">{{ title() }}</div>\n <div [class]=\"subtitleKlass()\">{{ subtitle() }}</div>\n </div>\n\n @if (value()?.length) {\n <button\n type=\"button\"\n [class]=\"clearButtonKlass()\"\n (click)=\"$event.stopPropagation(); clear()\"\n >\n Clear\n </button>\n }\n </div>\n\n @if (accept()) {\n <div class=\"mt-2\" [class]=\"hintKlass()\">\n Accepted: {{ accept() }}\n </div>\n }\n </div>\n\n <!-- File list -->\n @if (value()?.length) {\n <div [class]=\"fileListKlass()\">\n @for (f of value()!; track trackByName($index, f)) {\n <div [class]=\"fileItemKlass()\">\n <span class=\"truncate\">{{ f.name }}</span>\n <span class=\"ml-3 text-xs text-disable\">\n {{ (f.size / 1024).toFixed(1) }} KB\n </span>\n </div>\n }\n </div>\n }\n</div>\n" }]
2090
+ }], propDecorators: { fileInput: [{
2091
+ type: ViewChild,
2092
+ args: ['fileInput', { static: true }]
2093
+ }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], dropzoneKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropzoneKlass", required: false }] }], titleKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKlass", required: false }] }], subtitleKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitleKlass", required: false }] }], hintKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKlass", required: false }] }], fileListKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileListKlass", required: false }] }], fileItemKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileItemKlass", required: false }] }], clearButtonKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearButtonKlass", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
2094
+
2095
+ class TngFormField {
2096
+ // Get NgControl from projected child (input/select/custom CVA)
2097
+ ngControl = contentChild(NgControl, { ...(ngDevMode ? { debugName: "ngControl" } : {}), descendants: true });
2098
+ destroyRef = inject(DestroyRef);
2099
+ /**
2100
+ * Bridge Angular Forms mutable state (touched/dirty/errors) into signals world.
2101
+ * We bump this tick on any control event so all computed() re-evaluate.
2102
+ */
2103
+ _tick = signal(0, ...(ngDevMode ? [{ debugName: "_tick" }] : []));
2104
+ bump() {
2105
+ this._tick.update((v) => v + 1);
2106
+ }
2107
+ /* =====================
2108
+ * Content
2109
+ * ===================== */
2110
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2111
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
2112
+ /**
2113
+ * Manual overrides:
2114
+ * If provided, they take precedence over NgControl state.
2115
+ */
2116
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : [])); // manual error text (wins)
2117
+ invalid = input(null, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
2118
+ disabled = input(null, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2119
+ /**
2120
+ * Required mark:
2121
+ * Reliable + explicit. (Auto-detecting Validators.required is not stable.)
2122
+ */
2123
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
2124
+ /* =====================
2125
+ * Variants
2126
+ * ===================== */
2127
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
2128
+ appearance = input('outline', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
2129
+ /* =====================
2130
+ * Theming / class hooks (section-wise)
2131
+ * ===================== */
2132
+ rootKlass = input('w-full', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
2133
+ labelKlass = input('text-sm font-medium text-fg', ...(ngDevMode ? [{ debugName: "labelKlass" }] : []));
2134
+ requiredMarkKlass = input('text-danger', ...(ngDevMode ? [{ debugName: "requiredMarkKlass" }] : []));
2135
+ hintKlass = input('text-xs text-disable', ...(ngDevMode ? [{ debugName: "hintKlass" }] : []));
2136
+ errorKlass = input('text-xs text-danger', ...(ngDevMode ? [{ debugName: "errorKlass" }] : []));
2137
+ /** Wrapper around projected control */
2138
+ controlShellKlass = input('', ...(ngDevMode ? [{ debugName: "controlShellKlass" }] : []));
2139
+ prefixKlass = input('text-disable', ...(ngDevMode ? [{ debugName: "prefixKlass" }] : []));
2140
+ suffixKlass = input('text-disable', ...(ngDevMode ? [{ debugName: "suffixKlass" }] : []));
2141
+ footerKlass = input('mt-1 flex items-start justify-between gap-3', ...(ngDevMode ? [{ debugName: "footerKlass" }] : []));
2142
+ /* =====================
2143
+ * Control wiring
2144
+ * ===================== */
2145
+ ctrl = computed(() => this.ngControl()?.control ?? null, ...(ngDevMode ? [{ debugName: "ctrl" }] : []));
2146
+ constructor() {
2147
+ // Subscribe to control events and bump tick whenever form state changes.
2148
+ effect(() => {
2149
+ const c = this.ngControl()?.control;
2150
+ if (!c)
2151
+ return;
2152
+ // Angular 17+ provides `events` stream (touched/dirty/etc). Fallback to value/status.
2153
+ const anyCtrl = c;
2154
+ const events$ = anyCtrl.events ?? merge(c.valueChanges ?? of(null), c.statusChanges ?? of(null));
2155
+ events$
2156
+ .pipe(takeUntilDestroyed(this.destroyRef))
2157
+ .subscribe(() => this.bump());
2158
+ // initial bump so computed picks up current state
2159
+ this.bump();
2160
+ });
2161
+ }
2162
+ /* =====================
2163
+ * Auto state from control
2164
+ * ===================== */
2165
+ autoDisabled = computed(() => {
2166
+ this._tick();
2167
+ return !!this.ctrl()?.disabled;
2168
+ }, ...(ngDevMode ? [{ debugName: "autoDisabled" }] : []));
2169
+ autoInvalid = computed(() => {
2170
+ this._tick();
2171
+ const c = this.ctrl();
2172
+ if (!c)
2173
+ return false;
2174
+ // typical UX: show errors after touched or dirty
2175
+ return !!c.invalid && (c.touched || c.dirty);
2176
+ }, ...(ngDevMode ? [{ debugName: "autoInvalid" }] : []));
2177
+ /** Prefer explicit inputs; fall back to form control. */
2178
+ isDisabled = computed(() => this.disabled() ?? this.autoDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2179
+ isInvalid = computed(() => this.invalid() ?? this.autoInvalid(), ...(ngDevMode ? [{ debugName: "isInvalid" }] : []));
2180
+ isRequired = computed(() => this.required(), ...(ngDevMode ? [{ debugName: "isRequired" }] : []));
2181
+ /**
2182
+ * Auto error message:
2183
+ * - manual [error] wins
2184
+ * - otherwise derive from control.errors
2185
+ */
2186
+ errorText = computed(() => {
2187
+ this._tick();
2188
+ const manual = this.error();
2189
+ if (manual)
2190
+ return manual;
2191
+ const c = this.ctrl();
2192
+ if (!c || !this.isInvalid())
2193
+ return '';
2194
+ const errors = c.errors ?? {};
2195
+ if (errors['required'])
2196
+ return 'This field is required';
2197
+ if (errors['email'])
2198
+ return 'Please enter a valid email';
2199
+ if (errors['minlength']?.requiredLength)
2200
+ return `Minimum ${errors['minlength'].requiredLength} characters`;
2201
+ if (errors['maxlength']?.requiredLength)
2202
+ return `Maximum ${errors['maxlength'].requiredLength} characters`;
2203
+ if (errors['min']?.min != null)
2204
+ return `Minimum value is ${errors['min'].min}`;
2205
+ if (errors['max']?.max != null)
2206
+ return `Maximum value is ${errors['max'].max}`;
2207
+ return 'Invalid value';
2208
+ }, ...(ngDevMode ? [{ debugName: "errorText" }] : []));
2209
+ showHint = computed(() => !!this.hint() && !this.errorText(), ...(ngDevMode ? [{ debugName: "showHint" }] : []));
2210
+ showError = computed(() => !!this.errorText(), ...(ngDevMode ? [{ debugName: "showError" }] : []));
2211
+ /* =====================
2212
+ * Classes
2213
+ * ===================== */
2214
+ sizeShell = computed(() => {
2215
+ switch (this.size()) {
2216
+ case 'sm':
2217
+ return 'px-2 py-1 text-sm';
2218
+ case 'lg':
2219
+ return 'px-4 py-3 text-base';
2220
+ case 'md':
2221
+ default:
2222
+ return 'px-3 py-2 text-sm';
2223
+ }
2224
+ }, ...(ngDevMode ? [{ debugName: "sizeShell" }] : []));
2225
+ sizeLabel = computed(() => {
2226
+ switch (this.size()) {
2227
+ case 'sm':
2228
+ return 'text-xs';
2229
+ case 'lg':
2230
+ return 'text-base';
2231
+ case 'md':
2232
+ default:
2233
+ return 'text-sm';
2234
+ }
2235
+ }, ...(ngDevMode ? [{ debugName: "sizeLabel" }] : []));
2236
+ appearanceShell = computed(() => {
2237
+ return this.appearance() === 'filled'
2238
+ ? 'bg-alternate-background border-border'
2239
+ : 'bg-bg border-border';
2240
+ }, ...(ngDevMode ? [{ debugName: "appearanceShell" }] : []));
2241
+ controlShellClasses = computed(() => {
2242
+ // ensure this recomputes when form state changes
2243
+ this._tick();
2244
+ const base = 'w-full flex items-center gap-2 rounded-md border transition text-fg ' +
2245
+ 'focus-within:ring-2 focus-within:ring-primary ' +
2246
+ 'focus-within:ring-offset-2 focus-within:ring-offset-background';
2247
+ const border = this.isInvalid() ? ' border-danger' : '';
2248
+ const ring = this.isInvalid() ? ' focus-within:ring-danger' : '';
2249
+ const disabled = this.isDisabled() ? ' opacity-60 pointer-events-none' : '';
2250
+ return `${base} ${this.sizeShell()} ${this.appearanceShell()}${border}${ring}${disabled} ${this.controlShellKlass()}`.trim();
2251
+ }, ...(ngDevMode ? [{ debugName: "controlShellClasses" }] : []));
2252
+ labelClasses = computed(() => `${this.labelKlass()} ${this.sizeLabel()}`.trim(), ...(ngDevMode ? [{ debugName: "labelClasses" }] : []));
2253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
2254
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngFormField, isStandalone: true, selector: "tng-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, labelKlass: { classPropertyName: "labelKlass", publicName: "labelKlass", isSignal: true, isRequired: false, transformFunction: null }, requiredMarkKlass: { classPropertyName: "requiredMarkKlass", publicName: "requiredMarkKlass", isSignal: true, isRequired: false, transformFunction: null }, hintKlass: { classPropertyName: "hintKlass", publicName: "hintKlass", isSignal: true, isRequired: false, transformFunction: null }, errorKlass: { classPropertyName: "errorKlass", publicName: "errorKlass", isSignal: true, isRequired: false, transformFunction: null }, controlShellKlass: { classPropertyName: "controlShellKlass", publicName: "controlShellKlass", isSignal: true, isRequired: false, transformFunction: null }, prefixKlass: { classPropertyName: "prefixKlass", publicName: "prefixKlass", isSignal: true, isRequired: false, transformFunction: null }, suffixKlass: { classPropertyName: "suffixKlass", publicName: "suffixKlass", isSignal: true, isRequired: false, transformFunction: null }, footerKlass: { classPropertyName: "footerKlass", publicName: "footerKlass", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "ngControl", first: true, predicate: NgControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div [class]=\"rootKlass()\">\n @if (label()) {\n <label class=\"mb-1 block\" [class]=\"labelClasses()\">\n {{ label() }}\n @if (isRequired()) {\n <span [class]=\"requiredMarkKlass()\">&nbsp;*</span>\n }\n </label>\n }\n\n <div [class]=\"controlShellClasses()\">\n <span [class]=\"prefixKlass()\">\n <ng-content select=\"[tngPrefix]\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <ng-content />\n </div>\n\n <span [class]=\"suffixKlass()\">\n <ng-content select=\"[tngSuffix]\" />\n </span>\n </div>\n\n @if (showHint() || showError()) {\n <div [class]=\"footerKlass()\">\n @if (showError()) {\n <div [class]=\"errorKlass()\">{{ errorText() }}</div>\n } @else {\n <div [class]=\"hintKlass()\">{{ hint() }}</div>\n }\n </div>\n }\n</div>\n" });
2255
+ }
2256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFormField, decorators: [{
2257
+ type: Component,
2258
+ args: [{ selector: 'tng-form-field', standalone: true, template: "<div [class]=\"rootKlass()\">\n @if (label()) {\n <label class=\"mb-1 block\" [class]=\"labelClasses()\">\n {{ label() }}\n @if (isRequired()) {\n <span [class]=\"requiredMarkKlass()\">&nbsp;*</span>\n }\n </label>\n }\n\n <div [class]=\"controlShellClasses()\">\n <span [class]=\"prefixKlass()\">\n <ng-content select=\"[tngPrefix]\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <ng-content />\n </div>\n\n <span [class]=\"suffixKlass()\">\n <ng-content select=\"[tngSuffix]\" />\n </span>\n </div>\n\n @if (showHint() || showError()) {\n <div [class]=\"footerKlass()\">\n @if (showError()) {\n <div [class]=\"errorKlass()\">{{ errorText() }}</div>\n } @else {\n <div [class]=\"hintKlass()\">{{ hint() }}</div>\n }\n </div>\n }\n</div>\n" }]
2259
+ }], ctorParameters: () => [], propDecorators: { ngControl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgControl), { ...{ descendants: true }, isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], labelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKlass", required: false }] }], requiredMarkKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredMarkKlass", required: false }] }], hintKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKlass", required: false }] }], errorKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorKlass", required: false }] }], controlShellKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlShellKlass", required: false }] }], prefixKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixKlass", required: false }] }], suffixKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixKlass", required: false }] }], footerKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerKlass", required: false }] }] } });
2260
+
2261
+ class TngNumberInput {
2262
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
2263
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
2264
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
2265
+ klass = input('', ...(ngDevMode ? [{ debugName: "klass" }] : []));
2266
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2267
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
2268
+ // For mobile keypad experience
2269
+ inputmode = input('decimal', ...(ngDevMode ? [{ debugName: "inputmode" }] : []));
2270
+ autocomplete = input('off', ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
2271
+ // numeric constraints
2272
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
2273
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
2274
+ step = input(null, ...(ngDevMode ? [{ debugName: "step" }] : [])); // e.g. 1, 0.01, 'any'
2275
+ _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : []));
2276
+ value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : []));
2277
+ onChange = () => { };
2278
+ onTouched = () => { };
2279
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
2280
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2281
+ writeValue(value) {
2282
+ this._value.set(value ?? null);
2283
+ }
2284
+ registerOnChange(fn) {
2285
+ this.onChange = fn;
2286
+ }
2287
+ registerOnTouched(fn) {
2288
+ this.onTouched = fn;
2289
+ }
2290
+ setDisabledState(isDisabled) {
2291
+ this._formDisabled.set(isDisabled);
2292
+ }
2293
+ classes = computed(() => (`h-10 w-full rounded-md px-3 text-sm ` +
2294
+ `border border-border bg-bg text-foreground ` +
2295
+ `placeholder:text-muted ` +
2296
+ `focus-visible:outline-none ` +
2297
+ `focus-visible:ring-2 focus-visible:ring-primary ` +
2298
+ `focus-visible:ring-offset-2 focus-visible:ring-offset-background ` +
2299
+ `disabled:opacity-50 disabled:pointer-events-none ` +
2300
+ `read-only:bg-muted/30 read-only:text-muted ` +
2301
+ this.klass()).trim(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
2302
+ onInput(event) {
2303
+ const raw = event.target.value;
2304
+ // empty means null
2305
+ if (raw === '') {
2306
+ this._value.set(null);
2307
+ this.onChange(null);
2308
+ return;
2309
+ }
2310
+ const next = Number(raw);
2311
+ // protect forms from NaN
2312
+ if (Number.isNaN(next)) {
2313
+ this._value.set(null);
2314
+ this.onChange(null);
2315
+ return;
2316
+ }
2317
+ this._value.set(next);
2318
+ this.onChange(next);
2319
+ }
2320
+ onBlur() {
2321
+ // optional clamp on blur (Material doesn't clamp automatically, but it's often nice)
2322
+ const v = this._value();
2323
+ if (v !== null) {
2324
+ const min = this.min();
2325
+ const max = this.max();
2326
+ let clamped = v;
2327
+ if (min !== null)
2328
+ clamped = Math.max(min, clamped);
2329
+ if (max !== null)
2330
+ clamped = Math.min(max, clamped);
2331
+ if (clamped !== v) {
2332
+ this._value.set(clamped);
2333
+ this.onChange(clamped);
2334
+ }
2335
+ }
2336
+ this.onTouched();
2337
+ }
2338
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngNumberInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2339
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngNumberInput, isStandalone: true, selector: "tng-number-input", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, klass: { classPropertyName: "klass", publicName: "klass", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
2340
+ {
2341
+ provide: NG_VALUE_ACCESSOR,
2342
+ useExisting: forwardRef(() => TngNumberInput),
2343
+ multi: true,
2344
+ },
2345
+ ], ngImport: i0, template: "<input\n type=\"number\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n [class]=\"classes()\"\n [value]=\"value() ?? ''\"\n [attr.placeholder]=\"placeholder() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n/>\n" });
2346
+ }
2347
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngNumberInput, decorators: [{
2348
+ type: Component,
2349
+ args: [{ selector: 'tng-number-input', standalone: true, providers: [
2350
+ {
2351
+ provide: NG_VALUE_ACCESSOR,
2352
+ useExisting: forwardRef(() => TngNumberInput),
2353
+ multi: true,
2354
+ },
2355
+ ], template: "<input\n type=\"number\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n [class]=\"classes()\"\n [value]=\"value() ?? ''\"\n [attr.placeholder]=\"placeholder() || null\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n/>\n" }]
2356
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], klass: [{ type: i0.Input, args: [{ isSignal: true, alias: "klass", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }] } });
2357
+
2358
+ class TngRadioButton {
2359
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
2360
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
2361
+ value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
2362
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2363
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2364
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
2365
+ /* =====================
2366
+ * Theming / class hooks (section-wise)
2367
+ * ===================== */
2368
+ /** Root <label> */
2369
+ rootKlass = input('inline-flex items-center gap-2 cursor-pointer select-none', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
2370
+ /** <input type="radio"> */
2371
+ inputKlass = input('', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
2372
+ /** Label <span> */
2373
+ labelKlass = input('text-sm text-fg', ...(ngDevMode ? [{ debugName: "labelKlass" }] : []));
2374
+ _formValue = signal(null, ...(ngDevMode ? [{ debugName: "_formValue" }] : []));
2375
+ isChecked = computed(() => this._formValue() === this.value(), ...(ngDevMode ? [{ debugName: "isChecked" }] : []));
2376
+ onChange = () => { };
2377
+ onTouched = () => { };
2378
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
2379
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2380
+ writeValue(value) {
2381
+ this._formValue.set(value ?? null);
2382
+ }
2383
+ registerOnChange(fn) {
2384
+ this.onChange = fn;
2385
+ }
2386
+ registerOnTouched(fn) {
2387
+ this.onTouched = fn;
2388
+ }
2389
+ setDisabledState(isDisabled) {
2390
+ this._formDisabled.set(isDisabled);
2391
+ }
2392
+ /** Final classes */
2393
+ classes = computed(() => (
2394
+ // size + shape
2395
+ `h-4 w-4 rounded-full ` +
2396
+ // theme tokens
2397
+ `border border-border bg-bg ` +
2398
+ `accent-primary ` +
2399
+ // focus ring
2400
+ `focus-visible:outline-none ` +
2401
+ `focus-visible:ring-2 focus-visible:ring-primary ` +
2402
+ `focus-visible:ring-offset-2 focus-visible:ring-offset-background ` +
2403
+ // disabled
2404
+ `disabled:opacity-50 disabled:pointer-events-none ` +
2405
+ // user override
2406
+ this.inputKlass()).trim(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
2407
+ onSelect(event) {
2408
+ if (this.isDisabled())
2409
+ return;
2410
+ const checked = event.target.checked;
2411
+ if (checked) {
2412
+ this._formValue.set(this.value());
2413
+ this.onChange(this.value());
2414
+ this.onTouched();
2415
+ }
2416
+ }
2417
+ onBlur() {
2418
+ this.onTouched();
2419
+ }
2420
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngRadioButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
2421
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngRadioButton, isStandalone: true, selector: "tng-radio-button", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null }, labelKlass: { classPropertyName: "labelKlass", publicName: "labelKlass", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
2422
+ {
2423
+ provide: NG_VALUE_ACCESSOR,
2424
+ useExisting: forwardRef(() => TngRadioButton),
2425
+ multi: true,
2426
+ },
2427
+ ], ngImport: i0, template: "<label [class]=\"rootKlass()\">\n <input\n type=\"radio\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [value]=\"value()\"\n [checked]=\"isChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [class]=\"classes()\"\n (change)=\"onSelect($event)\"\n (blur)=\"onBlur()\"\n />\n\n @if (label()) {\n <span [class]=\"labelKlass()\">\n {{ label() }}\n </span>\n }\n</label>\n" });
2428
+ }
2429
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngRadioButton, decorators: [{
2430
+ type: Component,
2431
+ args: [{ selector: 'tng-radio-button', standalone: true, providers: [
2432
+ {
2433
+ provide: NG_VALUE_ACCESSOR,
2434
+ useExisting: forwardRef(() => TngRadioButton),
2435
+ multi: true,
2436
+ },
2437
+ ], template: "<label [class]=\"rootKlass()\">\n <input\n type=\"radio\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [value]=\"value()\"\n [checked]=\"isChecked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [class]=\"classes()\"\n (change)=\"onSelect($event)\"\n (blur)=\"onBlur()\"\n />\n\n @if (label()) {\n <span [class]=\"labelKlass()\">\n {{ label() }}\n </span>\n }\n</label>\n" }]
2438
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }], labelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKlass", required: false }] }] } });
2439
+
2440
+ class TngSelect {
2441
+ /* =====================
2442
+ * Projected templates
2443
+ * ===================== */
2444
+ optionTpl;
2445
+ valueTpl;
2446
+ triggerEl;
2447
+ // Delegate list navigation keys to OptionList
2448
+ optionList;
2449
+ /* =====================
2450
+ * Inputs / Outputs
2451
+ * ===================== */
2452
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
2453
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
2454
+ placeholder = input('Select…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
2455
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2456
+ displayWith = input((v) => String(v), ...(ngDevMode ? [{ debugName: "displayWith" }] : []));
2457
+ selected = output();
2458
+ closed = output();
2459
+ /* =====================
2460
+ * Theming
2461
+ * ===================== */
2462
+ rootKlass = input('relative', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
2463
+ triggerKlass = input([
2464
+ 'w-full',
2465
+ 'flex items-center justify-between',
2466
+ 'border border-border rounded-md',
2467
+ 'px-3 py-2',
2468
+ 'text-sm',
2469
+ 'bg-bg text-fg',
2470
+ 'focus:outline-none',
2471
+ 'focus:ring-2 focus:ring-primary',
2472
+ ].join(' '), ...(ngDevMode ? [{ debugName: "triggerKlass" }] : []));
2473
+ valueKlass = input('truncate text-left', ...(ngDevMode ? [{ debugName: "valueKlass" }] : []));
2474
+ placeholderKlass = input('text-disable', ...(ngDevMode ? [{ debugName: "placeholderKlass" }] : []));
2475
+ iconKlass = input('ml-2 text-disable', ...(ngDevMode ? [{ debugName: "iconKlass" }] : []));
2476
+ /* =====================
2477
+ * State
2478
+ * ===================== */
2479
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
2480
+ activeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
2481
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2482
+ selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "selectedValue" }] : []));
2483
+ usingCva = false;
2484
+ /* =====================
2485
+ * CVA
2486
+ * ===================== */
2487
+ onChange = () => { };
2488
+ onTouched = () => { };
2489
+ constructor() {
2490
+ effect(() => {
2491
+ this.isDisabled.set(this.disabled());
2492
+ if (this.isDisabled())
2493
+ this.close('programmatic');
2494
+ });
2495
+ effect(() => {
2496
+ const v = this.value();
2497
+ if (this.usingCva)
2498
+ return;
2499
+ this.selectedValue.set(v);
2500
+ });
2501
+ }
2502
+ writeValue(value) {
2503
+ this.usingCva = true;
2504
+ this.selectedValue.set(value);
2505
+ }
2506
+ registerOnChange(fn) {
2507
+ this.usingCva = true;
2508
+ this.onChange = fn;
2509
+ }
2510
+ registerOnTouched(fn) {
2511
+ this.onTouched = fn;
2512
+ }
2513
+ setDisabledState(isDisabled) {
2514
+ this.isDisabled.set(isDisabled);
2515
+ if (isDisabled)
2516
+ this.close('programmatic');
2517
+ }
2518
+ /* =====================
2519
+ * Helpers
2520
+ * ===================== */
2521
+ display(item) {
2522
+ return item == null ? '' : this.displayWith()(item);
2523
+ }
2524
+ currentValue() {
2525
+ return this.selectedValue();
2526
+ }
2527
+ triggerClasses = computed(() => (this.triggerKlass() + (this.isDisabled() ? ' opacity-60 pointer-events-none' : '')).trim(), ...(ngDevMode ? [{ debugName: "triggerClasses" }] : []));
2528
+ /* =====================
2529
+ * Overlay state
2530
+ * ===================== */
2531
+ open(_reason) {
2532
+ if (this.isDisabled())
2533
+ return;
2534
+ this.isOpen.set(true);
2535
+ const current = this.selectedValue();
2536
+ if (current != null) {
2537
+ const idx = this.options().indexOf(current);
2538
+ this.activeIndex.set(idx >= 0 ? idx : 0);
2539
+ }
2540
+ else {
2541
+ this.activeIndex.set(this.options().length ? 0 : -1);
2542
+ }
2543
+ void _reason;
2544
+ }
2545
+ close(reason) {
2546
+ if (!this.isOpen())
2547
+ return;
2548
+ this.isOpen.set(false);
2549
+ this.activeIndex.set(-1);
2550
+ this.closed.emit(reason);
2551
+ queueMicrotask(() => {
2552
+ this.triggerEl.nativeElement.focus();
2553
+ });
2554
+ }
2555
+ onOverlayOpenChange(open) {
2556
+ if (this.isDisabled()) {
2557
+ this.isOpen.set(false);
2558
+ return;
2559
+ }
2560
+ if (open)
2561
+ this.open('programmatic');
2562
+ else
2563
+ this.close('programmatic');
2564
+ }
2565
+ onOverlayClosed(reason) {
2566
+ this.close(reason);
2567
+ }
2568
+ /* =====================
2569
+ * UI Events
2570
+ * ===================== */
2571
+ onTriggerClick() {
2572
+ if (this.isDisabled())
2573
+ return;
2574
+ this.isOpen() ? this.close('programmatic') : this.open('programmatic');
2575
+ }
2576
+ onBlur() {
2577
+ this.onTouched();
2578
+ // minimal safer blur (prevents weird immediate refocus issues)
2579
+ queueMicrotask(() => {
2580
+ if (document.activeElement === this.triggerEl.nativeElement)
2581
+ return;
2582
+ this.close('blur');
2583
+ });
2584
+ }
2585
+ onKeydown(ev) {
2586
+ if (this.isDisabled())
2587
+ return;
2588
+ // Escape closes
2589
+ if (ev.key === 'Escape' && this.isOpen()) {
2590
+ ev.preventDefault();
2591
+ ev.stopPropagation();
2592
+ this.close('escape');
2593
+ return;
2594
+ }
2595
+ // Open on ArrowDown/ArrowUp when closed (and delegate once mounted)
2596
+ if (!this.isOpen() && (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')) {
2597
+ ev.preventDefault();
2598
+ ev.stopPropagation();
2599
+ this.open('programmatic');
2600
+ requestAnimationFrame(() => {
2601
+ const replay = this.cloneKeyboardEvent(ev);
2602
+ this.optionList?.onKeydown(replay);
2603
+ });
2604
+ return;
2605
+ }
2606
+ if (!this.isOpen())
2607
+ return;
2608
+ // Delegate list-navigation keys to OptionList (including Enter)
2609
+ if (!this.isListNavigationKey(ev))
2610
+ return;
2611
+ // WARNING: DO NOT preventDefault here — OptionList ignores defaultPrevented events
2612
+ ev.stopPropagation();
2613
+ this.optionList?.onKeydown(ev);
2614
+ }
2615
+ isListNavigationKey(ev) {
2616
+ switch (ev.key) {
2617
+ case 'ArrowDown':
2618
+ case 'ArrowUp':
2619
+ case 'Home':
2620
+ case 'End':
2621
+ case 'PageDown':
2622
+ case 'PageUp':
2623
+ case 'Enter':
2624
+ return true;
2625
+ default:
2626
+ return false;
2627
+ }
2628
+ }
2629
+ cloneKeyboardEvent(ev) {
2630
+ return new KeyboardEvent(ev.type, {
2631
+ key: ev.key,
2632
+ code: ev.code,
2633
+ location: ev.location,
2634
+ repeat: ev.repeat,
2635
+ ctrlKey: ev.ctrlKey,
2636
+ shiftKey: ev.shiftKey,
2637
+ altKey: ev.altKey,
2638
+ metaKey: ev.metaKey,
2639
+ bubbles: true,
2640
+ cancelable: true,
2641
+ });
2642
+ }
2643
+ /* =====================
2644
+ * OptionList wiring
2645
+ * ===================== */
2646
+ onActiveIndexChange(i) {
2647
+ this.activeIndex.set(i);
2648
+ }
2649
+ requestSelectActive() {
2650
+ const i = this.activeIndex();
2651
+ const item = this.options()[i];
2652
+ if (item !== undefined)
2653
+ this.select(item);
2654
+ }
2655
+ /* =====================
2656
+ * Selection
2657
+ * ===================== */
2658
+ select(item) {
2659
+ if (this.isDisabled())
2660
+ return;
2661
+ this.selectedValue.set(item);
2662
+ this.onChange(item);
2663
+ this.onTouched();
2664
+ this.selected.emit(item);
2665
+ this.close('selection');
2666
+ }
2667
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
2668
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngSelect, isStandalone: true, selector: "tng-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, triggerKlass: { classPropertyName: "triggerKlass", publicName: "triggerKlass", isSignal: true, isRequired: false, transformFunction: null }, valueKlass: { classPropertyName: "valueKlass", publicName: "valueKlass", isSignal: true, isRequired: false, transformFunction: null }, placeholderKlass: { classPropertyName: "placeholderKlass", publicName: "placeholderKlass", isSignal: true, isRequired: false, transformFunction: null }, iconKlass: { classPropertyName: "iconKlass", publicName: "iconKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected", closed: "closed" }, providers: [
2669
+ {
2670
+ provide: NG_VALUE_ACCESSOR,
2671
+ useExisting: forwardRef(() => TngSelect),
2672
+ multi: true,
2673
+ },
2674
+ ], queries: [{ propertyName: "optionTpl", first: true, predicate: ["optionTpl"], descendants: true, read: TemplateRef }, { propertyName: "valueTpl", first: true, predicate: ["valueTpl"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["triggerEl"], descendants: true, static: true }, { propertyName: "optionList", first: true, predicate: TngOptionList, descendants: true }], ngImport: i0, template: "<div [class]=\"rootKlass()\">\n <!-- Trigger -->\n <button\n #triggerEl\n type=\"button\"\n [class]=\"triggerClasses()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n >\n <span [class]=\"valueKlass()\">\n @if (currentValue()) {\n @if (valueTpl) {\n <ng-container\n [ngTemplateOutlet]=\"valueTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: currentValue()! }\"\n />\n } @else {\n {{ display(currentValue()) }}\n }\n } @else {\n <span [class]=\"placeholderKlass()\">{{ placeholder() }}</span>\n }\n </span>\n\n <span [class]=\"iconKlass()\">\u25BE</span>\n </button>\n\n <!-- Overlay -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"triggerEl\"\n placement=\"bottom-start\"\n width=\"anchor\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel>\n <tng-option-list\n [items]=\"options()\"\n [activeIndex]=\"activeIndex()\"\n [displayWith]=\"displayWith()\"\n [optionTemplate]=\"optionTpl ?? null\"\n [loop]=\"false\"\n [typeahead]=\"false\"\n (activeIndexChange)=\"onActiveIndexChange($event)\"\n (requestSelectActive)=\"requestSelectActive()\"\n (optionMouseDown)=\"select($event.item)\"\n (optionHover)=\"activeIndex.set($event)\"\n />\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TngConnectedOverlay, selector: "tng-connected-overlay", inputs: ["open", "anchor", "placement", "offset", "width", "closeOnOutsideClick", "closeOnInsideClick", "closeOnEscape", "hasBackdrop", "backdropClass"], outputs: ["opened", "closed", "backdropClick"] }, { kind: "component", type: TngOverlayPanel, selector: "tng-overlay-panel", inputs: ["klass", "modal", "role", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "restoreFocus", "autoCapture", "deferCaptureElements"] }, { kind: "component", type: TngOptionList, selector: "tng-option-list", inputs: ["modal", "panelKlass", "restoreFocus", "autoCapture", "deferCaptureElements", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "tabindex", "optionTemplate", "items", "activeIndex", "displayWith", "emptyText", "containerKlass", "optionKlass", "optionActiveKlass", "optionInactiveKlass", "emptyKlass", "propagateKeys", "keyboard", "loop", "selectOnEnter", "typeahead", "typeaheadMode", "typeaheadResetMs", "pageJumpSize"], outputs: ["optionMouseDown", "optionHover", "activeIndexChange", "requestSelectActive", "requestTypeaheadMatch", "keyStroke"] }, { kind: "component", type: TngOverlayRef, selector: "tng-overlay-ref", inputs: ["open"], outputs: ["openChange", "opened", "closed"], exportAs: ["tngOverlayRef"] }] });
2675
+ }
2676
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSelect, decorators: [{
2677
+ type: Component,
2678
+ args: [{ selector: 'tng-select', standalone: true, imports: [
2679
+ NgTemplateOutlet,
2680
+ TngConnectedOverlay,
2681
+ TngOverlayPanel,
2682
+ TngOptionList,
2683
+ TngOverlayRef,
2684
+ ], providers: [
2685
+ {
2686
+ provide: NG_VALUE_ACCESSOR,
2687
+ useExisting: forwardRef(() => TngSelect),
2688
+ multi: true,
2689
+ },
2690
+ ], template: "<div [class]=\"rootKlass()\">\n <!-- Trigger -->\n <button\n #triggerEl\n type=\"button\"\n [class]=\"triggerClasses()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n >\n <span [class]=\"valueKlass()\">\n @if (currentValue()) {\n @if (valueTpl) {\n <ng-container\n [ngTemplateOutlet]=\"valueTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: currentValue()! }\"\n />\n } @else {\n {{ display(currentValue()) }}\n }\n } @else {\n <span [class]=\"placeholderKlass()\">{{ placeholder() }}</span>\n }\n </span>\n\n <span [class]=\"iconKlass()\">\u25BE</span>\n </button>\n\n <!-- Overlay -->\n <tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed($event)\"\n >\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"triggerEl\"\n placement=\"bottom-start\"\n width=\"anchor\"\n (closed)=\"overlayRef.close($event)\"\n >\n <tng-overlay-panel>\n <tng-option-list\n [items]=\"options()\"\n [activeIndex]=\"activeIndex()\"\n [displayWith]=\"displayWith()\"\n [optionTemplate]=\"optionTpl ?? null\"\n [loop]=\"false\"\n [typeahead]=\"false\"\n (activeIndexChange)=\"onActiveIndexChange($event)\"\n (requestSelectActive)=\"requestSelectActive()\"\n (optionMouseDown)=\"select($event.item)\"\n (optionHover)=\"activeIndex.set($event)\"\n />\n </tng-overlay-panel>\n </tng-connected-overlay>\n </tng-overlay-ref>\n</div>\n" }]
2691
+ }], ctorParameters: () => [], propDecorators: { optionTpl: [{
2692
+ type: ContentChild,
2693
+ args: ['optionTpl', { read: TemplateRef }]
2694
+ }], valueTpl: [{
2695
+ type: ContentChild,
2696
+ args: ['valueTpl', { read: TemplateRef }]
2697
+ }], triggerEl: [{
2698
+ type: ViewChild,
2699
+ args: ['triggerEl', { static: true }]
2700
+ }], optionList: [{
2701
+ type: ViewChild,
2702
+ args: [TngOptionList]
2703
+ }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], closed: [{ type: i0.Output, args: ["closed"] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], triggerKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerKlass", required: false }] }], valueKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKlass", required: false }] }], placeholderKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderKlass", required: false }] }], iconKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconKlass", required: false }] }] } });
2704
+
2705
+ class TngSlider {
2706
+ // ids
2707
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
2708
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
2709
+ // label / accessibility
2710
+ ariaLabel = input('Slider', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
2711
+ // slider config
2712
+ min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : []));
2713
+ max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : []));
2714
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : []));
2715
+ // disabled
2716
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2717
+ /**
2718
+ * Controlled usage (Angular 21 signals):
2719
+ * <tng-slider [value]="v()" (valueChange)="v.set($event)" />
2720
+ *
2721
+ * When `value` is provided (not null), it becomes the source of truth.
2722
+ */
2723
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
2724
+ valueChange = output();
2725
+ // theming / section-wise klass hooks
2726
+ rootKlass = input('w-full', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
2727
+ labelKlass = input('text-sm text-fg', ...(ngDevMode ? [{ debugName: "labelKlass" }] : []));
2728
+ trackKlass = input('', ...(ngDevMode ? [{ debugName: "trackKlass" }] : [])); // base track
2729
+ fillKlass = input('', ...(ngDevMode ? [{ debugName: "fillKlass" }] : [])); // filled part
2730
+ thumbKlass = input('', ...(ngDevMode ? [{ debugName: "thumbKlass" }] : [])); // thumb
2731
+ rangeKlass = input('', ...(ngDevMode ? [{ debugName: "rangeKlass" }] : [])); // native input
2732
+ _value = signal(0, ...(ngDevMode ? [{ debugName: "_value" }] : []));
2733
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
2734
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2735
+ onChange = () => { };
2736
+ onTouched = () => { };
2737
+ /** effective value: controlled `value` wins when provided */
2738
+ currentValue = computed(() => {
2739
+ const controlled = this.value();
2740
+ return controlled ?? this._value();
2741
+ }, ...(ngDevMode ? [{ debugName: "currentValue" }] : []));
2742
+ clampedValue = computed(() => {
2743
+ const min = this.min();
2744
+ const max = this.max();
2745
+ const v = this.currentValue();
2746
+ return Math.min(max, Math.max(min, v));
2747
+ }, ...(ngDevMode ? [{ debugName: "clampedValue" }] : []));
2748
+ percent = computed(() => {
2749
+ const min = this.min();
2750
+ const max = this.max();
2751
+ const v = this.clampedValue();
2752
+ const span = max - min;
2753
+ if (span <= 0)
2754
+ return 0;
2755
+ return ((v - min) / span) * 100;
2756
+ }, ...(ngDevMode ? [{ debugName: "percent" }] : []));
2757
+ // classes (Tailng theme tokens)
2758
+ trackClasses = computed(() => {
2759
+ const base = 'relative h-2 w-full rounded-full border border-primary bg-on-primary';
2760
+ const disabled = this.isDisabled() ? ' opacity-60 pointer-events-none' : '';
2761
+ return `${base}${disabled} ${this.trackKlass()}`.trim();
2762
+ }, ...(ngDevMode ? [{ debugName: "trackClasses" }] : []));
2763
+ fillClasses = computed(() => {
2764
+ const base = 'absolute left-0 top-0 h-full rounded-full bg-primary';
2765
+ return `${base} ${this.fillKlass()}`.trim();
2766
+ }, ...(ngDevMode ? [{ debugName: "fillClasses" }] : []));
2767
+ thumbClasses = computed(() => {
2768
+ const base = 'absolute top-1/2 -translate-y-1/2 h-4 w-4 rounded-full bg-primary border border-primary ' +
2769
+ 'shadow transition-transform';
2770
+ const disabled = this.isDisabled() ? ' opacity-60 pointer-events-none' : '';
2771
+ return `${base}${disabled} ${this.thumbKlass()}`.trim();
2772
+ }, ...(ngDevMode ? [{ debugName: "thumbClasses" }] : []));
2773
+ rangeClasses = computed(() => {
2774
+ // make native range invisible but still interactive + keyboard accessible
2775
+ const base = 'absolute inset-0 h-full w-full cursor-pointer opacity-0 ' +
2776
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ' +
2777
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-background';
2778
+ const disabled = this.isDisabled() ? ' cursor-not-allowed' : '';
2779
+ return `${base}${disabled} ${this.rangeKlass()}`.trim();
2780
+ }, ...(ngDevMode ? [{ debugName: "rangeClasses" }] : []));
2781
+ /* =====================
2782
+ * CVA
2783
+ * ===================== */
2784
+ writeValue(value) {
2785
+ this._value.set(value ?? this.min());
2786
+ }
2787
+ registerOnChange(fn) {
2788
+ this.onChange = fn;
2789
+ }
2790
+ registerOnTouched(fn) {
2791
+ this.onTouched = fn;
2792
+ }
2793
+ setDisabledState(isDisabled) {
2794
+ this._formDisabled.set(isDisabled);
2795
+ }
2796
+ /* =====================
2797
+ * Events
2798
+ * ===================== */
2799
+ onInput(ev) {
2800
+ if (this.isDisabled())
2801
+ return;
2802
+ const raw = ev.target.value;
2803
+ const next = Number(raw);
2804
+ // internal (CVA path)
2805
+ this._value.set(next);
2806
+ this.onChange(next);
2807
+ // controlled (signal) path
2808
+ this.valueChange.emit(next);
2809
+ }
2810
+ onBlur() {
2811
+ this.onTouched();
2812
+ }
2813
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSlider, deps: [], target: i0.ɵɵFactoryTarget.Component });
2814
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngSlider, isStandalone: true, selector: "tng-slider", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, labelKlass: { classPropertyName: "labelKlass", publicName: "labelKlass", isSignal: true, isRequired: false, transformFunction: null }, trackKlass: { classPropertyName: "trackKlass", publicName: "trackKlass", isSignal: true, isRequired: false, transformFunction: null }, fillKlass: { classPropertyName: "fillKlass", publicName: "fillKlass", isSignal: true, isRequired: false, transformFunction: null }, thumbKlass: { classPropertyName: "thumbKlass", publicName: "thumbKlass", isSignal: true, isRequired: false, transformFunction: null }, rangeKlass: { classPropertyName: "rangeKlass", publicName: "rangeKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, providers: [
2815
+ {
2816
+ provide: NG_VALUE_ACCESSOR,
2817
+ useExisting: forwardRef(() => TngSlider),
2818
+ multi: true,
2819
+ },
2820
+ ], ngImport: i0, template: "<div [class]=\"rootKlass()\">\n <!-- Visual slider -->\n <div class=\"relative w-full h-6\">\n <!-- Track -->\n <div [class]=\"trackClasses()\">\n <!-- Fill -->\n <div\n [class]=\"fillClasses()\"\n [style.width.%]=\"percent()\"\n ></div>\n\n <!-- Thumb -->\n <div\n [class]=\"thumbClasses()\"\n [style.left.%]=\"percent()\"\n style=\"transform: translate(-50%, -50%);\"\n ></div>\n\n <!-- Native range (interactive) -->\n <input\n type=\"range\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [value]=\"clampedValue()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [class]=\"rangeClasses()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n />\n </div>\n </div>\n\n <!-- Value (optional simple display; remove if you don't want it) -->\n <div class=\"mt-1 text-xs text-disable\">\n {{ clampedValue() }}\n </div>\n</div>\n" });
2821
+ }
2822
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSlider, decorators: [{
2823
+ type: Component,
2824
+ args: [{ selector: 'tng-slider', standalone: true, providers: [
2825
+ {
2826
+ provide: NG_VALUE_ACCESSOR,
2827
+ useExisting: forwardRef(() => TngSlider),
2828
+ multi: true,
2829
+ },
2830
+ ], template: "<div [class]=\"rootKlass()\">\n <!-- Visual slider -->\n <div class=\"relative w-full h-6\">\n <!-- Track -->\n <div [class]=\"trackClasses()\">\n <!-- Fill -->\n <div\n [class]=\"fillClasses()\"\n [style.width.%]=\"percent()\"\n ></div>\n\n <!-- Thumb -->\n <div\n [class]=\"thumbClasses()\"\n [style.left.%]=\"percent()\"\n style=\"transform: translate(-50%, -50%);\"\n ></div>\n\n <!-- Native range (interactive) -->\n <input\n type=\"range\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [value]=\"clampedValue()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"ariaLabel()\"\n [class]=\"rangeClasses()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n />\n </div>\n </div>\n\n <!-- Value (optional simple display; remove if you don't want it) -->\n <div class=\"mt-1 text-xs text-disable\">\n {{ clampedValue() }}\n </div>\n</div>\n" }]
2831
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], labelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKlass", required: false }] }], trackKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackKlass", required: false }] }], fillKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillKlass", required: false }] }], thumbKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbKlass", required: false }] }], rangeKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeKlass", required: false }] }] } });
2832
+
2833
+ class TngSlideToggle {
2834
+ // ids
2835
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
2836
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
2837
+ // texts
2838
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
2839
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2840
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
2841
+ // allow controlled usage (signals) in addition to CVA
2842
+ checked = input(null, ...(ngDevMode ? [{ debugName: "checked" }] : []));
2843
+ checkedChange = output();
2844
+ // theming / class hooks (section-wise)
2845
+ rootKlass = input('inline-flex items-center gap-2 select-none', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
2846
+ trackKlass = input('', ...(ngDevMode ? [{ debugName: "trackKlass" }] : []));
2847
+ thumbKlass = input('', ...(ngDevMode ? [{ debugName: "thumbKlass" }] : []));
2848
+ labelKlass = input('text-sm text-fg', ...(ngDevMode ? [{ debugName: "labelKlass" }] : []));
2849
+ inputKlass = input('sr-only', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
2850
+ _value = signal(false, ...(ngDevMode ? [{ debugName: "_value" }] : []));
2851
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
2852
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2853
+ onChange = () => { };
2854
+ onTouched = () => { };
2855
+ /** effective value: controlled `checked` wins when provided */
2856
+ value = computed(() => (this.checked() ?? this._value()), ...(ngDevMode ? [{ debugName: "value" }] : []));
2857
+ writeValue(value) {
2858
+ this._value.set(value ?? false);
2859
+ }
2860
+ registerOnChange(fn) {
2861
+ this.onChange = fn;
2862
+ }
2863
+ registerOnTouched(fn) {
2864
+ this.onTouched = fn;
2865
+ }
2866
+ setDisabledState(isDisabled) {
2867
+ this._formDisabled.set(isDisabled);
2868
+ }
2869
+ trackClasses = computed(() => {
2870
+ const base = 'relative inline-flex h-6 w-11 items-center rounded-full border transition-colors duration-200 ' +
2871
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ' +
2872
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-background';
2873
+ const stateOn = 'bg-primary border-primary';
2874
+ const stateOff = 'bg-on-primary border-primary';
2875
+ const disabled = this.isDisabled() ? ' opacity-60 pointer-events-none' : '';
2876
+ const state = this.value() ? stateOn : stateOff;
2877
+ return `${base} ${state}${disabled} ${this.trackKlass()}`.trim();
2878
+ }, ...(ngDevMode ? [{ debugName: "trackClasses" }] : []));
2879
+ thumbClasses = computed(() => {
2880
+ const base = 'inline-block h-5 w-5 rounded-full shadow transition-transform duration-200';
2881
+ const posOn = 'translate-x-5';
2882
+ const posOff = 'translate-x-1';
2883
+ const colorOn = 'bg-on-primary';
2884
+ const colorOff = 'bg-primary';
2885
+ const pos = this.value() ? posOn : posOff;
2886
+ const color = this.value() ? colorOn : colorOff;
2887
+ return `${base} ${pos} ${color} ${this.thumbKlass()}`.trim();
2888
+ }, ...(ngDevMode ? [{ debugName: "thumbClasses" }] : []));
2889
+ onToggle(ev) {
2890
+ if (this.isDisabled())
2891
+ return;
2892
+ const next = ev.target.checked;
2893
+ // internal (CVA path)
2894
+ this._value.set(next);
2895
+ this.onChange(next);
2896
+ this.onTouched();
2897
+ // controlled usage (signal binding path)
2898
+ this.checkedChange.emit(next);
2899
+ }
2900
+ onBlur() {
2901
+ this.onTouched();
2902
+ }
2903
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSlideToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
2904
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngSlideToggle, isStandalone: true, selector: "tng-slide-toggle", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, trackKlass: { classPropertyName: "trackKlass", publicName: "trackKlass", isSignal: true, isRequired: false, transformFunction: null }, thumbKlass: { classPropertyName: "thumbKlass", publicName: "thumbKlass", isSignal: true, isRequired: false, transformFunction: null }, labelKlass: { classPropertyName: "labelKlass", publicName: "labelKlass", isSignal: true, isRequired: false, transformFunction: null }, inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checkedChange: "checkedChange" }, providers: [
2905
+ {
2906
+ provide: NG_VALUE_ACCESSOR,
2907
+ useExisting: forwardRef(() => TngSlideToggle),
2908
+ multi: true,
2909
+ },
2910
+ ], ngImport: i0, template: "<label [class]=\"rootKlass()\">\n <input\n type=\"checkbox\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [checked]=\"value()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [class]=\"inputKlass()\"\n (change)=\"onToggle($event)\"\n (blur)=\"onBlur()\"\n />\n\n <span [class]=\"trackClasses()\" role=\"switch\" [attr.aria-checked]=\"value()\">\n <span [class]=\"thumbClasses()\"></span>\n </span>\n\n @if (label()) {\n <span [class]=\"labelKlass()\">\n {{ label() }}\n </span>\n }\n</label>\n" });
2911
+ }
2912
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSlideToggle, decorators: [{
2913
+ type: Component,
2914
+ args: [{ selector: 'tng-slide-toggle', standalone: true, providers: [
2915
+ {
2916
+ provide: NG_VALUE_ACCESSOR,
2917
+ useExisting: forwardRef(() => TngSlideToggle),
2918
+ multi: true,
2919
+ },
2920
+ ], template: "<label [class]=\"rootKlass()\">\n <input\n type=\"checkbox\"\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [checked]=\"value()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [class]=\"inputKlass()\"\n (change)=\"onToggle($event)\"\n (blur)=\"onBlur()\"\n />\n\n <span [class]=\"trackClasses()\" role=\"switch\" [attr.aria-checked]=\"value()\">\n <span [class]=\"thumbClasses()\"></span>\n </span>\n\n @if (label()) {\n <span [class]=\"labelKlass()\">\n {{ label() }}\n </span>\n }\n</label>\n" }]
2921
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], checkedChange: [{ type: i0.Output, args: ["checkedChange"] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], trackKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackKlass", required: false }] }], thumbKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbKlass", required: false }] }], labelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKlass", required: false }] }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }] } });
2922
+
2923
+ class TngTextarea {
2924
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
2925
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2926
+ rows = input(4, ...(ngDevMode ? [{ debugName: "rows" }] : []));
2927
+ klass = input('', ...(ngDevMode ? [{ debugName: "klass" }] : []));
2928
+ _value = signal('', ...(ngDevMode ? [{ debugName: "_value" }] : []));
2929
+ value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : []));
2930
+ onChange = () => { };
2931
+ onTouched = () => { };
2932
+ writeValue(value) {
2933
+ this._value.set(value ?? '');
2934
+ }
2935
+ registerOnChange(fn) {
2936
+ this.onChange = fn;
2937
+ }
2938
+ registerOnTouched(fn) {
2939
+ this.onTouched = fn;
2940
+ }
2941
+ setDisabledState(isDisabled) {
2942
+ this._formDisabled.set(isDisabled);
2943
+ }
2944
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
2945
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
2946
+ classes = computed(() => (`w-full rounded-md border border-gray-300 px-3 py-2 text-sm ` +
2947
+ `focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary ` +
2948
+ `disabled:opacity-50 disabled:pointer-events-none ${this.klass()}`).trim(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
2949
+ onInput(event) {
2950
+ const next = event.target.value;
2951
+ this._value.set(next);
2952
+ this.onChange(next);
2953
+ }
2954
+ onBlur() {
2955
+ this.onTouched();
2956
+ }
2957
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
2958
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngTextarea, isStandalone: true, selector: "tng-textarea", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, klass: { classPropertyName: "klass", publicName: "klass", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
2959
+ {
2960
+ provide: NG_VALUE_ACCESSOR,
2961
+ useExisting: forwardRef(() => TngTextarea),
2962
+ multi: true,
2963
+ },
2964
+ ], ngImport: i0, template: "<textarea\n [disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder()\"\n [rows]=\"rows()\"\n [class]=\"classes()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n></textarea>\n\n" });
2965
+ }
2966
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTextarea, decorators: [{
2967
+ type: Component,
2968
+ args: [{ selector: 'tng-textarea', standalone: true, providers: [
2969
+ {
2970
+ provide: NG_VALUE_ACCESSOR,
2971
+ useExisting: forwardRef(() => TngTextarea),
2972
+ multi: true,
2973
+ },
2974
+ ], template: "<textarea\n [disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder()\"\n [rows]=\"rows()\"\n [class]=\"classes()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n></textarea>\n\n" }]
2975
+ }], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], klass: [{ type: i0.Input, args: [{ isSignal: true, alias: "klass", required: false }] }] } });
2976
+
2977
+ class TngTextInput {
2978
+ /* ─────────────────────────
2979
+ * Inputs (public API)
2980
+ * ───────────────────────── */
2981
+ id = input('', ...(ngDevMode ? [{ debugName: "id" }] : []));
2982
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : []));
2983
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
2984
+ type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : []));
2985
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2986
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
2987
+ autocomplete = input('off', ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
2988
+ inputmode = input('text', ...(ngDevMode ? [{ debugName: "inputmode" }] : []));
2989
+ minlength = input(null, ...(ngDevMode ? [{ debugName: "minlength" }] : []));
2990
+ maxlength = input(null, ...(ngDevMode ? [{ debugName: "maxlength" }] : []));
2991
+ pattern = input(null, ...(ngDevMode ? [{ debugName: "pattern" }] : []));
2992
+ /**
2993
+ * Prefix: non-clickable by default (safer UX).
2994
+ * If you want clickable prefix, set prefixClickable=true and use a <button tngPrefix>.
2995
+ */
2996
+ prefixClickable = input(false, ...(ngDevMode ? [{ debugName: "prefixClickable" }] : []));
2997
+ /* ─────────────────────────
2998
+ * Klass hooks (theming)
2999
+ * ───────────────────────── */
3000
+ rootKlass = input('', ...(ngDevMode ? [{ debugName: "rootKlass" }] : []));
3001
+ inputKlass = input('', ...(ngDevMode ? [{ debugName: "inputKlass" }] : []));
3002
+ prefixKlass = input('', ...(ngDevMode ? [{ debugName: "prefixKlass" }] : []));
3003
+ suffixKlass = input('', ...(ngDevMode ? [{ debugName: "suffixKlass" }] : []));
3004
+ /* ─────────────────────────
3005
+ * Internal value handling
3006
+ * ───────────────────────── */
3007
+ _value = signal('', ...(ngDevMode ? [{ debugName: "_value" }] : []));
3008
+ value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : []));
3009
+ onChange = () => { };
3010
+ onTouched = () => { };
3011
+ /* ─────────────────────────
3012
+ * Disabled state (forms + input)
3013
+ * ───────────────────────── */
3014
+ _formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_formDisabled" }] : []));
3015
+ isDisabled = computed(() => this.disabled() || this._formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
3016
+ setDisabledState(isDisabled) {
3017
+ this._formDisabled.set(isDisabled);
3018
+ }
3019
+ /* ─────────────────────────
3020
+ * ControlValueAccessor
3021
+ * ───────────────────────── */
3022
+ writeValue(value) {
3023
+ this._value.set(value ?? '');
3024
+ }
3025
+ registerOnChange(fn) {
3026
+ this.onChange = fn;
3027
+ }
3028
+ registerOnTouched(fn) {
3029
+ this.onTouched = fn;
3030
+ }
3031
+ /* ─────────────────────────
3032
+ * Klass finals (defaults + overrides)
3033
+ * ───────────────────────── */
3034
+ rootKlassFinal = computed(() => this.join('flex h-10 w-full items-center rounded-md border border-border bg-bg text-foreground', 'focus-within:border-transparent', 'focus-within:ring-2 focus-within:ring-primary', 'focus-within:ring-offset-1 focus-within:ring-offset-background', this.isDisabled() ? 'pointer-events-none opacity-50' : '', this.readonly() ? 'bg-muted/30 text-muted' : '', this.rootKlass()), ...(ngDevMode ? [{ debugName: "rootKlassFinal" }] : []));
3035
+ inputKlassFinal = computed(() => this.join('h-full min-w-0 flex-1 bg-transparent px-3 text-sm outline-none placeholder:text-muted', this.inputKlass()), ...(ngDevMode ? [{ debugName: "inputKlassFinal" }] : []));
3036
+ prefixKlassFinal = computed(() => this.join(
3037
+ // spacing is provided by projected element classes; wrapper stays minimal
3038
+ this.prefixClickable() ? '' : 'pointer-events-none', this.prefixKlass()), ...(ngDevMode ? [{ debugName: "prefixKlassFinal" }] : []));
3039
+ suffixKlassFinal = computed(() => this.join(this.suffixKlass()), ...(ngDevMode ? [{ debugName: "suffixKlassFinal" }] : []));
3040
+ /* ─────────────────────────
3041
+ * IME-safe input handling
3042
+ * ───────────────────────── */
3043
+ composing = false;
3044
+ onCompositionStart() {
3045
+ this.composing = true;
3046
+ }
3047
+ onCompositionEnd(event) {
3048
+ this.composing = false;
3049
+ this.commitValue(event);
3050
+ }
3051
+ onInput(event) {
3052
+ if (this.composing)
3053
+ return;
3054
+ this.commitValue(event);
3055
+ }
3056
+ commitValue(event) {
3057
+ const next = event.target.value;
3058
+ this._value.set(next);
3059
+ this.onChange(next);
3060
+ }
3061
+ onBlur() {
3062
+ this.onTouched();
3063
+ }
3064
+ join(...parts) {
3065
+ return parts.map((p) => (p ?? '').trim()).filter(Boolean).join(' ');
3066
+ }
3067
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTextInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3068
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngTextInput, isStandalone: true, selector: "tng-text-input", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, minlength: { classPropertyName: "minlength", publicName: "minlength", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, pattern: { classPropertyName: "pattern", publicName: "pattern", isSignal: true, isRequired: false, transformFunction: null }, prefixClickable: { classPropertyName: "prefixClickable", publicName: "prefixClickable", isSignal: true, isRequired: false, transformFunction: null }, rootKlass: { classPropertyName: "rootKlass", publicName: "rootKlass", isSignal: true, isRequired: false, transformFunction: null }, inputKlass: { classPropertyName: "inputKlass", publicName: "inputKlass", isSignal: true, isRequired: false, transformFunction: null }, prefixKlass: { classPropertyName: "prefixKlass", publicName: "prefixKlass", isSignal: true, isRequired: false, transformFunction: null }, suffixKlass: { classPropertyName: "suffixKlass", publicName: "suffixKlass", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
3069
+ {
3070
+ provide: NG_VALUE_ACCESSOR,
3071
+ useExisting: forwardRef(() => TngTextInput),
3072
+ multi: true,
3073
+ },
3074
+ ], ngImport: i0, template: "<div [class]=\"rootKlassFinal()\">\n <span [class]=\"prefixKlassFinal()\">\n <ng-content select=\"[tngPrefix]\"></ng-content>\n </span>\n\n <input\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [type]=\"type()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [autocomplete]=\"autocomplete()\"\n [class]=\"inputKlassFinal()\"\n [value]=\"value()\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.minlength]=\"minlength() ?? null\"\n [attr.maxlength]=\"maxlength() ?? null\"\n [attr.pattern]=\"pattern() ?? null\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n (compositionstart)=\"onCompositionStart()\"\n (compositionend)=\"onCompositionEnd($event)\"\n />\n\n <span [class]=\"suffixKlassFinal()\">\n <ng-content select=\"[tngSuffix]\"></ng-content>\n </span>\n</div>" });
3075
+ }
3076
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTextInput, decorators: [{
3077
+ type: Component,
3078
+ args: [{ selector: 'tng-text-input', standalone: true, providers: [
3079
+ {
3080
+ provide: NG_VALUE_ACCESSOR,
3081
+ useExisting: forwardRef(() => TngTextInput),
3082
+ multi: true,
3083
+ },
3084
+ ], template: "<div [class]=\"rootKlassFinal()\">\n <span [class]=\"prefixKlassFinal()\">\n <ng-content select=\"[tngPrefix]\"></ng-content>\n </span>\n\n <input\n [id]=\"id() || null\"\n [name]=\"name() || null\"\n [type]=\"type()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [autocomplete]=\"autocomplete()\"\n [class]=\"inputKlassFinal()\"\n [value]=\"value()\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.minlength]=\"minlength() ?? null\"\n [attr.maxlength]=\"maxlength() ?? null\"\n [attr.pattern]=\"pattern() ?? null\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n (compositionstart)=\"onCompositionStart()\"\n (compositionend)=\"onCompositionEnd($event)\"\n />\n\n <span [class]=\"suffixKlassFinal()\">\n <ng-content select=\"[tngSuffix]\"></ng-content>\n </span>\n</div>" }]
3085
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], minlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minlength", required: false }] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], pattern: [{ type: i0.Input, args: [{ isSignal: true, alias: "pattern", required: false }] }], prefixClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixClickable", required: false }] }], rootKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootKlass", required: false }] }], inputKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputKlass", required: false }] }], prefixKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixKlass", required: false }] }], suffixKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixKlass", required: false }] }] } });
3086
+
3087
+ class TngTimepicker {
3088
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTimepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
3089
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: TngTimepicker, isStandalone: true, selector: "tng-timepicker", ngImport: i0, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Welcome hello component - Timepicker</p>\n</div>\n\n" });
3090
+ }
3091
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTimepicker, decorators: [{
3092
+ type: Component,
3093
+ args: [{ selector: 'tng-timepicker', standalone: true, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Welcome hello component - Timepicker</p>\n</div>\n\n" }]
3094
+ }] });
18
3095
 
19
3096
  /**
20
3097
  * Generated bundle index. Do not edit.
21
3098
  */
3099
+
3100
+ export { TNG_DATE_ADAPTER, TngAutocomplete, TngButtonToggle, TngCheckbox, TngChips, TngDatepicker, TngFileDropzone, TngFileUpload, TngFormField, TngNativeDateAdapter, TngNumberInput, TngRadioButton, TngSelect, TngSlideToggle, TngSlider, TngTextInput, TngTextarea, TngTimepicker, computeNextCaretPos, formatDate, parseSmartDate };
22
3101
  //# sourceMappingURL=tociva-tailng-ui-form-controls.mjs.map