Skip to content

Commit 2febdad

Browse files
Toolbar: use transform-safe measurements for layout calculations (T1245421) (#33252)
1 parent 75ae87e commit 2febdad

File tree

2 files changed

+60
-24
lines changed

2 files changed

+60
-24
lines changed

packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { dxElementWrapper } from '@js/core/renderer';
55
import $ from '@js/core/renderer';
66
import { BindableTemplate } from '@js/core/templates/bindable_template';
77
import { each } from '@js/core/utils/iterator';
8-
import { getBoundingRect } from '@js/core/utils/position';
98
import { getHeight, getOuterWidth, getWidth } from '@js/core/utils/size';
109
import { isDefined, isPlainObject } from '@js/core/utils/type';
1110
import {
@@ -47,8 +46,8 @@ export interface ToolbarBaseProperties<
4746
TKey extends CollectionItemKey = CollectionItemKey,
4847
> extends Properties<TItem, TKey>,
4948
Omit<
50-
CollectionWidgetBaseProperties<ToolbarBase, TItem, TKey>,
51-
keyof Properties<TItem, TKey> & keyof CollectionWidgetBaseProperties<ToolbarBase, TItem, TKey>
49+
CollectionWidgetBaseProperties<ToolbarBase, TItem, TKey>,
50+
keyof Properties<TItem, TKey> & keyof CollectionWidgetBaseProperties<ToolbarBase, TItem, TKey>
5251
> {
5352
grouped: boolean;
5453
renderAs: 'topToolbar';
@@ -68,7 +67,6 @@ class ToolbarBase<
6867

6968
_$afterSection!: dxElementWrapper;
7069

71-
// eslint-disable-next-line no-restricted-globals
7270
_waitParentAnimationTimeout?: ReturnType<typeof setTimeout>;
7371

7472
_getSynchronizableOptionsForCreateComponent(): (keyof TProperties)[] {
@@ -230,10 +228,10 @@ class ToolbarBase<
230228
float: 'none',
231229
});
232230

233-
const beforeRect = getBoundingRect(this._$beforeSection?.get(0));
234-
const afterRect = getBoundingRect(this._$afterSection?.get(0));
231+
const beforeWidth = getOuterWidth(this._$beforeSection?.get(0)) ?? 0;
232+
const afterWidth = getOuterWidth(this._$afterSection?.get(0)) ?? 0;
235233

236-
this._alignCenterSection(beforeRect, afterRect, elementWidth);
234+
this._alignCenterSection(beforeWidth, afterWidth, elementWidth);
237235

238236
const $label = this._$toolbarItemsContainer.find(`.${TOOLBAR_LABEL_CLASS}`).eq(0);
239237
const $section: dxElementWrapper = $label.parent();
@@ -242,9 +240,9 @@ class ToolbarBase<
242240
return;
243241
}
244242

245-
const labelOffset = beforeRect.width ? beforeRect.width : $label.position()?.left;
243+
const labelOffset = beforeWidth ?? $label.position()?.left;
246244
const widthBeforeSection = $section.hasClass(TOOLBAR_BEFORE_CLASS) ? 0 : labelOffset;
247-
const widthAfterSection = $section.hasClass(TOOLBAR_AFTER_CLASS) ? 0 : afterRect.width;
245+
const widthAfterSection = $section.hasClass(TOOLBAR_AFTER_CLASS) ? 0 : afterWidth;
248246
let elemsAtSectionWidth = 0;
249247

250248
// @ts-expect-error ts error
@@ -265,27 +263,35 @@ class ToolbarBase<
265263
}
266264
}
267265

268-
_alignCenterSection(
269-
beforeRect: DOMRect,
270-
afterRect: DOMRect,
271-
elementWidth: number,
272-
): void {
266+
_alignCenterSection(beforeWidth: number, afterWidth: number, elementWidth: number): void {
273267
if (!this._$centerSection) {
274268
return;
275269
}
276270

277-
this._alignSection(this._$centerSection, elementWidth - beforeRect.width - afterRect.width);
271+
this._alignSection(this._$centerSection, elementWidth - beforeWidth - afterWidth);
278272

279273
const isRTL = this.option('rtlEnabled');
280-
const leftRect = isRTL ? afterRect : beforeRect;
281-
const rightRect = isRTL ? beforeRect : afterRect;
282-
const centerRect = getBoundingRect(this._$centerSection.get(0));
283-
284-
if (leftRect.right > centerRect.left || centerRect.right > rightRect.left) {
274+
const leftWidth = isRTL ? afterWidth : beforeWidth;
275+
const rightWidth = isRTL ? beforeWidth : afterWidth;
276+
277+
const centerEl = this._$centerSection.get(0) as HTMLElement;
278+
const centerLeft = centerEl.offsetLeft;
279+
const centerRight = centerLeft + centerEl.offsetWidth;
280+
281+
const beforeEl = this._$beforeSection?.get(0) as HTMLElement | undefined;
282+
const afterEl = this._$afterSection?.get(0) as HTMLElement | undefined;
283+
const leftSectionRight = afterEl ? afterEl.offsetLeft + afterEl.offsetWidth : 0;
284+
const leftSectionRightLTR = beforeEl ? beforeEl.offsetLeft + beforeEl.offsetWidth : 0;
285+
const leftRight = isRTL ? leftSectionRight : leftSectionRightLTR;
286+
const rightLeft = isRTL
287+
? beforeEl?.offsetLeft ?? elementWidth
288+
: afterEl?.offsetLeft ?? elementWidth;
289+
290+
if (leftRight > centerLeft || centerRight > rightLeft) {
285291
this._$centerSection.css({
286-
marginLeft: leftRect.width,
287-
marginRight: rightRect.width,
288-
float: leftRect.width > rightRect.width ? 'none' : 'right',
292+
marginLeft: leftWidth,
293+
marginRight: rightWidth,
294+
float: leftWidth > rightWidth ? 'none' : 'right',
289295
});
290296
}
291297
}
@@ -312,7 +318,8 @@ class ToolbarBase<
312318
difference: number,
313319
expanding: boolean,
314320
): void {
315-
const getRealLabelWidth = (label: Element): number => (getBoundingRect(label) as DOMRect).width;
321+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
322+
const getRealLabelWidth = (label: Element): number => getOuterWidth(label) ?? 0;
316323

317324
// eslint-disable-next-line @typescript-eslint/prefer-for-of, no-plusplus
318325
for (let i = 0; i < labels.length; i++) {

packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.tests.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,35 @@ QUnit.module('render', {
104104
assert.ok(labelWidth <= labelMaxWidth, 'Real label width less or equal to the max width');
105105
});
106106

107+
QUnit.test('label max-width should be calculated correctly when parent has CSS transform scale (T1245421)', function(assert) {
108+
const $container = $('<div>').appendTo('#qunit-fixture').css('width', '400px');
109+
const $element = $('<div>').appendTo($container);
110+
111+
$element.dxToolbar({
112+
items: [
113+
{ location: 'before', text: 'Very long toolbar label text that should be truncated' },
114+
{ location: 'after', widget: 'dxButton', options: { text: 'Action' } }
115+
]
116+
});
117+
118+
const toolbar = $element.dxToolbar('instance');
119+
const $label = $element.find(`.${TOOLBAR_LABEL_CLASS}`).eq(0);
120+
const maxWidthBefore = parseFloat($label.css('max-width'));
121+
122+
$container.css('transform', 'scale(0.5)');
123+
toolbar._dimensionChanged();
124+
const maxWidthDuringScale = parseFloat($label.css('max-width'));
125+
126+
$container.css('transform', '');
127+
toolbar._dimensionChanged();
128+
const maxWidthAfter = parseFloat($label.css('max-width'));
129+
130+
assert.roughEqual(maxWidthDuringScale, maxWidthBefore, 1, 'max-width is not affected by CSS transform scale');
131+
assert.roughEqual(maxWidthAfter, maxWidthBefore, 1, 'max-width is restored correctly after transform is removed');
132+
133+
$container.remove();
134+
});
135+
107136
QUnit.test('items - long labels', function(assert) {
108137
this.$element.dxToolbar({
109138
items: [

0 commit comments

Comments
 (0)