1const { initFlowbite, Dropdown } = await packageLoader.flowbite(); 2 3/** 4 * Represents a Component function that initializes Flowbite. 5 * 6 * @param {Object} dotnetContext - The dotnet context object. 7 * @param {Object} itemRef - The item reference object. 8 * @throws {Error} Throws an error if initialization fails. 9 */ 10export function Component(dotnetContext, itemRef) { 11 initFlowbite(); 12 initializePhonePinCodeInput(); 13 return { 14 reinit: function () { 15 // Re-initialize Flowbite to pick up new elements added to the DOM 16 try { 17 // Find all dropdown toggle buttons 18 const dropdownToggles = document.querySelectorAll( 19 "[data-dropdown-toggle]", 20 ); 21 22 dropdownToggles.forEach((toggle) => { 23 const dropdownId = toggle.getAttribute("data-dropdown-toggle"); 24 const targetEl = document.getElementById(dropdownId); 25 26 if (targetEl) { 27 // Get dropdown configuration from data attributes 28 const placement = 29 toggle.getAttribute("data-dropdown-placement") || "bottom"; 30 const triggerType = 31 toggle.getAttribute("data-dropdown-trigger") || "click"; 32 const delay = 33 parseInt(toggle.getAttribute("data-dropdown-delay")) || 0; 34 35 // Initialize or reinitialize the dropdown 36 // The override: true option replaces existing instances 37 new Dropdown( 38 targetEl, 39 toggle, 40 { 41 placement: placement, 42 triggerType: triggerType, 43 delay: delay, 44 }, 45 { id: dropdownId, override: true }, 46 ); 47 } else { 48 console.warn( 49 `Target element not found for dropdown: ${dropdownId}`, 50 ); 51 } 52 }); 53 54 console.log( 55 `Flowbite dropdowns reinitialized: ${dropdownToggles.length} found`, 56 ); 57 } catch (error) { 58 console.error("Error reinitializing Flowbite:", error); 59 } 60 }, 61 hideAllDropdowns: function () { 62 // Hide all open dropdowns immediately 63 try { 64 const openDropdowns = document.querySelectorAll( 65 "[data-dropdown-toggle]", 66 ); 67 68 openDropdowns.forEach((toggle) => { 69 const dropdownId = toggle.getAttribute("data-dropdown-toggle"); 70 const targetEl = document.getElementById(dropdownId); 71 72 if (targetEl && !targetEl.classList.contains("hidden")) { 73 // Hide the dropdown by adding the hidden class 74 targetEl.classList.add("hidden"); 75 console.log(`Closed dropdown: ${dropdownId}`); 76 } 77 }); 78 } catch (error) { 79 console.error("Error hiding dropdowns:", error); 80 } 81 }, 82 }; 83} 84 85/** 86 * Initializes pin code input functionality with auto-advancing focus and validation 87 * Supports both existing and dynamically added elements 88 */ 89const initializePhonePinCodeInput = function () { 90 try { 91 // Initialize existing elements 92 initializeExistingPinInputs(); 93 94 // Set up event delegation for future elements 95 setupEventDelegation(); 96 97 // Set up MutationObserver to watch for new elements 98 setupMutationObserver(); 99 } catch (error) { 100 console.warn("Error initializing pin code input:", error); 101 } 102}; 103 104/** 105 * Initialize existing pin input elements 106 */ 107function initializeExistingPinInputs() { 108 const pinContainers = document.querySelectorAll("[data-pin-input]"); 109 pinContainers.forEach((container) => { 110 initializePinContainer(container); 111 }); 112} 113 114/** 115 * Initialize a specific pin container 116 * @param {Element} container - The pin input container element 117 */ 118function initializePinContainer(container) { 119 // Skip if already initialized 120 if (container.hasAttribute("data-pin-initialized")) { 121 return; 122 } 123 124 const inputs = container.querySelectorAll("[data-pin-input-item]"); 125 if (inputs.length === 0) return; 126 127 // Mark as initialized 128 container.setAttribute("data-pin-initialized", "true"); 129 130 // Store reference to inputs on the container for easy access
131 container._pinInputs = inputs; 132} 133 134/** 135 * Set up event delegation for pin input functionality 136 */ 137function setupEventDelegation() { 138 // Use event delegation on document body to catch all pin input events 139 document.body.addEventListener("input", (e) => { 140 if (e.target.matches("[data-pin-input-item]")) { 141 const container = e.target.closest("[data-pin-input]"); 142 if (container) { 143 const inputs = container.querySelectorAll("[data-pin-input-item]"); 144 const index = Array.from(inputs).indexOf(e.target); 145 handleInput(e, inputs, index); 146 } 147 } 148 }); 149 150 document.body.addEventListener("keydown", (e) => { 151 if (e.target.matches("[data-pin-input-item]")) { 152 const container = e.target.closest("[data-pin-input]"); 153 if (container) { 154 const inputs = container.querySelectorAll("[data-pin-input-item]"); 155 const index = Array.from(inputs).indexOf(e.target); 156 handleKeydown(e, inputs, index); 157 } 158 } 159 }); 160 161 document.body.addEventListener("paste", (e) => { 162 if (e.target.matches("[data-pin-input-item]")) { 163 const container = e.target.closest("[data-pin-input]"); 164 if (container) { 165 const inputs = container.querySelectorAll("[data-pin-input-item]"); 166 const index = Array.from(inputs).indexOf(e.target); 167 handlePaste(e, inputs, index); 168 } 169 } 170 }); 171 172 document.body.addEventListener( 173 "focus", 174 (e) => { 175 if (e.target.matches("[data-pin-input-item]")) { 176 e.target.select(); // Select all text when focused 177 } 178 }, 179 true, 180 ); // Use capture phase for focus events 181 182 document.body.addEventListener("keypress", (e) => { 183 if (e.target.matches("[data-pin-input-item]")) { 184 if (!/^[0-9a-zA-Z]$/.test(e.key) && !isNavigationKey(e.key)) { 185 e.preventDefault(); 186 } 187 } 188 }); 189} 190 191/** 192 * Set up MutationObserver to watch for dynamically added pin input containers 193 */ 194function setupMutationObserver() { 195 const observer = new MutationObserver((mutations) => { 196 mutations.forEach((mutation) => { 197 mutation.addedNodes.forEach((node) => { 198 if (node.nodeType === Node.ELEMENT_NODE) { 199 // Check if the added node is a pin container 200 if (node.matches && node.matches("[data-pin-input]")) { 201 initializePinContainer(node); 202 } 203 204 // Check for pin containers within the added node 205 const pinContainers = 206 node.querySelectorAll && node.querySelectorAll("[data-pin-input]"); 207 if (pinContainers) { 208 pinContainers.forEach((container) => { 209 initializePinContainer(container); 210 }); 211 } 212 } 213 }); 214 }); 215 }); 216 217 // Start observing 218 observer.observe(document.body, { 219 childList: true, 220 subtree: true, 221 }); 222 223 // Store observer reference for potential cleanup 224 if (!window._pinInputObserver) { 225 window._pinInputObserver = observer; 226 } 227} 228 229/** 230 * Handles input events for pin code fields 231 * @param {Event} e - The input event 232 * @param {NodeList} inputs - All input elements 233 * @param {number} index - Current input index 234 */ 235function handleInput(e, inputs, index) { 236 const input = e.target; 237 const value = input.value; 238 239 // Limit to single character and sanitize 240 if (value.length > 1) { 241 input.value = value.slice(-1); // Keep only the last character 242 } 243 244 // Auto-advance to next input if current has value and there's a next input 245 if (input.value && index < inputs.length - 1) { 246 const nextInput = inputs[index + 1]; 247 if (nextInput && !nextInput.disabled) { 248 setTimeout(() => { 249 nextInput.focus(); 250 nextInput.select(); 251 }, 0); 252 } 253 } 254 255 // Trigger form validation or completion check 256 checkCompletion(inputs); 257} 258 259/** 260 * Handles keydown events for navigation and deletion 261 * @param {Event} e - The keydown event 262 * @param {NodeList} inputs - All input elements 263 * @param {number} index - Current input index 264 */ 265function handleKeydown(e, inputs, index) { 266 const input = e.target; 267 268 switch (e.key) { 269 case "Backspace": 270 // If current input is empty, move to previous and clear it 271 if (!input.value && index > 0) { 272 e.preventDefault(); 273 const prevInput = inputs[index - 1]; 274 if (prevInput && !prevInput.disabled) { 275 prevInput.focus(); 276 prevInput.select(); 277 prevInput.value = ""; 278 } 279 } 280 break; 281 282 case "Delete": 283 // Clear current input 284 input.value = ""; 285 break; 286 287 case "ArrowLeft": 288 e.preventDefault(); 289 if (index > 0) { 290 const prevInput = inputs[index - 1]; 291 if (prevInput && !prevInput.disabled) { 292 prevInput.focus(); 293 prevInput.select(); 294 } 295 } 296 break; 297 298 case "ArrowRight": 299 e.preventDefault(); 300 if (index < inputs.length - 1) { 301 const nextInput = inputs[index + 1]; 302 if (nextInput && !nextInput.disabled) { 303 nextInput.focus(); 304 nextInput.select(); 305 } 306 } 307 break; 308 309 case "Home": 310 e.preventDefault(); 311 const firstInput = inputs[0]; 312 if (firstInput && !firstInput.disabled) { 313 firstInput.focus(); 314 firstInput.select(); 315 } 316 break; 317 318 case "End": 319 e.preventDefault(); 320 const lastInput = inputs[inputs.length - 1]; 321 if (lastInput && !lastInput.disabled) { 322 lastInput.focus(); 323 lastInput.select(); 324 } 325 break; 326 } 327} 328 329/** 330 * Handles paste operations to distribute characters across inputs 331 * @param {Event} e - The paste event 332 * @param {NodeList} inputs - All input elements 333 * @param {number} index - Current input index 334 */ 335function handlePaste(e, inputs, index) { 336 e.preventDefault(); 337 338 try { 339 const pastedData = (e.clipboardData || window.clipboardData).getData( 340 "text", 341 ); 342 const sanitizedData = pastedData.replace(/[^0-9a-zA-Z]/g, ""); // Remove invalid characters 343 344 // Distribute characters across available inputs starting from current position 345 for ( 346 let i = 0; 347 i < sanitizedData.length && index + i < inputs.length; 348 i++ 349 ) { 350 const targetInput = inputs[index + i]; 351 if (targetInput && !targetInput.disabled) { 352 targetInput.value = sanitizedData[i]; 353 } 354 } 355
356 // Focus the next empty input or the last filled input 357 const nextEmptyIndex = Math.min( 358 index + sanitizedData.length, 359 inputs.length - 1, 360 ); 361 const targetInput = inputs[nextEmptyIndex]; 362 if (targetInput && !targetInput.disabled) { 363 setTimeout(() => { 364 targetInput.focus(); 365 targetInput.select(); 366 }, 0); 367 } 368 369 // Check completion after paste 370 checkCompletion(inputs); 371 } catch (error) { 372 console.warn("Error handling paste operation:", error); 373 } 374} 375 376/** 377 * Checks if navigation key should be allowed 378 * @param {string} key - The key being pressed 379 * @returns {boolean} - Whether the key is a navigation key 380 */ 381function isNavigationKey(key) { 382 const navigationKeys = [ 383 "Backspace", 384 "Delete", 385 "Tab", 386 "Escape", 387 "Enter", 388 "Home", 389 "End", 390 "ArrowLeft", 391 "ArrowRight", 392 "ArrowUp", 393 "ArrowDown", 394 ]; 395 return navigationKeys.includes(key); 396} 397 398/** 399 * Checks if all inputs are filled and triggers completion event 400 * @param {NodeList} inputs - All input elements 401 */ 402function checkCompletion(inputs) { 403 try { 404 const values = Array.from(inputs).map((input) => input.value); 405 const allFilled = values.every((value) => value.length > 0); 406 const pinValue = values.join(""); 407 const container = inputs[0].closest("[data-pin-input]"); 408 409 // Find the first hidden input within the container that has the [data-pin-input-value] selector 410 const hiddenInput = container.querySelector("[data-pin-input-value]"); 411 412 if (hiddenInput) { 413 // Update the hidden input value 414 const previousValue = hiddenInput.value; 415 hiddenInput.value = pinValue; 416 417 // Trigger change event if the value actually changed 418 if (previousValue !== pinValue) { 419 // Create and dispatch input event 420 const inputEvent = new Event("input", { 421 bubbles: true, 422 cancelable: true, 423 }); 424 hiddenInput.dispatchEvent(inputEvent); 425 426 // Create and dispatch change event 427 const changeEvent = new Event("change", { 428 bubbles: true, 429 cancelable: true, 430 }); 431 hiddenInput.dispatchEvent(changeEvent); 432 } 433 } 434 435 if (allFilled) { 436 // Dispatch custom event for completion 437 const completionEvent = new CustomEvent("pinCompleted", { 438 detail: { value: pinValue, inputs: inputs }, 439 bubbles: true, 440 }); 441 442 container.dispatchEvent(completionEvent); 443 } 444 } catch (error) { 445 console.warn("Error checking completion:", error); 446 } 447} 448 449/** 450 * Cleanup function to remove observers and event listeners if needed 451 * Call this if you need to clean up the pin input functionality 452 */ 453window.cleanupPinInputs = function () { 454 if (window._pinInputObserver) { 455 window._pinInputObserver.disconnect(); 456 window._pinInputObserver = null; 457 } 458 459 // Remove initialization markers 460 document.querySelectorAll("[data-pin-initialized]").forEach((container) => { 461 container.removeAttribute("data-pin-initialized"); 462 delete container._pinInputs; 463 }); 464};
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.