From 7e0f3577093363bb4a1ce193a835c24eacdd7059 Mon Sep 17 00:00:00 2001 From: pharret31 Date: Wed, 19 Nov 2025 13:13:37 +0100 Subject: [PATCH 1/2] Other UI: move files to .ts --- .../accessibility.js => __internal/ui/shared/accessibility.ts} | 0 .../{ui/shared/filtering.js => __internal/ui/shared/filtering.ts} | 0 .../ui/shared/ui.editor_factory_mixin.ts} | 0 .../{ui/splitter_control.js => __internal/ui/splitter_control.ts} | 0 packages/devextreme/js/{ui/themes.js => __internal/ui/themes.ts} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename packages/devextreme/js/{ui/shared/accessibility.js => __internal/ui/shared/accessibility.ts} (100%) rename packages/devextreme/js/{ui/shared/filtering.js => __internal/ui/shared/filtering.ts} (100%) rename packages/devextreme/js/{ui/shared/ui.editor_factory_mixin.js => __internal/ui/shared/ui.editor_factory_mixin.ts} (100%) rename packages/devextreme/js/{ui/splitter_control.js => __internal/ui/splitter_control.ts} (100%) rename packages/devextreme/js/{ui/themes.js => __internal/ui/themes.ts} (100%) diff --git a/packages/devextreme/js/ui/shared/accessibility.js b/packages/devextreme/js/__internal/ui/shared/accessibility.ts similarity index 100% rename from packages/devextreme/js/ui/shared/accessibility.js rename to packages/devextreme/js/__internal/ui/shared/accessibility.ts diff --git a/packages/devextreme/js/ui/shared/filtering.js b/packages/devextreme/js/__internal/ui/shared/filtering.ts similarity index 100% rename from packages/devextreme/js/ui/shared/filtering.js rename to packages/devextreme/js/__internal/ui/shared/filtering.ts diff --git a/packages/devextreme/js/ui/shared/ui.editor_factory_mixin.js b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts similarity index 100% rename from packages/devextreme/js/ui/shared/ui.editor_factory_mixin.js rename to packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts diff --git a/packages/devextreme/js/ui/splitter_control.js b/packages/devextreme/js/__internal/ui/splitter_control.ts similarity index 100% rename from packages/devextreme/js/ui/splitter_control.js rename to packages/devextreme/js/__internal/ui/splitter_control.ts diff --git a/packages/devextreme/js/ui/themes.js b/packages/devextreme/js/__internal/ui/themes.ts similarity index 100% rename from packages/devextreme/js/ui/themes.js rename to packages/devextreme/js/__internal/ui/themes.ts From 3409e6fc67c06916141147abfd7b7d1fadc67d2f Mon Sep 17 00:00:00 2001 From: pharret31 Date: Wed, 19 Nov 2025 22:16:39 +0100 Subject: [PATCH 2/2] OtherUI: fix ts issues --- .../editor_factory/m_editor_factory.ts | 2 +- .../js/__internal/pagination/content.tsx | 2 +- .../ui.file_manager.adaptivity.ts | 14 +- .../ui/gantt/ui.gantt.size_helper.ts | 13 +- .../js/__internal/ui/gantt/ui.gantt.ts | 6 +- .../js/__internal/ui/shared/accessibility.ts | 379 +++++---- .../js/__internal/ui/shared/filtering.ts | 403 +++++----- .../ui/shared/ui.editor_factory_mixin.ts | 622 ++++++++------- .../js/__internal/ui/splitter_control.ts | 504 +++++++----- .../devextreme/js/__internal/ui/themes.ts | 725 ++++++++++-------- .../devextreme/js/ui/shared/accessibility.js | 21 + packages/devextreme/js/ui/shared/filtering.js | 2 + .../js/ui/shared/ui.editor_factory_mixin.js | 3 + packages/devextreme/js/ui/splitter_control.js | 3 + packages/devextreme/js/ui/themes.js | 24 + 15 files changed, 1530 insertions(+), 1193 deletions(-) create mode 100644 packages/devextreme/js/ui/shared/accessibility.js create mode 100644 packages/devextreme/js/ui/shared/filtering.js create mode 100644 packages/devextreme/js/ui/shared/ui.editor_factory_mixin.js create mode 100644 packages/devextreme/js/ui/splitter_control.js create mode 100644 packages/devextreme/js/ui/themes.js diff --git a/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts b/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts index 7b920a7f408d..972b9beba24f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editor_factory/m_editor_factory.ts @@ -50,7 +50,7 @@ interface EditorFactoryMixinType { } -const ViewControllerWithMixin: ModuleType = EditorFactoryMixin(modules.ViewController) as any; +const ViewControllerWithMixin: ModuleType = EditorFactoryMixin(modules.ViewController); export class EditorFactory extends ViewControllerWithMixin { private _isFocusOverlay: any; diff --git a/packages/devextreme/js/__internal/pagination/content.tsx b/packages/devextreme/js/__internal/pagination/content.tsx index 881882e6a791..0a1525c9c2dd 100644 --- a/packages/devextreme/js/__internal/pagination/content.tsx +++ b/packages/devextreme/js/__internal/pagination/content.tsx @@ -6,10 +6,10 @@ import type { RefObject } from '@ts/core/r1/types'; import { Widget } from '@ts/core/r1/widget'; import { createRef as infernoCreateRef } from 'inferno'; -import { registerKeyboardAction } from '../../ui/shared/accessibility'; import type { EventCallback } from '../core/r1/event_callback'; import type { DisposeEffectReturn } from '../core/r1/utils/effect_return'; import { combineClasses } from '../core/r1/utils/render_utils'; +import { registerKeyboardAction } from '../ui/shared/accessibility'; import { LIGHT_MODE_CLASS, PAGER_CLASS, diff --git a/packages/devextreme/js/__internal/ui/file_manager/ui.file_manager.adaptivity.ts b/packages/devextreme/js/__internal/ui/file_manager/ui.file_manager.adaptivity.ts index f777e003879b..848d5a5591fd 100644 --- a/packages/devextreme/js/__internal/ui/file_manager/ui.file_manager.adaptivity.ts +++ b/packages/devextreme/js/__internal/ui/file_manager/ui.file_manager.adaptivity.ts @@ -8,6 +8,7 @@ import type { OptionChanged } from '@ts/core/widget/types'; import type { WidgetProperties } from '@ts/core/widget/widget'; import Widget from '@ts/core/widget/widget'; import Drawer from '@ts/ui/drawer/drawer'; +import type { ActiveStateChangedEvent, ApplyPanelSizeEvent } from '@ts/ui/splitter_control'; const window = getWindow(); const ADAPTIVE_STATE_SCREEN_WIDTH = 573; @@ -16,11 +17,6 @@ const FILE_MANAGER_ADAPTIVITY_DRAWER_PANEL_CLASS = 'dx-filemanager-adaptivity-dr const DRAWER_PANEL_CONTENT_INITIAL = 'dx-drawer-panel-content-initial'; const DRAWER_PANEL_CONTENT_ADAPTIVE = 'dx-drawer-panel-content-adaptive'; -interface PanelSize { - leftPanelWidth: string | number; - rightPanelWidth: string | number; -} - interface FileManagerAdaptivityControlOptions extends WidgetProperties { drawerTemplate?: (container: dxElementWrapper | Element) => void; contentTemplate?: (container: dxElementWrapper) => void; @@ -87,7 +83,7 @@ class FileManagerAdaptivityControl extends Widget { this._sizeHelper?.onApplyPanelSize(e); }, }); - this._splitter.option( - 'initialLeftPanelWidth', - this.option('taskListWidth'), - ); + const { taskListWidth } = this.option(); + this._splitter.option('initialLeftPanelWidth', taskListWidth); } _renderBars(): void { diff --git a/packages/devextreme/js/__internal/ui/shared/accessibility.ts b/packages/devextreme/js/__internal/ui/shared/accessibility.ts index 4a08acc45e78..58547a3432ef 100644 --- a/packages/devextreme/js/__internal/ui/shared/accessibility.ts +++ b/packages/devextreme/js/__internal/ui/shared/accessibility.ts @@ -1,9 +1,10 @@ -import $ from '../../core/renderer'; -import eventsEngine from '../../common/core/events/core/events_engine'; -import { normalizeKeyName } from '../../common/core/events/utils/index'; -import { extend } from '../../core/utils/extend'; -import domAdapter from '../../core/dom_adapter'; -import { noop } from '../../core/utils/common'; +import eventsEngine from '@js/common/core/events/core/events_engine'; +import { normalizeKeyName } from '@js/common/core/events/utils/index'; +import domAdapter from '@js/core/dom_adapter'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import { noop } from '@js/core/utils/common'; +import { extend } from '@js/core/utils/extend'; const FOCUS_STATE_CLASS = 'dx-state-focused'; const FOCUS_DISABLED_CLASS = 'dx-cell-focus-disabled'; @@ -13,205 +14,257 @@ const GRID_CELL_SELECTOR = `${GRID_ROW_SELECTOR} > td`; const TREELIST_ROW_SELECTOR = '.dx-treelist-rowsview .dx-row'; const TREELIST_CELL_SELECTOR = `${TREELIST_ROW_SELECTOR} > td`; const viewItemSelectorMap = { - groupPanel: ['.dx-datagrid-group-panel .dx-group-panel-item[tabindex]'], - columnHeaders: ['.dx-datagrid-headers .dx-header-row > td.dx-datagrid-action', '.dx-treelist-headers .dx-header-row > td.dx-treelist-action'], - filterRow: [ - '.dx-datagrid-headers .dx-datagrid-filter-row .dx-editor-cell .dx-texteditor-input', - '.dx-treelist-headers .dx-treelist-filter-row .dx-editor-cell .dx-texteditor-input' - ], - rowsView: [ - `${FOCUSED_ROW_SELECTOR}`, - `${GRID_ROW_SELECTOR}[tabindex]`, - `${GRID_CELL_SELECTOR}[tabindex]`, - `${GRID_CELL_SELECTOR}`, - `${TREELIST_ROW_SELECTOR}[tabindex]`, - `${TREELIST_CELL_SELECTOR}[tabindex]`, - `${TREELIST_CELL_SELECTOR}` - ], - footer: ['.dx-datagrid-total-footer .dx-datagrid-summary-item', '.dx-treelist-total-footer .dx-treelist-summary-item'], - filterPanel: ['.dx-datagrid-filter-panel .dx-icon-filter', '.dx-treelist-filter-panel .dx-icon-filter'], - pager: ['.dx-datagrid-pager [tabindex]', '.dx-treelist-pager [tabindex]'] + groupPanel: ['.dx-datagrid-group-panel .dx-group-panel-item[tabindex]'], + columnHeaders: ['.dx-datagrid-headers .dx-header-row > td.dx-datagrid-action', '.dx-treelist-headers .dx-header-row > td.dx-treelist-action'], + filterRow: [ + '.dx-datagrid-headers .dx-datagrid-filter-row .dx-editor-cell .dx-texteditor-input', + '.dx-treelist-headers .dx-treelist-filter-row .dx-editor-cell .dx-texteditor-input', + ], + rowsView: [ + `${FOCUSED_ROW_SELECTOR}`, + `${GRID_ROW_SELECTOR}[tabindex]`, + `${GRID_CELL_SELECTOR}[tabindex]`, + `${GRID_CELL_SELECTOR}`, + `${TREELIST_ROW_SELECTOR}[tabindex]`, + `${TREELIST_CELL_SELECTOR}[tabindex]`, + `${TREELIST_CELL_SELECTOR}`, + ], + footer: ['.dx-datagrid-total-footer .dx-datagrid-summary-item', '.dx-treelist-total-footer .dx-treelist-summary-item'], + filterPanel: ['.dx-datagrid-filter-panel .dx-icon-filter', '.dx-treelist-filter-panel .dx-icon-filter'], + pager: ['.dx-datagrid-pager [tabindex]', '.dx-treelist-pager [tabindex]'], }; let isMouseDown = false; let isHiddenFocusing = false; -let focusedElementInfo = null; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let focusedElementInfo: any = null; let needToSkipFocusin = false; -function processKeyDown(viewName, instance, event, action, $mainElement, executeKeyDown) { - const isHandled = fireKeyDownEvent(instance, event.originalEvent, executeKeyDown); - if(isHandled) { - return; - } +function getActiveAccessibleElements(ariaLabel?: string, viewElement?): dxElementWrapper { + const $viewElement = $(viewElement); + let $activeElements: dxElementWrapper = $(); - const keyName = normalizeKeyName(event); + if (ariaLabel) { + const escapedAriaLabel = ariaLabel?.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + $activeElements = $viewElement.find(`[aria-label="${escapedAriaLabel}"][tabindex]`); + } else { + $activeElements = $viewElement.find('[tabindex]'); + } - if(keyName === 'enter' || keyName === 'space') { - saveFocusedElementInfo(event.target, instance); - action && action({ event: event }); - } else if(keyName === 'tab') { - $mainElement.addClass(FOCUS_STATE_CLASS); - } else { - selectView(viewName, instance, event); - } + return $activeElements; } -export function saveFocusedElementInfo(target, instance) { - const $target = $(target); - const ariaLabel = $target.attr('aria-label'); - const $activeElements = getActiveAccessibleElements(ariaLabel, instance.element()); - const targetIndex = $activeElements.index($target); - - focusedElementInfo = extend({}, - { ariaLabel: ariaLabel, index: targetIndex }, - { viewInstance: instance }); +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function saveFocusedElementInfo(target, instance): void { + const $target = $(target); + const ariaLabel = $target.attr('aria-label'); + const $activeElements = getActiveAccessibleElements(ariaLabel, instance.element()); + const targetIndex = $activeElements.index($target); + + focusedElementInfo = extend( + {}, + { ariaLabel, index: targetIndex }, + { viewInstance: instance }, + ); } -function getActiveAccessibleElements(ariaLabel, viewElement) { - const $viewElement = $(viewElement); - let $activeElements; +function fireKeyDownEvent(instance, event, executeAction): boolean { + const args = { + event, + handled: false, + }; - if(ariaLabel) { - const escapedAriaLabel = ariaLabel?.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); - $activeElements = $viewElement.find(`[aria-label="${escapedAriaLabel}"][tabindex]`); - } else { - $activeElements = $viewElement.find('[tabindex]'); - } + if (executeAction) { + executeAction(args); + } else { + instance._createActionByOption('onKeyDown')(args); + } - return $activeElements; + return args.handled; } -function findFocusedViewElement(instanceRootDomNode, viewSelectors, element) { - const root = instanceRootDomNode ?? element?.getRootNode() ?? domAdapter.getDocument(); +function findFocusedViewElement( + instanceRootDomNode, + viewSelectors, + element, + // @ts-expect-error ts-error +): dxElementWrapper | undefined { + const root = instanceRootDomNode ?? element?.getRootNode() ?? domAdapter.getDocument(); - if(!root) { return; } + if (!root) { + return; + } - const $root = $(root); + const $root = $(root); - for(const index in viewSelectors) { - const selector = viewSelectors[index]; - const $focusViewElement = $root.find(selector).first(); + // eslint-disable-next-line no-restricted-syntax,guard-for-in + for (const index in viewSelectors) { + const selector = viewSelectors[index]; + const $focusViewElement = $root.find(selector).first(); - if($focusViewElement.length) { - return $focusViewElement; - } + if ($focusViewElement.length) { + // eslint-disable-next-line consistent-return + return $focusViewElement; } + } } -function fireKeyDownEvent(instance, event, executeAction) { - const args = { - event: event, - handled: false - }; - - if(executeAction) { - executeAction(args); - } else { - instance._createActionByOption('onKeyDown')(args); +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function selectView(viewName: string, instance, event): void { + const keyName = normalizeKeyName(event); + + if (event.ctrlKey && (keyName === 'upArrow' || keyName === 'downArrow')) { + const viewNames = Object.keys(viewItemSelectorMap); + let viewItemIndex = viewNames.indexOf(viewName); + + const instanceRootDomNode = instance?.component?.element?.(); + + while (viewItemIndex >= 0 && viewItemIndex < viewNames.length) { + viewItemIndex += keyName === 'upArrow' ? -1 : 1; + // eslint-disable-next-line @typescript-eslint/no-shadow + const viewName = viewNames[viewItemIndex]; + const viewSelectors = viewItemSelectorMap[viewName]; + const $focusViewElement = findFocusedViewElement( + instanceRootDomNode, + viewSelectors, + event.target, + ); + if ($focusViewElement?.length) { + $focusViewElement.attr('tabindex', instance.option('tabindex') || 0); + // @ts-expect-error ts-error + eventsEngine.trigger($focusViewElement, 'focus'); + $focusViewElement.removeClass(FOCUS_DISABLED_CLASS); + break; + } } + } +} - return args.handled; +function processKeyDown(viewName, instance, event, action, $mainElement, executeKeyDown): void { + const isHandled = fireKeyDownEvent(instance, event.originalEvent, executeKeyDown); + if (isHandled) { + return; + } + + const keyName = normalizeKeyName(event); + + if (keyName === 'enter' || keyName === 'space') { + saveFocusedElementInfo(event.target, instance); + action?.({ event }); + } else if (keyName === 'tab') { + $mainElement.addClass(FOCUS_STATE_CLASS); + } else { + selectView(viewName, instance, event); + } } -function onDocumentVisibilityChange() { - const focusedElement = domAdapter.getActiveElement(); +function onDocumentVisibilityChange(): void { + const focusedElement = domAdapter.getActiveElement(); - needToSkipFocusin = focusedElement && !focusedElement.closest(`.${FOCUS_STATE_CLASS}`); + needToSkipFocusin = focusedElement && !focusedElement.closest(`.${FOCUS_STATE_CLASS}`); } -export function subscribeVisibilityChange() { - eventsEngine.on(domAdapter.getDocument(), 'visibilitychange', onDocumentVisibilityChange); +export function subscribeVisibilityChange(): void { + eventsEngine.on(domAdapter.getDocument(), 'visibilitychange', onDocumentVisibilityChange); } -export function unsubscribeVisibilityChange() { - eventsEngine.off(domAdapter.getDocument(), 'visibilitychange', onDocumentVisibilityChange); +export function unsubscribeVisibilityChange(): void { + eventsEngine.off(domAdapter.getDocument(), 'visibilitychange', onDocumentVisibilityChange); } -export function hiddenFocus(element, preventScroll) { - isHiddenFocusing = true; - element.focus({ preventScroll }); - isHiddenFocusing = false; +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function hiddenFocus(element, preventScroll?: boolean): void { + isHiddenFocusing = true; + element.focus({ preventScroll }); + isHiddenFocusing = false; } -export function registerKeyboardAction(viewName, instance, $element, selector, action, executeKeyDown) { - if(instance.option('useLegacyKeyboardNavigation')) { - return noop; +export function registerKeyboardAction( + viewName: string, + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + instance, + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + $element, + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + selector, + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + action, + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + executeKeyDown?, +): () => void { + const { useLegacyKeyboardNavigation } = instance.option(); + if (useLegacyKeyboardNavigation) { + return noop; + } + + const getMainElement = (): dxElementWrapper => $(instance.element()); + const keyDownHandler = (e): void => processKeyDown( + viewName, + instance, + e, + action, + getMainElement(), + executeKeyDown, + ); + const mouseDownHandler = (): void => { + isMouseDown = true; + getMainElement().removeClass(FOCUS_STATE_CLASS); + }; + const focusinHandler = (): void => { + if (needToSkipFocusin) { + needToSkipFocusin = false; + return; } - const getMainElement = () => $(instance.element()); - const keyDownHandler = e => processKeyDown(viewName, instance, e, action, getMainElement(), executeKeyDown); - const mouseDownHandler = () => { - isMouseDown = true; - getMainElement().removeClass(FOCUS_STATE_CLASS); - }; - const focusinHandler = () => { - if(needToSkipFocusin) { - needToSkipFocusin = false; - return; - } - - const needShowOverlay = !isMouseDown && !isHiddenFocusing; - if(needShowOverlay) { - getMainElement().addClass(FOCUS_STATE_CLASS); - } - isMouseDown = false; - }; - const mouseUpHandler = () => { - isMouseDown = false; - }; - - eventsEngine.on($element, 'keydown', selector, keyDownHandler); - eventsEngine.on($element, 'mousedown', selector, mouseDownHandler); - eventsEngine.on($element, 'focusin', selector, focusinHandler); - eventsEngine.on($element, 'mouseup contextmenu', selector, mouseUpHandler); - return () => { - eventsEngine.off($element, 'keydown', selector, keyDownHandler); - eventsEngine.off($element, 'mousedown', selector, mouseDownHandler); - eventsEngine.off($element, 'focusin', selector, focusinHandler); - eventsEngine.off($element, 'mouseup contextmenu', selector, mouseUpHandler); - }; -} - -export function restoreFocus(instance) { - if(!instance.option('useLegacyKeyboardNavigation') && focusedElementInfo) { - const viewInstance = focusedElementInfo.viewInstance; - if(viewInstance) { - const $activeElements = getActiveAccessibleElements(focusedElementInfo.ariaLabel, viewInstance.element()); - const $targetElement = $activeElements.eq(focusedElementInfo.index); - - focusedElementInfo = null; - - eventsEngine.trigger($targetElement, 'focus'); - } + const needShowOverlay = !isMouseDown && !isHiddenFocusing; + if (needShowOverlay) { + getMainElement().addClass(FOCUS_STATE_CLASS); } + isMouseDown = false; + }; + const mouseUpHandler = (): void => { + isMouseDown = false; + }; + + eventsEngine.on($element, 'keydown', selector, keyDownHandler); + eventsEngine.on($element, 'mousedown', selector, mouseDownHandler); + eventsEngine.on($element, 'focusin', selector, focusinHandler); + eventsEngine.on($element, 'mouseup contextmenu', selector, mouseUpHandler); + return (): void => { + // @ts-expect-error ts-error + eventsEngine.off($element, 'keydown', selector, keyDownHandler); + // @ts-expect-error ts-error + eventsEngine.off($element, 'mousedown', selector, mouseDownHandler); + // @ts-expect-error ts-error + eventsEngine.off($element, 'focusin', selector, focusinHandler); + // @ts-expect-error ts-error + eventsEngine.off($element, 'mouseup contextmenu', selector, mouseUpHandler); + }; } -export function selectView(viewName, instance, event) { - const keyName = normalizeKeyName(event); - - if(event.ctrlKey && (keyName === 'upArrow' || keyName === 'downArrow')) { - const viewNames = Object.keys(viewItemSelectorMap); - let viewItemIndex = viewNames.indexOf(viewName); - - const instanceRootDomNode = instance?.component?.element?.(); - - while(viewItemIndex >= 0 && viewItemIndex < viewNames.length) { - viewItemIndex = keyName === 'upArrow' ? --viewItemIndex : ++viewItemIndex; - const viewName = viewNames[viewItemIndex]; - const viewSelectors = viewItemSelectorMap[viewName]; - const $focusViewElement = findFocusedViewElement(instanceRootDomNode, viewSelectors, event.target); - if($focusViewElement && $focusViewElement.length) { - $focusViewElement.attr('tabindex', instance.option('tabindex') || 0); - eventsEngine.trigger($focusViewElement, 'focus'); - $focusViewElement.removeClass(FOCUS_DISABLED_CLASS); - break; - } - } +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function restoreFocus(instance): void { + if (!instance.option('useLegacyKeyboardNavigation') && focusedElementInfo) { + const { viewInstance } = focusedElementInfo; + if (viewInstance) { + const $activeElements = getActiveAccessibleElements( + focusedElementInfo.ariaLabel, + viewInstance.element(), + ); + const $targetElement = $activeElements.eq(focusedElementInfo.index); + + focusedElementInfo = null; + + // @ts-expect-error ts-error + eventsEngine.trigger($targetElement, 'focus'); } + } } -export function setTabIndex(instance, $element) { - if(!instance.option('useLegacyKeyboardnavigation')) { - $element.attr('tabindex', instance.option('tabindex') || 0); - } +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function setTabIndex(instance, $element: dxElementWrapper): void { + const { useLegacyKeyboardNavigation } = instance.option(); + if (!useLegacyKeyboardNavigation) { + $element.attr('tabindex', instance.option('tabindex') || 0); + } } diff --git a/packages/devextreme/js/__internal/ui/shared/filtering.ts b/packages/devextreme/js/__internal/ui/shared/filtering.ts index cdddba184a60..d8dc56baa125 100644 --- a/packages/devextreme/js/__internal/ui/shared/filtering.ts +++ b/packages/devextreme/js/__internal/ui/shared/filtering.ts @@ -1,192 +1,241 @@ -import { isDate, isDefined } from '../../core/utils/type'; -import { map } from '../../core/utils/iterator'; +import { map } from '@js/core/utils/iterator'; +import { isDate, isDefined } from '@js/core/utils/type'; const DEFAULT_DATE_INTERVAL = ['year', 'month', 'day']; const DEFAULT_DATETIME_INTERVAL = ['year', 'month', 'day', 'hour', 'minute']; -const isDateType = function(dataType) { - return dataType === 'date' || dataType === 'datetime'; -}; - -const getGroupInterval = function(column) { - let index; - let result = []; - const dateIntervals = ['year', 'month', 'day', 'hour', 'minute', 'second']; - const groupInterval = column.headerFilter && column.headerFilter.groupInterval; - const interval = groupInterval === 'quarter' ? 'month' : groupInterval; - - if(isDateType(column.dataType) && groupInterval !== null) { - result = column.dataType === 'datetime' ? DEFAULT_DATETIME_INTERVAL : DEFAULT_DATE_INTERVAL; - index = dateIntervals.indexOf(interval); - - if(index >= 0) { - result = dateIntervals.slice(0, index); - result.push(groupInterval); - return result; - } - - return result; - } else if(isDefined(groupInterval)) { - return Array.isArray(groupInterval) ? groupInterval : [groupInterval]; +const isDateType = (dataType): boolean => dataType === 'date' || dataType === 'datetime'; + +// @ts-expect-error ts-error +// eslint-disable-next-line consistent-return +const getGroupInterval = (column): string[] | undefined => { + // eslint-disable-next-line @typescript-eslint/init-declarations + let index; + let result: string[] = []; + const dateIntervals = ['year', 'month', 'day', 'hour', 'minute', 'second']; + const groupInterval = column.headerFilter?.groupInterval; + const interval = groupInterval === 'quarter' ? 'month' : groupInterval; + + if (isDateType(column.dataType) && groupInterval !== null) { + result = column.dataType === 'datetime' ? DEFAULT_DATETIME_INTERVAL : DEFAULT_DATE_INTERVAL; + index = dateIntervals.indexOf(interval); + + if (index >= 0) { + result = dateIntervals.slice(0, index); + result.push(groupInterval); + return result; } -}; -const getNormalizedCalculateDisplayValue = function(column) { - return column.calculateDisplayValue?.context ? column.calculateDisplayValue : null; + return result; + } + + if (isDefined(groupInterval)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Array.isArray(groupInterval) ? groupInterval : [groupInterval]; + } }; -export default (function() { - const getFilterSelector = function(column, target) { - let selector = column.dataField || column.selector; - if(target === 'search') { - selector = column.displayField || getNormalizedCalculateDisplayValue(column) || selector; +// eslint-disable-next-line @stylistic/max-len +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/no-unsafe-return +const getNormalizedCalculateDisplayValue = (column) => (column.calculateDisplayValue?.context + ? column.calculateDisplayValue + : null); + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export default (function () { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const getFilterSelector = (column, target) => { + let selector = column.dataField || column.selector; + if (target === 'search') { + selector = column.displayField || getNormalizedCalculateDisplayValue(column) || selector; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return selector; + }; + + const isZeroTime = (date): boolean => date.getHours() + + date.getMinutes() + + date.getSeconds() + + date.getMilliseconds() + < 1; + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const getDateValues = (dateValue) => { + if (isDate(dateValue)) { + return [ + dateValue.getFullYear(), + dateValue.getMonth(), + dateValue.getDate(), + dateValue.getHours(), + dateValue.getMinutes(), + dateValue.getSeconds(), + ]; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return map( + `${dateValue}`.split('/'), + (value, index): number => (index === 1 ? Number(value) - 1 : Number(value)), + ); + }; + + // @ts-expect-error ts-error + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type,consistent-return + const getFilterExpressionByRange = function (filterValue, target) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const column = this; + // eslint-disable-next-line @typescript-eslint/init-declarations + let endFilterValue; + // eslint-disable-next-line @typescript-eslint/init-declarations + let startFilterExpression; + // eslint-disable-next-line @typescript-eslint/init-declarations + let endFilterExpression; + const selector = getFilterSelector(column, target); + + if (Array.isArray(filterValue) && isDefined(filterValue[0]) && isDefined(filterValue[1])) { + startFilterExpression = [selector, '>=', filterValue[0]]; + endFilterExpression = [selector, '<=', filterValue[1]]; + + if (isDateType(column.dataType) && isZeroTime(filterValue[1])) { + endFilterValue = new Date(filterValue[1].getTime()); + if (column.dataType === 'date') { + endFilterValue.setDate(filterValue[1].getDate() + 1); } - return selector; - }; + endFilterExpression = [selector, '<', endFilterValue]; + } - const isZeroTime = function(date) { - return date.getHours() + date.getMinutes() + date.getSeconds() + date.getMilliseconds() < 1; - }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [startFilterExpression, 'and', endFilterExpression]; + } + }; + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const getFilterExpressionForDate = function (filterValue, selectedFilterOperation, target) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const column = this; + // eslint-disable-next-line @typescript-eslint/init-declarations + let dateStart; + // eslint-disable-next-line @typescript-eslint/init-declarations + let dateEnd; + // eslint-disable-next-line @typescript-eslint/init-declarations + let dateInterval; + const values = getDateValues(filterValue); + const selector = getFilterSelector(column, target); + + if (target === 'headerFilter') { + dateInterval = getGroupInterval(column)?.[values.length - 1]; + } else if (column.dataType === 'datetime') { + dateInterval = 'minute'; + } - const getDateValues = function(dateValue) { - if(isDate(dateValue)) { - return [dateValue.getFullYear(), dateValue.getMonth(), dateValue.getDate(), dateValue.getHours(), dateValue.getMinutes(), dateValue.getSeconds()]; - } - return map(('' + dateValue).split('/'), function(value, index) { - return index === 1 ? Number(value) - 1 : Number(value); - }); - }; - - const getFilterExpressionByRange = function(filterValue, target) { - const column = this; - let endFilterValue; - let startFilterExpression; - let endFilterExpression; - const selector = getFilterSelector(column, target); - - if(Array.isArray(filterValue) && isDefined(filterValue[0]) && isDefined(filterValue[1])) { - startFilterExpression = [selector, '>=', filterValue[0]]; - endFilterExpression = [selector, '<=', filterValue[1]]; - - if(isDateType(column.dataType) && isZeroTime(filterValue[1])) { - endFilterValue = new Date(filterValue[1].getTime()); - if(column.dataType === 'date') { - endFilterValue.setDate(filterValue[1].getDate() + 1); - } - endFilterExpression = [selector, '<', endFilterValue]; - } - - return [startFilterExpression, 'and', endFilterExpression]; - } - }; - - const getFilterExpressionForDate = function(filterValue, selectedFilterOperation, target) { - const column = this; - let dateStart; - let dateEnd; - let dateInterval; - const values = getDateValues(filterValue); - const selector = getFilterSelector(column, target); - - if(target === 'headerFilter') { - dateInterval = getGroupInterval(column)[values.length - 1]; - } else if(column.dataType === 'datetime') { - dateInterval = 'minute'; - } + switch (dateInterval) { + case 'year': + dateStart = new Date(values[0], 0, 1); + dateEnd = new Date(values[0] + 1, 0, 1); + break; + case 'month': + dateStart = new Date(values[0], values[1], 1); + dateEnd = new Date(values[0], values[1] + 1, 1); + break; + case 'quarter': + dateStart = new Date(values[0], 3 * values[1], 1); + dateEnd = new Date(values[0], 3 * values[1] + 3, 1); + break; + case 'hour': + dateStart = new Date(values[0], values[1], values[2], values[3]); + dateEnd = new Date(values[0], values[1], values[2], values[3] + 1); + break; + case 'minute': + dateStart = new Date(values[0], values[1], values[2], values[3], values[4]); + dateEnd = new Date(values[0], values[1], values[2], values[3], values[4] + 1); + break; + case 'second': + dateStart = new Date(values[0], values[1], values[2], values[3], values[4], values[5]); + dateEnd = new Date(values[0], values[1], values[2], values[3], values[4], values[5] + 1); + break; + default: + dateStart = new Date(values[0], values[1], values[2]); + dateEnd = new Date(values[0], values[1], values[2] + 1); + } - switch(dateInterval) { - case 'year': - dateStart = new Date(values[0], 0, 1); - dateEnd = new Date(values[0] + 1, 0, 1); - break; - case 'month': - dateStart = new Date(values[0], values[1], 1); - dateEnd = new Date(values[0], values[1] + 1, 1); - break; - case 'quarter': - dateStart = new Date(values[0], 3 * values[1], 1); - dateEnd = new Date(values[0], 3 * values[1] + 3, 1); - break; - case 'hour': - dateStart = new Date(values[0], values[1], values[2], values[3]); - dateEnd = new Date(values[0], values[1], values[2], values[3] + 1); - break; - case 'minute': - dateStart = new Date(values[0], values[1], values[2], values[3], values[4]); - dateEnd = new Date(values[0], values[1], values[2], values[3], values[4] + 1); - break; - case 'second': - dateStart = new Date(values[0], values[1], values[2], values[3], values[4], values[5]); - dateEnd = new Date(values[0], values[1], values[2], values[3], values[4], values[5] + 1); - break; - default: - dateStart = new Date(values[0], values[1], values[2]); - dateEnd = new Date(values[0], values[1], values[2] + 1); - } + switch (selectedFilterOperation) { + case '<': + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [selector, '<', dateStart]; + case '<=': + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [selector, '<', dateEnd]; + case '>': + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [selector, '>=', dateEnd]; + case '>=': + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [selector, '>=', dateStart]; + case '<>': + return [[selector, '<', dateStart], 'or', [selector, '>=', dateEnd]]; + default: + return [[selector, '>=', dateStart], 'and', [selector, '<', dateEnd]]; + } + }; + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const getFilterExpressionForNumber = function (filterValue, selectedFilterOperation, target) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const column = this; + const selector = getFilterSelector(column, target); + const groupInterval = getGroupInterval(column); + + if (target === 'headerFilter' && groupInterval && isDefined(filterValue)) { + const values = `${filterValue}`.split('/'); + const value = Number(values[values.length - 1]); + + const interval = groupInterval[values.length - 1]; + const startFilterValue = [selector, '>=', value]; + const endFilterValue = [selector, '<', value + interval]; + const condition = [startFilterValue, 'and', endFilterValue]; + return condition; + } - switch(selectedFilterOperation) { - case '<': - return [selector, '<', dateStart]; - case '<=': - return [selector, '<', dateEnd]; - case '>': - return [selector, '>=', dateEnd]; - case '>=': - return [selector, '>=', dateStart]; - case '<>': - return [[selector, '<', dateStart], 'or', [selector, '>=', dateEnd]]; - default: - return [[selector, '>=', dateStart], 'and', [selector, '<', dateEnd]]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [selector, selectedFilterOperation || '=', filterValue]; + }; + + return { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + defaultCalculateFilterExpression(filterValue, selectedFilterOperation, target) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const column = this; + const selector = getFilterSelector(column, target); + const isSearchByDisplayValue = column.calculateDisplayValue && target === 'search'; + // eslint-disable-next-line @stylistic/no-mixed-operators + const dataType = isSearchByDisplayValue && column.lookup?.dataType || column.dataType; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let filter: any = null; + + if ((target === 'headerFilter' || target === 'filterBuilder') && filterValue === null) { + filter = [selector, selectedFilterOperation || '=', null]; + if (dataType === 'string') { + filter = [filter, selectedFilterOperation === '=' ? 'or' : 'and', [selector, selectedFilterOperation || '=', '']]; } - }; - - const getFilterExpressionForNumber = function(filterValue, selectedFilterOperation, target) { - const column = this; - const selector = getFilterSelector(column, target); - const groupInterval = getGroupInterval(column); - - if(target === 'headerFilter' && groupInterval && isDefined(filterValue)) { - const values = ('' + filterValue).split('/'); - const value = Number(values[values.length - 1]); - - const interval = groupInterval[values.length - 1]; - const startFilterValue = [selector, '>=', value]; - const endFilterValue = [selector, '<', value + interval]; - const condition = [startFilterValue, 'and', endFilterValue]; - return condition; - } - - return [selector, selectedFilterOperation || '=', filterValue]; - }; - - return { - defaultCalculateFilterExpression: function(filterValue, selectedFilterOperation, target) { - const column = this; - const selector = getFilterSelector(column, target); - const isSearchByDisplayValue = column.calculateDisplayValue && target === 'search'; - const dataType = isSearchByDisplayValue && column.lookup && column.lookup.dataType || column.dataType; - let filter = null; - - if((target === 'headerFilter' || target === 'filterBuilder') && filterValue === null) { - filter = [selector, selectedFilterOperation || '=', null]; - if(dataType === 'string') { - filter = [filter, selectedFilterOperation === '=' ? 'or' : 'and', [selector, selectedFilterOperation || '=', '']]; - } - } else if(dataType === 'string' && (!column.lookup || isSearchByDisplayValue)) { - filter = [selector, selectedFilterOperation || 'contains', filterValue]; - } else if(selectedFilterOperation === 'between') { - return getFilterExpressionByRange.apply(column, [filterValue, target]); - } else if(isDateType(dataType) && isDefined(filterValue)) { - return getFilterExpressionForDate.apply(column, arguments); - } else if(dataType === 'number') { - return getFilterExpressionForNumber.apply(column, arguments); - } else { - filter = [selector, selectedFilterOperation || '=', filterValue]; - } - - return filter; - }, - - getGroupInterval: getGroupInterval - }; + } else if (dataType === 'string' && (!column.lookup || isSearchByDisplayValue)) { + filter = [selector, selectedFilterOperation || 'contains', filterValue]; + } else if (selectedFilterOperation === 'between') { + return getFilterExpressionByRange.apply(column, [filterValue, target]); + } else if (isDateType(dataType) && isDefined(filterValue)) { + // @ts-expect-error ts-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,prefer-rest-params + return getFilterExpressionForDate.apply(column, arguments); + } else if (dataType === 'number') { + // @ts-expect-error ts-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,prefer-rest-params + return getFilterExpressionForNumber.apply(column, arguments); + } else { + filter = [selector, selectedFilterOperation || '=', filterValue]; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return filter; + }, + + getGroupInterval, + }; }()); diff --git a/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts index e2f693daa68c..0458f8f1c274 100644 --- a/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts +++ b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts @@ -1,353 +1,383 @@ -import $ from '../../core/renderer'; -import eventsEngine from '../../common/core/events/core/events_engine'; -import { isDefined, isObject, isFunction } from '../../core/utils/type'; -import variableWrapper from '../../core/utils/variable_wrapper'; -import { compileGetter } from '../../core/utils/data'; -import browser from '../../core/utils/browser'; -import { extend } from '../../core/utils/extend'; -import devices from '../../core/devices'; -import { getPublicElement } from '../../core/element'; -import { normalizeDataSourceOptions } from '../../common/data/data_source/utils'; -import { normalizeKeyName } from '../../common/core/events/utils/index'; +import '@js/ui/text_box'; +import '@js/ui/number_box'; +import '@js/ui/check_box'; +import '@js/ui/select_box'; +import '@js/ui/date_box'; + +import eventsEngine from '@js/common/core/events/core/events_engine'; +import { normalizeKeyName } from '@js/common/core/events/utils/index'; +import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; +import devices from '@js/core/devices'; +import { getPublicElement } from '@js/core/element'; +import $ from '@js/core/renderer'; +import browser from '@js/core/utils/browser'; +import { compileGetter } from '@js/core/utils/data'; +import { extend } from '@js/core/utils/extend'; +import { isDefined, isFunction, isObject } from '@js/core/utils/type'; +import variableWrapper from '@js/core/utils/variable_wrapper'; const { isWrapped } = variableWrapper; -import '../text_box'; -import '../number_box'; -import '../check_box'; -import '../select_box'; -import '../date_box'; - const CHECKBOX_SIZE_CLASS = 'checkbox-size'; const EDITOR_INLINE_BLOCK = 'dx-editor-inline-block'; -const getResultConfig = function(config, options) { - return extend(config, { - readOnly: options.readOnly, - placeholder: options.placeholder, - inputAttr: { - id: options.id, - 'aria-labelledby': options['aria-labelledby'] - }, - tabIndex: options.tabIndex - }, options.editorOptions); -}; - -const checkEnterBug = function() { - return browser.mozilla || devices.real().ios;// Workaround for T344096, T249363, T314719, caused by https://connect.microsoft.com/IE/feedback/details/1552272/ +// eslint-disable-next-line @stylistic/max-len +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/no-unsafe-return +const getResultConfig = (config, options) => extend(config, { + readOnly: options.readOnly, + placeholder: options.placeholder, + inputAttr: { + id: options.id, + 'aria-labelledby': options['aria-labelledby'], + }, + tabIndex: options.tabIndex, +}, options.editorOptions); + +// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing +const checkEnterBug = (): boolean | undefined => browser.mozilla || devices.real().ios; // Workaround for T344096, T249363, T314719, caused by https://connect.microsoft.com/IE/feedback/details/1552272/ + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const getTextEditorConfig = (options) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data: any = {}; + const isEnterBug = checkEnterBug(); + const sharedData = options.sharedData || data; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return getResultConfig({ + placeholder: options.placeholder, + width: options.width, + value: options.value, + onValueChanged(e) { + const needDelayedUpdate = options.parentType === 'filterRow' || options.parentType === 'searchPanel'; + const isInputOrKeyUpEvent = e.event && (e.event.type === 'input' || e.event.type === 'keyup'); + // eslint-disable-next-line @typescript-eslint/no-shadow + const updateValue = function (e, notFireEvent?): void { + options?.setValue(e.value, notFireEvent); + }; + + clearTimeout(data.valueChangeTimeout); + + if (isInputOrKeyUpEvent && needDelayedUpdate) { + // eslint-disable-next-line no-multi-assign,no-restricted-globals + sharedData.valueChangeTimeout = data.valueChangeTimeout = setTimeout((): void => { + updateValue(e, data.valueChangeTimeout !== sharedData.valueChangeTimeout); + }, isDefined(options.updateValueTimeout) ? options.updateValueTimeout : 0); + } else { + updateValue(e); + } + }, + onKeyDown(e): void { + if (isEnterBug && normalizeKeyName(e.event) === 'enter') { + // @ts-expect-error ts-error + eventsEngine.trigger($(e.component._input()), 'change'); + } + }, + valueChangeEvent: `change${options.parentType === 'filterRow' ? ' keyup input' : ''}`, + }, options); }; -const getTextEditorConfig = function(options) { - const data = {}; - const isEnterBug = checkEnterBug(); - const sharedData = options.sharedData || data; - - return getResultConfig({ - placeholder: options.placeholder, - width: options.width, - value: options.value, - onValueChanged: function(e) { - - const needDelayedUpdate = options.parentType === 'filterRow' || options.parentType === 'searchPanel'; - const isInputOrKeyUpEvent = e.event && (e.event.type === 'input' || e.event.type === 'keyup'); - const updateValue = function(e, notFireEvent) { - options && options.setValue(e.value, notFireEvent); - }; - - clearTimeout(data.valueChangeTimeout); - - if(isInputOrKeyUpEvent && needDelayedUpdate) { - sharedData.valueChangeTimeout = data.valueChangeTimeout = setTimeout(function() { - updateValue(e, data.valueChangeTimeout !== sharedData.valueChangeTimeout); - }, isDefined(options.updateValueTimeout) ? options.updateValueTimeout : 0); - } else { - updateValue(e); - } - }, - onKeyDown: function(e) { - if(isEnterBug && normalizeKeyName(e.event) === 'enter') { - eventsEngine.trigger($(e.component._input()), 'change'); - } - }, - valueChangeEvent: 'change' + (options.parentType === 'filterRow' ? ' keyup input' : '') - }, options); -}; - -const prepareDateBox = function(options) { - options.editorName = 'dxDateBox'; - - options.editorOptions = getResultConfig({ - value: options.value, - onValueChanged: function(args) { - options.setValue(args.value); - }, - onKeyDown: function({ component, event }) { - const useMaskBehavior = component.option('useMaskBehavior'); - - if((checkEnterBug() || useMaskBehavior) && normalizeKeyName(event) === 'enter') { - component.blur(); - component.focus(); - } - }, - displayFormat: options.format, - type: options.dataType, - dateSerializationFormat: null, - width: options.parentType === 'filterBuilder' ? undefined : 'auto' - }, options); -}; - -const prepareTextBox = function(options) { - const config = getTextEditorConfig(options); - const isSearching = options.parentType === 'searchPanel'; - const toString = function(value) { - return isDefined(value) ? value.toString() : ''; +function watchLookupDataSource(options): void { + if (options.row?.watch && options.parentType === 'dataRow') { + const editorOptions = options.editorOptions || {}; + + options.editorOptions = editorOptions; + + // eslint-disable-next-line @typescript-eslint/init-declarations + let selectBox; + const { onInitialized } = editorOptions; + editorOptions.onInitialized = function (e): void { + // eslint-disable-next-line prefer-rest-params + onInitialized?.apply(this, arguments); + selectBox = e.component; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + selectBox.on('disposing', stopWatch); }; - if(options.editorType && options.editorType !== 'dxTextBox') { - config.value = options.value; - } else { - config.value = toString(options.value); - } - config.valueChangeEvent += (isSearching ? ' keyup input search' : ''); - config.mode = config.mode || (isSearching ? 'search' : 'text'); + // eslint-disable-next-line @typescript-eslint/init-declarations + let dataSource; + const stopWatch = options.row.watch(() => { + dataSource = options.lookup.dataSource(options.row); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return dataSource?.filter; + }, () => { + selectBox.option('dataSource', dataSource); + }, (row) => { + options.row = row; + }); + } +} - options.editorName = 'dxTextBox'; - options.editorOptions = config; +const prepareDateBox = (options): void => { + options.editorName = 'dxDateBox'; + + options.editorOptions = getResultConfig({ + value: options.value, + onValueChanged(args): void { + options.setValue(args.value); + }, + onKeyDown({ component, event }): void { + const useMaskBehavior = component.option('useMaskBehavior'); + + if ((checkEnterBug() || useMaskBehavior) && normalizeKeyName(event) === 'enter') { + component.blur(); + component.focus(); + } + }, + displayFormat: options.format, + type: options.dataType, + dateSerializationFormat: null, + width: options.parentType === 'filterBuilder' ? undefined : 'auto', + }, options); }; -const prepareNumberBox = function(options) { - const config = getTextEditorConfig(options); +const prepareTextBox = (options): void => { + const config = getTextEditorConfig(options); + const isSearching = options.parentType === 'searchPanel'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const toString = (value): string => (isDefined(value) ? value.toString() : ''); + + if (options.editorType && options.editorType !== 'dxTextBox') { + config.value = options.value; + } else { + config.value = toString(options.value); + } + config.valueChangeEvent += isSearching ? ' keyup input search' : ''; + config.mode = config.mode || (isSearching ? 'search' : 'text'); + + options.editorName = 'dxTextBox'; + options.editorOptions = config; +}; - config.value = isDefined(options.value) ? options.value : null; +const prepareNumberBox = (options): void => { + const config = getTextEditorConfig(options); - options.editorName = 'dxNumberBox'; + config.value = isDefined(options.value) ? options.value : null; - options.editorOptions = config; -}; + options.editorName = 'dxNumberBox'; -const prepareBooleanEditor = function(options) { - if(options.parentType === 'filterRow' || options.parentType === 'filterBuilder') { - prepareLookupEditor(extend(options, { - lookup: { - displayExpr: function(data) { - if(data === true) { - return options.trueText || 'true'; - } else if(data === false) { - return options.falseText || 'false'; - } - }, - dataSource: [true, false] - } - })); - } else { - prepareCheckBox(options); - } + options.editorOptions = config; }; -function watchLookupDataSource(options) { - if(options.row && options.row.watch && options.parentType === 'dataRow') { - const editorOptions = options.editorOptions || {}; +function prepareLookupEditor(options): void { + const { lookup } = options; + // eslint-disable-next-line @typescript-eslint/init-declarations + let displayGetter; + // eslint-disable-next-line @typescript-eslint/init-declarations + let dataSource; + // eslint-disable-next-line @typescript-eslint/init-declarations + let postProcess; + const isFilterRow = options.parentType === 'filterRow'; - options.editorOptions = editorOptions; + if (lookup) { + displayGetter = compileGetter(lookup.displayExpr); + dataSource = lookup.dataSource; - let selectBox; - const onInitialized = editorOptions.onInitialized; - editorOptions.onInitialized = function(e) { - onInitialized && onInitialized.apply(this, arguments); - selectBox = e.component; - selectBox.on('disposing', stopWatch); - }; + if (isFunction(dataSource) && !isWrapped(dataSource)) { + dataSource = dataSource(options.row || {}); - let dataSource; - const stopWatch = options.row.watch(() => { - dataSource = options.lookup.dataSource(options.row); - return dataSource && dataSource.filter; - }, () => { - selectBox.option('dataSource', dataSource); - }, (row) => { - options.row = row; - }); + watchLookupDataSource(options); } -} -function prepareLookupEditor(options) { - const lookup = options.lookup; - let displayGetter; - let dataSource; - let postProcess; - const isFilterRow = options.parentType === 'filterRow'; - - if(lookup) { - displayGetter = compileGetter(lookup.displayExpr); - dataSource = lookup.dataSource; + if (isObject(dataSource) || Array.isArray(dataSource)) { + // @ts-expect-error ts- + dataSource = normalizeDataSourceOptions(dataSource); + if (isFilterRow) { + postProcess = dataSource.postProcess; + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + dataSource.postProcess = function (items) { + if (this.pageIndex() === 0) { + // eslint-disable-next-line no-param-reassign + items = items.slice(0); + items.unshift(null); + } + if (postProcess) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return postProcess.call(this, items); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return items; + }; + } + } - if(isFunction(dataSource) && !isWrapped(dataSource)) { - dataSource = dataSource(options.row || {}); + const allowClearing = Boolean(lookup.allowClearing && !isFilterRow); - watchLookupDataSource(options); + options.editorName = options.editorType ?? 'dxSelectBox'; + options.editorOptions = getResultConfig({ + searchEnabled: true, + value: options.value, + valueExpr: options.lookup.valueExpr, + searchExpr: options.lookup.searchExpr || options.lookup.displayExpr, + allowClearing, + showClearButton: allowClearing, + displayExpr(data) { + if (data === null) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return options.showAllText; } - - if(isObject(dataSource) || Array.isArray(dataSource)) { - dataSource = normalizeDataSourceOptions(dataSource); - if(isFilterRow) { - postProcess = dataSource.postProcess; - dataSource.postProcess = function(items) { - if(this.pageIndex() === 0) { - items = items.slice(0); - items.unshift(null); - } - if(postProcess) { - return postProcess.call(this, items); - } - return items; - }; - } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return displayGetter(data); + }, + dataSource, + onValueChanged(e): void { + const params = [e.value]; + + if (!isFilterRow) { + params.push(e.component.option('text')); } - const allowClearing = Boolean(lookup.allowClearing && !isFilterRow); - - options.editorName = options.editorType ?? 'dxSelectBox'; - options.editorOptions = getResultConfig({ - searchEnabled: true, - value: options.value, - valueExpr: options.lookup.valueExpr, - searchExpr: options.lookup.searchExpr || options.lookup.displayExpr, - allowClearing: allowClearing, - showClearButton: allowClearing, - displayExpr: function(data) { - if(data === null) { - return options.showAllText; - } - return displayGetter(data); - }, - dataSource: dataSource, - onValueChanged: function(e) { - const params = [e.value]; - - !isFilterRow && params.push(e.component.option('text')); - options.setValue.apply(this, params); - } - }, options); - } + options.setValue.apply(this, params); + }, + }, options); + } } -function prepareCheckBox(options) { - options.editorName = 'dxCheckBox'; - options.editorOptions = getResultConfig({ - elementAttr: { - id: options.id - }, - value: isDefined(options.value) ? options.value : undefined, - hoverStateEnabled: !options.readOnly, - focusStateEnabled: !options.readOnly, - activeStateEnabled: false, - onValueChanged: function(e) { - options.setValue && options.setValue(e.value, e /* for selection */); - }, - }, options); +function prepareCheckBox(options): void { + options.editorName = 'dxCheckBox'; + options.editorOptions = getResultConfig({ + elementAttr: { + id: options.id, + }, + value: isDefined(options.value) ? options.value : undefined, + hoverStateEnabled: !options.readOnly, + focusStateEnabled: !options.readOnly, + activeStateEnabled: false, + onValueChanged(e) { + options.setValue?.(e.value, e /* for selection */); + }, + }, options); } -const createEditorCore = function(that, options) { - const $editorElement = $(options.editorElement); - if(options.editorName && options.editorOptions && $editorElement[options.editorName]) { - if(options.editorName === 'dxCheckBox' || options.editorName === 'dxSwitch') { - if(!options.isOnForm) { - $editorElement.addClass(that.addWidgetPrefix(CHECKBOX_SIZE_CLASS)); - $editorElement.parent().addClass(EDITOR_INLINE_BLOCK); - } - } +const prepareBooleanEditor = (options): void => { + if (options.parentType === 'filterRow' || options.parentType === 'filterBuilder') { + prepareLookupEditor(extend(options, { + lookup: { + // eslint-disable-next-line consistent-return + displayExpr(data) { + if (data === true) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return options.trueText || 'true'; + } if (data === false) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return options.falseText || 'false'; + } + }, + dataSource: [true, false], + }, + })); + } else { + prepareCheckBox(options); + } +}; - that._createComponent($editorElement, options.editorName, options.editorOptions); +const createEditorCore = (that, options): void => { + const $editorElement = $(options.editorElement); + if (options.editorName && options.editorOptions && $editorElement[options.editorName]) { + if (options.editorName === 'dxCheckBox' || options.editorName === 'dxSwitch') { + if (!options.isOnForm) { + $editorElement.addClass(that.addWidgetPrefix(CHECKBOX_SIZE_CLASS)); + $editorElement.parent().addClass(EDITOR_INLINE_BLOCK); + } + } - if(options.editorName === 'dxDateBox') { - const dateBox = $editorElement.dxDateBox('instance'); - const defaultEnterKeyHandler = dateBox._supportedKeys()['enter']; + that._createComponent($editorElement, options.editorName, options.editorOptions); - dateBox.registerKeyHandler('enter', (e) => { - if(dateBox.option('opened')) { - defaultEnterKeyHandler(e); - } + if (options.editorName === 'dxDateBox') { + // @ts-expect-error ts-error + const dateBox = $editorElement.dxDateBox('instance'); + const defaultEnterKeyHandler = dateBox._supportedKeys().enter; - return true; - }); + dateBox.registerKeyHandler('enter', (e): boolean => { + if (dateBox.option('opened')) { + defaultEnterKeyHandler(e); } - if(options.editorName === 'dxTextArea') { - $editorElement.dxTextArea('instance').registerKeyHandler('enter', function(event) { - if(normalizeKeyName(event) === 'enter' && !event.ctrlKey && !event.shiftKey) { - event.stopPropagation(); - } - }); + return true; + }); + } + + if (options.editorName === 'dxTextArea') { + // @ts-expect-error ts-error + $editorElement.dxTextArea('instance').registerKeyHandler('enter', (event): void => { + if (normalizeKeyName(event) === 'enter' && !event.ctrlKey && !event.shiftKey) { + event.stopPropagation(); } + }); } + } }; -const prepareCustomEditor = (options) => { - options.editorName = options.editorType; - options.editorOptions = getResultConfig({ - value: options.value, - onValueChanged: function(args) { - options.setValue(args.value); - }, - }, options); +const prepareCustomEditor = (options): void => { + options.editorName = options.editorType; + options.editorOptions = getResultConfig({ + value: options.value, + onValueChanged(args) { + options.setValue(args.value); + }, + }, options); }; -const prepareEditor = (options) => { - const prepareDefaultEditor = { - 'dxDateBox': prepareDateBox, - 'dxCheckBox': prepareCheckBox, - 'dxNumberBox': prepareNumberBox, - 'dxTextBox': prepareTextBox, - }; - - if(options.lookup) { - prepareLookupEditor(options); - } else if(options.editorType) { - (prepareDefaultEditor[options.editorType] ?? prepareCustomEditor)(options); - } else { - switch(options.dataType) { - case 'date': - case 'datetime': - prepareDateBox(options); - break; - case 'boolean': - prepareBooleanEditor(options); - break; - case 'number': - prepareNumberBox(options); - break; - default: - prepareTextBox(options); - break; - } +const prepareEditor = (options): void => { + const prepareDefaultEditor = { + dxDateBox: prepareDateBox, + dxCheckBox: prepareCheckBox, + dxNumberBox: prepareNumberBox, + dxTextBox: prepareTextBox, + }; + + if (options.lookup) { + prepareLookupEditor(options); + } else if (options.editorType) { + (prepareDefaultEditor[options.editorType] ?? prepareCustomEditor)(options); + } else { + switch (options.dataType) { + case 'date': + case 'datetime': + prepareDateBox(options); + break; + case 'boolean': + prepareBooleanEditor(options); + break; + case 'number': + prepareNumberBox(options); + break; + default: + prepareTextBox(options); + break; } + } }; -const EditorFactoryMixin = (Base) => class EditorFactoryMixin extends Base { - createEditor($container, options) { - options.cancel = false; - options.editorElement = getPublicElement($container); - - if(!isDefined(options.tabIndex)) { - options.tabIndex = this.option('tabIndex'); - } +const EditorFactoryMixin = ( + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + Base, + // eslint-disable-next-line @typescript-eslint/no-shadow +): typeof EditorFactoryMixin => class EditorFactoryMixin extends Base { + createEditor($container, options): void { + options.cancel = false; + options.editorElement = getPublicElement($container); + + if (!isDefined(options.tabIndex)) { + options.tabIndex = this.option('tabIndex'); + } - prepareEditor(options); + prepareEditor(options); - this.executeAction('onEditorPreparing', options); + this.executeAction('onEditorPreparing', options); - if(options.cancel) { - return; - } + if (options.cancel) { + return; + } - if(options.parentType === 'dataRow' && !options.isOnForm && !isDefined(options.editorOptions.showValidationMark)) { - options.editorOptions.showValidationMark = false; - } + if (options.parentType === 'dataRow' && !options.isOnForm && !isDefined(options.editorOptions.showValidationMark)) { + options.editorOptions.showValidationMark = false; + } - createEditorCore(this, options); + createEditorCore(this, options); - this.executeAction('onEditorPrepared', options); - } + this.executeAction('onEditorPrepared', options); + } }; export default EditorFactoryMixin; diff --git a/packages/devextreme/js/__internal/ui/splitter_control.ts b/packages/devextreme/js/__internal/ui/splitter_control.ts index 1d1a53df352a..a342cc75a0f3 100644 --- a/packages/devextreme/js/__internal/ui/splitter_control.ts +++ b/packages/devextreme/js/__internal/ui/splitter_control.ts @@ -1,11 +1,16 @@ -import $ from '../core/renderer'; -import Widget from './widget/ui.widget'; -import domAdapter from '../core/dom_adapter'; -import eventsEngine from '../common/core/events/core/events_engine'; -import pointerEvents from '../common/core/events/pointer'; -import { getWindow } from '../core/utils/window'; -import { addNamespace } from '../common/core/events/utils/index'; -import Guid from '../core/guid'; +import eventsEngine from '@js/common/core/events/core/events_engine'; +import pointerEvents from '@js/common/core/events/pointer'; +import { addNamespace } from '@js/common/core/events/utils/index'; +import domAdapter from '@js/core/dom_adapter'; +import Guid from '@js/core/guid'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import { getWindow } from '@js/core/utils/window'; +import type { OptionChanged } from '@ts/core/widget/types'; +import type { WidgetProperties } from '@ts/core/widget/widget'; +import Widget from '@ts/core/widget/widget'; + +type Dimension = 'height' | 'width'; const window = getWindow(); @@ -19,208 +24,317 @@ const STATE_DISABLED_CLASS = 'dx-state-disabled'; const SPLITTER_MODULE_NAMESPACE = 'dxSplitterResizing'; -export default class SplitterControl extends Widget { - _init() { - super._init(); - const eventGuid = new Guid().toString(); - this.SPLITTER_POINTER_DOWN_EVENT_NAME = addNamespace(pointerEvents.down, SPLITTER_MODULE_NAMESPACE + eventGuid); - this.SPLITTER_POINTER_MOVE_EVENT_NAME = addNamespace(pointerEvents.move, SPLITTER_MODULE_NAMESPACE + eventGuid); - this.SPLITTER_POINTER_UP_EVENT_NAME = addNamespace(pointerEvents.up, SPLITTER_MODULE_NAMESPACE + eventGuid); - } - _initMarkup() { - super._initMarkup(); - - this._initActions(); - this._$container = this.option('container'); - this._$leftElement = this.option('leftElement'); - this._$rightElement = this.option('rightElement'); - - this.$element() - .addClass(SPLITTER_WRAPPER_CLASS) - .addClass(SPLITTER_INITIAL_STATE_CLASS); - this._$splitterBorder = $('
') - .addClass(SPLITTER_BORDER_CLASS) - .appendTo(this.$element()); - this._$splitter = $('
') - .addClass(SPLITTER_CLASS) - .addClass(SPLITTER_INACTIVE_CLASS) - .appendTo(this._$splitterBorder); - } - - _initActions() { - this._actions = { - onApplyPanelSize: this._createActionByOption('onApplyPanelSize'), - onActiveStateChanged: this._createActionByOption('onActiveStateChanged') - }; - } - - _render() { - super._render(); - - this._detachEventHandlers(); - this._attachEventHandlers(); - } - - _clean() { - this._detachEventHandlers(); - super._clean(); - } - - _attachEventHandlers() { - const document = domAdapter.getDocument(); - eventsEngine.on(this._$splitterBorder, this.SPLITTER_POINTER_DOWN_EVENT_NAME, this._onMouseDownHandler.bind(this)); - eventsEngine.on(document, this.SPLITTER_POINTER_MOVE_EVENT_NAME, this._onMouseMoveHandler.bind(this)); - eventsEngine.on(document, this.SPLITTER_POINTER_UP_EVENT_NAME, this._onMouseUpHandler.bind(this)); - } - - _detachEventHandlers() { - const document = domAdapter.getDocument(); - eventsEngine.off(this._$splitterBorder, this.SPLITTER_POINTER_DOWN_EVENT_NAME); - eventsEngine.off(document, this.SPLITTER_POINTER_MOVE_EVENT_NAME); - eventsEngine.off(document, this.SPLITTER_POINTER_UP_EVENT_NAME); - } - - _dimensionChanged(dimension) { - if(!dimension || dimension !== 'height') { - this._containerWidth = this._$container.get(0).clientWidth; - this._setSplitterPositionLeft({ needUpdatePanels: true, usePercentagePanelsWidth: true }); - } - } - - _onMouseDownHandler(e) { - e.preventDefault(); - this._offsetX = e.pageX - this._$splitterBorder.offset().left <= this._getSplitterBorderWidth() - ? e.pageX - this._$splitterBorder.offset().left - : 0; - this._containerWidth = this._$container.get(0).clientWidth; - - this.$element().removeClass(SPLITTER_INITIAL_STATE_CLASS); - this._toggleActive(true); - - this._setSplitterPositionLeft({ needUpdatePanels: true }); - } - - _onMouseMoveHandler(e) { - if(!this._isSplitterActive) { - return; - } - this._setSplitterPositionLeft({ splitterPositionLeft: this._getNewSplitterPositionLeft(e), needUpdatePanels: true }); - } - - _onMouseUpHandler() { - if(!this._isSplitterActive) { - return; - } - this._leftPanelPercentageWidth = null; - this._toggleActive(false); - this._setSplitterPositionLeft({ needUpdatePanels: true, usePercentagePanelsWidth: true }); - } - - _getNewSplitterPositionLeft(e) { - let newSplitterPositionLeft = e.pageX - this._getContainerLeftOffset() - this._offsetX; - newSplitterPositionLeft = Math.max(0 - this._getSplitterOffset(), newSplitterPositionLeft); - newSplitterPositionLeft = Math.min(this._containerWidth - this._getSplitterOffset() - this._getSplitterWidth(), newSplitterPositionLeft); - return newSplitterPositionLeft; - } - - _getContainerLeftOffset() { - let offsetLeft = this._$container.offset().left; - - if(window) { - const style = window.getComputedStyle(this._$container.get(0)); - const paddingLeft = parseFloat(style['paddingLeft']) || 0; - const borderLeft = parseFloat(style['borderLeftWidth']) || 0; - offsetLeft += paddingLeft + borderLeft; - } - - return offsetLeft; - } - - _getSplitterOffset() { - return (this._getSplitterBorderWidth() - this._getSplitterWidth()) / 2; - } +export interface ApplyPanelSizeEvent { + leftPanelWidth: string | number; + rightPanelWidth: string | number; +} - _getSplitterWidth() { - return this._$splitter.get(0).clientWidth; - } +export interface ActiveStateChangedEvent { + isActive: boolean; +} - _getSplitterBorderWidth() { - return this._$splitterBorder.get(0).clientWidth; - } +interface SplitterControlActions { + onApplyPanelSize?: (e: ApplyPanelSizeEvent) => void; + onActiveStateChanged?: (e: ActiveStateChangedEvent) => void; +} - _getLeftPanelWidth() { - return this._$leftElement.get(0).clientWidth; - } +interface SplitterControlOptions extends WidgetProperties { + initialLeftPanelWidth?: number; + container: dxElementWrapper; + leftElement: dxElementWrapper; + rightElement: dxElementWrapper; + onApplyPanelSize?: (e: ApplyPanelSizeEvent) => void; + onActiveStateChanged?: (e: ActiveStateChangedEvent) => void; +} - getSplitterBorderElement() { - return this._$splitterBorder; +export default class SplitterControl extends Widget { + SPLITTER_POINTER_DOWN_EVENT_NAME?: string; + + SPLITTER_POINTER_MOVE_EVENT_NAME?: string; + + SPLITTER_POINTER_UP_EVENT_NAME?: string; + + _$container!: dxElementWrapper; + + _$leftElement!: dxElementWrapper; + + _$rightElement!: dxElementWrapper; + + _$splitterBorder!: dxElementWrapper; + + _$splitter!: dxElementWrapper; + + _actions!: SplitterControlActions; + + _containerWidth!: number; + + _offsetX!: number; + + _isSplitterActive!: boolean; + + _leftPanelPercentageWidth?: number | null; + + _isSplitterCalculationDisabled?: boolean; + + _init(): void { + super._init(); + const eventGuid = new Guid().toString(); + this.SPLITTER_POINTER_DOWN_EVENT_NAME = addNamespace( + pointerEvents.down, + SPLITTER_MODULE_NAMESPACE + eventGuid, + ); + this.SPLITTER_POINTER_MOVE_EVENT_NAME = addNamespace( + pointerEvents.move, + SPLITTER_MODULE_NAMESPACE + eventGuid, + ); + this.SPLITTER_POINTER_UP_EVENT_NAME = addNamespace( + pointerEvents.up, + SPLITTER_MODULE_NAMESPACE + eventGuid, + ); + } + + _initMarkup(): void { + super._initMarkup(); + + this._initActions(); + + const { container, leftElement, rightElement } = this.option(); + + this._$container = container; + this._$leftElement = leftElement; + this._$rightElement = rightElement; + + this.$element() + .addClass(SPLITTER_WRAPPER_CLASS) + .addClass(SPLITTER_INITIAL_STATE_CLASS); + this._$splitterBorder = $('
') + .addClass(SPLITTER_BORDER_CLASS) + .appendTo(this.$element()); + this._$splitter = $('
') + .addClass(SPLITTER_CLASS) + .addClass(SPLITTER_INACTIVE_CLASS) + .appendTo(this._$splitterBorder); + } + + _initActions(): void { + this._actions = { + onApplyPanelSize: this._createActionByOption('onApplyPanelSize'), + onActiveStateChanged: this._createActionByOption('onActiveStateChanged'), + }; + } + + _render(): void { + super._render(); + + this._detachEventHandlers(); + this._attachEventHandlers(); + } + + _clean(): void { + this._detachEventHandlers(); + super._clean(); + } + + _attachEventHandlers(): void { + const document = domAdapter.getDocument(); + eventsEngine.on( + this._$splitterBorder, + this.SPLITTER_POINTER_DOWN_EVENT_NAME, + this._onMouseDownHandler.bind(this), + ); + eventsEngine.on( + document, + this.SPLITTER_POINTER_MOVE_EVENT_NAME, + this._onMouseMoveHandler.bind(this), + ); + eventsEngine.on( + document, + this.SPLITTER_POINTER_UP_EVENT_NAME, + this._onMouseUpHandler.bind(this), + ); + } + + _detachEventHandlers(): void { + const document = domAdapter.getDocument(); + eventsEngine.off(this._$splitterBorder, this.SPLITTER_POINTER_DOWN_EVENT_NAME); + eventsEngine.off(document, this.SPLITTER_POINTER_MOVE_EVENT_NAME); + eventsEngine.off(document, this.SPLITTER_POINTER_UP_EVENT_NAME); + } + + _dimensionChanged(dimension?: Dimension): void { + if (!dimension || dimension !== 'height') { + this._containerWidth = this._$container.get(0).clientWidth; + this._setSplitterPositionLeft({ needUpdatePanels: true, usePercentagePanelsWidth: true }); } - - _toggleActive(isActive) { - this.$element().toggleClass(SPLITTER_INACTIVE_CLASS, !isActive); - this._$splitter.toggleClass(SPLITTER_INACTIVE_CLASS, !isActive); - this._isSplitterActive = isActive; - this._actions.onActiveStateChanged({ isActive }); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + _onMouseDownHandler(e): void { + e.preventDefault(); + // @ts-expect-error ts-error + // eslint-disable-next-line no-unsafe-optional-chaining + this._offsetX = e.pageX - this._$splitterBorder.offset()?.left <= this._getSplitterBorderWidth() + // @ts-expect-error ts-error + // eslint-disable-next-line no-unsafe-optional-chaining + ? e.pageX - this._$splitterBorder.offset()?.left + : 0; + this._containerWidth = this._$container.get(0).clientWidth; + + this.$element().removeClass(SPLITTER_INITIAL_STATE_CLASS); + this._toggleActive(true); + + this._setSplitterPositionLeft({ needUpdatePanels: true }); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + _onMouseMoveHandler(e): void { + if (!this._isSplitterActive) { + return; } - - toggleDisabled(isDisabled) { - this.$element().toggleClass(STATE_DISABLED_CLASS, isDisabled); - this._$splitter.toggleClass(STATE_DISABLED_CLASS, isDisabled); + this._setSplitterPositionLeft({ + splitterPositionLeft: this._getNewSplitterPositionLeft(e), + needUpdatePanels: true, + }); + } + + _onMouseUpHandler(): void { + if (!this._isSplitterActive) { + return; } - - isSplitterMoved() { - return !this.$element().hasClass(SPLITTER_INITIAL_STATE_CLASS); + this._leftPanelPercentageWidth = null; + this._toggleActive(false); + this._setSplitterPositionLeft({ needUpdatePanels: true, usePercentagePanelsWidth: true }); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + _getNewSplitterPositionLeft(e): number { + let newSplitterPositionLeft = e.pageX - this._getContainerLeftOffset() - this._offsetX; + newSplitterPositionLeft = Math.max(0 - this._getSplitterOffset(), newSplitterPositionLeft); + newSplitterPositionLeft = Math.min( + this._containerWidth - this._getSplitterOffset() - this._getSplitterWidth(), + newSplitterPositionLeft, + ); + return newSplitterPositionLeft; + } + + _getContainerLeftOffset(): number { + let offsetLeft = this._$container.offset()?.left as number; + + if (window) { + const style = window.getComputedStyle(this._$container.get(0)); + const paddingLeft = parseFloat(style.paddingLeft) || 0; + const borderLeft = parseFloat(style.borderLeftWidth) || 0; + offsetLeft += paddingLeft + borderLeft; } - disableSplitterCalculation(value) { - this._isSplitterCalculationDisabled = value; + return offsetLeft; + } + + _getSplitterOffset(): number { + return (this._getSplitterBorderWidth() - this._getSplitterWidth()) / 2; + } + + _getSplitterWidth(): number { + return this._$splitter.get(0).clientWidth; + } + + _getSplitterBorderWidth(): number { + return this._$splitterBorder.get(0).clientWidth; + } + + _getLeftPanelWidth(): number { + return this._$leftElement.get(0).clientWidth; + } + + getSplitterBorderElement(): dxElementWrapper { + return this._$splitterBorder; + } + + _toggleActive(isActive: boolean): void { + this.$element().toggleClass(SPLITTER_INACTIVE_CLASS, !isActive); + this._$splitter.toggleClass(SPLITTER_INACTIVE_CLASS, !isActive); + this._isSplitterActive = isActive; + this._actions.onActiveStateChanged?.({ isActive }); + } + + toggleDisabled(isDisabled: boolean): void { + this.$element().toggleClass(STATE_DISABLED_CLASS, isDisabled); + this._$splitter.toggleClass(STATE_DISABLED_CLASS, isDisabled); + } + + isSplitterMoved(): boolean { + return !this.$element().hasClass(SPLITTER_INITIAL_STATE_CLASS); + } + + disableSplitterCalculation(value: boolean): void { + this._isSplitterCalculationDisabled = value; + } + + _setSplitterPositionLeft({ + splitterPositionLeft = null, + needUpdatePanels = false, + usePercentagePanelsWidth = false, + }: { + splitterPositionLeft?: number | string | null; + needUpdatePanels?: boolean; + usePercentagePanelsWidth?: boolean; + } = {}): void { + // eslint-disable-next-line no-param-reassign + splitterPositionLeft = splitterPositionLeft + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + || this._getLeftPanelWidth() - this._getSplitterOffset(); + // @ts-expect-error ts-error + const leftPanelWidth = splitterPositionLeft + this._getSplitterOffset(); + const rightPanelWidth = this._containerWidth - leftPanelWidth; + + if (!this._isSplitterCalculationDisabled) { + this.$element().css('left', splitterPositionLeft); } - _setSplitterPositionLeft({ splitterPositionLeft = null, needUpdatePanels = false, usePercentagePanelsWidth = false } = {}) { - splitterPositionLeft = splitterPositionLeft || this._getLeftPanelWidth() - this._getSplitterOffset(); - const leftPanelWidth = splitterPositionLeft + this._getSplitterOffset(); - const rightPanelWidth = this._containerWidth - leftPanelWidth; + this._leftPanelPercentageWidth = this._leftPanelPercentageWidth + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + || this._convertToPercentage(leftPanelWidth); + const rightPanelPercentageWidth = this._convertToPercentage( + this._containerWidth - this._convertToPixels(this._leftPanelPercentageWidth), + ); - if(!this._isSplitterCalculationDisabled) { - this.$element().css('left', splitterPositionLeft); - } - - this._leftPanelPercentageWidth = this._leftPanelPercentageWidth || this._convertToPercentage(leftPanelWidth); - const rightPanelPercentageWidth = this._convertToPercentage(this._containerWidth - this._convertToPixels(this._leftPanelPercentageWidth)); - - if(!needUpdatePanels) { - return; - } - - this._actions.onApplyPanelSize({ - leftPanelWidth: usePercentagePanelsWidth ? `${this._leftPanelPercentageWidth}%` : leftPanelWidth, - rightPanelWidth: usePercentagePanelsWidth ? `${rightPanelPercentageWidth}%` : rightPanelWidth - }); + if (!needUpdatePanels) { + return; } - _optionChanged(args) { - switch(args.name) { - case 'initialLeftPanelWidth': - this._leftPanelPercentageWidth = this._convertToPercentage(args.value); - this._dimensionChanged(); - break; - case 'leftElement': - this.repaint(); - break; - case 'onActiveStateChanged': - case 'onApplyPanelSize': - this._actions[args.name] = this._createActionByOption(args.name); - break; - default: - super._optionChanged(args); - } + this._actions.onApplyPanelSize?.({ + leftPanelWidth: usePercentagePanelsWidth ? `${this._leftPanelPercentageWidth}%` : leftPanelWidth, + rightPanelWidth: usePercentagePanelsWidth ? `${rightPanelPercentageWidth}%` : rightPanelWidth, + }); + } + + _optionChanged(args: OptionChanged): void { + const { name, value } = args; + + switch (name) { + case 'initialLeftPanelWidth': + this._leftPanelPercentageWidth = this._convertToPercentage(value as number); + this._dimensionChanged(); + break; + case 'leftElement': + this.repaint(); + break; + case 'onActiveStateChanged': + case 'onApplyPanelSize': + this._actions[name] = this._createActionByOption(name); + break; + default: + super._optionChanged(args); } + } - _convertToPercentage(pixelWidth) { - return pixelWidth / this._$container.get(0).clientWidth * 100; - } + _convertToPercentage(pixelWidth: number): number { + // eslint-disable-next-line @stylistic/no-mixed-operators + return pixelWidth / this._$container.get(0).clientWidth * 100; + } - _convertToPixels(percentageWidth) { - return percentageWidth / 100 * this._$container.get(0).clientWidth; - } + _convertToPixels(percentageWidth: number): number { + // eslint-disable-next-line @stylistic/no-mixed-operators + return percentageWidth / 100 * this._$container.get(0).clientWidth; + } } diff --git a/packages/devextreme/js/__internal/ui/themes.ts b/packages/devextreme/js/__internal/ui/themes.ts index c7d1a6912038..0bba28d28db5 100644 --- a/packages/devextreme/js/__internal/ui/themes.ts +++ b/packages/devextreme/js/__internal/ui/themes.ts @@ -1,20 +1,23 @@ -import { getOuterHeight } from '../core/utils/size'; -import devices from '../core/devices'; -import domAdapter from '../core/dom_adapter'; -import $ from '../core/renderer'; -import { Deferred, when } from '../core/utils/deferred'; -import { parseHTML } from '../core/utils/html_parser'; -import { each } from '../core/utils/iterator'; -import readyCallbacks from '../core/utils/ready_callbacks'; -import { value as viewPortValue, changeCallback, originalViewPort } from '../core/utils/view_port'; -import { getWindow, hasWindow } from '../core/utils/window'; -import { themeReadyCallback } from './themes_callback'; -import { uiLayerInitialized } from '../__internal/core/utils/m_common'; -import errors from './widget/ui.errors'; +import devices from '@js/core/devices'; +import domAdapter from '@js/core/dom_adapter'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import { Deferred, when } from '@js/core/utils/deferred'; +import { parseHTML } from '@js/core/utils/html_parser'; +import { each } from '@js/core/utils/iterator'; +import readyCallbacks from '@js/core/utils/ready_callbacks'; +import { getOuterHeight } from '@js/core/utils/size'; +import { changeCallback, originalViewPort, value as viewPortValue } from '@js/core/utils/view_port'; +import { getWindow, hasWindow } from '@js/core/utils/window'; +import errors from '@js/ui/widget/ui.errors'; +import { uiLayerInitialized } from '@ts/core/utils/m_common'; +import { themeReadyCallback } from '@ts/ui/m_themes_callback'; + const window = getWindow(); const ready = readyCallbacks.add; const viewPort = viewPortValue; const viewPortChanged = changeCallback; +// @ts-expect-error ts-error let initDeferred = new Deferred(); const DX_LINK_SELECTOR = 'link[rel=dx-theme]'; @@ -23,429 +26,468 @@ const ACTIVE_ATTR = 'data-active'; const DX_HAIRLINES_CLASS = 'dx-hairlines'; const ANY_THEME = 'any'; +// eslint-disable-next-line @typescript-eslint/init-declarations let context; +// eslint-disable-next-line @typescript-eslint/init-declarations let $activeThemeLink; +// eslint-disable-next-line @typescript-eslint/init-declarations let knownThemes; +// eslint-disable-next-line @typescript-eslint/init-declarations let currentThemeName; +// eslint-disable-next-line @typescript-eslint/init-declarations let pendingThemeName; let defaultTimeout = 15000; const THEME_MARKER_PREFIX = 'dx.'; -function readThemeMarker() { - if(!hasWindow()) { - return null; +function readThemeMarker(): string | null { + if (!hasWindow()) { + return null; + } + // @ts-expect-error ts-error + const element = $('
', context).addClass('dx-theme-marker').appendTo(context.documentElement); + // eslint-disable-next-line @typescript-eslint/init-declarations + let result: string; + + try { + result = window.getComputedStyle(element.get(0)).fontFamily; + if (!result) { + return null; } - const element = $('
', context).addClass('dx-theme-marker').appendTo(context.documentElement); - let result; - - try { - result = window.getComputedStyle(element.get(0))['fontFamily']; - if(!result) { - return null; - } - - result = result.replace(/["']/g, ''); - if(result.substr(0, THEME_MARKER_PREFIX.length) !== THEME_MARKER_PREFIX) { - return null; - } - return result.substr(THEME_MARKER_PREFIX.length); - } finally { - element.remove(); + + result = result.replace(/["']/g, ''); + if (result.substr(0, THEME_MARKER_PREFIX.length) !== THEME_MARKER_PREFIX) { + return null; } + return result.substr(THEME_MARKER_PREFIX.length); + } finally { + element.remove(); + } } -// FYI -// http://stackoverflow.com/q/2635814 -// http://stackoverflow.com/a/3078636 -export function waitForThemeLoad(themeName) { - let waitStartTime; - let timerId; - let intervalCleared = true; +export function isPendingThemeLoaded(): boolean { + if (!pendingThemeName) { + return true; + } - pendingThemeName = themeName; + const anyThemePending = pendingThemeName === ANY_THEME; - function handleLoaded() { - pendingThemeName = null; - clearInterval(timerId); - intervalCleared = true; + if (initDeferred.state() === 'resolved' && anyThemePending) { + return true; + } - themeReadyCallback.fire(); - themeReadyCallback.empty(); + const themeMarker = readThemeMarker(); - initDeferred.resolve(); - } + if (themeMarker && anyThemePending) { + return true; + } - if(isPendingThemeLoaded() || !defaultTimeout) { - handleLoaded(); - } else { - - if(!intervalCleared) { - if(pendingThemeName) { - pendingThemeName = themeName; - } - return; - } - waitStartTime = Date.now(); - - intervalCleared = false; - timerId = setInterval(function() { - const isLoaded = isPendingThemeLoaded(); - const isTimeout = !isLoaded && Date.now() - waitStartTime > defaultTimeout; - - if(isTimeout) { - errors.log('W0004', pendingThemeName); - } - - if(isLoaded || isTimeout) { - handleLoaded(); - } - }, 10); - } + return themeMarker === pendingThemeName; } -export function isPendingThemeLoaded() { - if(!pendingThemeName) { - return true; - } +// FYI +// http://stackoverflow.com/q/2635814 +// http://stackoverflow.com/a/3078636 +export function waitForThemeLoad(themeName: string): void { + // eslint-disable-next-line @typescript-eslint/init-declarations + let waitStartTime; + // eslint-disable-next-line @typescript-eslint/init-declarations + let timerId; + let intervalCleared = true; - const anyThemePending = pendingThemeName === ANY_THEME; + pendingThemeName = themeName; - if(initDeferred.state() === 'resolved' && anyThemePending) { - return true; + function handleLoaded(): void { + pendingThemeName = null; + clearInterval(timerId); + intervalCleared = true; + + themeReadyCallback.fire(); + themeReadyCallback.empty(); + + initDeferred.resolve(); + } + + if (isPendingThemeLoaded() || !defaultTimeout) { + handleLoaded(); + } else { + if (!intervalCleared) { + if (pendingThemeName) { + pendingThemeName = themeName; + } + return; } + waitStartTime = Date.now(); - const themeMarker = readThemeMarker(); + intervalCleared = false; + // eslint-disable-next-line no-restricted-globals + timerId = setInterval((): void => { + const isLoaded = isPendingThemeLoaded(); + const isTimeout = !isLoaded && Date.now() - waitStartTime > defaultTimeout; - if(themeMarker && anyThemePending) { - return true; - } + if (isTimeout) { + errors.log('W0004', pendingThemeName); + } - return themeMarker === pendingThemeName; + if (isLoaded || isTimeout) { + handleLoaded(); + } + }, 10); + } } -function processMarkup() { - const $allThemeLinks = $(DX_LINK_SELECTOR, context); - if(!$allThemeLinks.length) { - return; - } - - knownThemes = {}; - $activeThemeLink = $(parseHTML(''), context); - - $allThemeLinks.each(function() { - const link = $(this, context); - const fullThemeName = link.attr(THEME_ATTR); - const url = link.attr('href'); - const isActive = link.attr(ACTIVE_ATTR) === 'true'; - - knownThemes[fullThemeName] = { - url: url, - isActive: isActive - }; - }); +function processMarkup(): void { + // @ts-expect-error ts-error + const $allThemeLinks = $(DX_LINK_SELECTOR, context); + if (!$allThemeLinks.length) { + return; + } + + knownThemes = {}; + // @ts-expect-error ts-error + $activeThemeLink = $(parseHTML(''), context); + + // @ts-expect-error ts-error + $allThemeLinks.each(function (): void { + // @ts-expect-error ts-error + const link = $(this, context); + const fullThemeName = link.attr(THEME_ATTR); + const url = link.attr('href'); + const isActive = link.attr(ACTIVE_ATTR) === 'true'; + + // @ts-expect-error ts-error + knownThemes[fullThemeName] = { + url, + isActive, + }; + }); - $allThemeLinks.last().after($activeThemeLink); - $allThemeLinks.remove(); + $allThemeLinks.last().after($activeThemeLink); + $allThemeLinks.remove(); } -function resolveFullThemeName(desiredThemeName) { +function resolveFullThemeName(desiredThemeName: string): string | null { + const desiredThemeParts = desiredThemeName ? desiredThemeName.split('.') : []; + let result = null; - const desiredThemeParts = desiredThemeName ? desiredThemeName.split('.') : []; - let result = null; - - if(knownThemes) { - if(desiredThemeName in knownThemes) { - return desiredThemeName; - } + if (knownThemes) { + if (desiredThemeName in knownThemes) { + return desiredThemeName; + } - each(knownThemes, function(knownThemeName, themeData) { - const knownThemeParts = knownThemeName.split('.'); + // @ts-expect-error ts-error + each(knownThemes, (knownThemeName, themeData) => { + const knownThemeParts = knownThemeName.split('.'); - if(desiredThemeParts[0] && knownThemeParts[0] !== desiredThemeParts[0]) { - return; - } + if (desiredThemeParts[0] && knownThemeParts[0] !== desiredThemeParts[0]) { + return; + } - if(desiredThemeParts[1] && desiredThemeParts[1] !== knownThemeParts[1]) { - return; - } + if (desiredThemeParts[1] && desiredThemeParts[1] !== knownThemeParts[1]) { + return; + } - if(desiredThemeParts[2] && desiredThemeParts[2] !== knownThemeParts[2]) { - return; - } + if (desiredThemeParts[2] && desiredThemeParts[2] !== knownThemeParts[2]) { + return; + } - if(!result || themeData.isActive) { - result = knownThemeName; - } + if (!result || themeData.isActive) { + result = knownThemeName; + } - if(themeData.isActive) { - return false; - } - }); - } + if (themeData.isActive) { + // eslint-disable-next-line consistent-return + return false; + } + }); + } - return result; + return result; } -function initContext(newContext) { - try { - if(newContext !== context) { - knownThemes = null; - } - } catch(x) { - // Cross-origin permission error - knownThemes = null; +function initContext(newContext): void { + try { + if (newContext !== context) { + knownThemes = null; } + } catch (x) { + // Cross-origin permission error + knownThemes = null; + } - context = newContext; + context = newContext; } -export function init(options) { - options = options || {}; - initContext(options.context || domAdapter.getDocument()); - - if(!context) return; - processMarkup(); - currentThemeName = undefined; - current(options); -} +function getCssClasses(themeName?: string): string[] { + // @ts-expect-error ts-error + // eslint-disable-next-line @stylistic/max-len + // eslint-disable-next-line no-param-reassign,@typescript-eslint/no-use-before-define, @typescript-eslint/prefer-nullish-coalescing + themeName = themeName || current(); -export function current(options) { - if(!arguments.length) { - currentThemeName = currentThemeName || readThemeMarker(); - return currentThemeName; - } + const result: string[] = []; + const themeNameParts = themeName?.split('.'); - detachCssClasses(viewPort()); + if (themeNameParts) { + result.push( + `dx-theme-${themeNameParts[0]}`, + `dx-theme-${themeNameParts[0]}-typography`, + ); - options = options || {}; - if(typeof options === 'string') { - options = { theme: options }; + if (themeNameParts.length > 1) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + result.push(`dx-color-scheme-${themeNameParts[1]}${isMaterialBased(themeName as string) ? `-${themeNameParts[2]}` : ''}`); } + } + + return result; +} - const isAutoInit = options._autoInit; - const loadCallback = options.loadCallback; - let currentThemeData; +// eslint-disable-next-line @typescript-eslint/init-declarations +let themeClasses; - currentThemeName = resolveFullThemeName(options.theme || currentThemeName); +// eslint-disable-next-line @typescript-eslint/naming-convention +function _attachCssClasses(element, themeName?: string): void { + themeClasses = getCssClasses(themeName).join(' '); + $(element).addClass(themeClasses); - if(currentThemeName) { - currentThemeData = knownThemes[currentThemeName]; - } + const activateHairlines = (): void => { + const pixelRatio = hasWindow() && window.devicePixelRatio; - if(loadCallback) { - themeReadyCallback.add(loadCallback); + if (!pixelRatio || pixelRatio < 2) { + return; } - if(currentThemeData) { - $activeThemeLink.attr('href', knownThemes[currentThemeName].url); - if((themeReadyCallback.has() || initDeferred.state() !== 'resolved' || options._forceTimeout)) { - waitForThemeLoad(currentThemeName); - } - } else { - if(isAutoInit) { - if(hasWindow()) { - waitForThemeLoad(ANY_THEME); - } - - themeReadyCallback.fire(); - themeReadyCallback.empty(); - } else { - throw errors.Error('E0021', currentThemeName); - } + const $tester = $('
'); + $tester.css('border', '.5px solid transparent'); + $('body').append($tester); + if (getOuterHeight($tester) === 1) { + $(element).addClass(DX_HAIRLINES_CLASS); + themeClasses += ` ${DX_HAIRLINES_CLASS}`; } + $tester.remove(); + }; - initDeferred.done(() => attachCssClasses(originalViewPort(), currentThemeName)); + activateHairlines(); } -function getCssClasses(themeName) { - themeName = themeName || current(); - - const result = []; - const themeNameParts = themeName && themeName.split('.'); - - if(themeNameParts) { - result.push( - 'dx-theme-' + themeNameParts[0], - 'dx-theme-' + themeNameParts[0] + '-typography' - ); - - if(themeNameParts.length > 1) { - result.push('dx-color-scheme-' + themeNameParts[1] + (isMaterialBased(themeName) ? ('-' + themeNameParts[2]) : '')); - } - } +export function attachCssClasses(element: dxElementWrapper, themeName?: string): void { + when(uiLayerInitialized).done((): void => { + _attachCssClasses(element, themeName); + }); +} - return result; +export function detachCssClasses(element: dxElementWrapper): void { + when(uiLayerInitialized).done((): void => { + $(element).removeClass(themeClasses); + }); } -let themeClasses; +// eslint-disable-next-line @stylistic/max-len +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/explicit-function-return-type,consistent-return +export function current(options) { + if (!arguments.length) { + currentThemeName = currentThemeName || readThemeMarker(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return currentThemeName; + } + + detachCssClasses(viewPort()); + + // eslint-disable-next-line no-param-reassign + options = options || {}; + if (typeof options === 'string') { + // eslint-disable-next-line no-param-reassign + options = { theme: options }; + } + + const isAutoInit = options._autoInit; + const { loadCallback } = options; + // eslint-disable-next-line @typescript-eslint/init-declarations + let currentThemeData; + + currentThemeName = resolveFullThemeName(options.theme || currentThemeName); + + if (currentThemeName) { + currentThemeData = knownThemes[currentThemeName]; + } + + if (loadCallback) { + themeReadyCallback.add(loadCallback); + } + + if (currentThemeData) { + $activeThemeLink.attr('href', knownThemes[currentThemeName].url); + // @ts-expect-error ts-error + if (themeReadyCallback.has() || initDeferred.state() !== 'resolved' || options._forceTimeout) { + waitForThemeLoad(currentThemeName); + } + } else if (isAutoInit) { + if (hasWindow()) { + waitForThemeLoad(ANY_THEME); + } -function _attachCssClasses(element, themeName) { - themeClasses = getCssClasses(themeName).join(' '); - $(element).addClass(themeClasses); - - const activateHairlines = function() { - const pixelRatio = hasWindow() && window.devicePixelRatio; - - if(!pixelRatio || pixelRatio < 2) { - return; - } - - const $tester = $('
'); - $tester.css('border', '.5px solid transparent'); - $('body').append($tester); - if(getOuterHeight($tester) === 1) { - $(element).addClass(DX_HAIRLINES_CLASS); - themeClasses += ' ' + DX_HAIRLINES_CLASS; - } - $tester.remove(); - }; + themeReadyCallback.fire(); + themeReadyCallback.empty(); + } else { + throw errors.Error('E0021', currentThemeName); + } - activateHairlines(); + initDeferred.done((): void => attachCssClasses(originalViewPort(), currentThemeName)); } -export function attachCssClasses(element, themeName) { - when(uiLayerInitialized).done(() => { - _attachCssClasses(element, themeName); - }); -} +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function init(options): void { + // eslint-disable-next-line no-param-reassign + options = options || {}; + initContext(options.context || domAdapter.getDocument()); -export function detachCssClasses(element) { - when(uiLayerInitialized).done(() => { - $(element).removeClass(themeClasses); - }); + if (!context) return; + processMarkup(); + currentThemeName = undefined; + current(options); } -function themeReady(callback) { - themeReadyCallback.add(callback); -} +function isTheme(themeRegExp: string, themeName: string): boolean { + if (!themeName) { + // eslint-disable-next-line no-param-reassign + themeName = currentThemeName || readThemeMarker(); + } -function isTheme(themeRegExp, themeName) { - if(!themeName) { - themeName = currentThemeName || readThemeMarker(); - } + return new RegExp(themeRegExp).test(themeName); +} - return new RegExp(themeRegExp).test(themeName); +export function isMaterial(themeName: string): boolean { + return isTheme('material', themeName); } -export function isMaterialBased(themeName) { - return isMaterial(themeName) || isFluent(themeName); +export function isFluent(themeName: string): boolean { + return isTheme('fluent', themeName); } -export function isMaterial(themeName) { - return isTheme('material', themeName); +export function isMaterialBased(themeName: string): boolean { + return isMaterial(themeName) || isFluent(themeName); } -export function isFluent(themeName) { - return isTheme('fluent', themeName); +export function isGeneric(themeName: string): boolean { + return isTheme('generic', themeName); } -export function isGeneric(themeName) { - return isTheme('generic', themeName); +export function isDark(themeName: string): boolean { + return isTheme('dark', themeName); } -export function isDark(themeName) { - return isTheme('dark', themeName); +export function isCompact(themeName: string): boolean { + return isTheme('compact', themeName); } -export function isCompact(themeName) { - return isTheme('compact', themeName); +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +function themeReady(callback): void { + themeReadyCallback.add(callback); } -export function isWebFontLoaded(text, fontWeight) { - const testedFont = 'roboto, \'roboto fallback\', arial'; - const etalonFont = 'arial'; +export function isWebFontLoaded(text: string, fontWeight: string): boolean { + const testedFont = 'roboto, \'roboto fallback\', arial'; + const etalonFont = 'arial'; - const document = domAdapter.getDocument(); - const testElement = document.createElement('span'); + const document = domAdapter.getDocument(); + const testElement = document.createElement('span'); - testElement.style.position = 'absolute'; - testElement.style.top = '-9999px'; - testElement.style.left = '-9999px'; - testElement.style.visibility = 'hidden'; - testElement.style.fontFamily = etalonFont; - testElement.style.fontSize = '250px'; - testElement.style.fontWeight = fontWeight; - testElement.innerHTML = text; + testElement.style.position = 'absolute'; + testElement.style.top = '-9999px'; + testElement.style.left = '-9999px'; + testElement.style.visibility = 'hidden'; + testElement.style.fontFamily = etalonFont; + testElement.style.fontSize = '250px'; + testElement.style.fontWeight = fontWeight; + testElement.innerHTML = text; - document.body.appendChild(testElement); + document.body.appendChild(testElement); - const etalonFontWidth = testElement.offsetWidth; - testElement.style.fontFamily = testedFont; - const testedFontWidth = testElement.offsetWidth; + const etalonFontWidth = testElement.offsetWidth; + testElement.style.fontFamily = testedFont; + const testedFontWidth = testElement.offsetWidth; - testElement.parentNode.removeChild(testElement); + testElement.parentNode?.removeChild(testElement); - return etalonFontWidth !== testedFontWidth; + return etalonFontWidth !== testedFontWidth; } -export function waitWebFont(text, fontWeight) { - const interval = 15; - const timeout = 2000; - - return new Promise(resolve => { - const check = () => { - if(isWebFontLoaded(text, fontWeight)) { - clear(); - } - }; - - const clear = () => { - clearInterval(intervalId); - clearTimeout(timeoutId); - resolve(); - }; - - const intervalId = setInterval(check, interval); - const timeoutId = setTimeout(clear, timeout); - }); +export function waitWebFont(text: string, fontWeight: string): Promise { + const interval = 15; + const timeout = 2000; + + return new Promise((resolve) => { + const clear = (): void => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + clearInterval(intervalId); + // eslint-disable-next-line @typescript-eslint/no-use-before-define + clearTimeout(timeoutId); + // @ts-expect-error ts-error + resolve(); + }; + + const check = (): void => { + if (isWebFontLoaded(text, fontWeight)) { + clear(); + } + }; + + // eslint-disable-next-line no-restricted-globals + const intervalId = setInterval(check, interval); + // eslint-disable-next-line no-restricted-globals + const timeoutId = setTimeout(clear, timeout); + }); } -function autoInit() { - init({ - _autoInit: true, - _forceTimeout: true - }); +function autoInit(): void { + init({ + _autoInit: true, + _forceTimeout: true, + }); - if($(DX_LINK_SELECTOR, context).length) { - throw errors.Error('E0022'); - } + // @ts-expect-error ts-error + if ($(DX_LINK_SELECTOR, context).length) { + throw errors.Error('E0022'); + } } -if(hasWindow()) { - autoInit(); +if (hasWindow()) { + autoInit(); } else { - ready(autoInit); + ready(autoInit); } -viewPortChanged.add(function(viewPort, prevViewPort) { - initDeferred.done(function() { - detachCssClasses(prevViewPort); - attachCssClasses(viewPort); - }); +// eslint-disable-next-line @typescript-eslint/no-shadow +viewPortChanged.add((viewPort, prevViewPort): void => { + initDeferred.done((): void => { + detachCssClasses(prevViewPort); + attachCssClasses(viewPort); + }); }); -devices.changed.add(function() { - init({ _autoInit: true }); +// @ts-expect-error ts-error +devices.changed.add((): void => { + init({ _autoInit: true }); }); export { - themeReady as ready, + themeReady as ready, }; -export function resetTheme() { - $activeThemeLink && $activeThemeLink.attr('href', 'about:blank'); - currentThemeName = null; - pendingThemeName = null; - initDeferred = new Deferred(); +export function resetTheme(): void { + $activeThemeLink?.attr('href', 'about:blank'); + currentThemeName = null; + pendingThemeName = null; + // @ts-expect-error ts-error + initDeferred = new Deferred(); } -export function initialized(callback) { - initDeferred.done(callback); +export function initialized(callback: Function): void { + initDeferred.done(callback); } -export function setDefaultTimeout(timeout) { - defaultTimeout = timeout; +export function setDefaultTimeout(timeout: number): void { + defaultTimeout = timeout; } /** @@ -453,21 +495,22 @@ export function setDefaultTimeout(timeout) { * https://js.devexpress.com/Documentation/ApiReference/Common/Utils/ui/themes/ * */ export default { - setDefaultTimeout, - initialized, - resetTheme, - ready: themeReady, - waitWebFont, - isWebFontLoaded, - isCompact, - isDark, - isGeneric, - isMaterial, - isFluent, - isMaterialBased, - detachCssClasses, - attachCssClasses, - current, - waitForThemeLoad, - isPendingThemeLoaded, + setDefaultTimeout, + init, + initialized, + resetTheme, + ready: themeReady, + waitWebFont, + isWebFontLoaded, + isCompact, + isDark, + isGeneric, + isMaterial, + isFluent, + isMaterialBased, + detachCssClasses, + attachCssClasses, + current, + waitForThemeLoad, + isPendingThemeLoaded, }; diff --git a/packages/devextreme/js/ui/shared/accessibility.js b/packages/devextreme/js/ui/shared/accessibility.js new file mode 100644 index 000000000000..e8aecab57ed2 --- /dev/null +++ b/packages/devextreme/js/ui/shared/accessibility.js @@ -0,0 +1,21 @@ +import { + saveFocusedElementInfo, + subscribeVisibilityChange, + unsubscribeVisibilityChange, + hiddenFocus, + registerKeyboardAction, + restoreFocus, + selectView, + setTabIndex, +} from '../../__internal/ui/shared/accessibility'; + +export { + saveFocusedElementInfo, + subscribeVisibilityChange, + unsubscribeVisibilityChange, + hiddenFocus, + registerKeyboardAction, + restoreFocus, + selectView, + setTabIndex, +}; diff --git a/packages/devextreme/js/ui/shared/filtering.js b/packages/devextreme/js/ui/shared/filtering.js new file mode 100644 index 000000000000..cd159cf65e49 --- /dev/null +++ b/packages/devextreme/js/ui/shared/filtering.js @@ -0,0 +1,2 @@ +export { default } from '../../__internal/ui/shared/filtering'; + diff --git a/packages/devextreme/js/ui/shared/ui.editor_factory_mixin.js b/packages/devextreme/js/ui/shared/ui.editor_factory_mixin.js new file mode 100644 index 000000000000..c917cee78f29 --- /dev/null +++ b/packages/devextreme/js/ui/shared/ui.editor_factory_mixin.js @@ -0,0 +1,3 @@ +import EditorFactoryMixin from '../../__internal/ui/shared/ui.editor_factory_mixin'; + +export default EditorFactoryMixin; diff --git a/packages/devextreme/js/ui/splitter_control.js b/packages/devextreme/js/ui/splitter_control.js new file mode 100644 index 000000000000..f1fce35534a4 --- /dev/null +++ b/packages/devextreme/js/ui/splitter_control.js @@ -0,0 +1,3 @@ +import SplitterControl from '../__internal/ui/splitter_control'; + +export default SplitterControl; diff --git a/packages/devextreme/js/ui/themes.js b/packages/devextreme/js/ui/themes.js new file mode 100644 index 000000000000..7ba25bcc98a7 --- /dev/null +++ b/packages/devextreme/js/ui/themes.js @@ -0,0 +1,24 @@ +import themesDefault from '../__internal/ui/themes'; + +export const { + setDefaultTimeout, + init, + initialized, + resetTheme, + ready, + waitWebFont, + isWebFontLoaded, + isCompact, + isDark, + isGeneric, + isMaterial, + isFluent, + isMaterialBased, + detachCssClasses, + attachCssClasses, + current, + waitForThemeLoad, + isPendingThemeLoaded, +} = themesDefault; + +export default themesDefault;