Skip to content

Commit 9b4e84a

Browse files
authored
List: prevent radio button from remaining active after double-click if another radio button is selected (T1294715) (#30141)
1 parent b2c7e4e commit 9b4e84a

File tree

4 files changed

+50
-19
lines changed

4 files changed

+50
-19
lines changed

packages/devextreme/js/__internal/ui/list/m_list.base.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { fx } from '@js/common/core/animation';
22
import { name as clickEventName } from '@js/common/core/events/click';
33
import eventsEngine from '@js/common/core/events/core/events_engine';
44
import { end as swipeEventEnd } from '@js/common/core/events/swipe';
5-
import { addNamespace } from '@js/common/core/events/utils/index';
5+
import { addNamespace } from '@js/common/core/events/utils';
66
import messageLocalization from '@js/common/core/localization/message';
77
import devices from '@js/core/devices';
88
import { getPublicElement } from '@js/core/element';
@@ -20,6 +20,7 @@ import { each } from '@js/core/utils/iterator';
2020
import { getHeight, getOuterHeight, setHeight } from '@js/core/utils/size';
2121
import { isDefined, isPlainObject } from '@js/core/utils/type';
2222
import { hasWindow } from '@js/core/utils/window';
23+
import type { DxEvent } from '@js/events';
2324
import Button from '@js/ui/button';
2425
import type { Item, Properties } from '@js/ui/list';
2526
import ScrollView from '@js/ui/scroll_view';
@@ -398,11 +399,12 @@ export class ListBase extends CollectionWidget<ListBaseProperties> {
398399
return this._itemElementsCache;
399400
}
400401

401-
_itemSelectHandler(e) {
402+
_itemSelectHandler(e: DxEvent) {
402403
const { selectionMode } = this.option();
403404

404405
const isSingleSelectedItemClicked = selectionMode === 'single'
405406
&& this.isItemSelected(e.currentTarget);
407+
406408
if (isSingleSelectedItemClicked) {
407409
return;
408410
}
@@ -417,7 +419,7 @@ export class ListBase extends CollectionWidget<ListBaseProperties> {
417419
return super._itemSelectHandler(e, isSelectionControlClicked);
418420
}
419421

420-
_allowDynamicItemsAppend() {
422+
_allowDynamicItemsAppend(): boolean {
421423
return true;
422424
}
423425

packages/devextreme/js/__internal/ui/list/m_list.edit.decorator.selection.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { name as clickEventName } from '@js/common/core/events/click';
22
import eventsEngine from '@js/common/core/events/core/events_engine';
3-
import { addNamespace } from '@js/common/core/events/utils/index';
3+
import { addNamespace } from '@js/common/core/events/utils';
44
import messageLocalization from '@js/common/core/localization/message';
55
import type { dxElementWrapper } from '@js/core/renderer';
66
import $ from '@js/core/renderer';
@@ -49,7 +49,9 @@ class EditDecoratorSelection extends EditDecorator {
4949
const selectionMode = this._list.option('selectionMode');
5050

5151
this._singleStrategy = selectionMode === 'single';
52-
this._containerClass = this._singleStrategy ? SELECT_RADIO_BUTTON_CONTAINER_CLASS : SELECT_CHECKBOX_CONTAINER_CLASS;
52+
this._containerClass = this._singleStrategy
53+
? SELECT_RADIO_BUTTON_CONTAINER_CLASS
54+
: SELECT_CHECKBOX_CONTAINER_CLASS;
5355
this._controlClass = this._singleStrategy ? SELECT_RADIO_BUTTON_CLASS : SELECT_CHECKBOX_CLASS;
5456

5557
this._controlWidget = this._singleStrategy ? RadioButton : CheckBox;

packages/devextreme/js/__internal/ui/radio_group/m_radio_button.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { name as clickEventName } from '@js/common/core/events/click';
22
import eventsEngine from '@js/common/core/events/core/events_engine';
3-
import { addNamespace } from '@js/common/core/events/utils/index';
3+
import { addNamespace } from '@js/common/core/events/utils';
44
import registerComponent from '@js/core/component_registrator';
55
import devices from '@js/core/devices';
66
import type { DefaultOptionsRule } from '@js/core/options/utils';
77
import type { dxElementWrapper } from '@js/core/renderer';
88
import $ from '@js/core/renderer';
9+
import type { DxEvent } from '@js/events';
910
import type { OptionChanged } from '@ts/core/widget/types';
1011
import type { EditorProperties } from '@ts/ui/editor/editor';
1112
import Editor from '@ts/ui/editor/editor';
@@ -16,9 +17,7 @@ const RADIO_BUTTON_ICON_DOT_CLASS = 'dx-radiobutton-icon-dot';
1617
const RADIO_BUTTON_CHECKED_CLASS = 'dx-radiobutton-checked';
1718
const RADIO_BUTTON_ICON_CHECKED_CLASS = 'dx-radiobutton-icon-checked';
1819

19-
export interface RadioButtonProperties extends EditorProperties {
20-
21-
}
20+
export interface RadioButtonProperties extends EditorProperties {}
2221

2322
class RadioButton extends Editor {
2423
_$icon?: dxElementWrapper;
@@ -98,25 +97,28 @@ class RadioButton extends Editor {
9897
// @ts-expect-error ts-error
9998
const eventName = addNamespace(clickEventName, this.NAME);
10099

101-
this._clickAction = this._createAction((args) => {
100+
this._clickAction = this._createAction((args): void => {
102101
this._clickHandler(args.event);
103102
});
104103

105104
eventsEngine.off(this.$element(), eventName);
106-
eventsEngine.on(this.$element(), eventName, (e) => {
105+
eventsEngine.on(this.$element(), eventName, (e: DxEvent): void => {
107106
this._clickAction?.({ event: e });
108107
});
109108
}
110109

111110
_clickHandler(e): void {
112111
this._saveValueChangeEvent(e);
113112
this.option('value', true);
113+
this._saveValueChangeEvent(undefined);
114114
}
115115

116116
_optionChanged(args: OptionChanged<RadioButtonProperties>): void {
117-
switch (args.name) {
117+
const { name, value } = args;
118+
119+
switch (name) {
118120
case 'value':
119-
this._renderCheckedState(args.value);
121+
this._renderCheckedState(value);
120122
super._optionChanged(args);
121123
break;
122124
default:

packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/editingUITests.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ QUnit.module('switchable button delete decorator', {
377377
const $items = $list.find(`.${LIST_ITEM_CLASS}`);
378378
const $item = $items.eq(0);
379379

380-
assert.strictEqual($item.children(`.${SWITCHABLE_DELETE_BUTTON_CONTAINER_CLASS}`).length, 0, 'delete button won\'t rendered');
380+
assert.strictEqual($item.children(`.${SWITCHABLE_DELETE_BUTTON_CONTAINER_CLASS}`).length, 0, 'delete button was not rendered');
381381
});
382382

383383
QUnit.test('button should be added only when item is ready to delete', function(assert) {
@@ -1474,7 +1474,7 @@ QUnit.module('context menu decorator', {
14741474
list.option('allowItemDeleting', false);
14751475

14761476
const $menu = list.$element().find(`.${CONTEXTMENU_CLASS}`);
1477-
assert.ok(!$menu.length, 'overlay won\'t created');
1477+
assert.ok(!$menu.length, 'overlay was not created');
14781478
});
14791479

14801480
QUnit.test('item hold should not open overlay if widget is disabled', function(assert) {
@@ -2051,7 +2051,7 @@ QUnit.module('selectAll for all pages', () => {
20512051
assert.strictEqual($list.find('.dx-list-item-selected').length, 1, 'selected items should have selected class');
20522052
});
20532053

2054-
QUnit.test('selectAll checkbox should change it\'s state to undefined when one item was deselected', function(assert) {
2054+
QUnit.test('selectAll checkbox should change its state to undefined when one item was deselected', function(assert) {
20552055
const ds = new DataSource({
20562056
store: [1, 2, 3, 4, 5, 6],
20572057
pageSize: 2,
@@ -2595,6 +2595,31 @@ QUnit.module('item select decorator with single selection mode', () => {
25952595
assert.strictEqual(radioButton.option('value'), true, 'item selected');
25962596
});
25972597

2598+
QUnit.test('only one item should remain selected after double-clicking on its radio control and then clicking on another item control (T1294715)', function(assert) {
2599+
const $list = $('#list').dxList({
2600+
items: [
2601+
{ id: 1, text: 'item 1' },
2602+
{ id: 2, text: 'item 2' },
2603+
],
2604+
showSelectionControls: true,
2605+
selectionMode: 'single'
2606+
});
2607+
2608+
const $radioButtons = $list.find(`.${SELECT_RADIO_BUTTON_CLASS}`);
2609+
const $firstRadioButton = $radioButtons.eq(0);
2610+
const $secondRadioButton = $radioButtons.eq(1);
2611+
2612+
$firstRadioButton.trigger('dxclick');
2613+
$firstRadioButton.trigger('dxclick');
2614+
$secondRadioButton.trigger('dxclick');
2615+
2616+
const firstRadioButtonInstance = $firstRadioButton.dxRadioButton('instance');
2617+
const secondRadioButtonInstance = $secondRadioButton.dxRadioButton('instance');
2618+
2619+
assert.strictEqual(firstRadioButtonInstance.option('value'), false, 'first item is not selected');
2620+
assert.strictEqual(secondRadioButtonInstance.option('value'), true, 'second item is selected');
2621+
});
2622+
25982623
QUnit.test('keyboard navigation should work with without selectAll checkbox', function(assert) {
25992624
const $list = $('#templated-list').dxList({
26002625
focusStateEnabled: true,
@@ -2813,14 +2838,14 @@ QUnit.module('reordering decorator', {
28132838
assert.ok(!$ghostItem.hasClass(REORDERING_ITEM_CLASS), 'reordering class is not present');
28142839

28152840
assert.equal($ghostItem.css('direction'), 'rtl', 'direction is rtl');
2816-
assert.ok($ghostItem.parent().hasClass('dx-rtl'), 'ghost\'s parent has dx-rtl class');
2841+
assert.ok($ghostItem.parent().hasClass('dx-rtl'), 'ghost parent has dx-rtl class');
28172842

28182843
pointer.dragEnd();
28192844
$ghostItem = $list.find(`.${REORDERING_ITEM_GHOST_CLASS}`);
28202845
assert.strictEqual($items.length, 1, 'duplicate item was removed');
28212846
});
28222847

2823-
QUnit.test('cached items doesn\'t contains a ghost item after reordering', function(assert) {
2848+
QUnit.test('cached items does not contains a ghost item after reordering', function(assert) {
28242849
const $list = $('#list').dxList({
28252850
items: ['0', '1', '2'],
28262851
itemDragging: { allowReordering: true }
@@ -2836,7 +2861,7 @@ QUnit.module('reordering decorator', {
28362861
const cachedItems = list._itemElements();
28372862

28382863
assert.strictEqual(cachedItems.length, 3, 'Cached items contains 3 items');
2839-
assert.notOk(cachedItems.hasClass(REORDERING_ITEM_GHOST_CLASS), 'Cached items isn\'t contain a ghost item');
2864+
assert.notOk(cachedItems.hasClass(REORDERING_ITEM_GHOST_CLASS), 'Cached items does not contain a ghost item');
28402865
});
28412866

28422867
QUnit.test('ghost item should be moved by drag', function(assert) {

0 commit comments

Comments
 (0)