1// Onboarding modal â auto-opens once per id (localStorage-gated). 2 3document.addEventListener('alpine:init', () => { 4 Alpine.data('onboardingModal', (config) => { 5 const baseModal = window.__labelBaseModal(); 6 return { 7 ...baseModal, 8 slides: config.slides || [], 9 currentIndex: 0, 10 id: config.id, 11 finalCtaLabel: config.final_cta_label, 12 finalCtaAction: config.final_cta_action || { type: 'close' }, 13 14 get seenKey() { 15 return `onboarding-seen-${this.id}`; 16 }, 17 18 get primaryLabel() { 19 if (this.currentIndex === 0) return "C'est parti !"; 20 if (this.currentIndex === this.slides.length - 1) return this.finalCtaLabel; 21 return 'Suivant'; 22 }, 23 24 init() { 25 baseModal.init && baseModal.init.call(this); 26 try { 27 if (!localStorage.getItem(this.seenKey)) { 28 // Marked on display, not on dismissal: there is no close button, so a 29 // user leaving via Back or a closed tab reaches no dismissal path and 30 // would otherwise be greeted again on every page load and every login. 31 this.markSeen(); 32 this.$nextTick(() => { 33 this.open = true; 34 }); 35 } 36 } catch (_e) { 37 // localStorage may be unavailable (private mode, quota); skip auto-open. 38 } 39 }, 40 41 markSeen() { 42 try { 43 localStorage.setItem(this.seenKey, '1'); 44 } catch (_e) { 45 // noop â best effort 46 } 47 }, 48 49 toggle() { 50 if (this.open) this.markSeen(); 51 baseModal.toggle.call(this); 52 }, 53 54 close() { 55 this.markSeen(); 56 return baseModal.close.call(this); 57 }, 58 59 prev() { 60 if (this.currentIndex > 0) this.currentIndex -= 1; 61 }, 62 63 advance() { 64 if (this.currentIndex < this.slides.length - 1) { 65 this.currentIndex += 1; 66 } else { 67 this.finish(); 68 } 69 }, 70 71 finish() { 72 this.markSeen(); 73 const action = this.finalCtaAction || { type: 'close' }; 74 if (action.type === 'redirect' && action.url) { 75 window.location.href = action.url; 76 } else { 77 this.open = false; 78 } 79 }, 80 81 modalWrapper: { 82 ...baseModal.modalWrapper, 83 // Topmost-only ESC: when other tiered modales render alongside, 84 // focus-trap puts focus in the topmost wrapper; only that one acts. 85 ['@keydown.window.escape']() { 86 if (this.open && this.$el.contains(document.activeElement)) { 87 this.close(); 88 } 89 }, 90 }, 91 }; 92 }); 93});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.