fcad-core-dragon 2.0.1 → 2.0.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG +16 -1
  2. package/package.json +1 -1
  3. package/src/$locales/en.json +18 -4
  4. package/src/$locales/fr.json +17 -3
  5. package/src/components/AppBase.vue +36 -341
  6. package/src/components/AppBaseModule.vue +16 -21
  7. package/src/components/AppBasePage.vue +45 -14
  8. package/src/components/AppBaseSkeleton.vue +45 -0
  9. package/src/components/AppCompAudio.vue +12 -3
  10. package/src/components/AppCompButtonProgress.vue +13 -2
  11. package/src/components/AppCompCarousel.vue +12 -4
  12. package/src/components/AppCompInputCheckBoxNx.vue +324 -0
  13. package/src/components/AppCompInputDropdownNx.vue +295 -0
  14. package/src/components/AppCompInputRadioNx.vue +264 -0
  15. package/src/components/AppCompInputTextNx.vue +148 -0
  16. package/src/components/AppCompInputTextTableNx.vue +198 -0
  17. package/src/components/AppCompInputTextToFillDropdownNx.vue +291 -0
  18. package/src/components/AppCompInputTextToFillNx.vue +277 -0
  19. package/src/components/AppCompJauge.vue +11 -4
  20. package/src/components/AppCompMenu.vue +7 -14
  21. package/src/components/AppCompMenuItem.vue +7 -5
  22. package/src/components/AppCompNavigation.vue +21 -21
  23. package/src/components/AppCompNoteCall.vue +1 -0
  24. package/src/components/AppCompNoteCredit.vue +2 -1
  25. package/src/components/AppCompPlayBarNext.vue +94 -41
  26. package/src/components/AppCompPopUpNext.vue +6 -6
  27. package/src/components/AppCompQuizNx.vue +500 -0
  28. package/src/components/AppCompQuizRecall.vue +113 -66
  29. package/src/components/AppCompTableOfContent.vue +39 -10
  30. package/src/components/AppCompVideoPlayer.vue +1 -1
  31. package/src/composables/useQuiz.js +62 -179
  32. package/src/directives/nvdaFix.js +53 -0
  33. package/src/main.js +227 -30
  34. package/src/mixins/$mediaMixins.js +1 -11
  35. package/src/module/stores/appStore.js +29 -11
  36. package/src/plugins/idb.js +1 -1
  37. package/src/shared/generalfuncs.js +134 -0
  38. package/src/shared/validators.js +308 -234
  39. package/src/components/AppCompInputCheckBoxNext.vue +0 -205
  40. package/src/components/AppCompInputDropdownNext.vue +0 -201
  41. package/src/components/AppCompInputRadioNext.vue +0 -158
  42. package/src/components/AppCompInputTextNext.vue +0 -124
  43. package/src/components/AppCompInputTextTableNext.vue +0 -142
  44. package/src/components/AppCompInputTextToFillDropdownNext.vue +0 -238
  45. package/src/components/AppCompInputTextToFillNext.vue +0 -171
  46. package/src/components/AppCompQuizNext.vue +0 -2908
@@ -6,42 +6,51 @@
6
6
  ** data object should containt following attributes: quizId,activityId,pageId,hypertext_done',hypertext_undone,title,titletag
7
7
  -->
8
8
  <template>
9
- <section class="quizRecall">
9
+ <section v-if="quizRecallData" class="quizRecall">
10
10
  <!--Optionnal title, out of quiz-answer-conditionning, default tag H4, but can be change by user-->
11
- <component
12
- :is="quizRecall.titletag"
13
- v-if="quizRecall.title"
14
- class="quizRecall-title"
15
- >
16
- {{ quizRecall.title }}
17
- </component>
18
- <!--Quiz answer conditionning-->
19
- <app-base-error-display
20
- v-if="hasError.length"
21
- :error-group="'component'"
22
- :error-title="`ERREUR: COMPOSANT QUIZ RECALL`"
23
- :errors-list="hasError"
24
- ></app-base-error-display>
11
+ <!--Show skeleton while app data is not ready-->
12
+ <template v-if="!isReady">
13
+ <app-base-skeleton :skeleton-lines="2" />
14
+ </template>
15
+ <!--Show app data when ready-->
25
16
  <template v-else>
26
- <template v-if="quizRecall.done == true">
27
- <div
28
- v-if="quizRecall.hypertext_done"
29
- class="quizRecall-text-done"
30
- v-html="quizRecall.hypertext_done"
31
- ></div>
32
- <div class="quizRecall-ennonce" v-html="quizRecall.ennonce"></div>
33
- <textarea
34
- v-model="quizRecall.answer"
35
- disabled
36
- class="form-control"
37
- ></textarea>
38
- </template>
39
- <template v-if="quizRecall.done == false">
40
- <div
41
- v-if="quizRecall.hypertext_undone"
42
- class="quizRecall-text-undone"
43
- v-html="quizRecall.hypertext_undone"
44
- ></div>
17
+ <component
18
+ :is="quizRecall.titletag"
19
+ v-if="quizRecall.title"
20
+ class="quizRecall-title"
21
+ >
22
+ {{ quizRecall.title }} -
23
+ </component>
24
+ <!--Quiz answer conditionning-->
25
+ <app-base-error-display
26
+ v-if="hasError.length"
27
+ :error-group="'component'"
28
+ :error-title="`ERREUR: COMPOSANT QUIZ RECALL`"
29
+ :errors-list="hasError"
30
+ ></app-base-error-display>
31
+ <template v-else>
32
+ <template v-if="quizRecall.done == true">
33
+ <div
34
+ v-if="quizRecall.hypertext_done"
35
+ class="quizRecall-text-done"
36
+ v-html="quizRecall.hypertext_done"
37
+ ></div>
38
+ <div class="quizRecall-ennonce" v-html="quizRecall.ennonce"></div>
39
+
40
+ <textarea
41
+ :id="`quizRecall-answer-${quizRecallData.quizId}`"
42
+ v-model="quizRecall.answer"
43
+ disabled
44
+ class="form-control"
45
+ ></textarea>
46
+ </template>
47
+ <template v-if="quizRecall.done == false">
48
+ <div
49
+ v-if="quizRecall.hypertext_undone"
50
+ class="quizRecall-text-undone"
51
+ v-html="quizRecall.hypertext_undone"
52
+ ></div>
53
+ </template>
45
54
  </template>
46
55
  </template>
47
56
  </section>
@@ -51,6 +60,7 @@
51
60
  //Recall mixins has the necessary datas and functions to give back quizRecall statu
52
61
  import { mapState } from 'pinia'
53
62
  import { useAppStore } from '../module/stores/appStore'
63
+ import { nextTick } from 'vue'
54
64
 
55
65
  export default {
56
66
  name: 'AppCompQuizRecall',
@@ -71,7 +81,8 @@ export default {
71
81
  )}</p>` /*String traduite par défaut*/
72
82
  },
73
83
  hasError: [],
74
- quizData: null
84
+ quizData: null,
85
+ isReady: false //Used to display skeleton while app data is not ready
75
86
  }
76
87
  },
77
88
  computed: {
@@ -79,25 +90,46 @@ export default {
79
90
  'getAllCompleted',
80
91
  'getUserInteraction',
81
92
  'getPageInteraction',
93
+ 'getAppStatus',
82
94
  'getPageData'
83
95
  ])
84
96
  },
97
+
98
+ watch: {
99
+ //Watch for user interaction change to get quizRecall answer
100
+ getUserInteraction: {
101
+ async handler(newValue) {
102
+ if (!this.getUserInteraction) return
103
+
104
+ const { activityId, pageId } = this.quizRecallData
105
+ const interaction = this.getPageInteraction(
106
+ activityId,
107
+ pageId
108
+ ).userInteraction
109
+
110
+ if (!interaction) return
111
+ //Get quizRecall answer
112
+ if (this.quizRecallData && this.quizData) {
113
+ await this.getQuizRecallAnswer(interaction)
114
+ } else {
115
+ this.quizRecall.done == false
116
+ }
117
+ },
118
+ immediate: true,
119
+ deep: true
120
+ }
121
+ },
85
122
  created() {
86
123
  //Validate quizRecall data
87
124
  //(no validation to the quiz, only to the new datas)
88
- this.validateQuizRecallData(this.quizRecallData)
125
+ if (import.meta.env.DEV) {
126
+ this.validateQuizRecallData(this.quizRecallData)
127
+ }
89
128
  const { activityId, pageId } = this.quizRecallData
90
129
  //Get quizData and validate the quiz type
91
130
  if (activityId && pageId) {
92
131
  this.getQuizData(activityId, pageId)
93
132
  }
94
-
95
- //Get quizRecall answer
96
- if (this.quizRecallData && this.quizData) {
97
- this.getQuizRecallAnswer(this.quizRecallData, this.quizData)
98
- } else {
99
- this.quizRecall.done == false
100
- }
101
133
  },
102
134
  methods: {
103
135
  /**
@@ -165,19 +197,10 @@ export default {
165
197
  }
166
198
  },
167
199
  //Get datas from quizRecallData and userData and add them to quizRecall object
168
- getQuizRecallAnswer() {
169
- //Get userData
170
- const {
171
- activityId,
172
- pageId,
173
- quizId,
174
- hypertext_done,
175
- hypertext_undone,
176
- title,
177
- titletag
178
- } = this.quizRecallData
179
-
180
- let userData = this.getPageInteraction(activityId, pageId)
200
+ async getQuizRecallAnswer(userData) {
201
+ await nextTick() //wait for the DOM to update
202
+ const { quizId, hypertext_done, hypertext_undone, title, titletag } =
203
+ this.quizRecallData
181
204
 
182
205
  //Add hypertext_done and undone to quizRecall
183
206
  if (hypertext_done) {
@@ -197,19 +220,21 @@ export default {
197
220
  }
198
221
  }
199
222
  }
223
+ //Get the quiz answers from userData
224
+ const answers = userData.quizAnswers || {}
225
+ let quizAnswer = answers[quizId] || null
200
226
 
201
227
  //If quiz answer exists in userData, quizRecall done is true and add the quiz answer to quizRecall
202
- if (!userData || !userData.userInteraction)
203
- return (this.quizRecall.done = false)
204
-
205
- const { quizAnswers } = userData.userInteraction
228
+ quizAnswer
229
+ ? (this.quizRecall.done = true)
230
+ : (this.quizRecall.done = false)
206
231
 
207
- if (!quizAnswers || !quizAnswers[quizId])
208
- return (this.quizRecall.done = false)
209
-
210
- this.quizRecall.done = true
211
- this.quizRecall.answer = quizAnswers[quizId].value
212
- this.quizRecall.ennonce = this.quizData.ennonce
232
+ if (quizAnswer) {
233
+ this.quizRecall.answer = quizAnswer.value[0].filled || ''
234
+ this.quizRecall.ennonce = this.quizData.ennonce
235
+ }
236
+ this.quizRecall
237
+ this.isReady = true //Set isReady to true to display the component
213
238
  },
214
239
 
215
240
  //Validate quizRecallData
@@ -296,3 +321,25 @@ export default {
296
321
  }
297
322
  }
298
323
  </script>
324
+
325
+ <style>
326
+ .skeleton {
327
+ height: 20px;
328
+ width: 200px;
329
+ background-color: #ccc;
330
+ border-radius: 4px;
331
+ animation: pulse 1.5s infinite ease-in-out;
332
+ }
333
+
334
+ @keyframes pulse {
335
+ 0% {
336
+ opacity: 1;
337
+ }
338
+ 50% {
339
+ opacity: 0.4;
340
+ }
341
+ 100% {
342
+ opacity: 1;
343
+ }
344
+ }
345
+ </style>
@@ -11,19 +11,21 @@
11
11
  <div v-if="error" id="sidebar-submenu" :class="{ isOpen: isOpened }">
12
12
  <focus-trap :active="isOpened">
13
13
  <div ref="target">
14
+ <!-- <div class="submenu-header"> -->
14
15
  <app-base-button
15
16
  id="close-toc"
16
17
  class="btn-ghost"
17
18
  :title="$t('button.closePopUp')"
18
19
  @click="onCloseWidget('toc')"
19
20
  >
20
- <svg>
21
+ <svg aria-hidden="true" focusable="false">
21
22
  <use href="#close-square-icon" />
22
23
  </svg>
23
24
  </app-base-button>
25
+ <!-- </div> -->
24
26
 
25
- <p class="t-act" v-html="title"></p>
26
-
27
+ <!-- <div class="submenu-content"> -->
28
+ <p class="t-act" v-html="`${title}`"></p>
27
29
  <div class="box-prog-act">
28
30
  <p>
29
31
  {{ $t('text.activity_progress') }}
@@ -46,19 +48,20 @@
46
48
  <p class="anchor-t" v-html="anchor.title"></p>
47
49
  <p class="state">
48
50
  <span class="box-text">
49
- <svg>
51
+ <svg aria-hidden="true" focusable="false">
50
52
  <use href="#check-toc" />
51
53
  </svg>
52
54
  {{ $t('text.complete') }}
53
55
  </span>
54
56
  </p>
55
57
  </div>
56
- <svg>
58
+ <svg aria-hidden="true" focusable="false">
57
59
  <use href="#chevronD-icon" />
58
60
  </svg>
59
61
  </div>
60
62
  </RouterLink>
61
63
  </div>
64
+ <!-- </div> -->
62
65
  </div>
63
66
  </focus-trap>
64
67
  </div>
@@ -147,8 +150,25 @@ export default {
147
150
  if (activity.charAt(1) == '0' || activity.charAt(1) == 0)
148
151
  nb = activity.substr(2)
149
152
  else nb = activity.substr(1)
150
-
151
- this.title = `${this.$t('text.activity')} ${nb} : ${menuInfo[activity].subTitle}`
153
+ //Format title in the Progress info
154
+ switch (nb) {
155
+ case '0':
156
+ this.title = menuInfo[activity].subTitle
157
+ ? `Introduction : ${menuInfo[activity].subTitle}`
158
+ : `Introduction`
159
+ break
160
+
161
+ case '99':
162
+ this.title = menuInfo[activity].subTitle
163
+ ? `Conclusion : ${menuInfo[activity].subTitle}`
164
+ : `Conclusion`
165
+ break
166
+
167
+ default:
168
+ this.title = menuInfo[activity].subTitle
169
+ ? `${this.$t('text.activity')} ${nb} : ${menuInfo[activity].subTitle}`
170
+ : `${this.$t('text.activity')} ${nb}`
171
+ }
152
172
 
153
173
  //create anchors title and path
154
174
  if (menuInfo[activity] && menuInfo[activity].anchors) {
@@ -308,14 +328,19 @@ export default {
308
328
  transition: left 0.5s ease-in-out;
309
329
 
310
330
  &.isOpen {
311
- display: block;
331
+ display: flex;
332
+ flex-direction: column;
312
333
  }
313
334
 
335
+ // .sidebar-submenu-content {
336
+ // display: flex;
337
+ // flex-direction: column;
338
+ // }
314
339
  #close-toc {
315
340
  position: absolute;
316
341
  right: 24px;
317
342
  margin-bottom: 0;
318
- padding: 11px;
343
+ padding: 12px;
319
344
  width: 48px;
320
345
  height: 48px;
321
346
  display: flex;
@@ -374,7 +399,11 @@ export default {
374
399
  }
375
400
  &[done='true'] {
376
401
  .state {
377
- display: block;
402
+ display: flex;
403
+ align-items: center;
404
+ svg {
405
+ margin-right: 10px;
406
+ }
378
407
  }
379
408
  }
380
409
 
@@ -111,7 +111,7 @@ export default {
111
111
  ...mapState(useAppStore, ['getCurrentBrowser', 'getCurrentPage']),
112
112
  //Return true if the video is loading (to show the loading display)
113
113
  isLoading() {
114
- if (!this.isSet && !this.hasSourceLoadingError) return true
114
+ if (!this.isSet) return true
115
115
  else return false
116
116
  },
117
117
 
@@ -2,185 +2,8 @@
2
2
 
3
3
  import i18n from '@/i18n' //Must import directly the local from project app because vue-18in does not work in legacy mode with composable
4
4
 
5
- export function useQuiz(quiz) {
6
- const { quizType, solution, showSolution, quizInputType } = quiz
5
+ export function useQuiz() {
7
6
  const { t } = i18n.global
8
-
9
- // /**
10
- // * @param {String} inputId
11
- // * @returns {Array} the class
12
- // */
13
- function classInput(inputId, optSelected = null) {
14
- let theClass = []
15
-
16
- switch (true) {
17
- case ['choix_unique', 'reponse_ouverte'].includes(quizType.value):
18
- if (inputId == quizInputType.value) {
19
- theClass.push('reponseSelectionner')
20
- }
21
- break
22
-
23
- case quizType.value == 'choix_mult':
24
- if (containsValue(quizInputType.value, inputId)) {
25
- theClass.push('reponseSelectionner')
26
- }
27
- }
28
-
29
- if (showSolution.value) {
30
- if (solution.value !== null) {
31
- switch (true) {
32
- case ['choix_unique', 'reponse_ouverte'].includes(quizType.value):
33
- if (inputId == solution.value) {
34
- theClass.push('correct_answer')
35
- } else {
36
- theClass.push('wrong_answer')
37
- }
38
- break
39
-
40
- case quizType.value == 'choix_mult':
41
- if (containsValue(solution.value, inputId)) {
42
- theClass.push('correct_answer')
43
- } else {
44
- theClass.push('wrong_answer')
45
- }
46
- break
47
- case quizType.value == 'dropdown':
48
- if (!optSelected || !optSelected[inputId]) return
49
- if (solution.value[inputId] == optSelected[inputId]) {
50
- theClass.push('correct_answer')
51
- } else {
52
- theClass.push('wrong_answer')
53
- }
54
- break
55
-
56
- case quizType.value == 'texte_troue_select':
57
- if (typeof optSelected[inputId] !== 'undefined') {
58
- if (
59
- Object.values(solution.value[inputId])[0] ==
60
- optSelected[inputId]
61
- ) {
62
- theClass.push('correct_answer')
63
- } else {
64
- theClass.push('wrong_answer')
65
- }
66
- }
67
- break
68
-
69
- case quizType.value == 'texte_tableau':
70
- if (
71
- containsValue(
72
- solution.value[inputId].reponse_value,
73
- optSelected[inputId]
74
- )
75
- ) {
76
- theClass.push('correct_answer')
77
- } else {
78
- theClass.push('wrong_answer')
79
- }
80
- break
81
- case quizType.value == 'texte_troue':
82
- if (typeof optSelected[inputId] !== 'undefined') {
83
- if (
84
- containsValue(
85
- Object.values(solution.value[inputId])[0],
86
- optSelected[inputId]
87
- )
88
- ) {
89
- theClass.push('correct_answer')
90
- } else {
91
- theClass.push('wrong_answer')
92
- }
93
- }
94
- break
95
- }
96
- }
97
- }
98
- return theClass
99
- }
100
-
101
- function messageAccessibility(inputId, optSelected = null) {
102
- let mess = ''
103
- if (showSolution.value) {
104
- if (solution.value !== null) {
105
- switch (true) {
106
- case ['choix_unique', 'reponse_ouverte'].includes(quizType.value):
107
- if (inputId == solution.value) {
108
- mess = `${t('quizState.goodAnswer')}`
109
- } else {
110
- mess = `${t('quizState.badAnswer')}`
111
- }
112
- break
113
-
114
- case quizType.value == 'choix_mult':
115
- if (containsValue(solution.value, inputId)) {
116
- mess = `${t('quizState.goodAnswer')}`
117
- } else {
118
- mess = `${t('quizState.badAnswer')}`
119
- }
120
- break
121
- case quizType.value == 'dropdown':
122
- if (solution[inputId.value] == optSelected[inputId.value]) {
123
- mess = `${t('quizState.goodAnswer')}`
124
- } else {
125
- mess = `${t('quizState.badAnswer')}`
126
- }
127
- break
128
-
129
- case quizType.value == 'texte_troue_select':
130
- if (typeof optSelected[inputId] !== 'undefined') {
131
- if (
132
- Object.values(solution.value[inputId])[0] ==
133
- optSelected[inputId]
134
- ) {
135
- mess = `${t('quizState.goodAnswer')}`
136
- } else {
137
- mess = `${t('quizState.badAnswer')}`
138
- }
139
- }
140
- break
141
-
142
- case quizType.value == 'texte_tableau':
143
- if (
144
- containsValue(
145
- solution.value[inputId].reponse_value,
146
- optSelected[inputId]
147
- )
148
- ) {
149
- mess = `${t('quizState.goodAnswer')}`
150
- } else {
151
- mess = `${t('quizState.badAnswer')}`
152
- }
153
- break
154
- case quizType.value == 'texte_troue':
155
- if (typeof optSelected[inputId] !== 'undefined') {
156
- if (
157
- containsValue(
158
- Object.values(solution.value[inputId])[0],
159
- optSelected[inputId]
160
- )
161
- ) {
162
- mess = `${t('quizState.goodAnswer')}`
163
- } else {
164
- mess = `${t('quizState.badAnswer')}`
165
- }
166
- }
167
- break
168
- }
169
- }
170
- }
171
- return mess
172
- }
173
-
174
- /**
175
- * @description check if a values exists in a array
176
- * @param {Array} array
177
- * @param value
178
- * @returns {Boolean}
179
- */
180
- function containsValue(array, value) {
181
- return array.includes(value)
182
- }
183
-
184
7
  /**
185
8
  * @description shuffles an array used to randomized the option order if shuffleAnswers is true
186
9
  * @param {Array} array
@@ -201,6 +24,66 @@ export function useQuiz(quiz) {
201
24
  }
202
25
  return newArray2
203
26
  }
27
+ /**
28
+ * @description add retro style to the quiz
29
+ * @param {Object} solution - the solution of the quiz
30
+ * @param {Array} reponse - the user response
31
+ * @param {Number} length - the length of the input value
32
+ * @returns {Object|Boolean}
33
+ */
34
+ function addRetroStyle(solution, reponse, length) {
35
+ let classRetro = []
36
+ let mesA11y = []
37
+ if (solution != null) {
38
+ reponse.forEach((el) => {
39
+ if (el.correct == true) {
40
+ classRetro.push('goodAnswer')
41
+ mesA11y.push(`${t('quizState.goodAnswer')}`)
42
+ } else {
43
+ classRetro.push('badAnswer')
44
+ mesA11y.push(`${t('quizState.badAnswer')}`)
45
+ }
46
+ })
47
+ } else {
48
+ classRetro.push('NeutralAnswer')
49
+ mesA11y.push(`${t('quizState.neutralAnswer')}`)
50
+ }
204
51
 
205
- return { containsValue, shuffleArray, classInput, messageAccessibility }
52
+ return { classRetro, mesA11y }
53
+ }
54
+
55
+ /**
56
+ * @description reset the list of validation style of a quiz
57
+ * @param {Array} aList - a list of array to reset
58
+ * @emits hide-retro - event message to hide the retro Message
59
+ * $el- the target quiz,
60
+ * false|true of th event
61
+ * */
62
+ function resetRetroStyle(aList) {
63
+ aList.forEach((a) => {
64
+ if (a && a.length > 0) a.splice(0)
65
+ })
66
+ this.$bus.$emit('hide-retro', this.$el, false)
67
+ }
68
+
69
+ function retroType(solution, reponse) {
70
+ if (solution != null) {
71
+ if (reponse.length == solution.length) {
72
+ let Ag = (i) => i.correct
73
+ if (reponse.every(Ag)) return 'retro_positive'
74
+ else return 'retro_negative'
75
+ } else {
76
+ return 'retro_negative'
77
+ }
78
+ } else {
79
+ return `retro_neutre`
80
+ }
81
+ }
82
+
83
+ return {
84
+ addRetroStyle,
85
+ resetRetroStyle,
86
+ retroType,
87
+ shuffleArray
88
+ }
206
89
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @file nvdaFix.js
3
+ * @description Directive to fix NVDA screen reader issues with Vuetify dropdowns.
4
+ * This directive hides the input field from screen readers and provides an aria-live region
5
+ * to announce the selected option.
6
+ *
7
+ * @example
8
+ * <v-select v-nvda-fix="selectedOptionText" ...>
9
+ */
10
+
11
+ export default {
12
+ mounted(el, binding) {
13
+ // 1. Hide Vuetify input by adding aria-hidden="true"
14
+ const input = el.querySelector('input[type="text"]')
15
+ if (input) {
16
+ input.setAttribute('aria-hidden', 'true')
17
+ }
18
+
19
+ // 2. Create/update aria-live for screen reader
20
+ const valueText =
21
+ binding && binding.value ? binding.value : 'choisir une option'
22
+ let liveRegion = el.querySelector('.nvda-live-region')
23
+
24
+ if (!liveRegion) {
25
+ liveRegion = document.createElement('div')
26
+ liveRegion.className = 'nvda-live-region sr-only'
27
+ liveRegion.setAttribute('aria-live', 'polite')
28
+ el.appendChild(liveRegion)
29
+ }
30
+
31
+ liveRegion.textContent = `🍍${valueText}`
32
+ },
33
+ /**
34
+ * @description Directive updated hook to handle changes in the selected option.
35
+ * This updates the aria-live region with the new selected option text.
36
+ * @param {Object} el - The element the directive is bound to.
37
+ * @param {Object} binding - The binding object containing the new value.
38
+ */
39
+ updated(el, binding) {
40
+ // update aria-live when selected option changes
41
+ const valueText = binding.value?.text || ''
42
+ const input = el.querySelector('input[type="text"]')
43
+ const liveRegion = el.querySelector('.nvda-live-region')
44
+
45
+ if (input) {
46
+ input.setAttribute('aria-hidden', 'true')
47
+ }
48
+
49
+ if (liveRegion) {
50
+ liveRegion.textContent = `🍍${valueText}`
51
+ }
52
+ }
53
+ }