From d327be421cab87504c9f911eadccf45b334bae63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Sun, 8 Feb 2026 22:09:35 +0100 Subject: [PATCH] Datepicker: Support init inside a delegated focus handler in jQuery 4 jQuery 4.0+ follows native focus events order, meaning that `focusin` is fired after `focus`. As delegated `focus` is implemented in jQuery via `focusin`, `focus` handlers attached during a delegated `focus` handler will not fire until the second time the field receives focus. This is what the `_attachments` method does. To account for that, show the datepicker if input is already focused. `_showDatepicker` checks if the datepicker is already open, so it's not a problem that it fires again as a `focus` handler in jQuery <4. Note that the fact such an initialization worked inside of delegated focus handlers was a result of an implementation detail in jQuery. If a regular `focus` handler was used to initialize the datepicker, neither jQuery 4.0 nor 3.x would show the datepicker. This issue is now fixed as well. Fixes gh-2385 Closes gh-2390 --- tests/unit/datepicker/core.js | 38 ++++++++++++++++++++++++++++++++++- ui/widgets/datepicker.js | 23 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/unit/datepicker/core.js b/tests/unit/datepicker/core.js index edc16d005d..a8e394696f 100644 --- a/tests/unit/datepicker/core.js +++ b/tests/unit/datepicker/core.js @@ -29,7 +29,7 @@ QUnit.test( "initialization - Reinitialization after body had been emptied.", fu QUnit.test( "widget method - empty collection", function( assert ) { assert.expect( 1 ); $( "#nonExist" ).datepicker(); // Should create nothing - assert.ok( !$( "#ui-datepicker-div" ).length, "Non init on empty collection" ); + assert.strictEqual( $( "#ui-datepicker-div" ).length, 0, "Non init on empty collection" ); } ); QUnit.test( "widget method", function( assert ) { @@ -540,4 +540,40 @@ QUnit.test( "mouse", function( assert ) { "Mouse click inline - next" ); } ); +QUnit.test( "initialized on focus is immediately shown (gh-2385)", function( assert ) { + assert.expect( 2 ); + + var dp, dp2, inp, inp2, parent; + + try { + inp = $( "#inp" ); + parent = inp.parent(); + parent.on( "focus", "#inp:not(.hasDatepicker)", function() { + testHelper.init( "#inp" ); + dp = $( "#ui-datepicker-div" ); + } ); + inp.trigger( "focus" ); + assert.equal( dp.css( "display" ), "block", + "Datepicker - visible (delegated focus)" ); + } finally { + inp.datepicker( "destroy" ); + } + + try { + inp2 = $( "#inp2" ); + inp2.on( "focus", function() { + if ( $( this ).hasClass( "hasDatepicker" ) ) { + return; + } + testHelper.init( "#inp2" ); + dp2 = $( "#ui-datepicker-div" ); + } ); + inp2.trigger( "focus" ); + assert.equal( dp2.css( "display" ), "block", + "Datepicker - visible (regular focus)" ); + } finally { + inp2.datepicker( "destroy" ); + } +} ); + } ); diff --git a/ui/widgets/datepicker.js b/ui/widgets/datepicker.js index 029f255e87..da70d5e00c 100644 --- a/ui/widgets/datepicker.js +++ b/ui/widgets/datepicker.js @@ -235,6 +235,22 @@ $.extend( Datepicker.prototype, { if ( inst.settings.disabled ) { this._disableDatepicker( target ); } + + // Support: jQuery 4.0.0+ + // jQuery 4.0+ follows native focus events order, meaning that `focusin` + // is fired after `focus`. As delegated `focus` is implemented in jQuery + // via `focusin`, `focus` handlers attached during a delegated `focus` + // handler will not fire until the second time the field receives focus. + // This is what the `_attachments` method does. To account for that, show + // the datepicker if input is already focused. `_showDatepicker` checks + // if the datepicker is already open, so it's not a problem that it + // fires again as a `focus` handler in jQuery <4. + // + // Note that the fact such an initialization worked inside of delegated + // focus handlers was a result of an implementation detail in jQuery. If + // a regular `focus` handler was used to initialize the datepicker, neither + // jQuery 4.0 nor 3.x would show the datepicker without the call below. + this._showDatepickerIfFocused( input ); }, /* Make attachments based on settings. */ @@ -594,6 +610,7 @@ $.extend( Datepicker.prototype, { this._setDate( inst, date ); this._updateAlternate( inst ); this._updateDatepicker( inst ); + this._showDatepickerIfFocused( target ); } }, @@ -862,6 +879,12 @@ $.extend( Datepicker.prototype, { } }, + _showDatepickerIfFocused: function( input ) { + if ( input.length && input.is( ":focus" ) ) { + this._showDatepicker( input[ 0 ] ); + } + }, + /* Generate the date picker content. */ _updateDatepicker: function( inst ) { this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)