From 84d8e59ea9af496f98c3af6ecf00c46ef05f9221 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Mon, 13 Apr 2026 15:30:20 +0400 Subject: [PATCH 1/2] Toolbar: use transform-safe measurements for layout calculations (T1245421) --- .../js/__internal/ui/toolbar/toolbar.base.ts | 51 +++++++++++-------- .../DevExpress.ui.widgets/toolbar.tests.js | 29 +++++++++++ 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts b/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts index 2b085950bb19..896d6b40b0a9 100644 --- a/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts +++ b/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts @@ -5,7 +5,6 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { BindableTemplate } from '@js/core/templates/bindable_template'; import { each } from '@js/core/utils/iterator'; -import { getBoundingRect } from '@js/core/utils/position'; import { getHeight, getOuterWidth, getWidth } from '@js/core/utils/size'; import { isDefined, isPlainObject } from '@js/core/utils/type'; import { @@ -68,7 +67,6 @@ class ToolbarBase< _$afterSection!: dxElementWrapper; - // eslint-disable-next-line no-restricted-globals _waitParentAnimationTimeout?: ReturnType; _getSynchronizableOptionsForCreateComponent(): (keyof TProperties)[] { @@ -230,10 +228,10 @@ class ToolbarBase< float: 'none', }); - const beforeRect = getBoundingRect(this._$beforeSection?.get(0)); - const afterRect = getBoundingRect(this._$afterSection?.get(0)); + const beforeWidth = getOuterWidth(this._$beforeSection?.get(0)) ?? 0; + const afterWidth = getOuterWidth(this._$afterSection?.get(0)) ?? 0; - this._alignCenterSection(beforeRect, afterRect, elementWidth); + this._alignCenterSection(beforeWidth, afterWidth, elementWidth); const $label = this._$toolbarItemsContainer.find(`.${TOOLBAR_LABEL_CLASS}`).eq(0); const $section: dxElementWrapper = $label.parent(); @@ -242,9 +240,9 @@ class ToolbarBase< return; } - const labelOffset = beforeRect.width ? beforeRect.width : $label.position()?.left; + const labelOffset = beforeWidth ?? $label.position()?.left; const widthBeforeSection = $section.hasClass(TOOLBAR_BEFORE_CLASS) ? 0 : labelOffset; - const widthAfterSection = $section.hasClass(TOOLBAR_AFTER_CLASS) ? 0 : afterRect.width; + const widthAfterSection = $section.hasClass(TOOLBAR_AFTER_CLASS) ? 0 : afterWidth; let elemsAtSectionWidth = 0; // @ts-expect-error ts error @@ -265,27 +263,35 @@ class ToolbarBase< } } - _alignCenterSection( - beforeRect: DOMRect, - afterRect: DOMRect, - elementWidth: number, - ): void { + _alignCenterSection(beforeWidth: number, afterWidth: number, elementWidth: number): void { if (!this._$centerSection) { return; } - this._alignSection(this._$centerSection, elementWidth - beforeRect.width - afterRect.width); + this._alignSection(this._$centerSection, elementWidth - beforeWidth - afterWidth); const isRTL = this.option('rtlEnabled'); - const leftRect = isRTL ? afterRect : beforeRect; - const rightRect = isRTL ? beforeRect : afterRect; - const centerRect = getBoundingRect(this._$centerSection.get(0)); - - if (leftRect.right > centerRect.left || centerRect.right > rightRect.left) { + const leftWidth = isRTL ? afterWidth : beforeWidth; + const rightWidth = isRTL ? beforeWidth : afterWidth; + + const centerEl = this._$centerSection.get(0) as HTMLElement; + const centerLeft = centerEl.offsetLeft; + const centerRight = centerLeft + centerEl.offsetWidth; + + const beforeEl = this._$beforeSection?.get(0) as HTMLElement | undefined; + const afterEl = this._$afterSection?.get(0) as HTMLElement | undefined; + const leftSectionRight = afterEl ? afterEl.offsetLeft + afterEl.offsetWidth : 0; + const leftSectionRightLTR = beforeEl ? beforeEl.offsetLeft + beforeEl.offsetWidth : 0; + const leftRight = isRTL ? leftSectionRight : leftSectionRightLTR; + const rightLeft = isRTL + ? (beforeEl?.offsetLeft ?? elementWidth) + : (afterEl?.offsetLeft ?? elementWidth); + + if (leftRight > centerLeft || centerRight > rightLeft) { this._$centerSection.css({ - marginLeft: leftRect.width, - marginRight: rightRect.width, - float: leftRect.width > rightRect.width ? 'none' : 'right', + marginLeft: leftWidth, + marginRight: rightWidth, + float: leftWidth > rightWidth ? 'none' : 'right', }); } } @@ -312,7 +318,8 @@ class ToolbarBase< difference: number, expanding: boolean, ): void { - const getRealLabelWidth = (label: Element): number => (getBoundingRect(label) as DOMRect).width; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const getRealLabelWidth = (label: Element): number => getOuterWidth(label) ?? 0; // eslint-disable-next-line @typescript-eslint/prefer-for-of, no-plusplus for (let i = 0; i < labels.length; i++) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.tests.js index d6f4f945bee1..238e9731f6c7 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.tests.js @@ -104,6 +104,35 @@ QUnit.module('render', { assert.ok(labelWidth <= labelMaxWidth, 'Real label width less or equal to the max width'); }); + QUnit.test('label max-width should be calculated correctly when parent has CSS transform scale (T1245421)', function(assert) { + const $container = $('
').appendTo('#qunit-fixture').css('width', '400px'); + const $element = $('
').appendTo($container); + + $element.dxToolbar({ + items: [ + { location: 'before', text: 'Very long toolbar label text that should be truncated' }, + { location: 'after', widget: 'dxButton', options: { text: 'Action' } } + ] + }); + + const toolbar = $element.dxToolbar('instance'); + const $label = $element.find(`.${TOOLBAR_LABEL_CLASS}`).eq(0); + const maxWidthBefore = parseFloat($label.css('max-width')); + + $container.css('transform', 'scale(0.5)'); + toolbar._dimensionChanged(); + const maxWidthDuringScale = parseFloat($label.css('max-width')); + + $container.css('transform', ''); + toolbar._dimensionChanged(); + const maxWidthAfter = parseFloat($label.css('max-width')); + + assert.roughEqual(maxWidthDuringScale, maxWidthBefore, 1, 'max-width is not affected by CSS transform scale'); + assert.roughEqual(maxWidthAfter, maxWidthBefore, 1, 'max-width is restored correctly after transform is removed'); + + $container.remove(); + }); + QUnit.test('items - long labels', function(assert) { this.$element.dxToolbar({ items: [ From d063dadc64ac274a8a88262435d5f392ffc9fa28 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Tue, 14 Apr 2026 10:39:52 +0400 Subject: [PATCH 2/2] Update toolbar.base.ts --- .../devextreme/js/__internal/ui/toolbar/toolbar.base.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts b/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts index 896d6b40b0a9..f336c427c23b 100644 --- a/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts +++ b/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts @@ -46,8 +46,8 @@ export interface ToolbarBaseProperties< TKey extends CollectionItemKey = CollectionItemKey, > extends Properties, Omit< - CollectionWidgetBaseProperties, - keyof Properties & keyof CollectionWidgetBaseProperties + CollectionWidgetBaseProperties, + keyof Properties & keyof CollectionWidgetBaseProperties > { grouped: boolean; renderAs: 'topToolbar'; @@ -284,8 +284,8 @@ class ToolbarBase< const leftSectionRightLTR = beforeEl ? beforeEl.offsetLeft + beforeEl.offsetWidth : 0; const leftRight = isRTL ? leftSectionRight : leftSectionRightLTR; const rightLeft = isRTL - ? (beforeEl?.offsetLeft ?? elementWidth) - : (afterEl?.offsetLeft ?? elementWidth); + ? beforeEl?.offsetLeft ?? elementWidth + : afterEl?.offsetLeft ?? elementWidth; if (leftRight > centerLeft || centerRight > rightLeft) { this._$centerSection.css({