@ktortu/aaa 0.9.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.
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-tabs.mjs","sources":["../../../../projects/ktortu/aaa/tabs/tab-scroller.ts","../../../../projects/ktortu/aaa/tabs/tabs-config.ts","../../../../projects/ktortu/aaa/tabs/tab-scroller-pager.ts","../../../../projects/ktortu/aaa/tabs/tab-scroll.ts","../../../../projects/ktortu/aaa/tabs/ktortu-aaa-tabs.ts"],"sourcesContent":["import {\n Directive,\n DOCUMENT,\n DestroyRef,\n ElementRef,\n afterNextRender,\n afterRenderEffect,\n computed,\n inject,\n signal,\n} from '@angular/core';\nimport { TabList } from '@angular/aria/tabs';\n\n/** Cible de défilement calculée en phase de lecture, appliquée en phase d'écriture. */\ntype ScrollTarget = { axis: 'left' | 'top'; value: number };\n\n/** Métriques de débordement de la liste d'onglets, mesurées de façon impérative. */\ninterface ScrollMetrics {\n /** Reste-t-il du contenu hors-champ côté début (gauche en LTR / droite en RTL ; haut si vertical) ? */\n canStart: boolean;\n /** Reste-t-il du contenu hors-champ côté fin ? */\n canEnd: boolean;\n /** La liste déborde-t-elle (taille de défilement > taille visible) ? */\n overflowing: boolean;\n /** Axe courant. */\n vertical: boolean;\n}\n\nconst EMPTY_METRICS: ScrollMetrics = { canStart: false, canEnd: false, overflowing: false, vertical: false };\n\n/** Tolérance d'1px : arrondis sub-pixel, zoom navigateur. */\nconst EPS = 1;\n\n/** Fraction de la zone visible parcourue par un « page » de pagination (chevauchement d'un onglet). */\nconst PAGE_RATIO = 0.8;\n\n/**\n * Pilote le **débordement** d'une liste d'onglets `@angular/aria/tabs` (cf. tabs.css). Deux rôles :\n *\n * 1. **Scroll-into-view** : amène l'onglet sélectionné dans la vue au montage et lors d'un changement\n * de sélection programmatique (ce que le CSS seul ne fait pas), en respectant `prefers-reduced-motion`.\n * 2. **Pagination** : expose un état réactif (`canScrollStart()`, `canScrollEnd()`, `overflowing()`,\n * `orientation()`) et une méthode `scrollByPage()`, pour brancher des boutons chevrons (cf. le\n * composant clé-en-main `KtTabScrollerPager`, ou des boutons custom via `exportAs: 'ktTabScroller'`).\n *\n * Opt-in, à poser sur `[ngTabList]` :\n *\n * @example\n * ```html\n * <ul ngTabList ktTabScroller #s=\"ktTabScroller\" [(selectedTab)]=\"tab\"> … </ul>\n * <button (click)=\"s.scrollByPage('start')\" [disabled]=\"!s.canScrollStart()\">‹</button>\n * ```\n */\n@Directive({\n selector: '[ngTabList][ktTabScroller]',\n exportAs: 'ktTabScroller',\n host: {\n // Marqueur CSS : pagination réactive active → la scrollbar native est masquée (cf. tabs.css).\n '[attr.data-kt-tab-scroller]': '\"\"',\n },\n})\nexport class KtTabScroller {\n private readonly list = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly tabList = inject(TabList);\n private readonly doc = inject(DOCUMENT);\n private readonly destroyRef = inject(DestroyRef);\n /** Préférence « réduire les animations », lue à chaud (absente possible en SSR/jsdom). */\n private readonly reducedMotion = this.doc.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)');\n\n /**\n * Métriques de débordement. Mises à jour UNIQUEMENT depuis des callbacks navigateur\n * (`afterNextRender`, `scroll`, `ResizeObserver`, `MutationObserver`) — jamais depuis le corps\n * d'un effet réactif, pour rester conforme (pas d'écriture de signal dans un effet).\n */\n private readonly metrics = signal<ScrollMetrics>(EMPTY_METRICS);\n\n /** Reste-t-il du contenu hors-champ côté début (à brancher sur le bouton « précédent ») ? */\n readonly canScrollStart = computed(() => this.metrics().canStart);\n /** Reste-t-il du contenu hors-champ côté fin (à brancher sur le bouton « suivant ») ? */\n readonly canScrollEnd = computed(() => this.metrics().canEnd);\n /** La liste déborde-t-elle ? (utile pour masquer toute la barre de pagination). */\n readonly overflowing = computed(() => this.metrics().overflowing);\n /** Axe courant, dérivé de l'orientation `@angular/aria`. */\n readonly orientation = computed<'horizontal' | 'vertical'>(() =>\n this.metrics().vertical ? 'vertical' : 'horizontal',\n );\n\n constructor() {\n // 1. Scroll-into-view (phasé : mesure en `earlyRead`, scroll en `write`, pas de reflow forcé).\n // Le `scrollTo` émet un événement `scroll` → remeasure via le listener (aucun `set` ici).\n afterRenderEffect({\n earlyRead: () => this.computeScrollTarget(),\n write: (target) => {\n const t = target();\n if (!t) return;\n this.list.scrollTo({ [t.axis]: t.value, behavior: this.scrollBehavior() });\n },\n });\n\n // 2. Mesure initiale + observateurs (browser-only ; `afterNextRender` ne tourne pas en SSR).\n afterNextRender(() => {\n const view = this.doc.defaultView;\n if (!view) return;\n this.remeasure();\n\n const onScroll = () => this.remeasure();\n this.list.addEventListener('scroll', onScroll, { passive: true });\n\n // ResizeObserver : viewport ET contenu (les onglets qui changent de largeur). Référencé en\n // global (jamais instancié en SSR puisque dans `afterNextRender`).\n const ro = new ResizeObserver(() => this.remeasure());\n const observeAll = () => {\n ro.disconnect();\n ro.observe(this.list);\n for (const child of Array.from(this.list.children)) ro.observe(child);\n };\n observeAll();\n\n // MutationObserver : onglets ajoutés/retirés (@for) → re-observer + remeasure.\n const mo = new MutationObserver(() => {\n observeAll();\n this.remeasure();\n });\n mo.observe(this.list, { childList: true });\n\n this.destroyRef.onDestroy(() => {\n this.list.removeEventListener('scroll', onScroll);\n ro.disconnect();\n mo.disconnect();\n });\n });\n }\n\n /**\n * Fait défiler d'environ une « page » (≈ {@link PAGE_RATIO} de la zone visible) vers le début ou la\n * fin. `behavior` respecte `prefers-reduced-motion`. Le navigateur sature au min/max ; les boutons\n * câblés sont `disabled` aux extrémités, donc l'appel n'a pas lieu à vide.\n */\n scrollByPage(dir: 'start' | 'end'): void {\n const el = this.list;\n const sign = dir === 'end' ? 1 : -1;\n if (this.metrics().vertical) {\n el.scrollBy({ top: sign * el.clientHeight * PAGE_RATIO, behavior: this.scrollBehavior() });\n return;\n }\n // RTL : le sens d'écriture inverse le signe de `scrollLeft`.\n const rtlSign = this.isRtl() ? -1 : 1;\n el.scrollBy({ left: sign * rtlSign * el.clientWidth * PAGE_RATIO, behavior: this.scrollBehavior() });\n }\n\n /** Met à jour le signal de métriques. Appelé depuis des callbacks navigateur (jamais un effet). */\n private remeasure(): void {\n this.metrics.set(this.measure());\n }\n\n /** Mesure le débordement (lecture pure de géométrie, n'écrit rien). */\n private measure(): ScrollMetrics {\n const el = this.list;\n const vertical = this.isVertical();\n\n if (vertical) {\n const { scrollTop, scrollHeight, clientHeight } = el;\n const max = scrollHeight - clientHeight;\n return { vertical: true, overflowing: max > EPS, canStart: scrollTop > EPS, canEnd: scrollTop < max - EPS };\n }\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const max = scrollWidth - clientWidth;\n const rtl = this.isRtl();\n // Distances logiques indépendantes du signe de `scrollLeft` (négatif en RTL sur les navigateurs récents).\n const fromStart = rtl ? -scrollLeft : scrollLeft;\n const fromEnd = max - Math.abs(scrollLeft);\n return { vertical: false, overflowing: max > EPS, canStart: fromStart > EPS, canEnd: fromEnd > EPS };\n }\n\n private isVertical(): boolean {\n return (\n this.list.getAttribute('aria-orientation') === 'vertical' || this.list.getAttribute('orientation') === 'vertical'\n );\n }\n\n private isRtl(): boolean {\n return this.doc.defaultView?.getComputedStyle(this.list).direction === 'rtl';\n }\n\n /**\n * `auto` si l'utilisateur a demandé à réduire les animations (WCAG 2.3.3 AAA), `smooth` sinon.\n * Passer un `behavior` explicite à `scrollTo`/`scrollBy` écrase la propriété CSS `scroll-behavior`,\n * d'où la nécessité de répliquer ici la décision que le CSS prend via `prefers-reduced-motion`.\n */\n private scrollBehavior(): ScrollBehavior {\n return this.reducedMotion?.matches ? 'auto' : 'smooth';\n }\n\n /**\n * Calcule l'offset à atteindre pour amener l'onglet actif dans la vue, ou `null` s'il est déjà\n * visible (ou s'il n'y a aucun onglet sélectionné). Lit `selectedTab()` → dépendance réactive.\n */\n private computeScrollTarget(): ScrollTarget | null {\n const selected = this.tabList.findTab(this.tabList.selectedTab())?.element;\n if (!selected) return null;\n\n const container = this.list;\n if (this.isVertical()) {\n const selectedTop = selected.offsetTop;\n const selectedBottom = selectedTop + selected.offsetHeight;\n const containerTop = container.scrollTop;\n const containerBottom = containerTop + container.clientHeight;\n\n if (selectedTop < containerTop) return { axis: 'top', value: selectedTop };\n if (selectedBottom > containerBottom) return { axis: 'top', value: selectedBottom - container.clientHeight };\n return null;\n }\n\n const selectedLeft = selected.offsetLeft;\n const selectedRight = selectedLeft + selected.offsetWidth;\n const containerLeft = container.scrollLeft;\n const containerRight = containerLeft + container.clientWidth;\n\n if (selectedLeft < containerLeft) return { axis: 'left', value: selectedLeft };\n if (selectedRight > containerRight) return { axis: 'left', value: selectedRight - container.clientWidth };\n return null;\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/** Défauts applicables à tous les `kt-tab-scroller` (surchargeables par instance via les inputs).\n Lib neutre i18n : les textes sont fournis par le consommateur (ici en une fois).\n Fourni en `Partial` : un consommateur n'override que ce qu'il veut.\n Calqué sur `KT_FIELD_CONFIG`/`KT_CHIPS_CONFIG`. */\nexport interface KtTabsConfig {\n /** `aria-label` du chevron de pagination « onglets précédents ». Défaut : `'Previous tabs'`. */\n previousLabel: string;\n /** `aria-label` du chevron de pagination « onglets suivants ». Défaut : `'Next tabs'`. */\n nextLabel: string;\n}\n\nexport const KT_TABS_CONFIG = new InjectionToken<Partial<KtTabsConfig>>('KT_TABS_CONFIG');\n","import {\n ChangeDetectionStrategy,\n Component,\n DOCUMENT,\n ElementRef,\n afterNextRender,\n computed,\n contentChild,\n inject,\n input,\n signal,\n} from '@angular/core';\n\nimport { KtButton } from '@ktortu/aaa/button';\nimport { KtTabScroller } from './tab-scroller';\nimport { KT_TABS_CONFIG } from './tabs-config';\n\n/**\n * Composant clé-en-main : enveloppe une liste d'onglets `@angular/aria/tabs` débordante de deux\n * **chevrons de pagination** accessibles (style Material). Il ne contient aucune logique propre :\n * il lit l'état réactif de la directive headless {@link KtTabScroller} (à poser sur le `ngTabList`\n * projeté) et câble deux boutons {@link KtButton}.\n *\n * @example\n * ```html\n * <kt-tab-scroller>\n * <ul ngTabList ktTabScroller [(selectedTab)]=\"tab\">\n * @for (t of tabs; track t) { <li ngTab [value]=\"t\">…</li> }\n * </ul>\n * </kt-tab-scroller>\n * ```\n */\n@Component({\n selector: 'kt-tab-scroller',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [KtButton],\n host: {\n '[attr.data-orientation]': 'scroller().orientation()',\n '[attr.data-overflowing]': 'scroller().overflowing() ? \"\" : null',\n },\n template: `\n <button\n ktButton\n type=\"button\"\n mode=\"text\"\n iconOnly\n class=\"kt-tab-scroller__chevron kt-tab-scroller__chevron--start\"\n [icon]=\"startIcon()\"\n [ariaLabel]=\"previousLabel()\"\n [disabled]=\"!scroller().canScrollStart()\"\n (click)=\"scroller().scrollByPage('start')\"\n ></button>\n\n <div class=\"kt-tab-scroller__viewport\">\n <ng-content select=\"[ngTabList]\" />\n </div>\n\n <button\n ktButton\n type=\"button\"\n mode=\"text\"\n iconOnly\n class=\"kt-tab-scroller__chevron kt-tab-scroller__chevron--end\"\n [icon]=\"endIcon()\"\n [ariaLabel]=\"nextLabel()\"\n [disabled]=\"!scroller().canScrollEnd()\"\n (click)=\"scroller().scrollByPage('end')\"\n ></button>\n `,\n})\nexport class KtTabScrollerPager {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly doc = inject(DOCUMENT);\n\n /** Défauts d'app (provider global `KT_TABS_CONFIG`) ; les inputs ci-dessous priment dessus. */\n private readonly config = inject(KT_TABS_CONFIG, { optional: true });\n\n /** La directive headless posée sur le `ngTabList` projeté (`descendants` car dans le viewport). */\n protected readonly scroller = contentChild.required(KtTabScroller, { descendants: true });\n\n /** Sens de lecture (figé au montage : le RTL inverse les chevrons gauche/droite). */\n private readonly dir = signal<'ltr' | 'rtl'>('ltr');\n\n protected readonly startIcon = computed(() => {\n if (this.scroller().orientation() === 'vertical') return 'expand_less';\n return this.dir() === 'rtl' ? 'chevron_right' : 'chevron_left';\n });\n protected readonly endIcon = computed(() => {\n if (this.scroller().orientation() === 'vertical') return 'expand_more';\n return this.dir() === 'rtl' ? 'chevron_left' : 'chevron_right';\n });\n\n /**\n * Noms accessibles des chevrons (`aria-label`). Résolution `input ?? KT_TABS_CONFIG ?? défaut`,\n * comme partout dans la lib (cf. `KT_FIELD_CONFIG`/`KT_CHIPS_CONFIG`). Défauts en anglais : lib\n * neutre i18n, les textes sont fournis par le consommateur (provider global ou binding).\n */\n /**\n * `aria-label` du chevron « onglets précédents ».\n * @default config.previousLabel ?? 'Previous tabs'\n */\n readonly previousLabel = input(this.config?.previousLabel ?? 'Previous tabs');\n /**\n * `aria-label` du chevron « onglets suivants ».\n * @default config.nextLabel ?? 'Next tabs'\n */\n readonly nextLabel = input(this.config?.nextLabel ?? 'Next tabs');\n\n constructor() {\n afterNextRender(() => {\n if (this.doc.defaultView?.getComputedStyle(this.host).direction === 'rtl') this.dir.set('rtl');\n });\n }\n}\n","import { Directive } from '@angular/core';\nimport { KtTabScroller } from './tab-scroller';\n\n/**\n * @deprecated Utilisez {@link KtTabScroller} (sélecteur `[ktTabScroller]`) : même défilement de\n * l'onglet sélectionné dans la vue, plus la pagination par chevrons. `ktTabScroll` reste un **alias\n * fonctionnel** (il applique `KtTabScroller` comme directive hôte) et sera retiré dans une version\n * majeure ultérieure.\n *\n * Note : depuis cette version, une liste pilotée par `ktTabScroll`/`ktTabScroller` masque sa\n * scrollbar native par défaut (les chevrons / ombres prennent le relais). Réafficher la barre via\n * `--tab-scroller-scrollbar: thin`.\n */\n@Directive({\n selector: '[ngTabList][ktTabScroll]',\n exportAs: 'ktTabScroll',\n hostDirectives: [KtTabScroller],\n})\nexport class KtTabScroll {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.KtTabScroller"],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-tabs.mjs","sources":["../../../../projects/ktortu/aaa/tabs/tab-scroller.ts","../../../../projects/ktortu/aaa/tabs/tabs-config.ts","../../../../projects/ktortu/aaa/tabs/tab-scroller-pager.ts","../../../../projects/ktortu/aaa/tabs/tab-scroll.ts","../../../../projects/ktortu/aaa/tabs/ktortu-aaa-tabs.ts"],"sourcesContent":["import {\n Directive,\n DOCUMENT,\n DestroyRef,\n ElementRef,\n afterNextRender,\n afterRenderEffect,\n computed,\n inject,\n signal,\n} from '@angular/core';\nimport { TabList } from '@angular/aria/tabs';\n\n/** Cible de défilement calculée en phase de lecture, appliquée en phase d'écriture. */\ninterface ScrollTarget {\n axis: 'left' | 'top';\n value: number;\n}\n\n/** Métriques de débordement de la liste d'onglets, mesurées de façon impérative. */\ninterface ScrollMetrics {\n /** Reste-t-il du contenu hors-champ côté début (gauche en LTR / droite en RTL ; haut si vertical) ? */\n canStart: boolean;\n /** Reste-t-il du contenu hors-champ côté fin ? */\n canEnd: boolean;\n /** La liste déborde-t-elle (taille de défilement > taille visible) ? */\n overflowing: boolean;\n /** Axe courant. */\n vertical: boolean;\n}\n\nconst EMPTY_METRICS: ScrollMetrics = { canStart: false, canEnd: false, overflowing: false, vertical: false };\n\n/** Tolérance d'1px : arrondis sub-pixel, zoom navigateur. */\nconst EPS = 1;\n\n/** Fraction de la zone visible parcourue par un « page » de pagination (chevauchement d'un onglet). */\nconst PAGE_RATIO = 0.8;\n\n/**\n * Pilote le **débordement** d'une liste d'onglets `@angular/aria/tabs` (cf. tabs.css). Deux rôles :\n *\n * 1. **Scroll-into-view** : amène l'onglet sélectionné dans la vue au montage et lors d'un changement\n * de sélection programmatique (ce que le CSS seul ne fait pas), en respectant `prefers-reduced-motion`.\n * 2. **Pagination** : expose un état réactif (`canScrollStart()`, `canScrollEnd()`, `overflowing()`,\n * `orientation()`) et une méthode `scrollByPage()`, pour brancher des boutons chevrons (cf. le\n * composant clé-en-main `KtTabScrollerPager`, ou des boutons custom via `exportAs: 'ktTabScroller'`).\n *\n * Opt-in, à poser sur `[ngTabList]` :\n *\n * @example\n * ```html\n * <ul ngTabList ktTabScroller #s=\"ktTabScroller\" [(selectedTab)]=\"tab\"> … </ul>\n * <button (click)=\"s.scrollByPage('start')\" [disabled]=\"!s.canScrollStart()\">‹</button>\n * ```\n */\n@Directive({\n selector: '[ngTabList][ktTabScroller]',\n exportAs: 'ktTabScroller',\n host: {\n // Marqueur CSS : pagination réactive active → la scrollbar native est masquée (cf. tabs.css).\n '[attr.data-kt-tab-scroller]': '\"\"',\n },\n})\nexport class KtTabScroller {\n private readonly list = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly tabList = inject(TabList);\n private readonly doc = inject(DOCUMENT);\n private readonly destroyRef = inject(DestroyRef);\n /** Préférence « réduire les animations », lue à chaud (absente possible en SSR/jsdom). */\n private readonly reducedMotion = this.doc.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)');\n\n /**\n * Métriques de débordement. Mises à jour UNIQUEMENT depuis des callbacks navigateur\n * (`afterNextRender`, `scroll`, `ResizeObserver`, `MutationObserver`) — jamais depuis le corps\n * d'un effet réactif, pour rester conforme (pas d'écriture de signal dans un effet).\n */\n private readonly metrics = signal<ScrollMetrics>(EMPTY_METRICS);\n\n /** Reste-t-il du contenu hors-champ côté début (à brancher sur le bouton « précédent ») ? */\n readonly canScrollStart = computed(() => this.metrics().canStart);\n /** Reste-t-il du contenu hors-champ côté fin (à brancher sur le bouton « suivant ») ? */\n readonly canScrollEnd = computed(() => this.metrics().canEnd);\n /** La liste déborde-t-elle ? (utile pour masquer toute la barre de pagination). */\n readonly overflowing = computed(() => this.metrics().overflowing);\n /** Axe courant, dérivé de l'orientation `@angular/aria`. */\n readonly orientation = computed<'horizontal' | 'vertical'>(() =>\n this.metrics().vertical ? 'vertical' : 'horizontal',\n );\n\n constructor() {\n // 1. Scroll-into-view (phasé : mesure en `earlyRead`, scroll en `write`, pas de reflow forcé).\n // Le `scrollTo` émet un événement `scroll` → remeasure via le listener (aucun `set` ici).\n afterRenderEffect({\n earlyRead: () => this.computeScrollTarget(),\n write: (target) => {\n const t = target();\n if (!t) return;\n this.list.scrollTo({ [t.axis]: t.value, behavior: this.scrollBehavior() });\n },\n });\n\n // 2. Mesure initiale + observateurs (browser-only ; `afterNextRender` ne tourne pas en SSR).\n afterNextRender(() => {\n const view = this.doc.defaultView;\n if (!view) return;\n this.remeasure();\n\n const onScroll = () => this.remeasure();\n this.list.addEventListener('scroll', onScroll, { passive: true });\n\n // ResizeObserver : viewport ET contenu (les onglets qui changent de largeur). Référencé en\n // global (jamais instancié en SSR puisque dans `afterNextRender`).\n const ro = new ResizeObserver(() => this.remeasure());\n const observeAll = () => {\n ro.disconnect();\n ro.observe(this.list);\n for (const child of Array.from(this.list.children)) ro.observe(child);\n };\n observeAll();\n\n // MutationObserver : onglets ajoutés/retirés (@for) → re-observer + remeasure.\n const mo = new MutationObserver(() => {\n observeAll();\n this.remeasure();\n });\n mo.observe(this.list, { childList: true });\n\n this.destroyRef.onDestroy(() => {\n this.list.removeEventListener('scroll', onScroll);\n ro.disconnect();\n mo.disconnect();\n });\n });\n }\n\n /**\n * Fait défiler d'environ une « page » (≈ {@link PAGE_RATIO} de la zone visible) vers le début ou la\n * fin. `behavior` respecte `prefers-reduced-motion`. Le navigateur sature au min/max ; les boutons\n * câblés sont `disabled` aux extrémités, donc l'appel n'a pas lieu à vide.\n */\n scrollByPage(dir: 'start' | 'end'): void {\n const el = this.list;\n const sign = dir === 'end' ? 1 : -1;\n if (this.metrics().vertical) {\n el.scrollBy({ top: sign * el.clientHeight * PAGE_RATIO, behavior: this.scrollBehavior() });\n return;\n }\n // RTL : le sens d'écriture inverse le signe de `scrollLeft`.\n const rtlSign = this.isRtl() ? -1 : 1;\n el.scrollBy({ left: sign * rtlSign * el.clientWidth * PAGE_RATIO, behavior: this.scrollBehavior() });\n }\n\n /** Met à jour le signal de métriques. Appelé depuis des callbacks navigateur (jamais un effet). */\n private remeasure(): void {\n this.metrics.set(this.measure());\n }\n\n /** Mesure le débordement (lecture pure de géométrie, n'écrit rien). */\n private measure(): ScrollMetrics {\n const el = this.list;\n const vertical = this.isVertical();\n\n if (vertical) {\n const { scrollTop, scrollHeight, clientHeight } = el;\n const max = scrollHeight - clientHeight;\n return { vertical: true, overflowing: max > EPS, canStart: scrollTop > EPS, canEnd: scrollTop < max - EPS };\n }\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const max = scrollWidth - clientWidth;\n const rtl = this.isRtl();\n // Distances logiques indépendantes du signe de `scrollLeft` (négatif en RTL sur les navigateurs récents).\n const fromStart = rtl ? -scrollLeft : scrollLeft;\n const fromEnd = max - Math.abs(scrollLeft);\n return { vertical: false, overflowing: max > EPS, canStart: fromStart > EPS, canEnd: fromEnd > EPS };\n }\n\n private isVertical(): boolean {\n return (\n this.list.getAttribute('aria-orientation') === 'vertical' || this.list.getAttribute('orientation') === 'vertical'\n );\n }\n\n private isRtl(): boolean {\n return this.doc.defaultView?.getComputedStyle(this.list).direction === 'rtl';\n }\n\n /**\n * `auto` si l'utilisateur a demandé à réduire les animations (WCAG 2.3.3 AAA), `smooth` sinon.\n * Passer un `behavior` explicite à `scrollTo`/`scrollBy` écrase la propriété CSS `scroll-behavior`,\n * d'où la nécessité de répliquer ici la décision que le CSS prend via `prefers-reduced-motion`.\n */\n private scrollBehavior(): ScrollBehavior {\n return this.reducedMotion?.matches ? 'auto' : 'smooth';\n }\n\n /**\n * Calcule l'offset à atteindre pour amener l'onglet actif dans la vue, ou `null` s'il est déjà\n * visible (ou s'il n'y a aucun onglet sélectionné). Lit `selectedTab()` → dépendance réactive.\n */\n private computeScrollTarget(): ScrollTarget | null {\n const selected = this.tabList.findTab(this.tabList.selectedTab())?.element;\n if (!selected) return null;\n\n const container = this.list;\n if (this.isVertical()) {\n const selectedTop = selected.offsetTop;\n const selectedBottom = selectedTop + selected.offsetHeight;\n const containerTop = container.scrollTop;\n const containerBottom = containerTop + container.clientHeight;\n\n if (selectedTop < containerTop) return { axis: 'top', value: selectedTop };\n if (selectedBottom > containerBottom) return { axis: 'top', value: selectedBottom - container.clientHeight };\n return null;\n }\n\n const selectedLeft = selected.offsetLeft;\n const selectedRight = selectedLeft + selected.offsetWidth;\n const containerLeft = container.scrollLeft;\n const containerRight = containerLeft + container.clientWidth;\n\n if (selectedLeft < containerLeft) return { axis: 'left', value: selectedLeft };\n if (selectedRight > containerRight) return { axis: 'left', value: selectedRight - container.clientWidth };\n return null;\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/** Défauts applicables à tous les `kt-tab-scroller` (surchargeables par instance via les inputs).\n Lib neutre i18n : les textes sont fournis par le consommateur (ici en une fois).\n Fourni en `Partial` : un consommateur n'override que ce qu'il veut.\n Calqué sur `KT_FIELD_CONFIG`/`KT_CHIPS_CONFIG`. */\nexport interface KtTabsConfig {\n /** `aria-label` du chevron de pagination « onglets précédents ». Défaut : `'Previous tabs'`. */\n previousLabel: string;\n /** `aria-label` du chevron de pagination « onglets suivants ». Défaut : `'Next tabs'`. */\n nextLabel: string;\n}\n\nexport const KT_TABS_CONFIG = new InjectionToken<Partial<KtTabsConfig>>('KT_TABS_CONFIG');\n","import {\n ChangeDetectionStrategy,\n Component,\n DOCUMENT,\n ElementRef,\n afterNextRender,\n computed,\n contentChild,\n inject,\n input,\n signal,\n} from '@angular/core';\n\nimport { KtButton } from '@ktortu/aaa/button';\nimport { KtTabScroller } from './tab-scroller';\nimport { KT_TABS_CONFIG } from './tabs-config';\n\n/**\n * Composant clé-en-main : enveloppe une liste d'onglets `@angular/aria/tabs` débordante de deux\n * **chevrons de pagination** accessibles (style Material). Il ne contient aucune logique propre :\n * il lit l'état réactif de la directive headless {@link KtTabScroller} (à poser sur le `ngTabList`\n * projeté) et câble deux boutons {@link KtButton}.\n *\n * @example\n * ```html\n * <kt-tab-scroller>\n * <ul ngTabList ktTabScroller [(selectedTab)]=\"tab\">\n * @for (t of tabs; track t) { <li ngTab [value]=\"t\">…</li> }\n * </ul>\n * </kt-tab-scroller>\n * ```\n */\n@Component({\n selector: 'kt-tab-scroller',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [KtButton],\n host: {\n '[attr.data-orientation]': 'scroller().orientation()',\n '[attr.data-overflowing]': 'scroller().overflowing() ? \"\" : null',\n },\n template: `\n <button\n ktButton\n type=\"button\"\n mode=\"text\"\n iconOnly\n class=\"kt-tab-scroller__chevron kt-tab-scroller__chevron--start\"\n [icon]=\"startIcon()\"\n [ariaLabel]=\"previousLabel()\"\n [disabled]=\"!scroller().canScrollStart()\"\n (click)=\"scroller().scrollByPage('start')\"\n ></button>\n\n <div class=\"kt-tab-scroller__viewport\">\n <ng-content select=\"[ngTabList]\" />\n </div>\n\n <button\n ktButton\n type=\"button\"\n mode=\"text\"\n iconOnly\n class=\"kt-tab-scroller__chevron kt-tab-scroller__chevron--end\"\n [icon]=\"endIcon()\"\n [ariaLabel]=\"nextLabel()\"\n [disabled]=\"!scroller().canScrollEnd()\"\n (click)=\"scroller().scrollByPage('end')\"\n ></button>\n `,\n})\nexport class KtTabScrollerPager {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly doc = inject(DOCUMENT);\n\n /** Défauts d'app (provider global `KT_TABS_CONFIG`) ; les inputs ci-dessous priment dessus. */\n private readonly config = inject(KT_TABS_CONFIG, { optional: true });\n\n /** La directive headless posée sur le `ngTabList` projeté (`descendants` car dans le viewport). */\n protected readonly scroller = contentChild.required(KtTabScroller, { descendants: true });\n\n /** Sens de lecture (figé au montage : le RTL inverse les chevrons gauche/droite). */\n private readonly dir = signal<'ltr' | 'rtl'>('ltr');\n\n protected readonly startIcon = computed(() => {\n if (this.scroller().orientation() === 'vertical') return 'expand_less';\n return this.dir() === 'rtl' ? 'chevron_right' : 'chevron_left';\n });\n protected readonly endIcon = computed(() => {\n if (this.scroller().orientation() === 'vertical') return 'expand_more';\n return this.dir() === 'rtl' ? 'chevron_left' : 'chevron_right';\n });\n\n /**\n * Noms accessibles des chevrons (`aria-label`). Résolution `input ?? KT_TABS_CONFIG ?? défaut`,\n * comme partout dans la lib (cf. `KT_FIELD_CONFIG`/`KT_CHIPS_CONFIG`). Défauts en anglais : lib\n * neutre i18n, les textes sont fournis par le consommateur (provider global ou binding).\n */\n /**\n * `aria-label` du chevron « onglets précédents ».\n * @default config.previousLabel ?? 'Previous tabs'\n */\n readonly previousLabel = input(this.config?.previousLabel ?? 'Previous tabs');\n /**\n * `aria-label` du chevron « onglets suivants ».\n * @default config.nextLabel ?? 'Next tabs'\n */\n readonly nextLabel = input(this.config?.nextLabel ?? 'Next tabs');\n\n constructor() {\n afterNextRender(() => {\n if (this.doc.defaultView?.getComputedStyle(this.host).direction === 'rtl') this.dir.set('rtl');\n });\n }\n}\n","import { Directive } from '@angular/core';\nimport { KtTabScroller } from './tab-scroller';\n\n/**\n * @deprecated Utilisez {@link KtTabScroller} (sélecteur `[ktTabScroller]`) : même défilement de\n * l'onglet sélectionné dans la vue, plus la pagination par chevrons. `ktTabScroll` reste un **alias\n * fonctionnel** (il applique `KtTabScroller` comme directive hôte) et sera retiré dans une version\n * majeure ultérieure.\n *\n * Note : depuis cette version, une liste pilotée par `ktTabScroll`/`ktTabScroller` masque sa\n * scrollbar native par défaut (les chevrons / ombres prennent le relais). Réafficher la barre via\n * `--tab-scroller-scrollbar: thin`.\n */\n@Directive({\n selector: '[ngTabList][ktTabScroll]',\n exportAs: 'ktTabScroll',\n hostDirectives: [KtTabScroller],\n})\nexport class KtTabScroll {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.KtTabScroller"],"mappings":"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"}
@@ -5,6 +5,7 @@ export * from '@ktortu/aaa/dialog';
5
5
  export * from '@ktortu/aaa/forms';
6
6
  export * from '@ktortu/aaa/i18n';
7
7
  export * from '@ktortu/aaa/menu';
8
+ export * from '@ktortu/aaa/snackbar';
8
9
  export * from '@ktortu/aaa/tabs';
9
10
  export * from '@ktortu/aaa/tooltip';
10
11
 
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa.mjs","sources":["../../../../projects/ktortu/aaa/src/public-api.ts","../../../../projects/ktortu/aaa/src/ktortu-aaa.ts"],"sourcesContent":["/*\n * Public API Surface of @ktortu/aaa\n */\nexport * from '@ktortu/aaa/button';\nexport * from '@ktortu/aaa/card';\nexport * from '@ktortu/aaa/cdk';\nexport * from '@ktortu/aaa/dialog';\nexport * from '@ktortu/aaa/forms';\nexport * from '@ktortu/aaa/i18n';\nexport * from '@ktortu/aaa/menu';\nexport * from '@ktortu/aaa/tabs';\nexport * from '@ktortu/aaa/tooltip';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;AAAA;;AAEG;;ACFH;;AAEG"}
1
+ {"version":3,"file":"ktortu-aaa.mjs","sources":["../../../../projects/ktortu/aaa/src/public-api.ts","../../../../projects/ktortu/aaa/src/ktortu-aaa.ts"],"sourcesContent":["/*\n * Public API Surface of @ktortu/aaa\n */\nexport * from '@ktortu/aaa/button';\nexport * from '@ktortu/aaa/card';\nexport * from '@ktortu/aaa/cdk';\nexport * from '@ktortu/aaa/dialog';\nexport * from '@ktortu/aaa/forms';\nexport * from '@ktortu/aaa/i18n';\nexport * from '@ktortu/aaa/menu';\nexport * from '@ktortu/aaa/snackbar';\nexport * from '@ktortu/aaa/tabs';\nexport * from '@ktortu/aaa/tooltip';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;AAAA;;AAEG;;ACFH;;AAEG"}
@@ -21,14 +21,6 @@
21
21
  color: var(--field-required-color, var(--kt-danger, #8c1d18));
22
22
  }
23
23
 
24
- /* Disposition des options : pilotable par token (colonne par défaut, ligne possible). */
25
- .kt-checkbox-group__options {
26
- display: flex;
27
- flex-direction: var(--checkbox-group-direction, column);
28
- flex-wrap: wrap;
29
- gap: var(--checkbox-group-gap, 0.25rem);
30
- }
31
-
32
24
  .kt-checkbox-group__hint {
33
25
  margin: 0;
34
26
  font-size: var(--field-hint-font-size, 0.8125rem);
@@ -52,6 +52,11 @@
52
52
  /* Texte réservé aux lecteurs d'écran (live region des retraits). */
53
53
  .kt-chip-list__status {
54
54
  position: absolute;
55
+ /* Ancrée au coin de l'hôte : sans `inset`, un `position: absolute` sans ancêtre positionné
56
+ retombe sur sa position STATIQUE (bas de la liste). Dans un conteneur scrollable, ce 1px
57
+ atterrit loin dans le document et crée une 2ᵉ barre de défilement fantôme. */
58
+ inset-block-start: 0;
59
+ inset-inline-start: 0;
55
60
  inline-size: 1px;
56
61
  block-size: 1px;
57
62
  padding: 0;
@@ -21,14 +21,6 @@
21
21
  color: var(--field-required-color, var(--kt-danger, #8c1d18));
22
22
  }
23
23
 
24
- /* Disposition des options : pilotable par token (colonne par défaut, ligne possible). */
25
- .kt-radio-group__options {
26
- display: flex;
27
- flex-direction: var(--radio-group-direction, row);
28
- flex-wrap: wrap;
29
- gap: var(--radio-group-gap, 1rem);
30
- }
31
-
32
24
  .kt-radio-group__hint {
33
25
  margin: 0;
34
26
  font-size: var(--field-hint-font-size, 0.8125rem);
@@ -52,4 +44,4 @@
52
44
  animation: none;
53
45
  }
54
46
  }
55
- }
47
+ }
@@ -221,7 +221,9 @@
221
221
  font-size: var(--field-font-size, 1rem);
222
222
  font-weight: 400;
223
223
  color: var(--field-hint-color, #5f6368);
224
- transition: transform 0.15s ease, color 0.15s ease;
224
+ transition:
225
+ transform 0.15s ease,
226
+ color 0.15s ease;
225
227
  }
226
228
 
227
229
  /* RTL : `transform-origin` n'accepte pas de mot-clé logique → on bascule l'origine de la mise à
@@ -0,0 +1,46 @@
1
+ @layer kt-aaa.components {
2
+ /* Spinner « validation asynchrone en cours » par défaut.
3
+
4
+ 100 % CSS (pseudo-élément, aucun markup) et piloté par l'attribut `[data-pending]` posé par les
5
+ contrôles quand leur input `pending` est vrai (l'accessibilité passe par `aria-busy`, indépendant).
6
+
7
+ Entièrement surchargeable / suppressible côté consommateur :
8
+ - retailler/recolorer via les tokens `--field-spinner-*` ;
9
+ - supprimer : kt-text-field .kt-field-box[data-pending]::after { content: none; }
10
+ - remplacer par sa propre affordance (le hook `[data-pending]` reste disponible). */
11
+ @keyframes kt-field-spin {
12
+ to {
13
+ transform: rotate(1turn);
14
+ }
15
+ }
16
+
17
+ .kt-field-box[data-pending]::after,
18
+ .kt-switch-field[data-pending] .kt-switch-row::after,
19
+ .kt-checkbox-field[data-pending] .kt-checkbox::after,
20
+ .kt-radio-group-field[data-pending] .kt-radio-group__legend::after,
21
+ .kt-checkbox-group-field[data-pending] .kt-checkbox-group__legend::after {
22
+ content: '';
23
+ display: inline-block;
24
+ flex: none;
25
+ vertical-align: middle;
26
+ inline-size: var(--field-spinner-size, 1em);
27
+ block-size: var(--field-spinner-size, 1em);
28
+ margin-inline-start: var(--field-spinner-gap, 0.5rem);
29
+ border: var(--field-spinner-width, 2px) solid var(--field-spinner-track, currentColor);
30
+ border-top-color: var(--field-spinner-color, var(--field-border-color-focus, #1a73e8));
31
+ border-radius: 50%;
32
+ opacity: var(--field-spinner-opacity, 0.7);
33
+ animation: kt-field-spin var(--field-spinner-duration, 0.7s) linear infinite;
34
+ }
35
+
36
+ /* Respect de prefers-reduced-motion : on fige la rotation (le cercle reste visible comme indicateur). */
37
+ @media (prefers-reduced-motion: reduce) {
38
+ .kt-field-box[data-pending]::after,
39
+ .kt-switch-field[data-pending] .kt-switch-row::after,
40
+ .kt-checkbox-field[data-pending] .kt-checkbox::after,
41
+ .kt-radio-group-field[data-pending] .kt-radio-group__legend::after,
42
+ .kt-checkbox-group-field[data-pending] .kt-checkbox-group__legend::after {
43
+ animation: none;
44
+ }
45
+ }
46
+ }
@@ -178,6 +178,10 @@
178
178
  /* Texte réservé aux lecteurs d'écran (live region du nombre de résultats). */
179
179
  .kt-select__sr-only {
180
180
  position: absolute;
181
+ /* Ancrée au coin : sinon ce 1px `absolute` retombe sur sa position statique et, dans un
182
+ conteneur scrollable, étire le document → barre de défilement fantôme. */
183
+ inset-block-start: 0;
184
+ inset-inline-start: 0;
181
185
  inline-size: 1px;
182
186
  block-size: 1px;
183
187
  padding: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktortu/aaa",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Bibliothèque de composants Angular headless + thémés par tokens CSS.",
5
5
  "keywords": [
6
6
  "angular",
@@ -85,6 +85,10 @@
85
85
  "types": "./types/ktortu-aaa-menu.d.ts",
86
86
  "default": "./fesm2022/ktortu-aaa-menu.mjs"
87
87
  },
88
+ "./snackbar": {
89
+ "types": "./types/ktortu-aaa-snackbar.d.ts",
90
+ "default": "./fesm2022/ktortu-aaa-snackbar.mjs"
91
+ },
88
92
  "./tabs": {
89
93
  "types": "./types/ktortu-aaa-tabs.d.ts",
90
94
  "default": "./fesm2022/ktortu-aaa-tabs.mjs"
@@ -0,0 +1,53 @@
1
+ /* Design tokens de la snackbar (service KtSnackbar) — contrat public de theming.
2
+
3
+ PRINCIPE (identique au reste de la lib) : tout DÉRIVE du socle --kt-*. Surcharger un --kt-*
4
+ rethématise la snackbar en même temps que les champs, boutons et dialog. Chaque valeur reste
5
+ surchargeable en redéclarant son token --snackbar-* (sur :root ou un conteneur de spécificité ≥).
6
+
7
+ La snackbar est posée sur une « surface inversée » (fond sombre, texte clair) façon Material —
8
+ d'où des on-* dérivés de --kt-on-surface / --kt-surface, qui suivent automatiquement un thème.
9
+ Les variantes ne recolorisent PAS la pastille (le texte reste à contraste AAA) : elles ne pilotent
10
+ que l'accent + l'icône. Un thème qui veut des pastilles pleinement colorées surcharge simplement
11
+ --snackbar-bg / --snackbar-fg sous [data-variant='…']. */
12
+ @layer kt-aaa.tokens {
13
+ :root {
14
+ /* Surface inversée : fond ~ on-surface, texte ~ surface (contraste ≈ 16:1 → AAA 1.4.6). */
15
+ --snackbar-bg: color-mix(in oklab, var(--kt-on-surface, #1f1f1f) 92%, var(--kt-surface, #ffffff));
16
+ --snackbar-fg: var(--kt-surface, #ffffff);
17
+
18
+ --snackbar-radius: var(--kt-control-radius, 8px);
19
+ --snackbar-shadow: 0 4px 12px rgb(0 0 0 / 24%);
20
+
21
+ /* Géométrie interne */
22
+ --snackbar-padding-block: 0.625rem;
23
+ --snackbar-padding-inline: 1rem;
24
+ --snackbar-gap: 0.75rem;
25
+ --snackbar-font: var(--kt-control-font, 1rem);
26
+
27
+ /* Placement : distance au bord du viewport, avec marge de sécurité mobile (encoches / barres). */
28
+ --snackbar-offset: 16px;
29
+ --snackbar-offset-bottom: calc(var(--snackbar-offset) + env(safe-area-inset-bottom, 0px));
30
+ --snackbar-offset-top: calc(var(--snackbar-offset) + env(safe-area-inset-top, 0px));
31
+ --snackbar-max-inline-size: 560px;
32
+
33
+ /* Bouton de fermeture : cible 44px (AAA 2.5.5). */
34
+ --snackbar-close-size: var(--kt-control-height, 44px);
35
+ --snackbar-close-icon-size: 1rem;
36
+
37
+ /* Variantes : accent (icône) clair pour contraster sur la pastille sombre (≥ 3:1), et glyphe
38
+ (ligature de la police d'icônes --kt-icon-font). Couleur + FORME : la forme distincte est
39
+ l'indice non coloré (WCAG 1.4.1). Surchargez accent/glyphe pour rebrander une variante. */
40
+ --snackbar-icon-size: 1.25rem;
41
+ --snackbar-accent-info: #8ab4f8;
42
+ --snackbar-accent-success: #81c995;
43
+ --snackbar-accent-warning: #fdd663;
44
+ --snackbar-accent-error: #f28b82;
45
+ --snackbar-glyph-info: 'info';
46
+ --snackbar-glyph-success: 'check_circle';
47
+ --snackbar-glyph-warning: 'warning';
48
+ --snackbar-glyph-error: 'error';
49
+
50
+ /* Animation d'entrée/sortie. Token de durée surchargeable ; neutralisée en prefers-reduced-motion. */
51
+ --snackbar-anim-duration: 150ms;
52
+ }
53
+ }
@@ -0,0 +1,175 @@
1
+ @layer kt-aaa.components {
2
+ /* Styles de la snackbar (conteneur monté dans un overlay CDK).
3
+ Global : l'élément est ajouté hors de toute vue (overlay top-layer).
4
+ Tokens (--snackbar-*) définis dans snackbar-tokens.css (chargé avant ce fichier).
5
+
6
+ Requiert côté hôte : '@angular/cdk/overlay-prebuilt.css' (positionnement de l'overlay) ET
7
+ '@angular/cdk/a11y-prebuilt.css' (masque l'élément du LiveAnnouncer qui porte l'annonce). */
8
+
9
+ /* Le pane d'overlay ne capte pas les clics hors de la snackbar (feedback NON bloquant). */
10
+ .kt-snackbar-pane {
11
+ pointer-events: none;
12
+ max-inline-size: 100vw;
13
+ }
14
+
15
+ .kt-snackbar {
16
+ pointer-events: auto; /* la snackbar elle-même reste survolable / cliquable */
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--snackbar-gap);
20
+ box-sizing: border-box;
21
+
22
+ margin: var(--snackbar-offset);
23
+ inline-size: max-content;
24
+ max-inline-size: min(var(--snackbar-max-inline-size), 100vw - 2 * var(--snackbar-offset));
25
+
26
+ padding-block: var(--snackbar-padding-block);
27
+ padding-inline: var(--snackbar-padding-inline);
28
+
29
+ border-radius: var(--snackbar-radius);
30
+ background: var(--snackbar-bg);
31
+ color: var(--snackbar-fg);
32
+ box-shadow: var(--snackbar-shadow);
33
+
34
+ font-size: var(--snackbar-font);
35
+ line-height: 1.4;
36
+ }
37
+
38
+ /* Marge de sécurité mobile (encoches / barres système) selon le bord d'ancrage. */
39
+ .kt-snackbar-pane--bottom .kt-snackbar {
40
+ margin-block-end: var(--snackbar-offset-bottom);
41
+ }
42
+ .kt-snackbar-pane--top .kt-snackbar {
43
+ margin-block-start: var(--snackbar-offset-top);
44
+ }
45
+
46
+ .kt-snackbar__message {
47
+ flex: 1 1 auto;
48
+ min-inline-size: 0; /* autorise le retour à la ligne d'un message long */
49
+ }
50
+
51
+ /* ============ ICÔNE DE VARIANTE (décorative, aria-hidden) ============ */
52
+ /* Couleur = accent de la variante ; glyphe = ligature de la police d'icônes (comme [ktButton]).
53
+ Si la police d'icônes est absente, la ligature dégrade en texte (même comportement que le bouton). */
54
+ .kt-snackbar__icon {
55
+ flex: none;
56
+ display: inline-grid;
57
+ place-items: center;
58
+ color: var(--snackbar-accent, currentColor);
59
+ }
60
+ .kt-snackbar__icon::before {
61
+ content: var(--snackbar-glyph, '');
62
+ font-family: var(--kt-icon-font, 'Material Symbols Outlined');
63
+ font-weight: var(--kt-icon-font-weight, normal);
64
+ font-style: normal;
65
+ font-size: var(--snackbar-icon-size, 1.25rem);
66
+ line-height: 1;
67
+ font-feature-settings: 'liga';
68
+ -webkit-font-smoothing: antialiased;
69
+ }
70
+
71
+ /* Mapping variante → accent + glyphe (un thème surcharge accent/glyphe, ou bg/fg pour recolorer). */
72
+ .kt-snackbar[data-variant='info'] {
73
+ --snackbar-accent: var(--snackbar-accent-info);
74
+ --snackbar-glyph: var(--snackbar-glyph-info);
75
+ }
76
+ .kt-snackbar[data-variant='success'] {
77
+ --snackbar-accent: var(--snackbar-accent-success);
78
+ --snackbar-glyph: var(--snackbar-glyph-success);
79
+ }
80
+ .kt-snackbar[data-variant='warning'] {
81
+ --snackbar-accent: var(--snackbar-accent-warning);
82
+ --snackbar-glyph: var(--snackbar-glyph-warning);
83
+ }
84
+ .kt-snackbar[data-variant='error'] {
85
+ --snackbar-accent: var(--snackbar-accent-error);
86
+ --snackbar-glyph: var(--snackbar-glyph-error);
87
+ }
88
+
89
+ /* ============ BOUTON DE FERMETURE ============ */
90
+ /* Cible 44px, croix dessinée en CSS (zéro dépendance à une police d'icônes). */
91
+ .kt-snackbar__close {
92
+ position: relative;
93
+ flex: none;
94
+ inline-size: var(--snackbar-close-size);
95
+ block-size: var(--snackbar-close-size);
96
+ /* compense optiquement le padding inline du conteneur, sans réduire la cible de 44px */
97
+ margin-inline-end: calc(-0.5 * var(--snackbar-padding-inline));
98
+
99
+ display: inline-grid;
100
+ place-items: center;
101
+ padding: 0;
102
+ border: 0;
103
+ border-radius: 50%;
104
+ background: transparent;
105
+ color: inherit;
106
+ cursor: pointer;
107
+ }
108
+
109
+ .kt-snackbar__close::before,
110
+ .kt-snackbar__close::after {
111
+ content: '';
112
+ position: absolute;
113
+ inline-size: var(--snackbar-close-icon-size);
114
+ block-size: 2px;
115
+ border-radius: 2px;
116
+ background: currentColor;
117
+ translate: 0 0;
118
+ }
119
+ .kt-snackbar__close::before {
120
+ rotate: 45deg;
121
+ }
122
+ .kt-snackbar__close::after {
123
+ rotate: -45deg;
124
+ }
125
+
126
+ .kt-snackbar__close:hover {
127
+ background: color-mix(in srgb, currentColor 14%, transparent);
128
+ }
129
+ .kt-snackbar__close:focus-visible {
130
+ outline: var(--kt-focus-ring-width, 2px) solid currentColor;
131
+ outline-offset: 2px;
132
+ }
133
+
134
+ /* ============ ANIMATION D'ENTRÉE / SORTIE ============ */
135
+ /* Entrée par keyframe (glissement depuis le bord), sortie par transition (classe --leaving).
136
+ Toutes deux sont activées UNIQUEMENT hors prefers-reduced-motion → en reduced-motion la durée
137
+ de transition est nulle, donc playExit() démonte instantanément (WCAG 2.3.3). */
138
+ @media (prefers-reduced-motion: no-preference) {
139
+ .kt-snackbar {
140
+ transition:
141
+ opacity var(--snackbar-anim-duration) ease,
142
+ translate var(--snackbar-anim-duration) ease;
143
+ }
144
+ .kt-snackbar-pane--bottom .kt-snackbar {
145
+ animation: kt-snackbar-in-bottom var(--snackbar-anim-duration) ease-out;
146
+ }
147
+ .kt-snackbar-pane--top .kt-snackbar {
148
+ animation: kt-snackbar-in-top var(--snackbar-anim-duration) ease-out;
149
+ }
150
+
151
+ /* Sortie : on fond + on glisse vers le bord d'ancrage. */
152
+ .kt-snackbar--leaving {
153
+ opacity: 0;
154
+ }
155
+ .kt-snackbar-pane--bottom .kt-snackbar--leaving {
156
+ translate: 0 100%;
157
+ }
158
+ .kt-snackbar-pane--top .kt-snackbar--leaving {
159
+ translate: 0 -100%;
160
+ }
161
+ }
162
+
163
+ @keyframes kt-snackbar-in-bottom {
164
+ from {
165
+ opacity: 0;
166
+ translate: 0 100%;
167
+ }
168
+ }
169
+ @keyframes kt-snackbar-in-top {
170
+ from {
171
+ opacity: 0;
172
+ translate: 0 -100%;
173
+ }
174
+ }
175
+ }
package/styles/forms.css CHANGED
@@ -6,6 +6,7 @@
6
6
  @import '../forms/styles/tokens.css';
7
7
  @import '../forms/chips/tokens.css';
8
8
  @import '../forms/styles/field-box.css';
9
+ @import '../forms/styles/field-pending.css';
9
10
  @import '../forms/switch/switch.css';
10
11
  @import '../forms/checkbox/checkbox.css';
11
12
  @import '../forms/checkbox/checkbox-group.css';
@@ -0,0 +1,9 @@
1
+ @layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
2
+
3
+ /* Snackbar (service KtSnackbar, overlay CDK). Nécessite le socle (@ktortu/aaa/foundation.css),
4
+ ET côté hôte l'overlay CDK + le masquage du LiveAnnouncer :
5
+ @import '@angular/cdk/overlay-prebuilt.css';
6
+ @import '@angular/cdk/a11y-prebuilt.css';
7
+ @import '@ktortu/aaa/snackbar.css'; */
8
+ @import '../snackbar/snackbar-tokens.css';
9
+ @import '../snackbar/snackbar.css';
package/styles/styles.css CHANGED
@@ -22,3 +22,4 @@
22
22
  @import './tooltip.css';
23
23
  @import './dialog.css';
24
24
  @import './menu.css';
25
+ @import './snackbar.css';