@ktortu/aaa 0.1.0-beta.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +23 -7
  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 +58 -15
  8. package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
  9. package/fesm2022/ktortu-aaa-dialog.mjs +69 -12
  10. package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
  11. package/fesm2022/ktortu-aaa-forms.mjs +455 -260
  12. package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
  13. package/fesm2022/ktortu-aaa-i18n.mjs +114 -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-tabs.mjs +319 -42
  18. package/fesm2022/ktortu-aaa-tabs.mjs.map +1 -1
  19. package/fesm2022/ktortu-aaa-tooltip.mjs +3 -2
  20. package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -1
  21. package/fesm2022/ktortu-aaa.mjs +1 -0
  22. package/fesm2022/ktortu-aaa.mjs.map +1 -1
  23. package/forms/radio/radio-group.css +3 -3
  24. package/forms/styles/field-box.css +150 -2
  25. package/forms/styles/tokens.css +3 -0
  26. package/menu/menu.css +8 -4
  27. package/package.json +5 -1
  28. package/types/ktortu-aaa-button.d.ts +22 -8
  29. package/types/ktortu-aaa-card.d.ts +24 -4
  30. package/types/ktortu-aaa-cdk.d.ts +38 -0
  31. package/types/ktortu-aaa-dialog.d.ts +45 -9
  32. package/types/ktortu-aaa-forms.d.ts +336 -149
  33. package/types/ktortu-aaa-i18n.d.ts +74 -0
  34. package/types/ktortu-aaa-menu.d.ts +15 -8
  35. package/types/ktortu-aaa-tabs.d.ts +130 -13
  36. package/types/ktortu-aaa-tooltip.d.ts +5 -0
  37. package/types/ktortu-aaa.d.ts +1 -0
@@ -0,0 +1,114 @@
1
+ import { makeEnvironmentProviders } from '@angular/core';
2
+ import { KT_FIELD_CONFIG, KT_SELECT_CONFIG, KT_CHIPS_CONFIG } from '@ktortu/aaa/forms';
3
+ import { KT_TABS_CONFIG } from '@ktortu/aaa/tabs';
4
+
5
+ /**
6
+ * Enregistre, en un seul appel, les libellés fournis sur les tokens de config de la lib.
7
+ * Base = défauts anglais de chaque composant ; seules les familles **présentes** dans `t` sont
8
+ * fournies (les autres gardent leur défaut anglais et restent surchargeables ailleurs).
9
+ *
10
+ * À placer dans les `providers` de `app.config.ts` (ou de tout injecteur).
11
+ *
12
+ * @param t Libellés à fournir, groupés par famille ; seules les familles présentes sont enregistrées.
13
+ * @returns Les `EnvironmentProviders` liant chaque famille présente à son token de config.
14
+ * @example
15
+ * ```ts
16
+ * provideKtTranslations({ tabs: { nextLabel: 'Suivant', previousLabel: 'Précédent' } })
17
+ * ```
18
+ */
19
+ function provideKtTranslations(t) {
20
+ const providers = [];
21
+ if (t.field)
22
+ providers.push({ provide: KT_FIELD_CONFIG, useValue: t.field });
23
+ if (t.select)
24
+ providers.push({ provide: KT_SELECT_CONFIG, useValue: t.select });
25
+ if (t.chips)
26
+ providers.push({ provide: KT_CHIPS_CONFIG, useValue: t.chips });
27
+ if (t.tabs)
28
+ providers.push({ provide: KT_TABS_CONFIG, useValue: t.tabs });
29
+ return makeEnvironmentProviders(providers);
30
+ }
31
+ /**
32
+ * Fusion par famille de deux {@link KtTranslations} : `over` écrase `base`, clé par clé.
33
+ * @param base Libellés de référence (ex. un dictionnaire de langue complet).
34
+ * @param over Libellés prioritaires venant écraser `base`.
35
+ * @returns Un nouveau {@link KtTranslations} fusionné, famille par famille.
36
+ * @example mergeKtTranslations(KT_FR_TRANSLATIONS, { tabs: { nextLabel: 'Suivant' } })
37
+ */
38
+ function mergeKtTranslations(base, over) {
39
+ return {
40
+ field: { ...base.field, ...over.field },
41
+ select: { ...base.select, ...over.select },
42
+ chips: { ...base.chips, ...over.chips },
43
+ tabs: { ...base.tabs, ...over.tabs },
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Dictionnaire **français** complet de tous les libellés de la lib. Figé et réutilisable :
49
+ * importable tel quel (ex. pour le composer avec d'autres réglages) ou via {@link provideKtDefaultFR}.
50
+ * Les libellés à pluriels sont des fonctions, pour laisser la grammaire au consommateur.
51
+ */
52
+ const KT_FR_TRANSLATIONS = {
53
+ field: {
54
+ clearLabel: 'Effacer',
55
+ helpLabel: 'Aide',
56
+ numberParseError: 'La valeur saisie doit être un nombre valide.',
57
+ },
58
+ select: {
59
+ emptyText: 'Aucune option',
60
+ closeLabel: 'Fermer',
61
+ filterLabel: 'Filtrer les options',
62
+ // En français, 0 ET 1 prennent le singulier (≠ anglais où seul 1 est singulier).
63
+ filterResultsText: (count) => (count <= 1 ? `${count} résultat` : `${count} résultats`),
64
+ removeItemLabel: (itemLabel) => `Retirer ${itemLabel}`,
65
+ selectedItemsLabel: (fieldLabel) => fieldLabel ? `Éléments sélectionnés pour ${fieldLabel}` : 'Éléments sélectionnés',
66
+ selectionSummaryText: (count) => (count <= 1 ? `${count} élément sélectionné` : `${count} éléments sélectionnés`),
67
+ itemRemovedText: (itemLabel) => `${itemLabel} retiré`,
68
+ selectionCountText: (count) => (count <= 1 ? `${count} sélectionné` : `${count} sélectionnés`),
69
+ selectAllLabel: 'Tout sélectionner',
70
+ clearAllLabel: 'Tout effacer',
71
+ moreChipsLabel: (hiddenCount) => `+${hiddenCount} de plus`,
72
+ lessChipsLabel: 'Afficher moins',
73
+ truncatedResultsText: (max, total) => `Affichage des ${max} premiers résultats sur ${total}. Affinez votre recherche pour en voir plus.`,
74
+ truncatedResultsAnnouncement: (max, total) => `${max} résultats affichés sur ${total}. Affinez votre recherche pour en voir plus.`,
75
+ },
76
+ chips: {
77
+ removeItemLabel: (itemLabel) => `Retirer ${itemLabel}`,
78
+ itemRemovedText: (itemLabel) => `${itemLabel} retiré`,
79
+ moreLabel: (hiddenCount) => `+${hiddenCount} de plus`,
80
+ lessLabel: 'Afficher moins',
81
+ listLabel: 'Éléments sélectionnés',
82
+ },
83
+ tabs: {
84
+ previousLabel: 'Onglets précédents',
85
+ nextLabel: 'Onglets suivants',
86
+ },
87
+ };
88
+ /**
89
+ * Enregistre les libellés **français** par défaut sur toute la lib, en un seul appel.
90
+ * `overrides` permet d'ajuster ponctuellement sans tout réécrire (fusion par famille,
91
+ * cf. {@link mergeKtTranslations}). À placer dans les `providers` de `app.config.ts`.
92
+ *
93
+ * @param overrides Libellés à ajuster par-dessus le français (fusion par famille). Défaut : `{}`.
94
+ * @returns Les `EnvironmentProviders` enregistrant le français (fusionné aux `overrides`).
95
+ * @example
96
+ * ```ts
97
+ * // app.config.ts
98
+ * providers: [
99
+ * provideKtDefaultFR(), // tout en français
100
+ * // ou en ajustant un libellé précis :
101
+ * provideKtDefaultFR({ tabs: { nextLabel: 'Suivant' } }),
102
+ * ]
103
+ * ```
104
+ */
105
+ function provideKtDefaultFR(overrides = {}) {
106
+ return provideKtTranslations(mergeKtTranslations(KT_FR_TRANSLATIONS, overrides));
107
+ }
108
+
109
+ /**
110
+ * Generated bundle index. Do not edit.
111
+ */
112
+
113
+ export { KT_FR_TRANSLATIONS, mergeKtTranslations, provideKtDefaultFR, provideKtTranslations };
114
+ //# sourceMappingURL=ktortu-aaa-i18n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ktortu-aaa-i18n.mjs","sources":["../../../../projects/ktortu/aaa/i18n/translations.ts","../../../../projects/ktortu/aaa/i18n/fr.ts","../../../../projects/ktortu/aaa/i18n/ktortu-aaa-i18n.ts"],"sourcesContent":["import { EnvironmentProviders, Provider, makeEnvironmentProviders } from '@angular/core';\n\nimport { KT_FIELD_CONFIG, KT_CHIPS_CONFIG, KT_SELECT_CONFIG } from '@ktortu/aaa/forms';\nimport type { KtFieldConfig, KtChipsConfig, KtSelectConfigOptions } from '@ktortu/aaa/forms';\nimport { KT_TABS_CONFIG } from '@ktortu/aaa/tabs';\nimport type { KtTabsConfig } from '@ktortu/aaa/tabs';\n\n/**\n * Agrégat de TOUS les libellés traduisibles de la lib, groupés par famille de composants.\n * Chaque clé reprend la config du token correspondant (`Partial` : tout est optionnel — on ne\n * fournit que ce qu'on veut traduire). C'est l'« interface de traduction » unique de la lib :\n * un point d'entrée pour brancher une langue d'un coup.\n *\n * @see provideKtTranslations pour enregistrer un jeu de libellés.\n * @see provideKtDefaultFR pour partir des libellés français.\n */\nexport interface KtTranslations {\n /** Libellés des champs (`clearLabel`, `helpLabel`) → {@link KT_FIELD_CONFIG}. */\n field?: Partial<KtFieldConfig>;\n /** Libellés des selects / multi-selects → {@link KT_SELECT_CONFIG}. */\n select?: Partial<KtSelectConfigOptions>;\n /** Libellés des listes de chips → {@link KT_CHIPS_CONFIG}. */\n chips?: Partial<KtChipsConfig>;\n /** Libellés de la pagination des onglets → {@link KT_TABS_CONFIG}. */\n tabs?: Partial<KtTabsConfig>;\n}\n\n/**\n * Enregistre, en un seul appel, les libellés fournis sur les tokens de config de la lib.\n * Base = défauts anglais de chaque composant ; seules les familles **présentes** dans `t` sont\n * fournies (les autres gardent leur défaut anglais et restent surchargeables ailleurs).\n *\n * À placer dans les `providers` de `app.config.ts` (ou de tout injecteur).\n *\n * @param t Libellés à fournir, groupés par famille ; seules les familles présentes sont enregistrées.\n * @returns Les `EnvironmentProviders` liant chaque famille présente à son token de config.\n * @example\n * ```ts\n * provideKtTranslations({ tabs: { nextLabel: 'Suivant', previousLabel: 'Précédent' } })\n * ```\n */\nexport function provideKtTranslations(t: KtTranslations): EnvironmentProviders {\n const providers: Provider[] = [];\n if (t.field) providers.push({ provide: KT_FIELD_CONFIG, useValue: t.field });\n if (t.select) providers.push({ provide: KT_SELECT_CONFIG, useValue: t.select });\n if (t.chips) providers.push({ provide: KT_CHIPS_CONFIG, useValue: t.chips });\n if (t.tabs) providers.push({ provide: KT_TABS_CONFIG, useValue: t.tabs });\n return makeEnvironmentProviders(providers);\n}\n\n/**\n * Fusion par famille de deux {@link KtTranslations} : `over` écrase `base`, clé par clé.\n * @param base Libellés de référence (ex. un dictionnaire de langue complet).\n * @param over Libellés prioritaires venant écraser `base`.\n * @returns Un nouveau {@link KtTranslations} fusionné, famille par famille.\n * @example mergeKtTranslations(KT_FR_TRANSLATIONS, { tabs: { nextLabel: 'Suivant' } })\n */\nexport function mergeKtTranslations(base: KtTranslations, over: KtTranslations): KtTranslations {\n return {\n field: { ...base.field, ...over.field },\n select: { ...base.select, ...over.select },\n chips: { ...base.chips, ...over.chips },\n tabs: { ...base.tabs, ...over.tabs },\n };\n}\n","import { EnvironmentProviders } from '@angular/core';\n\nimport { KtTranslations, mergeKtTranslations, provideKtTranslations } from './translations';\n\n/**\n * Dictionnaire **français** complet de tous les libellés de la lib. Figé et réutilisable :\n * importable tel quel (ex. pour le composer avec d'autres réglages) ou via {@link provideKtDefaultFR}.\n * Les libellés à pluriels sont des fonctions, pour laisser la grammaire au consommateur.\n */\nexport const KT_FR_TRANSLATIONS: KtTranslations = {\n field: {\n clearLabel: 'Effacer',\n helpLabel: 'Aide',\n numberParseError: 'La valeur saisie doit être un nombre valide.',\n },\n select: {\n emptyText: 'Aucune option',\n closeLabel: 'Fermer',\n filterLabel: 'Filtrer les options',\n // En français, 0 ET 1 prennent le singulier (≠ anglais où seul 1 est singulier).\n filterResultsText: (count) => (count <= 1 ? `${count} résultat` : `${count} résultats`),\n removeItemLabel: (itemLabel) => `Retirer ${itemLabel}`,\n selectedItemsLabel: (fieldLabel) =>\n fieldLabel ? `Éléments sélectionnés pour ${fieldLabel}` : 'Éléments sélectionnés',\n selectionSummaryText: (count) => (count <= 1 ? `${count} élément sélectionné` : `${count} éléments sélectionnés`),\n itemRemovedText: (itemLabel) => `${itemLabel} retiré`,\n selectionCountText: (count) => (count <= 1 ? `${count} sélectionné` : `${count} sélectionnés`),\n selectAllLabel: 'Tout sélectionner',\n clearAllLabel: 'Tout effacer',\n moreChipsLabel: (hiddenCount) => `+${hiddenCount} de plus`,\n lessChipsLabel: 'Afficher moins',\n truncatedResultsText: (max, total) =>\n `Affichage des ${max} premiers résultats sur ${total}. Affinez votre recherche pour en voir plus.`,\n truncatedResultsAnnouncement: (max, total) =>\n `${max} résultats affichés sur ${total}. Affinez votre recherche pour en voir plus.`,\n },\n chips: {\n removeItemLabel: (itemLabel) => `Retirer ${itemLabel}`,\n itemRemovedText: (itemLabel) => `${itemLabel} retiré`,\n moreLabel: (hiddenCount) => `+${hiddenCount} de plus`,\n lessLabel: 'Afficher moins',\n listLabel: 'Éléments sélectionnés',\n },\n tabs: {\n previousLabel: 'Onglets précédents',\n nextLabel: 'Onglets suivants',\n },\n};\n\n/**\n * Enregistre les libellés **français** par défaut sur toute la lib, en un seul appel.\n * `overrides` permet d'ajuster ponctuellement sans tout réécrire (fusion par famille,\n * cf. {@link mergeKtTranslations}). À placer dans les `providers` de `app.config.ts`.\n *\n * @param overrides Libellés à ajuster par-dessus le français (fusion par famille). Défaut : `{}`.\n * @returns Les `EnvironmentProviders` enregistrant le français (fusionné aux `overrides`).\n * @example\n * ```ts\n * // app.config.ts\n * providers: [\n * provideKtDefaultFR(), // tout en français\n * // ou en ajustant un libellé précis :\n * provideKtDefaultFR({ tabs: { nextLabel: 'Suivant' } }),\n * ]\n * ```\n */\nexport function provideKtDefaultFR(overrides: KtTranslations = {}): EnvironmentProviders {\n return provideKtTranslations(mergeKtTranslations(KT_FR_TRANSLATIONS, overrides));\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;AA2BA;;;;;;;;;;;;;AAaG;AACG,SAAU,qBAAqB,CAAC,CAAiB,EAAA;IACrD,MAAM,SAAS,GAAe,EAAE;IAChC,IAAI,CAAC,CAAC,KAAK;AAAE,QAAA,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5E,IAAI,CAAC,CAAC,MAAM;AAAE,QAAA,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/E,IAAI,CAAC,CAAC,KAAK;AAAE,QAAA,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5E,IAAI,CAAC,CAAC,IAAI;AAAE,QAAA,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACzE,IAAA,OAAO,wBAAwB,CAAC,SAAS,CAAC;AAC5C;AAEA;;;;;;AAMG;AACG,SAAU,mBAAmB,CAAC,IAAoB,EAAE,IAAoB,EAAA;IAC5E,OAAO;QACL,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE;QACvC,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;QAC1C,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE;QACvC,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE;KACrC;AACH;;AC5DA;;;;AAIG;AACI,MAAM,kBAAkB,GAAmB;AAChD,IAAA,KAAK,EAAE;AACL,QAAA,UAAU,EAAE,SAAS;AACrB,QAAA,SAAS,EAAE,MAAM;AACjB,QAAA,gBAAgB,EAAE,8CAA8C;AACjE,KAAA;AACD,IAAA,MAAM,EAAE;AACN,QAAA,SAAS,EAAE,eAAe;AAC1B,QAAA,UAAU,EAAE,QAAQ;AACpB,QAAA,WAAW,EAAE,qBAAqB;;QAElC,iBAAiB,EAAE,CAAC,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,CAAA,EAAG,KAAK,CAAA,SAAA,CAAW,GAAG,CAAA,EAAG,KAAK,CAAA,UAAA,CAAY,CAAC;QACvF,eAAe,EAAE,CAAC,SAAS,KAAK,CAAA,QAAA,EAAW,SAAS,CAAA,CAAE;AACtD,QAAA,kBAAkB,EAAE,CAAC,UAAU,KAC7B,UAAU,GAAG,8BAA8B,UAAU,CAAA,CAAE,GAAG,uBAAuB;QACnF,oBAAoB,EAAE,CAAC,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,CAAA,EAAG,KAAK,CAAA,oBAAA,CAAsB,GAAG,CAAA,EAAG,KAAK,CAAA,sBAAA,CAAwB,CAAC;QACjH,eAAe,EAAE,CAAC,SAAS,KAAK,CAAA,EAAG,SAAS,CAAA,OAAA,CAAS;QACrD,kBAAkB,EAAE,CAAC,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,CAAA,EAAG,KAAK,CAAA,YAAA,CAAc,GAAG,CAAA,EAAG,KAAK,CAAA,aAAA,CAAe,CAAC;AAC9F,QAAA,cAAc,EAAE,mBAAmB;AACnC,QAAA,aAAa,EAAE,cAAc;QAC7B,cAAc,EAAE,CAAC,WAAW,KAAK,CAAA,CAAA,EAAI,WAAW,CAAA,QAAA,CAAU;AAC1D,QAAA,cAAc,EAAE,gBAAgB;AAChC,QAAA,oBAAoB,EAAE,CAAC,GAAG,EAAE,KAAK,KAC/B,CAAA,cAAA,EAAiB,GAAG,CAAA,wBAAA,EAA2B,KAAK,CAAA,4CAAA,CAA8C;AACpG,QAAA,4BAA4B,EAAE,CAAC,GAAG,EAAE,KAAK,KACvC,CAAA,EAAG,GAAG,CAAA,wBAAA,EAA2B,KAAK,CAAA,4CAAA,CAA8C;AACvF,KAAA;AACD,IAAA,KAAK,EAAE;QACL,eAAe,EAAE,CAAC,SAAS,KAAK,CAAA,QAAA,EAAW,SAAS,CAAA,CAAE;QACtD,eAAe,EAAE,CAAC,SAAS,KAAK,CAAA,EAAG,SAAS,CAAA,OAAA,CAAS;QACrD,SAAS,EAAE,CAAC,WAAW,KAAK,CAAA,CAAA,EAAI,WAAW,CAAA,QAAA,CAAU;AACrD,QAAA,SAAS,EAAE,gBAAgB;AAC3B,QAAA,SAAS,EAAE,uBAAuB;AACnC,KAAA;AACD,IAAA,IAAI,EAAE;AACJ,QAAA,aAAa,EAAE,oBAAoB;AACnC,QAAA,SAAS,EAAE,kBAAkB;AAC9B,KAAA;;AAGH;;;;;;;;;;;;;;;;AAgBG;AACG,SAAU,kBAAkB,CAAC,SAAA,GAA4B,EAAE,EAAA;IAC/D,OAAO,qBAAqB,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AAClF;;ACpEA;;AAEG;;;;"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, inject, ElementRef, PLATFORM_ID, effect, model, InjectionToken, input, computed, isDevMode } from '@angular/core';
2
+ import { Directive, inject, PLATFORM_ID, effect, ElementRef, model, afterNextRender, isDevMode, InjectionToken, input, computed } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
4
  import { MenuItem, MenuTrigger } from '@angular/aria/menu';
5
5
  import { KtIdGenerator } from '@ktortu/aaa/cdk';
@@ -55,7 +55,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
55
55
  * ```
56
56
  */
57
57
  class KtMenuItem {
58
- host = inject(ElementRef).nativeElement;
59
58
  // Optionnel : `[ktMenuItem]` reste utilisable seul (simple thème) même hors d'un `[ngMenuItem]`.
60
59
  ariaItem = inject(MenuItem, { self: true, optional: true });
61
60
  platformId = inject(PLATFORM_ID);
@@ -72,7 +71,7 @@ class KtMenuItem {
72
71
  this.anchorName = undefined;
73
72
  return;
74
73
  }
75
- this.anchorName ??= `--kt-submenu-anchor-${this.idGen.generateId()}`;
74
+ this.anchorName ??= `--kt-submenu-anchor-${this.idGen.generateId('menu')}`;
76
75
  if (isPlatformBrowser(this.platformId)) {
77
76
  submenu.style.setProperty('position-anchor', this.anchorName);
78
77
  // Marque la surface comme un SOUS-menu : menu.css l'ouvre en latéral (inline-end) et non
@@ -131,11 +130,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
131
130
  * ```
132
131
  */
133
132
  class KtMenuTrigger {
134
- host = inject(ElementRef).nativeElement;
135
133
  ariaTrigger = inject(MenuTrigger, { self: true });
136
134
  platformId = inject(PLATFORM_ID);
137
135
  idGen = inject(KtIdGenerator);
138
- anchorName = `--kt-menu-anchor-${this.idGen.generateId()}`;
136
+ anchorName = `--kt-menu-anchor-${this.idGen.generateId('menu')}`;
139
137
  constructor() {
140
138
  effect(() => {
141
139
  const menuEl = this.ariaTrigger.menu()?.element;
@@ -171,7 +169,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
171
169
  * Elles ne touchent pas au `role` (laissé à l'input `role` d'aria, posé par le consommateur) pour
172
170
  * éviter tout conflit de binding sur `[attr.role]` ; la démo montre l'usage `role="menuitemradio"`.
173
171
  */
174
- /** Activation commune : un item de menu se déclenche au clic ET au clavier (Entrée / Espace). */
172
+ /**
173
+ * Activation commune : clic ET clavier (Entrée / Espace). Les deux liaisons coexistent VOLONTAIREMENT
174
+ * pour supporter un hôte non-`<button>` (ex. `<div ngMenuItem>`), où le clavier n'émet pas de `click`
175
+ * natif. Sur un `<button>`, `activate()` appelle `event.preventDefault()` sur le `keydown`, ce qui
176
+ * neutralise le `click` synthétique → une seule bascule (pas de double-toggle). Couvert en E2E.
177
+ */
175
178
  const ACTIVATION_HOST = {
176
179
  '(click)': 'activate($event)',
177
180
  '(keydown.enter)': 'activate($event)',
@@ -190,9 +193,21 @@ const ACTIVATION_HOST = {
190
193
  */
191
194
  class KtMenuItemCheckbox {
192
195
  ariaItem = inject(MenuItem, { self: true, optional: true });
193
- /** État coché, bidirectionnel : `[(checked)]`. */
196
+ host = inject(ElementRef).nativeElement;
197
+ /**
198
+ * État coché, bidirectionnel : `[(checked)]`.
199
+ * @default false
200
+ */
194
201
  checked = model(false, /* @ts-ignore */
195
202
  ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
203
+ constructor() {
204
+ // Garde-fou dev : sans `role="menuitemcheckbox"`, `aria-checked` est ignoré → item muet pour les SR.
205
+ afterNextRender(() => {
206
+ if (!isDevMode() || this.host.getAttribute('role') === 'menuitemcheckbox')
207
+ return;
208
+ console.warn('[ktMenuItemCheckbox] attend `role="menuitemcheckbox"` sur l’hôte, sinon `aria-checked` est inopérant.');
209
+ });
210
+ }
196
211
  activate(event) {
197
212
  if (this.ariaItem?.disabled())
198
213
  return;
@@ -213,7 +228,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
213
228
  '[attr.aria-checked]': 'checked()',
214
229
  },
215
230
  }]
216
- }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }] } });
231
+ }], ctorParameters: () => [], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }] } });
217
232
  /** Source de la valeur sélectionnée d'un groupe radio, exposée aux `[ktMenuItemRadio]` enfants. */
218
233
  const KT_MENU_RADIO_GROUP = new InjectionToken('KT_MENU_RADIO_GROUP');
219
234
  /**
@@ -230,7 +245,10 @@ const KT_MENU_RADIO_GROUP = new InjectionToken('KT_MENU_RADIO_GROUP');
230
245
  * ```
231
246
  */
232
247
  class KtMenuRadioGroup {
233
- /** Valeur sélectionnée du groupe, bidirectionnelle : `[(value)]`. */
248
+ /**
249
+ * Valeur sélectionnée du groupe, bidirectionnelle : `[(value)]`.
250
+ * @default null
251
+ */
234
252
  value = model(null, /* @ts-ignore */
235
253
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
236
254
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtMenuRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -257,7 +275,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
257
275
  class KtMenuItemRadio {
258
276
  ariaItem = inject(MenuItem, { self: true, optional: true });
259
277
  group = inject(KT_MENU_RADIO_GROUP, { optional: true });
260
- /** Valeur portée par ce radio (comparée à celle du groupe). */
278
+ host = inject(ElementRef).nativeElement;
279
+ /** Valeur portée par ce radio (comparée à celle du groupe). @default (requis) */
261
280
  value = input.required(/* @ts-ignore */
262
281
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
263
282
  checked = computed(() => this.group?.value() === this.value(), /* @ts-ignore */
@@ -268,6 +287,12 @@ class KtMenuItemRadio {
268
287
  console.warn('[ktMenuItemRadio] doit être placé dans un [ktMenuRadioGroup] : ' +
269
288
  'sans groupe, la sélection et `aria-checked` restent inopérants.');
270
289
  }
290
+ // Garde-fou dev : sans `role="menuitemradio"`, `aria-checked` est ignoré → item muet pour les SR.
291
+ afterNextRender(() => {
292
+ if (!isDevMode() || this.host.getAttribute('role') === 'menuitemradio')
293
+ return;
294
+ console.warn('[ktMenuItemRadio] attend `role="menuitemradio"` sur l’hôte, sinon `aria-checked` est inopérant.');
295
+ });
271
296
  }
272
297
  activate(event) {
273
298
  if (this.ariaItem?.disabled())
@@ -293,11 +318,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
293
318
 
294
319
  /**
295
320
  * Import ergonomique de toute la famille menu (couche de THÈME ktortu) en une fois :
296
- * `imports: [KT_MENU]` au lieu d'énumérer chaque directive. À composer avec les directives
321
+ * `imports: [KtMenuImports]` au lieu d'énumérer chaque directive. À composer avec les directives
297
322
  * d'`@angular/aria/menu` (`Menu`, `MenuItem`, `MenuTrigger`, `MenuContent`), qui apportent le
298
323
  * comportement accessible.
299
324
  */
300
- const KT_MENU = [
325
+ const KtMenuImports = [
301
326
  KtMenu,
302
327
  KtMenuItem,
303
328
  KtMenuSeparator,
@@ -311,5 +336,5 @@ const KT_MENU = [
311
336
  * Generated bundle index. Do not edit.
312
337
  */
313
338
 
314
- export { KT_MENU, KT_MENU_RADIO_GROUP, KtMenu, KtMenuItem, KtMenuItemCheckbox, KtMenuItemRadio, KtMenuRadioGroup, KtMenuSeparator, KtMenuTrigger };
339
+ export { KT_MENU_RADIO_GROUP, KtMenu, KtMenuImports, KtMenuItem, KtMenuItemCheckbox, KtMenuItemRadio, KtMenuRadioGroup, KtMenuSeparator, KtMenuTrigger };
315
340
  //# sourceMappingURL=ktortu-aaa-menu.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-menu.mjs","sources":["../../../../projects/ktortu/aaa/menu/menu.ts","../../../../projects/ktortu/aaa/menu/menu-trigger.ts","../../../../projects/ktortu/aaa/menu/menu-toggle.ts","../../../../projects/ktortu/aaa/menu/public-api.ts","../../../../projects/ktortu/aaa/menu/ktortu-aaa-menu.ts"],"sourcesContent":["import { Directive, ElementRef, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenu]` — marqueur de THÈME posé sur l'hôte `[ngMenu]` d'`@angular/aria`. Style UNIQUEMENT la\n * SURFACE du menu (fond, bord, ombre, rayon, padding, positionnement en dropdown ancré). N'ajoute\n * AUCUN rôle ni comportement : la sémantique (`role=\"menu\"`), le clavier, le focus roving, le\n * retour de focus au trigger, les sous-menus et le typeahead viennent intégralement d'aria\n * (`[ngMenu]`). Même philosophie que `[ktCard]` : la lib thématise, aria gère l'accessibilité.\n *\n * La directive n'a pas de logique propre : tout vit dans `menu.css` via le sélecteur `[ktMenu]`,\n * piloté par l'attribut `data-visible` qu'aria pose sur l'hôte (ouvert/fermé). Le positionnement\n * (CSS Anchor Positioning) est câblé par `[ktMenuTrigger]` (menu racine) et par `[ktMenuItem]`\n * (sous-menus) qui posent les `anchor-name` / `position-anchor`.\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Options</button>\n * <div ngMenu ktMenu #m=\"ngMenu\" (itemSelected)=\"onSelect($event)\" aria-label=\"Options\">\n * <button ngMenuItem ktMenuItem value=\"new\">Nouveau</button>\n * </div>\n * ```\n */\n@Directive({ selector: '[ktMenu]' })\nexport class KtMenu {}\n\n/**\n * `[ktMenuItem]` — marqueur de thème posé sur l'hôte `[ngMenuItem]`. Style la RANGÉE (hauteur de\n * cible tactile ≥44px, focus, survol, désactivé, item de sous-menu avec chevron). Comme `[ktMenu]`,\n * sans logique de sélection : l'état stylé est lu sur les attributs ARIA déjà posés par aria\n * (`:focus-visible` pour l'item actif via focus roving, `[aria-disabled]`, `[aria-expanded]`,\n * `[aria-haspopup]`, `[aria-checked]`).\n *\n * SEULE responsabilité technique : si l'item porte un sous-menu (`[submenu]` côté aria), on câble\n * l'ancrage CSS du sous-menu sur cet item (anchor-name sur l'item, position-anchor sur la surface\n * du sous-menu, marqueur `data-kt-submenu` pour l'ouvrir en latéral plutôt qu'en dropdown).\n *\n * EXIGENCE DE STRUCTURE (sous-menus) : le `[ngMenu]` d'un sous-menu DOIT être un DESCENDANT DOM du\n * menu parent (et non un frère). aria décide de maintenir un menu ouvert via `element.contains()`\n * sur la cible du focus/survol ; un sous-menu placé hors du sous-arbre du parent ferait croire à\n * aria que le focus a quitté le menu et provoquerait sa fermeture dès qu'on survole le sous-menu.\n * Le sous-menu reste rendu en place (top-layer non requis : `position: fixed` + ancrage CSS).\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem value=\"open\">Ouvrir</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItem]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuItem {\n private readonly host = inject(ElementRef).nativeElement;\n // Optionnel : `[ktMenuItem]` reste utilisable seul (simple thème) même hors d'un `[ngMenuItem]`.\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n // Alloué à la PREMIÈRE détection d'un sous-menu (et non pour chaque item) : un item simple ne\n // consomme pas d'anchor-name.\n protected anchorName: string | undefined;\n private readonly idGen = inject(KtIdGenerator);\n\n constructor() {\n if (!this.ariaItem) return;\n\n effect(() => {\n const submenu = this.ariaItem!.submenu()?.element;\n if (!submenu) {\n this.anchorName = undefined;\n return;\n }\n this.anchorName ??= `--kt-submenu-anchor-${this.idGen.generateId()}`;\n if (isPlatformBrowser(this.platformId)) {\n submenu.style.setProperty('position-anchor', this.anchorName);\n // Marque la surface comme un SOUS-menu : menu.css l'ouvre en latéral (inline-end) et non\n // sous l'item, avec ses propres fallbacks de débordement.\n submenu.setAttribute('data-kt-submenu', '');\n }\n });\n }\n}\n\n/**\n * `[ktMenuSeparator]` — filet de séparation entre groupes d'items. Pose `role=\"separator\"`\n * (sémantique standard d'un séparateur de menu) ; la ligne elle-même vit dans `menu.css`.\n *\n * @example\n * ```html\n * <hr ktMenuSeparator />\n * ```\n */\n@Directive({\n selector: '[ktMenuSeparator]',\n host: { role: 'separator' },\n})\nexport class KtMenuSeparator {}\n","import { Directive, ElementRef, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuTrigger as AriaMenuTrigger } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenuTrigger]` — sucre de POSITIONNEMENT pour le menu racine. À poser SUR le même élément\n * qu'`[ngMenuTrigger]` (le bouton qui ouvre le menu). Il ne réimplémente RIEN du comportement :\n * l'ouverture/fermeture, `aria-expanded`, Échap, le clic-dehors et le retour de focus sont gérés\n * par aria (`[ngMenuTrigger]`). Sa seule tâche : poser un `anchor-name` sur le trigger et le\n * `position-anchor` correspondant sur la surface du menu, pour que `menu.css` l'ancre en dropdown\n * (CSS Anchor Positioning), comme le popup du Select — sans CDK Overlay (styles scopés conservés,\n * et pas de coordination fragile avec le focus qu'aria pose à l'ouverture).\n *\n * Requiert `[ngMenuTrigger]` sur le même hôte (injecté en `self`).\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Fichier</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuTrigger]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuTrigger {\n private readonly host = inject(ElementRef).nativeElement;\n private readonly ariaTrigger = inject(AriaMenuTrigger, { self: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n private readonly idGen = inject(KtIdGenerator);\n protected readonly anchorName = `--kt-menu-anchor-${this.idGen.generateId()}`;\n\n constructor() {\n effect(() => {\n const menuEl = this.ariaTrigger.menu()?.element;\n if (!menuEl) return;\n if (isPlatformBrowser(this.platformId)) {\n menuEl.style.setProperty('position-anchor', this.anchorName);\n }\n });\n }\n}\n","import { Directive, InjectionToken, computed, inject, input, isDevMode, model } from '@angular/core';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\n\n/**\n * Items de menu À ÉTAT (checkbox / radio).\n *\n * Pourquoi ces directives existent : `@angular/aria` accepte bien `role=\"menuitemcheckbox\"` /\n * `\"menuitemradio\"` sur `[ngMenuItem]`, MAIS NE POSE JAMAIS `aria-checked` — son pattern se\n * contente d'émettre `itemSelected(value)`. Sans `aria-checked`, l'item coché est MUET pour les\n * lecteurs d'écran (échec WCAG 4.1.2). Ces directives comblent ce trou : elles possèdent l'état\n * coché, le bindent en `aria-checked`, et le basculent à l'activation (clic / Entrée / Espace).\n *\n * Elles ne touchent pas au `role` (laissé à l'input `role` d'aria, posé par le consommateur) pour\n * éviter tout conflit de binding sur `[attr.role]` ; la démo montre l'usage `role=\"menuitemradio\"`.\n */\n\n/** Activation commune : un item de menu se déclenche au clic ET au clavier (Entrée / Espace). */\nconst ACTIVATION_HOST = {\n '(click)': 'activate($event)',\n '(keydown.enter)': 'activate($event)',\n '(keydown.space)': 'activate($event)',\n} as const;\n\n/**\n * `[ktMenuItemCheckbox]` — case à cocher de menu (bascule indépendante). À poser sur un\n * `[ngMenuItem] role=\"menuitemcheckbox\"`. Bind `aria-checked` et bascule `checked` à l'activation.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemCheckbox role=\"menuitemcheckbox\" [(checked)]=\"wrap\">\n * Retour à la ligne\n * </button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemCheckbox]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemCheckbox {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n\n /** État coché, bidirectionnel : `[(checked)]`. */\n readonly checked = model<boolean>(false);\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.checked.update((v) => !v);\n }\n}\n\n/** Source de la valeur sélectionnée d'un groupe radio, exposée aux `[ktMenuItemRadio]` enfants. */\nexport const KT_MENU_RADIO_GROUP = new InjectionToken<KtMenuRadioGroup<unknown>>('KT_MENU_RADIO_GROUP');\n\n/**\n * `[ktMenuRadioGroup]` — coordinateur d'un groupe d'items radio mutuellement exclusifs. À poser sur\n * un conteneur englobant les `[ktMenuItemRadio]` (typiquement un `role=\"group\"`). Détient la valeur\n * sélectionnée ; chaque radio s'y compare pour son `aria-checked` et la met à jour à l'activation.\n *\n * @example\n * ```html\n * <div role=\"group\" ktMenuRadioGroup [(value)]=\"sortBy\" aria-label=\"Trier par\">\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Nom</button>\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'date'\">Date</button>\n * </div>\n * ```\n */\n@Directive({\n selector: '[ktMenuRadioGroup]',\n exportAs: 'ktMenuRadioGroup',\n providers: [{ provide: KT_MENU_RADIO_GROUP, useExisting: KtMenuRadioGroup }],\n})\nexport class KtMenuRadioGroup<V> {\n /** Valeur sélectionnée du groupe, bidirectionnelle : `[(value)]`. */\n readonly value = model<V | null>(null);\n}\n\n/**\n * `[ktMenuItemRadio]` — bouton radio de menu. À poser sur un `[ngMenuItem] role=\"menuitemradio\"`\n * dans un `[ktMenuRadioGroup]`. `aria-checked` reflète l'égalité avec la valeur du groupe ;\n * l'activation sélectionne cette valeur.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Trier par nom</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemRadio]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemRadio<V> {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly group = inject<KtMenuRadioGroup<V>>(KT_MENU_RADIO_GROUP, { optional: true });\n\n /** Valeur portée par ce radio (comparée à celle du groupe). */\n readonly value = input.required<V>();\n\n protected readonly checked = computed(() => this.group?.value() === this.value());\n\n constructor() {\n // Garde-fou dev : sans [ktMenuRadioGroup] parent, aria-checked reste figé (item muet pour les SR).\n if (isDevMode() && !this.group) {\n console.warn(\n '[ktMenuItemRadio] doit être placé dans un [ktMenuRadioGroup] : ' +\n 'sans groupe, la sélection et `aria-checked` restent inopérants.',\n );\n }\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.group?.value.set(this.value());\n }\n}\n","import { KtMenu, KtMenuItem, KtMenuSeparator } from './menu';\nimport { KtMenuTrigger } from './menu-trigger';\nimport { KtMenuItemCheckbox, KtMenuItemRadio, KtMenuRadioGroup } from './menu-toggle';\n\nexport * from './menu';\nexport * from './menu-trigger';\nexport * from './menu-toggle';\n\n/**\n * Import ergonomique de toute la famille menu (couche de THÈME ktortu) en une fois :\n * `imports: [KT_MENU]` au lieu d'énumérer chaque directive. À composer avec les directives\n * d'`@angular/aria/menu` (`Menu`, `MenuItem`, `MenuTrigger`, `MenuContent`), qui apportent le\n * comportement accessible.\n */\nexport const KT_MENU = [\n KtMenu,\n KtMenuItem,\n KtMenuSeparator,\n KtMenuTrigger,\n KtMenuItemCheckbox,\n KtMenuItemRadio,\n KtMenuRadioGroup,\n] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["AriaMenuItem","AriaMenuTrigger"],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-menu.mjs","sources":["../../../../projects/ktortu/aaa/menu/menu.ts","../../../../projects/ktortu/aaa/menu/menu-trigger.ts","../../../../projects/ktortu/aaa/menu/menu-toggle.ts","../../../../projects/ktortu/aaa/menu/public-api.ts","../../../../projects/ktortu/aaa/menu/ktortu-aaa-menu.ts"],"sourcesContent":["import { Directive, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenu]` — marqueur de THÈME posé sur l'hôte `[ngMenu]` d'`@angular/aria`. Style UNIQUEMENT la\n * SURFACE du menu (fond, bord, ombre, rayon, padding, positionnement en dropdown ancré). N'ajoute\n * AUCUN rôle ni comportement : la sémantique (`role=\"menu\"`), le clavier, le focus roving, le\n * retour de focus au trigger, les sous-menus et le typeahead viennent intégralement d'aria\n * (`[ngMenu]`). Même philosophie que `[ktCard]` : la lib thématise, aria gère l'accessibilité.\n *\n * La directive n'a pas de logique propre : tout vit dans `menu.css` via le sélecteur `[ktMenu]`,\n * piloté par l'attribut `data-visible` qu'aria pose sur l'hôte (ouvert/fermé). Le positionnement\n * (CSS Anchor Positioning) est câblé par `[ktMenuTrigger]` (menu racine) et par `[ktMenuItem]`\n * (sous-menus) qui posent les `anchor-name` / `position-anchor`.\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Options</button>\n * <div ngMenu ktMenu #m=\"ngMenu\" (itemSelected)=\"onSelect($event)\" aria-label=\"Options\">\n * <button ngMenuItem ktMenuItem value=\"new\">Nouveau</button>\n * </div>\n * ```\n */\n@Directive({ selector: '[ktMenu]' })\nexport class KtMenu {}\n\n/**\n * `[ktMenuItem]` — marqueur de thème posé sur l'hôte `[ngMenuItem]`. Style la RANGÉE (hauteur de\n * cible tactile ≥44px, focus, survol, désactivé, item de sous-menu avec chevron). Comme `[ktMenu]`,\n * sans logique de sélection : l'état stylé est lu sur les attributs ARIA déjà posés par aria\n * (`:focus-visible` pour l'item actif via focus roving, `[aria-disabled]`, `[aria-expanded]`,\n * `[aria-haspopup]`, `[aria-checked]`).\n *\n * SEULE responsabilité technique : si l'item porte un sous-menu (`[submenu]` côté aria), on câble\n * l'ancrage CSS du sous-menu sur cet item (anchor-name sur l'item, position-anchor sur la surface\n * du sous-menu, marqueur `data-kt-submenu` pour l'ouvrir en latéral plutôt qu'en dropdown).\n *\n * EXIGENCE DE STRUCTURE (sous-menus) : le `[ngMenu]` d'un sous-menu DOIT être un DESCENDANT DOM du\n * menu parent (et non un frère). aria décide de maintenir un menu ouvert via `element.contains()`\n * sur la cible du focus/survol ; un sous-menu placé hors du sous-arbre du parent ferait croire à\n * aria que le focus a quitté le menu et provoquerait sa fermeture dès qu'on survole le sous-menu.\n * Le sous-menu reste rendu en place (top-layer non requis : `position: fixed` + ancrage CSS).\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem value=\"open\">Ouvrir</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItem]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuItem {\n // Optionnel : `[ktMenuItem]` reste utilisable seul (simple thème) même hors d'un `[ngMenuItem]`.\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n // Alloué à la PREMIÈRE détection d'un sous-menu (et non pour chaque item) : un item simple ne\n // consomme pas d'anchor-name.\n protected anchorName: string | undefined;\n private readonly idGen = inject(KtIdGenerator);\n\n constructor() {\n if (!this.ariaItem) return;\n\n effect(() => {\n const submenu = this.ariaItem!.submenu()?.element;\n if (!submenu) {\n this.anchorName = undefined;\n return;\n }\n this.anchorName ??= `--kt-submenu-anchor-${this.idGen.generateId('menu')}`;\n if (isPlatformBrowser(this.platformId)) {\n submenu.style.setProperty('position-anchor', this.anchorName);\n // Marque la surface comme un SOUS-menu : menu.css l'ouvre en latéral (inline-end) et non\n // sous l'item, avec ses propres fallbacks de débordement.\n submenu.setAttribute('data-kt-submenu', '');\n }\n });\n }\n}\n\n/**\n * `[ktMenuSeparator]` — filet de séparation entre groupes d'items. Pose `role=\"separator\"`\n * (sémantique standard d'un séparateur de menu) ; la ligne elle-même vit dans `menu.css`.\n *\n * @example\n * ```html\n * <hr ktMenuSeparator />\n * ```\n */\n@Directive({\n selector: '[ktMenuSeparator]',\n host: { role: 'separator' },\n})\nexport class KtMenuSeparator {}\n","import { Directive, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuTrigger as AriaMenuTrigger } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenuTrigger]` — sucre de POSITIONNEMENT pour le menu racine. À poser SUR le même élément\n * qu'`[ngMenuTrigger]` (le bouton qui ouvre le menu). Il ne réimplémente RIEN du comportement :\n * l'ouverture/fermeture, `aria-expanded`, Échap, le clic-dehors et le retour de focus sont gérés\n * par aria (`[ngMenuTrigger]`). Sa seule tâche : poser un `anchor-name` sur le trigger et le\n * `position-anchor` correspondant sur la surface du menu, pour que `menu.css` l'ancre en dropdown\n * (CSS Anchor Positioning), comme le popup du Select — sans CDK Overlay (styles scopés conservés,\n * et pas de coordination fragile avec le focus qu'aria pose à l'ouverture).\n *\n * Requiert `[ngMenuTrigger]` sur le même hôte (injecté en `self`).\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Fichier</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuTrigger]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuTrigger {\n private readonly ariaTrigger = inject(AriaMenuTrigger, { self: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n private readonly idGen = inject(KtIdGenerator);\n protected readonly anchorName = `--kt-menu-anchor-${this.idGen.generateId('menu')}`;\n\n constructor() {\n effect(() => {\n const menuEl = this.ariaTrigger.menu()?.element;\n if (!menuEl) return;\n if (isPlatformBrowser(this.platformId)) {\n menuEl.style.setProperty('position-anchor', this.anchorName);\n }\n });\n }\n}\n","import { Directive, ElementRef, afterNextRender, computed, inject, input, InjectionToken, isDevMode, model } from '@angular/core';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\n\n/**\n * Items de menu À ÉTAT (checkbox / radio).\n *\n * Pourquoi ces directives existent : `@angular/aria` accepte bien `role=\"menuitemcheckbox\"` /\n * `\"menuitemradio\"` sur `[ngMenuItem]`, MAIS NE POSE JAMAIS `aria-checked` — son pattern se\n * contente d'émettre `itemSelected(value)`. Sans `aria-checked`, l'item coché est MUET pour les\n * lecteurs d'écran (échec WCAG 4.1.2). Ces directives comblent ce trou : elles possèdent l'état\n * coché, le bindent en `aria-checked`, et le basculent à l'activation (clic / Entrée / Espace).\n *\n * Elles ne touchent pas au `role` (laissé à l'input `role` d'aria, posé par le consommateur) pour\n * éviter tout conflit de binding sur `[attr.role]` ; la démo montre l'usage `role=\"menuitemradio\"`.\n */\n\n/**\n * Activation commune : clic ET clavier (Entrée / Espace). Les deux liaisons coexistent VOLONTAIREMENT\n * pour supporter un hôte non-`<button>` (ex. `<div ngMenuItem>`), où le clavier n'émet pas de `click`\n * natif. Sur un `<button>`, `activate()` appelle `event.preventDefault()` sur le `keydown`, ce qui\n * neutralise le `click` synthétique → une seule bascule (pas de double-toggle). Couvert en E2E.\n */\nconst ACTIVATION_HOST = {\n '(click)': 'activate($event)',\n '(keydown.enter)': 'activate($event)',\n '(keydown.space)': 'activate($event)',\n} as const;\n\n/**\n * `[ktMenuItemCheckbox]` — case à cocher de menu (bascule indépendante). À poser sur un\n * `[ngMenuItem] role=\"menuitemcheckbox\"`. Bind `aria-checked` et bascule `checked` à l'activation.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemCheckbox role=\"menuitemcheckbox\" [(checked)]=\"wrap\">\n * Retour à la ligne\n * </button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemCheckbox]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemCheckbox {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /**\n * État coché, bidirectionnel : `[(checked)]`.\n * @default false\n */\n readonly checked = model<boolean>(false);\n\n constructor() {\n // Garde-fou dev : sans `role=\"menuitemcheckbox\"`, `aria-checked` est ignoré → item muet pour les SR.\n afterNextRender(() => {\n if (!isDevMode() || this.host.getAttribute('role') === 'menuitemcheckbox') return;\n console.warn('[ktMenuItemCheckbox] attend `role=\"menuitemcheckbox\"` sur l’hôte, sinon `aria-checked` est inopérant.');\n });\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.checked.update((v) => !v);\n }\n}\n\n/** Source de la valeur sélectionnée d'un groupe radio, exposée aux `[ktMenuItemRadio]` enfants. */\nexport const KT_MENU_RADIO_GROUP = new InjectionToken<KtMenuRadioGroup<unknown>>('KT_MENU_RADIO_GROUP');\n\n/**\n * `[ktMenuRadioGroup]` — coordinateur d'un groupe d'items radio mutuellement exclusifs. À poser sur\n * un conteneur englobant les `[ktMenuItemRadio]` (typiquement un `role=\"group\"`). Détient la valeur\n * sélectionnée ; chaque radio s'y compare pour son `aria-checked` et la met à jour à l'activation.\n *\n * @example\n * ```html\n * <div role=\"group\" ktMenuRadioGroup [(value)]=\"sortBy\" aria-label=\"Trier par\">\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Nom</button>\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'date'\">Date</button>\n * </div>\n * ```\n */\n@Directive({\n selector: '[ktMenuRadioGroup]',\n exportAs: 'ktMenuRadioGroup',\n providers: [{ provide: KT_MENU_RADIO_GROUP, useExisting: KtMenuRadioGroup }],\n})\nexport class KtMenuRadioGroup<V> {\n /**\n * Valeur sélectionnée du groupe, bidirectionnelle : `[(value)]`.\n * @default null\n */\n readonly value = model<V | null>(null);\n}\n\n/**\n * `[ktMenuItemRadio]` — bouton radio de menu. À poser sur un `[ngMenuItem] role=\"menuitemradio\"`\n * dans un `[ktMenuRadioGroup]`. `aria-checked` reflète l'égalité avec la valeur du groupe ;\n * l'activation sélectionne cette valeur.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Trier par nom</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemRadio]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemRadio<V> {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly group = inject<KtMenuRadioGroup<V>>(KT_MENU_RADIO_GROUP, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** Valeur portée par ce radio (comparée à celle du groupe). @default (requis) */\n readonly value = input.required<V>();\n\n protected readonly checked = computed(() => this.group?.value() === this.value());\n\n constructor() {\n // Garde-fou dev : sans [ktMenuRadioGroup] parent, aria-checked reste figé (item muet pour les SR).\n if (isDevMode() && !this.group) {\n console.warn(\n '[ktMenuItemRadio] doit être placé dans un [ktMenuRadioGroup] : ' +\n 'sans groupe, la sélection et `aria-checked` restent inopérants.',\n );\n }\n // Garde-fou dev : sans `role=\"menuitemradio\"`, `aria-checked` est ignoré → item muet pour les SR.\n afterNextRender(() => {\n if (!isDevMode() || this.host.getAttribute('role') === 'menuitemradio') return;\n console.warn('[ktMenuItemRadio] attend `role=\"menuitemradio\"` sur l’hôte, sinon `aria-checked` est inopérant.');\n });\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.group?.value.set(this.value());\n }\n}\n","import { KtMenu, KtMenuItem, KtMenuSeparator } from './menu';\nimport { KtMenuTrigger } from './menu-trigger';\nimport { KtMenuItemCheckbox, KtMenuItemRadio, KtMenuRadioGroup } from './menu-toggle';\n\nexport * from './menu';\nexport * from './menu-trigger';\nexport * from './menu-toggle';\n\n/**\n * Import ergonomique de toute la famille menu (couche de THÈME ktortu) en une fois :\n * `imports: [KtMenuImports]` au lieu d'énumérer chaque directive. À composer avec les directives\n * d'`@angular/aria/menu` (`Menu`, `MenuItem`, `MenuTrigger`, `MenuContent`), qui apportent le\n * comportement accessible.\n */\nexport const KtMenuImports = [\n KtMenu,\n KtMenuItem,\n KtMenuSeparator,\n KtMenuTrigger,\n KtMenuItemCheckbox,\n KtMenuItemRadio,\n KtMenuRadioGroup,\n] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["AriaMenuItem","AriaMenuTrigger"],"mappings":"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"}