Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3dbcfb4
Get rid of SearchBoxMixin
pharret31 Jul 17, 2025
e024820
Add getAriaTarget to treeviewsearch
pharret31 Jul 17, 2025
d4bd317
Add refresh
pharret31 Jul 18, 2025
e6496f9
Update cleaning in treeviewsearch
pharret31 Jul 18, 2025
1cad0c6
Fix
pharret31 Jul 18, 2025
4524a98
Revert
pharret31 Jul 18, 2025
93c5991
code review
pharret31 Jul 18, 2025
ac833f6
Revert
pharret31 Jul 20, 2025
db4e7ca
Refactoring
pharret31 Jul 21, 2025
936e212
Fix testcafe test
pharret31 Jul 21, 2025
231a5c7
Revert refactoring
pharret31 Jul 21, 2025
adb641d
Revert refactoring
pharret31 Jul 21, 2025
d900e17
Revert refactoring
pharret31 Jul 21, 2025
cc10385
Add dependency
pharret31 Jul 21, 2025
5cace7a
Merge branch '25_2' into 25_2_899-refactoring-data-converter-mixin
pharret31 Jul 22, 2025
44c33f8
Merge branch '25_2' into 25_2_899-refactoring-data-converter-mixin
pharret31 Jul 22, 2025
6f3d06b
Merge branch '25_2' into 25_2_899-refactoring-data-converter-mixin
pharret31 Jul 23, 2025
5384c80
Merge branch '25_2' into 25_2_899-refactoring-data-converter-mixin
pharret31 Jul 23, 2025
30a22ce
Refactor search box integration and update editor class handling
pharret31 Jul 24, 2025
5b1aca7
Refactor data converter mixin to improve type definitions and enhance…
pharret31 Jul 24, 2025
1bcab1e
Update type annotations for _createComponent method in tree_view and …
pharret31 Jul 24, 2025
e0fec8b
Refactor search box integration and improve type definitions in relat…
pharret31 Jul 25, 2025
6330f5d
Refactor list and dropdown components to improve type definitions and…
pharret31 Jul 25, 2025
816a813
Refactor TreeView and List components to improve type definitions for…
pharret31 Jul 25, 2025
c1fc379
Refactor stubComponent to improve type definitions and update referen…
pharret31 Jul 25, 2025
b9b3140
Refactor EditorClass initialization in search box mixin to simplify s…
pharret31 Jul 25, 2025
e1a6401
remove excess private field from search_box
EugeniyKiyashko Jul 28, 2025
7c44a11
Revert header core tree view option handling
pharret31 Jul 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/devextreme-themebuilder/tests/data/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export const dependencies: FlatStylesDependencies = {
actionsheet: ['validation', 'button', 'popup', 'popover'],
textbox: ['validation', 'button', 'loadindicator'],
scrollview: ['loadindicator', 'loadpanel'],
list: ['validation', 'button', 'loadindicator', 'loadpanel', 'scrollview'],
list: ['validation', 'button', 'loadindicator', 'loadpanel', 'scrollview', 'textbox'],
autocomplete: ['validation', 'button', 'loadindicator', 'textbox', 'popup', 'loadpanel', 'scrollview', 'list'],
box: [],
buttongroup: ['validation', 'button'],
dropdownbutton: ['validation', 'button', 'buttongroup', 'popup', 'loadindicator', 'loadpanel', 'scrollview', 'list'],
dropdownbutton: ['validation', 'button', 'buttongroup', 'popup', 'loadindicator', 'loadpanel', 'scrollview', 'list', 'textbox'],
Comment thread
pharret31 marked this conversation as resolved.
Outdated
calendar: ['validation', 'button'],
cardview: ['box', 'button', 'calendar', 'checkbox', 'contextmenu', 'datebox', 'filterbuilder', 'form', 'list', 'loadindicator', 'loadpanel', 'multiview', 'numberbox', 'popup', 'responsivebox', 'scrollview', 'selectbox', 'sortable', 'tabpanel', 'tabs', 'textbox', 'toast', 'toolbar', 'treeview', 'validation'],
chat: ['button', 'loadindicator', 'loadpanel', 'popup', 'scrollview', 'textbox', 'toolbar', 'validation'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable max-classes-per-file */
import '@ts/ui/list/modules/m_search';
import '@ts/ui/list/modules/m_selection';

import type { ChangedOptionInfo } from '@js/common/core/events';
Expand All @@ -16,7 +15,7 @@ import Popup from '@js/ui/popup/ui.popup';
import TreeView from '@js/ui/tree_view';
import Modules from '@ts/grids/grid_core/m_modules';
import type { ModuleType } from '@ts/grids/grid_core/m_types';
import type TextBox from '@ts/ui/text_box/m_text_box';
import type SearchBoxController from '@ts/ui/collection/m_search_box_mixin';

import gridCoreUtils from '../m_utils';

Expand Down Expand Up @@ -336,7 +335,7 @@ export class HeaderFilterView extends Modules.View {

const onTreeViewOptionChanged = (
event: ChangedOptionInfo & {
component: TreeView & { _searchEditor: TextBox };
component: TreeView & { _searchController: SearchBoxController };
Comment thread
pharret31 marked this conversation as resolved.
Outdated
},
): void => {
switch (true) {
Comment thread
pharret31 marked this conversation as resolved.
Expand All @@ -351,7 +350,7 @@ export class HeaderFilterView extends Modules.View {
// So we should focus the searchEditor only after render will be completed
Promise.resolve()
.then(() => {
event.component._searchEditor.focus();
event.component._searchController.focus();
Comment thread
pharret31 marked this conversation as resolved.
Outdated
})
.catch(() => {});
break;
Expand Down
274 changes: 122 additions & 152 deletions packages/devextreme/js/__internal/ui/collection/m_search_box_mixin.ts
Original file line number Diff line number Diff line change
@@ -1,178 +1,148 @@
import type { SearchMode } from '@js/common';
import messageLocalization from '@js/common/core/localization/message';
import type { dxElementWrapper } from '@js/core/renderer';
import $ from '@js/core/renderer';
import type { DeferredObj } from '@js/core/utils/deferred';
import { Deferred } from '@js/core/utils/deferred';
import { extend } from '@js/core/utils/extend';
import { stubComponent } from '@js/core/utils/stubs';
import errors from '@js/ui/widget/ui.errors';

let EditorClass = stubComponent('TextBox');

export default {
_getDefaultOptions() {
return extend(this.callBase(), {
searchMode: '',
searchExpr: null,
searchValue: '',
searchEnabled: false,
searchEditorOptions: {},
});
},
import type { ValueChangedEvent } from '@js/ui/text_box';
import TextBox from '@js/ui/text_box';
import type { SearchBoxMixinOptions } from '@js/ui/widget/ui.search_box_mixin';

_initMarkup(): void {
this._renderSearch();
this.callBase();
},
export const getOperationBySearchMode = (searchMode?: SearchMode): string | undefined => (searchMode === 'equals' ? '=' : searchMode);

_renderSearch(): void {
const $element = this.$element();
const searchEnabled = this.option('searchEnabled');
const searchBoxClassName = this._addWidgetPrefix('search');
const rootElementClassName = this._addWidgetPrefix('with-search');
export type SearchBoxControllerOptions = SearchBoxMixinOptions & {
tabIndex?: number;
onValueChanged?: (value: string) => void;
};

if (!searchEnabled) {
$element.removeClass(rootElementClassName);
this._removeSearchBox();
return;
}
interface SearchBoxControllerProps {
createEditor: (
$element: dxElementWrapper,
component: typeof TextBox,
options: Record<string, unknown>,
) => TextBox;
widgetPrefix: string;
editorWidget?: typeof TextBox;
}

const editorOptions = this._getSearchEditorOptions();
class SearchBoxController {
_createEditor: SearchBoxControllerProps['createEditor'];

if (this._searchEditor) {
this._searchEditor.option(editorOptions);
} else {
$element.addClass(rootElementClassName);
this._$searchEditorElement = $('<div>').addClass(searchBoxClassName).prependTo($element);
this._searchEditor = this._createComponent(this._$searchEditorElement, EditorClass, editorOptions);
}
},
_widgetPrefix: string;

_removeSearchBox(): void {
this._$searchEditorElement && this._$searchEditorElement.remove();
delete this._$searchEditorElement;
delete this._searchEditor;
},
_editorWidget: typeof TextBox = TextBox;

_getSearchEditorOptions() {
const that = this;
const userEditorOptions = that.option('searchEditorOptions');
const searchText = messageLocalization.format('Search');
_$element!: dxElementWrapper | null;

return extend({
mode: 'search',
placeholder: searchText,
tabIndex: that.option('tabIndex'),
value: that.option('searchValue'),
valueChangeEvent: 'input',
inputAttr: {
'aria-label': searchText,
},
onValueChanged(e) {
const searchTimeout = that.option('searchTimeout');
that._valueChangeDeferred = Deferred();
clearTimeout(that._valueChangeTimeout);

that._valueChangeDeferred.done(function () {
this.option('searchValue', e.value);
}.bind(that));

if (e.event && e.event.type === 'input' && searchTimeout) {
that._valueChangeTimeout = setTimeout(() => {
that._valueChangeDeferred.resolve();
}, searchTimeout);
} else {
that._valueChangeDeferred.resolve();
}
},
}, userEditorOptions);
},
_editor!: TextBox | null;

_getAriaTarget() {
if (this.option('searchEnabled')) {
return this._itemContainer(true);
}
return this.callBase();
},
_valueChangeDeferred!: DeferredObj<unknown>;

_focusTarget() {
if (this.option('searchEnabled')) {
return this._itemContainer(true);
}
// eslint-disable-next-line no-restricted-globals
_valueChangeTimeout!: ReturnType<typeof setTimeout>;

return this.callBase();
},
_onSearchBoxValueChanged?: (value: string) => void;

_updateFocusState(e, isFocused): void {
if (this.option('searchEnabled')) {
this._toggleFocusClass(isFocused, this.$element());
}
this.callBase(e, isFocused);
},

getOperationBySearchMode(searchMode) {
return searchMode === 'equals' ? '=' : searchMode;
},

_optionChanged(args) {
switch (args.name) {
case 'searchEnabled':
case 'searchEditorOptions':
this._invalidate();
break;
case 'searchExpr':
case 'searchMode':
case 'searchValue':
if (!this._dataSource) {
errors.log('W1009');
return;
}
if (args.name === 'searchMode') {
this._dataSource.searchOperation(this.getOperationBySearchMode(args.value));
} else {
this._dataSource[args.name](args.value);
}
this._dataSource.load();
break;
case 'searchTimeout':
break;
default:
this.callBase(args);
}
},
constructor({
createEditor,
widgetPrefix,
editorWidget = TextBox,
}: SearchBoxControllerProps) {
this._createEditor = createEditor;
this._widgetPrefix = widgetPrefix;
this._editorWidget = editorWidget;
}

focus() {
if (!this.option('focusedElement') && this.option('searchEnabled')) {
this._searchEditor && this._searchEditor.focus();
render($container: dxElementWrapper, options: SearchBoxControllerOptions): void {
const rootElementClassName = this._addWidgetPrefix('with-search');
const searchBoxClassName = this._addWidgetPrefix('search');
const { searchEnabled, onValueChanged } = options;

this._onSearchBoxValueChanged = onValueChanged;

if (!searchEnabled) {
$container.removeClass(rootElementClassName);
this.remove();
return;
}

this.callBase();
},
if (this._editor) {
this.updateEditorOptions(options);
} else {
const editorOptions = this._getEditorOptions(options);
$container.addClass(rootElementClassName);
this._$element = $('<div>').addClass(searchBoxClassName).prependTo($container);
this._editor = this._createEditor(this._$element, this._editorWidget, editorOptions);
}
}

updateEditorOptions(options: SearchBoxControllerOptions): void {
const editorOptions = this._getEditorOptions(options);
this._editor?.option(editorOptions);
}

_getEditorOptions(options: SearchBoxControllerOptions): Record<string, unknown> {
Comment thread
pharret31 marked this conversation as resolved.
Outdated
const {
tabIndex,
searchValue,
searchEditorOptions,
searchTimeout,
} = options;
const placeholder = messageLocalization.format('Search');

return {
mode: 'search',
placeholder,
tabIndex,
value: searchValue,
valueChangeEvent: 'input',
inputAttr: { 'aria-label': placeholder },
onValueChanged: (e: ValueChangedEvent): void => {
this._onValueChanged(e, searchTimeout);
},
...searchEditorOptions,
};
}

_onValueChanged(e: ValueChangedEvent, searchTimeout = 0): void {
this._valueChangeDeferred = Deferred();
clearTimeout(this._valueChangeTimeout);

this._valueChangeDeferred.done((): void => {
this._onSearchBoxValueChanged?.(e.value);
});

_cleanAria(): void {
const $element = this.$element();
if (e.event?.type === 'input' && searchTimeout) {
// eslint-disable-next-line no-restricted-globals
this._valueChangeTimeout = setTimeout((): void => {
this._valueChangeDeferred?.resolve();
}, searchTimeout);
} else {
this._valueChangeDeferred?.resolve();
}
}

this.setAria({
role: null,
activedescendant: null,
}, $element);
resolveValueChange(): void {
this._valueChangeDeferred?.resolve();
}

$element.attr('tabIndex', null);
},
remove(): void {
this._$element?.remove();
this._$element = null;
this._editor = null;
}

_clean(): void {
this.callBase();
this._cleanAria();
},
focus(): void {
this._editor?.focus();
}

_refresh(): void {
if (this._valueChangeDeferred) {
this._valueChangeDeferred.resolve();
}
dispose(): void {
this.remove();
}

this.callBase();
},
_addWidgetPrefix(className: string): string {
return `${this._widgetPrefix}-${className}`;
}
}

setEditorClass(value): void {
EditorClass = value;
},
};
export default SearchBoxController;
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ class DropDownList<
.appendTo(this._popup.$content());
this._$list = $list;

// @ts-expect-error
Comment thread
pharret31 marked this conversation as resolved.
Outdated
this._list = this._createComponent($list, List, this._listConfig());
this._refreshList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { noop } from '@js/core/utils/common';
import { extend } from '@js/core/utils/extend';
import { each } from '@js/core/utils/iterator';
import { isDefined, isFunction } from '@js/core/utils/type';
import TextBox from '@js/ui/text_box';
import errors from '@js/ui/widget/ui.errors';
import uiSearchBoxMixin from '@js/ui/widget/ui.search_box_mixin';
import { getOperationBySearchMode } from '@ts/ui/collection/m_search_box_mixin';
Comment thread
ksercs marked this conversation as resolved.
Outdated

import HierarchicalDataConverter, {
type DataAccessors,
Expand Down Expand Up @@ -52,9 +51,6 @@ export interface DataAdapterOptions extends BaseDataAdapterOptions {
searchExpr?: string | string[] | ((item: ItemData) => unknown);
}

// @ts-expect-error ts-error
uiSearchBoxMixin.setEditorClass(TextBox);

class DataAdapter {
options: DataAdapterOptions;

Expand Down Expand Up @@ -574,8 +570,8 @@ class DataAdapter {

_filterDataStructure(filterValue: string, dataStructure?: InternalNode[]): InternalNode[] {
const selector = this.options.searchExpr ?? this.options.dataAccessors.getters.display;
// @ts-expect-error ts-error
const operation = uiSearchBoxMixin.getOperationBySearchMode(this.options.searchMode);
const operation = getOperationBySearchMode(this.options.searchMode);
// @ts-expect-error
const criteria = DataAdapter._createCriteria(selector, filterValue, operation);

const data = dataStructure ?? this._initialDataStructure;
Expand Down
Loading