@ktortu/aaa 0.9.1 → 0.9.2

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 (39) hide show
  1. package/fesm2022/ktortu-aaa-button.mjs +10 -2
  2. package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
  3. package/fesm2022/ktortu-aaa-card.mjs +14 -5
  4. package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
  5. package/fesm2022/ktortu-aaa-cdk.mjs +49 -2
  6. package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
  7. package/fesm2022/ktortu-aaa-dialog.mjs +19 -31
  8. package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
  9. package/fesm2022/ktortu-aaa-disclosure.mjs +201 -0
  10. package/fesm2022/ktortu-aaa-disclosure.mjs.map +1 -0
  11. package/fesm2022/ktortu-aaa-forms.mjs +210 -93
  12. package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
  13. package/fesm2022/ktortu-aaa-progress-bar.mjs +91 -0
  14. package/fesm2022/ktortu-aaa-progress-bar.mjs.map +1 -0
  15. package/fesm2022/ktortu-aaa-snackbar.mjs +16 -3
  16. package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -1
  17. package/fesm2022/ktortu-aaa-tooltip.mjs +20 -30
  18. package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -1
  19. package/fesm2022/ktortu-aaa.mjs +1 -0
  20. package/fesm2022/ktortu-aaa.mjs.map +1 -1
  21. package/forms/field/field.css +16 -0
  22. package/forms/styles/field-box.css +11 -5
  23. package/package.json +9 -1
  24. package/progress-bar/progress-bar.css +127 -0
  25. package/styles/disclosure.css +6 -0
  26. package/styles/progress-bar.css +5 -0
  27. package/styles/styles.css +2 -0
  28. package/themes/theme-cyberpunk.css +11 -0
  29. package/tooltip/tooltip.css +1 -1
  30. package/types/ktortu-aaa-button.d.ts +2 -0
  31. package/types/ktortu-aaa-card.d.ts +3 -0
  32. package/types/ktortu-aaa-cdk.d.ts +27 -1
  33. package/types/ktortu-aaa-dialog.d.ts +7 -14
  34. package/types/ktortu-aaa-disclosure.d.ts +112 -0
  35. package/types/ktortu-aaa-forms.d.ts +66 -14
  36. package/types/ktortu-aaa-progress-bar.d.ts +27 -0
  37. package/types/ktortu-aaa-snackbar.d.ts +11 -68
  38. package/types/ktortu-aaa-tooltip.d.ts +0 -1
  39. package/types/ktortu-aaa.d.ts +1 -0
@@ -1,8 +1,8 @@
1
+ import { NgTemplateOutlet, DOCUMENT, isPlatformBrowser } from '@angular/common';
1
2
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, computed, ElementRef, Directive, input, output, booleanAttribute, contentChild, viewChild, forwardRef, ChangeDetectionStrategy, Component, Injectable, model, TemplateRef, afterNextRender, isDevMode, effect, NgZone, PLATFORM_ID, DestroyRef, signal, untracked, Injector, ChangeDetectorRef, viewChildren, LOCALE_ID, Pipe } from '@angular/core';
3
+ import { InjectionToken, inject, computed, ElementRef, Directive, input, output, booleanAttribute, contentChild, TemplateRef, forwardRef, ChangeDetectionStrategy, Component, Injectable, model, viewChild, signal, afterNextRender, isDevMode, DestroyRef, effect, PLATFORM_ID, untracked, Injector, ChangeDetectorRef, viewChildren, afterRenderEffect, LOCALE_ID, Pipe } from '@angular/core';
3
4
  import { KtTooltip } from '@ktortu/aaa/tooltip';
4
- import { KtIdGenerator, KtViewport, createKtSheetDrag } from '@ktortu/aaa/cdk';
5
- import { NgTemplateOutlet, DOCUMENT, isPlatformBrowser } from '@angular/common';
5
+ import { KtIdGenerator, KT_AUDIT_ENABLED, KtBodyScrollLock, KtViewport, createKtSheetDrag } from '@ktortu/aaa/cdk';
6
6
  import { transformedValue } from '@angular/forms/signals';
7
7
  import { Listbox, Option } from '@angular/aria/listbox';
8
8
  import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox';
@@ -142,14 +142,6 @@ class KtField {
142
142
  // Le contrôle est projeté : l'injection DI ne le traverse pas, on le cible via contentChild.
143
143
  control = contentChild(KtFieldControl, /* @ts-ignore */
144
144
  ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
145
- // Détecte le tooltip d'aide standard défini dans field.html (input DX)
146
- templateTooltip = viewChild(KtTooltip, /* @ts-ignore */
147
- ...(ngDevMode ? [{ debugName: "templateTooltip" }] : /* istanbul ignore next */ []));
148
- // Détecte un tooltip d'aide custom projeté dans le slot ktFieldHelp
149
- projectedTooltip = contentChild(KtTooltip, { ...(ngDevMode ? { debugName: "projectedTooltip" } : /* istanbul ignore next */ {}), descendants: true });
150
- // Résout le tooltip actif
151
- activeTooltip = computed(() => this.projectedTooltip() ?? this.templateTooltip(), /* @ts-ignore */
152
- ...(ngDevMode ? [{ debugName: "activeTooltip" }] : /* istanbul ignore next */ []));
153
145
  idGen = inject(KtIdGenerator);
154
146
  uid = this.idGen.generateId('field');
155
147
  baseId = computed(() => this.fieldId() ?? `kt-field-${this.uid}`, /* @ts-ignore */
@@ -160,6 +152,28 @@ class KtField {
160
152
  ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
161
153
  errorId = computed(() => `${this.baseId()}-error`, /* @ts-ignore */
162
154
  ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
155
+ helpId = computed(() => `${this.baseId()}-help`, /* @ts-ignore */
156
+ ...(ngDevMode ? [{ debugName: "helpId" }] : /* istanbul ignore next */ []));
157
+ // `helpText` accepte texte simple OU TemplateRef : on sépare les deux pour la copie cachée (a11y).
158
+ helpTemplate = computed(() => {
159
+ const help = this.helpText();
160
+ return help instanceof TemplateRef ? help : null;
161
+ }, /* @ts-ignore */
162
+ ...(ngDevMode ? [{ debugName: "helpTemplate" }] : /* istanbul ignore next */ []));
163
+ helpString = computed(() => {
164
+ const help = this.helpText();
165
+ return typeof help === 'string' ? help : null;
166
+ }, /* @ts-ignore */
167
+ ...(ngDevMode ? [{ debugName: "helpString" }] : /* istanbul ignore next */ []));
168
+ // Aide réellement signifiante : texte non blanc, ou TemplateRef. Un texte vide/blanc rend le
169
+ // bouton d'aide (DX) mais ne doit PAS être référencé par aria-describedby (rien à annoncer).
170
+ hasHelp = computed(() => {
171
+ const help = this.helpText();
172
+ if (help == null)
173
+ return false;
174
+ return typeof help === 'string' ? help.trim().length > 0 : true;
175
+ }, /* @ts-ignore */
176
+ ...(ngDevMode ? [{ debugName: "hasHelp" }] : /* istanbul ignore next */ []));
163
177
  hasError = computed(() => this.invalid() && this.errors().length > 0, /* @ts-ignore */
164
178
  ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
165
179
  // Hint visible sauf s'il est masqué en erreur (option façon Material).
@@ -174,11 +188,11 @@ class KtField {
174
188
  ids.push(this.hintId());
175
189
  if (this.hasError())
176
190
  ids.push(this.errorId());
177
- // Liaison a11y automatique avec le Tooltip d'aide actif (si présent et actif)
178
- const tooltip = this.activeTooltip();
179
- if (tooltip && tooltip.isActive()) {
180
- ids.push(tooltip.idForA11y);
181
- }
191
+ // Aide contextuelle : référence une copie TOUJOURS présente (visually-hidden) du texte d'aide.
192
+ // Le tooltip visuel n'est créé dans le DOM qu'à l'affichage ; cette copie garantit que les
193
+ // technologies d'assistance trouvent toujours la description, sans référence pendante.
194
+ if (this.hasHelp())
195
+ ids.push(this.helpId());
182
196
  // Ajout de l'ID personnalisé externe (decouplage complet)
183
197
  const custom = this.customDescribedBy();
184
198
  if (custom)
@@ -192,15 +206,15 @@ class KtField {
192
206
  this.helpClick.emit(event);
193
207
  }
194
208
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtField, deps: [], target: i0.ɵɵFactoryTarget.Component });
195
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtField, isStandalone: true, selector: "kt-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, helpText: { classPropertyName: "helpText", publicName: "helpText", isSignal: true, isRequired: false, transformFunction: null }, helpLabel: { classPropertyName: "helpLabel", publicName: "helpLabel", isSignal: true, isRequired: false, transformFunction: null }, customDescribedBy: { classPropertyName: "customDescribedBy", publicName: "customDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, fieldId: { classPropertyName: "fieldId", publicName: "fieldId", isSignal: true, isRequired: false, transformFunction: null }, hideHintWhenInvalid: { classPropertyName: "hideHintWhenInvalid", publicName: "hideHintWhenInvalid", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, floatLabel: { classPropertyName: "floatLabel", publicName: "floatLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { helpClick: "helpClick" }, host: { properties: { "attr.data-appearance": "resolvedAppearance()", "attr.data-float-label": "resolvedFloatLabel()" } }, providers: [{ provide: KT_FIELD, useExisting: forwardRef(() => KtField) }], queries: [{ propertyName: "control", first: true, predicate: KtFieldControl, descendants: true, isSignal: true }, { propertyName: "projectedTooltip", first: true, predicate: KtTooltip, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "templateTooltip", first: true, predicate: KtTooltip, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"kt-field__header\">\n @if (label(); as labelText) {\n <label class=\"kt-field__label\" [id]=\"labelId()\" [for]=\"baseId()\">\n {{ labelText }}\n @if (required()) {\n <span class=\"kt-field__required\" aria-hidden=\"true\">*</span>\n }\n </label>\n }\n\n @if (helpText(); as help) {\n <button\n type=\"button\"\n class=\"kt-field__help\"\n [ktTooltip]=\"help\"\n [attr.aria-label]=\"helpLabel()\"\n [attr.aria-describedby]=\"null\"\n (click)=\"onHelpClick($event)\"\n >\n <span class=\"kt-field__help-icon\" aria-hidden=\"true\">help</span>\n </button>\n }\n\n <ng-content select=\"[ktFieldHelp]\" />\n</div>\n\n<div class=\"kt-field__control\">\n <ng-content />\n</div>\n\n@if (showHint()) {\n <p class=\"kt-field__hint\" [id]=\"hintId()\">{{ hint() }}</p>\n}\n\n<!-- R\u00E9gion live toujours pr\u00E9sente : les AT annoncent l'erreur d\u00E8s son apparition. -->\n<p class=\"kt-field__error\" [id]=\"errorId()\" aria-live=\"polite\">\n @if (hasError()) {\n @for (error of displayedErrors(); track $index) {\n <span class=\"kt-field__error-message\">{{ error.message }}</span>\n }\n }\n</p>\n", styles: ["@layer kt-aaa.components{:host{display:flex;flex-direction:column;gap:var(--field-gap, .375rem);font-size:var(--field-font-size, 1rem);color:var(--field-color, inherit)}.kt-field__header{display:flex;align-items:center;gap:.375rem}.kt-field__label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);text-transform:var(--field-label-transform, none);letter-spacing:var(--field-label-letter-spacing, normal);color:var(--field-label-color, inherit)}.kt-field__help{display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;background:transparent;color:var(--field-icon-color, #474747);cursor:pointer;position:relative}.kt-field__help:after{content:\"\";position:absolute;inset:-12px}.kt-field__help-icon{font-family:Material Symbols Outlined;font-size:1.25rem;line-height:1;font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.kt-field__required{margin-inline-start:.125rem;color:var(--field-required-color, #8c1d18)}.kt-field__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-field__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-field__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-field__error-message{animation:none}}}\n"], dependencies: [{ kind: "directive", type: KtTooltip, selector: "[ktTooltip]", inputs: ["ktTooltip", "tooltipPosition", "tooltipDisabled", "showDelay", "hideDelay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
209
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtField, isStandalone: true, selector: "kt-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, helpText: { classPropertyName: "helpText", publicName: "helpText", isSignal: true, isRequired: false, transformFunction: null }, helpLabel: { classPropertyName: "helpLabel", publicName: "helpLabel", isSignal: true, isRequired: false, transformFunction: null }, customDescribedBy: { classPropertyName: "customDescribedBy", publicName: "customDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, fieldId: { classPropertyName: "fieldId", publicName: "fieldId", isSignal: true, isRequired: false, transformFunction: null }, hideHintWhenInvalid: { classPropertyName: "hideHintWhenInvalid", publicName: "hideHintWhenInvalid", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, floatLabel: { classPropertyName: "floatLabel", publicName: "floatLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { helpClick: "helpClick" }, host: { properties: { "attr.data-appearance": "resolvedAppearance()", "attr.data-float-label": "resolvedFloatLabel()" } }, providers: [{ provide: KT_FIELD, useExisting: forwardRef(() => KtField) }], queries: [{ propertyName: "control", first: true, predicate: KtFieldControl, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"kt-field__header\">\n @if (label(); as labelText) {\n <label class=\"kt-field__label\" [id]=\"labelId()\" [for]=\"baseId()\">\n {{ labelText }}\n @if (required()) {\n <span class=\"kt-field__required\" aria-hidden=\"true\">*</span>\n }\n </label>\n }\n\n @if (helpText(); as help) {\n <button\n type=\"button\"\n class=\"kt-field__help\"\n [ktTooltip]=\"help\"\n [attr.aria-label]=\"helpLabel()\"\n [attr.aria-describedby]=\"null\"\n (click)=\"onHelpClick($event)\"\n >\n <span class=\"kt-field__help-icon\" aria-hidden=\"true\">help</span>\n </button>\n }\n\n <ng-content select=\"[ktFieldHelp]\" />\n</div>\n\n<div class=\"kt-field__control\">\n <ng-content />\n</div>\n\n@if (hasHelp()) {\n <!-- Copie de l'aide TOUJOURS pr\u00E9sente (visually-hidden) : cible stable d'aria-describedby du\n contr\u00F4le, alors que la bulle visuelle du tooltip n'existe dans le DOM qu'\u00E0 l'affichage. -->\n <span [id]=\"helpId()\" class=\"kt-field__help-description\">\n @if (helpTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ helpString() }}\n }\n </span>\n}\n\n@if (showHint()) {\n <p class=\"kt-field__hint\" [id]=\"hintId()\">{{ hint() }}</p>\n}\n\n<!-- R\u00E9gion live toujours pr\u00E9sente : les AT annoncent l'erreur d\u00E8s son apparition. -->\n<p class=\"kt-field__error\" [id]=\"errorId()\" aria-live=\"polite\">\n @if (hasError()) {\n @for (error of displayedErrors(); track $index) {\n <span class=\"kt-field__error-message\">{{ error.message }}</span>\n }\n }\n</p>\n", styles: ["@layer kt-aaa.components{:host{display:flex;flex-direction:column;gap:var(--field-gap, .375rem);font-size:var(--field-font-size, 1rem);color:var(--field-color, inherit)}.kt-field__header{display:flex;align-items:center;gap:.375rem}.kt-field__label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);text-transform:var(--field-label-transform, none);letter-spacing:var(--field-label-letter-spacing, normal);color:var(--field-label-color, inherit)}.kt-field__help{display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;background:transparent;color:var(--field-icon-color, #474747);cursor:pointer;position:relative}.kt-field__help:after{content:\"\";position:absolute;inset:-12px}.kt-field__help-icon{font-family:Material Symbols Outlined;font-size:1.25rem;line-height:1;font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.kt-field__required{margin-inline-start:.125rem;color:var(--field-required-color, #8c1d18)}.kt-field__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-field__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-field__help-description{position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.kt-field__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-field__error-message{animation:none}}}\n"], dependencies: [{ kind: "directive", type: KtTooltip, selector: "[ktTooltip]", inputs: ["ktTooltip", "tooltipPosition", "tooltipDisabled", "showDelay", "hideDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
196
210
  }
197
211
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtField, decorators: [{
198
212
  type: Component,
199
- args: [{ selector: 'kt-field', imports: [KtTooltip], changeDetection: ChangeDetectionStrategy.OnPush, host: {
213
+ args: [{ selector: 'kt-field', imports: [KtTooltip, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
200
214
  '[attr.data-appearance]': 'resolvedAppearance()',
201
215
  '[attr.data-float-label]': 'resolvedFloatLabel()',
202
- }, providers: [{ provide: KT_FIELD, useExisting: forwardRef(() => KtField) }], template: "<div class=\"kt-field__header\">\n @if (label(); as labelText) {\n <label class=\"kt-field__label\" [id]=\"labelId()\" [for]=\"baseId()\">\n {{ labelText }}\n @if (required()) {\n <span class=\"kt-field__required\" aria-hidden=\"true\">*</span>\n }\n </label>\n }\n\n @if (helpText(); as help) {\n <button\n type=\"button\"\n class=\"kt-field__help\"\n [ktTooltip]=\"help\"\n [attr.aria-label]=\"helpLabel()\"\n [attr.aria-describedby]=\"null\"\n (click)=\"onHelpClick($event)\"\n >\n <span class=\"kt-field__help-icon\" aria-hidden=\"true\">help</span>\n </button>\n }\n\n <ng-content select=\"[ktFieldHelp]\" />\n</div>\n\n<div class=\"kt-field__control\">\n <ng-content />\n</div>\n\n@if (showHint()) {\n <p class=\"kt-field__hint\" [id]=\"hintId()\">{{ hint() }}</p>\n}\n\n<!-- R\u00E9gion live toujours pr\u00E9sente : les AT annoncent l'erreur d\u00E8s son apparition. -->\n<p class=\"kt-field__error\" [id]=\"errorId()\" aria-live=\"polite\">\n @if (hasError()) {\n @for (error of displayedErrors(); track $index) {\n <span class=\"kt-field__error-message\">{{ error.message }}</span>\n }\n }\n</p>\n", styles: ["@layer kt-aaa.components{:host{display:flex;flex-direction:column;gap:var(--field-gap, .375rem);font-size:var(--field-font-size, 1rem);color:var(--field-color, inherit)}.kt-field__header{display:flex;align-items:center;gap:.375rem}.kt-field__label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);text-transform:var(--field-label-transform, none);letter-spacing:var(--field-label-letter-spacing, normal);color:var(--field-label-color, inherit)}.kt-field__help{display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;background:transparent;color:var(--field-icon-color, #474747);cursor:pointer;position:relative}.kt-field__help:after{content:\"\";position:absolute;inset:-12px}.kt-field__help-icon{font-family:Material Symbols Outlined;font-size:1.25rem;line-height:1;font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.kt-field__required{margin-inline-start:.125rem;color:var(--field-required-color, #8c1d18)}.kt-field__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-field__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-field__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-field__error-message{animation:none}}}\n"] }]
203
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], helpText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpText", required: false }] }], helpLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpLabel", required: false }] }], customDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "customDescribedBy", required: false }] }], helpClick: [{ type: i0.Output, args: ["helpClick"] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], fieldId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldId", required: false }] }], hideHintWhenInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideHintWhenInvalid", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], floatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatLabel", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtFieldControl), { isSignal: true }] }], templateTooltip: [{ type: i0.ViewChild, args: [i0.forwardRef(() => KtTooltip), { isSignal: true }] }], projectedTooltip: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtTooltip), { ...{ descendants: true }, isSignal: true }] }] } });
216
+ }, providers: [{ provide: KT_FIELD, useExisting: forwardRef(() => KtField) }], template: "<div class=\"kt-field__header\">\n @if (label(); as labelText) {\n <label class=\"kt-field__label\" [id]=\"labelId()\" [for]=\"baseId()\">\n {{ labelText }}\n @if (required()) {\n <span class=\"kt-field__required\" aria-hidden=\"true\">*</span>\n }\n </label>\n }\n\n @if (helpText(); as help) {\n <button\n type=\"button\"\n class=\"kt-field__help\"\n [ktTooltip]=\"help\"\n [attr.aria-label]=\"helpLabel()\"\n [attr.aria-describedby]=\"null\"\n (click)=\"onHelpClick($event)\"\n >\n <span class=\"kt-field__help-icon\" aria-hidden=\"true\">help</span>\n </button>\n }\n\n <ng-content select=\"[ktFieldHelp]\" />\n</div>\n\n<div class=\"kt-field__control\">\n <ng-content />\n</div>\n\n@if (hasHelp()) {\n <!-- Copie de l'aide TOUJOURS pr\u00E9sente (visually-hidden) : cible stable d'aria-describedby du\n contr\u00F4le, alors que la bulle visuelle du tooltip n'existe dans le DOM qu'\u00E0 l'affichage. -->\n <span [id]=\"helpId()\" class=\"kt-field__help-description\">\n @if (helpTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ helpString() }}\n }\n </span>\n}\n\n@if (showHint()) {\n <p class=\"kt-field__hint\" [id]=\"hintId()\">{{ hint() }}</p>\n}\n\n<!-- R\u00E9gion live toujours pr\u00E9sente : les AT annoncent l'erreur d\u00E8s son apparition. -->\n<p class=\"kt-field__error\" [id]=\"errorId()\" aria-live=\"polite\">\n @if (hasError()) {\n @for (error of displayedErrors(); track $index) {\n <span class=\"kt-field__error-message\">{{ error.message }}</span>\n }\n }\n</p>\n", styles: ["@layer kt-aaa.components{:host{display:flex;flex-direction:column;gap:var(--field-gap, .375rem);font-size:var(--field-font-size, 1rem);color:var(--field-color, inherit)}.kt-field__header{display:flex;align-items:center;gap:.375rem}.kt-field__label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);text-transform:var(--field-label-transform, none);letter-spacing:var(--field-label-letter-spacing, normal);color:var(--field-label-color, inherit)}.kt-field__help{display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;background:transparent;color:var(--field-icon-color, #474747);cursor:pointer;position:relative}.kt-field__help:after{content:\"\";position:absolute;inset:-12px}.kt-field__help-icon{font-family:Material Symbols Outlined;font-size:1.25rem;line-height:1;font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.kt-field__required{margin-inline-start:.125rem;color:var(--field-required-color, #8c1d18)}.kt-field__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-field__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-field__help-description{position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.kt-field__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-field__error-message{animation:none}}}\n"] }]
217
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], helpText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpText", required: false }] }], helpLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpLabel", required: false }] }], customDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "customDescribedBy", required: false }] }], helpClick: [{ type: i0.Output, args: ["helpClick"] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], fieldId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldId", required: false }] }], hideHintWhenInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideHintWhenInvalid", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], floatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatLabel", required: false }] }], control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtFieldControl), { isSignal: true }] }] } });
204
218
 
205
219
  /**
206
220
  * Lit un param typé d'une `ValidationError` (ex. `min`, `minLength`, `maxLength`, `minDate`…).
@@ -614,13 +628,6 @@ class KtTextArea extends KtBaseInputField {
614
628
  /** Plafond de caractères ; poussé par le form (validateur maxLength) ou par le consommateur. @default undefined */
615
629
  maxLength = input(/* @ts-ignore */
616
630
  ...(ngDevMode ? [undefined, { debugName: "maxLength" }] : /* istanbul ignore next */ []));
617
- /** Motifs de validation (contrat Signal Forms). L'attribut natif `pattern` ne prend qu'une regex →
618
- seule la première est posée. @default [] */
619
- pattern = input([], /* @ts-ignore */
620
- ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
621
- /** Source de la première regex pour l'attribut `pattern` natif (`null` si aucune). */
622
- patternAttr = computed(() => this.pattern()[0]?.source ?? null, /* @ts-ignore */
623
- ...(ngDevMode ? [{ debugName: "patternAttr" }] : /* istanbul ignore next */ []));
624
631
  parse(raw) {
625
632
  return raw;
626
633
  }
@@ -631,12 +638,12 @@ class KtTextArea extends KtBaseInputField {
631
638
  return value.length === 0;
632
639
  }
633
640
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTextArea, deps: null, target: i0.ɵɵFactoryTarget.Component });
634
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtTextArea, isStandalone: true, selector: "kt-text-area", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", 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 } }, outputs: { value: "valueChange" }, usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n data-multiline\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <textarea\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [value]=\"value()\"\n [rows]=\"rows()\"\n [style.min-height.lh]=\"rows()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"patternAttr()\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n ></textarea>\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors", "appearance", "floatLabel"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
641
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtTextArea, isStandalone: true, selector: "kt-text-area", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", 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 } }, outputs: { value: "valueChange" }, usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n data-multiline\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <textarea\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [value]=\"value()\"\n [rows]=\"rows()\"\n [style.min-height.lh]=\"rows()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n ></textarea>\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors", "appearance", "floatLabel"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
635
642
  }
636
643
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTextArea, decorators: [{
637
644
  type: Component,
638
- args: [{ selector: 'kt-text-area', imports: [KtField, KtFieldControl, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n data-multiline\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <textarea\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [value]=\"value()\"\n [rows]=\"rows()\"\n [style.min-height.lh]=\"rows()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"patternAttr()\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n ></textarea>\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n" }]
639
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", 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 }] }] } });
645
+ args: [{ selector: 'kt-text-area', imports: [KtField, KtFieldControl, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n data-multiline\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <textarea\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [value]=\"value()\"\n [rows]=\"rows()\"\n [style.min-height.lh]=\"rows()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n ></textarea>\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n" }]
646
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], minLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minLength", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }] } });
640
647
 
641
648
  /**
642
649
  * Champ texte (valeur `string`) intégré aux Signal Forms via `FormValueControl`. Hérite de la
@@ -696,6 +703,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
696
703
  args: [{ selector: 'kt-text-field', imports: [KtField, KtFieldControl, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <input\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [type]=\"type()\"\n [value]=\"value()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"patternAttr()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n />\n\n @if (hasSuggestions()) {\n <datalist [id]=\"datalistId\">\n @for (option of datalistOptions(); track $index) {\n <option [value]=\"option.value\" [attr.label]=\"option.label\"></option>\n }\n </datalist>\n }\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n" }]
697
704
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", 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 }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }] } });
698
705
 
706
+ /**
707
+ * Champ mot de passe (valeur `string`) intégré aux Signal Forms via `FormValueControl`.
708
+ * Inclut un bouton pour masquer/afficher la saisie en clair de manière accessible.
709
+ *
710
+ * @example
711
+ * ```html
712
+ * <kt-password-field label="Mot de passe" [(value)]="password" required />
713
+ * ```
714
+ */
715
+ class KtPasswordField extends KtBaseInputField {
716
+ /** Valeur saisie (two-way). @default '' */
717
+ value = model('', /* @ts-ignore */
718
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
719
+ /** Plancher de caractères (attribut `minlength` natif ; poussé par le validateur minLength). @default undefined */
720
+ minLength = input(/* @ts-ignore */
721
+ ...(ngDevMode ? [undefined, { debugName: "minLength" }] : /* istanbul ignore next */ []));
722
+ /** Plafond de caractères (attribut `maxlength` natif ; poussé par le validateur maxLength). @default undefined */
723
+ maxLength = input(/* @ts-ignore */
724
+ ...(ngDevMode ? [undefined, { debugName: "maxLength" }] : /* istanbul ignore next */ []));
725
+ /** Motifs de validation. @default [] */
726
+ pattern = input([], /* @ts-ignore */
727
+ ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
728
+ /** Source de la première regex pour l'attribut `pattern` natif (`null` si aucune). */
729
+ patternAttr = computed(() => this.pattern()[0]?.source ?? null, /* @ts-ignore */
730
+ ...(ngDevMode ? [{ debugName: "patternAttr" }] : /* istanbul ignore next */ []));
731
+ /** Indice d'autoremplissage effectif (par défaut 'current-password'). */
732
+ resolvedAutocomplete = computed(() => this.autocomplete() ?? 'current-password', /* @ts-ignore */
733
+ ...(ngDevMode ? [{ debugName: "resolvedAutocomplete" }] : /* istanbul ignore next */ []));
734
+ /** État de visibilité du mot de passe en clair. @default false */
735
+ isPasswordVisible = signal(false, /* @ts-ignore */
736
+ ...(ngDevMode ? [{ debugName: "isPasswordVisible" }] : /* istanbul ignore next */ []));
737
+ /** Type natif de l'input selon l'état de visibilité. */
738
+ inputType = computed(() => (this.isPasswordVisible() ? 'text' : 'password'), /* @ts-ignore */
739
+ ...(ngDevMode ? [{ debugName: "inputType" }] : /* istanbul ignore next */ []));
740
+ idGen = inject(KtIdGenerator);
741
+ uid = this.idGen.generateId('password-field');
742
+ inputId = computed(() => this.id() ?? `kt-password-field-${this.uid}`, /* @ts-ignore */
743
+ ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
744
+ parse(raw) {
745
+ return raw;
746
+ }
747
+ emptyValue() {
748
+ return '';
749
+ }
750
+ isEmpty(value) {
751
+ return value.length === 0;
752
+ }
753
+ /**
754
+ * Bascule l'état de visibilité du mot de passe.
755
+ * Utilise preventDefault() pour empêcher l'input de perdre le focus.
756
+ */
757
+ toggleVisibility(event) {
758
+ event.preventDefault();
759
+ if (this.disabled() || this.readonly()) {
760
+ return;
761
+ }
762
+ this.isPasswordVisible.update((visible) => !visible);
763
+ }
764
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtPasswordField, deps: null, target: i0.ɵɵFactoryTarget.Component });
765
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtPasswordField, isStandalone: true, selector: "kt-password-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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 } }, outputs: { value: "valueChange" }, usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"inputId()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <input\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [type]=\"inputType()\"\n [value]=\"value()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"resolvedAutocomplete()\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"patternAttr()\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n />\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"kt-field-box__toggle\"\n [disabled]=\"disabled() || readonly()\"\n [attr.aria-label]=\"isPasswordVisible() ? 'Masquer le mot de passe' : 'Afficher le mot de passe'\"\n [attr.aria-controls]=\"inputId()\"\n (mousedown)=\"toggleVisibility($event)\"\n >\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">\n {{ isPasswordVisible() ? 'visibility_off' : 'visibility' }}\n </span>\n </button>\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors", "appearance", "floatLabel"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
766
+ }
767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtPasswordField, decorators: [{
768
+ type: Component,
769
+ args: [{ selector: 'kt-password-field', imports: [KtField, KtFieldControl, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"displayErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"inputId()\"\n [appearance]=\"appearance()\"\n [floatLabel]=\"floatLabel()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div\n class=\"kt-field-box\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n >\n @if (icon(); as icon) {\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">{{ icon }}</span>\n }\n\n @if (prefix(); as prefix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(prefix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(prefix) }}\n }\n </span>\n }\n\n <input\n #input\n ktFieldControl\n class=\"kt-field-box__input\"\n [type]=\"inputType()\"\n [value]=\"value()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholderAttr()\"\n [attr.autocomplete]=\"resolvedAutocomplete()\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.pattern]=\"patternAttr()\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n (blur)=\"touched.set(true)\"\n />\n\n @if (showClear()) {\n <button type=\"button\" class=\"kt-field-box__clear\" [attr.aria-label]=\"clearLabel()\" (click)=\"clear()\">\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"kt-field-box__toggle\"\n [disabled]=\"disabled() || readonly()\"\n [attr.aria-label]=\"isPasswordVisible() ? 'Masquer le mot de passe' : 'Afficher le mot de passe'\"\n [attr.aria-controls]=\"inputId()\"\n (mousedown)=\"toggleVisibility($event)\"\n >\n <span class=\"kt-field-box__icon\" aria-hidden=\"true\">\n {{ isPasswordVisible() ? 'visibility_off' : 'visibility' }}\n </span>\n </button>\n\n @if (suffix(); as suffix) {\n <span class=\"kt-field-box__affix\">\n @if (asTemplate(suffix); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" />\n } @else {\n {{ asText(suffix) }}\n }\n </span>\n }\n </div>\n</kt-field>\n" }]
770
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], 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 }] }] } });
771
+
699
772
  /**
700
773
  * Bouton bascule (Switch / Slide-Toggle) accessible conforme aux normes WAI-ARIA.
701
774
  * Permet d'activer ou désactiver une option avec effet immédiat.
@@ -1203,6 +1276,8 @@ class KtCheckbox {
1203
1276
  inputEl = viewChild.required('input');
1204
1277
  el = inject(ElementRef);
1205
1278
  errorResolver = inject(KtFieldErrorResolver);
1279
+ auditEnabled = inject(KT_AUDIT_ENABLED);
1280
+ isDestroyed = false;
1206
1281
  idGen = inject(KtIdGenerator);
1207
1282
  uid = this.idGen.generateId('checkbox');
1208
1283
  baseId = computed(() => this.id() ?? `kt-checkbox-${this.uid}`, /* @ts-ignore */
@@ -1239,14 +1314,19 @@ class KtCheckbox {
1239
1314
  }, /* @ts-ignore */
1240
1315
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1241
1316
  constructor() {
1317
+ inject(DestroyRef).onDestroy(() => {
1318
+ this.isDestroyed = true;
1319
+ });
1242
1320
  // `indeterminate` est une PROPRIÉTÉ (non un attribut) : poussée impérativement sur l'input natif.
1243
1321
  effect(() => {
1244
1322
  this.inputEl().nativeElement.indeterminate = this.indeterminate();
1245
1323
  });
1246
- // Garde-fou a11y (dev, navigateur uniquement) : nom accessible = `label`, `ariaLabel` OU contenu
1324
+ // Garde-fou a11y (navigateur uniquement) : nom accessible = `label`, `ariaLabel` OU contenu
1247
1325
  // projeté. Sans aucun des trois, la case est annoncée vide (WCAG 4.1.2).
1248
1326
  afterNextRender(() => {
1249
- if (!isDevMode() || this.label() || this.ariaLabel())
1327
+ if (this.isDestroyed)
1328
+ return;
1329
+ if (!this.auditEnabled || this.label() || this.ariaLabel())
1250
1330
  return;
1251
1331
  const labelText = this.el.nativeElement
1252
1332
  .querySelector('.kt-checkbox__label')
@@ -1775,11 +1855,23 @@ class KtBaseSelect {
1775
1855
  errorResolver = inject(KtFieldErrorResolver);
1776
1856
  config = inject(KT_SELECT_CONFIG, { optional: true });
1777
1857
  host = inject(ElementRef);
1778
- ngZone = inject(NgZone);
1779
1858
  doc = inject(DOCUMENT);
1780
1859
  platformId = inject(PLATFORM_ID);
1781
1860
  destroyRef = inject(DestroyRef);
1861
+ bodyScrollLock = inject(KtBodyScrollLock);
1782
1862
  wasExpanded = false;
1863
+ scrollLocked = false;
1864
+ /** Pose/relâche le verrou de scroll du fond de façon idempotente (n'agit qu'à la transition),
1865
+ via le compteur partagé `KtBodyScrollLock` — deux sheets ouvertes ne se désynchronisent pas. */
1866
+ setBodyScrollLock(lock) {
1867
+ if (lock === this.scrollLocked)
1868
+ return;
1869
+ this.scrollLocked = lock;
1870
+ if (lock)
1871
+ this.bodyScrollLock.lock();
1872
+ else
1873
+ this.bodyScrollLock.unlock();
1874
+ }
1783
1875
  // --- Données / dérivation (clé OU fonction, défauts intelligents) ---
1784
1876
  /** Liste des options affichées (source de vérité du contrôle). */
1785
1877
  options = input.required(/* @ts-ignore */
@@ -2033,9 +2125,7 @@ class KtBaseSelect {
2033
2125
  this.destroyRef.onDestroy(() => {
2034
2126
  clearTimeout(this.announceTimer);
2035
2127
  this.sheetDrag.destroy();
2036
- if (isPlatformBrowser(this.platformId) && this.compact() && this.expanded()) {
2037
- this.doc.body.style.overflow = '';
2038
- }
2128
+ this.setBodyScrollLock(false);
2039
2129
  });
2040
2130
  // Préserve le contenu du template sur mobile pour permettre l'animation de sortie
2041
2131
  effect(() => {
@@ -2044,42 +2134,36 @@ class KtBaseSelect {
2044
2134
  cb.preserveContent.set(this.compact());
2045
2135
  }
2046
2136
  });
2047
- // Sheet mobile : verrouille le scroll du fond et restitue le focus au trigger à la fermeture.
2137
+ // Sheet mobile : verrouille le scroll du fond (compteur partagé) et restitue le focus au
2138
+ // trigger à la fermeture.
2048
2139
  effect(() => {
2049
- if (!this.compact())
2050
- return;
2051
2140
  if (!isPlatformBrowser(this.platformId))
2052
2141
  return;
2053
- if (this.expanded()) {
2054
- this.doc.body.style.overflow = 'hidden';
2142
+ const compact = this.compact();
2143
+ const expanded = this.expanded();
2144
+ this.setBodyScrollLock(compact && expanded);
2145
+ if (compact && expanded) {
2055
2146
  this.wasExpanded = true;
2056
2147
  }
2057
- else {
2058
- this.doc.body.style.overflow = '';
2059
- if (this.wasExpanded) {
2060
- this.triggerEl()?.nativeElement.focus();
2061
- }
2148
+ else if (compact && this.wasExpanded) {
2149
+ this.triggerEl()?.nativeElement.focus();
2062
2150
  }
2063
2151
  });
2064
- // Ferme au clic/tap en dehors du composant (remplace le binding d'hôte statique document:pointerdown)
2065
- // Enregistré une seule fois hors de la zone Angular pour éviter le change detection global sur chaque clic.
2152
+ // Ferme au clic/tap en dehors du composant (remplace le binding d'hôte statique document:pointerdown,
2153
+ // enregistré dynamiquement pour pouvoir comparer la cible au popup monté dans un overlay).
2066
2154
  if (isPlatformBrowser(this.platformId)) {
2067
- this.ngZone.runOutsideAngular(() => {
2068
- const onPointerDown = (event) => {
2069
- if (!this.expanded())
2070
- return;
2071
- const target = event.target;
2072
- const popup = this.popupEl()?.nativeElement;
2073
- if (target && (!this.host.nativeElement.contains(target) || target === popup)) {
2074
- this.ngZone.run(() => {
2075
- this.expanded.set(false);
2076
- });
2077
- }
2078
- };
2079
- this.doc.addEventListener('pointerdown', onPointerDown);
2080
- this.destroyRef.onDestroy(() => {
2081
- this.doc.removeEventListener('pointerdown', onPointerDown);
2082
- });
2155
+ const onPointerDown = (event) => {
2156
+ if (!this.expanded())
2157
+ return;
2158
+ const target = event.target;
2159
+ const popup = this.popupEl()?.nativeElement;
2160
+ if (target && (!this.host.nativeElement.contains(target) || target === popup)) {
2161
+ this.expanded.set(false);
2162
+ }
2163
+ };
2164
+ this.doc.addEventListener('pointerdown', onPointerDown);
2165
+ this.destroyRef.onDestroy(() => {
2166
+ this.doc.removeEventListener('pointerdown', onPointerDown);
2083
2167
  });
2084
2168
  }
2085
2169
  // Fait défiler la liste pour afficher l'option active lors de la navigation au clavier (mode activedescendant).
@@ -2092,9 +2176,9 @@ class KtBaseSelect {
2092
2176
  if (!listbox)
2093
2177
  return;
2094
2178
  const activeEl = listbox.querySelector('.kt-select__option--active');
2095
- if (activeEl) {
2096
- activeEl.scrollIntoView({ block: 'nearest' });
2097
- }
2179
+ // `?.` : scrollIntoView n'est pas implémenté par tous les environnements de test (jsdom) ;
2180
+ // sans ce garde, un consommateur testant le Select en jsdom subit un crash en rAF.
2181
+ activeEl?.scrollIntoView?.({ block: 'nearest' });
2098
2182
  });
2099
2183
  });
2100
2184
  if (isDevMode()) {
@@ -2159,14 +2243,10 @@ class KtBaseSelect {
2159
2243
  }
2160
2244
  /** Annonce le contenu du filtre ~500 ms après la dernière frappe (évite le spam SR). */
2161
2245
  scheduleResultsAnnouncement() {
2162
- this.ngZone.runOutsideAngular(() => {
2163
- clearTimeout(this.announceTimer);
2164
- this.announceTimer = setTimeout(() => {
2165
- this.ngZone.run(() => {
2166
- this.announcedCount.set(this.composeFilterAnnouncement());
2167
- });
2168
- }, ANNOUNCE_DELAY_MS);
2169
- });
2246
+ clearTimeout(this.announceTimer);
2247
+ this.announceTimer = setTimeout(() => {
2248
+ this.announcedCount.set(this.composeFilterAnnouncement());
2249
+ }, ANNOUNCE_DELAY_MS);
2170
2250
  }
2171
2251
  /** Annonce immédiate dans la live region du panneau (annule une annonce différée en attente). */
2172
2252
  announceNow(text) {
@@ -2595,7 +2675,6 @@ class KtChipList {
2595
2675
  doc = inject(DOCUMENT);
2596
2676
  platformId = inject(PLATFORM_ID);
2597
2677
  cdr = inject(ChangeDetectorRef);
2598
- ngZone = inject(NgZone);
2599
2678
  /** Vrai pendant la View Transition de CETTE liste : les chips (standards via Chip,
2600
2679
  custom via tokens.css) ne portent leur `view-transition-name` que dans cette fenêtre. */
2601
2680
  transitioning = signal(false, /* @ts-ignore */
@@ -2646,6 +2725,19 @@ class KtChipList {
2646
2725
  ...(ngDevMode ? [{ debugName: "itemDef" }] : /* istanbul ignore next */ []));
2647
2726
  chips = viewChildren(KtChip, /* @ts-ignore */
2648
2727
  ...(ngDevMode ? [{ debugName: "chips" }] : /* istanbul ignore next */ []));
2728
+ // Sélecteurs des focusables (source unique, dédupliquée) : boutons « retirer » + focusables de
2729
+ // templates custom, avec (séquence clavier complète / roving) ou sans (cible de focus au retrait)
2730
+ // le bouton de repli « +N more ».
2731
+ static CHIP_FOCUSABLES = '.kt-chip__remove, .kt-chip-list__item button, .kt-chip-list__item [tabindex]';
2732
+ static ROVING_FOCUSABLES = `${KtChipList.CHIP_FOCUSABLES}, .kt-chip-list__more`;
2733
+ query(selector) {
2734
+ return Array.from(this.host.nativeElement.querySelectorAll(selector));
2735
+ }
2736
+ /** Index de l'unique focusable portant `tabindex="0"` : point d'entrée clavier UNIQUE dans la
2737
+ liste (pattern composite WAI-ARIA). Les flèches/Home/End le déplacent ; un focus direct
2738
+ (souris) le resynchronise via `onFocusin`. */
2739
+ activeIndex = signal(0, /* @ts-ignore */
2740
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
2649
2741
  // --- Textes résolus (input > KT_CHIPS_CONFIG > défaut lib neutre EN) ---
2650
2742
  resolvedListLabel = computed(() => this.listLabel() ?? this.config?.listLabel ?? 'Selected items', /* @ts-ignore */
2651
2743
  ...(ngDevMode ? [{ debugName: "resolvedListLabel" }] : /* istanbul ignore next */ []));
@@ -2711,7 +2803,22 @@ class KtChipList {
2711
2803
  untracked(() => {
2712
2804
  this.expanded.set(false);
2713
2805
  });
2714
- }, { allowSignalWrites: true });
2806
+ });
2807
+ // Roving tabindex : un seul focusable porte tabindex=0 (les autres -1), recalculé après chaque
2808
+ // rendu (items/repli/index actif). Garantit un point d'entrée clavier UNIQUE (Tab) ; la
2809
+ // navigation interne reste aux flèches/Home/End.
2810
+ afterRenderEffect(() => {
2811
+ this.visibleItems();
2812
+ this.expanded();
2813
+ const active = this.activeIndex();
2814
+ const focusables = this.query(KtChipList.ROVING_FOCUSABLES);
2815
+ if (focusables.length === 0)
2816
+ return;
2817
+ const clamped = Math.min(Math.max(active, 0), focusables.length - 1);
2818
+ for (let i = 0; i < focusables.length; i++) {
2819
+ focusables[i].tabIndex = i === clamped ? 0 : -1;
2820
+ }
2821
+ });
2715
2822
  inject(DestroyRef).onDestroy(() => clearTimeout(this.statusTimer));
2716
2823
  }
2717
2824
  idGen = inject(KtIdGenerator);
@@ -2769,9 +2876,9 @@ class KtChipList {
2769
2876
  // (`appRef.tick()`) corrigerait le rendu mais ajouterait une passe synchrone sur toute l'appli à
2770
2877
  // chaque retrait. On préfère DIFFÉRER la capture de l'état « après » : le callback d'une View
2771
2878
  // Transition peut renvoyer une Promise, le navigateur attend sa résolution avant le snapshot.
2772
- // On déclenche la mutation dans la zone (la CD déjà planifiée, OnPush, ne vérifie que les vues
2773
- // « dirty » : pas de passe globale supplémentaire) et on résout au prochain rendu.
2774
- this.ngZone.run(action);
2879
+ // La mutation passe par un signal (le parent met à jour `items`) : en zoneless, c'est ce signal
2880
+ // qui planifie la CD (OnPush, seules les vues « dirty » sont vérifiées) ; on résout au prochain rendu.
2881
+ action();
2775
2882
  return new Promise((resolve) => {
2776
2883
  let settled = false;
2777
2884
  const done = () => {
@@ -2782,7 +2889,7 @@ class KtChipList {
2782
2889
  };
2783
2890
  afterNextRender({ read: done }, { injector: this.injector });
2784
2891
  // Garde-fou : si aucun rendu n'est planifié (retrait ignoré par le parent), ne pas figer la page.
2785
- this.ngZone.runOutsideAngular(() => setTimeout(done, 100));
2892
+ setTimeout(done, 100);
2786
2893
  });
2787
2894
  });
2788
2895
  this.activeTransition = vt;
@@ -2805,10 +2912,11 @@ class KtChipList {
2805
2912
  this.removed.emit({ item, index });
2806
2913
  afterNextRender({
2807
2914
  read: () => {
2808
- const hostEl = this.host.nativeElement;
2809
- const focusables = Array.from(hostEl.querySelectorAll('.kt-chip__remove, .kt-chip-list__item button, .kt-chip-list__item [tabindex="0"]'));
2810
- const target = focusables[Math.min(index, focusables.length - 1)];
2915
+ const focusables = this.query(KtChipList.CHIP_FOCUSABLES);
2916
+ const targetIndex = Math.min(index, focusables.length - 1);
2917
+ const target = focusables[targetIndex];
2811
2918
  if (target) {
2919
+ this.activeIndex.set(targetIndex);
2812
2920
  target.focus();
2813
2921
  return;
2814
2922
  }
@@ -2830,9 +2938,12 @@ class KtChipList {
2830
2938
  const firstRevealed = this.maxVisible() ?? 0;
2831
2939
  afterNextRender({
2832
2940
  read: () => {
2833
- const hostEl = this.host.nativeElement;
2834
- const focusables = Array.from(hostEl.querySelectorAll('.kt-chip__remove, .kt-chip-list__item button, .kt-chip-list__item [tabindex="0"]'));
2835
- focusables[firstRevealed]?.focus();
2941
+ const focusables = this.query(KtChipList.CHIP_FOCUSABLES);
2942
+ const target = focusables[firstRevealed];
2943
+ if (target) {
2944
+ this.activeIndex.set(firstRevealed);
2945
+ target.focus();
2946
+ }
2836
2947
  },
2837
2948
  }, { injector: this.injector });
2838
2949
  });
@@ -2842,12 +2953,10 @@ class KtChipList {
2842
2953
  if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key)) {
2843
2954
  return;
2844
2955
  }
2845
- const hostEl = this.host.nativeElement;
2846
- const focusables = Array.from(hostEl.querySelectorAll('.kt-chip__remove, .kt-chip-list__item button, .kt-chip-list__item [tabindex="0"], .kt-chip-list__more'));
2956
+ const focusables = this.query(KtChipList.ROVING_FOCUSABLES);
2847
2957
  if (focusables.length === 0)
2848
2958
  return;
2849
- const active = this.doc.activeElement;
2850
- const currentIndex = focusables.indexOf(active);
2959
+ const currentIndex = focusables.indexOf(this.doc.activeElement);
2851
2960
  let nextIndex = -1;
2852
2961
  if (key === 'ArrowRight' || key === 'ArrowDown') {
2853
2962
  nextIndex = currentIndex + 1;
@@ -2863,11 +2972,18 @@ class KtChipList {
2863
2972
  }
2864
2973
  if (nextIndex >= 0 && nextIndex < focusables.length) {
2865
2974
  event.preventDefault();
2975
+ this.activeIndex.set(nextIndex);
2866
2976
  focusables[nextIndex].focus();
2867
2977
  }
2868
2978
  }
2979
+ /** Resynchronise le point de tabulation unique sur l'élément réellement focusé (souris/voix). */
2980
+ onFocusin(event) {
2981
+ const index = this.query(KtChipList.ROVING_FOCUSABLES).indexOf(event.target);
2982
+ if (index >= 0)
2983
+ this.activeIndex.set(index);
2984
+ }
2869
2985
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtChipList, deps: [], target: i0.ɵɵFactoryTarget.Component });
2870
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtChipList, isStandalone: true, selector: "kt-chip-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemKey: { classPropertyName: "itemKey", publicName: "itemKey", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", 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 }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, listLabel: { classPropertyName: "listLabel", publicName: "listLabel", isSignal: true, isRequired: false, transformFunction: null }, removeItemLabel: { classPropertyName: "removeItemLabel", publicName: "removeItemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemRemovedText: { classPropertyName: "itemRemovedText", publicName: "itemRemovedText", isSignal: true, isRequired: false, transformFunction: null }, moreLabel: { classPropertyName: "moreLabel", publicName: "moreLabel", isSignal: true, isRequired: false, transformFunction: null }, lessLabel: { classPropertyName: "lessLabel", publicName: "lessLabel", isSignal: true, isRequired: false, transformFunction: null }, chipTemplate: { classPropertyName: "chipTemplate", publicName: "chipTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyFocusTarget: { classPropertyName: "emptyFocusTarget", publicName: "emptyFocusTarget", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.data-empty": "items().length === 0 ? '' : null", "attr.data-vt-active": "transitioning() ? '' : null" } }, providers: [{ provide: ChipTransitionScope, useExisting: KtChipList }], queries: [{ propertyName: "itemDef", first: true, predicate: KtChipItemDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "chips", predicate: KtChip, descendants: true, isSignal: true }], ngImport: i0, template: "@if (items().length > 0) {\n <div class=\"kt-chip-list\">\n <div class=\"kt-chip-list__items\" role=\"list\" [attr.aria-label]=\"resolvedListLabel()\">\n @for (item of visibleItems(); track keyOf(item); let i = $index) {\n @if (effectiveTemplate(); as tpl) {\n <div role=\"listitem\" class=\"kt-chip-list__item\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item, remove: removeCallback(item, i) }\"\n />\n </div>\n } @else {\n <kt-chip\n role=\"listitem\"\n [removable]=\"showRemove()\"\n [disabled]=\"disabled()\"\n [removeLabel]=\"removeLabelFor(item)\"\n (remove)=\"removeAt(item, i)\"\n >{{ labelOf(item) }}</kt-chip\n >\n }\n }\n </div>\n <!-- Le bouton de repli/d\u00E9pli n'est PAS un chip : hors du role=\"list\" pour ne pas \u00EAtre\n annonc\u00E9 comme un item de la liste (reste dans la m\u00EAme rang\u00E9e flex). -->\n @if (overflow()) {\n <button\n type=\"button\"\n class=\"kt-chip-list__more\"\n [attr.aria-expanded]=\"expanded()\"\n [style.view-transition-name]=\"transitioning() ? moreBtnTransitionName : null\"\n [style.view-transition-class]=\"'chip-transition'\"\n (click)=\"toggleExpanded()\"\n >\n {{ expanded() ? resolvedLessLabel() : resolvedMoreLabel()(hiddenCount()) }}\n </button>\n }\n </div>\n}\n<!-- Annonce des retraits : TOUJOURS rendue (hors du @if, sinon elle dispara\u00EEt avec le dernier chip). -->\n<div class=\"kt-chip-list__status\" role=\"status\" aria-live=\"polite\">{{ status() }}</div>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-chip-list{display:flex;flex-wrap:wrap;gap:.5rem}.kt-chip-list__items,.kt-chip-list__item{display:contents}.kt-chip-list__more{display:inline-flex;align-items:center;gap:var(--chip-gap);padding:var(--chip-padding-y) var(--chip-padding-x);border-radius:var(--chip-radius);background:var(--chip-bg);border:1px solid var(--chip-border);box-shadow:var(--chip-shadow, none);font:inherit;font-size:var(--chip-font-size);color:var(--chip-color);cursor:pointer;min-block-size:24px}.kt-chip-list__more:hover{background:var(--chip-bg-hover);box-shadow:var(--chip-shadow-hover, var(--chip-shadow, none))}.kt-chip-list__more:focus-visible{outline:2px solid var(--chip-focus-ring);outline-offset:1px}.kt-chip-list__status{position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(pointer:coarse){.kt-chip-list__more{min-block-size:32px}}}\n"], dependencies: [{ kind: "component", type: KtChip, selector: "kt-chip", inputs: ["removable", "disabled", "removeLabel"], outputs: ["remove"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2986
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtChipList, isStandalone: true, selector: "kt-chip-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemKey: { classPropertyName: "itemKey", publicName: "itemKey", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", 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 }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, listLabel: { classPropertyName: "listLabel", publicName: "listLabel", isSignal: true, isRequired: false, transformFunction: null }, removeItemLabel: { classPropertyName: "removeItemLabel", publicName: "removeItemLabel", isSignal: true, isRequired: false, transformFunction: null }, itemRemovedText: { classPropertyName: "itemRemovedText", publicName: "itemRemovedText", isSignal: true, isRequired: false, transformFunction: null }, moreLabel: { classPropertyName: "moreLabel", publicName: "moreLabel", isSignal: true, isRequired: false, transformFunction: null }, lessLabel: { classPropertyName: "lessLabel", publicName: "lessLabel", isSignal: true, isRequired: false, transformFunction: null }, chipTemplate: { classPropertyName: "chipTemplate", publicName: "chipTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyFocusTarget: { classPropertyName: "emptyFocusTarget", publicName: "emptyFocusTarget", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { listeners: { "keydown": "onKeydown($event)", "focusin": "onFocusin($event)" }, properties: { "attr.data-empty": "items().length === 0 ? '' : null", "attr.data-vt-active": "transitioning() ? '' : null" } }, providers: [{ provide: ChipTransitionScope, useExisting: KtChipList }], queries: [{ propertyName: "itemDef", first: true, predicate: KtChipItemDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "chips", predicate: KtChip, descendants: true, isSignal: true }], ngImport: i0, template: "@if (items().length > 0) {\n <div class=\"kt-chip-list\">\n <div class=\"kt-chip-list__items\" role=\"list\" [attr.aria-label]=\"resolvedListLabel()\">\n @for (item of visibleItems(); track keyOf(item); let i = $index) {\n @if (effectiveTemplate(); as tpl) {\n <div role=\"listitem\" class=\"kt-chip-list__item\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item, remove: removeCallback(item, i) }\"\n />\n </div>\n } @else {\n <kt-chip\n role=\"listitem\"\n [removable]=\"showRemove()\"\n [disabled]=\"disabled()\"\n [removeLabel]=\"removeLabelFor(item)\"\n (remove)=\"removeAt(item, i)\"\n >{{ labelOf(item) }}</kt-chip\n >\n }\n }\n </div>\n <!-- Le bouton de repli/d\u00E9pli n'est PAS un chip : hors du role=\"list\" pour ne pas \u00EAtre\n annonc\u00E9 comme un item de la liste (reste dans la m\u00EAme rang\u00E9e flex). -->\n @if (overflow()) {\n <button\n type=\"button\"\n class=\"kt-chip-list__more\"\n [attr.aria-expanded]=\"expanded()\"\n [style.view-transition-name]=\"transitioning() ? moreBtnTransitionName : null\"\n [style.view-transition-class]=\"'chip-transition'\"\n (click)=\"toggleExpanded()\"\n >\n {{ expanded() ? resolvedLessLabel() : resolvedMoreLabel()(hiddenCount()) }}\n </button>\n }\n </div>\n}\n<!-- Annonce des retraits : TOUJOURS rendue (hors du @if, sinon elle dispara\u00EEt avec le dernier chip). -->\n<div class=\"kt-chip-list__status\" role=\"status\" aria-live=\"polite\">{{ status() }}</div>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-chip-list{display:flex;flex-wrap:wrap;gap:.5rem}.kt-chip-list__items,.kt-chip-list__item{display:contents}.kt-chip-list__more{display:inline-flex;align-items:center;gap:var(--chip-gap);padding:var(--chip-padding-y) var(--chip-padding-x);border-radius:var(--chip-radius);background:var(--chip-bg);border:1px solid var(--chip-border);box-shadow:var(--chip-shadow, none);font:inherit;font-size:var(--chip-font-size);color:var(--chip-color);cursor:pointer;min-block-size:24px}.kt-chip-list__more:hover{background:var(--chip-bg-hover);box-shadow:var(--chip-shadow-hover, var(--chip-shadow, none))}.kt-chip-list__more:focus-visible{outline:2px solid var(--chip-focus-ring);outline-offset:1px}.kt-chip-list__status{position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(pointer:coarse){.kt-chip-list__more{min-block-size:32px}}}\n"], dependencies: [{ kind: "component", type: KtChip, selector: "kt-chip", inputs: ["removable", "disabled", "removeLabel"], outputs: ["remove"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2871
2987
  }
2872
2988
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtChipList, decorators: [{
2873
2989
  type: Component,
@@ -2877,6 +2993,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2877
2993
  // nomment les chips à template custom QUE sous ce marqueur (cf. ChipTransitionScope).
2878
2994
  '[attr.data-vt-active]': "transitioning() ? '' : null",
2879
2995
  '(keydown)': 'onKeydown($event)',
2996
+ '(focusin)': 'onFocusin($event)',
2880
2997
  }, template: "@if (items().length > 0) {\n <div class=\"kt-chip-list\">\n <div class=\"kt-chip-list__items\" role=\"list\" [attr.aria-label]=\"resolvedListLabel()\">\n @for (item of visibleItems(); track keyOf(item); let i = $index) {\n @if (effectiveTemplate(); as tpl) {\n <div role=\"listitem\" class=\"kt-chip-list__item\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item, remove: removeCallback(item, i) }\"\n />\n </div>\n } @else {\n <kt-chip\n role=\"listitem\"\n [removable]=\"showRemove()\"\n [disabled]=\"disabled()\"\n [removeLabel]=\"removeLabelFor(item)\"\n (remove)=\"removeAt(item, i)\"\n >{{ labelOf(item) }}</kt-chip\n >\n }\n }\n </div>\n <!-- Le bouton de repli/d\u00E9pli n'est PAS un chip : hors du role=\"list\" pour ne pas \u00EAtre\n annonc\u00E9 comme un item de la liste (reste dans la m\u00EAme rang\u00E9e flex). -->\n @if (overflow()) {\n <button\n type=\"button\"\n class=\"kt-chip-list__more\"\n [attr.aria-expanded]=\"expanded()\"\n [style.view-transition-name]=\"transitioning() ? moreBtnTransitionName : null\"\n [style.view-transition-class]=\"'chip-transition'\"\n (click)=\"toggleExpanded()\"\n >\n {{ expanded() ? resolvedLessLabel() : resolvedMoreLabel()(hiddenCount()) }}\n </button>\n }\n </div>\n}\n<!-- Annonce des retraits : TOUJOURS rendue (hors du @if, sinon elle dispara\u00EEt avec le dernier chip). -->\n<div class=\"kt-chip-list__status\" role=\"status\" aria-live=\"polite\">{{ status() }}</div>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-chip-list{display:flex;flex-wrap:wrap;gap:.5rem}.kt-chip-list__items,.kt-chip-list__item{display:contents}.kt-chip-list__more{display:inline-flex;align-items:center;gap:var(--chip-gap);padding:var(--chip-padding-y) var(--chip-padding-x);border-radius:var(--chip-radius);background:var(--chip-bg);border:1px solid var(--chip-border);box-shadow:var(--chip-shadow, none);font:inherit;font-size:var(--chip-font-size);color:var(--chip-color);cursor:pointer;min-block-size:24px}.kt-chip-list__more:hover{background:var(--chip-bg-hover);box-shadow:var(--chip-shadow-hover, var(--chip-shadow, none))}.kt-chip-list__more:focus-visible{outline:2px solid var(--chip-focus-ring);outline-offset:1px}.kt-chip-list__status{position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(pointer:coarse){.kt-chip-list__more{min-block-size:32px}}}\n"] }]
2881
2998
  }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], itemKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemKey", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }], listLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "listLabel", required: false }] }], removeItemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeItemLabel", required: false }] }], itemRemovedText: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemRemovedText", required: false }] }], moreLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "moreLabel", required: false }] }], lessLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "lessLabel", required: false }] }], chipTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipTemplate", required: false }] }], emptyFocusTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyFocusTarget", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }], itemDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtChipItemDef), { isSignal: true }] }], chips: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => KtChip), { isSignal: true }] }] } });
2882
2999
 
@@ -3665,5 +3782,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
3665
3782
  * Generated bundle index. Do not edit.
3666
3783
  */
3667
3784
 
3668
- export { DEFAULT_KT_SELECT_CONFIG, KT_CHIPS_CONFIG, KT_DEFAULT_FIELD_ERROR_MESSAGES, KT_FIELD, KT_FIELD_CONFIG, KT_SELECT_CONFIG, KtBaseInputField, KtBaseSelect, KtBaseTemporalField, KtBaseTimeTemporalField, KtCheckbox, KtCheckboxGroup, KtChip, KtChipItemDef, KtChipList, KtClock, KtDateField, KtDateTimeField, KtField, KtFieldControl, KtFieldErrorResolver, KtFixedClock, KtInstantField, KtMultiSelect, KtMultiSelectChipDef, KtMultiSelectOptionDef, KtMultiSelectTriggerDef, KtNumberField, KtRadio, KtRadioGroup, KtSelect, KtSelectConfig, KtSelectOptionDef, KtSelectTriggerDef, KtSwitch, KtTemporalDatePipe, KtTextArea, KtTextField, KtTimeField, KtYearMonthField, Temporal, defaultKtFieldErrorMatcher, ktErrorParam, normalizeKtSuggestions, provideKtField };
3785
+ export { DEFAULT_KT_SELECT_CONFIG, KT_CHIPS_CONFIG, KT_DEFAULT_FIELD_ERROR_MESSAGES, KT_FIELD, KT_FIELD_CONFIG, KT_SELECT_CONFIG, KtBaseInputField, KtBaseSelect, KtBaseTemporalField, KtBaseTimeTemporalField, KtCheckbox, KtCheckboxGroup, KtChip, KtChipItemDef, KtChipList, KtClock, KtDateField, KtDateTimeField, KtField, KtFieldControl, KtFieldErrorResolver, KtFixedClock, KtInstantField, KtMultiSelect, KtMultiSelectChipDef, KtMultiSelectOptionDef, KtMultiSelectTriggerDef, KtNumberField, KtPasswordField, KtRadio, KtRadioGroup, KtSelect, KtSelectConfig, KtSelectOptionDef, KtSelectTriggerDef, KtSwitch, KtTemporalDatePipe, KtTextArea, KtTextField, KtTimeField, KtYearMonthField, Temporal, defaultKtFieldErrorMatcher, ktErrorParam, normalizeKtSuggestions, provideKtField };
3669
3786
  //# sourceMappingURL=ktortu-aaa-forms.mjs.map