@cdevhub/ngx-tw 0.2.0 → 0.3.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.
Files changed (144) hide show
  1. package/README.md +264 -49
  2. package/fesm2022/cdevhub-ngx-tw-accordion.mjs +11 -7
  3. package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
  4. package/fesm2022/cdevhub-ngx-tw-alert.mjs +43 -29
  5. package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -1
  6. package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs +103 -0
  7. package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs.map +1 -0
  8. package/fesm2022/cdevhub-ngx-tw-avatar.mjs +56 -31
  9. package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
  10. package/fesm2022/cdevhub-ngx-tw-badge.mjs +61 -35
  11. package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
  12. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +154 -93
  13. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
  14. package/fesm2022/cdevhub-ngx-tw-button.mjs +35 -22
  15. package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
  16. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +3 -3
  17. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
  18. package/fesm2022/cdevhub-ngx-tw-calendar.mjs +416 -216
  19. package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
  20. package/fesm2022/cdevhub-ngx-tw-card.mjs +33 -24
  21. package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
  22. package/fesm2022/cdevhub-ngx-tw-carousel.mjs +152 -81
  23. package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
  24. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +99 -47
  25. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
  26. package/fesm2022/cdevhub-ngx-tw-code-block.mjs +42 -29
  27. package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
  28. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +46 -29
  29. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
  30. package/fesm2022/cdevhub-ngx-tw-combobox.mjs +205 -110
  31. package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
  32. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +141 -74
  33. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
  34. package/fesm2022/cdevhub-ngx-tw-core.mjs +16 -11
  35. package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
  36. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +395 -149
  37. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
  38. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +305 -150
  39. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
  40. package/fesm2022/cdevhub-ngx-tw-dialog.mjs +56 -43
  41. package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
  42. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +51 -33
  43. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
  44. package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +905 -0
  45. package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -0
  46. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +49 -26
  47. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
  48. package/fesm2022/cdevhub-ngx-tw-form-field.mjs +133 -74
  49. package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
  50. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +2 -2
  51. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -1
  52. package/fesm2022/cdevhub-ngx-tw-icon.mjs +28 -17
  53. package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
  54. package/fesm2022/cdevhub-ngx-tw-input.mjs +37 -21
  55. package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
  56. package/fesm2022/cdevhub-ngx-tw-item.mjs +33 -24
  57. package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
  58. package/fesm2022/cdevhub-ngx-tw-menu.mjs +63 -48
  59. package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
  60. package/fesm2022/cdevhub-ngx-tw-number-input.mjs +622 -0
  61. package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -0
  62. package/fesm2022/cdevhub-ngx-tw-paginator.mjs +142 -72
  63. package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
  64. package/fesm2022/cdevhub-ngx-tw-popover.mjs +101 -46
  65. package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
  66. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +57 -30
  67. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
  68. package/fesm2022/cdevhub-ngx-tw-radio.mjs +108 -57
  69. package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
  70. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +46 -26
  71. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
  72. package/fesm2022/cdevhub-ngx-tw-select.mjs +227 -122
  73. package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
  74. package/fesm2022/cdevhub-ngx-tw-separator.mjs +21 -12
  75. package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -1
  76. package/fesm2022/cdevhub-ngx-tw-sheet.mjs +58 -44
  77. package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
  78. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +25 -14
  79. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -1
  80. package/fesm2022/cdevhub-ngx-tw-slider.mjs +155 -79
  81. package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
  82. package/fesm2022/cdevhub-ngx-tw-sort.mjs +47 -25
  83. package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
  84. package/fesm2022/cdevhub-ngx-tw-spinner.mjs +29 -16
  85. package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -1
  86. package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -42
  87. package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
  88. package/fesm2022/cdevhub-ngx-tw-stat.mjs +107 -64
  89. package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
  90. package/fesm2022/cdevhub-ngx-tw-stepper.mjs +113 -47
  91. package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
  92. package/fesm2022/cdevhub-ngx-tw-switch.mjs +61 -32
  93. package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
  94. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +88 -42
  95. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
  96. package/fesm2022/cdevhub-ngx-tw-table.mjs +266 -118
  97. package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
  98. package/fesm2022/cdevhub-ngx-tw-tabs.mjs +102 -52
  99. package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
  100. package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +798 -0
  101. package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -0
  102. package/fesm2022/cdevhub-ngx-tw-textarea.mjs +16 -8
  103. package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
  104. package/fesm2022/cdevhub-ngx-tw-theme.mjs +22 -14
  105. package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
  106. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +243 -95
  107. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
  108. package/fesm2022/cdevhub-ngx-tw-timeline.mjs +149 -82
  109. package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
  110. package/fesm2022/cdevhub-ngx-tw-toast.mjs +100 -55
  111. package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
  112. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +50 -28
  113. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
  114. package/fesm2022/cdevhub-ngx-tw-transfer.mjs +1129 -0
  115. package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -0
  116. package/fesm2022/cdevhub-ngx-tw-tree.mjs +554 -0
  117. package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -0
  118. package/fesm2022/cdevhub-ngx-tw.mjs +7 -0
  119. package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -1
  120. package/package.json +45 -17
  121. package/theme/_dark.css +16 -12
  122. package/theme/_semantic.css +3 -2
  123. package/theme/index.css +10 -2
  124. package/types/cdevhub-ngx-tw-aspect-ratio.d.ts +40 -0
  125. package/types/cdevhub-ngx-tw-calendar.d.ts +39 -8
  126. package/types/cdevhub-ngx-tw-code-block.d.ts +8 -7
  127. package/types/cdevhub-ngx-tw-command-palette.d.ts +7 -0
  128. package/types/cdevhub-ngx-tw-date-picker.d.ts +36 -10
  129. package/types/cdevhub-ngx-tw-file-upload.d.ts +250 -0
  130. package/types/cdevhub-ngx-tw-form-field.d.ts +22 -3
  131. package/types/cdevhub-ngx-tw-icon-lucide.d.ts +21 -3
  132. package/types/cdevhub-ngx-tw-number-input.d.ts +130 -0
  133. package/types/cdevhub-ngx-tw-popover.d.ts +8 -0
  134. package/types/cdevhub-ngx-tw-select.d.ts +1 -1
  135. package/types/cdevhub-ngx-tw-stepper.d.ts +30 -2
  136. package/types/cdevhub-ngx-tw-tab-nav.d.ts +8 -0
  137. package/types/cdevhub-ngx-tw-table.d.ts +30 -1
  138. package/types/cdevhub-ngx-tw-tabs.d.ts +8 -0
  139. package/types/cdevhub-ngx-tw-tags-input.d.ts +217 -0
  140. package/types/cdevhub-ngx-tw-time-picker.d.ts +31 -6
  141. package/types/cdevhub-ngx-tw-toast.d.ts +1 -1
  142. package/types/cdevhub-ngx-tw-transfer.d.ts +330 -0
  143. package/types/cdevhub-ngx-tw-tree.d.ts +189 -0
  144. package/types/cdevhub-ngx-tw.d.ts +7 -0
@@ -0,0 +1,250 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { OnInit, Signal, TemplateRef } from '@angular/core';
3
+ import { ControlValueAccessor } from '@angular/forms';
4
+ import { TwSize, ErrorStateMatcher } from '@cdevhub/ngx-tw/core';
5
+ import { FormFieldControl } from '@cdevhub/ngx-tw/form-field';
6
+
7
+ /** Lifecycle status of a single selected file. */
8
+ type FileUploadStatus = 'pending' | 'uploading' | 'success' | 'error';
9
+ /** Visual variant of the drop zone container. */
10
+ type FileUploadVariant = 'outline' | 'soft';
11
+ /** Reason a file was rejected during validation. */
12
+ type FileUploadRejectionReason = 'accept' | 'size' | 'count';
13
+ /** A single tracked entry in the file-upload's internal state. */
14
+ interface FileUploadItem {
15
+ /** Stable opaque id for this entry. Generated by the component on add. Use as the key when calling `setItemProgress` / `setItemStatus`. */
16
+ readonly id: string;
17
+ /** The underlying `File` object as provided by the browser (or by `writeValue`). */
18
+ readonly file: File;
19
+ /** Current upload progress, integer 0–100. `0` when no progress has been reported. */
20
+ readonly progress: number;
21
+ /** Current lifecycle status. Defaults to `'pending'`. */
22
+ readonly status: FileUploadStatus;
23
+ /** Optional error message attached when `status === 'error'`. Displayed in the file row and announced via `LiveAnnouncer`. */
24
+ readonly error?: string;
25
+ }
26
+ /** Payload emitted on `fileRejected`. */
27
+ interface FileUploadRejection {
28
+ /** The browser-supplied `File` that was rejected. */
29
+ readonly file: File;
30
+ /** Why the file was rejected. */
31
+ readonly reason: FileUploadRejectionReason;
32
+ /** Human-readable message suitable for assistive-tech announcement and inline display. */
33
+ readonly message: string;
34
+ }
35
+ /**
36
+ * Structural directive that captures a template used to render each item row.
37
+ * Apply with `*twFileUploadItem="let item"` to override the default row layout.
38
+ *
39
+ * The template context exposes the current `FileUploadItem` as both `$implicit`
40
+ * and `item`, so both `let-item` and `let-item="item"` work.
41
+ */
42
+ declare class FileUploadItemDirective {
43
+ /** @internal Reference to the captured template — read by the host component. */
44
+ readonly templateRef: TemplateRef<{
45
+ $implicit: FileUploadItem;
46
+ item: FileUploadItem;
47
+ }>;
48
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FileUploadItemDirective, never>;
49
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FileUploadItemDirective, "ng-template[twFileUploadItem]", never, {}, {}, never, never, true, never>;
50
+ }
51
+ declare class FileUploadComponent extends FormFieldControl<File[]> implements ControlValueAccessor, OnInit {
52
+ /** When true, the user can select more than one file. Mirrors to the hidden `<input multiple>` attribute. Defaults to `false`. */
53
+ readonly multiple: _angular_core.InputSignalWithTransform<boolean, unknown>;
54
+ /** Comma-separated list of accepted file types using the native `<input accept>` syntax (e.g., `'image/*,.pdf'`). Forwarded to the hidden input and used to reject dropped files that do not match. Defaults to `undefined` (accept any). */
55
+ readonly accept: _angular_core.InputSignal<string | undefined>;
56
+ /** Maximum size per file, in bytes. Files exceeding this are rejected with reason `'size'` and never reach `value`. Defaults to `undefined` (no limit). */
57
+ readonly maxSize: _angular_core.InputSignal<number | undefined>;
58
+ /** Maximum total file count. Drops that would push the count over this limit are rejected with reason `'count'`. Ignored when `multiple` is `false` (the limit is always 1). Defaults to `undefined` (no limit). */
59
+ readonly maxFiles: _angular_core.InputSignal<number | undefined>;
60
+ /** Visual style of the drop zone. `'outline'` renders a dashed-bordered transparent region; `'soft'` renders a filled muted background. Defaults to `'outline'`. */
61
+ readonly variant: _angular_core.InputSignal<FileUploadVariant>;
62
+ /** Controls overall density: drop-zone padding, icon scale, headline typography. Defaults to `'md'`. */
63
+ readonly size: _angular_core.InputSignal<TwSize>;
64
+ /** When true, blocks file selection (button, click, drop, keyboard) and applies muted styling. Reactive forms also propagate `disabled` via `setDisabledState`. Defaults to `false`. */
65
+ readonly disabledInput: _angular_core.InputSignalWithTransform<boolean, unknown>;
66
+ /** Marks the control as required. Mirrors to `aria-required`. Also inferred from `Validators.required` on a bound `NgControl`. Defaults to `false`. */
67
+ readonly requiredInput: _angular_core.InputSignalWithTransform<boolean, unknown>;
68
+ /** Headline text rendered inside the drop zone (e.g., `'Drag files here'`). Projected `[twFileUploadHeadline]` content takes precedence over this input. */
69
+ readonly label: _angular_core.InputSignal<string | undefined>;
70
+ /** Secondary text rendered under the label (e.g., `'PDF up to 10MB'`). Projected `[twFileUploadDescription]` content takes precedence. */
71
+ readonly description: _angular_core.InputSignal<string | undefined>;
72
+ /** Label rendered inside the trigger button. Defaults to `'Choose files'`. Use `'Choose file'` when `multiple` is `false` (consumer responsibility). */
73
+ readonly triggerLabel: _angular_core.InputSignal<string>;
74
+ /** Accessible name applied to the host when no visible label is projected. Mirrored to `aria-label`. */
75
+ readonly ariaLabel: _angular_core.InputSignal<string | undefined>;
76
+ /** ID of an external element that labels the control. Mirrored to `aria-labelledby`. */
77
+ readonly ariaLabelledby: _angular_core.InputSignal<string | undefined>;
78
+ /** ID of an external element that describes the control. Form-field merges its hint/error ids alongside. */
79
+ readonly ariaDescribedby: _angular_core.InputSignal<string | undefined>;
80
+ /** Optional `name` attribute on the hidden `<input type="file">` for native form submissions. */
81
+ readonly name: _angular_core.InputSignal<string | undefined>;
82
+ /** Id on the host element. Auto-generated as `tw-file-upload-N` when not provided. Used by the form-field's `<label for>` attribute. */
83
+ readonly idInput: _angular_core.InputSignal<string | undefined>;
84
+ /** Per-instance override of the {@link ErrorStateMatcher}. When omitted, the component uses the `TW_ERROR_STATE_MATCHER` token's value. */
85
+ readonly errorStateMatcher: _angular_core.InputSignal<ErrorStateMatcher | undefined>;
86
+ /** Fires once per add operation with the items that were accepted and appended. Does not fire for rejected files (see `fileRejected`). Does not fire on `writeValue`. */
87
+ readonly filesAdded: _angular_core.OutputEmitterRef<FileUploadItem[]>;
88
+ /** Fires when a file is removed via the remove button or programmatic `remove()`. Does not fire on `writeValue`. */
89
+ readonly fileRemoved: _angular_core.OutputEmitterRef<FileUploadItem>;
90
+ /** Fires for each file that failed validation during add. Payload includes a human-readable message suitable for assistive-tech announcement. */
91
+ readonly fileRejected: _angular_core.OutputEmitterRef<FileUploadRejection>;
92
+ /** Fires when all files are cleared at once via programmatic `clear()`. Does not fire on `writeValue`, even when the new value is empty. */
93
+ readonly cleared: _angular_core.OutputEmitterRef<void>;
94
+ private readonly elementRef;
95
+ private readonly focusMonitor;
96
+ private readonly liveAnnouncer;
97
+ private readonly destroyRef;
98
+ private readonly ngControl;
99
+ private readonly parentForm;
100
+ private readonly parentFormGroup;
101
+ private readonly defaultMatcher;
102
+ private readonly itemsSignal;
103
+ private readonly cvaDisabled;
104
+ private readonly isDraggingSignal;
105
+ private readonly dragHasInvalidFilesSignal;
106
+ private readonly _focused;
107
+ private readonly _ngControlRev;
108
+ private readonly _formSubmitRev;
109
+ private readonly describedByIdsSignal;
110
+ private readonly labelledByIdsSignal;
111
+ private dragCounter;
112
+ private onChange;
113
+ private onTouched;
114
+ /** @internal Captured custom-item template (from `*twFileUploadItem`). When absent, the default row template renders. */
115
+ readonly itemSlot: Signal<FileUploadItemDirective | undefined>;
116
+ /** @internal Dropzone wrapper — used to scope the form-field container-click guard. */
117
+ private readonly dropzoneRef;
118
+ /** @internal The visible trigger button — the keyboard-operable control that receives focus on container click and after the last item is removed. */
119
+ private readonly triggerRef;
120
+ /** @internal Hidden native input — used as the OS picker trigger. */
121
+ private readonly hiddenInputRef;
122
+ private readonly uid;
123
+ /** @internal Auto-generated host id used when `[id]` is not supplied. */
124
+ readonly hostId: string;
125
+ /** @internal */
126
+ readonly headlineId: string;
127
+ /** @internal */
128
+ readonly descriptionId: string;
129
+ /** @internal */
130
+ readonly hiddenInputId: string;
131
+ /** Current items with their progress and status. Snapshot of internal state. */
132
+ readonly items: Signal<readonly FileUploadItem[]>;
133
+ /** True while a valid file drag is over the drop zone. */
134
+ readonly isDragging: Signal<boolean>;
135
+ /** Convenience alias for `items().length`. Useful for counter hints. */
136
+ readonly valueLength: Signal<number>;
137
+ /** @internal */
138
+ readonly id: Signal<string>;
139
+ /** @internal */
140
+ readonly value: Signal<File[] | null>;
141
+ /** @internal */
142
+ readonly focused: Signal<boolean>;
143
+ /** @internal */
144
+ readonly empty: Signal<boolean>;
145
+ /** @internal */
146
+ readonly disabled: Signal<boolean>;
147
+ /** @internal */
148
+ readonly required: Signal<boolean>;
149
+ /** @internal */
150
+ readonly errorState: Signal<boolean>;
151
+ /** @internal Validation errors map from the bound `NgControl`. */
152
+ readonly errors: Signal<Record<string, unknown> | null>;
153
+ /** @internal */
154
+ readonly controlType = "file-upload";
155
+ /** @internal */
156
+ readonly userAriaDescribedBy: Signal<string | undefined>;
157
+ /** @internal */
158
+ readonly userAriaLabelledby: Signal<string | undefined>;
159
+ /** @internal */
160
+ readonly effectiveAriaLabelledby: Signal<string | undefined>;
161
+ /** @internal */
162
+ readonly effectiveAriaDescribedby: Signal<string | undefined>;
163
+ private readonly stateVariant;
164
+ private readonly variantResult;
165
+ /** @internal */
166
+ readonly rootClasses: Signal<string>;
167
+ /** @internal */
168
+ readonly dropzoneClasses: Signal<string>;
169
+ /** @internal */
170
+ readonly dropzoneInnerClasses: Signal<string>;
171
+ /** @internal */
172
+ readonly illustrationClasses: Signal<string>;
173
+ /** @internal */
174
+ readonly headlineClasses: Signal<string>;
175
+ /** @internal */
176
+ readonly descriptionClasses: Signal<string>;
177
+ /** @internal */
178
+ readonly listClasses: Signal<string>;
179
+ /** @internal */
180
+ readonly itemClasses: Signal<string>;
181
+ /** @internal */
182
+ readonly itemIconClasses: Signal<string>;
183
+ /** @internal */
184
+ readonly itemBodyClasses: Signal<string>;
185
+ /** @internal */
186
+ readonly itemNameClasses: Signal<string>;
187
+ /** @internal */
188
+ readonly itemMetaClasses: Signal<string>;
189
+ /** @internal */
190
+ readonly itemProgressClasses: Signal<string>;
191
+ /** @internal */
192
+ readonly itemActionsClasses: Signal<string>;
193
+ /** @internal Mapping component size → icon size for the illustration. */
194
+ readonly iconSize: Signal<TwSize>;
195
+ constructor();
196
+ /** Programmatically opens the OS file picker. No-op when disabled. */
197
+ open(): void;
198
+ /** Removes the item with the given id. Emits `fileRemoved`. No-op when the id is unknown or the component is disabled. */
199
+ remove(id: string): void;
200
+ /** Removes all items. Emits `cleared` and calls `onChange([])`. No-op when already empty or disabled. */
201
+ clear(): void;
202
+ /** Updates the per-item progress (0–100, clamped). Does not change status. No-op when the id is unknown. */
203
+ setItemProgress(id: string, percent: number): void;
204
+ /** Updates the per-item status and (optionally) the error message. Announces status transitions via `LiveAnnouncer`. No-op when the id is unknown. */
205
+ setItemStatus(id: string, status: FileUploadStatus, error?: string): void;
206
+ /** @internal */
207
+ onDragEnter(event: DragEvent): void;
208
+ /** @internal */
209
+ onDragOver(event: DragEvent): void;
210
+ /** @internal */
211
+ onDragLeave(event: DragEvent): void;
212
+ /** @internal */
213
+ onDrop(event: DragEvent): void;
214
+ /** @internal */
215
+ onTriggerClick(event: MouseEvent): void;
216
+ /** @internal Called by the hidden `<input type="file">` `change` event. */
217
+ onNativeChange(event: Event): void;
218
+ private addFiles;
219
+ private wrapFile;
220
+ private reject;
221
+ private rejectionMessage;
222
+ private isFileDrag;
223
+ private computeDragInvalid;
224
+ writeValue(value: File[] | File | null | undefined): void;
225
+ registerOnChange(fn: (value: File[]) => void): void;
226
+ registerOnTouched(fn: () => void): void;
227
+ setDisabledState(isDisabled: boolean): void;
228
+ private emitChange;
229
+ /** @internal */
230
+ setDescribedByIds(ids: string[]): void;
231
+ /** @internal */
232
+ setLabelledByIds(ids: string[]): void;
233
+ /** @internal */
234
+ onContainerClick(event: MouseEvent): void;
235
+ /** @internal */
236
+ iconNameFor(item: FileUploadItem): string;
237
+ /** @internal */
238
+ shouldShowProgress(item: FileUploadItem): boolean;
239
+ /** @internal */
240
+ progressColorFor(item: FileUploadItem): 'primary' | 'success' | 'error';
241
+ /** @internal */
242
+ metaTextFor(item: FileUploadItem): string;
243
+ ngOnInit(): void;
244
+ private hasAccessibleNameHint;
245
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FileUploadComponent, never>;
246
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FileUploadComponent, "tw-file-upload", ["twFileUpload"], { "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; "requiredInput": { "alias": "required"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "triggerLabel": { "alias": "triggerLabel"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; "ariaDescribedby": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "idInput": { "alias": "id"; "required": false; "isSignal": true; }; "errorStateMatcher": { "alias": "errorStateMatcher"; "required": false; "isSignal": true; }; }, { "filesAdded": "filesAdded"; "fileRemoved": "fileRemoved"; "fileRejected": "fileRejected"; "cleared": "cleared"; }, ["itemSlot"], ["[twFileUploadIcon]", "[twFileUploadHeadline]", "[twFileUploadDescription]"], true, never>;
247
+ }
248
+
249
+ export { FileUploadComponent, FileUploadItemDirective };
250
+ export type { FileUploadItem, FileUploadRejection, FileUploadRejectionReason, FileUploadStatus, FileUploadVariant };
@@ -52,6 +52,25 @@ declare abstract class FormFieldControl<T = unknown> {
52
52
  * `providers: [{ provide: TW_FORM_FIELD_CONTROL, useExisting: MyControl }]`.
53
53
  */
54
54
  declare const TW_FORM_FIELD_CONTROL: InjectionToken<FormFieldControl<unknown>>;
55
+ /**
56
+ * Minimal surface a wrapped control reads from its surrounding form-field —
57
+ * presence (via optional injection) plus whether a label is projected — without
58
+ * depending on the concrete {@link FormFieldComponent}. Controls inject
59
+ * {@link TW_FORM_FIELD} typed as this interface so detecting a parent form-field
60
+ * never pins the heavier component class into the control's own bundle.
61
+ */
62
+ interface TwFormFieldParent {
63
+ /** Whether a `twLabel` element is projected into the form-field. */
64
+ readonly hasLabel: Signal<boolean>;
65
+ }
66
+ /**
67
+ * Injection token exposing the wrapping {@link TwFormFieldParent}. The
68
+ * {@link FormFieldComponent} provides itself under this token; controls inject
69
+ * it with `{ optional: true }` to detect (and read label state from) a parent
70
+ * `tw-form-field` without referencing the concrete component class — keeping the
71
+ * form-field component out of each control's bundle.
72
+ */
73
+ declare const TW_FORM_FIELD: InjectionToken<TwFormFieldParent>;
55
74
  declare class LabelDirective {
56
75
  private readonly formField;
57
76
  private readonly elementRef;
@@ -119,7 +138,7 @@ declare class SuffixIconDirective extends SuffixDirective {
119
138
  static ɵfac: i0.ɵɵFactoryDeclaration<SuffixIconDirective, never>;
120
139
  static ɵdir: i0.ɵɵDirectiveDeclaration<SuffixIconDirective, "[twSuffixIcon]", never, {}, {}, never, never, true, never>;
121
140
  }
122
- declare class FormFieldComponent implements AfterContentInit {
141
+ declare class FormFieldComponent implements AfterContentInit, TwFormFieldParent {
123
142
  /** Visual appearance of the field container. `'outline'` draws a full border around the control; `'filled'` uses a filled surface with a bottom border. Defaults to `'outline'`. */
124
143
  readonly appearance: i0.InputSignal<FormFieldAppearance>;
125
144
  /** Density of the field container. Maps to the inline-padding scale (`px-2 py-1` xs … `px-5 py-3` xl) and the floating-label font scale. Defaults to `'md'`. */
@@ -206,5 +225,5 @@ declare class FormFieldComponent implements AfterContentInit {
206
225
  static ɵcmp: i0.ɵɵComponentDeclaration<FormFieldComponent, "tw-form-field", never, { "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "floatLabel": { "alias": "floatLabel"; "required": false; "isSignal": true; }; "subscriptSizing": { "alias": "subscriptSizing"; "required": false; "isSignal": true; }; "hideRequiredMarker": { "alias": "hideRequiredMarker"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "hintAlign": { "alias": "hintAlign"; "required": false; "isSignal": true; }; }, {}, ["control", "labelChild", "hintChildren", "errorChildren", "prefixChildren"], ["[twPrefix]", "[twPrefixIcon]", "*", "[twSuffix]", "[twSuffixIcon]", "[twLabel]", "[twError]", "[twHint]"], true, never>;
207
226
  }
208
227
 
209
- export { ErrorDirective, FormFieldComponent, FormFieldControl, HintDirective, LabelDirective, PrefixDirective, PrefixIconDirective, SuffixDirective, SuffixIconDirective, TW_FORM_FIELD_CONTROL };
210
- export type { FloatLabel, FormFieldAppearance, SubscriptSizing };
228
+ export { ErrorDirective, FormFieldComponent, FormFieldControl, HintDirective, LabelDirective, PrefixDirective, PrefixIconDirective, SuffixDirective, SuffixIconDirective, TW_FORM_FIELD, TW_FORM_FIELD_CONTROL };
229
+ export type { FloatLabel, FormFieldAppearance, SubscriptSizing, TwFormFieldParent };
@@ -1,11 +1,28 @@
1
1
  import { Provider } from '@angular/core';
2
- import { LucideIconData, LucideIcons } from 'lucide-angular';
3
2
  import { TwIconData } from '@cdevhub/ngx-tw/icon';
4
3
 
4
+ /**
5
+ * A single Lucide SVG element: `[tagName, attributes]`.
6
+ *
7
+ * Declared structurally rather than imported from a Lucide package on purpose.
8
+ * An `import type … from 'lucide'` here would be emitted into the published
9
+ * `.d.ts`, making a package this library does not depend on a hard requirement
10
+ * for anyone who merely *type-checks* against this entry point. Declaring the
11
+ * shape locally keeps the entry point dependency-free and lets it accept icons
12
+ * from `lucide`, `lucide-angular`, or any other source with the same shape.
13
+ */
14
+ type LucideIconNode = readonly [
15
+ tag: string,
16
+ attrs: Record<string, string | number | undefined>
17
+ ];
18
+ /** Icon data as Lucide emits it: the SVG child elements, without the `<svg>` wrapper. */
19
+ type LucideIconData = readonly LucideIconNode[];
20
+ /** A map of PascalCase icon names to Lucide icon data, as produced by `import { Star } from 'lucide'`. */
21
+ type LucideIcons = Readonly<Record<string, LucideIconData>>;
5
22
  /**
6
23
  * Converts a single Lucide icon to the generic `TwIconData` format.
7
24
  *
8
- * Lucide's `LucideIconData` is structurally identical to `TwIconData`.
25
+ * Lucide's icon data is structurally identical to `TwIconData`.
9
26
  * This function exists for type-level conversion and explicit intent.
10
27
  */
11
28
  declare function fromLucideIcon(icon: LucideIconData): TwIconData;
@@ -17,7 +34,7 @@ declare function fromLucideIcon(icon: LucideIconData): TwIconData;
17
34
  * @example
18
35
  * ```ts
19
36
  * import { provideTwLucideIcons } from '@cdevhub/ngx-tw/icon/lucide';
20
- * import { ChevronRight, Star, Check } from 'lucide-angular';
37
+ * import { ChevronRight, Star, Check } from 'lucide';
21
38
  *
22
39
  * export const appConfig = {
23
40
  * providers: [provideTwLucideIcons({ ChevronRight, Star, Check })]
@@ -27,3 +44,4 @@ declare function fromLucideIcon(icon: LucideIconData): TwIconData;
27
44
  declare function provideTwLucideIcons(icons: LucideIcons): Provider[];
28
45
 
29
46
  export { fromLucideIcon, provideTwLucideIcons };
47
+ export type { LucideIconData, LucideIconNode, LucideIcons };
@@ -0,0 +1,130 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { ControlValueAccessor } from '@angular/forms';
3
+ import { TwSize } from '@cdevhub/ngx-tw/core';
4
+
5
+ /**
6
+ * Turns a plain `<input twInput twNumberInput>` into a robust numeric field
7
+ * without `type="number"` — fixing the broken-on-mobile, browser-inconsistent
8
+ * native control. It composes the sibling {@link InputDirective}: that directive
9
+ * keeps owning the form-field chrome, error-state, and `aria-invalid` /
10
+ * `aria-required`, while this directive owns the numeric value transport,
11
+ * locale-aware parse/format, spinbutton ARIA, `inputmode`, and keyboard stepping.
12
+ *
13
+ * Pair it with the companion `<tw-number-stepper [for]="ref">` for visible
14
+ * up/down spinner buttons.
15
+ *
16
+ * Forms: implements `ControlValueAccessor`, so the value round-trips as a real
17
+ * `number | null` (never a string) through template-driven, reactive, and
18
+ * signal-based forms.
19
+ */
20
+ declare class NumberInputDirective implements ControlValueAccessor {
21
+ private readonly el;
22
+ /** Smallest accepted value. Clamps the committed value (on blur, Enter, and stepping) and sets `aria-valuemin`. Does not clamp per keystroke. Defaults to `undefined` (no lower bound). */
23
+ readonly min: _angular_core.InputSignal<number | undefined>;
24
+ /** Largest accepted value. Clamps the committed value (on blur, Enter, and stepping) and sets `aria-valuemax`. Does not clamp per keystroke. Defaults to `undefined` (no upper bound). */
25
+ readonly max: _angular_core.InputSignal<number | undefined>;
26
+ /** Amount added or subtracted by ArrowUp/ArrowDown and the stepper buttons. Defaults to `1`. Values `<= 0` or non-finite fall back to `1`. */
27
+ readonly step: _angular_core.InputSignal<number>;
28
+ /** `Intl.NumberFormat` options driving the blurred display (grouping, decimals, currency). The formatter's resolved `maximumFractionDigits` also sets commit-time rounding precision and switches `inputmode` to `'numeric'` when it is `0`. Percent style is not supported in v1. Defaults to `undefined` (locale default number formatting, grouping on). */
29
+ readonly format: _angular_core.InputSignal<Intl.NumberFormatOptions | undefined>;
30
+ /** BCP-47 locale for `Intl.NumberFormat` formatting and for locale-aware parsing (decimal and group separators). Defaults to `undefined` (the runtime default locale). */
31
+ readonly locale: _angular_core.InputSignal<string | undefined>;
32
+ /** Fires when the committed numeric value changes through user interaction (typing, stepping, clamping on blur/Enter). Does not fire on `writeValue` (programmatic form writes). Useful for non-form / template-ref usage alongside the `value` signal. */
33
+ readonly valueChange: _angular_core.OutputEmitterRef<number | null>;
34
+ private readonly _value;
35
+ private readonly cvaDisabled;
36
+ private readonly readonlySig;
37
+ /** The current committed numeric value, or `null` when empty / unparseable. Read this from a template ref (`#n="twNumberInput"`) for non-form usage. */
38
+ readonly value: _angular_core.Signal<number | null>;
39
+ /** @internal Formatted display text surfaced through `TW_INPUT_VALUE_ACCESSOR` so the sibling InputDirective's empty / float-label state tracks the text in the box (not the numeric model). Updated on every field mutation. */
40
+ readonly displayText: _angular_core.WritableSignal<string>;
41
+ /** True when the control is disabled. Reactively tracks the reactive-forms path (`setDisabledState` via `control.disable()`) and a static `disabled` attribute read at mount. A runtime-toggled declarative `[disabled]` binding without reactive forms is NOT reactively tracked (v2 limitation — same class as `readonly`). Read by the companion stepper so its buttons disable in lock-step. */
42
+ readonly disabled: _angular_core.Signal<boolean>;
43
+ /** True when the host input carries the `readonly` attribute (static / declarative, seeded after render). A runtime toggle of the bare `readonly` attribute is not reactively tracked (v2). Read by the companion stepper so its buttons disable in lock-step. */
44
+ readonly readonly: _angular_core.Signal<boolean>;
45
+ /** @internal Mobile-keyboard hint: `'numeric'` for integer-only formats, else `'decimal'`. */
46
+ readonly inputMode: _angular_core.Signal<"numeric" | "decimal">;
47
+ /** @internal Locale group / decimal separators, memoized per `locale()`. */
48
+ private readonly separators;
49
+ private _focused;
50
+ private _lastValue;
51
+ private _lastDisplay;
52
+ private onChange;
53
+ private onTouched;
54
+ constructor();
55
+ /** @internal */
56
+ writeValue(value: number | null | undefined): void;
57
+ /** @internal */
58
+ registerOnChange(fn: (value: number | null) => void): void;
59
+ /** @internal */
60
+ registerOnTouched(fn: () => void): void;
61
+ /** @internal */
62
+ setDisabledState(isDisabled: boolean): void;
63
+ /** Steps the value up: treats an empty field as `0`, adds `step`, clamps to `[min,max]`, rounds to the formatter's resolved precision, formats, and writes both the display and the model. Emits `valueChange`. No-op when the host input is disabled or readonly. Does not move focus. */
64
+ increment(): void;
65
+ /** Steps the value down: treats an empty field as `0`, subtracts `step`, clamps to `[min,max]`, rounds, formats, writes display and model. Emits `valueChange`. No-op when disabled or readonly. Does not move focus. */
66
+ decrement(): void;
67
+ /** Moves focus to the underlying input element. */
68
+ focus(options?: FocusOptions): void;
69
+ /** @internal */
70
+ onInput(): void;
71
+ /** @internal */
72
+ onFocus(): void;
73
+ /** @internal */
74
+ onBlur(): void;
75
+ /** @internal */
76
+ onKeydown(event: KeyboardEvent): void;
77
+ private stepBy;
78
+ private commitFromElement;
79
+ private commitNumber;
80
+ private commitEmpty;
81
+ private revertToLastCommitted;
82
+ private resolvedStep;
83
+ private clamp;
84
+ private roundToPrecision;
85
+ private resolvedMaxFractionDigits;
86
+ private formatValue;
87
+ /**
88
+ * Parse a raw string to `number | null` in a load-bearing order: the `e`/`E`
89
+ * rejection MUST precede stripping (otherwise `'1e3'` would survive as `'13'`).
90
+ */
91
+ private parseValue;
92
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NumberInputDirective, never>;
93
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NumberInputDirective, "input[twNumberInput]", ["twNumberInput"], { "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
94
+ }
95
+
96
+ /**
97
+ * The visible up/down spinner column for a {@link NumberInputDirective}. A
98
+ * directive cannot emit sibling DOM, so the spinner buttons live in this tiny
99
+ * companion. Bind it to the directive via a template ref and drop it into a
100
+ * `<tw-form-field>`'s `[twSuffix]` slot (or a standalone flex row):
101
+ *
102
+ * ```html
103
+ * <input twInput twNumberInput #qty="twNumberInput" [formControl]="ctrl" />
104
+ * <tw-number-stepper twSuffix [for]="qty" />
105
+ * ```
106
+ *
107
+ * The buttons are kept out of the tab order (`tabindex="-1"`) — the spinbutton
108
+ * input owns value + keyboard semantics — and refocus the input after stepping.
109
+ */
110
+ declare class NumberStepperComponent {
111
+ /** The number-input directive instance this stepper controls. Bind to a template ref, e.g. `[for]="qty"` with `#qty="twNumberInput"`. When omitted, the buttons render but do nothing (no-op) and disable. */
112
+ readonly for: _angular_core.InputSignal<NumberInputDirective | undefined>;
113
+ /** Button + glyph density. Match the field's `size` for visual alignment. Defaults to `'md'`. */
114
+ readonly size: _angular_core.InputSignal<TwSize>;
115
+ private readonly variant;
116
+ /** @internal */
117
+ readonly groupClasses: _angular_core.Signal<string>;
118
+ /** @internal */
119
+ readonly buttonClasses: _angular_core.Signal<string>;
120
+ /** @internal */
121
+ readonly iconClasses: _angular_core.Signal<string>;
122
+ /** @internal Disabled when there is no target, or the target input is disabled / readonly. */
123
+ readonly isDisabled: _angular_core.Signal<boolean>;
124
+ /** @internal Step the bound directive, then refocus the input (the refocus lives here, not in `increment`/`decrement`). */
125
+ onStep(direction: 1 | -1): void;
126
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NumberStepperComponent, never>;
127
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NumberStepperComponent, "tw-number-stepper", ["twNumberStepper"], { "for": { "alias": "for"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, ["[slot=up]", "[slot=down]"], true, never>;
128
+ }
129
+
130
+ export { NumberInputDirective, NumberStepperComponent };
@@ -111,6 +111,14 @@ declare class PopoverDirective {
111
111
  private setupFocusTrap;
112
112
  private destroyFocusTrap;
113
113
  private disposeOverlay;
114
+ /**
115
+ * Aborts an in-flight close, leaving the popover open.
116
+ *
117
+ * `closePopover` tears the focus trap down immediately and defers only the
118
+ * detach, so cancelling has to rebuild the trap — the overlay itself is still
119
+ * attached and the instance still live.
120
+ */
121
+ private cancelPendingClose;
114
122
  private clearCloseTimer;
115
123
  private resolveScrollStrategy;
116
124
  private resolvePanelClass;
@@ -75,7 +75,7 @@ interface TwSelectTriggerContext<T, O = TwSelectOption<T>> {
75
75
  selectedOptions: readonly O[];
76
76
  }
77
77
  /** Rendered row in the panel — either a group-label header or an option. */
78
- type SelectRenderedRow<T, O = unknown> = {
78
+ type SelectRenderedRow<_T, O = unknown> = {
79
79
  readonly kind: 'group-label';
80
80
  readonly group: string;
81
81
  } | {
@@ -1,7 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { TemplateRef, AfterViewInit, Provider } from '@angular/core';
2
+ import { TemplateRef, AfterViewInit, QueryList, Provider } from '@angular/core';
3
3
  import * as i1 from '@angular/cdk/stepper';
4
- import { CdkStep, CdkStepLabel, CdkStepper, StepperOrientation, StepState, StepperOptions } from '@angular/cdk/stepper';
4
+ import { CdkStep, CdkStepLabel, CdkStepper, StepperOrientation, StepState, CdkStepHeader, StepperOptions } from '@angular/cdk/stepper';
5
5
  export { STEPPER_GLOBAL_OPTIONS, STEP_STATE, StepState, StepperOptions, StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
6
6
  import { TwColor, TwSize } from '@cdevhub/ngx-tw/core';
7
7
 
@@ -106,6 +106,34 @@ declare class StepperComponent extends CdkStepper implements AfterViewInit {
106
106
  /** @internal Context supplied to custom icon templates. */
107
107
  iconContext(index: number): StepperIconContext;
108
108
  /** @internal Click handler for the step header button. */
109
+ /**
110
+ * @internal Re-declares CdkStepper's `_stepHeader` query as a **view** query.
111
+ *
112
+ * CdkStepper declares it as `@ContentChildren`, which assumes the host
113
+ * projects its own headers. `tw-stepper` renders the header strip inside its
114
+ * own template instead, so the base query matched nothing: `_sortedHeaders`
115
+ * stayed empty, the FocusKeyManager had zero items, and arrow-key navigation
116
+ * silently did nothing at all. Angular Material overrides the same query for
117
+ * the same reason.
118
+ *
119
+ * Decorator form, not `viewChildren()`, because CdkStepper subscribes to
120
+ * `_stepHeader.changes` — a QueryList API that signal queries do not expose.
121
+ */
122
+ _stepHeader: QueryList<CdkStepHeader>;
123
+ /**
124
+ * @internal Which step header currently holds the single tab stop.
125
+ *
126
+ * Mirrors CdkStepper's `_getFocusIndex()` into a signal because that method
127
+ * reads `_keyManager.activeItemIndex`, a plain property — and CdkStepper's
128
+ * `_onKeydown` does not call `_stateChanged()` on the arrow-key path, so an
129
+ * OnPush template binding straight to `_getFocusIndex()` would render the
130
+ * roving tabindex once and then go stale the moment the user arrows.
131
+ */
132
+ readonly focusIndexValue: _angular_core.WritableSignal<number>;
133
+ /** @internal Re-reads the CDK key manager's active item into `focusIndexValue`. */
134
+ private syncFocusIndex;
135
+ /** @internal Relays tablist keydown to CdkStepper, then republishes the tab stop. */
136
+ onTablistKeydown(event: KeyboardEvent): void;
109
137
  onHeaderClick(step: StepComponent, index: number): void;
110
138
  /** @internal Whether the stepper should render the error state for a step. */
111
139
  shouldRenderError(step: StepComponent): boolean;
@@ -41,6 +41,14 @@ declare class TabNavComponent {
41
41
  /** @internal Resolved panel — explicit `tabPanel` input wins, falls back to a projected `<tw-tab-nav-panel>` inside the nav. */
42
42
  readonly resolvedPanel: _angular_core.Signal<TabNavPanel | undefined>;
43
43
  private readonly liveAnnouncer;
44
+ private readonly directionality;
45
+ private readonly destroyRef;
46
+ /**
47
+ * Live layout direction. Hardcoding 'ltr' inverts ArrowLeft/ArrowRight in RTL
48
+ * locales — the arrow moves to the next link in DOM order but the previous
49
+ * one visually. Mirrored into a signal so the key manager rebuilds on change.
50
+ */
51
+ private readonly layoutDirection;
44
52
  /** Tracks the previously-announced active link id so we only announce on actual changes. `null` = before the first sync. */
45
53
  private readonly previousActiveLinkId;
46
54
  private readonly layoutResult;
@@ -322,7 +322,16 @@ declare class ColumnComponent<T = unknown> {
322
322
  declare class TableComponent<T = unknown> {
323
323
  /** The table's rows. Accepts a plain array, an `Observable<readonly T[]>`, or a CDK `DataSource<T>`. Wired directly to `CdkTable.dataSource`. Defaults to an empty array. */
324
324
  readonly data: _angular_core.InputSignal<TwTableDataSourceInput<T>>;
325
- /** Tracking function used by CDK to identify rows across data changes. When unset, CDK uses identity. */
325
+ /**
326
+ * Tracking function used to identify rows across data changes. When unset, rows are identified by
327
+ * object reference.
328
+ *
329
+ * This drives **selection and expansion membership** as well as CDK's DOM diffing. Supply it when
330
+ * the data source re-emits equal-but-new objects — an HTTP refetch, an immutable store — or the
331
+ * user's selection empties on every refresh with no event and no error. It is called with `-1` as
332
+ * the index for membership lookups, so return a value derived from the row alone (`row.id`), not
333
+ * from the index.
334
+ */
326
335
  readonly trackBy: _angular_core.InputSignal<TrackByFunction<T> | undefined>;
327
336
  /** When true, renders the loading slot as an overlay and applies `opacity-60 pointer-events-none` to the body. Defaults to `false`. */
328
337
  readonly loading: _angular_core.InputSignal<boolean>;
@@ -444,6 +453,26 @@ declare class TableComponent<T = unknown> {
444
453
  collapse(row: T): void;
445
454
  /** Toggles the expansion state of the given row. */
446
455
  toggleExpansion(row: T): void;
456
+ /**
457
+ * @internal Identity key for a row.
458
+ *
459
+ * Selection and expansion are membership questions, and answering them by
460
+ * object reference breaks the moment a data source re-emits equal-but-new
461
+ * objects — an HTTP refetch, an immutable store, `signal.set([...mapped])`.
462
+ * The user's selection empties with no event and no error.
463
+ *
464
+ * `trackBy` is already the consumer's declaration of row identity, so it is
465
+ * reused here rather than adding a second, redundant input. With no `trackBy`
466
+ * the key IS the row, i.e. reference identity — the same default CDK uses.
467
+ *
468
+ * Note this must be a *key* function, not a comparator: a comparator would fix
469
+ * identity but leave membership as an O(rows x selected) scan, and a
470
+ * reference-keyed Set would fix the scan but ignore custom identity. Only a
471
+ * key fixes both.
472
+ */
473
+ private rowKey;
474
+ /** @internal O(1) membership lookup for the current selection, keyed by `rowKey`. */
475
+ private readonly selectedKeys;
447
476
  /** Whether a given row is currently in the `selected` list. */
448
477
  isSelected(row: T): boolean;
449
478
  /** Selects or deselects a row and emits `selectionChange`. */
@@ -68,6 +68,14 @@ declare class TabsComponent implements AfterViewInit {
68
68
  readonly closed: _angular_core.OutputEmitterRef<string>;
69
69
  private readonly destroyRef;
70
70
  private readonly liveAnnouncer;
71
+ private readonly directionality;
72
+ /**
73
+ * Live layout direction, mirrored into a signal so the key manager rebuilds
74
+ * when it changes. Without this the tablist hardcodes 'ltr' and ArrowRight
75
+ * moves to the next tab in DOM order but the *previous* tab visually in an
76
+ * RTL locale. CdkStepper and `tw-split` already honour Directionality.
77
+ */
78
+ private readonly layoutDirection;
71
79
  /** @internal */
72
80
  readonly tabs: _angular_core.Signal<readonly TabComponent[]>;
73
81
  /** @internal */