@ktortu/aaa 0.1.0-beta.0 → 0.9.1

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 (49) hide show
  1. package/README.md +27 -9
  2. package/cdk/styles/tabs.css +100 -21
  3. package/fesm2022/ktortu-aaa-button.mjs +18 -11
  4. package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
  5. package/fesm2022/ktortu-aaa-card.mjs +29 -4
  6. package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
  7. package/fesm2022/ktortu-aaa-cdk.mjs +59 -15
  8. package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
  9. package/fesm2022/ktortu-aaa-dialog.mjs +134 -24
  10. package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
  11. package/fesm2022/ktortu-aaa-forms.mjs +748 -294
  12. package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
  13. package/fesm2022/ktortu-aaa-i18n.mjs +139 -0
  14. package/fesm2022/ktortu-aaa-i18n.mjs.map +1 -0
  15. package/fesm2022/ktortu-aaa-menu.mjs +38 -13
  16. package/fesm2022/ktortu-aaa-menu.mjs.map +1 -1
  17. package/fesm2022/ktortu-aaa-snackbar.mjs +465 -0
  18. package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -0
  19. package/fesm2022/ktortu-aaa-tabs.mjs +319 -42
  20. package/fesm2022/ktortu-aaa-tabs.mjs.map +1 -1
  21. package/fesm2022/ktortu-aaa-tooltip.mjs +3 -2
  22. package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -1
  23. package/fesm2022/ktortu-aaa.mjs +2 -0
  24. package/fesm2022/ktortu-aaa.mjs.map +1 -1
  25. package/forms/checkbox/checkbox-group.css +0 -8
  26. package/forms/chips/chip-list.css +5 -0
  27. package/forms/radio/radio-group.css +0 -8
  28. package/forms/styles/field-box.css +152 -2
  29. package/forms/styles/field-pending.css +46 -0
  30. package/forms/styles/select-panel.css +4 -0
  31. package/forms/styles/tokens.css +3 -0
  32. package/menu/menu.css +8 -4
  33. package/package.json +9 -1
  34. package/snackbar/snackbar-tokens.css +53 -0
  35. package/snackbar/snackbar.css +175 -0
  36. package/styles/forms.css +1 -0
  37. package/styles/snackbar.css +9 -0
  38. package/styles/styles.css +1 -0
  39. package/types/ktortu-aaa-button.d.ts +22 -8
  40. package/types/ktortu-aaa-card.d.ts +24 -4
  41. package/types/ktortu-aaa-cdk.d.ts +38 -0
  42. package/types/ktortu-aaa-dialog.d.ts +129 -31
  43. package/types/ktortu-aaa-forms.d.ts +503 -160
  44. package/types/ktortu-aaa-i18n.d.ts +77 -0
  45. package/types/ktortu-aaa-menu.d.ts +15 -8
  46. package/types/ktortu-aaa-snackbar.d.ts +275 -0
  47. package/types/ktortu-aaa-tabs.d.ts +130 -13
  48. package/types/ktortu-aaa-tooltip.d.ts +5 -0
  49. package/types/ktortu-aaa.d.ts +2 -0
@@ -1,19 +1,58 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, computed, ElementRef, Directive, input, output, contentChild, viewChild, forwardRef, ChangeDetectionStrategy, Component, model, booleanAttribute, TemplateRef, effect, NgZone, PLATFORM_ID, DestroyRef, signal, untracked, isDevMode, Injector, ChangeDetectorRef, viewChildren, afterNextRender, Injectable, LOCALE_ID, Pipe } 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
3
  import { KtTooltip } from '@ktortu/aaa/tooltip';
4
4
  import { KtIdGenerator, KtViewport, createKtSheetDrag } from '@ktortu/aaa/cdk';
5
5
  import { NgTemplateOutlet, DOCUMENT, isPlatformBrowser } from '@angular/common';
6
6
  import { transformedValue } from '@angular/forms/signals';
7
- import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox';
8
7
  import { Listbox, Option } from '@angular/aria/listbox';
8
+ import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox';
9
9
 
10
10
  /** Défaut : afficher l'erreur une fois le champ quitté (blur). */
11
11
  const defaultKtFieldErrorMatcher = (state) => state.invalid && state.touched;
12
12
  const KT_FIELD_CONFIG = new InjectionToken('KT_FIELD_CONFIG');
13
+ /**
14
+ * Fournit des défauts de champ (apparence, libellés, matcher d'erreurs, messages d'erreur par
15
+ * défaut…) pour un sous-arbre ou l'application entière. Idéal pour une transition globale — ex. tout
16
+ * passer en `outline` façon Material en un seul provider.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // app.config.ts — apparence globale
21
+ * providers: [provideKtField({ appearance: 'outline' })]
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // Surcharger des messages d'erreur par défaut (fusionnés par-dessus les défauts anglais).
27
+ * // Une fabrique reçoit l'erreur complète : ses params typés sont lisibles via `ktErrorParam`.
28
+ * providers: [
29
+ * provideKtField({
30
+ * errorMessages: {
31
+ * required: 'Champ obligatoire.',
32
+ * minLength: (e) => `Au moins ${ktErrorParam<number>(e, 'minLength')} caractères.`,
33
+ * pattern: '', // '' supprime le texte de ce kind : champ rouge, sans message
34
+ * },
35
+ * }),
36
+ * ]
37
+ * ```
38
+ *
39
+ * @remarks Pour passer toute la lib en français d'un coup (libellés + messages d'erreur), préférez
40
+ * `provideKtDefaultFR()` (paquet `@ktortu/aaa/i18n`), qui fournit déjà un dictionnaire complet.
41
+ */
42
+ function provideKtField(config) {
43
+ return { provide: KT_FIELD_CONFIG, useValue: config };
44
+ }
13
45
  const KT_FIELD = new InjectionToken('KT_FIELD');
14
46
 
15
47
  /** Marque le contrôle (natif ou custom) d'un champ pour que `Field` y câble l'a11y
16
- (id, aria-describedby, aria-invalid, aria-required). */
48
+ (id, aria-describedby, aria-invalid, aria-required).
49
+
50
+ @example
51
+ ```html
52
+ <kt-field label="E-mail" [invalid]="invalid()" [errors]="errors()">
53
+ <input ktFieldControl type="email" [(ngModel)]="email" />
54
+ </kt-field>
55
+ ``` */
17
56
  class KtFieldControl {
18
57
  parent = inject(KT_FIELD, { optional: true });
19
58
  id = computed(() => this.parent?.baseId() ?? null, /* @ts-ignore */
@@ -24,16 +63,19 @@ class KtFieldControl {
24
63
  ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
25
64
  required = computed(() => this.parent?.required() ?? false, /* @ts-ignore */
26
65
  ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
66
+ /** Élément DOM hôte du contrôle (exposé pour le focus / la mesure par le parent). */
27
67
  element = inject(ElementRef).nativeElement;
28
68
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtFieldControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
29
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: KtFieldControl, isStandalone: true, selector: "[ktFieldControl]", host: { properties: { "id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "invalid() ? \"true\" : null", "attr.aria-required": "required() ? \"true\" : null" } }, ngImport: i0 });
69
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: KtFieldControl, isStandalone: true, selector: "[ktFieldControl]", host: { properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "invalid() ? \"true\" : null", "attr.aria-required": "required() ? \"true\" : null" } }, ngImport: i0 });
30
70
  }
31
71
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtFieldControl, decorators: [{
32
72
  type: Directive,
33
73
  args: [{
34
74
  selector: '[ktFieldControl]',
35
75
  host: {
36
- '[id]': 'id()',
76
+ // `[attr.id]` (et non `[id]`) : quand le contrôle est orphelin (pas de parent KtField), `id()`
77
+ // vaut null et l'attribut doit DISPARAÎTRE — un binding de propriété poserait `id="null"`.
78
+ '[attr.id]': 'id()',
37
79
  '[attr.aria-describedby]': 'describedBy()',
38
80
  '[attr.aria-invalid]': 'invalid() ? "true" : null',
39
81
  '[attr.aria-required]': 'required() ? "true" : null',
@@ -73,20 +115,30 @@ class KtField {
73
115
  errors = input([], /* @ts-ignore */
74
116
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
75
117
  /** Marque le champ comme invalide (pose `aria-invalid` et conditionne l'affichage des erreurs). @default false */
76
- invalid = input(false, /* @ts-ignore */
77
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
118
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
78
119
  /** Affiche l'astérisque requis et pose `aria-required` sur le contrôle. @default false */
79
- required = input(false, /* @ts-ignore */
80
- ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
81
- /** id imposé (sélecteurs de test stables) ; sinon auto-généré, anti-collision. */
120
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
121
+ /** id imposé (sélecteurs de test stables) ; sinon auto-généré, anti-collision. @default undefined */
82
122
  fieldId = input(/* @ts-ignore */
83
123
  ...(ngDevMode ? [undefined, { debugName: "fieldId" }] : /* istanbul ignore next */ []));
84
- /** Masquer le hint quand l'erreur s'affiche (façon Material). */
85
- hideHintWhenInvalid = input(this.config?.hideHintWhenInvalid ?? false, /* @ts-ignore */
86
- ...(ngDevMode ? [{ debugName: "hideHintWhenInvalid" }] : /* istanbul ignore next */ []));
87
- /** Afficher toutes les erreurs au lieu de la première seule. */
88
- showAllErrors = input(this.config?.showAllErrors ?? false, /* @ts-ignore */
89
- ...(ngDevMode ? [{ debugName: "showAllErrors" }] : /* istanbul ignore next */ []));
124
+ /** Masquer le hint quand l'erreur s'affiche (façon Material). @default KT_FIELD_CONFIG.hideHintWhenInvalid ?? false */
125
+ hideHintWhenInvalid = input(this.config?.hideHintWhenInvalid ?? false, { ...(ngDevMode ? { debugName: "hideHintWhenInvalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
126
+ /** Afficher toutes les erreurs au lieu de la première seule. @default KT_FIELD_CONFIG.showAllErrors ?? false */
127
+ showAllErrors = input(this.config?.showAllErrors ?? false, { ...(ngDevMode ? { debugName: "showAllErrors" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
128
+ /** Apparence du chrome : `fill` (label au-dessus) ou `outline` (bordure + label flottant, façon
129
+ Material). Non fournie valeur du `KT_FIELD_CONFIG`, sinon `'fill'`. @default undefined */
130
+ appearance = input(/* @ts-ignore */
131
+ ...(ngDevMode ? [undefined, { debugName: "appearance" }] : /* istanbul ignore next */ []));
132
+ /** Apparence effective : input ?? KT_FIELD_CONFIG.appearance ?? 'fill'. */
133
+ resolvedAppearance = computed(() => this.appearance() ?? this.config?.appearance ?? 'fill', /* @ts-ignore */
134
+ ...(ngDevMode ? [{ debugName: "resolvedAppearance" }] : /* istanbul ignore next */ []));
135
+ /** Politique du label flottant (outline) : `auto` (flotte au focus/rempli) ou `always` (toujours
136
+ flotté, même vide). Non fournie ⇒ valeur du `KT_FIELD_CONFIG`, sinon `'auto'`. @default undefined */
137
+ floatLabel = input(/* @ts-ignore */
138
+ ...(ngDevMode ? [undefined, { debugName: "floatLabel" }] : /* istanbul ignore next */ []));
139
+ /** Politique effective : input ?? KT_FIELD_CONFIG.floatLabel ?? 'auto'. */
140
+ resolvedFloatLabel = computed(() => this.floatLabel() ?? this.config?.floatLabel ?? 'auto', /* @ts-ignore */
141
+ ...(ngDevMode ? [{ debugName: "resolvedFloatLabel" }] : /* istanbul ignore next */ []));
90
142
  // Le contrôle est projeté : l'injection DI ne le traverse pas, on le cible via contentChild.
91
143
  control = contentChild(KtFieldControl, /* @ts-ignore */
92
144
  ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
@@ -99,7 +151,7 @@ class KtField {
99
151
  activeTooltip = computed(() => this.projectedTooltip() ?? this.templateTooltip(), /* @ts-ignore */
100
152
  ...(ngDevMode ? [{ debugName: "activeTooltip" }] : /* istanbul ignore next */ []));
101
153
  idGen = inject(KtIdGenerator);
102
- uid = this.idGen.generateId();
154
+ uid = this.idGen.generateId('field');
103
155
  baseId = computed(() => this.fieldId() ?? `kt-field-${this.uid}`, /* @ts-ignore */
104
156
  ...(ngDevMode ? [{ debugName: "baseId" }] : /* istanbul ignore next */ []));
105
157
  labelId = computed(() => `${this.baseId()}-label`, /* @ts-ignore */
@@ -140,18 +192,117 @@ class KtField {
140
192
  this.helpClick.emit(event);
141
193
  }
142
194
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtField, deps: [], target: i0.ɵɵFactoryTarget.Component });
143
- 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 } }, outputs: { helpClick: "helpClick" }, 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 });
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 });
144
196
  }
145
197
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtField, decorators: [{
146
198
  type: Component,
147
- args: [{ selector: 'kt-field', imports: [KtTooltip], changeDetection: ChangeDetectionStrategy.OnPush, 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"] }]
148
- }], 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 }] }], 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 }] }] } });
199
+ args: [{ selector: 'kt-field', imports: [KtTooltip], changeDetection: ChangeDetectionStrategy.OnPush, host: {
200
+ '[attr.data-appearance]': 'resolvedAppearance()',
201
+ '[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 }] }] } });
204
+
205
+ /**
206
+ * Lit un param typé d'une `ValidationError` (ex. `min`, `minLength`, `maxLength`, `minDate`…).
207
+ * Les sous-types d'erreur de Signal Forms exposent ces params, mais le type de base `ValidationError`
208
+ * ne les déclare pas : ce helper centralise l'accès pour les fabriques de message, sans `any`.
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * minLength: (e) => `Au moins ${ktErrorParam<number>(e, 'minLength')} caractères.`
213
+ * ```
214
+ */
215
+ function ktErrorParam(error, key) {
216
+ return error[key];
217
+ }
218
+ /**
219
+ * Messages d'erreur **anglais** par défaut de la lib, indexés par `kind`. Couvrent tous les
220
+ * validateurs natifs de Signal Forms (`required`, `email`, `min`, `max`, `minLength`, `maxLength`,
221
+ * `minDate`, `maxDate`, `pattern`). Surchargeables famille par famille via `KT_FIELD_CONFIG.errorMessages`
222
+ * (ou globalement par langue, cf. `provideKtDefaultFR`). Le `numberParseError` reste géré à la source
223
+ * par `kt-number-field` (kind `parse`).
224
+ */
225
+ const KT_DEFAULT_FIELD_ERROR_MESSAGES = {
226
+ required: 'This field is required.',
227
+ email: 'Enter a valid email address.',
228
+ min: (e) => `Enter a value greater than or equal to ${ktErrorParam(e, 'min')}.`,
229
+ max: (e) => `Enter a value less than or equal to ${ktErrorParam(e, 'max')}.`,
230
+ minLength: (e) => `Enter at least ${ktErrorParam(e, 'minLength')} characters.`,
231
+ maxLength: (e) => `Enter at most ${ktErrorParam(e, 'maxLength')} characters.`,
232
+ minDate: (e) => `Choose a date on or after ${ktErrorParam(e, 'minDate').toLocaleDateString()}.`,
233
+ maxDate: (e) => `Choose a date on or before ${ktErrorParam(e, 'maxDate').toLocaleDateString()}.`,
234
+ pattern: 'The value is not in the expected format.',
235
+ };
236
+ /**
237
+ * Résout le **message par défaut** d'une erreur de validation à partir de son `kind` (et de ses
238
+ * params typés), en fusionnant les défauts anglais embarqués avec les surcharges du `KT_FIELD_CONFIG`.
239
+ *
240
+ * Utilisé par chaque contrôle `kt-*` au moment du rendu, là où la `ValidationError` complète (params)
241
+ * est encore disponible — `kt-field` reste agnostique de Signal Forms. Précédence du texte affiché :
242
+ * `message` du validateur > `KT_FIELD_CONFIG.errorMessages[kind]` > défaut anglais > rien.
243
+ *
244
+ * Injectable racine : un seul résolveur partagé par tous les champs.
245
+ */
246
+ // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT Angular
247
+ // (NG1010) ; aucune logique à muter dans le décorateur.
248
+ class KtFieldErrorResolver {
249
+ config = inject(KT_FIELD_CONFIG, { optional: true });
250
+ /** Map effective : défauts anglais + surcharges du `KT_FIELD_CONFIG`, par `kind`. */
251
+ messages = {
252
+ ...KT_DEFAULT_FIELD_ERROR_MESSAGES,
253
+ ...this.config?.errorMessages,
254
+ };
255
+ /**
256
+ * Message par défaut d'une erreur (selon son `kind` + params), ou `undefined` si aucun défaut
257
+ * n'est connu pour ce `kind` (l'UI n'affiche alors rien).
258
+ */
259
+ resolve(error) {
260
+ const entry = this.messages[error.kind];
261
+ if (entry === undefined)
262
+ return undefined;
263
+ return typeof entry === 'function' ? entry(error) : entry;
264
+ }
265
+ /**
266
+ * Prépare une liste d'erreurs pour l'affichage : applique la précédence (message du validateur
267
+ * sinon défaut résolu) et **écarte les erreurs au message vide** — suppression explicite via
268
+ * `{ message: '' }`. Le champ reste invalide (rouge / `aria-invalid`), simplement sans texte.
269
+ */
270
+ resolveAll(errors) {
271
+ const resolved = [];
272
+ for (const error of errors) {
273
+ const message = error.message ?? this.resolve(error);
274
+ if (message === undefined || message.trim() === '')
275
+ continue;
276
+ resolved.push({ kind: error.kind, message });
277
+ }
278
+ return resolved;
279
+ }
280
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtFieldErrorResolver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
281
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtFieldErrorResolver, providedIn: 'root' });
282
+ }
283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtFieldErrorResolver, decorators: [{
284
+ type: Injectable,
285
+ args: [{ providedIn: 'root' }]
286
+ }] });
149
287
 
150
288
  /** Base partagée des champs simples (TextField, NumberField) : état FormValueControl,
151
289
  présentation (label/hint/prefix/suffix/clear) et politique d'affichage des erreurs.
152
- La valeur, son parsing et la notion de « vide » sont fournis par la sous-classe. */
290
+ La valeur, son parsing et la notion de « vide » sont fournis par la sous-classe.
291
+
292
+ @example
293
+ ```ts
294
+ // Sous-classement : on fournit value (model concret) + parse/emptyValue/isEmpty.
295
+ @Component({ selector: 'kt-text-field', templateUrl: './text-field.html' })
296
+ export class KtTextField extends KtBaseInputField<string> implements FormValueControl<string> {
297
+ readonly value = model<string>('');
298
+ protected parse(raw: string): string { return raw; }
299
+ protected emptyValue(): string { return ''; }
300
+ protected isEmpty(value: string): boolean { return value.length === 0; }
301
+ }
302
+ ``` */
153
303
  class KtBaseInputField {
154
304
  config = inject(KT_FIELD_CONFIG, { optional: true });
305
+ errorResolver = inject(KtFieldErrorResolver);
155
306
  // --- État poussé par [formField] ---
156
307
  // Ces entrées sont généralement câblées par l'intégration Signal Forms (`[formField]`) ;
157
308
  // tu peux aussi les piloter à la main en usage contrôlé.
@@ -159,20 +310,17 @@ class KtBaseInputField {
159
310
  touched = model(false, /* @ts-ignore */
160
311
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
161
312
  /** Désactive la saisie et le focus. @default false */
162
- disabled = input(false, /* @ts-ignore */
163
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
313
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
164
314
  /** Rend le champ en lecture seule (valeur visible, non modifiable). @default false */
165
- readonly = input(false, /* @ts-ignore */
166
- ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
315
+ readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
167
316
  /** Le champ est-il en erreur (état fourni par la validation). @default false */
168
- invalid = input(false, /* @ts-ignore */
169
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
317
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
170
318
  /** Marque le champ comme requis (ajoute `aria-required`). @default false */
171
- required = input(false, /* @ts-ignore */
172
- ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
319
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
173
320
  /** Le champ a-t-il été modifié depuis sa valeur initiale. @default false */
174
- dirty = input(false, /* @ts-ignore */
175
- ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
321
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
322
+ /** Validation asynchrone en cours (poussé par `[field]`) : pose `aria-busy` + `data-pending`. @default false */
323
+ pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
176
324
  /** Liste des erreurs de validation à afficher. @default [] */
177
325
  errors = input([], /* @ts-ignore */
178
326
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
@@ -180,27 +328,27 @@ class KtBaseInputField {
180
328
  name = input('', /* @ts-ignore */
181
329
  ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
182
330
  // --- Présentation ---
183
- /** id imposé pour des sélecteurs de test stables ; sinon auto-généré par Field. */
331
+ /** id imposé pour des sélecteurs de test stables ; sinon auto-généré par Field. @default undefined */
184
332
  id = input(/* @ts-ignore */
185
333
  ...(ngDevMode ? [undefined, { debugName: "id" }] : /* istanbul ignore next */ []));
186
- /** Libellé du champ (associé via `<label for>`). */
334
+ /** Libellé du champ (associé via `<label for>`). @default undefined */
187
335
  label = input(/* @ts-ignore */
188
336
  ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
189
- /** Texte d'aide affiché sous le champ quand il est valide. */
337
+ /** Texte d'aide affiché sous le champ quand il est valide. @default undefined */
190
338
  hint = input(/* @ts-ignore */
191
339
  ...(ngDevMode ? [undefined, { debugName: "hint" }] : /* istanbul ignore next */ []));
192
- /** Aide contextuelle riche : texte ou `TemplateRef` projeté dans une infobulle d'aide. */
340
+ /** Aide contextuelle riche : texte ou `TemplateRef` projeté dans une infobulle d'aide. @default undefined */
193
341
  helpText = input(/* @ts-ignore */
194
342
  ...(ngDevMode ? [undefined, { debugName: "helpText" }] : /* istanbul ignore next */ []));
195
343
  /** Libellé accessible du bouton d'aide. @default 'Help' (ou KT_FIELD_CONFIG.helpLabel) */
196
344
  helpLabel = input(this.config?.helpLabel ?? 'Help', /* @ts-ignore */
197
345
  ...(ngDevMode ? [{ debugName: "helpLabel" }] : /* istanbul ignore next */ []));
198
- /** Force la valeur de `aria-describedby` (sinon dérivée de hint/erreur). */
346
+ /** Force la valeur de `aria-describedby` (sinon dérivée de hint/erreur). @default undefined */
199
347
  customDescribedBy = input(/* @ts-ignore */
200
348
  ...(ngDevMode ? [undefined, { debugName: "customDescribedBy" }] : /* istanbul ignore next */ []));
201
349
  /** Émis au clic sur le bouton d'aide contextuelle. */
202
350
  helpClick = output();
203
- /** Texte indicatif affiché dans le champ vide. */
351
+ /** Texte indicatif affiché dans le champ vide. @default undefined */
204
352
  placeholder = input(/* @ts-ignore */
205
353
  ...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
206
354
  /** Indice d'autoremplissage du navigateur (attribut `autocomplete` natif) ; ex. `'current-password'`,
@@ -208,23 +356,31 @@ class KtBaseInputField {
208
356
  @default undefined */
209
357
  autocomplete = input(/* @ts-ignore */
210
358
  ...(ngDevMode ? [undefined, { debugName: "autocomplete" }] : /* istanbul ignore next */ []));
211
- /** Nom d'icône Material Symbols affichée en tête de champ. */
359
+ /** Nom d'icône Material Symbols affichée en tête de champ. @default undefined */
212
360
  icon = input(/* @ts-ignore */
213
361
  ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
214
362
  /** Affiche un bouton « effacer » quand le champ contient une valeur. @default false */
215
363
  clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
216
- /** Contenu décoratif en tête de champ : texte ou `TemplateRef`. */
364
+ /** Contenu décoratif en tête de champ : texte ou `TemplateRef`. @default undefined */
217
365
  prefix = input(/* @ts-ignore */
218
366
  ...(ngDevMode ? [undefined, { debugName: "prefix" }] : /* istanbul ignore next */ []));
219
- /** Contenu décoratif en fin de champ : texte ou `TemplateRef`. */
367
+ /** Contenu décoratif en fin de champ : texte ou `TemplateRef`. @default undefined */
220
368
  suffix = input(/* @ts-ignore */
221
369
  ...(ngDevMode ? [undefined, { debugName: "suffix" }] : /* istanbul ignore next */ []));
222
370
  /** Libellé i18n du bouton « effacer ». @default 'Clear' (ou KT_FIELD_CONFIG.clearLabel) */
223
371
  clearLabel = input(this.config?.clearLabel ?? 'Clear', /* @ts-ignore */
224
372
  ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
225
- /** Quand afficher l'erreur ; surcharge KT_FIELD_CONFIG et le défaut (`invalid && touched`). */
373
+ /** Quand afficher l'erreur ; surcharge KT_FIELD_CONFIG et le défaut (`invalid && touched`). @default undefined */
226
374
  errorMatcher = input(/* @ts-ignore */
227
375
  ...(ngDevMode ? [undefined, { debugName: "errorMatcher" }] : /* istanbul ignore next */ []));
376
+ /** Apparence du chrome (`fill` | `outline`) ; non fournie ⇒ `KT_FIELD_CONFIG.appearance` ?? `'fill'`.
377
+ @default undefined */
378
+ appearance = input(/* @ts-ignore */
379
+ ...(ngDevMode ? [undefined, { debugName: "appearance" }] : /* istanbul ignore next */ []));
380
+ /** Politique du label flottant en outline (`auto` | `always`) ; non fournie ⇒ `KT_FIELD_CONFIG.floatLabel`
381
+ ?? `'auto'`. En `always`, le label reste en haut même vide et le placeholder s'affiche. @default undefined */
382
+ floatLabel = input(/* @ts-ignore */
383
+ ...(ngDevMode ? [undefined, { debugName: "floatLabel" }] : /* istanbul ignore next */ []));
228
384
  inputRef = viewChild('input', /* @ts-ignore */
229
385
  ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
230
386
  matcher = computed(() => this.errorMatcher() ?? this.config?.errorMatcher ?? defaultKtFieldErrorMatcher, /* @ts-ignore */
@@ -232,8 +388,30 @@ class KtBaseInputField {
232
388
  // L'erreur ne s'affiche qu'au déclenchement décidé par le matcher (invalid/touched/dirty).
233
389
  showInvalid = computed(() => this.matcher()({ invalid: this.invalid(), touched: this.touched(), dirty: this.dirty() }), /* @ts-ignore */
234
390
  ...(ngDevMode ? [{ debugName: "showInvalid" }] : /* istanbul ignore next */ []));
391
+ /** Erreurs prêtes pour `kt-field` : messages par défaut résolus, suppressions (`message: ''`) écartées. */
392
+ displayErrors = computed(() => this.errorResolver.resolveAll(this.errors()), /* @ts-ignore */
393
+ ...(ngDevMode ? [{ debugName: "displayErrors" }] : /* istanbul ignore next */ []));
235
394
  showClear = computed(() => this.clearable() && !this.disabled() && !this.readonly() && !this.isEmpty(this.value()), /* @ts-ignore */
236
395
  ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
396
+ /** Apparence effective (input ?? config ?? 'fill'). Sert aux templates : forward à `kt-field` et,
397
+ en outline, pose une sentinelle de placeholder pour la détection « rempli » du label flottant. */
398
+ outline = computed(() => (this.appearance() ?? this.config?.appearance ?? 'fill') === 'outline', /* @ts-ignore */
399
+ ...(ngDevMode ? [{ debugName: "outline" }] : /* istanbul ignore next */ []));
400
+ /** Label toujours flotté en outline (input ?? config ?? 'auto'). */
401
+ alwaysFloat = computed(() => (this.floatLabel() ?? this.config?.floatLabel ?? 'auto') === 'always', /* @ts-ignore */
402
+ ...(ngDevMode ? [{ debugName: "alwaysFloat" }] : /* istanbul ignore next */ []));
403
+ /** Valeur de l'attribut `placeholder` natif selon l'apparence :
404
+ - fill : le placeholder tel quel ;
405
+ - outline + always : le vrai placeholder (le label est en haut, pas de chevauchement) ;
406
+ - outline + auto : sentinelle `' '` (le label tient lieu de placeholder ; active `:placeholder-shown`). */
407
+ placeholderAttr = computed(() => {
408
+ if (!this.outline())
409
+ return this.placeholder() ?? null;
410
+ if (this.alwaysFloat())
411
+ return this.placeholder() ?? null;
412
+ return this.placeholder() ?? ' ';
413
+ }, /* @ts-ignore */
414
+ ...(ngDevMode ? [{ debugName: "placeholderAttr" }] : /* istanbul ignore next */ []));
237
415
  asTemplate(value) {
238
416
  return value instanceof TemplateRef ? value : null;
239
417
  }
@@ -249,17 +427,32 @@ class KtBaseInputField {
249
427
  this.clear();
250
428
  }
251
429
  }
430
+ /** Focus le contrôle natif (utilisé par Signal Forms `focusBoundControl`). */
431
+ focus(options) {
432
+ this.inputRef()?.nativeElement.focus(options);
433
+ }
434
+ /** Ré-aligne le texte affiché sur la valeur courante (utilisé par Signal Forms `reset`). */
435
+ reset() {
436
+ const el = this.inputRef()?.nativeElement;
437
+ if (el)
438
+ el.value = this.displayValue();
439
+ }
440
+ /** Représentation texte de la valeur pour l'input natif. Surchargeable par sous-classe. */
441
+ displayValue() {
442
+ const v = this.value();
443
+ return v == null ? '' : String(v);
444
+ }
252
445
  clear() {
253
446
  this.value.set(this.emptyValue());
254
447
  this.touched.set(true);
255
- this.inputRef()?.nativeElement.focus();
448
+ this.focus();
256
449
  }
257
450
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseInputField, deps: [], target: i0.ɵɵFactoryTarget.Directive });
258
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: KtBaseInputField, isStandalone: true, inputs: { touched: { classPropertyName: "touched", publicName: "touched", 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, 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 }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touched: "touchedChange", helpClick: "helpClick" }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0 });
451
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: KtBaseInputField, isStandalone: true, inputs: { touched: { classPropertyName: "touched", publicName: "touched", 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, 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 }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", 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: { touched: "touchedChange", helpClick: "helpClick" }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0 });
259
452
  }
260
453
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseInputField, decorators: [{
261
454
  type: Directive
262
- }], propDecorators: { touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], 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"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
455
+ }], propDecorators: { touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], 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"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], floatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatLabel", required: false }] }], inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
263
456
 
264
457
  /** Normalise une liste de suggestions en options `<datalist>`. `toValue` sérialise la valeur en
265
458
  chaîne attendue par l'input natif (identité pour le texte, `String()` pour les nombres,
@@ -304,23 +497,25 @@ class KtNumberField extends KtBaseInputField {
304
497
  suggestions = input(/* @ts-ignore */
305
498
  ...(ngDevMode ? [undefined, { debugName: "suggestions" }] : /* istanbul ignore next */ []));
306
499
  idGen = inject(KtIdGenerator);
307
- uid = this.idGen.generateId();
500
+ uid = this.idGen.generateId('number-field');
308
501
  datalistId = `kt-number-field-list-${this.uid}`;
309
502
  hasSuggestions = computed(() => (this.suggestions()?.length ?? 0) > 0, /* @ts-ignore */
310
503
  ...(ngDevMode ? [{ debugName: "hasSuggestions" }] : /* istanbul ignore next */ []));
311
504
  datalistOptions = computed(() => normalizeKtSuggestions(this.suggestions(), (value) => String(value)), /* @ts-ignore */
312
505
  ...(ngDevMode ? [{ debugName: "datalistOptions" }] : /* istanbul ignore next */ []));
313
- // Utilitaire v22 standardisé pour la synchronisation réactive propre du champ numérique
506
+ // Chemin de valeur unique : `parse()` (contrat de `KtBaseInputField`) est la SEULE source de
507
+ // vérité du parsing numérique ; `transformedValue` ne fait que l'envelopper pour émettre une
508
+ // erreur de validation Signal Forms sur une saisie non numérique (et formater pour l'affichage).
314
509
  rawValue = transformedValue(this.value, {
315
510
  parse: (val) => {
316
511
  const trimmed = val.trim().replace(',', '.');
317
- if (trimmed === '')
318
- return { value: null };
319
- const parsed = Number(trimmed);
320
- if (Number.isNaN(parsed)) {
321
- return { error: { kind: 'parse', message: 'La valeur saisie doit être un nombre valide.' } };
512
+ if (trimmed !== '' && Number.isNaN(Number(trimmed))) {
513
+ // Défaut anglais neutre ; surchargeable via KT_FIELD_CONFIG.numberParseError (cf. provideKtDefaultFR).
514
+ return {
515
+ error: { kind: 'parse', message: this.config?.numberParseError ?? 'Please enter a valid number.' },
516
+ };
322
517
  }
323
- return { value: parsed };
518
+ return { value: this.parse(val) };
324
519
  },
325
520
  format: (val) => (val === null ? '' : String(val)),
326
521
  });
@@ -378,6 +573,11 @@ class KtNumberField extends KtBaseInputField {
378
573
  }
379
574
  this.value.set(newValue);
380
575
  }
576
+ // `rawValue` est un `transformedValue` : on passe par son canal officiel (et non par
577
+ // `nativeElement.value`) pour ne pas désynchroniser la représentation brute interne.
578
+ reset() {
579
+ this.rawValue.set(this.value() === null ? '' : String(this.value()));
580
+ }
381
581
  emptyValue() {
382
582
  return null;
383
583
  }
@@ -385,11 +585,11 @@ class KtNumberField extends KtBaseInputField {
385
585
  return value === null;
386
586
  }
387
587
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtNumberField, deps: null, target: i0.ɵɵFactoryTarget.Component });
388
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtNumberField, isStandalone: true, selector: "kt-number-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"text\"\n inputmode=\"decimal\"\n role=\"spinbutton\"\n [attr.aria-valuemin]=\"min() ?? null\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuenow]=\"value() ?? null\"\n [value]=\"rawValue()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"rawValue.set($event.target.value)\"\n (keydown)=\"onKeyDown($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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
588
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtNumberField, isStandalone: true, selector: "kt-number-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", 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 [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=\"text\"\n inputmode=\"decimal\"\n role=\"spinbutton\"\n [attr.aria-valuemin]=\"min() ?? null\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuenow]=\"value() ?? null\"\n [value]=\"rawValue()\"\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.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"rawValue.set($event.target.value)\"\n (keydown)=\"onKeyDown($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", 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 });
389
589
  }
390
590
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtNumberField, decorators: [{
391
591
  type: Component,
392
- args: [{ selector: 'kt-number-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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"text\"\n inputmode=\"decimal\"\n role=\"spinbutton\"\n [attr.aria-valuemin]=\"min() ?? null\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuenow]=\"value() ?? null\"\n [value]=\"rawValue()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"rawValue.set($event.target.value)\"\n (keydown)=\"onKeyDown($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" }]
592
+ args: [{ selector: 'kt-number-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=\"text\"\n inputmode=\"decimal\"\n role=\"spinbutton\"\n [attr.aria-valuemin]=\"min() ?? null\"\n [attr.aria-valuemax]=\"max() ?? null\"\n [attr.aria-valuenow]=\"value() ?? null\"\n [value]=\"rawValue()\"\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.min]=\"min() ?? null\"\n [attr.max]=\"max() ?? null\"\n [attr.step]=\"step() ?? null\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"rawValue.set($event.target.value)\"\n (keydown)=\"onKeyDown($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" }]
393
593
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }] } });
394
594
 
395
595
  /**
@@ -408,9 +608,19 @@ class KtTextArea extends KtBaseInputField {
408
608
  /** Hauteur initiale (lignes). L'autosize est géré en CSS via `field-sizing: content`. @default 3 */
409
609
  rows = input(3, /* @ts-ignore */
410
610
  ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
611
+ /** Plancher de caractères (attribut `minlength` natif ; poussé par le validateur minLength). @default undefined */
612
+ minLength = input(/* @ts-ignore */
613
+ ...(ngDevMode ? [undefined, { debugName: "minLength" }] : /* istanbul ignore next */ []));
411
614
  /** Plafond de caractères ; poussé par le form (validateur maxLength) ou par le consommateur. @default undefined */
412
615
  maxLength = input(/* @ts-ignore */
413
616
  ...(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 */ []));
414
624
  parse(raw) {
415
625
  return raw;
416
626
  }
@@ -421,12 +631,12 @@ class KtTextArea extends KtBaseInputField {
421
631
  return value.length === 0;
422
632
  }
423
633
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTextArea, deps: null, target: i0.ɵɵFactoryTarget.Component });
424
- 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 }, 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.maxlength]=\"maxLength() ?? null\"\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"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
425
635
  }
426
636
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTextArea, decorators: [{
427
637
  type: Component,
428
- 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.maxlength]=\"maxLength() ?? null\"\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" }]
429
- }], 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 }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }] } });
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 }] }] } });
430
640
 
431
641
  /**
432
642
  * Champ texte (valeur `string`) intégré aux Signal Forms via `FormValueControl`. Hérite de la
@@ -444,13 +654,26 @@ class KtTextField extends KtBaseInputField {
444
654
  /** Variante HTML du champ (pilote `type` natif et le clavier mobile). @default 'text' */
445
655
  type = input('text', /* @ts-ignore */
446
656
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
657
+ /** Plancher de caractères (attribut `minlength` natif ; poussé par le validateur minLength). @default undefined */
658
+ minLength = input(/* @ts-ignore */
659
+ ...(ngDevMode ? [undefined, { debugName: "minLength" }] : /* istanbul ignore next */ []));
660
+ /** Plafond de caractères (attribut `maxlength` natif ; poussé par le validateur maxLength). @default undefined */
661
+ maxLength = input(/* @ts-ignore */
662
+ ...(ngDevMode ? [undefined, { debugName: "maxLength" }] : /* istanbul ignore next */ []));
663
+ /** Motifs de validation (contrat Signal Forms). L'attribut natif `pattern` ne prend qu'une regex →
664
+ seule la première est posée. @default [] */
665
+ pattern = input([], /* @ts-ignore */
666
+ ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
667
+ /** Source de la première regex pour l'attribut `pattern` natif (`null` si aucune). */
668
+ patternAttr = computed(() => this.pattern()[0]?.source ?? null, /* @ts-ignore */
669
+ ...(ngDevMode ? [{ debugName: "patternAttr" }] : /* istanbul ignore next */ []));
447
670
  /** Suggestions d'autocomplétion proposées via un `<datalist>` natif (la saisie reste libre).
448
671
  Valeurs simples (`string[]`) ou couples `{ value, label }` pour distinguer libellé affiché et
449
672
  valeur insérée. @default undefined */
450
673
  suggestions = input(/* @ts-ignore */
451
674
  ...(ngDevMode ? [undefined, { debugName: "suggestions" }] : /* istanbul ignore next */ []));
452
675
  idGen = inject(KtIdGenerator);
453
- uid = this.idGen.generateId();
676
+ uid = this.idGen.generateId('text-field');
454
677
  datalistId = `kt-text-field-list-${this.uid}`;
455
678
  hasSuggestions = computed(() => (this.suggestions()?.length ?? 0) > 0, /* @ts-ignore */
456
679
  ...(ngDevMode ? [{ debugName: "hasSuggestions" }] : /* istanbul ignore next */ []));
@@ -466,12 +689,12 @@ class KtTextField extends KtBaseInputField {
466
689
  return value.length === 0;
467
690
  }
468
691
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTextField, deps: null, target: i0.ɵɵFactoryTarget.Component });
469
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtTextField, isStandalone: true, selector: "kt-text-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
692
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtTextField, isStandalone: true, selector: "kt-text-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", 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 }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", 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 [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", 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 });
470
693
  }
471
694
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTextField, decorators: [{
472
695
  type: Component,
473
- 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\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" }]
474
- }], 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 }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }] } });
696
+ 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
+ }], 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 }] }] } });
475
698
 
476
699
  /**
477
700
  * Bouton bascule (Switch / Slide-Toggle) accessible conforme aux normes WAI-ARIA.
@@ -495,13 +718,13 @@ class KtSwitch {
495
718
  /** Désactive la bascule (non actionnable). @default false */
496
719
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
497
720
  /** Marque la bascule comme invalide (combiné à `touched`/`dirty` via l'`errorMatcher`). @default false */
498
- invalid = input(false, /* @ts-ignore */
499
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
721
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
500
722
  /** Affiche l'astérisque requis sur le libellé. @default false */
501
723
  required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
502
724
  /** État « modifié » (entre dans la logique d'affichage des erreurs). @default false */
503
- dirty = input(false, /* @ts-ignore */
504
- ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
725
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
726
+ /** Validation asynchrone en cours (poussé par `[field]`) : pose `aria-busy` + `data-pending`. @default false */
727
+ pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
505
728
  /** Erreurs de validation à afficher sous la bascule. @default [] */
506
729
  errors = input([], /* @ts-ignore */
507
730
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
@@ -525,8 +748,9 @@ class KtSwitch {
525
748
  errorMatcher = input(/* @ts-ignore */
526
749
  ...(ngDevMode ? [undefined, { debugName: "errorMatcher" }] : /* istanbul ignore next */ []));
527
750
  /** Afficher toutes les erreurs au lieu de la première seule. @default KT_FIELD_CONFIG.showAllErrors ?? false */
528
- showAllErrors = input(this.config?.showAllErrors ?? false, /* @ts-ignore */
529
- ...(ngDevMode ? [{ debugName: "showAllErrors" }] : /* istanbul ignore next */ []));
751
+ showAllErrors = input(this.config?.showAllErrors ?? false, { ...(ngDevMode ? { debugName: "showAllErrors" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
752
+ switchBtn = viewChild.required('switchBtn');
753
+ errorResolver = inject(KtFieldErrorResolver);
530
754
  idGen = inject(KtIdGenerator);
531
755
  uid = this.idGen.generateId('switch');
532
756
  baseId = computed(() => this.id() ?? `kt-switch-${this.uid}`, /* @ts-ignore */
@@ -541,7 +765,10 @@ class KtSwitch {
541
765
  ...(ngDevMode ? [{ debugName: "matcher" }] : /* istanbul ignore next */ []));
542
766
  showInvalid = computed(() => this.matcher()({ invalid: this.invalid(), touched: this.touched(), dirty: this.dirty() }), /* @ts-ignore */
543
767
  ...(ngDevMode ? [{ debugName: "showInvalid" }] : /* istanbul ignore next */ []));
544
- displayedErrors = computed(() => this.showAllErrors() ? this.errors() : this.errors().slice(0, 1), /* @ts-ignore */
768
+ /** Erreurs résolues (messages par défaut appliqués, suppressions `message: ''` écartées). */
769
+ resolvedErrors = computed(() => this.errorResolver.resolveAll(this.errors()), /* @ts-ignore */
770
+ ...(ngDevMode ? [{ debugName: "resolvedErrors" }] : /* istanbul ignore next */ []));
771
+ displayedErrors = computed(() => this.showAllErrors() ? this.resolvedErrors() : this.resolvedErrors().slice(0, 1), /* @ts-ignore */
545
772
  ...(ngDevMode ? [{ debugName: "displayedErrors" }] : /* istanbul ignore next */ []));
546
773
  resolvedAriaLabel = computed(() => this.ariaLabel() ?? null, /* @ts-ignore */
547
774
  ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
@@ -549,11 +776,24 @@ class KtSwitch {
549
776
  const ids = [];
550
777
  if (this.hint() && !this.showInvalid())
551
778
  ids.push(this.hintId());
552
- if (this.showInvalid() && this.errors().length > 0)
779
+ if (this.showInvalid() && this.resolvedErrors().length > 0)
553
780
  ids.push(this.errorId());
554
781
  return ids.length ? ids.join(' ') : null;
555
782
  }, /* @ts-ignore */
556
783
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
784
+ constructor() {
785
+ // Garde-fou a11y (dev, navigateur uniquement) : un switch sans nom accessible est annoncé vide
786
+ // (WCAG 4.1.2). Le nom vient de `label` ou `ariaLabel` (pas de contenu projeté ici).
787
+ afterNextRender(() => {
788
+ if (!isDevMode() || this.label() || this.ariaLabel())
789
+ return;
790
+ console.warn('[ktSwitch] sans `label` ni `ariaLabel` : le contrôle est annoncé sans nom accessible (WCAG 4.1.2).');
791
+ });
792
+ }
793
+ /** Focus le bouton bascule natif (utilisé par Signal Forms `focusBoundControl`). */
794
+ focus(options) {
795
+ this.switchBtn().nativeElement.focus(options);
796
+ }
557
797
  toggle() {
558
798
  if (this.disabled())
559
799
  return;
@@ -565,11 +805,12 @@ class KtSwitch {
565
805
  this.toggle();
566
806
  }
567
807
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
568
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtSwitch, isStandalone: true, selector: "kt-switch", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: `
808
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtSwitch, isStandalone: true, selector: "kt-switch", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, viewQueries: [{ propertyName: "switchBtn", first: true, predicate: ["switchBtn"], descendants: true, isSignal: true }], ngImport: i0, template: `
569
809
  <div
570
810
  class="kt-switch-field"
571
811
  [class.kt-switch-field--invalid]="showInvalid()"
572
812
  [class.kt-switch-field--disabled]="disabled()"
813
+ [attr.data-pending]="pending() ? '' : null"
573
814
  >
574
815
  <div class="kt-switch-row">
575
816
  <button
@@ -582,9 +823,12 @@ class KtSwitch {
582
823
  [attr.aria-labelledby]="label() ? labelId() : null"
583
824
  [attr.aria-describedby]="describedBy()"
584
825
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
826
+ [attr.aria-required]="required() ? 'true' : null"
827
+ [attr.aria-busy]="pending() ? 'true' : null"
585
828
  [disabled]="disabled()"
586
829
  (click)="toggle()"
587
830
  (keydown.space)="onSpacebar($event)"
831
+ (blur)="touched.set(true)"
588
832
  class="kt-switch"
589
833
  >
590
834
  <span class="kt-switch__thumb"></span>
@@ -620,6 +864,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
620
864
  class="kt-switch-field"
621
865
  [class.kt-switch-field--invalid]="showInvalid()"
622
866
  [class.kt-switch-field--disabled]="disabled()"
867
+ [attr.data-pending]="pending() ? '' : null"
623
868
  >
624
869
  <div class="kt-switch-row">
625
870
  <button
@@ -632,9 +877,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
632
877
  [attr.aria-labelledby]="label() ? labelId() : null"
633
878
  [attr.aria-describedby]="describedBy()"
634
879
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
880
+ [attr.aria-required]="required() ? 'true' : null"
881
+ [attr.aria-busy]="pending() ? 'true' : null"
635
882
  [disabled]="disabled()"
636
883
  (click)="toggle()"
637
884
  (keydown.space)="onSpacebar($event)"
885
+ (blur)="touched.set(true)"
638
886
  class="kt-switch"
639
887
  >
640
888
  <span class="kt-switch__thumb"></span>
@@ -662,7 +910,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
662
910
  </div>
663
911
  </div>
664
912
  `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-switch-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-switch-row{display:flex;align-items:center;gap:var(--field-control-gap, .5rem)}.kt-switch{position:relative;display:inline-flex;align-items:center;box-sizing:border-box;inline-size:var(--switch-width, 2.75rem);block-size:var(--switch-height, 1.5rem);padding:var(--switch-padding, 2px);border-radius:var(--switch-radius, 999px);border:var(--switch-border-width, 2px) solid var(--switch-border-color, var(--field-border-color, var(--kt-outline, #c4c7c5)));background-color:var(--switch-bg, var(--field-bg, var(--kt-surface, #ffffff)));cursor:pointer;outline:none;box-shadow:var(--switch-shadow, var(--field-shadow, none));transition:var( --switch-transition, background-color .2s cubic-bezier(.4, 0, .2, 1), border-color .2s cubic-bezier(.4, 0, .2, 1), box-shadow .2s ease )}.kt-switch:after{content:\"\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:100%;height:44px}.kt-switch__thumb{display:block;inline-size:var(--switch-thumb-size, 1rem);block-size:var(--switch-thumb-size, 1rem);border-radius:var(--switch-thumb-radius, 50%);background-color:var(--switch-thumb-bg, var(--kt-outline-strong, #5f6368));box-shadow:var(--switch-thumb-shadow, 0 1px 3px rgba(0, 0, 0, .2));transition:var( --switch-thumb-transition, transform .2s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease );transform:translate(0)}.kt-switch[aria-checked=true]{background-color:var(--switch-bg-active, var(--kt-primary, #0b57d0));border-color:var(--switch-border-color-active, var(--kt-primary, #0b57d0));box-shadow:var( --switch-shadow-active, 0 0 8px color-mix(in srgb, var(--switch-bg-active, var(--kt-primary, #0b57d0)) 50%, transparent) )}.kt-switch[aria-checked=true] .kt-switch__thumb{background-color:var(--switch-thumb-bg-active, #ffffff);transform:translate(calc(var(--switch-width, 2.75rem) - var(--switch-thumb-size, 1rem) - (var(--switch-padding, 2px) * 2) - (var(--switch-border-width, 2px) * 2)))}.kt-switch:hover:not(:disabled){border-color:var(--switch-border-color-hover, var(--field-border-color-hover, var(--kt-outline-strong, #5f6368)));box-shadow:var(--switch-shadow-hover, var(--field-shadow-hover, var(--switch-shadow, var(--field-shadow, none))))}.kt-switch[aria-checked=true]:hover:not(:disabled){background-color:var(--switch-bg-active-hover, color-mix(in srgb, var(--kt-primary, #0b57d0) 90%, black));border-color:var(--switch-border-color-active-hover, color-mix(in srgb, var(--kt-primary, #0b57d0) 90%, black))}.kt-switch:focus-visible{outline:var(--kt-focus-ring-width, 2px) solid var(--kt-focus-ring-color, #0b57d0);outline-offset:var(--switch-focus-ring-offset, 2px);box-shadow:var(--switch-shadow-focus, var(--field-shadow-focus, var(--switch-shadow, var(--field-shadow, none))))}.kt-switch:disabled{cursor:not-allowed;opacity:.5;background-color:var(--field-disabled-bg, #f1f3f4);border-color:var(--field-border-color, #c4c7c5)}.kt-switch:disabled .kt-switch__thumb{background-color:var(--kt-outline, #c4c7c5);box-shadow:none}.kt-switch-field--invalid .kt-switch{border-color:var(--field-error-color, var(--kt-danger, #b3261e))}.kt-switch-field--invalid .kt-switch[aria-checked=true]{background-color:var(--field-error-color, var(--kt-danger, #b3261e));border-color:var(--field-error-color, var(--kt-danger, #b3261e))}.kt-switch-label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit);cursor:pointer;-webkit-user-select:none;user-select:none}.kt-switch-field--disabled .kt-switch-label{cursor:not-allowed;color:var(--field-hint-color, #5f6368);opacity:.6}.kt-switch-label__required{margin-inline-start:.125rem;color:var(--field-required-color, #8c1d18)}.kt-switch-hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-switch-error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-switch-error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-switch,.kt-switch__thumb{transition:none}.kt-switch-error-message{animation:none}}}\n"] }]
665
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }] } });
913
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], switchBtn: [{ type: i0.ViewChild, args: ['switchBtn', { isSignal: true }] }] } });
666
914
 
667
915
  /**
668
916
  * Groupe de cases à cocher (Checkbox Group) conforme à l'ARIA APG / RGAA.
@@ -676,9 +924,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
676
924
  * @example
677
925
  * ```html
678
926
  * <kt-checkbox-group label="Centres d'intérêt" [(value)]="interests" required>
679
- * <kt-checkbox [value]="'sport'" label="Sport" />
680
- * <kt-checkbox [value]="'musique'" label="Musique" />
681
- * <kt-checkbox [value]="'cinema'" label="Cinéma" />
927
+ * <kt-checkbox [optionValue]="'sport'" label="Sport" />
928
+ * <kt-checkbox [optionValue]="'musique'" label="Musique" />
929
+ * <kt-checkbox [optionValue]="'cinema'" label="Cinéma" />
682
930
  * </kt-checkbox-group>
683
931
  * ```
684
932
  */
@@ -694,17 +942,19 @@ class KtCheckboxGroup {
694
942
  /** Désactive le groupe entier (hérité par chaque case enfant). @default false */
695
943
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
696
944
  /** Marque le groupe comme invalide (combiné à `touched`/`dirty` via l'`errorMatcher`). @default false */
697
- invalid = input(false, /* @ts-ignore */
698
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
945
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
699
946
  /** Affiche l'astérisque requis et `aria-required` sur le groupe. @default false */
700
947
  required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
701
948
  /** État « modifié » (entre dans la logique d'affichage des erreurs). @default false */
702
- dirty = input(false, /* @ts-ignore */
703
- ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
949
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
950
+ /** Validation asynchrone en cours (poussé par `[field]`) : pose `aria-busy` + `data-pending`. @default false */
951
+ pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
704
952
  /** Erreurs de validation à afficher sous le groupe. @default [] */
705
953
  errors = input([], /* @ts-ignore */
706
954
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
707
- /** Attribut `name` partagé (à titre indicatif). @default '' */
955
+ /** Nom logique du groupe, à titre INDICATIF : contrairement à `kt-radio-group` (où `name` est
956
+ propagé aux `<input>` natifs pour le regroupement radio), il n'est PAS répercuté sur les cases
957
+ enfants (les checkboxes n'ont pas besoin d'un `name` partagé). @default '' */
708
958
  name = input('', /* @ts-ignore */
709
959
  ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
710
960
  // --- Présentation ---
@@ -724,11 +974,12 @@ class KtCheckboxGroup {
724
974
  errorMatcher = input(/* @ts-ignore */
725
975
  ...(ngDevMode ? [undefined, { debugName: "errorMatcher" }] : /* istanbul ignore next */ []));
726
976
  /** Afficher toutes les erreurs au lieu de la première seule. @default KT_FIELD_CONFIG.showAllErrors ?? false */
727
- showAllErrors = input(this.config?.showAllErrors ?? false, /* @ts-ignore */
728
- ...(ngDevMode ? [{ debugName: "showAllErrors" }] : /* istanbul ignore next */ []));
977
+ showAllErrors = input(this.config?.showAllErrors ?? false, { ...(ngDevMode ? { debugName: "showAllErrors" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
729
978
  /** Égalité des valeurs en mode objet (défaut : identité `===`). */
730
979
  compareWith = input(/* @ts-ignore */
731
980
  ...(ngDevMode ? [undefined, { debugName: "compareWith" }] : /* istanbul ignore next */ []));
981
+ el = inject(ElementRef);
982
+ errorResolver = inject(KtFieldErrorResolver);
732
983
  idGen = inject(KtIdGenerator);
733
984
  uid = this.idGen.generateId('checkbox-group');
734
985
  baseId = computed(() => this.id() ?? `kt-checkbox-group-${this.uid}`, /* @ts-ignore */
@@ -743,7 +994,10 @@ class KtCheckboxGroup {
743
994
  ...(ngDevMode ? [{ debugName: "matcher" }] : /* istanbul ignore next */ []));
744
995
  showInvalid = computed(() => this.matcher()({ invalid: this.invalid(), touched: this.touched(), dirty: this.dirty() }), /* @ts-ignore */
745
996
  ...(ngDevMode ? [{ debugName: "showInvalid" }] : /* istanbul ignore next */ []));
746
- displayedErrors = computed(() => this.showAllErrors() ? this.errors() : this.errors().slice(0, 1), /* @ts-ignore */
997
+ /** Erreurs résolues (messages par défaut appliqués, suppressions `message: ''` écartées). */
998
+ resolvedErrors = computed(() => this.errorResolver.resolveAll(this.errors()), /* @ts-ignore */
999
+ ...(ngDevMode ? [{ debugName: "resolvedErrors" }] : /* istanbul ignore next */ []));
1000
+ displayedErrors = computed(() => this.showAllErrors() ? this.resolvedErrors() : this.resolvedErrors().slice(0, 1), /* @ts-ignore */
747
1001
  ...(ngDevMode ? [{ debugName: "displayedErrors" }] : /* istanbul ignore next */ []));
748
1002
  resolvedAriaLabel = computed(() => this.ariaLabel() ?? null, /* @ts-ignore */
749
1003
  ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
@@ -751,7 +1005,7 @@ class KtCheckboxGroup {
751
1005
  const ids = [];
752
1006
  if (this.hint() && !this.showInvalid())
753
1007
  ids.push(this.hintId());
754
- if (this.showInvalid() && this.errors().length > 0)
1008
+ if (this.showInvalid() && this.resolvedErrors().length > 0)
755
1009
  ids.push(this.errorId());
756
1010
  return ids.length ? ids.join(' ') : null;
757
1011
  }, /* @ts-ignore */
@@ -764,6 +1018,10 @@ class KtCheckboxGroup {
764
1018
  const cmp = this.comparator();
765
1019
  return arr.some((v) => cmp(v, optionValue));
766
1020
  }
1021
+ /** Focus la première case activable (utilisé par Signal Forms `focusBoundControl`). */
1022
+ focus(options) {
1023
+ this.el.nativeElement.querySelector('input[type="checkbox"]:not([disabled])')?.focus(options);
1024
+ }
767
1025
  /** Ajoute/retire une valeur d'option de la sélection (émis par un enfant au `change` natif). */
768
1026
  toggle(optionValue, checked) {
769
1027
  if (this.disabled())
@@ -780,7 +1038,7 @@ class KtCheckboxGroup {
780
1038
  this.touched.set(true);
781
1039
  }
782
1040
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCheckboxGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
783
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtCheckboxGroup, isStandalone: true, selector: "kt-checkbox-group", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: `
1041
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtCheckboxGroup, isStandalone: true, selector: "kt-checkbox-group", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: `
784
1042
  <div
785
1043
  class="kt-checkbox-group-field"
786
1044
  [class.kt-checkbox-group-field--invalid]="showInvalid()"
@@ -791,6 +1049,8 @@ class KtCheckboxGroup {
791
1049
  [attr.aria-describedby]="describedBy()"
792
1050
  [attr.aria-required]="required() ? 'true' : null"
793
1051
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
1052
+ [attr.aria-busy]="pending() ? 'true' : null"
1053
+ [attr.data-pending]="pending() ? '' : null"
794
1054
  >
795
1055
  @if (label(); as labelText) {
796
1056
  <span [id]="labelId()" class="kt-checkbox-group__legend">
@@ -816,7 +1076,7 @@ class KtCheckboxGroup {
816
1076
  }
817
1077
  </div>
818
1078
  </div>
819
- `, isInline: true, styles: ["@layer kt-aaa.components{:host{display:block}.kt-checkbox-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-checkbox-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-checkbox-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-checkbox-group__options{display:flex;flex-direction:var(--checkbox-group-direction, column);flex-wrap:wrap;gap:var(--checkbox-group-gap, .25rem)}.kt-checkbox-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-checkbox-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-checkbox-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-checkbox-group__error-message{animation:none}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1079
+ `, isInline: true, styles: ["@layer kt-aaa.components{:host{display:block}.kt-checkbox-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-checkbox-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-checkbox-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-checkbox-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-checkbox-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-checkbox-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-checkbox-group__error-message{animation:none}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
820
1080
  }
821
1081
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCheckboxGroup, decorators: [{
822
1082
  type: Component,
@@ -831,6 +1091,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
831
1091
  [attr.aria-describedby]="describedBy()"
832
1092
  [attr.aria-required]="required() ? 'true' : null"
833
1093
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
1094
+ [attr.aria-busy]="pending() ? 'true' : null"
1095
+ [attr.data-pending]="pending() ? '' : null"
834
1096
  >
835
1097
  @if (label(); as labelText) {
836
1098
  <span [id]="labelId()" class="kt-checkbox-group__legend">
@@ -856,8 +1118,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
856
1118
  }
857
1119
  </div>
858
1120
  </div>
859
- `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-checkbox-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-checkbox-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-checkbox-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-checkbox-group__options{display:flex;flex-direction:var(--checkbox-group-direction, column);flex-wrap:wrap;gap:var(--checkbox-group-gap, .25rem)}.kt-checkbox-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-checkbox-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-checkbox-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-checkbox-group__error-message{animation:none}}}\n"] }]
860
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }] } });
1121
+ `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-checkbox-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-checkbox-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-checkbox-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-checkbox-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-checkbox-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-checkbox-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-checkbox-group__error-message{animation:none}}}\n"] }]
1122
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }] } });
861
1123
 
862
1124
  /**
863
1125
  * Case à cocher (Checkbox) accessible conforme aux normes WAI-ARIA / RGAA.
@@ -867,8 +1129,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
867
1129
  * DEUX MODES, selon la présence d'un `kt-checkbox-group` parent :
868
1130
  * - Autonome : `value` est l'état coché (`boolean`, two-way), intégré aux Signal Forms via
869
1131
  * `FormValueControl<boolean>` (même contrat que `kt-switch`).
870
- * - Dans un groupe : `value` est la VALEUR d'option représentée ; l'état coché dérive de
871
- * l'appartenance au tableau du groupe, et le (dé)cochage met à jour ce tableau.
1132
+ * - Dans un groupe : `optionValue` porte la VALEUR d'option représentée ; l'état coché dérive de
1133
+ * l'appartenance au tableau du groupe, et le (dé)cochage met à jour ce tableau. `value` est
1134
+ * alors ignoré (c'est le groupe qui détient la sélection).
872
1135
  *
873
1136
  * Libellé : `label` (texte) par défaut ; un contenu projeté le remplace visuellement pour les
874
1137
  * rendus riches (carte/option), `ariaLabel` portant alors le nom accessible si besoin.
@@ -880,8 +1143,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
880
1143
  *
881
1144
  * <!-- Dans un groupe -->
882
1145
  * <kt-checkbox-group label="Centres d'intérêt" [(value)]="interests">
883
- * <kt-checkbox [value]="'sport'" label="Sport" />
884
- * <kt-checkbox [value]="'musique'" label="Musique" />
1146
+ * <kt-checkbox [optionValue]="'sport'" label="Sport" />
1147
+ * <kt-checkbox [optionValue]="'musique'" label="Musique" />
885
1148
  * </kt-checkbox-group>
886
1149
  * ```
887
1150
  */
@@ -889,9 +1152,14 @@ class KtCheckbox {
889
1152
  config = inject(KT_FIELD_CONFIG, { optional: true });
890
1153
  /** Groupe parent optionnel : présent ⇒ la case appartient à un `kt-checkbox-group`. */
891
1154
  group = inject(KtCheckboxGroup, { optional: true });
892
- /** Autonome : état coché (`boolean`, two-way). En groupe : la valeur d'option représentée. */
1155
+ /** État coché (`boolean`, two-way) en mode autonome. Ignoré en mode groupe : c'est alors
1156
+ `optionValue` + le tableau du groupe qui font foi. @default false */
893
1157
  value = model(false, /* @ts-ignore */
894
1158
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1159
+ /** Mode groupe uniquement : valeur d'option contribuée au tableau du `kt-checkbox-group` parent
1160
+ quand la case est cochée. Sans effet en mode autonome. @default undefined */
1161
+ optionValue = input(/* @ts-ignore */
1162
+ ...(ngDevMode ? [undefined, { debugName: "optionValue" }] : /* istanbul ignore next */ []));
895
1163
  /** Tri-état visuel (parent d'une arborescence) : propriété DOM `indeterminate`, pas un attribut. */
896
1164
  indeterminate = input(false, { ...(ngDevMode ? { debugName: "indeterminate" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
897
1165
  // --- État poussé par [formField] (mode autonome) ---
@@ -901,13 +1169,13 @@ class KtCheckbox {
901
1169
  /** Désactive la case (héritable du groupe parent). @default false */
902
1170
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
903
1171
  /** Marque la case comme invalide (combiné à `touched`/`dirty` via l'`errorMatcher`). @default false */
904
- invalid = input(false, /* @ts-ignore */
905
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
1172
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
906
1173
  /** Affiche l'astérisque requis et `aria-required`. @default false */
907
1174
  required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
908
1175
  /** État « modifié » (entre dans la logique d'affichage des erreurs). @default false */
909
- dirty = input(false, /* @ts-ignore */
910
- ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
1176
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1177
+ /** Validation asynchrone en cours (poussé par `[field]`) : pose `aria-busy` + `data-pending`. @default false */
1178
+ pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
911
1179
  /** Erreurs de validation à afficher sous la case. @default [] */
912
1180
  errors = input([], /* @ts-ignore */
913
1181
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
@@ -931,9 +1199,10 @@ class KtCheckbox {
931
1199
  errorMatcher = input(/* @ts-ignore */
932
1200
  ...(ngDevMode ? [undefined, { debugName: "errorMatcher" }] : /* istanbul ignore next */ []));
933
1201
  /** Afficher toutes les erreurs au lieu de la première seule. @default KT_FIELD_CONFIG.showAllErrors ?? false */
934
- showAllErrors = input(this.config?.showAllErrors ?? false, /* @ts-ignore */
935
- ...(ngDevMode ? [{ debugName: "showAllErrors" }] : /* istanbul ignore next */ []));
1202
+ showAllErrors = input(this.config?.showAllErrors ?? false, { ...(ngDevMode ? { debugName: "showAllErrors" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
936
1203
  inputEl = viewChild.required('input');
1204
+ el = inject(ElementRef);
1205
+ errorResolver = inject(KtFieldErrorResolver);
937
1206
  idGen = inject(KtIdGenerator);
938
1207
  uid = this.idGen.generateId('checkbox');
939
1208
  baseId = computed(() => this.id() ?? `kt-checkbox-${this.uid}`, /* @ts-ignore */
@@ -944,7 +1213,7 @@ class KtCheckbox {
944
1213
  ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
945
1214
  /** Coché : appartenance au groupe le cas échéant, sinon l'état booléen propre.
946
1215
  (Nommé `isChecked` car `FormValueControl` réserve la propriété `checked`.) */
947
- isChecked = computed(() => this.group ? this.group.isSelected(this.value()) : Boolean(this.value()), /* @ts-ignore */
1216
+ isChecked = computed(() => this.group ? this.group.isSelected(this.optionValue()) : this.value(), /* @ts-ignore */
948
1217
  ...(ngDevMode ? [{ debugName: "isChecked" }] : /* istanbul ignore next */ []));
949
1218
  /** Désactivé : hérité du groupe le cas échéant. */
950
1219
  isDisabled = computed(() => (this.group?.disabled() ?? false) || this.disabled(), /* @ts-ignore */
@@ -953,7 +1222,10 @@ class KtCheckbox {
953
1222
  ...(ngDevMode ? [{ debugName: "matcher" }] : /* istanbul ignore next */ []));
954
1223
  showInvalid = computed(() => this.matcher()({ invalid: this.invalid(), touched: this.touched(), dirty: this.dirty() }), /* @ts-ignore */
955
1224
  ...(ngDevMode ? [{ debugName: "showInvalid" }] : /* istanbul ignore next */ []));
956
- displayedErrors = computed(() => this.showAllErrors() ? this.errors() : this.errors().slice(0, 1), /* @ts-ignore */
1225
+ /** Erreurs résolues (messages par défaut appliqués, suppressions `message: ''` écartées). */
1226
+ resolvedErrors = computed(() => this.errorResolver.resolveAll(this.errors()), /* @ts-ignore */
1227
+ ...(ngDevMode ? [{ debugName: "resolvedErrors" }] : /* istanbul ignore next */ []));
1228
+ displayedErrors = computed(() => this.showAllErrors() ? this.resolvedErrors() : this.resolvedErrors().slice(0, 1), /* @ts-ignore */
957
1229
  ...(ngDevMode ? [{ debugName: "displayedErrors" }] : /* istanbul ignore next */ []));
958
1230
  resolvedAriaLabel = computed(() => this.ariaLabel() ?? null, /* @ts-ignore */
959
1231
  ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
@@ -961,7 +1233,7 @@ class KtCheckbox {
961
1233
  const ids = [];
962
1234
  if (this.hint() && !this.showInvalid())
963
1235
  ids.push(this.hintId());
964
- if (this.showInvalid() && this.errors().length > 0)
1236
+ if (this.showInvalid() && this.resolvedErrors().length > 0)
965
1237
  ids.push(this.errorId());
966
1238
  return ids.length ? ids.join(' ') : null;
967
1239
  }, /* @ts-ignore */
@@ -971,13 +1243,30 @@ class KtCheckbox {
971
1243
  effect(() => {
972
1244
  this.inputEl().nativeElement.indeterminate = this.indeterminate();
973
1245
  });
1246
+ // Garde-fou a11y (dev, navigateur uniquement) : nom accessible = `label`, `ariaLabel` OU contenu
1247
+ // projeté. Sans aucun des trois, la case est annoncée vide (WCAG 4.1.2).
1248
+ afterNextRender(() => {
1249
+ if (!isDevMode() || this.label() || this.ariaLabel())
1250
+ return;
1251
+ const labelText = this.el.nativeElement
1252
+ .querySelector('.kt-checkbox__label')
1253
+ ?.textContent?.replace('*', '')
1254
+ .trim();
1255
+ if (!labelText) {
1256
+ console.warn('[ktCheckbox] sans `label`, `ariaLabel` ni contenu projeté : annoncée sans nom accessible (WCAG 4.1.2).');
1257
+ }
1258
+ });
1259
+ }
1260
+ /** Focus la case native (utilisé par Signal Forms `focusBoundControl`). */
1261
+ focus(options) {
1262
+ this.inputEl().nativeElement.focus(options);
974
1263
  }
975
1264
  onChange(event) {
976
1265
  if (this.isDisabled())
977
1266
  return;
978
1267
  const checked = event.target.checked;
979
1268
  if (this.group) {
980
- this.group.toggle(this.value(), checked);
1269
+ this.group.toggle(this.optionValue(), checked);
981
1270
  this.group.touched.set(true);
982
1271
  return;
983
1272
  }
@@ -988,11 +1277,12 @@ class KtCheckbox {
988
1277
  (this.group?.touched ?? this.touched).set(true);
989
1278
  }
990
1279
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
991
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtCheckbox, isStandalone: true, selector: "kt-checkbox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
1280
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtCheckbox, isStandalone: true, selector: "kt-checkbox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
992
1281
  <div
993
1282
  class="kt-checkbox-field"
994
1283
  [class.kt-checkbox-field--invalid]="showInvalid()"
995
1284
  [class.kt-checkbox-field--disabled]="isDisabled()"
1285
+ [attr.data-pending]="pending() ? '' : null"
996
1286
  >
997
1287
  <label class="kt-checkbox">
998
1288
  <input
@@ -1007,6 +1297,7 @@ class KtCheckbox {
1007
1297
  [attr.aria-describedby]="describedBy()"
1008
1298
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
1009
1299
  [attr.aria-required]="required() ? 'true' : null"
1300
+ [attr.aria-busy]="pending() ? 'true' : null"
1010
1301
  (change)="onChange($event)"
1011
1302
  (blur)="onBlur()"
1012
1303
  />
@@ -1038,6 +1329,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1038
1329
  class="kt-checkbox-field"
1039
1330
  [class.kt-checkbox-field--invalid]="showInvalid()"
1040
1331
  [class.kt-checkbox-field--disabled]="isDisabled()"
1332
+ [attr.data-pending]="pending() ? '' : null"
1041
1333
  >
1042
1334
  <label class="kt-checkbox">
1043
1335
  <input
@@ -1052,6 +1344,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1052
1344
  [attr.aria-describedby]="describedBy()"
1053
1345
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
1054
1346
  [attr.aria-required]="required() ? 'true' : null"
1347
+ [attr.aria-busy]="pending() ? 'true' : null"
1055
1348
  (change)="onChange($event)"
1056
1349
  (blur)="onBlur()"
1057
1350
  />
@@ -1075,7 +1368,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1075
1368
  </div>
1076
1369
  </div>
1077
1370
  `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-checkbox-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-checkbox{display:flex;align-items:center;gap:var(--field-control-gap, .5rem);inline-size:100%;box-sizing:border-box;min-block-size:var(--checkbox-target-size, 44px);padding:var(--checkbox-padding, 0);border-radius:inherit;cursor:pointer;-webkit-user-select:none;user-select:none}.kt-checkbox__input{appearance:none;-webkit-appearance:none;margin:0;flex:none;box-sizing:border-box;display:grid;place-content:center;font-size:var(--checkbox-size, 1.25rem);inline-size:1em;block-size:1em;border:var(--checkbox-border-width, 2px) solid var(--checkbox-border-color, var(--field-border-color, var(--kt-outline, #c4c7c5)));border-radius:var(--checkbox-radius, .25rem);background-color:var(--checkbox-bg, var(--field-bg, var(--kt-surface, #ffffff)));cursor:pointer;box-shadow:var(--checkbox-shadow, var(--field-shadow, none));transition:var( --checkbox-transition, background-color .15s cubic-bezier(.4, 0, .2, 1), border-color .15s cubic-bezier(.4, 0, .2, 1), box-shadow .2s ease )}.kt-checkbox__input:before{content:\"\";inline-size:.65em;block-size:.65em;transform:scale(0);transform-origin:center;box-shadow:inset 1em 1em var(--checkbox-mark-color, #ffffff);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0%,43% 62%);transition:var(--checkbox-mark-transition, transform .12s ease-in)}.kt-checkbox__input:checked:before,.kt-checkbox__input:indeterminate:before{transform:scale(1);animation:var(--checkbox-mark-animation, kt-checkbox-pop .22s cubic-bezier(.34, 1.56, .64, 1))}.kt-checkbox__input:indeterminate:before{clip-path:inset(42% 12%)}@keyframes kt-checkbox-pop{0%{transform:scale(0)}}.kt-checkbox__input:checked,.kt-checkbox__input:indeterminate{background-color:var(--checkbox-bg-checked, var(--kt-primary, #0842a0));border-color:var(--checkbox-border-color-checked, var(--checkbox-bg-checked, var(--kt-primary, #0842a0)));box-shadow:var( --checkbox-shadow-checked, 0 0 8px color-mix(in srgb, var(--checkbox-bg-checked, var(--kt-primary, #0842a0)) 50%, transparent) )}.kt-checkbox__input:hover:not(:disabled){border-color:var( --checkbox-border-color-hover, var(--field-border-color-hover, var(--kt-outline-strong, #5f6368)) );box-shadow:var( --checkbox-shadow-hover, var(--field-shadow-hover, var(--checkbox-shadow, var(--field-shadow, none))) )}.kt-checkbox__input:checked:hover:not(:disabled),.kt-checkbox__input:indeterminate:hover:not(:disabled){background-color:var(--checkbox-bg-checked-hover, color-mix(in srgb, var(--kt-primary, #0842a0) 90%, black));border-color:var(--checkbox-bg-checked-hover, color-mix(in srgb, var(--kt-primary, #0842a0) 90%, black))}.kt-checkbox__input:focus-visible{outline:var(--kt-focus-ring-width, 2px) solid var(--kt-focus-ring-color, #0842a0);outline-offset:var(--checkbox-focus-ring-offset, 2px);box-shadow:var(--checkbox-shadow-focus, var(--field-shadow-focus, var(--field-shadow, none)))}.kt-checkbox__input:disabled{cursor:not-allowed;opacity:.5}.kt-checkbox-field--disabled .kt-checkbox{cursor:not-allowed}.kt-checkbox-field--invalid .kt-checkbox__input:not(:checked):not(:indeterminate){border-color:var(--field-error-color, var(--kt-danger, #8c1d18))}.kt-checkbox__label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-checkbox-field--disabled .kt-checkbox__label{color:var(--field-hint-color, #5f6368);opacity:.6}.kt-checkbox__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-checkbox-hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-checkbox-error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-checkbox-error-message{animation:var(--field-error-animation, none)}@media(forced-colors:active){.kt-checkbox__input{border-color:CanvasText;background-color:Canvas}.kt-checkbox__input:checked,.kt-checkbox__input:indeterminate{background-color:Highlight;border-color:Highlight}.kt-checkbox__input:before{box-shadow:inset 1em 1em HighlightText}.kt-checkbox__input:disabled{border-color:GrayText}.kt-checkbox__input:focus-visible{outline:2px solid CanvasText;outline-offset:2px}}@media(prefers-reduced-motion:reduce){.kt-checkbox__input,.kt-checkbox__input:before{transition:none;animation:none}.kt-checkbox-error-message{animation:none}}}\n"] }]
1078
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
1371
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
1079
1372
 
1080
1373
  /**
1081
1374
  * Groupe de boutons radio (Radio Group) conforme à l'ARIA APG / RGAA.
@@ -1090,9 +1383,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1090
1383
  * @example
1091
1384
  * ```html
1092
1385
  * <kt-radio-group label="Civilité" [(value)]="civility" required>
1093
- * <kt-radio [value]="'mme'" label="Madame" />
1094
- * <kt-radio [value]="'m'" label="Monsieur" />
1095
- * <kt-radio [value]="'autre'" label="Autre" />
1386
+ * <kt-radio [optionValue]="'mme'" label="Madame" />
1387
+ * <kt-radio [optionValue]="'m'" label="Monsieur" />
1388
+ * <kt-radio [optionValue]="'autre'" label="Autre" />
1096
1389
  * </kt-radio-group>
1097
1390
  * ```
1098
1391
  */
@@ -1108,13 +1401,13 @@ class KtRadioGroup {
1108
1401
  /** Désactive le groupe entier (hérité par chaque radio enfant). @default false */
1109
1402
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1110
1403
  /** Marque le groupe comme invalide (combiné à `touched`/`dirty` via l'`errorMatcher`). @default false */
1111
- invalid = input(false, /* @ts-ignore */
1112
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
1404
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1113
1405
  /** Affiche l'astérisque requis et `aria-required` sur le groupe. @default false */
1114
1406
  required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1115
1407
  /** État « modifié » (entre dans la logique d'affichage des erreurs). @default false */
1116
- dirty = input(false, /* @ts-ignore */
1117
- ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
1408
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1409
+ /** Validation asynchrone en cours (poussé par `[field]`) : pose `aria-busy` + `data-pending`. @default false */
1410
+ pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1118
1411
  /** Erreurs de validation à afficher sous le groupe. @default [] */
1119
1412
  errors = input([], /* @ts-ignore */
1120
1413
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
@@ -1138,11 +1431,12 @@ class KtRadioGroup {
1138
1431
  errorMatcher = input(/* @ts-ignore */
1139
1432
  ...(ngDevMode ? [undefined, { debugName: "errorMatcher" }] : /* istanbul ignore next */ []));
1140
1433
  /** Afficher toutes les erreurs au lieu de la première seule. @default KT_FIELD_CONFIG.showAllErrors ?? false */
1141
- showAllErrors = input(this.config?.showAllErrors ?? false, /* @ts-ignore */
1142
- ...(ngDevMode ? [{ debugName: "showAllErrors" }] : /* istanbul ignore next */ []));
1434
+ showAllErrors = input(this.config?.showAllErrors ?? false, { ...(ngDevMode ? { debugName: "showAllErrors" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1143
1435
  /** Égalité des valeurs en mode objet (défaut : identité `===`). Seul rescapé du contrat select. */
1144
1436
  compareWith = input(/* @ts-ignore */
1145
1437
  ...(ngDevMode ? [undefined, { debugName: "compareWith" }] : /* istanbul ignore next */ []));
1438
+ el = inject(ElementRef);
1439
+ errorResolver = inject(KtFieldErrorResolver);
1146
1440
  idGen = inject(KtIdGenerator);
1147
1441
  uid = this.idGen.generateId('radio-group');
1148
1442
  baseId = computed(() => this.id() ?? `kt-radio-group-${this.uid}`, /* @ts-ignore */
@@ -1160,7 +1454,10 @@ class KtRadioGroup {
1160
1454
  ...(ngDevMode ? [{ debugName: "matcher" }] : /* istanbul ignore next */ []));
1161
1455
  showInvalid = computed(() => this.matcher()({ invalid: this.invalid(), touched: this.touched(), dirty: this.dirty() }), /* @ts-ignore */
1162
1456
  ...(ngDevMode ? [{ debugName: "showInvalid" }] : /* istanbul ignore next */ []));
1163
- displayedErrors = computed(() => this.showAllErrors() ? this.errors() : this.errors().slice(0, 1), /* @ts-ignore */
1457
+ /** Erreurs résolues (messages par défaut appliqués, suppressions `message: ''` écartées). */
1458
+ resolvedErrors = computed(() => this.errorResolver.resolveAll(this.errors()), /* @ts-ignore */
1459
+ ...(ngDevMode ? [{ debugName: "resolvedErrors" }] : /* istanbul ignore next */ []));
1460
+ displayedErrors = computed(() => this.showAllErrors() ? this.resolvedErrors() : this.resolvedErrors().slice(0, 1), /* @ts-ignore */
1164
1461
  ...(ngDevMode ? [{ debugName: "displayedErrors" }] : /* istanbul ignore next */ []));
1165
1462
  resolvedAriaLabel = computed(() => this.ariaLabel() ?? null, /* @ts-ignore */
1166
1463
  ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
@@ -1168,20 +1465,28 @@ class KtRadioGroup {
1168
1465
  const ids = [];
1169
1466
  if (this.hint() && !this.showInvalid())
1170
1467
  ids.push(this.hintId());
1171
- if (this.showInvalid() && this.errors().length > 0)
1468
+ if (this.showInvalid() && this.resolvedErrors().length > 0)
1172
1469
  ids.push(this.errorId());
1173
1470
  return ids.length ? ids.join(' ') : null;
1174
1471
  }, /* @ts-ignore */
1175
1472
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1176
1473
  comparator = computed(() => this.compareWith() ?? ((a, b) => a === b), /* @ts-ignore */
1177
1474
  ...(ngDevMode ? [{ debugName: "comparator" }] : /* istanbul ignore next */ []));
1178
- /** Le `value` d'un enfant correspond-il à la sélection courante ? (lu dans le `computed` enfant) */
1475
+ /** L'`optionValue` d'un enfant correspond-il à la sélection courante ? (lu dans le `computed` enfant) */
1179
1476
  isSelected(radioValue) {
1180
1477
  const v = this.value();
1181
1478
  if (v === null || v === undefined)
1182
1479
  return false;
1183
1480
  return this.comparator()(radioValue, v);
1184
1481
  }
1482
+ /** Focus l'option pertinente (utilisé par Signal Forms `focusBoundControl`) : le radio coché
1483
+ s'il existe, sinon le premier radio activable — conforme au roving tabindex natif. */
1484
+ focus(options) {
1485
+ const root = this.el.nativeElement;
1486
+ const target = root.querySelector('input[type="radio"]:checked') ??
1487
+ root.querySelector('input[type="radio"]:not([disabled])');
1488
+ target?.focus(options);
1489
+ }
1185
1490
  /** Commit d'une sélection émis par un enfant au `change` natif. */
1186
1491
  select(radioValue) {
1187
1492
  if (this.disabled())
@@ -1190,7 +1495,7 @@ class KtRadioGroup {
1190
1495
  this.touched.set(true);
1191
1496
  }
1192
1497
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
1193
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtRadioGroup, isStandalone: true, selector: "kt-radio-group", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: `
1498
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtRadioGroup, isStandalone: true, selector: "kt-radio-group", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, showAllErrors: { classPropertyName: "showAllErrors", publicName: "showAllErrors", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: `
1194
1499
  <div
1195
1500
  class="kt-radio-group-field"
1196
1501
  [class.kt-radio-group-field--invalid]="showInvalid()"
@@ -1201,6 +1506,8 @@ class KtRadioGroup {
1201
1506
  [attr.aria-describedby]="describedBy()"
1202
1507
  [attr.aria-required]="required() ? 'true' : null"
1203
1508
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
1509
+ [attr.aria-busy]="pending() ? 'true' : null"
1510
+ [attr.data-pending]="pending() ? '' : null"
1204
1511
  >
1205
1512
  @if (label(); as labelText) {
1206
1513
  <span [id]="labelId()" class="kt-radio-group__legend">
@@ -1226,7 +1533,7 @@ class KtRadioGroup {
1226
1533
  }
1227
1534
  </div>
1228
1535
  </div>
1229
- `, isInline: true, styles: ["@layer kt-aaa.components{:host{display:block}.kt-radio-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-radio-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-radio-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-radio-group__options{display:flex;flex-direction:var(--radio-group-direction, column);flex-wrap:wrap;gap:var(--radio-group-gap, .25rem)}.kt-radio-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-radio-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-radio-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-radio-group__error-message{animation:none}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1536
+ `, isInline: true, styles: ["@layer kt-aaa.components{:host{display:block}.kt-radio-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-radio-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-radio-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-radio-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-radio-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-radio-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-radio-group__error-message{animation:none}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1230
1537
  }
1231
1538
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtRadioGroup, decorators: [{
1232
1539
  type: Component,
@@ -1241,6 +1548,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1241
1548
  [attr.aria-describedby]="describedBy()"
1242
1549
  [attr.aria-required]="required() ? 'true' : null"
1243
1550
  [attr.aria-invalid]="showInvalid() ? 'true' : null"
1551
+ [attr.aria-busy]="pending() ? 'true' : null"
1552
+ [attr.data-pending]="pending() ? '' : null"
1244
1553
  >
1245
1554
  @if (label(); as labelText) {
1246
1555
  <span [id]="labelId()" class="kt-radio-group__legend">
@@ -1266,8 +1575,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1266
1575
  }
1267
1576
  </div>
1268
1577
  </div>
1269
- `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-radio-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-radio-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-radio-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-radio-group__options{display:flex;flex-direction:var(--radio-group-direction, column);flex-wrap:wrap;gap:var(--radio-group-gap, .25rem)}.kt-radio-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-radio-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-radio-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-radio-group__error-message{animation:none}}}\n"] }]
1270
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }] } });
1578
+ `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-radio-group-field{display:flex;flex-direction:column;gap:var(--field-gap, .375rem)}.kt-radio-group__legend{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-radio-group__required{margin-inline-start:.125rem;color:var(--field-required-color, var(--kt-danger, #8c1d18))}.kt-radio-group__hint{margin:0;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}.kt-radio-group__error{margin:0;display:flex;flex-direction:column;font-size:var(--field-hint-font-size, .8125rem);color:var(--field-error-color, #8c1d18)}.kt-radio-group__error-message{animation:var(--field-error-animation, none)}@media(prefers-reduced-motion:reduce){.kt-radio-group__error-message{animation:none}}}\n"] }]
1579
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], showAllErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAllErrors", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }] } });
1271
1580
 
1272
1581
  /**
1273
1582
  * Bouton radio individuel (enfant déclaratif de `kt-radio-group`).
@@ -1278,16 +1587,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1278
1587
  * @example
1279
1588
  * ```html
1280
1589
  * <kt-radio-group label="Civilité" [(value)]="civility">
1281
- * <kt-radio [value]="'mme'" label="Madame" />
1282
- * <kt-radio [value]="'m'" label="Monsieur" />
1590
+ * <kt-radio [optionValue]="'mme'" label="Madame" />
1591
+ * <kt-radio [optionValue]="'m'" label="Monsieur" />
1283
1592
  * </kt-radio-group>
1284
1593
  * ```
1285
1594
  */
1286
1595
  class KtRadio {
1287
1596
  group = inject(KtRadioGroup);
1288
- /** Valeur d'option représentée par ce radio (sélectionnée ⇒ devient la valeur du groupe). @default (requis) */
1289
- value = input.required(/* @ts-ignore */
1290
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1597
+ /** Valeur d'option représentée par ce radio (sélectionnée ⇒ devient la valeur du groupe). Nommée
1598
+ `optionValue` pour s'aligner sur `kt-checkbox` (où `value` = état coché). @default (requis) */
1599
+ optionValue = input.required(/* @ts-ignore */
1600
+ ...(ngDevMode ? [{ debugName: "optionValue" }] : /* istanbul ignore next */ []));
1291
1601
  /** Texte du libellé (remplacé visuellement par un contenu projeté). @default undefined */
1292
1602
  label = input(/* @ts-ignore */
1293
1603
  ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
@@ -1308,15 +1618,32 @@ class KtRadio {
1308
1618
  ...(ngDevMode ? [{ debugName: "baseId" }] : /* istanbul ignore next */ []));
1309
1619
  hintId = computed(() => `${this.baseId()}-hint`, /* @ts-ignore */
1310
1620
  ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1311
- checked = computed(() => this.group.isSelected(this.value()), /* @ts-ignore */
1621
+ checked = computed(() => this.group.isSelected(this.optionValue()), /* @ts-ignore */
1312
1622
  ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
1313
1623
  isDisabled = computed(() => this.group.disabled() || this.disabled(), /* @ts-ignore */
1314
1624
  ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
1625
+ el = inject(ElementRef);
1626
+ constructor() {
1627
+ // Garde-fou a11y (dev, navigateur uniquement) : nom accessible = `label`, `ariaLabel` OU contenu
1628
+ // projeté. Sans aucun des trois, le radio est annoncé vide (WCAG 4.1.2).
1629
+ afterNextRender(() => {
1630
+ if (!isDevMode() || this.label() || this.ariaLabel())
1631
+ return;
1632
+ const labelText = this.el.nativeElement.querySelector('.kt-radio__label')?.textContent?.trim();
1633
+ if (!labelText) {
1634
+ console.warn('[ktRadio] sans `label`, `ariaLabel` ni contenu projeté : annoncé sans nom accessible (WCAG 4.1.2).');
1635
+ }
1636
+ });
1637
+ }
1315
1638
  onChange() {
1316
- this.group.select(this.value());
1639
+ // Garde défensive : un input natif `disabled` n'émet pas `change`, mais on protège un appel
1640
+ // programmatique éventuel (cohérence avec switch/checkbox qui gardent leur commit).
1641
+ if (this.isDisabled())
1642
+ return;
1643
+ this.group.select(this.optionValue());
1317
1644
  }
1318
1645
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtRadio, deps: [], target: i0.ɵɵFactoryTarget.Component });
1319
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtRadio, isStandalone: true, selector: "kt-radio", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1646
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtRadio, isStandalone: true, selector: "kt-radio", inputs: { optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1320
1647
  <label class="kt-radio">
1321
1648
  <input
1322
1649
  type="radio"
@@ -1365,9 +1692,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1365
1692
  <p [id]="hintId()" class="kt-radio__hint">{{ hintText }}</p>
1366
1693
  }
1367
1694
  `, styles: ["@layer kt-aaa.components{:host{display:block}.kt-radio{display:flex;align-items:center;gap:var(--field-control-gap, .5rem);inline-size:100%;box-sizing:border-box;min-block-size:var(--radio-target-size, 44px);padding:var(--radio-padding, 0);border-radius:inherit;cursor:pointer;-webkit-user-select:none;user-select:none}.kt-radio__input{appearance:none;-webkit-appearance:none;margin:0;flex:none;box-sizing:border-box;display:grid;place-content:center;font-size:var(--radio-size, 1.25rem);inline-size:1em;block-size:1em;border:var(--radio-border-width, 2px) solid var(--radio-border-color, var(--field-border-color, var(--kt-outline, #c4c7c5)));border-radius:50%;background-color:var(--radio-bg, var(--field-bg, var(--kt-surface, #ffffff)));cursor:pointer;box-shadow:var(--radio-shadow, var(--field-shadow, none));transition:var( --radio-transition, background-color .15s cubic-bezier(.4, 0, .2, 1), border-color .15s cubic-bezier(.4, 0, .2, 1), box-shadow .2s ease )}.kt-radio__input:before{content:\"\";inline-size:.5em;block-size:.5em;border-radius:50%;transform:scale(0);transform-origin:center;box-shadow:inset 1em 1em var(--radio-dot-color, #ffffff);transition:var(--radio-dot-transition, transform .12s ease-in)}.kt-radio__input:checked:before{transform:scale(1);animation:var(--radio-dot-animation, kt-radio-pop .22s cubic-bezier(.34, 1.56, .64, 1))}@keyframes kt-radio-pop{0%{transform:scale(0)}}.kt-radio__input:checked{background-color:var(--radio-bg-checked, var(--kt-primary, #0842a0));border-color:var(--radio-border-color-checked, var(--radio-bg-checked, var(--kt-primary, #0842a0)));box-shadow:var( --radio-shadow-checked, 0 0 8px color-mix(in srgb, var(--radio-bg-checked, var(--kt-primary, #0842a0)) 50%, transparent) )}.kt-radio__input:hover:not(:disabled){border-color:var(--radio-border-color-hover, var(--field-border-color-hover, var(--kt-outline-strong, #5f6368)));box-shadow:var(--radio-shadow-hover, var(--field-shadow-hover, var(--radio-shadow, var(--field-shadow, none))))}.kt-radio__input:checked:hover:not(:disabled){background-color:var(--radio-bg-checked-hover, color-mix(in srgb, var(--kt-primary, #0842a0) 90%, black));border-color:var(--radio-bg-checked-hover, color-mix(in srgb, var(--kt-primary, #0842a0) 90%, black))}.kt-radio__input:focus-visible{outline:var(--kt-focus-ring-width, 2px) solid var(--kt-focus-ring-color, #0842a0);outline-offset:var(--radio-focus-ring-offset, 2px);box-shadow:var(--radio-shadow-focus, var(--field-shadow-focus, var(--field-shadow, none)))}.kt-radio__input:disabled{cursor:not-allowed;opacity:.5}.kt-radio:has(.kt-radio__input:disabled){cursor:not-allowed}.kt-radio__label{font-size:var(--field-label-font-size, .875rem);font-weight:var(--field-label-weight, 500);color:var(--field-label-color, inherit)}.kt-radio:has(.kt-radio__input:disabled) .kt-radio__label{color:var(--field-hint-color, #5f6368);opacity:.6}.kt-radio__hint{margin:0;margin-inline-start:calc(var(--radio-size, 1.25rem) + var(--field-control-gap, .5rem));font-size:var(--field-hint-font-size, .8125rem);color:var(--field-hint-color, #474747)}@media(forced-colors:active){.kt-radio__input{border-color:CanvasText;background-color:Canvas}.kt-radio__input:checked{border-color:Highlight;background-color:Canvas}.kt-radio__input:before{box-shadow:inset 1em 1em Highlight}.kt-radio__input:disabled{border-color:GrayText}.kt-radio__input:focus-visible{outline:2px solid CanvasText;outline-offset:2px}}@media(prefers-reduced-motion:reduce){.kt-radio__input,.kt-radio__input:before{transition:none;animation:none}}}\n"] }]
1368
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
1695
+ }], ctorParameters: () => [], propDecorators: { optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
1369
1696
 
1370
1697
  const KT_SELECT_CONFIG = new InjectionToken('KT_SELECT_CONFIG');
1698
+ /**
1699
+ * Source UNIQUE des défauts (anglais neutre) de la config select / multi-select. Constitue le 3e
1700
+ * maillon de la résolution `input() ?? KT_SELECT_CONFIG ?? DEFAULT_KT_SELECT_CONFIG`, jusqu'ici
1701
+ * éparpillé entre `base-select.ts`, `multi-select.ts` et des fonctions locales. Centralisé ici pour
1702
+ * éviter toute dérive et garantir la complétude des traductions (cf. test de complétude i18n).
1703
+ */
1704
+ const DEFAULT_KT_SELECT_CONFIG = {
1705
+ placeholder: '',
1706
+ emptyText: 'No options',
1707
+ closeLabel: 'Close',
1708
+ closeOnSelect: true,
1709
+ filterPlaceholder: '',
1710
+ filterLabel: 'Filter options',
1711
+ filterResultsText: (count) => (count === 1 ? '1 result' : `${count} results`),
1712
+ removeItemLabel: (itemLabel) => `Remove ${itemLabel}`,
1713
+ selectedItemsLabel: (fieldLabel) => (fieldLabel ? `Selected items for ${fieldLabel}` : 'Selected items'),
1714
+ selectionSummaryText: (count) => `${count} items selected`,
1715
+ itemRemovedText: (itemLabel) => `${itemLabel} removed`,
1716
+ selectionCountText: (count) => (count === 1 ? '1 selected' : `${count} selected`),
1717
+ selectAllLabel: 'Select all',
1718
+ clearAllLabel: 'Clear all',
1719
+ moreChipsLabel: (hiddenCount) => `+${hiddenCount} more`,
1720
+ lessChipsLabel: 'Show less',
1721
+ truncatedResultsText: (max, total) => `Showing first ${max} results of ${total}. Refine your search to see more.`,
1722
+ truncatedResultsAnnouncement: (max, total) => `${max} results displayed out of ${total}. Refine your search to see more.`,
1723
+ };
1371
1724
 
1372
1725
  /** Normalise une clé/fonction en fonction d'accès, avec repli si non fourni. */
1373
1726
  function accessor(key, fallback) {
@@ -1406,16 +1759,6 @@ function normalizeForFilter(s) {
1406
1759
  .replace(/[\u0300-\u036f]/g, '')
1407
1760
  .toLowerCase();
1408
1761
  }
1409
- /** Annonce par défaut du nombre de résultats (lib neutre : anglais, comme `emptyText`). */
1410
- function defaultFilterResultsText(count) {
1411
- return count === 1 ? '1 result' : `${count} results`;
1412
- }
1413
- function defaultTruncatedResultsText(max, total) {
1414
- return `Showing first ${max} results of ${total}. Refine your search to see more.`;
1415
- }
1416
- function defaultTruncatedResultsAnnouncement(max, total) {
1417
- return `${max} results displayed out of ${total}. Refine your search to see more.`;
1418
- }
1419
1762
  /** Base partagée des sélecteurs à liste fermée (Select single et MultiSelect), bâtis sur
1420
1763
  `@angular/aria` (Combobox + Listbox) : options et dérivation (clé/fonction), état poussé
1421
1764
  par `[formField]`, popup Popover responsive (dropdown ancré desktop ↔ bottom-sheet
@@ -1429,6 +1772,7 @@ function defaultTruncatedResultsAnnouncement(max, total) {
1429
1772
  ou la clé extraite quand `optionValue` est fourni). */
1430
1773
  class KtBaseSelect {
1431
1774
  fieldConfig = inject(KT_FIELD_CONFIG, { optional: true });
1775
+ errorResolver = inject(KtFieldErrorResolver);
1432
1776
  config = inject(KT_SELECT_CONFIG, { optional: true });
1433
1777
  host = inject(ElementRef);
1434
1778
  ngZone = inject(NgZone);
@@ -1461,20 +1805,17 @@ class KtBaseSelect {
1461
1805
  touched = model(false, /* @ts-ignore */
1462
1806
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
1463
1807
  /** Champ désactivé (trigger inerte). Poussé par `[formField]`. @default false */
1464
- disabled = input(false, /* @ts-ignore */
1465
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
1808
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1466
1809
  /** Champ en lecture seule (sélection figée, valeur affichée). Poussé par `[formField]`. @default false */
1467
- readonly = input(false, /* @ts-ignore */
1468
- ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1810
+ readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1469
1811
  /** Le champ est-il invalide ? Pilote l'affichage des erreurs. Poussé par `[formField]`. @default false */
1470
- invalid = input(false, /* @ts-ignore */
1471
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
1812
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1472
1813
  /** Champ obligatoire (marqueur visuel/ARIA). Poussé par `[formField]`. @default false */
1473
- required = input(false, /* @ts-ignore */
1474
- ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
1814
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1475
1815
  /** Le champ a-t-il été modifié depuis sa valeur initiale ? Poussé par `[formField]`. @default false */
1476
- dirty = input(false, /* @ts-ignore */
1477
- ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
1816
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1817
+ /** Validation asynchrone en cours (poussé par `[field]`) : pose `aria-busy` + `data-pending`. @default false */
1818
+ pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1478
1819
  /** Erreurs de validation à afficher. Poussé par `[formField]`. @default [] */
1479
1820
  errors = input([], /* @ts-ignore */
1480
1821
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
@@ -1506,11 +1847,18 @@ class KtBaseSelect {
1506
1847
  /** Quand afficher l'erreur ; surcharge KT_FIELD_CONFIG et le défaut (`invalid && touched`). @default undefined */
1507
1848
  errorMatcher = input(/* @ts-ignore */
1508
1849
  ...(ngDevMode ? [undefined, { debugName: "errorMatcher" }] : /* istanbul ignore next */ []));
1850
+ /** Apparence du chrome (`fill` | `outline`) ; non fournie ⇒ `KT_FIELD_CONFIG.appearance` ?? `'fill'`.
1851
+ @default undefined */
1852
+ appearance = input(/* @ts-ignore */
1853
+ ...(ngDevMode ? [undefined, { debugName: "appearance" }] : /* istanbul ignore next */ []));
1854
+ /** Politique du label flottant en outline (`auto` | `always`) ; non fournie ⇒ `KT_FIELD_CONFIG.floatLabel`
1855
+ ?? `'auto'`. @default undefined */
1856
+ floatLabel = input(/* @ts-ignore */
1857
+ ...(ngDevMode ? [undefined, { debugName: "floatLabel" }] : /* istanbul ignore next */ []));
1509
1858
  // --- Filtrage (champ de recherche dans le popup, opt-in) ---
1510
1859
  /** Affiche un champ de recherche en tête du popup (desktop) / de la sheet (téléphone).
1511
- Pour les listes longues ; le texte tapé ne sert qu'à filtrer (réinitialisé à la fermeture). */
1512
- filterable = input(false, /* @ts-ignore */
1513
- ...(ngDevMode ? [{ debugName: "filterable" }] : /* istanbul ignore next */ []));
1860
+ Pour les listes longues ; le texte tapé ne sert qu'à filtrer (réinitialisé à la fermeture). @default false */
1861
+ filterable = input(false, { ...(ngDevMode ? { debugName: "filterable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1514
1862
  /** Placeholder du champ de recherche du popup. Défaut : `KT_SELECT_CONFIG.filterPlaceholder` ou vide. */
1515
1863
  filterPlaceholder = input(/* @ts-ignore */
1516
1864
  ...(ngDevMode ? [undefined, { debugName: "filterPlaceholder" }] : /* istanbul ignore next */ []));
@@ -1563,27 +1911,28 @@ class KtBaseSelect {
1563
1911
  ...(ngDevMode ? [{ debugName: "matcher" }] : /* istanbul ignore next */ []));
1564
1912
  showInvalid = computed(() => this.matcher()({ invalid: this.invalid(), touched: this.touched(), dirty: this.dirty() }), /* @ts-ignore */
1565
1913
  ...(ngDevMode ? [{ debugName: "showInvalid" }] : /* istanbul ignore next */ []));
1566
- // --- Textes résolus (input > KT_SELECT_CONFIG > défaut lib neutre EN) ---
1567
- resolvedPlaceholder = computed(() => this.placeholder() ?? this.config?.placeholder ?? '', /* @ts-ignore */
1914
+ // --- Textes résolus (input > KT_SELECT_CONFIG > DEFAULT_KT_SELECT_CONFIG, défauts EN centralisés) ---
1915
+ resolvedPlaceholder = computed(() => this.placeholder() ?? this.config?.placeholder ?? DEFAULT_KT_SELECT_CONFIG.placeholder, /* @ts-ignore */
1568
1916
  ...(ngDevMode ? [{ debugName: "resolvedPlaceholder" }] : /* istanbul ignore next */ []));
1569
- resolvedEmptyText = computed(() => this.config?.emptyText ?? 'No options', /* @ts-ignore */
1917
+ resolvedEmptyText = computed(() => this.config?.emptyText ?? DEFAULT_KT_SELECT_CONFIG.emptyText, /* @ts-ignore */
1570
1918
  ...(ngDevMode ? [{ debugName: "resolvedEmptyText" }] : /* istanbul ignore next */ []));
1571
- resolvedCloseLabel = computed(() => this.config?.closeLabel ?? 'Close', /* @ts-ignore */
1919
+ resolvedCloseLabel = computed(() => this.config?.closeLabel ?? DEFAULT_KT_SELECT_CONFIG.closeLabel, /* @ts-ignore */
1572
1920
  ...(ngDevMode ? [{ debugName: "resolvedCloseLabel" }] : /* istanbul ignore next */ []));
1573
- resolvedFilterPlaceholder = computed(() => this.filterPlaceholder() ?? this.config?.filterPlaceholder ?? '', /* @ts-ignore */
1921
+ resolvedFilterPlaceholder = computed(() => this.filterPlaceholder() ?? this.config?.filterPlaceholder ?? DEFAULT_KT_SELECT_CONFIG.filterPlaceholder, /* @ts-ignore */
1574
1922
  ...(ngDevMode ? [{ debugName: "resolvedFilterPlaceholder" }] : /* istanbul ignore next */ []));
1575
- resolvedFilterLabel = computed(() => this.filterLabel() ?? this.config?.filterLabel ?? 'Filter options', /* @ts-ignore */
1923
+ resolvedFilterLabel = computed(() => this.filterLabel() ?? this.config?.filterLabel ?? DEFAULT_KT_SELECT_CONFIG.filterLabel, /* @ts-ignore */
1576
1924
  ...(ngDevMode ? [{ debugName: "resolvedFilterLabel" }] : /* istanbul ignore next */ []));
1577
- resolvedFilterResultsText = computed(() => this.config?.filterResultsText ?? defaultFilterResultsText, /* @ts-ignore */
1925
+ resolvedFilterResultsText = computed(() => this.config?.filterResultsText ?? DEFAULT_KT_SELECT_CONFIG.filterResultsText, /* @ts-ignore */
1578
1926
  ...(ngDevMode ? [{ debugName: "resolvedFilterResultsText" }] : /* istanbul ignore next */ []));
1579
- resolvedTruncatedResultsText = computed(() => this.truncatedResultsText() ?? this.config?.truncatedResultsText ?? defaultTruncatedResultsText, /* @ts-ignore */
1927
+ resolvedTruncatedResultsText = computed(() => this.truncatedResultsText() ?? this.config?.truncatedResultsText ?? DEFAULT_KT_SELECT_CONFIG.truncatedResultsText, /* @ts-ignore */
1580
1928
  ...(ngDevMode ? [{ debugName: "resolvedTruncatedResultsText" }] : /* istanbul ignore next */ []));
1581
1929
  resolvedTruncatedResultsAnnouncement = computed(() => this.truncatedResultsAnnouncement() ??
1582
1930
  this.config?.truncatedResultsAnnouncement ??
1583
- defaultTruncatedResultsAnnouncement, /* @ts-ignore */
1931
+ DEFAULT_KT_SELECT_CONFIG.truncatedResultsAnnouncement, /* @ts-ignore */
1584
1932
  ...(ngDevMode ? [{ debugName: "resolvedTruncatedResultsAnnouncement" }] : /* istanbul ignore next */ []));
1585
- // Field attend des { kind, message } ; on mappe les ValidationError.
1586
- fieldErrors = computed(() => this.errors().map((e) => ({ kind: e.kind, message: e.message })), /* @ts-ignore */
1933
+ // Field attend des { kind, message } ; on résout les messages par défaut et on écarte les
1934
+ // suppressions explicites (`message: ''`).
1935
+ fieldErrors = computed(() => this.errorResolver.resolveAll(this.errors()), /* @ts-ignore */
1587
1936
  ...(ngDevMode ? [{ debugName: "fieldErrors" }] : /* istanbul ignore next */ []));
1588
1937
  // --- Accès aux options ---
1589
1938
  labelAccessor = computed(() => accessor(this.optionLabel(), defaultLabel), /* @ts-ignore */
@@ -1839,6 +2188,16 @@ class KtBaseSelect {
1839
2188
  return;
1840
2189
  listbox.value.set(expected);
1841
2190
  }
2191
+ /** Focus le trigger natif (utilisé par Signal Forms `focusBoundControl`). */
2192
+ focus(options) {
2193
+ this.triggerEl()?.nativeElement.focus(options);
2194
+ }
2195
+ /** Referme le panneau et vide le filtre (utilisé par Signal Forms `reset`). La sélection du
2196
+ listbox dérive de `value()` (computed) et se resynchronise seule. */
2197
+ reset() {
2198
+ this.expanded.set(false);
2199
+ this.filterText.set('');
2200
+ }
1842
2201
  /** Desktop filtrable : le focus DOM vit dans le popup (champ/options) — on le rend au trigger
1843
2202
  à la fermeture, sinon il tombe sur `<body>`. (Écran compact : déjà géré par l'effect `compact`.) */
1844
2203
  refocusTriggerAfterFilterClose() {
@@ -1912,7 +2271,7 @@ class KtBaseSelect {
1912
2271
  }, durationMs + 50);
1913
2272
  }
1914
2273
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1915
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: KtBaseSelect, isStandalone: true, inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: false, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: false, transformFunction: null }, optionDisabled: { classPropertyName: "optionDisabled", publicName: "optionDisabled", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, 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 }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, filterFn: { classPropertyName: "filterFn", publicName: "filterFn", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleOptions: { classPropertyName: "maxVisibleOptions", publicName: "maxVisibleOptions", isSignal: true, isRequired: false, transformFunction: null }, truncatedResultsText: { classPropertyName: "truncatedResultsText", publicName: "truncatedResultsText", isSignal: true, isRequired: false, transformFunction: null }, truncatedResultsAnnouncement: { classPropertyName: "truncatedResultsAnnouncement", publicName: "truncatedResultsAnnouncement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { helpClick: "helpClick", touched: "touchedChange" }, host: { listeners: { "keydown": "onHostKeydown($event)" } }, viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "popupEl", first: true, predicate: ["popup"], descendants: true, isSignal: true }, { propertyName: "filterInputEl", first: true, predicate: ["filterInput"], descendants: true, isSignal: true }, { propertyName: "listboxEl", first: true, predicate: ["listboxEl"], descendants: true, isSignal: true }, { propertyName: "listboxDir", first: true, predicate: Listbox, descendants: true, isSignal: true }, { propertyName: "comboboxDir", first: true, predicate: Combobox, descendants: true, isSignal: true }], ngImport: i0 });
2274
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: KtBaseSelect, isStandalone: true, inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: false, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: false, transformFunction: null }, optionDisabled: { classPropertyName: "optionDisabled", publicName: "optionDisabled", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, 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 }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, errorMatcher: { classPropertyName: "errorMatcher", publicName: "errorMatcher", 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 }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, filterFn: { classPropertyName: "filterFn", publicName: "filterFn", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleOptions: { classPropertyName: "maxVisibleOptions", publicName: "maxVisibleOptions", isSignal: true, isRequired: false, transformFunction: null }, truncatedResultsText: { classPropertyName: "truncatedResultsText", publicName: "truncatedResultsText", isSignal: true, isRequired: false, transformFunction: null }, truncatedResultsAnnouncement: { classPropertyName: "truncatedResultsAnnouncement", publicName: "truncatedResultsAnnouncement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { helpClick: "helpClick", touched: "touchedChange" }, host: { listeners: { "keydown": "onHostKeydown($event)" } }, viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "popupEl", first: true, predicate: ["popup"], descendants: true, isSignal: true }, { propertyName: "filterInputEl", first: true, predicate: ["filterInput"], descendants: true, isSignal: true }, { propertyName: "listboxEl", first: true, predicate: ["listboxEl"], descendants: true, isSignal: true }, { propertyName: "listboxDir", first: true, predicate: Listbox, descendants: true, isSignal: true }, { propertyName: "comboboxDir", first: true, predicate: Combobox, descendants: true, isSignal: true }], ngImport: i0 });
1916
2275
  }
1917
2276
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseSelect, decorators: [{
1918
2277
  type: Directive,
@@ -1921,7 +2280,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1921
2280
  '(keydown)': 'onHostKeydown($event)',
1922
2281
  },
1923
2282
  }]
1924
- }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], helpClick: [{ type: i0.Output, args: ["helpClick"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], 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 }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], filterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLabel", required: false }] }], filterFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFn", required: false }] }], maxVisibleOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleOptions", required: false }] }], truncatedResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncatedResultsText", required: false }] }], truncatedResultsAnnouncement: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncatedResultsAnnouncement", required: false }] }], triggerEl: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], popupEl: [{ type: i0.ViewChild, args: ['popup', { isSignal: true }] }], filterInputEl: [{ type: i0.ViewChild, args: ['filterInput', { isSignal: true }] }], listboxEl: [{ type: i0.ViewChild, args: ['listboxEl', { isSignal: true }] }], listboxDir: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Listbox), { isSignal: true }] }], comboboxDir: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Combobox), { isSignal: true }] }] } });
2283
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: false }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: false }] }], optionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionDisabled", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], helpClick: [{ type: i0.Output, args: ["helpClick"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], 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 }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], errorMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMatcher", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], floatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatLabel", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], filterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLabel", required: false }] }], filterFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterFn", required: false }] }], maxVisibleOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleOptions", required: false }] }], truncatedResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncatedResultsText", required: false }] }], truncatedResultsAnnouncement: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncatedResultsAnnouncement", required: false }] }], triggerEl: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], popupEl: [{ type: i0.ViewChild, args: ['popup', { isSignal: true }] }], filterInputEl: [{ type: i0.ViewChild, args: ['filterInput', { isSignal: true }] }], listboxEl: [{ type: i0.ViewChild, args: ['listboxEl', { isSignal: true }] }], listboxDir: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Listbox), { isSignal: true }] }], comboboxDir: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Combobox), { isSignal: true }] }] } });
1925
2284
 
1926
2285
  /** Template de rendu d'une option, posé sur un `<ng-template>` projeté dans `kt-select`.
1927
2286
  L'input sert UNIQUEMENT à inférer `T` (re-bind de la liste d'options) ; `ngTemplateContextGuard`
@@ -1985,7 +2344,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1985
2344
 
1986
2345
  @example
1987
2346
  ```html
1988
- <kt-select [options]="users" optionLabel="name" optionValue="id" [(value)]="selectedId" />
2347
+ <kt-select
2348
+ [options]="users"
2349
+ optionLabel="name"
2350
+ optionValue="id"
2351
+ [(value)]="selectedId"
2352
+ (selectionChange)="onSelectionChange($event)" />
1989
2353
  ``` */
1990
2354
  class KtSelect extends KtBaseSelect {
1991
2355
  // --- Modèle / sortie ---
@@ -2056,11 +2420,11 @@ class KtSelect extends KtBaseSelect {
2056
2420
  }
2057
2421
  }
2058
2422
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSelect, deps: null, target: i0.ɵɵFactoryTarget.Component });
2059
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtSelect, isStandalone: true, selector: "kt-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "optionDef", first: true, predicate: KtSelectOptionDef, descendants: true, isSignal: true }, { propertyName: "triggerDef", first: true, predicate: KtSelectTriggerDef, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container [ngTemplateOutlet]=\"def.template\" [ngTemplateOutletContext]=\"{ $implicit: selectedOption() }\" />\n } @else if (selectedOption(); as option) {\n {{ labelOf(option) }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Un seul Popover (top-layer). CSS @media : dropdown ancr\u00E9 (desktop) \u2194 bottom-sheet (t\u00E9l\u00E9phone).\n Pas de <dialog>.showModal() : ce combobox se ferme au blur, et showModal volerait le focus. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"filterable() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (filterable()) {\n <!-- Mode filtrable : le widget combobox est le panneau ENTIER (champ + annonce + liste).\n Indispensable : un focus pos\u00E9 hors de l'\u00E9l\u00E9ment ngComboboxWidget ferme le popup\n (closePopupOnBlur de @angular/aria) \u2014 le champ doit donc vivre dans ce sous-arbre. -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n >\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n <!-- Nombre de r\u00E9sultats annonc\u00E9 (diff\u00E9r\u00E9) aux lecteurs d'\u00E9cran : une liste qui\n r\u00E9tr\u00E9cit en silence est per\u00E7ue comme un bug (tests utilisateurs S. Higley). -->\n <div class=\"kt-select__sr-only\" role=\"status\" aria-live=\"polite\">{{ announcedCount() }}</div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, selected: !!opt.selected(), active: opt.active() }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n"], dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: Combobox, selector: "[ngCombobox]", inputs: ["disabled", "softDisabled", "alwaysExpanded", "tabindex", "expanded", "value", "inlineSuggestion"], outputs: ["expandedChange", "valueChange"], exportAs: ["ngCombobox"] }, { kind: "directive", type: ComboboxPopup, selector: "ng-template[ngComboboxPopup]", inputs: ["combobox", "popupType"], exportAs: ["ngComboboxPopup"] }, { kind: "directive", type: ComboboxWidget, selector: "[ngComboboxWidget]", inputs: ["activeDescendant"], exportAs: ["ngComboboxWidget"] }, { kind: "directive", type: Listbox, selector: "[ngListbox]", inputs: ["id", "orientation", "multi", "wrap", "softDisabled", "focusMode", "selectionMode", "typeaheadDelay", "disabled", "readonly", "tabindex", "value"], outputs: ["valueChange"], exportAs: ["ngListbox"] }, { kind: "directive", type: Option, selector: "[ngOption]", inputs: ["id", "value", "disabled", "label"], exportAs: ["ngOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtSelect, isStandalone: true, selector: "kt-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "optionDef", first: true, predicate: KtSelectOptionDef, descendants: true, isSignal: true }, { propertyName: "triggerDef", first: true, predicate: KtSelectTriggerDef, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\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 class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-filled]=\"selectedOption() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container [ngTemplateOutlet]=\"def.template\" [ngTemplateOutletContext]=\"{ $implicit: selectedOption() }\" />\n } @else if (selectedOption(); as option) {\n {{ labelOf(option) }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Un seul Popover (top-layer). CSS @media : dropdown ancr\u00E9 (desktop) \u2194 bottom-sheet (t\u00E9l\u00E9phone).\n Pas de <dialog>.showModal() : ce combobox se ferme au blur, et showModal volerait le focus. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"filterable() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (filterable()) {\n <!-- Mode filtrable : le widget combobox est le panneau ENTIER (champ + annonce + liste).\n Indispensable : un focus pos\u00E9 hors de l'\u00E9l\u00E9ment ngComboboxWidget ferme le popup\n (closePopupOnBlur de @angular/aria) \u2014 le champ doit donc vivre dans ce sous-arbre. -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n >\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n <!-- Nombre de r\u00E9sultats annonc\u00E9 (diff\u00E9r\u00E9) aux lecteurs d'\u00E9cran : une liste qui\n r\u00E9tr\u00E9cit en silence est per\u00E7ue comme un bug (tests utilisateurs S. Higley). -->\n <div class=\"kt-select__sr-only\" role=\"status\" aria-live=\"polite\">{{ announcedCount() }}</div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, selected: !!opt.selected(), active: opt.active() }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{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}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\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"] }, { kind: "directive", type: Combobox, selector: "[ngCombobox]", inputs: ["disabled", "softDisabled", "alwaysExpanded", "tabindex", "expanded", "value", "inlineSuggestion"], outputs: ["expandedChange", "valueChange"], exportAs: ["ngCombobox"] }, { kind: "directive", type: ComboboxPopup, selector: "ng-template[ngComboboxPopup]", inputs: ["combobox", "popupType"], exportAs: ["ngComboboxPopup"] }, { kind: "directive", type: ComboboxWidget, selector: "[ngComboboxWidget]", inputs: ["activeDescendant"], exportAs: ["ngComboboxWidget"] }, { kind: "directive", type: Listbox, selector: "[ngListbox]", inputs: ["id", "orientation", "multi", "wrap", "softDisabled", "focusMode", "selectionMode", "typeaheadDelay", "disabled", "readonly", "tabindex", "value"], outputs: ["valueChange"], exportAs: ["ngListbox"] }, { kind: "directive", type: Option, selector: "[ngOption]", inputs: ["id", "value", "disabled", "label"], exportAs: ["ngOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2060
2424
  }
2061
2425
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSelect, decorators: [{
2062
2426
  type: Component,
2063
- args: [{ selector: 'kt-select', changeDetection: ChangeDetectionStrategy.OnPush, imports: [KtField, KtFieldControl, NgTemplateOutlet, Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container [ngTemplateOutlet]=\"def.template\" [ngTemplateOutletContext]=\"{ $implicit: selectedOption() }\" />\n } @else if (selectedOption(); as option) {\n {{ labelOf(option) }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Un seul Popover (top-layer). CSS @media : dropdown ancr\u00E9 (desktop) \u2194 bottom-sheet (t\u00E9l\u00E9phone).\n Pas de <dialog>.showModal() : ce combobox se ferme au blur, et showModal volerait le focus. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"filterable() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (filterable()) {\n <!-- Mode filtrable : le widget combobox est le panneau ENTIER (champ + annonce + liste).\n Indispensable : un focus pos\u00E9 hors de l'\u00E9l\u00E9ment ngComboboxWidget ferme le popup\n (closePopupOnBlur de @angular/aria) \u2014 le champ doit donc vivre dans ce sous-arbre. -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n >\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n <!-- Nombre de r\u00E9sultats annonc\u00E9 (diff\u00E9r\u00E9) aux lecteurs d'\u00E9cran : une liste qui\n r\u00E9tr\u00E9cit en silence est per\u00E7ue comme un bug (tests utilisateurs S. Higley). -->\n <div class=\"kt-select__sr-only\" role=\"status\" aria-live=\"polite\">{{ announcedCount() }}</div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, selected: !!opt.selected(), active: opt.active() }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n"] }]
2427
+ args: [{ selector: 'kt-select', changeDetection: ChangeDetectionStrategy.OnPush, imports: [KtField, KtFieldControl, NgTemplateOutlet, Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\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 class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-filled]=\"selectedOption() ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container [ngTemplateOutlet]=\"def.template\" [ngTemplateOutletContext]=\"{ $implicit: selectedOption() }\" />\n } @else if (selectedOption(); as option) {\n {{ labelOf(option) }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Un seul Popover (top-layer). CSS @media : dropdown ancr\u00E9 (desktop) \u2194 bottom-sheet (t\u00E9l\u00E9phone).\n Pas de <dialog>.showModal() : ce combobox se ferme au blur, et showModal volerait le focus. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"filterable() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (filterable()) {\n <!-- Mode filtrable : le widget combobox est le panneau ENTIER (champ + annonce + liste).\n Indispensable : un focus pos\u00E9 hors de l'\u00E9l\u00E9ment ngComboboxWidget ferme le popup\n (closePopupOnBlur de @angular/aria) \u2014 le champ doit donc vivre dans ce sous-arbre. -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n >\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n <!-- Nombre de r\u00E9sultats annonc\u00E9 (diff\u00E9r\u00E9) aux lecteurs d'\u00E9cran : une liste qui\n r\u00E9tr\u00E9cit en silence est per\u00E7ue comme un bug (tests utilisateurs S. Higley). -->\n <div class=\"kt-select__sr-only\" role=\"status\" aria-live=\"polite\">{{ announcedCount() }}</div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, selected: !!opt.selected(), active: opt.active() }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{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}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n"] }]
2064
2428
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], optionDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtSelectOptionDef), { isSignal: true }] }], triggerDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtSelectTriggerDef), { isSignal: true }] }] } });
2065
2429
 
2066
2430
  /** Configure les `kt-select` d'un sous-arbre via DI (équivalent du Context provider de react-select).
@@ -2081,71 +2445,42 @@ class KtSelectConfig {
2081
2445
  /** Config héritée du contexte parent (provider global ou directive englobante) : la
2082
2446
  directive ne masque que les clés qu'elle définit, le reste continue d'en hériter. */
2083
2447
  parent = inject(KT_SELECT_CONFIG, { optional: true, skipSelf: true });
2448
+ /** Résout une clé : valeur bindée sur la directive, sinon valeur héritée du contexte parent. */
2084
2449
  resolve(key) {
2085
2450
  return this.ktSelectConfig()[key] ?? this.parent?.[key];
2086
2451
  }
2087
- get placeholder() {
2088
- return this.resolve('placeholder');
2089
- }
2090
- get emptyText() {
2091
- return this.resolve('emptyText');
2092
- }
2093
- get closeLabel() {
2094
- return this.resolve('closeLabel');
2095
- }
2096
- get closeOnSelect() {
2097
- return this.resolve('closeOnSelect');
2098
- }
2099
- get filterPlaceholder() {
2100
- return this.resolve('filterPlaceholder');
2101
- }
2102
- get filterLabel() {
2103
- return this.resolve('filterLabel');
2104
- }
2105
- get filterResultsText() {
2106
- return this.resolve('filterResultsText');
2107
- }
2108
- get removeItemLabel() {
2109
- return this.resolve('removeItemLabel');
2110
- }
2111
- get selectedItemsLabel() {
2112
- return this.resolve('selectedItemsLabel');
2113
- }
2114
- get selectionSummaryText() {
2115
- return this.resolve('selectionSummaryText');
2116
- }
2117
- get itemRemovedText() {
2118
- return this.resolve('itemRemovedText');
2119
- }
2120
- get selectionCountText() {
2121
- return this.resolve('selectionCountText');
2122
- }
2123
- get selectAllLabel() {
2124
- return this.resolve('selectAllLabel');
2125
- }
2126
- get clearAllLabel() {
2127
- return this.resolve('clearAllLabel');
2128
- }
2129
- get moreChipsLabel() {
2130
- return this.resolve('moreChipsLabel');
2131
- }
2132
- get lessChipsLabel() {
2133
- return this.resolve('lessChipsLabel');
2134
- }
2135
- get truncatedResultsText() {
2136
- return this.resolve('truncatedResultsText');
2137
- }
2138
- get truncatedResultsAnnouncement() {
2139
- return this.resolve('truncatedResultsAnnouncement');
2140
- }
2141
2452
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSelectConfig, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2142
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: KtSelectConfig, isStandalone: true, selector: "[ktSelectConfig]", inputs: { ktSelectConfig: { classPropertyName: "ktSelectConfig", publicName: "ktSelectConfig", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: KT_SELECT_CONFIG, useExisting: KtSelectConfig }], ngImport: i0 });
2453
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: KtSelectConfig, isStandalone: true, selector: "[ktSelectConfig]", inputs: { ktSelectConfig: { classPropertyName: "ktSelectConfig", publicName: "ktSelectConfig", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
2454
+ {
2455
+ provide: KT_SELECT_CONFIG,
2456
+ // Vue `Partial<KtSelectConfigOptions>` calculée à la volée : chaque accès de clé résout
2457
+ // `input bindé ?? contexte parent hérité`. Un Proxy évite d'énumérer (et de maintenir à la
2458
+ // main) un getter par clé — toute nouvelle clé de `KtSelectConfigOptions` est couverte
2459
+ // automatiquement, sans risque de dérive entre les deux fichiers.
2460
+ useFactory: (directive) => new Proxy({}, {
2461
+ get: (_target, key) => directive.resolve(key),
2462
+ }),
2463
+ deps: [KtSelectConfig],
2464
+ },
2465
+ ], ngImport: i0 });
2143
2466
  }
2144
2467
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSelectConfig, decorators: [{
2145
2468
  type: Directive,
2146
2469
  args: [{
2147
2470
  selector: '[ktSelectConfig]',
2148
- providers: [{ provide: KT_SELECT_CONFIG, useExisting: KtSelectConfig }],
2471
+ providers: [
2472
+ {
2473
+ provide: KT_SELECT_CONFIG,
2474
+ // Vue `Partial<KtSelectConfigOptions>` calculée à la volée : chaque accès de clé résout
2475
+ // `input bindé ?? contexte parent hérité`. Un Proxy évite d'énumérer (et de maintenir à la
2476
+ // main) un getter par clé — toute nouvelle clé de `KtSelectConfigOptions` est couverte
2477
+ // automatiquement, sans risque de dérive entre les deux fichiers.
2478
+ useFactory: (directive) => new Proxy({}, {
2479
+ get: (_target, key) => directive.resolve(key),
2480
+ }),
2481
+ deps: [KtSelectConfig],
2482
+ },
2483
+ ],
2149
2484
  }]
2150
2485
  }], propDecorators: { ktSelectConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "ktSelectConfig", required: false }] }] } });
2151
2486
 
@@ -2184,7 +2519,11 @@ class KtChip {
2184
2519
  remove = output();
2185
2520
  removeBtn = viewChild('removeBtn', /* @ts-ignore */
2186
2521
  ...(ngDevMode ? [{ debugName: "removeBtn" }] : /* istanbul ignore next */ []));
2187
- /** Focus programmatique du bouton « retirer » (focus management d'`kt-chip-list`). */
2522
+ /**
2523
+ * Focus programmatique du bouton « retirer ». Helper de coordination interne (focus management
2524
+ * d'`kt-chip-list`), non destiné aux consommateurs.
2525
+ * @internal
2526
+ */
2188
2527
  focusRemove() {
2189
2528
  this.removeBtn()?.nativeElement.focus();
2190
2529
  }
@@ -2215,6 +2554,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2215
2554
  </kt-chip-list>
2216
2555
  ``` */
2217
2556
  class KtChipItemDef {
2557
+ /** Liste des items rendus par ce template (sert aussi à inférer le type `T` du contexte). */
2218
2558
  ktChipItem = input.required(/* @ts-ignore */
2219
2559
  ...(ngDevMode ? [{ debugName: "ktChipItem" }] : /* istanbul ignore next */ []));
2220
2560
  template = inject(TemplateRef);
@@ -2255,6 +2595,7 @@ class KtChipList {
2255
2595
  doc = inject(DOCUMENT);
2256
2596
  platformId = inject(PLATFORM_ID);
2257
2597
  cdr = inject(ChangeDetectorRef);
2598
+ ngZone = inject(NgZone);
2258
2599
  /** Vrai pendant la View Transition de CETTE liste : les chips (standards via Chip,
2259
2600
  custom via tokens.css) ne portent leur `view-transition-name` que dans cette fenêtre. */
2260
2601
  transitioning = signal(false, /* @ts-ignore */
@@ -2421,9 +2762,28 @@ class KtChipList {
2421
2762
  this.transitioning.set(true);
2422
2763
  this.cdr.detectChanges();
2423
2764
  const vt = startViewTransition(() => {
2424
- action();
2425
- // Rendu synchrone : la View Transition capture l'état "après" à la fin de ce callback.
2426
- this.cdr.detectChanges();
2765
+ // `items` est un input POSSÉDÉ par le parent (`[items]="selectedOptions()"` côté MultiSelect) :
2766
+ // le retrait émet `removed`, le parent met à jour son signal, et seule une CD du PARENT re-pousse
2767
+ // le nouvel `items` dans cette liste. Une CD locale (`cdr.detectChanges()`) ne verrait que l'ancien
2768
+ // input → snapshot « après » identique au « avant » → aucune animation. Une CD globale forcée
2769
+ // (`appRef.tick()`) corrigerait le rendu mais ajouterait une passe synchrone sur toute l'appli à
2770
+ // chaque retrait. On préfère DIFFÉRER la capture de l'état « après » : le callback d'une View
2771
+ // 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);
2775
+ return new Promise((resolve) => {
2776
+ let settled = false;
2777
+ const done = () => {
2778
+ if (settled)
2779
+ return;
2780
+ settled = true;
2781
+ resolve();
2782
+ };
2783
+ afterNextRender({ read: done }, { injector: this.injector });
2784
+ // 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));
2786
+ });
2427
2787
  });
2428
2788
  this.activeTransition = vt;
2429
2789
  // `ready` rejette (AbortError) si la transition est sautée : attendu, ne pas laisser
@@ -2507,7 +2867,7 @@ class KtChipList {
2507
2867
  }
2508
2868
  }
2509
2869
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtChipList, deps: [], target: i0.ɵɵFactoryTarget.Component });
2510
- 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;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 });
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 });
2511
2871
  }
2512
2872
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtChipList, decorators: [{
2513
2873
  type: Component,
@@ -2517,7 +2877,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2517
2877
  // nomment les chips à template custom QUE sous ce marqueur (cf. ChipTransitionScope).
2518
2878
  '[attr.data-vt-active]': "transitioning() ? '' : null",
2519
2879
  '(keydown)': 'onKeydown($event)',
2520
- }, 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;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"] }]
2880
+ }, 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"] }]
2521
2881
  }], 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 }] }] } });
2522
2882
 
2523
2883
  /** Template de rendu d'une option dans la liste du multi-select. L'input sert à inférer `T` ;
@@ -2594,17 +2954,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2594
2954
  args: [{ selector: 'ng-template[ktMultiSelectChip]' }]
2595
2955
  }], propDecorators: { ktMultiSelectChip: [{ type: i0.Input, args: [{ isSignal: true, alias: "ktMultiSelectChip", required: true }] }] } });
2596
2956
 
2597
- // Défauts lib neutre (anglais, comme le Select single) : le consommateur fournit
2598
- // ses textes via KT_SELECT_CONFIG / les inputs (pas de $localize — cf. select-config.ts).
2599
- function defaultSelectionSummaryText(count) {
2600
- return `${count} items selected`;
2601
- }
2602
- function defaultSelectionCountText(count) {
2603
- return count === 1 ? '1 selected' : `${count} selected`;
2604
- }
2605
- function defaultSelectedItemsLabel(fieldLabel) {
2606
- return fieldLabel ? `Selected items for ${fieldLabel}` : 'Selected items';
2607
- }
2608
2957
  /**
2609
2958
  * Composant de sélection multiple (Multi-Select) conforme aux exigences d'accessibilité (WCAG 2.2 AAA / RGAA).
2610
2959
  * Repose sur `@angular/aria/combobox` et `@angular/aria/listbox` avec [multi]="true".
@@ -2621,7 +2970,12 @@ function defaultSelectedItemsLabel(fieldLabel) {
2621
2970
  *
2622
2971
  * @example
2623
2972
  * ```html
2624
- * <kt-multi-select [options]="tags" optionLabel="name" optionValue="id" [(value)]="selectedIds" />
2973
+ * <kt-multi-select
2974
+ * [options]="tags"
2975
+ * optionLabel="name"
2976
+ * optionValue="id"
2977
+ * [(value)]="selectedIds"
2978
+ * (selectionChange)="onSelectionChange($event)" />
2625
2979
  * ```
2626
2980
  */
2627
2981
  class KtMultiSelect extends KtBaseSelect {
@@ -2652,24 +3006,24 @@ class KtMultiSelect extends KtBaseSelect {
2652
3006
  ...(ngDevMode ? [{ debugName: "chipDef" }] : /* istanbul ignore next */ []));
2653
3007
  chipList = viewChild(KtChipList, /* @ts-ignore */
2654
3008
  ...(ngDevMode ? [{ debugName: "chipList" }] : /* istanbul ignore next */ []));
2655
- // --- Textes résolus spécifiques multi (input > KT_SELECT_CONFIG > défaut lib neutre EN) ---
2656
- resolvedRemoveItemLabel = computed(() => this.config?.removeItemLabel ?? ((itemLabel) => `Remove ${itemLabel}`), /* @ts-ignore */
3009
+ // --- Textes résolus spécifiques multi (input > KT_SELECT_CONFIG > DEFAULT_KT_SELECT_CONFIG centralisé) ---
3010
+ resolvedRemoveItemLabel = computed(() => this.config?.removeItemLabel ?? DEFAULT_KT_SELECT_CONFIG.removeItemLabel, /* @ts-ignore */
2657
3011
  ...(ngDevMode ? [{ debugName: "resolvedRemoveItemLabel" }] : /* istanbul ignore next */ []));
2658
- resolvedSelectedItemsLabel = computed(() => (this.config?.selectedItemsLabel ?? defaultSelectedItemsLabel)(this.label()), /* @ts-ignore */
3012
+ resolvedSelectedItemsLabel = computed(() => (this.config?.selectedItemsLabel ?? DEFAULT_KT_SELECT_CONFIG.selectedItemsLabel)(this.label()), /* @ts-ignore */
2659
3013
  ...(ngDevMode ? [{ debugName: "resolvedSelectedItemsLabel" }] : /* istanbul ignore next */ []));
2660
- resolvedSelectionSummaryText = computed(() => this.config?.selectionSummaryText ?? defaultSelectionSummaryText, /* @ts-ignore */
3014
+ resolvedSelectionSummaryText = computed(() => this.config?.selectionSummaryText ?? DEFAULT_KT_SELECT_CONFIG.selectionSummaryText, /* @ts-ignore */
2661
3015
  ...(ngDevMode ? [{ debugName: "resolvedSelectionSummaryText" }] : /* istanbul ignore next */ []));
2662
- resolvedItemRemovedText = computed(() => this.config?.itemRemovedText ?? ((itemLabel) => `${itemLabel} removed`), /* @ts-ignore */
3016
+ resolvedItemRemovedText = computed(() => this.config?.itemRemovedText ?? DEFAULT_KT_SELECT_CONFIG.itemRemovedText, /* @ts-ignore */
2663
3017
  ...(ngDevMode ? [{ debugName: "resolvedItemRemovedText" }] : /* istanbul ignore next */ []));
2664
- resolvedSelectionCountText = computed(() => this.config?.selectionCountText ?? defaultSelectionCountText, /* @ts-ignore */
3018
+ resolvedSelectionCountText = computed(() => this.config?.selectionCountText ?? DEFAULT_KT_SELECT_CONFIG.selectionCountText, /* @ts-ignore */
2665
3019
  ...(ngDevMode ? [{ debugName: "resolvedSelectionCountText" }] : /* istanbul ignore next */ []));
2666
- resolvedSelectAllLabel = computed(() => this.config?.selectAllLabel ?? 'Select all', /* @ts-ignore */
3020
+ resolvedSelectAllLabel = computed(() => this.config?.selectAllLabel ?? DEFAULT_KT_SELECT_CONFIG.selectAllLabel, /* @ts-ignore */
2667
3021
  ...(ngDevMode ? [{ debugName: "resolvedSelectAllLabel" }] : /* istanbul ignore next */ []));
2668
- resolvedClearAllLabel = computed(() => this.config?.clearAllLabel ?? 'Clear all', /* @ts-ignore */
3022
+ resolvedClearAllLabel = computed(() => this.config?.clearAllLabel ?? DEFAULT_KT_SELECT_CONFIG.clearAllLabel, /* @ts-ignore */
2669
3023
  ...(ngDevMode ? [{ debugName: "resolvedClearAllLabel" }] : /* istanbul ignore next */ []));
2670
- resolvedMoreChipsLabel = computed(() => this.config?.moreChipsLabel ?? ((hiddenCount) => `+${hiddenCount} more`), /* @ts-ignore */
3024
+ resolvedMoreChipsLabel = computed(() => this.config?.moreChipsLabel ?? DEFAULT_KT_SELECT_CONFIG.moreChipsLabel, /* @ts-ignore */
2671
3025
  ...(ngDevMode ? [{ debugName: "resolvedMoreChipsLabel" }] : /* istanbul ignore next */ []));
2672
- resolvedLessChipsLabel = computed(() => this.config?.lessChipsLabel ?? 'Show less', /* @ts-ignore */
3026
+ resolvedLessChipsLabel = computed(() => this.config?.lessChipsLabel ?? DEFAULT_KT_SELECT_CONFIG.lessChipsLabel, /* @ts-ignore */
2673
3027
  ...(ngDevMode ? [{ debugName: "resolvedLessChipsLabel" }] : /* istanbul ignore next */ []));
2674
3028
  /** Accesseurs bridgés vers le ChipList (fonctions fléchées : une référence de méthode
2675
3029
  non liée perdrait `this`). */
@@ -2860,7 +3214,7 @@ class KtMultiSelect extends KtBaseSelect {
2860
3214
  this.announceNow(this.resolvedSelectionCountText()(this.selectedOptions().length));
2861
3215
  }
2862
3216
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtMultiSelect, deps: null, target: i0.ɵɵFactoryTarget.Component });
2863
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtMultiSelect, isStandalone: true, selector: "kt-multi-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, selectionActions: { classPropertyName: "selectionActions", publicName: "selectionActions", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleChips: { classPropertyName: "maxVisibleChips", publicName: "maxVisibleChips", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "optionDef", first: true, predicate: KtMultiSelectOptionDef, descendants: true, isSignal: true }, { propertyName: "triggerDef", first: true, predicate: KtMultiSelectTriggerDef, descendants: true, isSignal: true }, { propertyName: "chipDef", first: true, predicate: KtMultiSelectChipDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "chipList", first: true, predicate: KtChipList, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [class.kt-select__trigger--clearable]=\"showClear()\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedOptions() }\"\n />\n } @else if (selectedOptions().length > 0) {\n {{ triggerText() }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Tout effacer depuis le champ : sibling du trigger (pas de bouton imbriqu\u00E9 dans un bouton). -->\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kt-select__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"clearSelection()\"\n >\n <span class=\"kt-select__clear-icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n <!-- Popup Popover (top-layer). M\u00EAme logique mobile bottom-sheet adaptative que le select classique. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"dialogMode() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (dialogMode()) {\n <!-- Mode panneau (filtre et/ou actions de masse) : Widget Dialog -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @if (selectionActions()) {\n <div class=\"kt-select__actions\">\n <button type=\"button\" class=\"kt-select__action\" (click)=\"selectAllFiltered()\">\n {{ resolvedSelectAllLabel() }}\n </button>\n <button type=\"button\" class=\"kt-select__action\" (click)=\"clearAllFiltered()\">\n {{ resolvedClearAllLabel() }}\n </button>\n </div>\n }\n @if (filterable()) {\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n }\n @if (selectedOptions().length > 0) {\n <!-- Rappel visuel (s\u00E9lections potentiellement masqu\u00E9es par le filtre) ;\n l'info SR passe par la live region ci-dessous \u2192 aria-hidden. -->\n <div class=\"kt-select__count\" aria-hidden=\"true\">{{ selectionCountLabel() }}</div>\n }\n <div class=\"kt-select__sr-only kt-select__filter-status\" role=\"status\" aria-live=\"polite\">\n {{ announcedCount() }}\n </div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <!-- Mode simple : Widget Listbox direct -->\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- Chips r\u00E9vocables (Option 1) sous le trigger : d\u00E9l\u00E9gu\u00E9s au composant r\u00E9utilisable\n kt-chip-list (focus management, repli +N, live region). Les textes r\u00E9solus depuis\n KT_SELECT_CONFIG sont pass\u00E9s en inputs (ils priment sur un \u00E9ventuel KT_CHIPS_CONFIG). -->\n <kt-chip-list\n [items]=\"selectedOptions()\"\n [itemLabel]=\"chipLabelOf\"\n [itemKey]=\"chipKeyOf\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [maxVisible]=\"maxVisibleChips()\"\n [listLabel]=\"resolvedSelectedItemsLabel()\"\n [removeItemLabel]=\"resolvedRemoveItemLabel()\"\n [itemRemovedText]=\"resolvedItemRemovedText()\"\n [moreLabel]=\"resolvedMoreChipsLabel()\"\n [lessLabel]=\"resolvedLessChipsLabel()\"\n [chipTemplate]=\"chipDef()?.template ?? null\"\n [emptyFocusTarget]=\"triggerEl()?.nativeElement\"\n (removed)=\"removeOption($event.item)\"\n />\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n", "@layer kt-aaa.components{.kt-select__trigger--clearable .kt-select__value{margin-inline-end:2rem}.kt-select__clear{position:absolute;inset-block-start:50%;inset-inline-end:2.5rem;translate:0 -50%;display:inline-flex;align-items:center;justify-content:center;inline-size:24px;block-size:24px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__clear:after{content:\"\";position:absolute;inset:-10px}.kt-select__clear:hover{background-color:color-mix(in srgb,currentColor 12%,transparent);color:var(--field-color, inherit)}.kt-select__clear:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:1px}.kt-select__clear-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__actions{display:flex;gap:.25rem;flex:none;padding:.25rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__action{flex:1;min-block-size:44px;padding:.375rem .625rem;border:0;border-radius:calc(var(--field-radius, 8px) * .75);background:transparent;color:var(--kt-primary, #0b57d0);font:inherit;font-weight:500;cursor:pointer}.kt-select__action:hover{background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__action:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-2px}.kt-select__count{flex:none;padding:.25rem .75rem;font-size:.8125rem;color:var(--field-hint-color, #5f6368);border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__checkbox{display:inline-flex;align-items:center;justify-content:center;flex:none;margin-inline-end:.25rem;-webkit-user-select:none;user-select:none}.kt-select__checkbox-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;color:var(--field-icon-color, #5f6368);-webkit-font-smoothing:antialiased}.kt-select__option[aria-selected=true] .kt-select__checkbox-icon{color:var(--kt-primary, #0b57d0)}.kt-select__option--multiple{display:flex;align-items:center;gap:.5rem}.kt-select__option-content{flex:1;min-inline-size:0}kt-chip-list:not([data-empty]){margin-block-start:.5rem}}\n"], dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: Combobox, selector: "[ngCombobox]", inputs: ["disabled", "softDisabled", "alwaysExpanded", "tabindex", "expanded", "value", "inlineSuggestion"], outputs: ["expandedChange", "valueChange"], exportAs: ["ngCombobox"] }, { kind: "directive", type: ComboboxPopup, selector: "ng-template[ngComboboxPopup]", inputs: ["combobox", "popupType"], exportAs: ["ngComboboxPopup"] }, { kind: "directive", type: ComboboxWidget, selector: "[ngComboboxWidget]", inputs: ["activeDescendant"], exportAs: ["ngComboboxWidget"] }, { kind: "directive", type: Listbox, selector: "[ngListbox]", inputs: ["id", "orientation", "multi", "wrap", "softDisabled", "focusMode", "selectionMode", "typeaheadDelay", "disabled", "readonly", "tabindex", "value"], outputs: ["valueChange"], exportAs: ["ngListbox"] }, { kind: "directive", type: Option, selector: "[ngOption]", inputs: ["id", "value", "disabled", "label"], exportAs: ["ngOption"] }, { kind: "component", type: KtChipList, selector: "kt-chip-list", inputs: ["items", "itemLabel", "itemKey", "removable", "disabled", "readonly", "maxVisible", "listLabel", "removeItemLabel", "itemRemovedText", "moreLabel", "lessLabel", "chipTemplate", "emptyFocusTarget"], outputs: ["removed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3217
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtMultiSelect, isStandalone: true, selector: "kt-multi-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, selectionActions: { classPropertyName: "selectionActions", publicName: "selectionActions", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleChips: { classPropertyName: "maxVisibleChips", publicName: "maxVisibleChips", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "optionDef", first: true, predicate: KtMultiSelectOptionDef, descendants: true, isSignal: true }, { propertyName: "triggerDef", first: true, predicate: KtMultiSelectTriggerDef, descendants: true, isSignal: true }, { propertyName: "chipDef", first: true, predicate: KtMultiSelectChipDef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "chipList", first: true, predicate: KtChipList, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\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 class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [class.kt-select__trigger--clearable]=\"showClear()\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-filled]=\"selectedOptions().length ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedOptions() }\"\n />\n } @else if (selectedOptions().length > 0) {\n {{ triggerText() }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Tout effacer depuis le champ : sibling du trigger (pas de bouton imbriqu\u00E9 dans un bouton). -->\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kt-select__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"clearSelection()\"\n >\n <span class=\"kt-select__clear-icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n <!-- Popup Popover (top-layer). M\u00EAme logique mobile bottom-sheet adaptative que le select classique. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"dialogMode() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (dialogMode()) {\n <!-- Mode panneau (filtre et/ou actions de masse) : Widget Dialog -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @if (selectionActions()) {\n <div class=\"kt-select__actions\">\n <button type=\"button\" class=\"kt-select__action\" (click)=\"selectAllFiltered()\">\n {{ resolvedSelectAllLabel() }}\n </button>\n <button type=\"button\" class=\"kt-select__action\" (click)=\"clearAllFiltered()\">\n {{ resolvedClearAllLabel() }}\n </button>\n </div>\n }\n @if (filterable()) {\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n }\n @if (selectedOptions().length > 0) {\n <!-- Rappel visuel (s\u00E9lections potentiellement masqu\u00E9es par le filtre) ;\n l'info SR passe par la live region ci-dessous \u2192 aria-hidden. -->\n <div class=\"kt-select__count\" aria-hidden=\"true\">{{ selectionCountLabel() }}</div>\n }\n <div class=\"kt-select__sr-only kt-select__filter-status\" role=\"status\" aria-live=\"polite\">\n {{ announcedCount() }}\n </div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <!-- Mode simple : Widget Listbox direct -->\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- Chips r\u00E9vocables (Option 1) sous le trigger : d\u00E9l\u00E9gu\u00E9s au composant r\u00E9utilisable\n kt-chip-list (focus management, repli +N, live region). Les textes r\u00E9solus depuis\n KT_SELECT_CONFIG sont pass\u00E9s en inputs (ils priment sur un \u00E9ventuel KT_CHIPS_CONFIG). -->\n <kt-chip-list\n [items]=\"selectedOptions()\"\n [itemLabel]=\"chipLabelOf\"\n [itemKey]=\"chipKeyOf\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [maxVisible]=\"maxVisibleChips()\"\n [listLabel]=\"resolvedSelectedItemsLabel()\"\n [removeItemLabel]=\"resolvedRemoveItemLabel()\"\n [itemRemovedText]=\"resolvedItemRemovedText()\"\n [moreLabel]=\"resolvedMoreChipsLabel()\"\n [lessLabel]=\"resolvedLessChipsLabel()\"\n [chipTemplate]=\"chipDef()?.template ?? null\"\n [emptyFocusTarget]=\"triggerEl()?.nativeElement\"\n (removed)=\"removeOption($event.item)\"\n />\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{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}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n", "@layer kt-aaa.components{.kt-select__trigger--clearable .kt-select__value{margin-inline-end:2rem}.kt-select__clear{position:absolute;inset-block-start:50%;inset-inline-end:2.5rem;translate:0 -50%;display:inline-flex;align-items:center;justify-content:center;inline-size:24px;block-size:24px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__clear:after{content:\"\";position:absolute;inset:-10px}.kt-select__clear:hover{background-color:color-mix(in srgb,currentColor 12%,transparent);color:var(--field-color, inherit)}.kt-select__clear:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:1px}.kt-select__clear-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__actions{display:flex;gap:.25rem;flex:none;padding:.25rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__action{flex:1;min-block-size:44px;padding:.375rem .625rem;border:0;border-radius:calc(var(--field-radius, 8px) * .75);background:transparent;color:var(--kt-primary, #0b57d0);font:inherit;font-weight:500;cursor:pointer}.kt-select__action:hover{background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__action:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-2px}.kt-select__count{flex:none;padding:.25rem .75rem;font-size:.8125rem;color:var(--field-hint-color, #5f6368);border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__checkbox{display:inline-flex;align-items:center;justify-content:center;flex:none;margin-inline-end:.25rem;-webkit-user-select:none;user-select:none}.kt-select__checkbox-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;color:var(--field-icon-color, #5f6368);-webkit-font-smoothing:antialiased}.kt-select__option[aria-selected=true] .kt-select__checkbox-icon{color:var(--kt-primary, #0b57d0)}.kt-select__option--multiple{display:flex;align-items:center;gap:.5rem}.kt-select__option-content{flex:1;min-inline-size:0}kt-chip-list:not([data-empty]){margin-block-start:.5rem}}\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"] }, { kind: "directive", type: Combobox, selector: "[ngCombobox]", inputs: ["disabled", "softDisabled", "alwaysExpanded", "tabindex", "expanded", "value", "inlineSuggestion"], outputs: ["expandedChange", "valueChange"], exportAs: ["ngCombobox"] }, { kind: "directive", type: ComboboxPopup, selector: "ng-template[ngComboboxPopup]", inputs: ["combobox", "popupType"], exportAs: ["ngComboboxPopup"] }, { kind: "directive", type: ComboboxWidget, selector: "[ngComboboxWidget]", inputs: ["activeDescendant"], exportAs: ["ngComboboxWidget"] }, { kind: "directive", type: Listbox, selector: "[ngListbox]", inputs: ["id", "orientation", "multi", "wrap", "softDisabled", "focusMode", "selectionMode", "typeaheadDelay", "disabled", "readonly", "tabindex", "value"], outputs: ["valueChange"], exportAs: ["ngListbox"] }, { kind: "directive", type: Option, selector: "[ngOption]", inputs: ["id", "value", "disabled", "label"], exportAs: ["ngOption"] }, { kind: "component", type: KtChipList, selector: "kt-chip-list", inputs: ["items", "itemLabel", "itemKey", "removable", "disabled", "readonly", "maxVisible", "listLabel", "removeItemLabel", "itemRemovedText", "moreLabel", "lessLabel", "chipTemplate", "emptyFocusTarget"], outputs: ["removed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2864
3218
  }
2865
3219
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtMultiSelect, decorators: [{
2866
3220
  type: Component,
@@ -2874,12 +3228,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2874
3228
  Listbox,
2875
3229
  Option,
2876
3230
  KtChipList,
2877
- ], template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\n (helpClick)=\"helpClick.emit($event)\"\n>\n <ng-content select=\"[ktFieldHelp]\" />\n <div class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [class.kt-select__trigger--clearable]=\"showClear()\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedOptions() }\"\n />\n } @else if (selectedOptions().length > 0) {\n {{ triggerText() }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Tout effacer depuis le champ : sibling du trigger (pas de bouton imbriqu\u00E9 dans un bouton). -->\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kt-select__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"clearSelection()\"\n >\n <span class=\"kt-select__clear-icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n <!-- Popup Popover (top-layer). M\u00EAme logique mobile bottom-sheet adaptative que le select classique. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"dialogMode() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (dialogMode()) {\n <!-- Mode panneau (filtre et/ou actions de masse) : Widget Dialog -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @if (selectionActions()) {\n <div class=\"kt-select__actions\">\n <button type=\"button\" class=\"kt-select__action\" (click)=\"selectAllFiltered()\">\n {{ resolvedSelectAllLabel() }}\n </button>\n <button type=\"button\" class=\"kt-select__action\" (click)=\"clearAllFiltered()\">\n {{ resolvedClearAllLabel() }}\n </button>\n </div>\n }\n @if (filterable()) {\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n }\n @if (selectedOptions().length > 0) {\n <!-- Rappel visuel (s\u00E9lections potentiellement masqu\u00E9es par le filtre) ;\n l'info SR passe par la live region ci-dessous \u2192 aria-hidden. -->\n <div class=\"kt-select__count\" aria-hidden=\"true\">{{ selectionCountLabel() }}</div>\n }\n <div class=\"kt-select__sr-only kt-select__filter-status\" role=\"status\" aria-live=\"polite\">\n {{ announcedCount() }}\n </div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <!-- Mode simple : Widget Listbox direct -->\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- Chips r\u00E9vocables (Option 1) sous le trigger : d\u00E9l\u00E9gu\u00E9s au composant r\u00E9utilisable\n kt-chip-list (focus management, repli +N, live region). Les textes r\u00E9solus depuis\n KT_SELECT_CONFIG sont pass\u00E9s en inputs (ils priment sur un \u00E9ventuel KT_CHIPS_CONFIG). -->\n <kt-chip-list\n [items]=\"selectedOptions()\"\n [itemLabel]=\"chipLabelOf\"\n [itemKey]=\"chipKeyOf\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [maxVisible]=\"maxVisibleChips()\"\n [listLabel]=\"resolvedSelectedItemsLabel()\"\n [removeItemLabel]=\"resolvedRemoveItemLabel()\"\n [itemRemovedText]=\"resolvedItemRemovedText()\"\n [moreLabel]=\"resolvedMoreChipsLabel()\"\n [lessLabel]=\"resolvedLessChipsLabel()\"\n [chipTemplate]=\"chipDef()?.template ?? null\"\n [emptyFocusTarget]=\"triggerEl()?.nativeElement\"\n (removed)=\"removeOption($event.item)\"\n />\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n", "@layer kt-aaa.components{.kt-select__trigger--clearable .kt-select__value{margin-inline-end:2rem}.kt-select__clear{position:absolute;inset-block-start:50%;inset-inline-end:2.5rem;translate:0 -50%;display:inline-flex;align-items:center;justify-content:center;inline-size:24px;block-size:24px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__clear:after{content:\"\";position:absolute;inset:-10px}.kt-select__clear:hover{background-color:color-mix(in srgb,currentColor 12%,transparent);color:var(--field-color, inherit)}.kt-select__clear:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:1px}.kt-select__clear-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__actions{display:flex;gap:.25rem;flex:none;padding:.25rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__action{flex:1;min-block-size:44px;padding:.375rem .625rem;border:0;border-radius:calc(var(--field-radius, 8px) * .75);background:transparent;color:var(--kt-primary, #0b57d0);font:inherit;font-weight:500;cursor:pointer}.kt-select__action:hover{background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__action:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-2px}.kt-select__count{flex:none;padding:.25rem .75rem;font-size:.8125rem;color:var(--field-hint-color, #5f6368);border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__checkbox{display:inline-flex;align-items:center;justify-content:center;flex:none;margin-inline-end:.25rem;-webkit-user-select:none;user-select:none}.kt-select__checkbox-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;color:var(--field-icon-color, #5f6368);-webkit-font-smoothing:antialiased}.kt-select__option[aria-selected=true] .kt-select__checkbox-icon{color:var(--kt-primary, #0b57d0)}.kt-select__option--multiple{display:flex;align-items:center;gap:.5rem}.kt-select__option-content{flex:1;min-inline-size:0}kt-chip-list:not([data-empty]){margin-block-start:.5rem}}\n"] }]
3231
+ ], template: "<kt-field\n [label]=\"label()\"\n [hint]=\"hint()\"\n [helpText]=\"helpText()\"\n [helpLabel]=\"helpLabel()\"\n [customDescribedBy]=\"customDescribedBy()\"\n [errors]=\"fieldErrors()\"\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 class=\"kt-select\" [class.kt-select--open]=\"expanded()\">\n <button\n #combobox=\"ngCombobox\"\n #trigger\n ngCombobox\n ktFieldControl\n type=\"button\"\n class=\"kt-field-box kt-select__trigger\"\n [class.kt-select__trigger--clearable]=\"showClear()\"\n [(expanded)]=\"expanded\"\n [disabled]=\"disabled()\"\n [softDisabled]=\"false\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.data-disabled]=\"disabled() ? '' : null\"\n [attr.data-filled]=\"selectedOptions().length ? '' : null\"\n [attr.data-pending]=\"pending() ? '' : null\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"kt-select__value\">\n @if (triggerDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedOptions() }\"\n />\n } @else if (selectedOptions().length > 0) {\n {{ triggerText() }}\n } @else {\n <span class=\"kt-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n </span>\n <span class=\"kt-select__arrow\" aria-hidden=\"true\">arrow_drop_down</span>\n </button>\n\n <!-- Tout effacer depuis le champ : sibling du trigger (pas de bouton imbriqu\u00E9 dans un bouton). -->\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kt-select__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"clearSelection()\"\n >\n <span class=\"kt-select__clear-icon\" aria-hidden=\"true\">close</span>\n </button>\n }\n\n <!-- Popup Popover (top-layer). M\u00EAme logique mobile bottom-sheet adaptative que le select classique. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\" [popupType]=\"dialogMode() ? 'dialog' : 'listbox'\">\n <div #popup popover=\"manual\" class=\"kt-select__popup\" [class.kt-select__popup--sheet]=\"compact()\">\n @if (compact()) {\n <div class=\"kt-select__sheet-scrim\" aria-hidden=\"true\" (click)=\"expanded.set(false)\"></div>\n }\n <div class=\"kt-select__sheet-card\">\n @if (compact()) {\n <div\n class=\"kt-select__sheet-grab\"\n aria-hidden=\"true\"\n (pointerdown)=\"onDragStart($event)\"\n (mousedown)=\"$event.preventDefault()\"\n ></div>\n <header class=\"kt-select__sheet-header\">\n <span class=\"kt-select__sheet-title\">{{ label() }}</span>\n <button\n type=\"button\"\n class=\"kt-select__sheet-close\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n (click)=\"expanded.set(false)\"\n >\n <span class=\"kt-select__sheet-close-icon\" aria-hidden=\"true\">close</span>\n </button>\n </header>\n }\n @if (dialogMode()) {\n <!-- Mode panneau (filtre et/ou actions de masse) : Widget Dialog -->\n <div\n ngComboboxWidget\n role=\"dialog\"\n class=\"kt-select__panel\"\n [id]=\"panelId\"\n [attr.aria-label]=\"label()\"\n [activeDescendant]=\"lb.activeDescendant()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n @if (selectionActions()) {\n <div class=\"kt-select__actions\">\n <button type=\"button\" class=\"kt-select__action\" (click)=\"selectAllFiltered()\">\n {{ resolvedSelectAllLabel() }}\n </button>\n <button type=\"button\" class=\"kt-select__action\" (click)=\"clearAllFiltered()\">\n {{ resolvedClearAllLabel() }}\n </button>\n </div>\n }\n @if (filterable()) {\n <div class=\"kt-select__filter\">\n <input\n #filterInput\n type=\"search\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n class=\"kt-select__filter-input\"\n autocomplete=\"off\"\n [placeholder]=\"resolvedFilterPlaceholder()\"\n [attr.aria-label]=\"resolvedFilterLabel()\"\n [attr.aria-controls]=\"lb.id()\"\n [attr.aria-activedescendant]=\"lb.activeDescendant()\"\n [value]=\"filterText()\"\n (input)=\"onFilterInput($event)\"\n (keydown)=\"onFilterKeydown($event)\"\n />\n </div>\n }\n @if (selectedOptions().length > 0) {\n <!-- Rappel visuel (s\u00E9lections potentiellement masqu\u00E9es par le filtre) ;\n l'info SR passe par la live region ci-dessous \u2192 aria-hidden. -->\n <div class=\"kt-select__count\" aria-hidden=\"true\">{{ selectionCountLabel() }}</div>\n }\n <div class=\"kt-select__sr-only kt-select__filter-status\" role=\"status\" aria-live=\"polite\">\n {{ announcedCount() }}\n </div>\n <ul\n #lb=\"ngListbox\"\n #listboxEl\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() && !filterable() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n @for (item of displayedOptions(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n @if (filterable() && filteredOptions().length > maxVisibleOptions()) {\n <li class=\"kt-select__truncated-info\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedTruncatedResultsText()(maxVisibleOptions(), filteredOptions().length) }}\n </li>\n }\n </ul>\n </div>\n } @else {\n <!-- Mode simple : Widget Listbox direct -->\n <ul\n #listbox=\"ngListbox\"\n #listboxEl\n ngComboboxWidget\n ngListbox\n [multi]=\"true\"\n [focusMode]=\"compact() ? 'roving' : 'activedescendant'\"\n selectionMode=\"explicit\"\n class=\"kt-select__listbox\"\n [attr.aria-label]=\"label()\"\n [value]=\"listboxValue()\"\n (valueChange)=\"onListboxValueChange($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [activeDescendant]=\"listbox.activeDescendant()\"\n >\n @for (item of options(); track keyOf(item)) {\n <li\n #opt=\"ngOption\"\n ngOption\n class=\"kt-select__option kt-select__option--multiple\"\n [value]=\"keyOf(item)\"\n [label]=\"labelOf(item)\"\n [disabled]=\"disabledOf(item)\"\n [class.kt-select__option--active]=\"opt.active()\"\n >\n <span class=\"kt-select__checkbox\" aria-hidden=\"true\">\n @if (opt.selected()) {\n <span class=\"kt-select__checkbox-icon\">check_box</span>\n } @else {\n <span class=\"kt-select__checkbox-icon\">check_box_outline_blank</span>\n }\n </span>\n <span class=\"kt-select__option-content\">\n @if (optionDef(); as def) {\n <ng-container\n [ngTemplateOutlet]=\"def.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n selected: !!opt.selected(),\n active: opt.active(),\n }\"\n />\n } @else {\n {{ labelOf(item) }}\n }\n </span>\n </li>\n } @empty {\n <li class=\"kt-select__empty\" role=\"option\" aria-disabled=\"true\" aria-selected=\"false\">\n {{ resolvedEmptyText() }}\n </li>\n }\n </ul>\n }\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- Chips r\u00E9vocables (Option 1) sous le trigger : d\u00E9l\u00E9gu\u00E9s au composant r\u00E9utilisable\n kt-chip-list (focus management, repli +N, live region). Les textes r\u00E9solus depuis\n KT_SELECT_CONFIG sont pass\u00E9s en inputs (ils priment sur un \u00E9ventuel KT_CHIPS_CONFIG). -->\n <kt-chip-list\n [items]=\"selectedOptions()\"\n [itemLabel]=\"chipLabelOf\"\n [itemKey]=\"chipKeyOf\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [maxVisible]=\"maxVisibleChips()\"\n [listLabel]=\"resolvedSelectedItemsLabel()\"\n [removeItemLabel]=\"resolvedRemoveItemLabel()\"\n [itemRemovedText]=\"resolvedItemRemovedText()\"\n [moreLabel]=\"resolvedMoreChipsLabel()\"\n [lessLabel]=\"resolvedLessChipsLabel()\"\n [chipTemplate]=\"chipDef()?.template ?? null\"\n [emptyFocusTarget]=\"triggerEl()?.nativeElement\"\n (removed)=\"removeOption($event.item)\"\n />\n</kt-field>\n", styles: ["@layer kt-aaa.components{:host{display:block}.kt-select{position:relative}.kt-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--field-control-gap, .5rem);inline-size:100%;cursor:pointer;text-align:start;font:inherit;color:var(--field-color, inherit)}.kt-select__trigger:disabled{cursor:not-allowed;background:var(--field-disabled-bg, #f1f3f4);color:color-mix(in srgb,currentColor 50%,transparent)}.kt-select__value{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kt-select__placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__arrow{flex:none;font-family:Material Symbols Outlined;font-size:1.25em;line-height:1;font-feature-settings:\"liga\";color:var(--field-icon-color, #5f6368);transition:var(--select-arrow-transition, transform .12s ease);-webkit-font-smoothing:antialiased}.kt-select--open .kt-select__arrow{transform:rotate(180deg)}.kt-select__popup{box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;border-width:var(--select-popup-border-width, var(--field-border-width, 1px));border-style:var(--field-border-style, solid);border-color:var(--field-border-color, #c4c7c5);border-radius:var(--field-radius, 8px);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);box-shadow:var(--select-popup-shadow, 0 4px 12px rgb(0 0 0 / 12%));-webkit-backdrop-filter:var(--select-popup-backdrop-filter, none);backdrop-filter:var(--select-popup-backdrop-filter, none);max-block-size:var(--select-popup-max-height, 16rem);overflow:hidden}.kt-select__sheet-card{display:contents}.kt-select__popup:not(:popover-open){display:none!important}.kt-select__popup:popover-open{animation:var(--select-popup-enter-animation, none)}@media(prefers-reduced-motion:reduce){.kt-select__popup:popover-open{animation:none}}.kt-select__listbox{flex:1 1 auto;min-block-size:0;margin:0;padding:.25rem;list-style:none;overflow-y:auto}.kt-select__panel{display:flex;flex-direction:column;flex:1 1 auto;min-block-size:0}.kt-select__filter{flex:none;padding:.5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__filter-input{box-sizing:border-box;inline-size:100%;min-block-size:var(--field-min-height, 44px);padding:.375rem .625rem;border:var(--field-border-width, 1px) var(--field-border-style, solid) var(--field-border-color, #c4c7c5);border-radius:calc(var(--field-radius, 8px) * .75);background:var(--select-popup-bg, var(--kt-surface, #fff));color:var(--field-color, inherit);font:inherit;appearance:none}.kt-select__filter-input::placeholder{color:var(--field-hint-color, #5f6368)}.kt-select__filter-input:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-1px}.kt-select__filter-input::-webkit-search-cancel-button{appearance:none;inline-size:1rem;block-size:1rem;margin-inline-start:.375rem;cursor:pointer;background-color:var(--field-icon-color, #5f6368);-webkit-mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat;mask:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E\") center / contain no-repeat}.kt-select__sr-only{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}@supports (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto;top:anchor(bottom);left:anchor(left);margin-block-start:.25rem;min-inline-size:anchor-size(width);width:max-content;max-inline-size:min(90vw,28rem);position-try-fallbacks:flip-block,flip-inline,flip-block flip-inline}}@supports not (anchor-name: --x){.kt-select__popup{position:fixed;inset:auto 0 0;inline-size:100%;max-inline-size:100%;border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0}}.kt-select__popup--sheet{position:fixed!important;inset:0!important;inline-size:100%!important;max-inline-size:100%!important;min-inline-size:0!important;width:100%!important;height:100%!important;max-block-size:none!important;margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:flex;flex-direction:column!important;justify-content:flex-end!important}.kt-select__popup--sheet .kt-select__sheet-card{position:relative;z-index:2;display:flex;flex-direction:column;background:var(--select-popup-bg, var(--kt-surface, #fff));border-radius:var(--kt-sheet-radius, 16px) var(--kt-sheet-radius, 16px) 0 0;box-shadow:var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));max-block-size:var(--kt-sheet-max-block-size, 85svh);width:100%;overflow:hidden;translate:0 0;transition:translate var(--kt-sheet-anim-duration, .12s) ease}.kt-select__popup--sheet .kt-select__sheet-card:has(.kt-select__filter){block-size:var(--kt-sheet-max-block-size, 85svh);max-block-size:var(--kt-sheet-max-block-size, 85svh)}.kt-select__popup--sheet:popover-open .kt-select__sheet-card{translate:0 0;animation:var(--select-sheet-enter-animation, kt-sheet-in var(--kt-sheet-anim-duration, .12s) ease)}.kt-select__popup--sheet .kt-select__sheet-card.kt-select__popup--dragging{transition:none}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-card,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-card{translate:0 100%;transition:translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(.4,0,.2,1)}::ng-deep .kt-select__popup--sheet::backdrop{display:none!important}.kt-select__sheet-scrim{display:none}.kt-select__popup--sheet .kt-select__sheet-scrim{display:block;position:fixed;inset:0;z-index:1;background:var(--kt-sheet-scrim, rgb(0 0 0 / 40%));opacity:0;pointer-events:auto;transition:opacity var(--kt-sheet-anim-duration, .12s) ease,overlay var(--kt-sheet-anim-duration, .12s) allow-discrete,display var(--kt-sheet-anim-duration, .12s) allow-discrete}.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:1}@starting-style{.kt-select__popup--sheet:popover-open .kt-select__sheet-scrim{opacity:0}}.kt-select__popup--sheet.kt-select__popup--closing .kt-select__sheet-scrim,.kt-select__popup--sheet:popover-open.kt-select__popup--closing .kt-select__sheet-scrim{opacity:0;transition:opacity var(--kt-sheet-exit-duration, 90ms) ease}.kt-select__popup--sheet .kt-select__option{--select-option-min-height: 44px;padding-block:.625rem}.kt-select__popup--sheet .kt-select__filter-input{font-size:1rem;min-block-size:44px}@media(prefers-reduced-motion:reduce){.kt-select__popup--sheet .kt-select__sheet-card,.kt-select__popup--sheet .kt-select__sheet-scrim{transition:none;translate:0 0}}.kt-select__sheet-grab{display:flex;align-items:center;justify-content:center;flex:none;block-size:44px;cursor:grab;touch-action:none}.kt-select__sheet-grab:active{cursor:grabbing}.kt-select__sheet-grab:before{content:\"\";inline-size:2.25rem;block-size:.25rem;border-radius:999px;background:var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5))}.kt-select__sheet-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;flex:none;padding-block:.5rem;padding-inline:1rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__sheet-title{font-weight:600}.kt-select__sheet-close{display:inline-flex;align-items:center;justify-content:center;flex:none;inline-size:44px;block-size:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__sheet-close:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:2px}.kt-select__sheet-close-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.5rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__option{display:flex;align-items:center;gap:.5rem;box-sizing:border-box;min-block-size:var( --select-option-min-height, 44px );padding:.5rem .625rem;border-radius:6px;cursor:pointer}.kt-select__option[aria-selected=true]{background:var(--select-option-selected-bg, color-mix(in srgb, var(--kt-primary, #0b57d0) 14%, transparent));color:var(--select-option-selected-color, inherit);font-weight:var(--select-option-selected-weight, 600)}.kt-select__option--active:not([aria-disabled=true]),.kt-select__option:hover:not([aria-disabled=true]){background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__option[aria-disabled=true]{opacity:.5;cursor:not-allowed}.kt-select__empty{padding:.5rem .625rem;color:var(--field-hint-color, #5f6368)}.kt-select__truncated-info{box-sizing:border-box;padding:.5rem .625rem;font-size:.875rem;font-style:italic;color:var(--field-hint-color, #5f6368);border-block-start:1px solid var(--field-border-color, #c4c7c5);pointer-events:none}}\n", "@layer kt-aaa.components{.kt-select__trigger--clearable .kt-select__value{margin-inline-end:2rem}.kt-select__clear{position:absolute;inset-block-start:50%;inset-inline-end:2.5rem;translate:0 -50%;display:inline-flex;align-items:center;justify-content:center;inline-size:24px;block-size:24px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--field-icon-color, #5f6368);cursor:pointer}.kt-select__clear:after{content:\"\";position:absolute;inset:-10px}.kt-select__clear:hover{background-color:color-mix(in srgb,currentColor 12%,transparent);color:var(--field-color, inherit)}.kt-select__clear:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:1px}.kt-select__clear-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;-webkit-font-smoothing:antialiased}.kt-select__actions{display:flex;gap:.25rem;flex:none;padding:.25rem .5rem;border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__action{flex:1;min-block-size:44px;padding:.375rem .625rem;border:0;border-radius:calc(var(--field-radius, 8px) * .75);background:transparent;color:var(--kt-primary, #0b57d0);font:inherit;font-weight:500;cursor:pointer}.kt-select__action:hover{background:var(--select-option-hover-bg, color-mix(in srgb, currentColor 8%, transparent))}.kt-select__action:focus-visible{outline:2px solid var(--field-border-color-focus, #0b57d0);outline-offset:-2px}.kt-select__count{flex:none;padding:.25rem .75rem;font-size:.8125rem;color:var(--field-hint-color, #5f6368);border-block-end:1px solid var(--field-border-color, #c4c7c5)}.kt-select__checkbox{display:inline-flex;align-items:center;justify-content:center;flex:none;margin-inline-end:.25rem;-webkit-user-select:none;user-select:none}.kt-select__checkbox-icon{font-family:Material Symbols Outlined;font-feature-settings:\"liga\";font-size:1.25rem;line-height:1;color:var(--field-icon-color, #5f6368);-webkit-font-smoothing:antialiased}.kt-select__option[aria-selected=true] .kt-select__checkbox-icon{color:var(--kt-primary, #0b57d0)}.kt-select__option--multiple{display:flex;align-items:center;gap:.5rem}.kt-select__option-content{flex:1;min-inline-size:0}kt-chip-list:not([data-empty]){margin-block-start:.5rem}}\n"] }]
2878
3232
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], selectionActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionActions", required: false }] }], maxVisibleChips: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleChips", required: false }] }], optionDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtMultiSelectOptionDef), { isSignal: true }] }], triggerDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtMultiSelectTriggerDef), { isSignal: true }] }], chipDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtMultiSelectChipDef), { isSignal: true }] }], chipList: [{ type: i0.ViewChild, args: [i0.forwardRef(() => KtChipList), { isSignal: true }] }] } });
2879
3233
 
2880
3234
  /** Base partagée des champs Temporal (Date/Time/DateTime/YearMonth).
2881
- Toute la logique commune (parsing tolérant, valeur vide `null`, `displayValue` pour l'input
2882
- natif) vit ici ; chaque sous-classe ne fournit que la construction et la sérialisation propres
3235
+ Toute la logique commune (parsing tolérant, valeur vide `null`, synchronisation de l'input natif)
3236
+ vit ici ; chaque sous-classe ne fournit que la construction et la sérialisation propres
2883
3237
  à son type Temporal (`fromString` / `serialize`).
2884
3238
 
2885
3239
  Contrat abstrait : une sous-classe implémente uniquement le couple
@@ -2889,7 +3243,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2889
3243
  tolérant (saisie partielle/invalide → `null`, jamais d'exception ni de valeur fausse).
2890
3244
 
2891
3245
  @template T Type Temporal porté par le champ (ex. `Temporal.PlainDate`,
2892
- `Temporal.PlainTime`, …). Doit exposer `toString()`. La valeur du champ est `T | null`. */
3246
+ `Temporal.PlainTime`, …). Doit exposer `toString()`. La valeur du champ est `T | null`.
3247
+
3248
+ @example
3249
+ ```ts
3250
+ // Sous-classer : ne fournir que fromString (input natif → T) et serialize (T → input natif).
3251
+ export class KtDateField extends KtBaseTemporalField<Temporal.PlainDate> {
3252
+ protected override fromString(raw: string): Temporal.PlainDate {
3253
+ return Temporal.PlainDate.from(raw);
3254
+ }
3255
+ protected override serialize(value: Temporal.PlainDate): string {
3256
+ return value.toString();
3257
+ }
3258
+ }
3259
+ ``` */
2893
3260
  class KtBaseTemporalField extends KtBaseInputField {
2894
3261
  doc = inject(DOCUMENT);
2895
3262
  constructor() {
@@ -2918,18 +3285,46 @@ class KtBaseTemporalField extends KtBaseInputField {
2918
3285
  return null;
2919
3286
  }
2920
3287
  }
3288
+ displayValue() {
3289
+ const v = this.value();
3290
+ return v === null ? '' : this.serialize(v);
3291
+ }
3292
+ /** Efface AUSSI l'input natif. L'effet de synchro ignore une mise à `null` quand l'input est
3293
+ focalisé (pour ne pas écraser une saisie partielle en cours), or `clear()` focalise justement
3294
+ le champ — sans ce forçage, le bouton « effacer » (et Échap) resterait sans effet sur les
3295
+ champs temporels. */
3296
+ clear() {
3297
+ super.clear();
3298
+ const el = this.inputRef()?.nativeElement;
3299
+ if (el)
3300
+ el.value = '';
3301
+ }
2921
3302
  emptyValue() {
2922
3303
  return null;
2923
3304
  }
2924
3305
  isEmpty(value) {
2925
3306
  return value === null;
2926
3307
  }
2927
- /** Représentation ISO pour l'attribut `[value]` de l'input natif (null champ vide). */
2928
- displayValue = computed(() => {
2929
- const value = this.value();
2930
- return value === null ? '' : this.serialize(value);
3308
+ /** Borne minimale autorisée (membre `min` du contrat `FormValueControl`, aussi posé en attribut
3309
+ `min` natif via `serialize`). Non bornée par défaut. @default undefined */
3310
+ min = input(/* @ts-ignore */
3311
+ ...(ngDevMode ? [undefined, { debugName: "min" }] : /* istanbul ignore next */ []));
3312
+ /** Borne maximale autorisée (membre `max` du contrat `FormValueControl`, aussi posé en attribut
3313
+ `max` natif via `serialize`). Non bornée par défaut. @default undefined */
3314
+ max = input(/* @ts-ignore */
3315
+ ...(ngDevMode ? [undefined, { debugName: "max" }] : /* istanbul ignore next */ []));
3316
+ /** Représentation ISO de `min` pour l'attribut natif (`null` quand non borné). */
3317
+ serializedMin = computed(() => {
3318
+ const value = this.min();
3319
+ return value == null ? null : this.serialize(value);
3320
+ }, /* @ts-ignore */
3321
+ ...(ngDevMode ? [{ debugName: "serializedMin" }] : /* istanbul ignore next */ []));
3322
+ /** Représentation ISO de `max` pour l'attribut natif (`null` quand non borné). */
3323
+ serializedMax = computed(() => {
3324
+ const value = this.max();
3325
+ return value == null ? null : this.serialize(value);
2931
3326
  }, /* @ts-ignore */
2932
- ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
3327
+ ...(ngDevMode ? [{ debugName: "serializedMax" }] : /* istanbul ignore next */ []));
2933
3328
  /** Suggestions proposées via un `<datalist>` natif (le picker date/heure reste disponible).
2934
3329
  Valeurs Temporal simples ou couples `{ value, label }` ; chaque valeur est sérialisée au
2935
3330
  format de l'input via `serialize`. @default undefined */
@@ -2943,22 +3338,67 @@ class KtBaseTemporalField extends KtBaseInputField {
2943
3338
  datalistOptions = computed(() => normalizeKtSuggestions(this.suggestions(), (value) => this.serialize(value)), /* @ts-ignore */
2944
3339
  ...(ngDevMode ? [{ debugName: "datalistOptions" }] : /* istanbul ignore next */ []));
2945
3340
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseTemporalField, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2946
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: KtBaseTemporalField, isStandalone: true, inputs: { suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
3341
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: KtBaseTemporalField, isStandalone: true, inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
2947
3342
  }
2948
3343
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseTemporalField, decorators: [{
2949
3344
  type: Directive
2950
- }], ctorParameters: () => [], propDecorators: { suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }] } });
2951
-
2952
- /** Valeur runtime `Temporal` (native ou polyfill). Cast via `unknown` : selon le contexte de
2953
- compilation, `globalThis.Temporal` peut déjà être typé (augmentation `temporal-polyfill/global`)
2954
- ou nonce cast fonctionne dans les deux cas. */
2955
- const rawTemporal = globalThis.Temporal;
2956
- if (typeof rawTemporal === 'undefined') {
2957
- throw new Error(`[ktortu/aaa] L'espace de noms 'Temporal' est introuvable. ` +
2958
- `Veuillez vous assurer que le polyfill 'temporal-polyfill/global' est correctement importé ` +
2959
- `dans le fichier 'main.ts' de votre application.`);
3345
+ }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }] } });
3346
+
3347
+ /** Base des champs Temporal porteurs d'une heure (`Time`, `DateTime`, `Instant`). Ajoute le réglage
3348
+ `precision` qui pilote, d'un seul point, la sérialisation (`smallestUnit` du `toString`) et
3349
+ l'attribut `step` natif de l'input un input `time`/`datetime-local` n'affiche le sélecteur de
3350
+ secondes que si `step` < 60. Les champs sans heure (`Date`, `YearMonth`) n'héritent pas de cette
3351
+ base. Chaque sous-classe lit `precision()` dans son `serialize`.
3352
+
3353
+ @example
3354
+ ```ts
3355
+ // Sous-classer : lire precision() dans serialize pour piloter smallestUnit.
3356
+ export class KtTimeField extends KtBaseTimeTemporalField<Temporal.PlainTime> {
3357
+ protected override fromString(raw: string): Temporal.PlainTime {
3358
+ return Temporal.PlainTime.from(raw);
3359
+ }
3360
+ protected override serialize(value: Temporal.PlainTime): string {
3361
+ return value.toString({ smallestUnit: this.precision() });
3362
+ }
3363
+ }
3364
+ ``` */
3365
+ class KtBaseTimeTemporalField extends KtBaseTemporalField {
3366
+ /** Précision de l'heure : `'minute'` (secondes masquées) ou `'second'`.
3367
+ @default 'minute' */
3368
+ precision = input('minute', /* @ts-ignore */
3369
+ ...(ngDevMode ? [{ debugName: "precision" }] : /* istanbul ignore next */ []));
3370
+ /** Attribut `step` natif : `'1'` (seconde) en précision seconde, sinon `null` (pas natif = minute). */
3371
+ step = computed(() => (this.precision() === 'second' ? '1' : null), /* @ts-ignore */
3372
+ ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
3373
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseTimeTemporalField, deps: null, target: i0.ɵɵFactoryTarget.Directive });
3374
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: KtBaseTimeTemporalField, isStandalone: true, inputs: { precision: { classPropertyName: "precision", publicName: "precision", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
3375
+ }
3376
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBaseTimeTemporalField, decorators: [{
3377
+ type: Directive
3378
+ }], propDecorators: { precision: [{ type: i0.Input, args: [{ isSignal: true, alias: "precision", required: false }] }] } });
3379
+
3380
+ /** Résout la valeur runtime `Temporal` (native ou polyfill) au moment de l'APPEL, pas à
3381
+ l'évaluation du module. Capturer `globalThis.Temporal` à l'import rendrait la lib fragile à
3382
+ l'ordre d'initialisation des modules décidé par le bundler : sur un moteur sans Temporal natif
3383
+ (Safari/WebKit), le module Temporal de la lib pouvait s'évaluer avant l'installation du polyfill
3384
+ et jeter au boot. */
3385
+ function resolveTemporal() {
3386
+ const value = globalThis.Temporal;
3387
+ if (value == null) {
3388
+ throw new Error(`[ktortu/aaa] L'espace de noms 'Temporal' est introuvable. ` +
3389
+ `Veuillez vous assurer que le polyfill 'temporal-polyfill/global' est correctement importé ` +
3390
+ `dans le fichier 'main.ts' de votre application.`);
3391
+ }
3392
+ return value;
2960
3393
  }
2961
- const Temporal = rawTemporal;
3394
+ /** Point d'accès `Temporal` de la lib. Proxy à résolution paresseuse : le namespace réel n'est lu
3395
+ qu'au premier accès de propriété (`Temporal.PlainDate`…), jamais à l'import — ce qui supprime la
3396
+ dépendance à l'ordre d'init des modules et fait booter Safari/WebKit tant que le polyfill est
3397
+ importé dans `main.ts`. */
3398
+ const Temporal = new Proxy({}, {
3399
+ get: (_target, property) => Reflect.get(resolveTemporal(), property),
3400
+ has: (_target, property) => Reflect.has(resolveTemporal(), property),
3401
+ });
2962
3402
 
2963
3403
  /** Champ « date sans heure » : valeur = `Temporal.PlainDate`, input natif `type="date"`.
2964
3404
  *
@@ -2980,11 +3420,11 @@ class KtDateField extends KtBaseTemporalField {
2980
3420
  return value.toString();
2981
3421
  }
2982
3422
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtDateField, deps: null, target: i0.ɵɵFactoryTarget.Component });
2983
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtDateField, isStandalone: true, selector: "kt-date-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"date\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtDateField, isStandalone: true, selector: "kt-date-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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 [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=\"date\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", 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 });
2984
3424
  }
2985
3425
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtDateField, decorators: [{
2986
3426
  type: Component,
2987
- args: [{ selector: 'kt-date-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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"date\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3427
+ args: [{ selector: 'kt-date-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=\"date\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
2988
3428
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
2989
3429
 
2990
3430
  /** Champ « heure sans date » : valeur = `Temporal.PlainTime`, input natif `type="time"`.
@@ -2994,7 +3434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2994
3434
  * <kt-time-field label="Heure de rendez-vous" [(value)]="appointmentTime" />
2995
3435
  * ```
2996
3436
  */
2997
- class KtTimeField extends KtBaseTemporalField {
3437
+ class KtTimeField extends KtBaseTimeTemporalField {
2998
3438
  /** Valeur du champ : un `Temporal.PlainTime` (heure « au mur », sans date ni fuseau).
2999
3439
  `null` = champ vide.
3000
3440
  @default null */
@@ -3004,15 +3444,15 @@ class KtTimeField extends KtBaseTemporalField {
3004
3444
  return Temporal.PlainTime.from(raw);
3005
3445
  }
3006
3446
  serialize(value) {
3007
- // Précision minute : un input `type="time"` ne saisit pas les secondes par défaut.
3008
- return value.toString({ smallestUnit: 'minute' });
3447
+ // Précision pilotée par `precision()` : minute (défaut) ou seconde.
3448
+ return value.toString({ smallestUnit: this.precision() });
3009
3449
  }
3010
3450
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTimeField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3011
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtTimeField, isStandalone: true, selector: "kt-time-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"time\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3451
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtTimeField, isStandalone: true, selector: "kt-time-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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 [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=\"time\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.step]=\"step()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", 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 });
3012
3452
  }
3013
3453
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTimeField, decorators: [{
3014
3454
  type: Component,
3015
- args: [{ selector: 'kt-time-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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"time\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3455
+ args: [{ selector: 'kt-time-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=\"time\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.step]=\"step()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3016
3456
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
3017
3457
 
3018
3458
  /** Champ « date-heure sans fuseau » : valeur = `Temporal.PlainDateTime`, input
@@ -3023,7 +3463,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
3023
3463
  * <kt-date-time-field label="Début de l'événement" [(value)]="startsAt" />
3024
3464
  * ```
3025
3465
  */
3026
- class KtDateTimeField extends KtBaseTemporalField {
3466
+ class KtDateTimeField extends KtBaseTimeTemporalField {
3027
3467
  /** Valeur du champ : un `Temporal.PlainDateTime` (date + heure « au mur », sans fuseau).
3028
3468
  `null` = champ vide.
3029
3469
  @default null */
@@ -3033,15 +3473,15 @@ class KtDateTimeField extends KtBaseTemporalField {
3033
3473
  return Temporal.PlainDateTime.from(raw);
3034
3474
  }
3035
3475
  serialize(value) {
3036
- // Précision minute : `type="datetime-local"` ne saisit pas les secondes par défaut.
3037
- return value.toString({ smallestUnit: 'minute' });
3476
+ // Précision pilotée par `precision()` : minute (défaut) ou seconde.
3477
+ return value.toString({ smallestUnit: this.precision() });
3038
3478
  }
3039
3479
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtDateTimeField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3040
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtDateTimeField, isStandalone: true, selector: "kt-date-time-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3480
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtDateTimeField, isStandalone: true, selector: "kt-date-time-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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 [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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.step]=\"step()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", 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 });
3041
3481
  }
3042
3482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtDateTimeField, decorators: [{
3043
3483
  type: Component,
3044
- args: [{ selector: 'kt-date-time-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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3484
+ args: [{ selector: 'kt-date-time-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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.step]=\"step()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3045
3485
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
3046
3486
 
3047
3487
  /** Champ « mois/année » (ex. expiration de carte) : valeur = `Temporal.PlainYearMonth`,
@@ -3065,11 +3505,11 @@ class KtYearMonthField extends KtBaseTemporalField {
3065
3505
  return value.toString();
3066
3506
  }
3067
3507
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtYearMonthField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3068
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtYearMonthField, isStandalone: true, selector: "kt-year-month-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"month\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3508
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtYearMonthField, isStandalone: true, selector: "kt-year-month-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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 [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=\"month\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", 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 });
3069
3509
  }
3070
3510
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtYearMonthField, decorators: [{
3071
3511
  type: Component,
3072
- args: [{ selector: 'kt-year-month-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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"month\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3512
+ args: [{ selector: 'kt-year-month-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=\"month\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3073
3513
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
3074
3514
 
3075
3515
  /** Source unique de l'heure courante et du **fuseau local** de l'utilisateur.
@@ -3147,7 +3587,7 @@ class KtFixedClock extends KtClock {
3147
3587
  * <kt-instant-field label="Horodatage" [(value)]="recordedAt" />
3148
3588
  * ```
3149
3589
  */
3150
- class KtInstantField extends KtBaseTemporalField {
3590
+ class KtInstantField extends KtBaseTimeTemporalField {
3151
3591
  clock = inject(KtClock);
3152
3592
  /** Valeur du champ : un `Temporal.Instant` (instant absolu en UTC), saisi/affiché en heure
3153
3593
  locale via `KtClock`. `null` = champ vide.
@@ -3159,16 +3599,19 @@ class KtInstantField extends KtBaseTemporalField {
3159
3599
  fromString(raw) {
3160
3600
  return Temporal.PlainDateTime.from(raw).toZonedDateTime(this.clock.timeZoneId()).toInstant();
3161
3601
  }
3162
- // Instant absolu → heure locale (précision minute, comme un input datetime-local).
3602
+ // Instant absolu → heure locale ; précision pilotée par `precision()` (minute par défaut).
3163
3603
  serialize(value) {
3164
- return value.toZonedDateTimeISO(this.clock.timeZoneId()).toPlainDateTime().toString({ smallestUnit: 'minute' });
3604
+ return value
3605
+ .toZonedDateTimeISO(this.clock.timeZoneId())
3606
+ .toPlainDateTime()
3607
+ .toString({ smallestUnit: this.precision() });
3165
3608
  }
3166
3609
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtInstantField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3167
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtInstantField, isStandalone: true, selector: "kt-instant-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", dependencies: [{ kind: "component", type: KtField, selector: "kt-field", inputs: ["label", "hint", "helpText", "helpLabel", "customDescribedBy", "errors", "invalid", "required", "fieldId", "hideHintWhenInvalid", "showAllErrors"], outputs: ["helpClick"] }, { kind: "directive", type: KtFieldControl, selector: "[ktFieldControl]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3610
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: KtInstantField, isStandalone: true, selector: "kt-instant-field", inputs: { value: { classPropertyName: "value", publicName: "value", 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 [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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.step]=\"step()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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", 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 });
3168
3611
  }
3169
3612
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtInstantField, decorators: [{
3170
3613
  type: Component,
3171
- args: [{ selector: 'kt-instant-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]=\"errors()\"\n [invalid]=\"showInvalid()\"\n [required]=\"required()\"\n [fieldId]=\"id()\"\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 >\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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3614
+ args: [{ selector: 'kt-instant-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=\"datetime-local\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-busy]=\"pending() ? 'true' : null\"\n [attr.name]=\"name() || null\"\n [attr.placeholder]=\"placeholder()\"\n [attr.min]=\"serializedMin()\"\n [attr.max]=\"serializedMax()\"\n [attr.step]=\"step()\"\n [attr.list]=\"hasSuggestions() ? datalistId : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeyDown($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" }]
3172
3615
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
3173
3616
 
3174
3617
  /** Formate une valeur Temporal pour l'affichage selon la locale active.
@@ -3188,9 +3631,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
3188
3631
  * {{ d | temporalDate:{ dateStyle: 'full' } }}
3189
3632
  * ```
3190
3633
  */
3634
+ // Exception R1 ASSUMÉE : le name `temporalDate` (non préfixé `kt`) relève du domaine Temporal,
3635
+ // exempté de préfixe au même titre que le namespace `Temporal` et ses alias de types. La CLASSE
3636
+ // reste préfixée (`KtTemporalDatePipe`). Choix ergonomique acté (ADR-4).
3637
+ // Stryker disable next-line all: le `name` du pipe doit rester statiquement analysable par l'AOT (NG1010).
3191
3638
  class KtTemporalDatePipe {
3192
3639
  locale = inject(LOCALE_ID);
3193
3640
  clock = inject(KtClock);
3641
+ /** Formate la valeur Temporal en chaîne localisée via `toLocaleString`.
3642
+ * @param value Valeur Temporal à formater (`Plain*`, `Instant` ou `ZonedDateTime`) ; `null`/`undefined` → chaîne vide.
3643
+ * @param options Options `Intl.DateTimeFormatOptions` transmises telles quelles. Styles globaux :
3644
+ * `dateStyle`/`timeStyle` (`'full' | 'long' | 'medium' | 'short'`). Champs fins :
3645
+ * `weekday`/`era`/`month` (`'long' | 'short' | 'narrow'`), `year`/`day`/`hour`/`minute`/`second`
3646
+ * (`'numeric' | '2-digit'`), `hour12`, `timeZoneName`, `timeZone`, etc. Omis → format par défaut de la locale.
3647
+ * @returns La chaîne formatée selon la locale active (`LOCALE_ID`), ou `''` si la valeur est absente. */
3194
3648
  transform(value, options) {
3195
3649
  if (value == null)
3196
3650
  return '';
@@ -3211,5 +3665,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
3211
3665
  * Generated bundle index. Do not edit.
3212
3666
  */
3213
3667
 
3214
- export { KT_CHIPS_CONFIG, KT_FIELD, KT_FIELD_CONFIG, KT_SELECT_CONFIG, KtBaseTemporalField, KtCheckbox, KtCheckboxGroup, KtChip, KtChipItemDef, KtChipList, KtClock, KtDateField, KtDateTimeField, KtField, KtFieldControl, KtFixedClock, KtInstantField, KtMultiSelect, KtMultiSelectChipDef, KtMultiSelectOptionDef, KtMultiSelectTriggerDef, KtNumberField, KtRadio, KtRadioGroup, KtSelect, KtSelectConfig, KtSelectOptionDef, KtSelectTriggerDef, KtSwitch, KtTemporalDatePipe, KtTextArea, KtTextField, KtTimeField, KtYearMonthField, defaultKtFieldErrorMatcher };
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 };
3215
3669
  //# sourceMappingURL=ktortu-aaa-forms.mjs.map