Skip to content

Commit 0b766a5

Browse files
merge with upstream
2 parents eb10d01 + 5801d57 commit 0b766a5

File tree

26 files changed

+462
-90
lines changed

26 files changed

+462
-90
lines changed
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
In our upcoming major release (v25.1) our Chat component will allow users to edit/delete messages once they’ve been sent.
1+
The Chat component allows users to edit and delete sent messages.
2+
3+
Use a [data source](/Documentation/ApiReference/UI_Components/dxChat/Configuration/#dataSource) if you want users to edit and delete messages. Implement custom insert, update, and delete operations. Once you configured these operations, enable [editing](/Documentation/ApiReference/UI_Components/dxChat/Configuration/editing/).
24
<!--split-->
3-
DevExtreme Chat is a client-side component that requires a backend solution. Message editing and delete operations will include relevant UI elements for end users and APIs to modify rendered messages. To edit and remove messages from a data source, you will need to use Push APIs.
5+
6+
The **editing** object includes [allowUpdating](/Documentation/ApiReference/UI_Components/dxChat/Configuration/editing/#allowUpdating) and [allowDeleting](/Documentation/ApiReference/UI_Components/dxChat/Configuration/editing/#allowDeleting) properties. These Boolean options are initially `false`. You can set them to `true` or use functions with custom logic. When options are `true`, users can edit/delete their own messages.
7+
8+
In this demo, try deleting or editing messages. First, check that the options are active in the panel next to the Chat component. Right-click or long-tap a message to access the "Edit" and "Delete" commands in the context menu. Edit a message in the input field and click "Send" to save changes.

e2e/testcafe-devextreme/tests/dataGrid/common/headerFilter/headerFilter.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,45 @@ test('[T1284200] Should handle dxList "selectAll" when has unselected items on t
316316
visible: true,
317317
},
318318
}));
319+
320+
test('Header filter search input loses focus on first key in datetime columns (T1284663)', async (t) => {
321+
const dataGrid = new DataGrid('#container');
322+
const headerCell = dataGrid.getHeaders()
323+
.getHeaderRow(0)
324+
.getHeaderCell(0);
325+
const filterIconElement = headerCell.getFilterIcon();
326+
const headerFilter = new HeaderFilter();
327+
328+
await t
329+
.click(filterIconElement)
330+
.pressKey('1');
331+
332+
await t.wait(1500);
333+
334+
const searchInput = headerFilter.getSearchInput();
335+
336+
await t
337+
.expect(searchInput.focused)
338+
.ok();
339+
}).before(async () => createWidget('dxDataGrid', {
340+
dataSource: [{
341+
id: 1,
342+
DeliveryDate: '2017/04/13 9:00',
343+
}],
344+
headerFilter: {
345+
visible: true,
346+
},
347+
keyExpr: 'id',
348+
columns: [{
349+
dataField: 'DeliveryDate',
350+
dataType: 'date',
351+
headerFilter: {
352+
dataSource: [
353+
{ text: 'March 11, 2025', value: '2025-03-11T00:00:00' },
354+
{ text: 'March 2, 2025', value: '2025-03-02T00:00:00' },
355+
{ text: 'February 3, 2025', value: '2025-02-03T00:00:00' },
356+
],
357+
search: { enabled: true },
358+
},
359+
}],
360+
}));

e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/columnReordering.visual.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,36 @@ safeSizeTest('The context menu should not have items for column reordering when
258258
});
259259
});
260260

261+
test('The cell focus should be correct after column reordering when previously the data cell was focused', async (t) => {
262+
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
263+
const dataGrid = new DataGrid(DATA_GRID_SELECTOR);
264+
const firstDataCell = dataGrid.getDataCell(0, 0);
265+
266+
await t
267+
.click(firstDataCell.element)
268+
.pressKey('shift+tab')
269+
.pressKey('ctrl+left');
270+
271+
await takeScreenshot(
272+
'cell_focus_after_column_reordering_when_data_cell_was_focused.png',
273+
dataGrid.element,
274+
);
275+
276+
await t
277+
.expect(compareResults.isValid())
278+
.ok(compareResults.errorMessages());
279+
}).before(async () => {
280+
await createWidget('dxDataGrid', {
281+
allowColumnReordering: true,
282+
dataSource: [{
283+
field1: 'test1',
284+
field2: 'test2',
285+
field3: 'test3',
286+
field4: 'test4',
287+
}],
288+
});
289+
});
290+
261291
// Fixed columns
262292
test('reorder fixed left column to right', async (t) => {
263293
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
Loading

e2e/testcafe-devextreme/tests/scheduler/common/a11y/editing.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import Scheduler from 'devextreme-testcafe-models/scheduler';
2+
import { createWidget } from '../../../../helpers/createWidget';
3+
import url from '../../../../helpers/getPageUrl';
4+
import { a11yCheck } from '../../../../helpers/accessibility/utils';
5+
6+
fixture.disablePageReloads`a11y - popup`
7+
.page(url(__dirname, '../../../container.html'));
8+
9+
const checkOptions = {
10+
rules: {
11+
'color-contrast': { enabled: false },
12+
},
13+
};
14+
15+
test('Scheduler edit appointment is accessible', async (t) => {
16+
const scheduler = new Scheduler('#container');
17+
18+
await t.doubleClick(scheduler.getAppointmentByIndex(0).element());
19+
await t.expect(scheduler.appointmentPopup.isVisible()).ok();
20+
21+
await a11yCheck(t, checkOptions, '#container');
22+
}).before(async () => {
23+
await createWidget('dxScheduler', {
24+
timeZone: 'America/Los_Angeles',
25+
dataSource: [{
26+
text: 'Install New Router in Dev Room',
27+
startDate: new Date('2021-03-29T21:30:00.000Z'),
28+
endDate: new Date('2021-03-29T22:30:00.000Z'),
29+
recurrenceRule: 'FREQ=DAILY',
30+
}],
31+
recurrenceEditMode: 'series',
32+
currentView: 'week',
33+
currentDate: new Date(2021, 2, 28),
34+
});
35+
});
36+
37+
test('Scheduler recurrence editor repeat end accessible', async (t) => {
38+
const scheduler = new Scheduler('#container');
39+
const getItem = (index: number) => scheduler
40+
.appointmentPopup
41+
.getEndRepeatRadioButton(index);
42+
const getAriaLabel = (index: number) => getItem(index)
43+
.getAttribute('aria-label');
44+
45+
await t.doubleClick(scheduler.getAppointmentByIndex(0).element());
46+
await t.expect(scheduler.appointmentPopup.isVisible()).ok();
47+
48+
await t
49+
.expect(getAriaLabel(1))
50+
.eql('On 22 May 2025')
51+
.expect(getAriaLabel(2))
52+
.eql('After')
53+
.typeText(scheduler.appointmentPopup.repeatUntilElement, '2026')
54+
.click(getItem(1)) // unfocus input
55+
.expect(getAriaLabel(1))
56+
.eql('On 22 May 2026')
57+
.expect(getAriaLabel(2))
58+
.eql('After');
59+
await t
60+
.click(getItem(0))
61+
.expect(getAriaLabel(1))
62+
.eql('On')
63+
.expect(getAriaLabel(2))
64+
.eql('After');
65+
await t
66+
.click(getItem(2))
67+
.expect(getAriaLabel(1))
68+
.eql('On')
69+
.expect(getAriaLabel(2))
70+
.eql('After 1 occurrence(s)')
71+
.typeText(scheduler.appointmentPopup.repeatCountElement, '3')
72+
.click(getItem(2)) // unfocus input
73+
.expect(getAriaLabel(1))
74+
.eql('On')
75+
.expect(getAriaLabel(2))
76+
.eql('After 13 occurrence(s)');
77+
}).before(async () => {
78+
await createWidget('dxScheduler', {
79+
timeZone: 'America/Los_Angeles',
80+
dataSource: [{
81+
text: 'Install New Router in Dev Room',
82+
startDate: new Date('2021-03-29T21:30:00.000Z'),
83+
endDate: new Date('2021-03-29T22:30:00.000Z'),
84+
recurrenceRule: 'FREQ=DAILY;UNTIL=20250522T215959Z',
85+
}],
86+
recurrenceEditMode: 'series',
87+
currentView: 'week',
88+
currentDate: new Date('2021-03-29T21:30:00.000Z'),
89+
});
90+
});

packages/devextreme-scss/scss/widgets/base/scrollable/_index.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
&.dx-scrollable-wrapper > .dx-scrollable-container,
2828
> div.dx-scrollable-wrapper > .dx-scrollable-container {
29-
-webkit-overflow-scrolling: touch;
3029
position: relative;
3130
height: 100%;
3231
}

packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import '@ts/ui/list/modules/m_search';
33
import '@ts/ui/list/modules/m_selection';
44

5+
import type { ChangedOptionInfo } from '@js/common/core/events';
56
import messageLocalization from '@js/common/core/localization/message';
67
import $ from '@js/core/renderer';
78
import type { DeferredObj } from '@js/core/utils/deferred';
@@ -15,6 +16,7 @@ import Popup from '@js/ui/popup/ui.popup';
1516
import TreeView from '@js/ui/tree_view';
1617
import Modules from '@ts/grids/grid_core/m_modules';
1718
import type { ModuleType } from '@ts/grids/grid_core/m_types';
19+
import type TextBox from '@ts/ui/text_box/m_text_box';
1820

1921
import gridCoreUtils from '../m_utils';
2022

@@ -329,24 +331,52 @@ export class HeaderFilterView extends Modules.View {
329331
},
330332
};
331333

332-
function onOptionChanged(e) {
333-
// T835492, T833015
334-
if (e.fullName === 'searchValue' && needShowSelectAllCheckbox && that.option('headerFilter.hideSelectAllOnSearch') !== false) {
335-
if (options.type === 'tree') {
336-
e.component.option('showCheckBoxesMode', e.value ? 'normal' : 'selectAll');
337-
} else {
338-
e.component.option('selectionMode', e.value ? 'multiple' : 'all');
339-
}
334+
const shouldChangeSelectAllCheckBoxVisibility = (): boolean => needShowSelectAllCheckbox
335+
&& that.option('headerFilter.hideSelectAllOnSearch') !== false;
336+
337+
const onTreeViewOptionChanged = (
338+
event: ChangedOptionInfo & {
339+
component: TreeView & { _searchEditor: TextBox };
340+
},
341+
): void => {
342+
switch (true) {
343+
case event.fullName === 'searchValue' && shouldChangeSelectAllCheckBoxVisibility():
344+
event.component.option('showCheckBoxesMode', event.value ? 'normal' : 'selectAll');
345+
break;
346+
// TODO TreeView: remove this WA after Navigation squad re-render fix
347+
// NOTE: WA for TreeView re-render after changing the "showCheckBoxesMode" option
348+
// After this option change the whole TreeView re-render and search input loose the focus
349+
case event.fullName === 'showCheckBoxesMode':
350+
// NOTE: the TreeView render is async
351+
// So we should focus the searchEditor only after render will be completed
352+
Promise.resolve()
353+
.then(() => {
354+
event.component._searchEditor.focus();
355+
})
356+
.catch(() => {});
357+
break;
358+
default:
359+
break;
340360
}
341-
}
361+
};
362+
363+
const onListOptionChanged = (
364+
event: ChangedOptionInfo & {
365+
component: dxList;
366+
},
367+
): void => {
368+
if (event.fullName === 'searchValue' && shouldChangeSelectAllCheckBoxVisibility()) {
369+
event.component.option('selectionMode', event.value ? 'multiple' : 'all');
370+
}
371+
};
342372

343373
if (options.type === 'tree') {
344374
that._listComponent = that._createComponent(
345375
$('<div>').appendTo($content),
346376
TreeView,
347377
extend(widgetOptions, {
348378
showCheckBoxesMode: needShowSelectAllCheckbox ? 'selectAll' : 'normal',
349-
onOptionChanged,
379+
onOptionChanged: onTreeViewOptionChanged,
350380
keyExpr: 'id',
351381
}),
352382
);
@@ -359,7 +389,7 @@ export class HeaderFilterView extends Modules.View {
359389
pageLoadMode: 'scrollBottom',
360390
showSelectionControls: true,
361391
selectionMode: needShowSelectAllCheckbox ? 'all' : 'multiple',
362-
onOptionChanged,
392+
onOptionChanged: onListOptionChanged,
363393
onSelectionChanged(event) {
364394
const { component: listComponent } = event;
365395
const items = listComponent.option('items');

packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,16 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo
232232
}
233233
}
234234

235-
protected focusOutHandler(): void {
235+
protected focusOutHandler(e): void {
236+
const { relatedTarget } = e;
237+
236238
this._toggleInertAttr(false);
239+
240+
if (relatedTarget && !this.isInsideFocusedView($(relatedTarget))) {
241+
this._isNeedFocus = false;
242+
this._isHiddenFocus = false;
243+
this._isNeedScroll = false;
244+
}
237245
}
238246

239247
protected subscribeToRowsViewFocusEvent(): void {
@@ -1459,10 +1467,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo
14591467
}
14601468
}
14611469

1462-
private _getFocusedViewByElement($element) {
1463-
const $view = $(this._focusedView?.element());
1464-
1465-
return $element?.closest($view).length !== 0;
1470+
private isInsideFocusedView($element: dxElementWrapper): boolean {
1471+
return $element.closest(this._focusedView?.element()).length !== 0;
14661472
}
14671473

14681474
private _focusView() {

packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export const keyboardNavigationScrollableA11yExtender = (Base: ModuleType<Keyboa
2828
super.focusinHandler(event);
2929
}
3030

31-
protected focusOutHandler(): void {
32-
super.focusOutHandler();
31+
protected focusOutHandler(e): void {
32+
super.focusOutHandler(e);
3333
this.makeScrollableFocusableIfNeed();
3434
}
3535

0 commit comments

Comments
 (0)