|
1 | 1 | <script lang="ts" setup> |
2 | 2 | import { computed, onMounted, onUnmounted, provide, ref, toRef } from 'vue' |
| 3 | +import { FocusTrap } from 'focus-trap-vue' |
3 | 4 |
|
4 | 5 | import type { DsfrLanguageSelectorElement } from '../DsfrLanguageSelector/DsfrLanguageSelector.vue' |
5 | 6 | import DsfrLanguageSelector from '../DsfrLanguageSelector/DsfrLanguageSelector.vue' |
@@ -164,8 +165,6 @@ provide(registerNavigationLinkKey, () => { |
164 | 165 | :data-fr-opened="showMenu" |
165 | 166 | aria-controls="header-navigation" |
166 | 167 | aria-haspopup="dialog" |
167 | | - :aria-label="menuLabel" |
168 | | - :title="menuLabel" |
169 | 168 | data-testid="open-menu-btn" |
170 | 169 | @click.prevent.stop="showMenu()" |
171 | 170 | > |
@@ -245,63 +244,74 @@ provide(registerNavigationLinkKey, () => { |
245 | 244 | </div> |
246 | 245 | </div> |
247 | 246 | </div> |
248 | | - <div |
249 | | - v-if="showSearch || isWithSlotNav || (quickLinks && quickLinks.length) || languageSelector" |
250 | | - id="header-navigation" |
251 | | - class="fr-header__menu fr-modal" |
252 | | - :class="{ 'fr-modal--opened': modalOpened }" |
253 | | - :aria-label="menuModalLabel" |
254 | | - role="dialog" |
255 | | - aria-modal="true" |
| 247 | + <FocusTrap |
| 248 | + v-if="modalOpened" |
| 249 | + :active="modalOpened" |
| 250 | + :focus-trap-options="{ |
| 251 | + initialFocus: '#close-button', |
| 252 | + fallbackFocus: '#close-button', |
| 253 | + escapeDeactivates: true, |
| 254 | + clickOutsideDeactivates: true, |
| 255 | + returnFocusOnDeactivate: true, |
| 256 | + }" |
256 | 257 | > |
257 | | - <div class="fr-container"> |
258 | | - <button |
259 | | - id="close-button" |
260 | | - class="fr-btn fr-btn--close" |
261 | | - aria-controls="header-navigation" |
262 | | - data-testid="close-modal-btn" |
263 | | - @click.prevent.stop="hideModal()" |
264 | | - > |
265 | | - {{ closeMenuModalLabel }} |
266 | | - </button> |
267 | | - <div class="fr-header__menu-links"> |
268 | | - <template v-if="languageSelector"> |
269 | | - <DsfrLanguageSelector |
270 | | - v-bind="languageSelector" |
271 | | - @select="languageSelector.currentLanguage = $event.codeIso" |
| 258 | + <div |
| 259 | + v-if="(showSearch || isWithSlotNav || (quickLinks && quickLinks.length) || languageSelector) && modalOpened" |
| 260 | + id="header-navigation" |
| 261 | + class="fr-header__menu fr-modal fr-modal--opened" |
| 262 | + :aria-label="menuModalLabel" |
| 263 | + role="dialog" |
| 264 | + aria-modal="true" |
| 265 | + > |
| 266 | + <div class="fr-container"> |
| 267 | + <button |
| 268 | + id="close-button" |
| 269 | + class="fr-btn fr-btn--close" |
| 270 | + aria-controls="header-navigation" |
| 271 | + data-testid="close-modal-btn" |
| 272 | + @click.prevent.stop="hideModal()" |
| 273 | + > |
| 274 | + {{ closeMenuModalLabel }} |
| 275 | + </button> |
| 276 | + <div class="fr-header__menu-links"> |
| 277 | + <template v-if="languageSelector"> |
| 278 | + <DsfrLanguageSelector |
| 279 | + v-bind="languageSelector" |
| 280 | + @select="languageSelector.currentLanguage = $event.codeIso" |
| 281 | + /> |
| 282 | + </template> |
| 283 | + <slot name="before-quick-links" /> |
| 284 | + <DsfrHeaderMenuLinks |
| 285 | + v-if="menuOpened" |
| 286 | + role="navigation" |
| 287 | + :links="quickLinks" |
| 288 | + :nav-aria-label="quickLinksAriaLabel" |
| 289 | + @link-click="onQuickLinkClick" |
272 | 290 | /> |
273 | | - </template> |
274 | | - <slot name="before-quick-links" /> |
275 | | - <DsfrHeaderMenuLinks |
276 | | - v-if="menuOpened" |
277 | | - role="navigation" |
278 | | - :links="quickLinks" |
279 | | - :nav-aria-label="quickLinksAriaLabel" |
280 | | - @link-click="onQuickLinkClick" |
281 | | - /> |
282 | | - <slot name="after-quick-links" /> |
283 | | - </div> |
| 291 | + <slot name="after-quick-links" /> |
| 292 | + </div> |
284 | 293 |
|
285 | | - <template v-if="modalOpened"> |
286 | | - <slot |
287 | | - name="mainnav" |
288 | | - :hidemodal="hideModal" |
289 | | - /> |
290 | | - </template> |
291 | | - <div |
292 | | - v-if="searchModalOpened" |
293 | | - class="flex justify-center items-center" |
294 | | - > |
295 | | - <DsfrSearchBar |
296 | | - :searchbar-id="searchbarId" |
297 | | - :model-value="modelValue" |
298 | | - :placeholder="placeholder" |
299 | | - @update:model-value="emit('update:modelValue', $event)" |
300 | | - @search="emit('search', $event)" |
301 | | - /> |
| 294 | + <template v-if="modalOpened"> |
| 295 | + <slot |
| 296 | + name="mainnav" |
| 297 | + :hidemodal="hideModal" |
| 298 | + /> |
| 299 | + </template> |
| 300 | + <div |
| 301 | + v-if="searchModalOpened" |
| 302 | + class="flex justify-center items-center" |
| 303 | + > |
| 304 | + <DsfrSearchBar |
| 305 | + :searchbar-id="searchbarId" |
| 306 | + :model-value="modelValue" |
| 307 | + :placeholder="placeholder" |
| 308 | + @update:model-value="emit('update:modelValue', $event)" |
| 309 | + @search="emit('search', $event)" |
| 310 | + /> |
| 311 | + </div> |
302 | 312 | </div> |
303 | 313 | </div> |
304 | | - </div> |
| 314 | + </FocusTrap> |
305 | 315 | <!-- @slot Slot par défaut pour le contenu du fieldset (sera dans `<div class="fr-header__body-row">`) --> |
306 | 316 | <slot /> |
307 | 317 | </div> |
|
0 commit comments