Skip to content

Commit 5072a26

Browse files
authored
Merge pull request #17053 from craftcms/feature/pt-2564-focusable-elements-in-inactive-rows-remain-in-the-focus
Fixes a bug where focusable elements in inactive elements remain in the focus order
2 parents 0f0d6e7 + 4e9ede1 commit 5072a26

File tree

4 files changed

+74
-11
lines changed

4 files changed

+74
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Fixed a bug where custom sources that were hidden for certain user groups weren’t available within element selector modals either. ([#17703](https://github.com/craftcms/cms/issues/17703))
1515
- Fixed a bug where element queries weren’t returning any results if they had a param that resolved to multiple generated fields. ([#17709](https://github.com/craftcms/cms/issues/17709))
1616
- Fixed a bug where Link fields weren’t preserving spaces within Phone and SMS link labels. ([#17707](https://github.com/craftcms/cms/issues/17707))
17+
- Fixed a bug where disabled rows within element selector modals could contain nested focusable elements. ([#17053](https://github.com/craftcms/cms/pull/17053))
1718

1819
## 5.8.12 - 2025-07-29
1920

src/web/assets/cp/dist/cp.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/dist/cp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/src/js/BaseElementIndexView.js

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,26 @@ Craft.BaseElementIndexView = Garnish.Base.extend(
7070
}
7171
);
7272

73+
this.updateInitialViewForSelectedElements($elements);
74+
7375
this._handleEnableElements = (ev) => {
7476
this.elementSelect.addItems(
7577
this.filterSelectableElements($(ev.elements))
7678
);
79+
80+
for (let i = 0; i < $(ev.elements).length; i++) {
81+
const $element = $(ev.elements).eq(i);
82+
this.enableFocusableElements($element);
83+
}
7784
};
7885

7986
this._handleDisableElements = (ev) => {
8087
this.elementSelect.removeItems(ev.elements);
88+
89+
for (let i = 0; i < $(ev.elements).length; i++) {
90+
const $element = $(ev.elements).eq(i);
91+
this.disableFocusableElements($element);
92+
}
8193
};
8294

8395
this.elementIndex.on('enableElements', this._handleEnableElements);
@@ -146,30 +158,80 @@ Craft.BaseElementIndexView = Garnish.Base.extend(
146158
}
147159
},
148160

149-
filterSelectableElements: function ($elements) {
150-
const selectable = [];
151-
161+
/**
162+
* Updates the initial view for selected elements
163+
* @param $elements
164+
*/
165+
updateInitialViewForSelectedElements: function ($elements) {
152166
for (let i = 0; i < $elements.length; i++) {
153167
const $element = $elements.eq(i);
154168
if ($element.hasClass('disabled')) {
155-
// remove checkbox from tab order and mark as checked
156-
$element.find('.checkbox').attr({
157-
tabindex: '-1',
169+
// mark as checked
170+
this.getElementCheckbox($element).attr({
158171
'aria-checked': 'true',
159172
});
173+
174+
// remove other focusable elements from the tab order
175+
this.disableFocusableElements($element);
160176
continue;
161177
}
178+
179+
if (!this.canSelectElement($element)) {
180+
$element.find('.checkbox').remove();
181+
}
182+
}
183+
},
184+
185+
/**
186+
* Returns selectable elements
187+
* @param $elements Elements to filter
188+
* @returns {jQuery} A jQuery object containing the selectable elements
189+
*/
190+
filterSelectableElements: function ($elements) {
191+
const selectable = [];
192+
193+
for (let i = 0; i < $elements.length; i++) {
194+
const $element = $elements.eq(i);
195+
162196
if (this.canSelectElement($element)) {
163197
selectable.push($element[0]);
164-
} else {
165-
// make sure it doesn't have a checkbox
166-
$element.find('.checkbox').remove();
167198
}
168199
}
169200

170201
return $(selectable);
171202
},
172203

204+
/**
205+
* Removes all focusable elements inside the given container from the focus order
206+
* @param $container
207+
*/
208+
disableFocusableElements: function ($container) {
209+
const disabledAttributes = {
210+
tabindex: '-1',
211+
'data-focusable': true,
212+
};
213+
// Disable all focusable elements inside the disabled elements
214+
const $focusable = Garnish.getKeyboardFocusableElements($container);
215+
216+
if ($focusable.length) {
217+
$focusable.attr(disabledAttributes);
218+
}
219+
this.getElementCheckbox($container).attr(disabledAttributes);
220+
},
221+
222+
/**
223+
* Moves all focusable elements inside the given container into the default focus order
224+
* @param $container
225+
*/
226+
enableFocusableElements: function ($container) {
227+
const $focusableElements = $container.find('[data-focusable]');
228+
229+
if (!$focusableElements.length) return;
230+
231+
$focusableElements.removeAttr('tabindex data-focusable');
232+
this.getElementCheckbox($container).attr('tabindex', '0');
233+
},
234+
173235
canSelectElement: function ($element) {
174236
if (this.settings.canSelectElement) {
175237
return this.settings.canSelectElement($element);

0 commit comments

Comments
 (0)