Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions packages/devextreme/js/__internal/core/utils/m_stubs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export function stubComponent(componentName) {
export function stubComponent<T>(componentName: string): T {
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
return class NoComponent {
constructor() {
// TODO: make correct exceptions here and in decorators
throw new Error(`Module '${componentName}' not found`);
}

static getInstance() {}
};
static getInstance(): void {}
} as T;
}
6 changes: 3 additions & 3 deletions packages/devextreme/js/__internal/core/widget/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ export interface ActionConfig {
}

export interface Properties<TComponent> extends ComponentOptions<
EventInfo<TComponent>,
InitializedEventInfo<TComponent>,
OptionChangedEventInfo<TComponent>
EventInfo<TComponent>,
InitializedEventInfo<TComponent>,
OptionChangedEventInfo<TComponent>
> {
onInitializing?: ((e: Record<string, unknown>) => void) | undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { extend } from '@js/core/utils/extend';
import { getDefaultAlignment } from '@js/core/utils/position';
import { format } from '@js/core/utils/string';
import { isDefined, isFunction } from '@js/core/utils/type';
import List from '@js/ui/list_light';
import errors from '@js/ui/widget/ui.errors';
import { prepareItems } from '@ts/grids/grid_core/m_export';
import List from '@ts/ui/list/m_list.edit.search';

import type { ColumnHeadersView } from '../../grid_core/column_headers/m_column_headers';
import type { ColumnsController } from '../../grid_core/columns_controller/m_columns_controller';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import { each } from '@js/core/utils/iterator';
import { isDeferred, isDefined, isFunction } from '@js/core/utils/type';
import type dxCheckBox from '@js/ui/check_box';
import type dxList from '@js/ui/list';
import List from '@js/ui/list_light';
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 List from '@ts/ui/list/m_list.edit.search';
import TreeView from '@ts/ui/tree_view/m_tree_view.search';

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;
},
): void => {
switch (true) {
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.getSearchBoxController().focus();
})
.catch(() => {});
break;
Expand Down
268 changes: 115 additions & 153 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,140 @@
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: {},
});
},

_initMarkup(): void {
this._renderSearch();
this.callBase();
},
import type { ValueChangedEvent } from '@js/ui/text_box';
import type { SearchBoxMixinOptions } from '@js/ui/widget/ui.search_box_mixin';
import { stubComponent } from '@ts/core/utils/m_stubs';
import type TextBox from '@ts/ui/text_box/m_text_box';
import type { TextBoxProperties } from '@ts/ui/text_box/m_text_box';

_renderSearch(): void {
const $element = this.$element();
const searchEnabled = this.option('searchEnabled');
const searchBoxClassName = this._addWidgetPrefix('search');
const rootElementClassName = this._addWidgetPrefix('with-search');
export const getOperationBySearchMode = (searchMode?: SearchMode): string | undefined => (searchMode === 'equals' ? '=' : searchMode);

if (!searchEnabled) {
$element.removeClass(rootElementClassName);
this._removeSearchBox();
return;
}
export type SearchBoxControllerOptions = SearchBoxMixinOptions & {
tabIndex?: number;
onValueChanged?: (value: string) => void;
};

const editorOptions = this._getSearchEditorOptions();
class SearchBoxController {
static EditorClass: (new (...args) => TextBox) = stubComponent('TextBox');

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);
}
},
_editor?: TextBox | null;

_removeSearchBox(): void {
this._$searchEditorElement && this._$searchEditorElement.remove();
delete this._$searchEditorElement;
delete this._searchEditor;
},
_valueChangeDeferred!: DeferredObj<unknown>;

_getSearchEditorOptions() {
const that = this;
const userEditorOptions = that.option('searchEditorOptions');
const searchText = messageLocalization.format('Search');
// eslint-disable-next-line no-restricted-globals
_valueChangeTimeout!: ReturnType<typeof setTimeout>;

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);
},
_onSearchBoxValueChanged?: (value: string) => void;

_getAriaTarget() {
if (this.option('searchEnabled')) {
return this._itemContainer(true);
}
return this.callBase();
},
static setEditorClass(value: new (...args) => TextBox): void {
SearchBoxController.EditorClass = value;
}

_focusTarget() {
if (this.option('searchEnabled')) {
return this._itemContainer(true);
}
render(
widgetPrefix: string,
$container: dxElementWrapper,
options: SearchBoxControllerOptions,
createEditorCallback: (
$element: dxElementWrapper,
component: (new (...args) => TextBox),
options: TextBoxProperties,
) => TextBox,
): void {
const rootElementClassName = `${widgetPrefix}-with-search`;
const searchBoxClassName = `${widgetPrefix}-search`;
const { searchEnabled, onValueChanged } = options;

return this.callBase();
},
this._onSearchBoxValueChanged = onValueChanged;

_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);
if (!searchEnabled) {
$container.removeClass(rootElementClassName);
this.remove();
return;
}
},

focus() {
if (!this.option('focusedElement') && this.option('searchEnabled')) {
this._searchEditor && this._searchEditor.focus();
return;
if (this._editor) {
this.updateEditorOptions(options);
} else {
const editorOptions = this._getEditorOptions(options);
$container.addClass(rootElementClassName);
const $editor = $('<div>')
.addClass(searchBoxClassName)
.prependTo($container);

this._editor = createEditorCallback(
$editor,
SearchBoxController.EditorClass,
editorOptions,
);
}
}

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

_getEditorOptions(options: SearchBoxControllerOptions): TextBoxProperties {
const {
tabIndex,
searchValue,
searchEditorOptions,
searchTimeout,
} = options;
const placeholder = messageLocalization.format('Search');

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

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

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

this.setAria({
role: null,
activedescendant: null,
}, $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();
}
}

$element.attr('tabIndex', null);
},
resolveValueChange(): void {
this._valueChangeDeferred?.resolve();
}

_clean(): void {
this.callBase();
this._cleanAria();
},
remove(): void {
this._editor?.$element().remove();
this._editor = null;
}

_refresh(): void {
if (this._valueChangeDeferred) {
this._valueChangeDeferred.resolve();
}
focus(): void {
this._editor?.focus();
}

this.callBase();
},
dispose(): void {
this.remove();
}
}

setEditorClass(value): void {
EditorClass = value;
},
};
export default SearchBoxController;
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import dateSerialization from '@js/core/utils/date_serialization';
import { getHeight, getOuterHeight } from '@js/core/utils/size';
import { isDate } from '@js/core/utils/type';
import { getWindow } from '@js/core/utils/window';
import List from '@js/ui/list_light';
import { getSizeValue } from '@ts/ui/drop_down_editor/m_utils';
import List from '@ts/ui/list/m_list.edit.search';

import DateBoxStrategy from './m_date_box.strategy';
import dateUtils from './m_date_utils';
Expand Down
Loading
Loading