@@ -41,6 +41,7 @@ let isFormSubmitting = false;
4141let FW_NewUpdateVersAvailable = ' ' ;
4242var fwTimeInvalidFromConfig = false ;
4343var fwTimeInvalidMsg = ' ' ;
44+ var fwUpdateEstimatedRunDate = ' TBD' ;
4445
4546// Order of NVRAM keys to search for 'Model ID' and 'Product ID' //
4647const modelKeys = [" nvram_odmpid" , " nvram_wps_modelnum" , " nvram_model" , " nvram_build_name" ];
@@ -343,8 +344,16 @@ function MerlinAU_TimeSelectFallbackAttach(id) {
343344 var input = document .getElementById (id);
344345 if (! input) return ;
345346
347+ // guard: don’t double-attach
348+ if (input .dataset .mauTimeApplied === ' 1' ) return ;
349+
350+ // Native is fine almost everywhere except Firefox.
346351 var isFirefox = / firefox/ i .test (navigator .userAgent );
347- if (! isFirefox && typeof input .showPicker === ' function' ) return ;
352+ var probe = document .createElement (' input' );
353+ probe .type = ' time' ;
354+ var hasNative = (probe .type === ' time' ); // real time input, not text
355+
356+ if (hasNative && ! isFirefox) return ; // keep native on Chrome/Edge/Safari/iOS/Android
348357
349358 function pad (n ){ return (n< 10 ? ' 0' : ' ' )+ n; }
350359 function parseHHMM (v ){
@@ -355,19 +364,29 @@ function MerlinAU_TimeSelectFallbackAttach(id) {
355364 }
356365
357366 var wrap = document .createElement (' span' );
367+ wrap .className = ' mau-time-fallback' ;
358368 var selH = document .createElement (' select' );
359369 var selM = document .createElement (' select' );
360370
361371 for (var h= 0 ; h< 24 ; h++ ) selH .add (new Option (pad (h), h));
362- for (var m= 0 ; m< 60 ; m++ ) selM .add (new Option (pad (m), m));
372+ var stepSec = parseInt (input .getAttribute (' step' ) || ' 60' , 10 );
373+ if (isNaN (stepSec) || stepSec < 60 ) stepSec = 60 ;
374+ var minInc = Math .max (1 , Math .floor (stepSec/ 60 ));
375+ for (var m= 0 ; m< 60 ; m+= minInc) selM .add (new Option (pad (m), m));
363376
364377 input .readOnly = true ;
378+ input .dataset .mauTimeApplied = ' 1' ;
379+
380+ function emit (el , type ){
381+ el .dispatchEvent (new Event (type, { bubbles: true }));
382+ }
365383
366384 function apply (){
367385 var hh = pad (+ selH .value ), mm = pad (+ selM .value );
368386 input .value = hh + ' :' + mm;
369387 if (window .ClearTimePickerInvalid ) window .ClearTimePickerInvalid (input);
370388 if (window .ValidateTimePicker ) window .ValidateTimePicker (input);
389+ emit (input,' input' ); emit (input,' change' );
371390 }
372391
373392 if (input .nextSibling ) input .parentNode .insertBefore (wrap, input .nextSibling );
@@ -376,10 +395,40 @@ function MerlinAU_TimeSelectFallbackAttach(id) {
376395 wrap .appendChild (document .createTextNode (' : ' ));
377396 wrap .appendChild (selM);
378397
398+ input .setAttribute (' aria-hidden' ,' true' );
399+ input .tabIndex = - 1 ;
400+ selH .setAttribute (' aria-label' ,' Hour' );
401+ selM .setAttribute (' aria-label' ,' Minute' );
402+
379403 var start = parseHHMM (input .value );
380404 selH .value = start .h ; selM .value = start .m ;
381405 selH .onchange = apply; selM .onchange = apply;
382- apply (); // normalize displayed HH:MM
406+ var flaggedInvalid = (input .getAttribute (' aria-invalid' ) === ' true' ) ||
407+ (typeof fwTimeInvalidFromConfig !== ' undefined' && fwTimeInvalidFromConfig) ||
408+ (input .classList && input .classList .contains (' Invalid' ));
409+
410+ if (! flaggedInvalid) {
411+ apply (); // normalize displayed HH:MM
412+ }
413+
414+ function syncDisabled (){
415+ var dis = !! input .disabled ;
416+ selH .disabled = dis; selM .disabled = dis;
417+ wrap .style .opacity = dis ? ' 0.5' : ' 1' ;
418+ }
419+ syncDisabled ();
420+
421+ var mo = new MutationObserver (syncDisabled);
422+ mo .observe (input, { attributes: true , attributeFilter: [' disabled' ] });
423+
424+ function syncFromInput (){
425+ var v = (input .value || ' ' ).trim ();
426+ if (! v) return ; // keep current selects if input was cleared for invalid state
427+ var t = parseHHMM (v);
428+ selH .value = t .h ; selM .value = t .m ;
429+ }
430+ input .addEventListener (' input' , syncFromInput);
431+ input .addEventListener (' change' , syncFromInput);
383432}
384433
385434/* *---------------------------------------**/
@@ -415,20 +464,21 @@ function MarkTimePickerInvalid(T, msg){
415464 fwTimeInvalidMsg = msg;
416465
417466 var $t = $ (T );
418- $t .addClass (' Invalid' );
419- // Remove ONLY namespaced handlers, then rebind
420- $t .off (' .fwtime' );
421- // Show tooltip ONLY on mouseover (no focus)
422- $t .on (' mouseover.fwtime' , function (){ return overlib (msg,0 ,0 ); });
423- // Hide tooltip as soon as user stops hovering or edits/leaves the field
424- $t .on (' mouseleave.fwtime input.fwtime keydown.fwtime keyup.fwtime blur.fwtime' , function (){
425- try { nd (); } catch (e){}
426- });
427- // if something already opened a tooltip, close it now
467+ $t .addClass (' Invalid' ).off (' .fwtime' )
468+ .on (' mouseover.fwtime' , function (){ return overlib (msg,0 ,0 ); })
469+ .on (' mouseleave.fwtime input.fwtime keydown.fwtime keyup.fwtime blur.fwtime' , function (){ try { nd (); } catch (e){} });
470+
428471 try { nd (); } catch (e){}
429472 T .setAttribute (' aria-invalid' ,' true' );
430473 T .value = ' ' ;
431- setTimeout (function (){ T .focus (); }, 0 ); // focus no longer triggers tooltip
474+
475+ // If our fallback is attached, focus the hour select instead of the hidden input
476+ if (T .dataset && T .dataset .mauTimeApplied === ' 1' ) {
477+ var wrap = T .nextElementSibling ; // our <span> wrapper
478+ var sel = wrap && wrap .querySelector (' select' );
479+ if (sel) { sel .focus (); return ; }
480+ }
481+ setTimeout (function (){ T .focus (); }, 0 );
432482}
433483
434484function ClearTimePickerInvalid (T ){
@@ -607,25 +657,22 @@ function SetScheduleDAYofWEEK (cronDAYofWEEK)
607657/* *------------------------------------------**/
608658// FW_New_Update_Cron_Job_Schedule //
609659function FWConvertCronScheduleToWebUISettings (rawCronSchedule ){
610- let fwRaw = rawCronSchedule .split (' ' );
660+ let s = (rawCronSchedule || ' ' ).replace (/ \| / g ,' ' ).trim ();
661+ let fwRaw = s .split (/ \s + / );
611662 let T = document .getElementById (' fwScheduleTIME' );
612- let fwSchedD1, fwSchedX, fwXD;
613-
614663 if (! T ) return ;
615664
616- // default UI state
617665 ClearTimePickerInvalid (T );
618666
619- if (rawCronSchedule === ' TBD ' || fwRaw .length < 5 ){
667+ if (fwRaw .length < 5 ) {
620668 ToggleDaysOfWeek (true , ' 1' );
621- fwSchedD1 = document .getElementById (' fwSchedBoxDAYS1' );
622- if (fwSchedD1){ fwSchedD1 .checked = true ; fwSchedD1 .disabled = false ; }
669+ document .getElementById (' fwSchedBoxDAYS1' )? .setAttribute (' checked' ,' ' );
623670 T .value = ' 00:00' ;
624671 T .disabled = false ;
625672 return ;
626673 }
627674
628- let rawM = fwRaw[ 0 ] , rawH = fwRaw[ 1 ] , rawDM = fwRaw[ 2 ] , rawMN = fwRaw[ 3 ] , rawDW = fwRaw[ 4 ] ;
675+ let [rawM , rawH, rawDM, rawMN, rawDW] = fwRaw;
629676
630677 // ---- Time handling: use fwScheduleTime messages only ----
631678 let timeCheck = ValidateHHMMUsingFwScheduleTime (String (rawH) + ' :' + String (rawM));
@@ -1679,7 +1726,7 @@ function InitializeFields()
16791726
16801727 let fwUpdateRawCronSchedule = custom_settings .FW_New_Update_Cron_Job_Schedule || ' TBD' ;
16811728 FWConvertCronScheduleToWebUISettings (fwUpdateRawCronSchedule);
1682-
1729+ MerlinAU_TimeSelectFallbackAttach ( ' fwScheduleTIME ' );
16831730 SetUpEmailNotificationFields ();
16841731
16851732 if (rogFWBuildType)
@@ -2150,7 +2197,6 @@ function initial()
21502197 UpdateScriptVersion ();
21512198 showhide (' Script_AutoUpdate_SchedText' ,true );
21522199 showhide (' FW_AutoUpdate_CheckSchedText' ,true );
2153- MerlinAU_TimeSelectFallbackAttach (' fwScheduleTIME' );
21542200
21552201 // Debugging iframe behavior //
21562202 var hiddenFrame = document .getElementById (' hidden_frame' );
0 commit comments