PageSourceSearch

https://railsblocks.com/assets/controllers/password_controller-ff0…1db2df12bf3f17e7fdcf9c526c5555ef93a2f539.js

js railsblocks.com collected 2026-09-25 20:23:54 UTC 11,590 bytes, 345 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2
3export default class extends Controller {
4  static targets = [
5    "input",
6    "toggleIcon",
7    "strengthBar",
8    "strengthText",
9    "lengthCheck",
10    "lowercaseCheck",
11    "uppercaseCheck",
12    "numberCheck",
13    "confirm",
14    "confirmToggleIcon",
15    "matchIndicator",
16    "matchText",
17  ];
18
19  static values = {
20    strength: { type: Boolean, default: false },
21    requirements: { type: Boolean, default: false },
22    confirm: { type: Boolean, default: false },
23    confirmDelay: { type: Number, default: 300 },
24  };
25
26  connect() {
27    this.isVisible = false;
28    this.confirmVisible = false;
29
30    if (this.hasInputTarget) {
31      if (this.strengthValue) {
32        this.checkStrength();
33      }
34      if (this.requirementsValue) {
35        this.checkRequirements();
36      }
37    }
38  }
39
40  disconnect() {
41    // Clear any pending timeout when the controller is disconnected
42    if (this.confirmValue) {
43      clearTimeout(this.checkTimeout);
44    }
45  }
46
47  toggle() {
48    this.isVisible = !this.isVisible;
49
50    if (this.isVisible) {
51      this.inputTarget.type = "text";
52      this.updateIcon(this.toggleIconTarget, true);
53    } else {
54      this.inputTarget.type = "password";
55      this.updateIcon(this.toggleIconTarget, false);
56    }
57  }
58
59  toggleConfirm() {
60    if (!this.confirmValue) return;
61
62    this.confirmVisible = !this.confirmVisible;
63
64    if (this.confirmVisible) {
65      this.confirmTarget.type = "text";
66      this.updateIcon(this.confirmToggleIconTarget, true);
67    } else {
68      this.confirmTarget.type = "password";
69      this.updateIcon(this.confirmToggleIconTarget, false);
70    }
71  }
72
73  // Called on input event
74  handleInput() {
75    if (this.strengthValue) {
76      this.checkStrength();
77    }
78    if (this.requirementsValue) {
79      this.checkRequirements();
80    }
81    if (this.confirmValue && this.hasConfirmTarget && this.confirmTarget.value) {
82      this.checkMatch();
83    }
84  }
85
86  // Strength checking functionality
87  checkStrength() {
88    const password = this.inputTarget.value;
89    let strength = 0;
90    const feedback = [];
91
92    // Length check
93    if (password.length >= 8) {
94      strength += 25;
95    } else if (password.length > 0) {
96      feedback.push("At least 8 characters");
97    }
98
99    // Lowercase check
100    if (/[a-z]/.test(password)) {
101      strength += 25;
102    } else if (password.length > 0) {
103      feedback.push("Include lowercase letter");
104    }
105
106    // Uppercase check
107    if (/[A-Z]/.test(password)) {
108      strength += 25;
109    } else if (password.length > 0) {
110      feedback.push("Include uppercase letter");
111    }
112
113    // Number or special character check
114    if (/[0-9!@#$%^&*]/.test(password)) {
115      strength += 25;
116    } else if (password.length > 0) {
117      feedback.push("Include number or special character");
118    }
119
120    this.updateStrengthIndicator(strength);
121  }
122
123  updateStrengthIndicator(strength) {
124    if (!this.hasStrengthBarTarget) return;
125
126    // Update bar width and color
127    this.strengthBarTarget.style.width = `${strength}%`;
128
129    // Update color classes
130    this.strengthBarTarget.classList.remove(
131      "bg-red-500",
132      "bg-yellow-500",
133      "bg-lime-500",
134      "bg-green-500",
135      "dark:bg-red-400",
136      "dark:bg-yellow-400",
137      "dark:bg-lime-400",
138      "dark:bg-green-400",
139      "bg-neutral-300",
140      "dark:bg-neutral-600",
141    );
142
143    if (strength === 0) {
144      this.strengthBarTarget.classList.add("bg-neutral-300", "dark:bg-neutral-600");
145    } else if (strength <= 25) {
146      this.strengthBarTarget.classList.add("bg-red-500", "dark:bg-red-400");
147    } else if (strength <= 50) {
148      this.strengthBarTarget.classList.add("bg-yellow-500", "dark:bg-yellow-400");
149    } else if (strength < 100) {
150      this.strengthBarTarget.classList.add("bg-lime-500", "dark:bg-lime-400");
151    } else {
152      this.strengthBarTarget.classList.add("bg-green-500", "dark:bg-green-400");
153    }
154
155    // Update text
156    if (this.hasStrengthTextTarget) {
157      if (strength === 0) {
158        this.strengthTextTarget.textContent = "";
159        this.strengthTextTarget.classList.add("hidden");
160      } else {
161        this.strengthTextTarget.classList.remove("hidden");
162
163        // Remove all text color classes
164        this.strengthTextTarget.classList.remove(
165          "text-red-600",
166          "text-yellow-600",
167          "text-lime-600",
168          "text-green-600",
169          "dark:text-red-400",
170          "dark:text-yellow-400",
171          "dark:text-lime-400",
172          "dark:text-green-400",
173        );
174
175        // Update text content and color classes
176        if (strength <= 25) {
177          this.strengthTextTarget.textContent = "Weak";
178          this.strengthTextTarget.classList.add("text-red-600", "dark:text-red-400");
179        } else if (strength <= 50) {
180          this.strengthTextTarget.textContent = "Fair";
181          this.strengthTextTarget.classList.add("text-yellow-600", "dark:text-yellow-400");
182        } else if (strength < 100) {
183          this.strengthTextTarget.textContent = "Good";
184          this.strengthTextTarget.classList.add("text-lime-600", "dark:text-lime-400");
185        } else {
186          this.strengthTextTarget.textContent = "Strong";
187          this.strengthTextTarget.classList.add("text-green-600", "dark:text-green-400");
188        }
189      }
190    }
191  }
192
193  // Requirements checking functionality
194  checkRequirements() {
195    const password = this.inputTarget.value;
196
197    // Check length
198    this.updateRequirement(this.lengthCheckTarget, password.length >= 8);
199
200    // Check lowercase
201    this.updateRequirement(this.lowercaseCheckTarget, /[a-z]/.test(password));
202
203    // Check uppercase
204    this.updateRequirement(this.uppercaseCheckTarget, /[A-Z]/.test(password));
205
206    // Check number or special character
207    this.updateRequirement(this.numberCheckTarget, /[0-9!@#$%^&*]/.test(password));
208  }
209
210  updateRequirement(target, met) {
211    if (!target) return;
212
213    const icons = target.querySelectorAll("svg");
214    const uncheckedIcon = icons[0]; // First SVG is the unchecked circle
215    const checkedIcon = icons[1]; // Second SVG is the checked circle
216    const text = target.querySelector("span");
217
218    if (met) {
219      // Requirement is met - show checked icon
220      target.classList.remove("text-neutral-500", "dark:text-neutral-400");
221      target.classList.add("text-green-600", "dark:text-green-500");
222
223      // Toggle icons
224      if (uncheckedIcon) uncheckedIcon.classList.add("hidden");
225      if (checkedIcon) checkedIcon.classList.remove("hidden");
226
227      if (text) text.classList.add("line-through");
228    } else {
229      // Requirement is not met - show unchecked icon
230      target.classList.remove("text-green-600", "dark:text-green-500");
231      target.classList.add("text-neutral-500", "dark:text-neutral-400");
232
233      // Toggle icons
234      if (uncheckedIcon) uncheckedIcon.classList.remove("hidden");
235      if (checkedIcon) checkedIcon.classList.add("hidden");
236
237      if (text) text.classList.remove("line-through");
238    }
239  }
240
241  // Confirmation matching functionality
242  checkMatch() {
243    // Clear any existing timeout
244    clearTimeout(this.checkTimeout);
245
246    // Set a new timeout to check after the delay
247    this.checkTimeout = setTimeout(() => {
248      this.performCheck();
249    }, this.confirmDelayValue);
250  }
251
252  performCheck() {
253    const password = this.inputTarget.value;
254    const confirm = this.confirmTarget.value;
255
256    if (!confirm) {
257      this.hideMatchIndicator();
258      return;
259    }
260
261    if (password === confirm) {
262      this.showMatch();
263    } else {
264      this.showMismatch();
265    }
266  }
267
268  showMatch() {
269    if (this.hasMatchIndicatorTarget) {
270      this.matchIndicatorTarget.classList.remove("hidden", "text-red-600", "dark:text-red-400");
271      this.matchIndicatorTarget.classList.add("text-green-600", "dark:text-green-400");
272
273      // Update icon to checkmark
274      const iconElement = this.matchIndicatorTarget.querySelector("svg");
275      if (iconElement) {
276        iconElement.innerHTML = `
277          <g fill="currentColor">
278            <path d="M5.25,11.75h-.002c-.177,0-.344-.081-.454-.219L1.794,7.531c-.202-.252-.161-.62,.092-.821,.253-.202,.621-.161,.821,.092l2.544,3.18L10.411,2.549c.204-.251,.571-.29,.822-.086,.251,.204,.29,.572,.086,.822l-5.611,8.246c-.111,.138-.278,.219-.458,.219Z" fill="currentColor"></path>
279          </g>
280        `;
281      }
282    }
283
284    if (this.hasMatchTextTarget) {
285      this.matchTextTarget.textContent = "Passwords match";
286    }
287
288    // Update confirm input border
289    this.confirmTarget.classList.remove("input-error");
290    this.confirmTarget.classList.add("input-success");
291  }
292
293  showMismatch() {
294    if (this.hasMatchIndicatorTarget) {
295      this.matchIndicatorTarget.classList.remove("hidden", "text-green-600", "dark:text-green-400");
296      this.matchIndicatorTarget.classList.add("text-red-600", "dark:text-red-400");
297
298      // Update icon to X mark
299      const iconElement = this.matchIndicatorTarget.querySelector("svg");
300      if (iconElement) {
301        iconElement.innerHTML = `
302          <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
303            <path d="M10 4L4 10M4 4l6 6"></path>
304          </g>
305        `;
306      }
307    }
308
309    if (this.hasMatchTextTarget) {
310      this.matchTextTarget.textContent = "Passwords do not match";
311    }
312
313    // Update confirm input border
314    this.confirmTarget.classList.remove("input-success");
315    this.confirmTarget.classList.add("input-error");
316  }
317
318  hideMatchIndicator() {
319    if (this.hasMatchIndicatorTarget) {
320      this.matchIndicatorTarget.classList.add("hidden");
321    }
322
323    // Reset confirm input border
324    if (this.hasConfirmTarget) {
325      this.confirmTarget.classList.remove("input-success", "input-error");
326    }
327  }
328
329  // Shared icon update functionality
330  updateIcon(target, visible) {
331    if (!target) return;
332
333    if (visible) {
334      // Show eye-off icon when password is visible
335      target.innerHTML = `
336        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M4.8077 13.1923C3.4687 12.267 2.56488 11.0325 2.04418 10.1133C1.65178 9.42061 1.65178 8.57951 2.04418 7.88681C2.99118 
3366.21511 5.2055 3.50009 8.9999 3.50009C10.708 3.50009 12.0959 4.0503 13.1921 4.8078"></path> <path d="M15.327 6.9151C15.578 7.2579 15.7869 7.58889 15.9556 7.88669C16.348 8.57939 16.348 9.42049 15.9556 10.1132C15.0086 11.7849 12.7943 14.4999 8.99994 14.4999C8.59234 14.4999 8.20304 14.4686 7.83154 14.4106"></path> <path d="M7.05551 10.9446C6.55781 10.4469 6.25 9.7594 6.25 9C6.25 7.4812 7.4812 6.25 9 6.25C9.7594 6.25 10.4469 6.55779 10.9445 7.05539"></path> <path d="M2 16L16 2"></path></g></svg>
337      `;
338    } else {
339      // Show eye icon when password is hidden
340      target.innerHTML = `
341        <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M9 11.75C10.5188 11.75 11.75 10.5188 11.75 9C11.75 7.48122 10.5188 6.25 9 6.25C7.48122 6.25 6.25 7.48122 6.25 9C6.25 10.5188 7.48122 11.75 9 11.75Z"></path> <path d="M15.9557 7.88669C16.3481 8.57939 16.3481 9.42049 15.9557 10.1132C15.0087 11.7849 12.7944 14.4999 9 14.4999C5.2056 14.4999 2.9912 11.7849 2.0443 10.1132C1.6519 9.42049 1.6519 8.57939 2.0443 7.88669C2.9913 6.21499 5.2056 3.5 9 3.5C12.7944 3.5 15.0088 6.21499 15.9557 7.88669Z"></path></g></svg>
342      `;
343    }
344  }
345};

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.