fcad-core-dragon 2.1.0-beta.2 → 2.1.0-beta.4

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.
package/CHANGELOG CHANGED
@@ -1,3 +1,26 @@
1
+ 2.1.0-beta.4(26 septembre 2025)
2
+ ajout du plugin ScrollTrigger à l’objet $gsap
3
+
4
+ 2.1.0-beta.3(24 septembre 2025)
5
+ RUPTURE DE COMPATIBILITÉ : Ajout de Google Analytics (analytics_id dans app.vue) nouvelle dépendance : npm install vue-gtag@2.0.1
6
+
7
+ Tracking Analytics des événements play/end pour les lecteurs audio/vidéo
8
+
9
+ RUPTURE DE COMPATIBILITÉ : Nouvelle structure de données utilisées pour les lecteurs audio/vidéo
10
+
11
+ Correction du bogue de scroll au changement de page
12
+
13
+ Optimisation de la mise à jour de la valeur de currentPage dans le Store
14
+
15
+ Nouvelle fonctionnalité : Il est maintenant possible de redéfinir certains libellés spécifiques du FCAD (FIAG)
16
+
17
+ Tracking learninglocker pour les lecteurs audio/vidéo
18
+
19
+ Refactoring de la fonctionnalité "idle detector" et du calcul du temps passé sur une activité
20
+
21
+ Ajout d’informations supplémentaires affichées quand on active le debug mode
22
+
23
+
1
24
  2.1.0-beta.1(27 août 2025)
2
25
  RUPTURE DE COMPATIBILITÉ : (structure de données des rétroactions). Seules les propriétés "title" et "hypertext" sont acceptées (anciennement "title", "hypertext_1" et "hypertext_2")
3
26
 
@@ -15,9 +15,41 @@ S'affiche dans un en cadré avec un titre (obligatoire), une image (pas obligat
15
15
  `<app-comp-audio-player :aud-data="audioData2"/>`
16
16
 
17
17
 
18
- ## Paramètre
19
-
20
- Aucun paramètre n'est nécessaire pour ce composant.
18
+ ## Structure de données
19
+ Voici la structure à utiliser pour les données d'un contenu audio
20
+ > [!IMPORTANT]
21
+ > audiosData doit être un tableau (array)
22
+ ```js
23
+ import aud1 from '@/assets/medias/exemple_audio.mp3'
24
+ import posterAud1 from '@/assets/img/audio_poster.png'
25
+
26
+ export default {
27
+ data() {
28
+ return {
29
+ id: 'P01',
30
+ activityRef: 'A03',
31
+ title: 'Lecteurs médias',
32
+ type: 'pg_normal',
33
+ audiosData: [
34
+ {
35
+ id: 'aud1',
36
+ mTitle: 'Et si... Annie Ernaux nous parlait',
37
+ mSources: [
38
+ {
39
+ type: 'mp3',
40
+ src: aud1
41
+ }
42
+ ],
43
+ mPoster: posterAud1,
44
+ mAlt: "Portrait de l'autrice Annie Ernaux",
45
+ mTranscript:
46
+ '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>'
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ }
52
+ ```
21
53
 
22
54
  ### Props
23
55
 
@@ -20,9 +20,48 @@ Les transcripts ouvrent dans une fenêtre latérale. Une seule fenêtre peux êt
20
20
 
21
21
  `<app-comp-video-player :vid-data="videoData" />`
22
22
 
23
- ## Paramètre
24
-
25
- Il n'y a pas de paramètre pour ce composant.
23
+ ## Structure de données
24
+
25
+ Voici la structure à utiliser pour les données d'une vidéo
26
+ > [!IMPORTANT]
27
+ > videosData doit être un tableau (array)
28
+
29
+ ```js
30
+ import videoExemple from '@/assets/medias/exemple_video.mp4'
31
+ import posterDandurand from '@/assets/img/video_poster.jpg'
32
+ import vttVid1FR from '@/assets/medias/exemple_soustitres.vtt'
33
+
34
+ export default {
35
+ data() {
36
+ return {
37
+ id: 'P01',
38
+ activityRef: 'A03',
39
+ title: 'Lecteurs médias',
40
+ type: 'pg_normal',
41
+ videosData: [
42
+ {
43
+ id: 'vid1',
44
+ mSources: [
45
+ {
46
+ type: 'mp4',
47
+ src: videoExemple
48
+ }
49
+ ],
50
+ mSubtitles: [
51
+ {
52
+ label: 'Français',
53
+ src: vttVid1FR,
54
+ srclang: 'fr'
55
+ }
56
+ ],
57
+ mPoster: posterDandurand,
58
+ mTranscript: 'exemple_transcript.html' // The file MUST be in the public folder of the project
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ }
64
+ ```
26
65
 
27
66
  ### Props
28
67
 
package/package.json CHANGED
@@ -1,59 +1,58 @@
1
1
  {
2
2
  "name": "fcad-core-dragon",
3
- "version": "2.1.0-beta.2",
3
+ "version": "2.1.0-beta.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./src/main.js",
7
- "config": {
8
- "projname": ""
9
- },
10
7
  "scripts": {
11
- "watch": "nodemon -e js,vue,html,json -x yalc publish --push",
12
- "lintfix": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
13
- "lintreport": "eslint src --ext .js,.vue --ignore-path .gitignore",
14
- "vue": "vue",
8
+ "build": "vite build",
15
9
  "dev": "vite",
10
+ "docs:build": "vitepress build documentation",
11
+ "docs:dev": "vitepress dev documentation",
12
+ "docs:preview": "vitepress preview documentation",
16
13
  "format": "prettier --write src/",
17
- "reset": "rm -rf ./node_modules package-lock.json .cache dist && npm i && npm run watch",
18
- "build": "vite build",
14
+ "lintfix": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
15
+ "lintreport": "eslint src --ext .js,.vue --ignore-path .gitignore",
19
16
  "preview": "vite preview",
17
+ "reset": "rm -rf ./node_modules package-lock.json .cache dist && npm i && npm run watch",
20
18
  "test:unit": "vitest",
21
- "docs:dev": "vitepress dev documentation",
22
- "docs:build": "vitepress build documentation",
23
- "docs:preview": "vitepress preview documentation"
19
+ "vue": "vue",
20
+ "watch": "nodemon -e js,vue,html,json -x yalc publish --push"
21
+ },
22
+ "config": {
23
+ "projname": ""
24
24
  },
25
+ "browserslist": [
26
+ "> 1%",
27
+ "last 2 versions",
28
+ "not dead"
29
+ ],
25
30
  "dependencies": {
26
31
  "axios": "^1.6.8",
27
32
  "gsap": "^3.12.5",
28
33
  "idb": "^8.0.0",
29
34
  "mobile-detect": "^1.4.5",
30
- "pinia": "^2.1.7",
35
+ "pinia": "^3.0.3",
31
36
  "tiny-emitter": "^2.1.0",
32
37
  "vue-safe-teleport": "^0.1.2"
33
38
  },
34
39
  "devDependencies": {
35
- "@vitejs/plugin-vue": "^5.0.5",
36
- "@vue/eslint-config-prettier": "^9.0.0",
37
- "@vue/test-utils": "^2.4.0-alpha.2",
38
- "eslint": "^8.57.0",
39
- "eslint-config-prettier": "^9.1.0",
40
- "eslint-plugin-cypress": "^3.3.0",
41
- "eslint-plugin-prettier": "^5.1.3",
42
- "eslint-plugin-vue": "^9.26.0",
40
+ "@vitejs/plugin-vue": "^6.0.1",
41
+ "@vue/eslint-config-prettier": "^10.2.0",
42
+ "@vue/test-utils": "^2.4.6",
43
+ "eslint": "^9.31.0",
44
+ "eslint-plugin-cypress": "^5.1.0",
45
+ "eslint-plugin-vue": "~10.3.0",
43
46
  "jsdom": "^25.0.1",
44
47
  "nodemon": "^3.1.0",
45
- "prettier": "^3.2.5",
46
- "prettier-eslint": "^16.3.0",
48
+ "prettier": "3.6.2",
49
+ "sass-embedded": "^1.91.0",
50
+ "vite-plugin-vue-devtools": "^8.0.2",
47
51
  "vitepress": "^1.6.3",
48
- "vitest": "^2.1.5",
49
- "vue": "^3.5.12",
52
+ "vitest": "^3.2.4",
53
+ "vue": "^3.5.18",
50
54
  "vue-router": "^4.4.5"
51
55
  },
52
- "browserslist": [
53
- "> 1%",
54
- "last 2 versions",
55
- "not dead"
56
- ],
57
56
  "engines": {
58
57
  "node": ">0.11.9"
59
58
  }
@@ -36,7 +36,12 @@
36
36
  </transition>
37
37
 
38
38
  <router-view class="box" />
39
-
39
+ <div v-if="appDebugMode" class="timer">
40
+ <!-- <div class="timer"> -->
41
+ <div>Act: {{ appTimer.ISOTimeParser(activityDuration) }}</div>
42
+ <span>&nbsp|&nbsp</span>
43
+ <div>Les: {{ appTimer.ISOTimeParser(lessonDuration) }}</div>
44
+ </div>
40
45
  <app-icons-next :extra-icons="userExtraIcons" />
41
46
  </template>
42
47
 
@@ -61,18 +66,19 @@
61
66
  <script>
62
67
  import { mapState, mapActions } from 'pinia'
63
68
  import { useAppStore } from '../module/stores/appStore.js'
64
- import { timerMixin } from '../mixins/timerMixin'
69
+ import { Timer } from '../composables/useTimer.js'
70
+ import { IdleDetector } from '../composables/useIdleDetector.js'
65
71
  import { validateAppContent } from '../shared/validators'
66
- import { computed } from 'vue'
72
+ import { version as fcadVersion } from '../../package.json'
73
+ import { computed, h, reactive } from 'vue'
67
74
  import mobileDetect from 'mobile-detect'
75
+
68
76
  export default {
69
- mixins: [timerMixin],
70
77
  provide() {
71
78
  return {
72
- elapsedIdleTime: computed(() => this.elapsedIdleTime),
73
- timerCurrentState: computed(() => this.timerCurrentState),
74
- lessonDuration: computed(() => this.lessonDuration),
75
- activityDuration: computed(() => this.activityDuration)
79
+ lessonDuration: computed(() => this.lessonDuration), //exposing the lesson Duration variable to all components
80
+ elapsedIdleTime: computed(() => this.idleDetector.getElapsedTime()), //exposing idle detector elapse time to all components
81
+ appTimer: this.appTimer //exposing the app timer instance to all components
76
82
  }
77
83
  },
78
84
  props: {
@@ -86,7 +92,9 @@ export default {
86
92
  },
87
93
  setup() {
88
94
  const store = useAppStore()
89
- return { store }
95
+ const appTimer = reactive(new Timer('ativityTimer')) // Making Timer instance reactive to be able to track changes
96
+ const idleDetector = reactive(new IdleDetector()) // Making detector instance reactive.
97
+ return { store, appTimer, idleDetector }
90
98
  },
91
99
 
92
100
  data() {
@@ -99,7 +107,10 @@ export default {
99
107
  buildInfoClicked: false,
100
108
  error: [],
101
109
  initialLoading: true,
102
- f5KeyPressed: false
110
+ f5KeyPressed: false,
111
+ lessonDuration: 0,
112
+ idleTimeout: 5 * 60000, //5 mins: this is the time of inactivity before considering the user is idle
113
+ starterTimeout: null
103
114
  }
104
115
  },
105
116
  computed: {
@@ -117,8 +128,16 @@ export default {
117
128
  'getErrorMenu',
118
129
  'getRouteHistory',
119
130
  'getBookmarkEnabled',
131
+ 'getAppDebugMode',
120
132
  'getApplicationSettings'
121
133
  ]),
134
+ appDebugMode() {
135
+ return this.getAppDebugMode
136
+ },
137
+ activityDuration() {
138
+ return this.appTimer.getTime() // return the time duration in seconds
139
+ },
140
+
122
141
  getwidth() {
123
142
  return window.innerWidth
124
143
  },
@@ -134,12 +153,9 @@ export default {
134
153
  },
135
154
  appReady() {
136
155
  let readyState = this.getAppStatus === 'ready' ? true : false
137
- if (readyState) {
138
- this.setInitialLoadingDone()
139
- }
140
-
141
156
  return readyState
142
157
  },
158
+
143
159
  displayLang() {
144
160
  let lang = false
145
161
  const displayList = ['en-US', 'fr-FR', 'es-ES'] // list of xapi verbs language display
@@ -162,6 +178,20 @@ export default {
162
178
  }
163
179
  },
164
180
  watch: {
181
+ initialLoading: {
182
+ deep: true,
183
+ handler(newVal) {
184
+ if (newVal !== false) {
185
+ return
186
+ }
187
+ const eventParams = {
188
+ fcad_version: fcadVersion,
189
+ course_id: this.getAppConfigs.crs_id,
190
+ lesson_id: this.getAppConfigs.id
191
+ }
192
+ this.$analytics.sendEvent('user_meta', eventParams)
193
+ }
194
+ },
165
195
  'store.$state.userDataLoaded': {
166
196
  handler() {
167
197
  !this.store.$state.userDataLoaded
@@ -169,6 +199,13 @@ export default {
169
199
  : this.updateTracker('appBase', 'ready')
170
200
  },
171
201
  immediate: true
202
+ },
203
+ appReady: {
204
+ handler(newValue) {
205
+ if (newValue) {
206
+ this.setInitialLoadingDone()
207
+ }
208
+ }
172
209
  }
173
210
  },
174
211
 
@@ -203,24 +240,19 @@ export default {
203
240
  this.$bus.$on('fire-exit-event', this.endLesson)
204
241
  this.$bus.$on('reset-focus-on', this.resetFocus)
205
242
  this.$bus.$on('move-to-target', this.moveTo)
243
+ this.$analytics.init(this.getAppConfigs.analytics_id, this.$router)
206
244
  this.$bus.$on('start-timer', this.startAppTimer)
245
+ this.$bus.$on('stop-app-timer', this.stopAppTimer)
207
246
  this.$bus.$on('start-idle-detector', this.setIdleDetector)
208
247
  this.$bus.$on('stop-idle-detector', this.unsetIdleDetector)
209
248
  },
210
249
  beforeMount() {
211
- window.addEventListener('keydown', this.handleF5KeyPressed)
212
250
  window.addEventListener(
213
251
  'beforeunload',
214
252
  (e) => {
253
+ this.executeCloseEventTriggered()
215
254
  // e.preventDefault()
216
255
  // e.returnValue = true
217
-
218
- //prevent app to send/Save data when F5 is pressed to reload app
219
- if (this.f5KeyPressed) return
220
-
221
- this.executeCloseEventTriggered()
222
-
223
- //debugger
224
256
  },
225
257
  true
226
258
  )
@@ -230,6 +262,7 @@ export default {
230
262
  // set the language of the app
231
263
  this.setLocale(this.getAppConfigs.lang)
232
264
  window.addEventListener('keydown', this.handleF5KeyPressed)
265
+ window.addEventListener('beforeunload', this.handleBeforeUnload)
233
266
  },
234
267
  beforeUnmount() {
235
268
  this.$bus.$off('set-comp-status', this.updateTracker)
@@ -241,7 +274,9 @@ export default {
241
274
  this.$bus.$off('start-idle-detector', this.setIdleDetector)
242
275
  this.$bus.$off('stop-idle-detector', this.unsetIdleDetector)
243
276
  this.$bus.$off('start-timer', this.startAppTimer)
277
+ this.$bus.$off('stop-app-timer', this.stopAppTimer)
244
278
  window.removeEventListener('keydown', this.handleF5KeyPressed)
279
+ window.removeEventListener('beforeunload', this.handleBeforeUnload)
245
280
  if (this.getAppConfigs.remote && this.unsubscribeToSetConfig)
246
281
  this.unsubscribeToSetConfig() //stop watching changing in store to save to local storage
247
282
  },
@@ -258,30 +293,82 @@ export default {
258
293
  'setMobileState',
259
294
  'setCurrentBrowser'
260
295
  ]),
296
+ /**
297
+ * @description start the app timer instance
298
+ * Check if the timer is already started, if yes it add the activity duration
299
+ * to the lesson duration and restart the timer
300
+ */
301
+
261
302
  startAppTimer() {
262
- if (this.timerCurrentState == 'started') this.stopTimer('activity')
263
- this.startTimer('activity')
303
+ if (this.$route.name === 'module') return // don't start timer when in menu
304
+ if (this.appTimer.getTimerState() == 'started') {
305
+ this.lessonDuration += this.activityDuration //
306
+ this.appTimer.stop() // Start the activity timer
307
+ }
308
+
309
+ //Delay actually one second before starting timers
310
+ this.starterTimeout = setTimeout(() => {
311
+ this.appTimer.start() // Start the activity timer
312
+ this.setIdleDetector() //start the idle time detector
313
+ }, 500)
264
314
  },
265
315
 
316
+ /**
317
+ * @description stop the app timer instance
318
+ * The function add the activity duration to the lesson duration and stop the timer
319
+ */
320
+ stopAppTimer() {
321
+ this.lessonDuration += this.activityDuration
322
+ this.appTimer.stop() // stop timer
323
+ clearTimeout(this.starterTimeout)
324
+ },
325
+
326
+ /**
327
+ * @description set the idle detector to start detecting idle time
328
+ * Attach DOM event listeners to reset the idle timer
329
+ * The idle detector will start after 1 second after the app is loaded
330
+ * The idle detector will pause the app timer when idle time is reached
331
+ */
332
+
266
333
  setIdleDetector() {
267
334
  setTimeout(() => {
268
- this.startIdleTimer() //start the iddle dectection
335
+ this.idleDetector.startIdleTimer(
336
+ () => this.appTimer.pause(),
337
+ this.idleTimeout
338
+ )
269
339
  document.addEventListener('mousedown', this.resetIdleTimer, false) //set eventlisteners
270
340
  document.addEventListener('mousemove', this.resetIdleTimer, false) //set eventlisteners
271
341
  document.addEventListener('keypress', this.resetIdleTimer, false) //set eventlisteners
272
342
  document.addEventListener('touchmove', this.resetIdleTimer, false) //set eventlisteners
273
- }, 1000)
343
+ }, 800)
274
344
  },
275
- /*
276
- * stop the idle timer and remove hidle-dector listeners
345
+ /**
346
+ * @description unset the idle detector
347
+ * stop the idle timer and remove DOM event to stop listening the idle timer
277
348
  */
278
349
  unsetIdleDetector() {
279
- this.stopIdleTimer()
350
+ this.idleDetector.stopIdleTimer()
280
351
  document.removeEventListener('mousedown', this.resetIdleTimer, false)
281
352
  document.removeEventListener('mousemove', this.resetIdleTimer, false)
282
353
  document.removeEventListener('keypress', this.resetIdleTimer, false)
283
354
  document.removeEventListener('touchmove', this.resetIdleTimer, false)
284
355
  },
356
+ /**
357
+ * @description reset the idle timer
358
+ * The function stop the idle timer and restart it
359
+ * start also the app timer if it was paused
360
+ *
361
+ */
362
+ resetIdleTimer() {
363
+ if (this.$route.name == 'menu' || this.$route.name == 'module') return // don't reset idle timer when in menu
364
+ this.idleDetector.stopIdleTimer()
365
+
366
+ if (this.appTimer.getTimerState() == 'stopped') this.appTimer.start()
367
+ this.idleDetector.startIdleTimer(
368
+ () => this.appTimer.pause(),
369
+ this.idleTimeout
370
+ )
371
+ },
285
372
 
286
373
  setInitialLoadingDone() {
287
374
  this.initialLoading = false
@@ -580,21 +667,31 @@ export default {
580
667
  ? (d = `PT${duration.split(':')[0]}H${duration.split(':')[1]}M${
581
668
  duration.split(':')[2]
582
669
  }S`)
583
- : (d = `PT${this.activityDuration.split(':')[0]}H${
584
- this.activityDuration.split(':')[1]
585
- }M${this.activityDuration.split(':')[2]}S`)
670
+ : (d = `PT${
671
+ this.appTimer
672
+ .ISOTimeParser(this.activityDuration)
673
+ .split(':')[0]
674
+ }H${
675
+ this.appTimer
676
+ .ISOTimeParser(this.activityDuration)
677
+ .split(':')[1]
678
+ }M${
679
+ this.appTimer
680
+ .ISOTimeParser(this.activityDuration)
681
+ .split(':')[2]
682
+ }S`)
586
683
 
587
684
  stmt.result['duration'] = d
588
685
  //Set the completion status of the result
589
686
  if (completion) stmt.result['completion'] = completion
590
687
  }
591
688
 
592
- // ===================== Extension of the Object definition =====================//
593
689
  if (
594
690
  extensions &&
595
691
  extensions.constructor === Array &&
596
692
  extensions.length > 0
597
693
  ) {
694
+ // ===================== Extension of the Object definition =====================//
598
695
  //Validate each entry given in the extension Array
599
696
  extensions.forEach((e) => {
600
697
  //entry must be of type Object
@@ -622,7 +719,6 @@ export default {
622
719
  //============================================================
623
720
  stmtsQueue.push(stmt)
624
721
  })
625
-
626
722
  this.$xapi._sendStatements(stmtsQueue, cb, withFetch)
627
723
  }
628
724
  },
@@ -681,7 +777,7 @@ export default {
681
777
  }
682
778
  }
683
779
  ],
684
- duration: this.lessonDuration
780
+ duration: this.appTimer.ISOTimeParser(this.lessonDuration)
685
781
  }
686
782
 
687
783
  const endStmt = { ...stmt }
@@ -732,7 +828,8 @@ export default {
732
828
  option
733
829
  ) //send xapi statement
734
830
 
735
- if (this.timerState === 'started') setTimeout(() => this.stopTimer(), 0) //clear the timer
831
+ if (this.appTimer.getTimerState() === 'started')
832
+ setTimeout(() => this.stopAppTimer(), 0) //clear the timer
736
833
 
737
834
  if (cb) cb()
738
835
  },
@@ -750,7 +847,7 @@ export default {
750
847
  this.setRouteHistory([]) // resetting store record for all last visited pages
751
848
 
752
849
  // this.$bus.$emit('stop-timer')
753
- this.stopTimer()
850
+ this.stopAppTimer()
754
851
  if (this.getModuleInfo.packageType !== 'xapi') return
755
852
 
756
853
  if (!this.getConnectionInfo || this.getConnectionInfo.remote == false)
@@ -929,7 +1026,7 @@ export default {
929
1026
  if (e) e.focus()
930
1027
  },
931
1028
  /**
932
- * @description Mothods to validate that application settings are correctly
1029
+ * @description Method to validate that application settings are correctly
933
1030
  * Opens error component when configurations are not correct
934
1031
  *
935
1032
  */
@@ -981,9 +1078,24 @@ export default {
981
1078
  'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
982
1079
  )
983
1080
  },
984
-
1081
+ /**
1082
+ * @description Handle the keydown event to detect if F5 key was pressed
1083
+ * The function set the f5KeyPressed to true if F5 key was pressed
1084
+ * @param {Event} e - keydown event
1085
+ */
985
1086
  handleF5KeyPressed(e) {
986
1087
  this.f5KeyPressed = e.code == 'F5'
1088
+ },
1089
+
1090
+ /**
1091
+ * @description Handle the beforeunload event to save data when the user close the app or reload the app
1092
+ * The function check if the F5 key was pressed to avoid sending data when the user reload the app
1093
+ */
1094
+ handleBeforeUnload() {
1095
+ //prevent app to send/Save data when F5 is pressed to reload app
1096
+ if (this.f5KeyPressed) return
1097
+
1098
+ this.executeCloseEventTriggered()
987
1099
  }
988
1100
  }
989
1101
  }
@@ -1039,4 +1151,20 @@ export default {
1039
1151
  transform: rotate(360deg);
1040
1152
  }
1041
1153
  }
1154
+
1155
+ .timer {
1156
+ position: sticky; /* Sticks inside the parent container */
1157
+ bottom: 90%; /* Sticks to the top edge of the parent */
1158
+ left: 100%; /* Sticks to the left edge of the parent */
1159
+ display: flex;
1160
+ align-items: center;
1161
+ justify-content: flex-start;
1162
+ font-size: 1em;
1163
+ width: fit-content;
1164
+ color: #333;
1165
+ opacity: 0.9;
1166
+ background-color: #eaabb6b3; /* Optional: ensures readability when content scrolls */
1167
+ padding: 5px 12px; /* Some breathing space */
1168
+ z-index: 1000; /* Keeps it above other elements */
1169
+ }
1042
1170
  </style>
@@ -11,7 +11,6 @@ describe('AppBaseButton Tests', () => {
11
11
  })
12
12
  expect(wrapper.find('button').exists()).toBeTruthy()
13
13
  })
14
-
15
14
  it('should fire click event', async () => {
16
15
  const wrapper = mount(AppBaseButton, {
17
16
  props: defaultProps