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
@@ -0,0 +1,324 @@
1
+ <!-- About this Component--
2
+ * Renders a checkboxe input type for the Quiz component
3
+ * Related Quiz to question: REPONSE_MULTIPLE
4
+ * Receives the a data object defined by user
5
+ * Used by AppCompQuizNx
6
+ -->
7
+ <template>
8
+ <div :id="id" class="input-box">
9
+ <fieldset :aria-label="fieldsetLabel">
10
+ <template
11
+ v-for="(choixReponse, index) in inputData"
12
+ :Key="`div_chx_${id}_${choixReponse.id}`"
13
+ >
14
+ <div class="box-checkbox" role="group">
15
+ <label
16
+ :key="`lbl_chx_${id}-${choixReponse.id}`"
17
+ :for="`boxchx_${id}_${choixReponse.id}`"
18
+ class="checkbox-label"
19
+ :class="[
20
+ {
21
+ answerSlct: isSelected(choixReponse.value)
22
+ },
23
+ `${retro[index]}`
24
+ ]"
25
+ >
26
+ <input
27
+ :id="`boxchx_${id}_${choixReponse.id}`"
28
+ :key="`boxchx_${id}_${id}-${choixReponse.id}`"
29
+ :checked="isSelected(choixReponse.value)"
30
+ type="checkbox"
31
+ :name="`btn-checkbox-${id}`"
32
+ :aria-labelledby="`span_${id}_${choixReponse.id}`"
33
+ :aria-describedby="`${id}_${choixReponse.id}-msg-erreur`"
34
+ @change="onSelectUpdate($event, choixReponse.value)"
35
+ />
36
+ <span
37
+ :id="`span_${id}_${choixReponse.id}`"
38
+ class="checkbox-contenu"
39
+ aria-hidden="true"
40
+ v-html="choixReponse.value"
41
+ ></span>
42
+ <span
43
+ :id="`${id}_${choixReponse.id}-msg-erreur`"
44
+ :key="`msg_chx_${id}_${choixReponse.id}`"
45
+ class="sr-only"
46
+ >
47
+ {{ messageAccessibility[index] }}
48
+ </span>
49
+ </label>
50
+ </div>
51
+ </template>
52
+ </fieldset>
53
+ </div>
54
+ </template>
55
+ <script>
56
+ import { useQuiz } from '../composables/useQuiz'
57
+ import { validateObjType } from '../shared/validators'
58
+ export default {
59
+ name: 'AppCompInputCheckBoxNx',
60
+ /* PROPS USED FOR PARENT TO COMMUNICATE DATA TO CHILD */
61
+ props: {
62
+ modelValue: {
63
+ type: Array,
64
+ default: () => []
65
+ },
66
+ inputData: {
67
+ type: Array,
68
+ default: () => []
69
+ },
70
+ solution: {
71
+ type: Array,
72
+ default: () => []
73
+ },
74
+ id: {
75
+ type: String,
76
+ default: ''
77
+ }
78
+ },
79
+ emits: ['update:modelValue', 'enable-submit'],
80
+ setup() {
81
+ const { retroType, addRetroStyle, resetRetroStyle } = useQuiz()
82
+
83
+ return { retroType, addRetroStyle, resetRetroStyle }
84
+ },
85
+ data() {
86
+ return {
87
+ quizInputDataValue: [],
88
+ result: null,
89
+ retro: [],
90
+ messageAccessibility: [],
91
+ selectedItems: []
92
+ }
93
+ },
94
+ computed: {
95
+ fieldsetLabel() {
96
+ return `${this.$t('quizState.checkboxFieldset')} ${this.inputData.length} ${this.$t('quizState.options')}`
97
+ },
98
+ /* INTERNAL REACTIVE VALUE THAT BIND WITH MODELVALUE PROPS */
99
+ inputsValue: {
100
+ get() {
101
+ return this.modelValue
102
+ },
103
+
104
+ set(newValue) {
105
+ if (
106
+ (this.$el && this.$el.id !== this.id) ||
107
+ newValue.constructor !== Array
108
+ )
109
+ return
110
+
111
+ this.$emit('update:modelValue', newValue)
112
+ }
113
+ },
114
+ /**
115
+ * internal reactive value that map the options to add SELECTED attribute on each option
116
+ * this attribute value can be changed when user select the option
117
+ */
118
+ mappedOptions() {
119
+ const options = this.inputData.map((op) => {
120
+ const selectedItem = this.modelValue.find((i) => i.id === op.id)
121
+ return {
122
+ ...op,
123
+ selected: selectedItem ? selectedItem.selected : false
124
+ }
125
+ })
126
+
127
+ return options
128
+ }
129
+ },
130
+ watch: {
131
+ inputsValue: {
132
+ handler(newVal) {
133
+ this.$emit('enable-submit', newVal.length > 0)
134
+ },
135
+ deep: true,
136
+ immediate: true
137
+ }
138
+ },
139
+ created() {
140
+ if (import.meta.env.DEV) {
141
+ let errors = this.validateInputData()
142
+
143
+ if (errors && errors.errorList.length)
144
+ return this.$bus.$emit('input-error', { e: this.id, errors })
145
+ }
146
+
147
+ this.quizInputDataValue = this.inputData
148
+ },
149
+ mounted() {},
150
+
151
+ methods: {
152
+ /**
153
+ * @description validate the raw data received by the component to render is view
154
+ * @returns {Object} errors - errorList: to display in view and errorConsole, to be displayed in console
155
+ */
156
+ validateInputData() {
157
+ let errors = null //array for errors dectected
158
+ let stringType = ['id', 'value']
159
+
160
+ if (!this.inputData.length) return errors
161
+ for (let i = 0; i < this.inputData.length; i++) {
162
+ errors = validateObjType(
163
+ this.inputData[i],
164
+ { stringType },
165
+ null,
166
+ `choix_reponse #${i + 1}`
167
+ )
168
+ const { errorList, errorInConsole } = errors
169
+
170
+ if (errorList.length || errorInConsole.length) return errors
171
+ }
172
+
173
+ return errors
174
+ },
175
+
176
+ /**
177
+ * @description inicate wether the element is checked or not.
178
+ * Element is selected if in the modelValue
179
+ * @param value value of tje element to be checked
180
+ */
181
+ isSelected(value) {
182
+ const existing = this.modelValue.includes(value)
183
+ return existing
184
+ },
185
+
186
+ /**
187
+ * @description Method to force update of this.inputsValue.
188
+ * this force the old value and new value of the input to be tracked by VUE.
189
+ * @param newValue, the new value of the input
190
+ * @param index index of the selected element
191
+ */
192
+ onSelectUpdate(event, value) {
193
+ if (this.$el && this.$el.id !== this.id) return //prevent event from firing on other input
194
+
195
+ const isChecked = event.target.checked
196
+ this.resetRetroStyle([this.retro, this.messageAccessibility])
197
+ let updatedValue = [...this.modelValue]
198
+
199
+ if (isChecked) {
200
+ const existing = updatedValue.includes(value)
201
+ if (!existing) updatedValue.push(value)
202
+ } else updatedValue = updatedValue.filter((v) => v !== value)
203
+
204
+ this.$emit('update:modelValue', updatedValue)
205
+ },
206
+ /**
207
+ * @description Component validation method to validate the user input against the solution
208
+ * can handle logic of Css style to apply (correct/wrong Answer) and return the result
209
+ * @return {Object} result
210
+ */
211
+ validateAnswer() {
212
+ // Validate the user answer
213
+ //Created a custom validated object for inputData
214
+ let mappedResults
215
+ if (this.solution != null) {
216
+ mappedResults = this.inputData.map(
217
+ (a) =>
218
+ (a = {
219
+ ...a,
220
+ correct: this.solution.includes(a.id)
221
+ })
222
+ )
223
+ } else {
224
+ mappedResults = this.inputData.map(
225
+ (a) =>
226
+ (a = {
227
+ ...a,
228
+ correct: null
229
+ })
230
+ )
231
+ }
232
+
233
+ let { classRetro, mesA11y } = this.addRetroStyle(
234
+ this.solution,
235
+ mappedResults,
236
+ this.inputData.length
237
+ )
238
+
239
+ this.retro = classRetro
240
+ this.messageAccessibility = mesA11y
241
+
242
+ //filer mapped to return a customized object of the user selection
243
+ this.result = mappedResults
244
+ .filter((a) => this.inputsValue.includes(a.value))
245
+ .map((a) => {
246
+ const { id, value, correct } = a
247
+ return { id, selected: value, correct }
248
+ })
249
+
250
+ let retro = this.retroType(this.solution, this.result)
251
+
252
+ let cAns
253
+ if (this.solution != null) cAns = this.computeResult(this.result)
254
+ else cAns = false
255
+
256
+ //Object to return validation to parent
257
+ return {
258
+ userAnswer: this.result,
259
+ correctAnswer: cAns,
260
+ retroType: retro
261
+ }
262
+ },
263
+ /**
264
+ * @description - compute the correct elements in user response
265
+ * compare with the solution and return wether user succeeded or failed
266
+ * @param {Object} result - user responses
267
+ * @retuns boolean
268
+ */
269
+ computeResult(result) {
270
+ const res = this.result.filter((el) => el.correct)
271
+
272
+ return (
273
+ res.length == this.solution.length &&
274
+ this.solution.length == this.result.length
275
+ )
276
+ }
277
+ }
278
+ }
279
+ </script>
280
+ <style lang="scss">
281
+ fieldset {
282
+ border: inherit;
283
+ }
284
+
285
+ .custom-control-input:focus ~ .custom-control-label::before {
286
+ border: inherit;
287
+ }
288
+
289
+ .custom-control-input:focus ~ .custom-control-label::before {
290
+ box-shadow: inherit;
291
+ -webkit-box-shadow: inherit;
292
+ }
293
+
294
+ fieldset {
295
+ border: inherit;
296
+ }
297
+
298
+ .custom-checkbox {
299
+ width: 100%;
300
+ height: 100%;
301
+ padding-left: 0;
302
+
303
+ input {
304
+ width: 100%;
305
+ height: 100%;
306
+ }
307
+
308
+ label {
309
+ width: 100%;
310
+ height: 100%;
311
+
312
+ &:after,
313
+ &:before {
314
+ border: inherit;
315
+ top: 0px;
316
+ left: 0px;
317
+ height: 0;
318
+ width: 0;
319
+ background-color: inherit;
320
+ border: inherit;
321
+ }
322
+ }
323
+ }
324
+ </style>
@@ -0,0 +1,295 @@
1
+ <!-- About this Component--
2
+ * Renders a SELECT input to display choices of response for the Quiz component
3
+ * Related Quiz to question: DROPDOWN
4
+ * Receives the a data object defined by user
5
+ * Used by AppCompQuizNx
6
+ * Uses useQuiz composable
7
+ -->
8
+ <template>
9
+ <div v-if="inputData.length > 0" :id="id" class="input-box">
10
+ <fieldset :aria-label="fieldsetLabel">
11
+ <div
12
+ v-for="(singleDropdown, index) in inputData"
13
+ :key="singleDropdown.id"
14
+ class="dropdown-container"
15
+ :class="`dropdownlist-${singleDropdown.id}`"
16
+ >
17
+ <label
18
+ :id="`label_dropdown_${id}_${singleDropdown.id}`"
19
+ :for="`dropdown_${id}_${singleDropdown.id}`"
20
+ v-html="singleDropdown.ennonce"
21
+ ></label>
22
+
23
+ <div class="cnt-input" :class="`${retro[index] ? retro[index] : ''}`">
24
+ <v-select
25
+ :id="`dropdown_${id}_${singleDropdown.id}`"
26
+ :model-value="inputsValue[singleDropdown.id]"
27
+ item-title="text"
28
+ :item-props="true"
29
+ :items="getItemOptions(index)"
30
+ :open-text="`${$t('message.dropdown_list')}_${singleDropdown.ennonce}`"
31
+ :aria-describedby="`${id}_${singleDropdown.id}-msg-erreur`"
32
+ @update:model-value="onSelectUpdate($event, index)"
33
+ ></v-select>
34
+
35
+ <span
36
+ :id="`${id}_${singleDropdown.id}-msg-erreur`"
37
+ :key="`msg_chx_${id}_${singleDropdown.id}`"
38
+ class="sr-only"
39
+ >
40
+ {{ messageAccessibility[index] }}
41
+ </span>
42
+ </div>
43
+ </div>
44
+ </fieldset>
45
+ </div>
46
+ </template>
47
+ <script>
48
+ import { useQuiz } from '../composables/useQuiz'
49
+ import { validateObjType } from '../shared/validators'
50
+ export default {
51
+ name: 'AppCompInputDropdown',
52
+ /* PROPS USED FOR PARENT TO COMMUNICATE DATA TO CHILD */
53
+ props: {
54
+ modelValue: {
55
+ type: Array,
56
+ default: () => []
57
+ },
58
+ inputData: {
59
+ type: Array,
60
+ default: () => []
61
+ },
62
+ solution: {
63
+ type: Array,
64
+ default: () => []
65
+ },
66
+ id: {
67
+ type: String,
68
+ default: ''
69
+ }
70
+ },
71
+ emits: ['update:modelValue', 'enable-submit'],
72
+ setup(props) {
73
+ const { retroType, addRetroStyle, resetRetroStyle } = useQuiz()
74
+
75
+ return { retroType, addRetroStyle, resetRetroStyle }
76
+ },
77
+ data() {
78
+ return {
79
+ retro: [],
80
+ messageAccessibility: [],
81
+ result: [],
82
+ oldValue: null
83
+ }
84
+ },
85
+ computed: {
86
+ fieldsetLabel() {
87
+ let label
88
+ if (this.inputData.length === 1) {
89
+ label = this.$t('quizState.dropdownFieldsetSingle')
90
+ } else {
91
+ label = `${this.$t('quizState.dropdownFieldsetMulti1')} ${this.inputData.length} ${this.$t('quizState.dropdownFieldsetMulti2')}`
92
+ }
93
+ return label
94
+ },
95
+ /* INTERNAL REACTIVE VALUE THAT BIND WITH MODELVALUE PROPS */
96
+ inputsValue: {
97
+ get() {
98
+ return this.modelValue
99
+ },
100
+ set(newValue) {
101
+ this.$emit('update:modelValue', [...newValue])
102
+ }
103
+ },
104
+ mappedOptions() {
105
+ if (this.inputData.length === 0) return []
106
+
107
+ return this.inputData.map((op) => {
108
+ const key = Object.keys(op)[0]
109
+ const values = Object.values(op)[2]
110
+
111
+ const newValues = [
112
+ {
113
+ value: null,
114
+ disabled: true,
115
+ selected: true,
116
+ text: this.$t('message.first_option_dropdown')
117
+ // text: 'choisir une option'
118
+ },
119
+ ...values.map((el) => ({
120
+ value: el,
121
+ text: el,
122
+ selected: false
123
+ }))
124
+ ]
125
+
126
+ return {
127
+ [key]: newValues
128
+ }
129
+ })
130
+ }
131
+ },
132
+ watch: {
133
+ inputsValue: {
134
+ handler() {
135
+ if (this.$el && this.$el.id !== this.id) return
136
+ const filled = this.modelValue.filter((val) => val && val.trim() !== '')
137
+
138
+ this.$emit('enable-submit', filled.length === this.inputData.length)
139
+ },
140
+ deep: true,
141
+ immediate: true
142
+ }
143
+ },
144
+ created() {
145
+ if (import.meta.env.DEV) {
146
+ let errors = this.validateInputData()
147
+ if (errors && errors.errorList.length) {
148
+ return this.$bus.$emit('input-error', { e: this.id, errors })
149
+ }
150
+ }
151
+ },
152
+ mounted() {},
153
+
154
+ methods: {
155
+ validateInputData() {
156
+ let errors = null
157
+ let stringType = ['id', 'ennonce']
158
+ let arrayType = ['option']
159
+ let e = null
160
+
161
+ if (!this.inputData.length) return errors
162
+ for (let i = 0; i < this.inputData.length; i++) {
163
+ e = validateObjType(
164
+ this.inputData[i],
165
+ { stringType, arrayType },
166
+ null,
167
+ `liste déroulante #${i + 1}`
168
+ )
169
+
170
+ if (e.errorInConsole.length && e.errorList.length) {
171
+ errors.errorInConsole.push(e.errorInConsole[0])
172
+ errors.errorList.push(e.errorList[0])
173
+ }
174
+ }
175
+
176
+ return errors
177
+ },
178
+ /**
179
+ * @description Method to retrive the options of each selectable input in the mappedOption Object
180
+ * @param {Number } index - number representing the index of the item in the array of inputElements
181
+ * @Return a collection of item representing the options for a the selectables
182
+ */
183
+ getItemOptions(index) {
184
+ return Object.values(this.mappedOptions[index])[0]
185
+ },
186
+ /**
187
+ * @description Method to force update of this.inputsValue.
188
+ * this force the old value and new value of the input to be tracked by VUE.
189
+ * @param newValue, the new value of the input
190
+ * @param index index of the selected element
191
+ */
192
+
193
+ onSelectUpdate(newValue, index) {
194
+ if (this.$el && this.$el.id !== this.id) return //prevent event from firing on other input
195
+ this.resetRetroStyle([this.retro, this.messageAccessibility])
196
+ const updated = [...this.inputsValue] //create new array from inputsValue
197
+ updated[index] = newValue
198
+ this.inputsValue = updated //trigger vue reactivity for inputsValue
199
+ },
200
+ /**
201
+ * @description Component validation method to validate the user input against the solution
202
+ * can handle logic of Css style to apply (correct/wrong Answer) and return the result
203
+ * @return {Object} result
204
+ */
205
+ validateAnswer() {
206
+ let mappedResults
207
+ if (this.solution != null) {
208
+ mappedResults = this.inputsValue.map((a, i) => {
209
+ const s = this.solution[i]
210
+ return { selected: a, correct: s == a }
211
+ })
212
+ } else {
213
+ mappedResults = this.inputsValue.map((a, i) => {
214
+ return { selected: a, correct: false }
215
+ })
216
+ }
217
+
218
+ let { classRetro, mesA11y } = this.addRetroStyle(
219
+ this.solution,
220
+ mappedResults,
221
+ this.inputData.length
222
+ )
223
+
224
+ this.retro = [...classRetro]
225
+ this.messageAccessibility = [...mesA11y]
226
+
227
+ this.retro = classRetro
228
+ this.messageAccessibility = mesA11y
229
+
230
+ this.result = mappedResults.filter((a) => a.selected)
231
+ let tr = this.retroType(this.solution, this.result)
232
+
233
+ let cAns
234
+ if (this.solution != null) cAns = this.computeResult(this.result)
235
+ else cAns = false
236
+
237
+ return {
238
+ userAnswer: this.result,
239
+ correctAnswer: cAns,
240
+ retroType: tr
241
+ }
242
+ },
243
+ computeResult(result) {
244
+ const res = result.filter((el) => el.correct)
245
+ return res.length == this.solution.length
246
+ }
247
+ }
248
+ }
249
+ </script>
250
+ <style lang="scss">
251
+ .dropdown-container {
252
+ position: relative;
253
+ }
254
+
255
+ div.texteatrou {
256
+ display: inline !important;
257
+ .cnt-input {
258
+ display: inline;
259
+
260
+ .v-input {
261
+ display: inline-block !important;
262
+
263
+ .v-input__control,
264
+ .v-field {
265
+ width: 240px !important;
266
+ grid-area: inherit !important;
267
+
268
+ .v-field__field {
269
+ height: 25px !important;
270
+ padding: 0 !important;
271
+ min-height: inherit !important;
272
+
273
+ .v-field__input {
274
+ padding-top: 0 !important;
275
+ padding-bottom: 0 !important;
276
+ min-height: inherit !important;
277
+
278
+ .v-select__selection {
279
+ min-height: inherit !important;
280
+ }
281
+ }
282
+ }
283
+
284
+ .v-field__append-inner {
285
+ margin-right: 6px;
286
+ }
287
+ }
288
+
289
+ .v-input__details {
290
+ display: none;
291
+ }
292
+ }
293
+ }
294
+ }
295
+ </style>