angular-tailwind-components 1.8.3-RC5 → 1.9.0-RC1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ A comprehensive Angular component library built entirely with **Tailwind CSS v4*
6
6
 
7
7
  ## Features
8
8
 
9
- - 🎨 **36 components** — Buttons, Inputs, Modals, Tables, DatePickers, and more
9
+ - 🎨 **37 components** — Buttons, Inputs, Modals, Tables, DatePickers, and more
10
10
  - 🎯 **Pure Tailwind CSS** — No Angular Material, Ng-Zorro, or other UI frameworks
11
11
  - ⚡ **Angular 21** — Signals, standalone components, modern control flow
12
12
  - 📝 **ControlValueAccessor** — Full reactive forms integration for all form components
@@ -69,7 +69,7 @@ export class ExampleComponent {
69
69
 
70
70
  ## Application configuration (`defineTheme`)
71
71
 
72
- Use **`defineTheme`** from `angular-tailwind-components` as the single app-level entry: it registers **`EnvironmentProviders`** for optional **injection tokens** (`iconSize`, `datetimeLanguage`, `componentsSize`, `buttonKind`, `paginationSummary`) and, when you pass **`colors`**, an app initializer that applies semantic CSS variables on `document.documentElement` in the browser. Add **one** entry to `providers` without spreading.
72
+ Use **`defineTheme`** from `angular-tailwind-components` as the single app-level entry: it registers **`EnvironmentProviders`** for optional **injection tokens** (`iconSize`, `datetimeLanguage`, `componentsSize`, `buttonKind`, `paginationSummary`, `passwordLabels`) and, when you pass **`colors`**, an app initializer that applies semantic CSS variables on `document.documentElement` in the browser. Add **one** entry to `providers` without spreading.
73
73
 
74
74
  `TailwindDefineThemeConfig` extends **`TailwindComponentsConfig`** with an optional **`colors`** field.
75
75
 
@@ -141,6 +141,7 @@ You can omit **`colors`** if you only need token defaults, or omit token keys if
141
141
  | `componentsSize` | `TAILWIND_COMPONENTS_SIZE` |
142
142
  | `buttonKind` | `TAILWIND_BUTTON_KIND` |
143
143
  | `paginationSummary` | `TAILWIND_PAGINATION_SUMMARY` |
144
+ | `passwordLabels` | `TAILWIND_PASSWORD_LABELS` |
144
145
 
145
146
  **`provideTailwindComponents`** is still exported for backward compatibility (token providers only, same implementation as the token slice of `defineTheme`). It is **deprecated**; prefer **`defineTheme`**.
146
147
 
@@ -204,6 +205,7 @@ Some components (for example `tailwind-card`, `tailwind-modal`, `tailwind-toolba
204
205
  ### Form Controls (with ControlValueAccessor)
205
206
 
206
207
  - **Input** (`tailwind-input`): Text, email, password, number, search
208
+ - **Input Password** (`tailwind-input-password`): Password field with optional strength meter and show/hide toggle
207
209
  - **Textarea** (`tailwind-textarea`): Multi-line text with resize modes and rows/cols
208
210
  - **Upload** (`tailwind-upload`): File picker as button or drop zone; value as base64 data URL for forms, `filesSelected` for raw files
209
211
  - **Input OTP** (`tailwind-input-otp`): Multi-digit OTP / PIN with paste and keyboard navigation
@@ -343,6 +343,13 @@ const TAILWIND_HEROICON_NAMES = [
343
343
  'x-mark',
344
344
  ];
345
345
 
346
+ const DEFAULT_TAILWIND_PASSWORD_LABELS = {
347
+ prompt: 'Inserisci una password',
348
+ weak: 'Debole',
349
+ medium: 'Buona',
350
+ strong: 'Forte'
351
+ };
352
+
346
353
  class TailwindComponent {
347
354
  /** Optional ID for the component */
348
355
  id = input(...(ngDevMode ? [undefined, { debugName: "id" }] : /* istanbul ignore next */ []));
@@ -622,6 +629,10 @@ const TAILWIND_BUTTON_KIND = new InjectionToken('TAILWIND_BUTTON_KIND');
622
629
  * Use placeholders `{start}`, `{end}`, `{total}` (same rules as the `summary` input).
623
630
  */
624
631
  const TAILWIND_PAGINATION_SUMMARY = new InjectionToken('TAILWIND_PAGINATION_SUMMARY');
632
+ /**
633
+ * Default labels for `tailwind-input-password` strength feedback when component inputs are omitted.
634
+ */
635
+ const TAILWIND_PASSWORD_LABELS = new InjectionToken('TAILWIND_PASSWORD_LABELS');
625
636
 
626
637
  const clampIconSize = (value) => {
627
638
  if (!Number.isFinite(value))
@@ -991,6 +1002,216 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
991
1002
  ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tailwind-input-wrapper flex flex-col gap-1.5\">\n @if (label()) {\n <label\n [attr.for]=\"id() ? id() + '-inner' : null\"\n class=\"text-sm font-medium text-neutral-700\"\n [class.text-danger-600]=\"hasError()\">\n {{ label() }}\n </label>\n }\n\n <input\n [attr.id]=\"id() ? id() + '-inner' : null\"\n [type]=\"type()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-invalid]=\"hasError() || null\"\n [attr.aria-describedby]=\"(helperText() || errorText()) && id() ? id() + '-helper' : null\"\n [value]=\"value()\"\n [class]=\"inputClasses()\"\n (input)=\"onInputChange($event)\"\n (blur)=\"onBlur()\" />\n\n @if (helperText() && !hasError()) {\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"text-xs text-neutral-500\">\n {{ helperText() }}\n </p>\n }\n @if (errorText() && hasError()) {\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"text-xs text-danger-600\">\n {{ errorText() }}\n </p>\n }\n</div>\n", styles: [":host{display:block}\n"] }]
992
1003
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], hasError: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasError", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
993
1004
 
1005
+ const LOWERCASE = /[a-z]/;
1006
+ const UPPERCASE = /[A-Z]/;
1007
+ const DIGIT = /\d/;
1008
+ const SYMBOL = /[^a-zA-Z0-9]/;
1009
+ const REPEATED_CHAR = /(.)\1{2,}/;
1010
+ const COMMON_SEQUENCES = ['123', '234', '345', '456', '567', '678', '789', 'abc', 'bcd', 'cde', 'qwerty', 'password'];
1011
+ function hasCommonSequence(value) {
1012
+ const lower = value.toLowerCase();
1013
+ return COMMON_SEQUENCES.some(seq => lower.includes(seq));
1014
+ }
1015
+ /**
1016
+ * Computes a password strength score (0–100) and maps it to weak / medium / strong.
1017
+ */
1018
+ function computePasswordStrength(password) {
1019
+ if (!password) {
1020
+ return { score: 0, level: 'weak' };
1021
+ }
1022
+ let score = 0;
1023
+ const length = password.length;
1024
+ if (length >= 8)
1025
+ score += 25;
1026
+ else if (length >= 6)
1027
+ score += 15;
1028
+ else if (length >= 4)
1029
+ score += 8;
1030
+ else
1031
+ score += 4;
1032
+ if (length >= 12)
1033
+ score += 10;
1034
+ if (length >= 16)
1035
+ score += 5;
1036
+ const variety = [LOWERCASE, UPPERCASE, DIGIT, SYMBOL].filter(re => re.test(password)).length;
1037
+ score += variety * 12;
1038
+ if (REPEATED_CHAR.test(password)) {
1039
+ score -= 10;
1040
+ }
1041
+ if (hasCommonSequence(password)) {
1042
+ score -= 15;
1043
+ }
1044
+ score = Math.max(0, Math.min(100, score));
1045
+ let level;
1046
+ if (score < 40) {
1047
+ level = 'weak';
1048
+ }
1049
+ else if (score < 70) {
1050
+ level = 'medium';
1051
+ }
1052
+ else {
1053
+ level = 'strong';
1054
+ }
1055
+ return { score, level };
1056
+ }
1057
+ /**
1058
+ * Returns how many of the three meter segments should be filled (1–3) for the given level.
1059
+ */
1060
+ function passwordStrengthMeterFill(level) {
1061
+ switch (level) {
1062
+ case 'weak':
1063
+ return 1;
1064
+ case 'medium':
1065
+ return 2;
1066
+ case 'strong':
1067
+ return 3;
1068
+ }
1069
+ }
1070
+
1071
+ class TailwindInputPassword extends TailwindComponent {
1072
+ themeLabels = inject(TAILWIND_PASSWORD_LABELS, { optional: true });
1073
+ /** Label text */
1074
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1075
+ /** Placeholder text */
1076
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1077
+ /** Size variant */
1078
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1079
+ /** Helper text shown below input */
1080
+ helperText = input('', ...(ngDevMode ? [{ debugName: "helperText" }] : /* istanbul ignore next */ []));
1081
+ /** Error text shown when hasError is true */
1082
+ errorText = input('', ...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
1083
+ /** Whether the input is in error state */
1084
+ hasError = input(false, ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
1085
+ /** Show strength feedback panel while typing */
1086
+ feedback = input(false, ...(ngDevMode ? [{ debugName: "feedback" }] : /* istanbul ignore next */ []));
1087
+ /** Show toggle to reveal/hide password */
1088
+ toggleMask = input(false, ...(ngDevMode ? [{ debugName: "toggleMask" }] : /* istanbul ignore next */ []));
1089
+ /** Override prompt label from theme token */
1090
+ promptLabel = input(undefined, ...(ngDevMode ? [{ debugName: "promptLabel" }] : /* istanbul ignore next */ []));
1091
+ /** Override weak strength label from theme token */
1092
+ weakLabel = input(undefined, ...(ngDevMode ? [{ debugName: "weakLabel" }] : /* istanbul ignore next */ []));
1093
+ /** Override medium strength label from theme token */
1094
+ mediumLabel = input(undefined, ...(ngDevMode ? [{ debugName: "mediumLabel" }] : /* istanbul ignore next */ []));
1095
+ /** Override strong strength label from theme token */
1096
+ strongLabel = input(undefined, ...(ngDevMode ? [{ debugName: "strongLabel" }] : /* istanbul ignore next */ []));
1097
+ /** Two-way bound value */
1098
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1099
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
1100
+ masked = signal(true, ...(ngDevMode ? [{ debugName: "masked" }] : /* istanbul ignore next */ []));
1101
+ isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
1102
+ strength = computed(() => computePasswordStrength(this.value()), ...(ngDevMode ? [{ debugName: "strength" }] : /* istanbul ignore next */ []));
1103
+ strengthLevel = computed(() => this.strength().level, ...(ngDevMode ? [{ debugName: "strengthLevel" }] : /* istanbul ignore next */ []));
1104
+ meterFill = computed(() => passwordStrengthMeterFill(this.strengthLevel()), ...(ngDevMode ? [{ debugName: "meterFill" }] : /* istanbul ignore next */ []));
1105
+ showFeedbackPanel = computed(() => this.feedback() && this.isFocused() && this.value().length > 0, ...(ngDevMode ? [{ debugName: "showFeedbackPanel" }] : /* istanbul ignore next */ []));
1106
+ resolvedLabels = computed(() => {
1107
+ const defaults = this.themeLabels ?? DEFAULT_TAILWIND_PASSWORD_LABELS;
1108
+ return {
1109
+ prompt: this.promptLabel() ?? defaults.prompt,
1110
+ weak: this.weakLabel() ?? defaults.weak,
1111
+ medium: this.mediumLabel() ?? defaults.medium,
1112
+ strong: this.strongLabel() ?? defaults.strong
1113
+ };
1114
+ }, ...(ngDevMode ? [{ debugName: "resolvedLabels" }] : /* istanbul ignore next */ []));
1115
+ strengthLabel = computed(() => {
1116
+ const labels = this.resolvedLabels();
1117
+ const level = this.strengthLevel();
1118
+ if (level === 'medium')
1119
+ return labels.medium;
1120
+ if (level === 'strong')
1121
+ return labels.strong;
1122
+ return labels.weak;
1123
+ }, ...(ngDevMode ? [{ debugName: "strengthLabel" }] : /* istanbul ignore next */ []));
1124
+ inputType = computed(() => (this.masked() ? 'password' : 'text'), ...(ngDevMode ? [{ debugName: "inputType" }] : /* istanbul ignore next */ []));
1125
+ inputPaddingClass = computed(() => (this.toggleMask() ? 'pr-10' : ''), ...(ngDevMode ? [{ debugName: "inputPaddingClass" }] : /* istanbul ignore next */ []));
1126
+ toggleIcon = computed(() => (this.masked() ? 'eye' : 'eye-slash'), ...(ngDevMode ? [{ debugName: "toggleIcon" }] : /* istanbul ignore next */ []));
1127
+ toggleAriaLabel = computed(() => (this.masked() ? 'Mostra password' : 'Nascondi password'), ...(ngDevMode ? [{ debugName: "toggleAriaLabel" }] : /* istanbul ignore next */ []));
1128
+ inputClasses = computed(() => {
1129
+ const base = [
1130
+ 'block w-full bg-white',
1131
+ 'border transition-colors duration-150',
1132
+ 'placeholder:text-neutral-400',
1133
+ 'outline-none focus:outline focus:outline-2 focus:outline-offset-2',
1134
+ 'disabled:bg-neutral-50 disabled:text-neutral-400 disabled:cursor-not-allowed'
1135
+ ];
1136
+ const sizeMap = {
1137
+ xs: 'text-xs px-2 py-1 rounded-sm',
1138
+ sm: 'text-sm px-2.5 py-1.5 rounded-md',
1139
+ md: 'text-sm px-3 py-2 rounded-md',
1140
+ lg: 'text-base px-3.5 py-2.5 rounded-lg',
1141
+ xl: 'text-base px-4 py-3 rounded-lg'
1142
+ };
1143
+ const stateClass = this.hasError()
1144
+ ? 'border-danger-400 focus:outline-danger-500 text-danger-900'
1145
+ : 'border-neutral-300 focus:outline-primary-500 text-neutral-900';
1146
+ return [...base, sizeMap[this.size()], stateClass, this.inputPaddingClass()].filter(Boolean).join(' ');
1147
+ }, ...(ngDevMode ? [{ debugName: "inputClasses" }] : /* istanbul ignore next */ []));
1148
+ meterSegmentClasses = computed(() => {
1149
+ const fill = this.meterFill();
1150
+ const level = this.strengthLevel();
1151
+ const activeClass = level === 'weak' ? 'bg-danger-500' : level === 'medium' ? 'bg-warning-500' : 'bg-success-600';
1152
+ return [0, 1, 2].map(index => {
1153
+ const isActive = index < fill;
1154
+ return isActive ? activeClass : 'bg-neutral-200';
1155
+ });
1156
+ }, ...(ngDevMode ? [{ debugName: "meterSegmentClasses" }] : /* istanbul ignore next */ []));
1157
+ // CVA
1158
+ onChange = () => { };
1159
+ onTouched = () => { };
1160
+ writeValue(value) {
1161
+ this.value.set(value ?? '');
1162
+ }
1163
+ registerOnChange(fn) {
1164
+ this.onChange = fn;
1165
+ }
1166
+ registerOnTouched(fn) {
1167
+ this.onTouched = fn;
1168
+ }
1169
+ setDisabledState(disabled) {
1170
+ this.isDisabled.set(disabled);
1171
+ }
1172
+ onInputChange(event) {
1173
+ const val = event.target.value;
1174
+ this.value.set(val);
1175
+ this.onChange(val);
1176
+ }
1177
+ onFocus() {
1178
+ this.isFocused.set(true);
1179
+ }
1180
+ onBlur() {
1181
+ this.isFocused.set(false);
1182
+ this.onTouched();
1183
+ }
1184
+ toggleMaskVisibility() {
1185
+ this.masked.update(v => !v);
1186
+ }
1187
+ onEscape() {
1188
+ if (this.showFeedbackPanel()) {
1189
+ this.isFocused.set(false);
1190
+ }
1191
+ }
1192
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TailwindInputPassword, deps: null, target: i0.ɵɵFactoryTarget.Component });
1193
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: TailwindInputPassword, isStandalone: true, selector: "tailwind-input-password", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, hasError: { classPropertyName: "hasError", publicName: "hasError", isSignal: true, isRequired: false, transformFunction: null }, feedback: { classPropertyName: "feedback", publicName: "feedback", isSignal: true, isRequired: false, transformFunction: null }, toggleMask: { classPropertyName: "toggleMask", publicName: "toggleMask", isSignal: true, isRequired: false, transformFunction: null }, promptLabel: { classPropertyName: "promptLabel", publicName: "promptLabel", isSignal: true, isRequired: false, transformFunction: null }, weakLabel: { classPropertyName: "weakLabel", publicName: "weakLabel", isSignal: true, isRequired: false, transformFunction: null }, mediumLabel: { classPropertyName: "mediumLabel", publicName: "mediumLabel", isSignal: true, isRequired: false, transformFunction: null }, strongLabel: { classPropertyName: "strongLabel", publicName: "strongLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, providers: [
1194
+ {
1195
+ provide: NG_VALUE_ACCESSOR,
1196
+ useExisting: forwardRef(() => TailwindInputPassword),
1197
+ multi: true
1198
+ }
1199
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"tailwind-input-password-wrapper flex flex-col gap-1.5\">\r\n @if (label()) {\r\n <label\r\n [attr.for]=\"id() ? id() + '-inner' : null\"\r\n class=\"text-sm font-medium text-neutral-700\"\r\n [class.text-danger-600]=\"hasError()\">\r\n {{ label() }}\r\n </label>\r\n }\r\n\r\n <div class=\"relative\">\r\n <input\r\n [attr.id]=\"id() ? id() + '-inner' : null\"\r\n [type]=\"inputType()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"isDisabled()\"\r\n autocomplete=\"current-password\"\r\n [attr.aria-invalid]=\"hasError() || null\"\r\n [attr.aria-describedby]=\"(helperText() || errorText() || feedback()) && id() ? id() + '-helper' : null\"\r\n [value]=\"value()\"\r\n [class]=\"inputClasses()\"\r\n (input)=\"onInputChange($event)\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\" />\r\n\r\n @if (toggleMask()) {\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-md p-1 text-neutral-500 hover:text-neutral-700 focus:outline focus:outline-offset-2 focus:outline-primary-500 disabled:pointer-events-none disabled:opacity-50\"\r\n [attr.aria-label]=\"toggleAriaLabel()\"\r\n [disabled]=\"isDisabled()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"toggleMaskVisibility()\">\r\n <tailwind-icon [icon]=\"toggleIcon()\" [size]=\"16\" />\r\n </button>\r\n }\r\n\r\n @if (showFeedbackPanel()) {\r\n <div\r\n class=\"absolute left-0 top-full z-popover mt-1 w-max max-w-xl min-w-48 rounded-lg border border-neutral-200 bg-white p-3 shadow-lg\"\r\n role=\"region\"\r\n [attr.aria-label]=\"resolvedLabels().prompt\">\r\n <p class=\"mb-2 text-xs font-medium text-neutral-700\">{{ resolvedLabels().prompt }}</p>\r\n <div class=\"flex w-56 max-w-full gap-1\" role=\"img\" [attr.aria-label]=\"strengthLabel()\">\r\n @for (segmentClass of meterSegmentClasses(); track $index) {\r\n <span class=\"h-1.5 flex-1 rounded-full transition-colors duration-150\" [class]=\"segmentClass\"></span>\r\n }\r\n </div>\r\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"mt-2 text-xs text-neutral-600\" aria-live=\"polite\">\r\n {{ strengthLabel() }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (helperText() && !hasError()) {\r\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"text-xs text-neutral-500\">\r\n {{ helperText() }}\r\n </p>\r\n }\r\n @if (errorText() && hasError()) {\r\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"text-xs text-danger-600\">\r\n {{ errorText() }}\r\n </p>\r\n }\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "component", type: TailwindIcon, selector: "tailwind-icon", inputs: ["icon", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1200
+ }
1201
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TailwindInputPassword, decorators: [{
1202
+ type: Component,
1203
+ args: [{ imports: [TailwindIcon], selector: 'tailwind-input-password', providers: [
1204
+ {
1205
+ provide: NG_VALUE_ACCESSOR,
1206
+ useExisting: forwardRef(() => TailwindInputPassword),
1207
+ multi: true
1208
+ }
1209
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tailwind-input-password-wrapper flex flex-col gap-1.5\">\r\n @if (label()) {\r\n <label\r\n [attr.for]=\"id() ? id() + '-inner' : null\"\r\n class=\"text-sm font-medium text-neutral-700\"\r\n [class.text-danger-600]=\"hasError()\">\r\n {{ label() }}\r\n </label>\r\n }\r\n\r\n <div class=\"relative\">\r\n <input\r\n [attr.id]=\"id() ? id() + '-inner' : null\"\r\n [type]=\"inputType()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"isDisabled()\"\r\n autocomplete=\"current-password\"\r\n [attr.aria-invalid]=\"hasError() || null\"\r\n [attr.aria-describedby]=\"(helperText() || errorText() || feedback()) && id() ? id() + '-helper' : null\"\r\n [value]=\"value()\"\r\n [class]=\"inputClasses()\"\r\n (input)=\"onInputChange($event)\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\" />\r\n\r\n @if (toggleMask()) {\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-md p-1 text-neutral-500 hover:text-neutral-700 focus:outline focus:outline-offset-2 focus:outline-primary-500 disabled:pointer-events-none disabled:opacity-50\"\r\n [attr.aria-label]=\"toggleAriaLabel()\"\r\n [disabled]=\"isDisabled()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"toggleMaskVisibility()\">\r\n <tailwind-icon [icon]=\"toggleIcon()\" [size]=\"16\" />\r\n </button>\r\n }\r\n\r\n @if (showFeedbackPanel()) {\r\n <div\r\n class=\"absolute left-0 top-full z-popover mt-1 w-max max-w-xl min-w-48 rounded-lg border border-neutral-200 bg-white p-3 shadow-lg\"\r\n role=\"region\"\r\n [attr.aria-label]=\"resolvedLabels().prompt\">\r\n <p class=\"mb-2 text-xs font-medium text-neutral-700\">{{ resolvedLabels().prompt }}</p>\r\n <div class=\"flex w-56 max-w-full gap-1\" role=\"img\" [attr.aria-label]=\"strengthLabel()\">\r\n @for (segmentClass of meterSegmentClasses(); track $index) {\r\n <span class=\"h-1.5 flex-1 rounded-full transition-colors duration-150\" [class]=\"segmentClass\"></span>\r\n }\r\n </div>\r\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"mt-2 text-xs text-neutral-600\" aria-live=\"polite\">\r\n {{ strengthLabel() }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (helperText() && !hasError()) {\r\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"text-xs text-neutral-500\">\r\n {{ helperText() }}\r\n </p>\r\n }\r\n @if (errorText() && hasError()) {\r\n <p [attr.id]=\"id() ? id() + '-helper' : null\" class=\"text-xs text-danger-600\">\r\n {{ errorText() }}\r\n </p>\r\n }\r\n</div>\r\n" }]
1210
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], hasError: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasError", required: false }] }], feedback: [{ type: i0.Input, args: [{ isSignal: true, alias: "feedback", required: false }] }], toggleMask: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleMask", required: false }] }], promptLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptLabel", required: false }] }], weakLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "weakLabel", required: false }] }], mediumLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediumLabel", required: false }] }], strongLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "strongLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onEscape: [{
1211
+ type: HostListener,
1212
+ args: ['document:keydown.escape']
1213
+ }] } });
1214
+
994
1215
  class TailwindTextarea extends TailwindComponent {
995
1216
  /** Label text */
996
1217
  label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
@@ -4661,6 +4882,9 @@ function providersFromTailwindComponentsConfig(config) {
4661
4882
  if (config.paginationSummary !== undefined) {
4662
4883
  providers.push({ provide: TAILWIND_PAGINATION_SUMMARY, useValue: config.paginationSummary });
4663
4884
  }
4885
+ if (config.passwordLabels !== undefined) {
4886
+ providers.push({ provide: TAILWIND_PASSWORD_LABELS, useValue: config.passwordLabels });
4887
+ }
4664
4888
  return providers;
4665
4889
  }
4666
4890
  /**
@@ -4808,5 +5032,5 @@ function defineTheme(config) {
4808
5032
  * Generated bundle index. Do not edit.
4809
5033
  */
4810
5034
 
4811
- export { DEFAULT_PAGINATION_LENGTH_OPTIONS, TAILWIND_BUTTON_KIND, TAILWIND_COMPONENTS_SIZE, TAILWIND_DATETIME_LANGUAGE, TAILWIND_HEROICON_NAMES, TAILWIND_ICON_SIZE, TAILWIND_MODAL_DATA, TAILWIND_PAGINATION_SUMMARY, TW_TABLE_SORT_DIR_ATTR, TW_TABLE_SORT_KEY_ATTR, TailwindAccordion, TailwindAccordionItem, TailwindAlert, TailwindAutocomplete, TailwindBadge, TailwindBreadcrumb, TailwindButton, TailwindCard, TailwindCheckbox, TailwindDatePicker, TailwindDateTimePicker, TailwindDivider, TailwindDrawer, TailwindIcon, TailwindInput, TailwindInputOtp, TailwindMenu, TailwindMessage, TailwindMeter, TailwindModal, TailwindModalRef, TailwindModalService, TailwindNotification, TailwindPagination, TailwindProgressBar, TailwindRadioGroup, TailwindSelect, TailwindSkeleton, TailwindSlider, TailwindSortHeaderDirective, TailwindSpinner, TailwindStep, TailwindStepper, TailwindTab, TailwindTabGroup, TailwindTable, TailwindTableRowDirective, TailwindTableRowDirective as TailwindTableRowTemplateDirective, TailwindTag, TailwindTextarea, TailwindTimePicker, TailwindTitle, TailwindToast, TailwindToastService, TailwindToggle, TailwindToolbar, TailwindTooltip, TailwindTooltipDirective, TailwindUpload, buildTailwindThemeVariableEntries, defineTheme, provideTailwindComponents };
5035
+ export { DEFAULT_PAGINATION_LENGTH_OPTIONS, DEFAULT_TAILWIND_PASSWORD_LABELS, TAILWIND_BUTTON_KIND, TAILWIND_COMPONENTS_SIZE, TAILWIND_DATETIME_LANGUAGE, TAILWIND_HEROICON_NAMES, TAILWIND_ICON_SIZE, TAILWIND_MODAL_DATA, TAILWIND_PAGINATION_SUMMARY, TAILWIND_PASSWORD_LABELS, TW_TABLE_SORT_DIR_ATTR, TW_TABLE_SORT_KEY_ATTR, TailwindAccordion, TailwindAccordionItem, TailwindAlert, TailwindAutocomplete, TailwindBadge, TailwindBreadcrumb, TailwindButton, TailwindCard, TailwindCheckbox, TailwindDatePicker, TailwindDateTimePicker, TailwindDivider, TailwindDrawer, TailwindIcon, TailwindInput, TailwindInputOtp, TailwindInputPassword, TailwindMenu, TailwindMessage, TailwindMeter, TailwindModal, TailwindModalRef, TailwindModalService, TailwindNotification, TailwindPagination, TailwindProgressBar, TailwindRadioGroup, TailwindSelect, TailwindSkeleton, TailwindSlider, TailwindSortHeaderDirective, TailwindSpinner, TailwindStep, TailwindStepper, TailwindTab, TailwindTabGroup, TailwindTable, TailwindTableRowDirective, TailwindTableRowDirective as TailwindTableRowTemplateDirective, TailwindTag, TailwindTextarea, TailwindTimePicker, TailwindTitle, TailwindToast, TailwindToastService, TailwindToggle, TailwindToolbar, TailwindTooltip, TailwindTooltipDirective, TailwindUpload, buildTailwindThemeVariableEntries, defineTheme, provideTailwindComponents };
4812
5036
  //# sourceMappingURL=angular-tailwind-components.mjs.map