1import { Controller } from "@hotwired/stimulus"; 2 3export default class extends Controller { 4 static targets = ["step"]; 5 static values = { 6 typeSpeed: { type: Number, default: 30 }, 7 glowDuration: { type: Number, default: 2000 }, 8 }; 9 10 connect() { 11 this.currentStep = 0; 12 this.charIndex = 0; 13 this.phase = "typing"; 14 15 this.cacheOriginalTexts(); 16 this.initializeSteps(); 17 this.startAnimation(); 18 } 19 20 disconnect() { 21 this.stopAnimation(); 22 if (this.initTimeout) { 23 clearTimeout(this.initTimeout); 24 this.initTimeout = null; 25 } 26 } 27 28 stepTargetConnected(element) { 29 if (!element.dataset.originalText) { 30 const textEl = element.querySelector("[data-loading-steps-text]"); 31 element.dataset.originalText = textEl ? textEl.textContent : element.textContent; 32 } 33 34 if (this.initTimeout) { 35 clearTimeout(this.initTimeout); 36 } 37 this.initTimeout = setTimeout(() => { 38 this.initializeSteps(); 39 this.startAnimation(); 40 }, 0); 41 } 42 43 cacheOriginalTexts() { 44 this.stepTargets.forEach((step) => { 45 if (!step.dataset.originalText) { 46 const textEl = step.querySelector("[data-loading-steps-text]"); 47 step.dataset.originalText = textEl ? textEl.textContent : step.textContent; 48 } 49 }); 50 } 51 52 initializeSteps() { 53 if (this.stepTargets.length === 0) return; 54 55 this.currentStep = Math.min(this.currentStep, this.stepTargets.length - 1); 56 57 this.stepTargets.forEach((step, index) => { 58 const isCurrent = index === this.currentStep; 59 step.classList.toggle("hidden", !isCurrent); 60 61 if (isCurrent) { 62 this.renderCurrentStep(); 63 } 64 }); 65 } 66 67 renderCurrentStep() { 68 const step = this.stepTargets[this.currentStep]; 69 if (!step) return; 70 71 if (!step.dataset.originalText) { 72 const textEl = step.querySelector("[data-loading-steps-text]"); 73 step.dataset.originalText = textEl ? textEl.textContent : step.textContent; 74 } 75 76 const originalText = step.dataset.originalText || ""; 77 const textEl = step.querySelector("[data-loading-steps-text]"); 78 const target = textEl || step; 79 80 target.textContent = originalText.slice(0, this.charIndex); 81 step.classList.toggle("loading-steps-glow", this.phase === "glowing"); 82 } 83 84 startAnimation() { 85 this.stopAnimation(); 86 if (this.stepTargets.length === 0) return; 87 88 if (this.phase === "typing") { 89 this.startTyping(); 90 } else { 91 this.startGlowing(); 92 } 93 } 94 95 startTyping() { 96 const step = this.stepTargets[this.currentStep]; 97 if (!step) return; 98 99 if (!step.dataset.originalText) { 100 const textEl = step.querySelector("[data-loading-steps-text]"); 101 step.dataset.originalText = textEl ? textEl.textContent : step.textContent; 102 } 103 104 const originalText = step.dataset.originalText || ""; 105 const textEl = step.querySelector("[data-loading-steps-text]"); 106 const target = textEl || step; 107 108 if (this.charIndex >= originalText.length) { 109 this.phase = "glowing"; 110 this.startGlowing(); 111 return; 112 } 113 114 this.timer = setInterval(() => { 115 this.charIndex++; 116 target.textContent = originalText.slice(0, this.charIndex); 117 118 if (this.charIndex >= originalText.length) { 119 this.stopAnimation(); 120 this.phase = "glowing"; 121 this.startGlowing(); 122 } 123 }, this.typeSpeedValue); 124 } 125 126 startGlowing() { 127 const step = this.stepTargets[this.currentStep]; 128 if (!step) return; 129 130 step.classList.add("loading-steps-glow"); 131 132 this.timer = setTimeout(() => { 133 step.classList.remove("loading-steps-glow"); 134 this.advanceToNextStep(); 135 }, this.glowDurationValue); 136 } 137 138 advanceToNextStep() { 139 if (this.stepTargets.length === 0) return; 140 141 const currentStep = this.stepTargets[this.currentStep]; 142 if (currentStep) { 143 currentStep.classList.add("hidden"); 144 currentStep.classList.remove("loading-steps-glow"); 145 } 146 147 this.currentStep = (this.currentStep + 1) % this.stepTargets.length; 148 this.charIndex = 0; 149 this.phase = "typing"; 150 151 const nextStep = this.stepTargets[this.currentStep]; 152 if (nextStep) { 153 nextStep.classList.remove("hidden"); 154 this.renderCurrentStep(); 155 } 156 157 this.startAnimation(); 158 } 159 160 stopAnimation() { 161 if (this.timer) { 162 clearInterval(this.timer); 163 clearTimeout(this.timer); 164 this.timer = null; 165 } 166 } 167};
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.