Skip to content

Commit 1373e46

Browse files
authored
fix(modal): do autofocus on content change
The autofocus feature does not work when the content of a modal or flyout is loaded/changed after the modal is shown (and contains an input after the change)
1 parent 338898a commit 1373e46

File tree

4 files changed

+123
-13
lines changed

4 files changed

+123
-13
lines changed

src/definitions/modules/flyout.js

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@
255255
resize: function () {
256256
module.setup.heights();
257257
},
258+
focus: function () {
259+
if (module.is.visible() && settings.autofocus && settings.dimPage) {
260+
requestAnimationFrame(module.set.autofocus);
261+
}
262+
},
258263
clickaway: function (event) {
259264
if (settings.closable) {
260265
var
@@ -357,6 +362,9 @@
357362
$closeIcon
358363
.on('keyup' + elementNamespace, module.event.closeKeyUp)
359364
;
365+
$window
366+
.on('focus' + elementNamespace, module.event.focus)
367+
;
360368
},
361369
clickaway: function () {
362370
module.verbose('Adding clickaway events to context', $context);
@@ -477,9 +485,42 @@
477485
observeChanges: function () {
478486
if ('MutationObserver' in window) {
479487
observer = new MutationObserver(function (mutations) {
480-
module.refreshInputs();
488+
var collectNodes = function (parent) {
489+
var nodes = [];
490+
for (var c = 0, cl = parent.length; c < cl; c++) {
491+
Array.prototype.push.apply(nodes, collectNodes(parent[c].childNodes));
492+
nodes.push(parent[c]);
493+
}
494+
495+
return nodes;
496+
},
497+
shouldRefreshInputs = false
498+
;
499+
mutations.every(function (mutation) {
500+
if (mutation.type === 'attributes') {
501+
if (mutation.attributeName === 'disabled' || $(mutation.target).find(':input').addBack(':input')) {
502+
shouldRefreshInputs = true;
503+
}
504+
} else {
505+
// mutationobserver only provides the parent nodes
506+
// so let's collect all childs as well to find nested inputs
507+
var $addedInputs = $(collectNodes(mutation.addedNodes)).filter('a[href], [tabindex], :input:enabled').filter(':visible'),
508+
$removedInputs = $(collectNodes(mutation.removedNodes)).filter('a[href], [tabindex], :input');
509+
if ($addedInputs.length > 0 || $removedInputs.length > 0) {
510+
shouldRefreshInputs = true;
511+
}
512+
}
513+
514+
return !shouldRefreshInputs;
515+
});
516+
517+
if (shouldRefreshInputs) {
518+
module.refreshInputs();
519+
}
481520
});
482521
observer.observe(element, {
522+
attributeFilter: ['class', 'disabled'],
523+
attributes: true,
483524
childList: true,
484525
subtree: true,
485526
});
@@ -508,15 +549,23 @@
508549
if (!settings.dimPage) {
509550
return;
510551
}
511-
$inputs = $module.find('[tabindex], :input').filter(':visible').filter(function () {
552+
$inputs = $module.find('[tabindex], :input:enabled').filter(':visible').filter(function () {
512553
return $(this).closest('.disabled').length === 0;
513554
});
555+
$module.removeAttr('tabindex');
556+
if ($inputs.length === 0) {
557+
$inputs = $module;
558+
$module.attr('tabindex', -1);
559+
}
514560
$inputs.first()
515561
.on('keydown' + elementNamespace, module.event.inputKeyDown.first)
516562
;
517563
$inputs.last()
518564
.on('keydown' + elementNamespace, module.event.inputKeyDown.last)
519565
;
566+
if (settings.autofocus && $inputs.filter(':focus').length === 0) {
567+
module.set.autofocus();
568+
}
520569
},
521570

522571
setup: {
@@ -611,9 +660,6 @@
611660
}
612661
module.save.focus();
613662
module.refreshInputs();
614-
if (settings.autofocus) {
615-
module.set.autofocus();
616-
}
617663
});
618664
settings.onChange.call(element);
619665
} else {
@@ -788,10 +834,18 @@
788834
autofocus: function () {
789835
var
790836
$autofocus = $inputs.filter('[autofocus]'),
837+
$rawInputs = $inputs.filter(':input'),
791838
$input = $autofocus.length > 0
792839
? $autofocus.first()
793-
: ($inputs.length > 1 ? $inputs.filter(':not(i.close)') : $inputs).first()
840+
: ($rawInputs.length > 0
841+
? $rawInputs
842+
: $inputs.filter(':not(i.close)')
843+
).first()
794844
;
845+
// check if only the close icon is remaining
846+
if ($input.length === 0 && $inputs.length > 0) {
847+
$input = $inputs.first();
848+
}
795849
if ($input.length > 0) {
796850
$input.trigger('focus');
797851
}

src/definitions/modules/flyout.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
overflow-y: hidden;
4040
z-index: @topLayer;
4141
background: #fff;
42+
outline: none;
4243
}
4344

4445
/* GPU Layers for Child Elements */

src/definitions/modules/modal.js

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,48 @@
251251
observeChanges: function () {
252252
if ('MutationObserver' in window) {
253253
observer = new MutationObserver(function (mutations) {
254-
if (settings.observeChanges) {
254+
var collectNodes = function (parent) {
255+
var nodes = [];
256+
for (var c = 0, cl = parent.length; c < cl; c++) {
257+
Array.prototype.push.apply(nodes, collectNodes(parent[c].childNodes));
258+
nodes.push(parent[c]);
259+
}
260+
261+
return nodes;
262+
},
263+
shouldRefresh = false,
264+
shouldRefreshInputs = false
265+
;
266+
mutations.every(function (mutation) {
267+
if (mutation.type === 'attributes') {
268+
if (mutation.attributeName === 'disabled' || $(mutation.target).find(':input').addBack(':input')) {
269+
shouldRefreshInputs = true;
270+
}
271+
} else {
272+
shouldRefresh = true;
273+
// mutationobserver only provides the parent nodes
274+
// so let's collect all childs as well to find nested inputs
275+
var $addedInputs = $(collectNodes(mutation.addedNodes)).filter('a[href], [tabindex], :input:enabled').filter(':visible'),
276+
$removedInputs = $(collectNodes(mutation.removedNodes)).filter('a[href], [tabindex], :input');
277+
if ($addedInputs.length > 0 || $removedInputs.length > 0) {
278+
shouldRefreshInputs = true;
279+
}
280+
}
281+
282+
return !shouldRefreshInputs;
283+
});
284+
285+
if (shouldRefresh && settings.observeChanges) {
255286
module.debug('DOM tree modified, refreshing');
256287
module.refresh();
257288
}
258-
module.refreshInputs();
289+
if (shouldRefreshInputs) {
290+
module.refreshInputs();
291+
}
259292
});
260293
observer.observe(element, {
294+
attributeFilter: ['class', 'disabled'],
295+
attributes: true,
261296
childList: true,
262297
subtree: true,
263298
});
@@ -286,15 +321,23 @@
286321
.off('keydown' + elementEventNamespace)
287322
;
288323
}
289-
$inputs = $module.find('[tabindex], :input').filter(':visible').filter(function () {
324+
$inputs = $module.find('a[href], [tabindex], :input:enabled').filter(':visible').filter(function () {
290325
return $(this).closest('.disabled').length === 0;
291326
});
327+
$module.removeAttr('tabindex');
328+
if ($inputs.length === 0) {
329+
$inputs = $module;
330+
$module.attr('tabindex', -1);
331+
}
292332
$inputs.first()
293333
.on('keydown' + elementEventNamespace, module.event.inputKeyDown.first)
294334
;
295335
$inputs.last()
296336
.on('keydown' + elementEventNamespace, module.event.inputKeyDown.last)
297337
;
338+
if (settings.autofocus && $inputs.filter(':focus').length === 0) {
339+
module.set.autofocus();
340+
}
298341
},
299342

300343
attachEvents: function (selector, event) {
@@ -328,6 +371,7 @@
328371
;
329372
$window
330373
.on('resize' + elementEventNamespace, module.event.resize)
374+
.on('focus' + elementEventNamespace, module.event.focus)
331375
;
332376
},
333377
scrollLock: function () {
@@ -485,6 +529,11 @@
485529
requestAnimationFrame(module.refresh);
486530
}
487531
},
532+
focus: function () {
533+
if ($dimmable.dimmer('is active') && module.is.active() && settings.autofocus) {
534+
requestAnimationFrame(module.set.autofocus);
535+
}
536+
},
488537
},
489538

490539
toggle: function () {
@@ -574,9 +623,6 @@
574623
module.save.focus();
575624
module.set.active();
576625
module.refreshInputs();
577-
if (settings.autofocus) {
578-
module.set.autofocus();
579-
}
580626
callback();
581627
},
582628
})
@@ -990,10 +1036,18 @@
9901036
autofocus: function () {
9911037
var
9921038
$autofocus = $inputs.filter('[autofocus]'),
1039+
$rawInputs = $inputs.filter(':input'),
9931040
$input = $autofocus.length > 0
9941041
? $autofocus.first()
995-
: ($inputs.length > 1 ? $inputs.filter(':not(i.close)') : $inputs).first()
1042+
: ($rawInputs.length > 0
1043+
? $rawInputs
1044+
: $inputs.filter(':not(i.close)')
1045+
).first()
9961046
;
1047+
// check if only the close icon is remaining
1048+
if ($input.length === 0 && $inputs.length > 0) {
1049+
$input = $inputs.first();
1050+
}
9971051
if ($input.length > 0) {
9981052
$input.trigger('focus');
9991053
}

src/definitions/modules/modal.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
border-radius: @borderRadius;
3535
user-select: text;
3636
will-change: top, left, margin, transform, opacity;
37+
outline: none;
3738
}
3839

3940
.ui.modal > :first-child:not(.close):not(.dimmer),

0 commit comments

Comments
 (0)