Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
**What's Fixed**
* [DropdownContainer]: fixed `autoFocus` always being `true` even when `false` is passed through params
* [RTE]: fixed incorrect floating toolbar position when in shadow DOM ([#3073](https://github.com/epam/UUI/issues/3073))
* Fixed multiple RTL layout issues across components ([#2548](https://github.com/epam/UUI/issues/2548)):
* [Slider], [RangeSlider]: fixed RTL layout and interaction (track fill, handle, scale marks, pointer position, arrow keys).
* [PopoverArrow]: fixed popover/tooltip arrow chevron alignment in RTL.
* [IndeterminateBar]: fixed progress bar animation direction in RTL.
* [Typography]: fixed list bullet alignment in RTL.
* [Accordion]: fixed chevron icon position in RTL.
* [RangeDatePickerBody]: fixed separator direction in RTL.
* [DataRowAddons]: fixed drag handle position in RTL.
* [PickerToggler]: fixed search input text direction in RTL.


# 6.4.4 - 01.04.2026
Expand Down
2 changes: 1 addition & 1 deletion epam-assets/scss/loveship/typography.scss
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
}

li {
margin-left: 1.25em;
margin-inline-start: 1.25em;
line-height: 1.5;
}

Expand Down
2 changes: 1 addition & 1 deletion epam-assets/scss/promo/typography.scss
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
}

li {
margin-left: 1.25em;
margin-inline-start: 1.25em;
line-height: 1.5;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ exports[`RangeSlider should be rendered correctly 1`] = `
/>
<div
class="uui-slider-filled"
style="width: 0px; left: 0px;"
style="width: 0px; inset-inline-start: 0;"
/>
<div
class="uui-slider-scale"
Expand Down
7 changes: 5 additions & 2 deletions uui-components/src/inputs/Slider/RangeSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export class RangeSlider extends SliderBase<RangeSliderValue, RangeSliderState>

const fromHandleOffset = (normValueFrom - this.props.min) * valueWidth;
const toHandleOffset = (normValueTo - this.props.min) * valueWidth;
const low = Math.min(normValueFrom, normValueTo);
const high = Math.max(normValueFrom, normValueTo);
const rangeBarWidth = (high - low) * valueWidth;

return (
<div
Expand All @@ -109,8 +112,8 @@ export class RangeSlider extends SliderBase<RangeSliderValue, RangeSliderState>
<div
className={ uuiSlider.filled }
style={ {
width: (normValueFrom < normValueTo ? normValueTo - normValueFrom : normValueFrom - normValueTo) * valueWidth,
left: (normValueFrom < normValueTo ? normValueFrom - this.props.min : normValueTo - this.props.min) * valueWidth,
width: rangeBarWidth,
insetInlineStart: (low - this.props.min) * valueWidth,
} }
/>
<RangeSliderScale
Expand Down
5 changes: 2 additions & 3 deletions uui-components/src/inputs/Slider/RangeSliderScale.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import { SliderScaleElement } from './SliderScaleElement';
import { SliderScaleBase } from './SliderScaleBase';
import { isClientSide } from '@epam/uui-core';
import { SliderScaleBase, getSliderTrackMarginInlineStart } from './SliderScaleBase';

interface HandleOffsetValue {
from: number;
Expand All @@ -14,7 +13,7 @@ export class RangeSliderScale extends SliderScaleBase<HandleOffsetValue> {
const sliderWidth = this.props.slider && this.props.slider.offsetWidth;
return this.generateScale(splitAt).map((value) => {
const offset = (value - this.props.min) * this.props.valueWidth;
const sliderMargin = isClientSide && this.props.slider && +window.getComputedStyle(this.props.slider).marginLeft.slice(0, -2);
const sliderMargin = getSliderTrackMarginInlineStart(this.props.slider);
return (
<SliderScaleElement
key={ value }
Expand Down
6 changes: 3 additions & 3 deletions uui-components/src/inputs/Slider/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ export class Slider extends SliderBase<number, any> {

handleKeyDownUpdate(type: 'left' | 'right') {
const { value, step, min, max } = this.props;
const floatPrecision = this.getFloatPrecision(step);
const factor = 10 ** this.getFloatPrecision(step);

if (type === 'left') {
const newValue = ((value * floatPrecision - step * floatPrecision) / floatPrecision);
const newValue = (value * factor - step * factor) / factor;
if (newValue < min) return;
else this.props.onValueChange(newValue);
} else if (type === 'right') {
const newValue = ((value * floatPrecision + step * floatPrecision) / floatPrecision);
const newValue = (value * factor + step * factor) / factor;
if (newValue > max) return;
this.props.onValueChange(newValue);
}
Expand Down
5 changes: 4 additions & 1 deletion uui-components/src/inputs/Slider/SliderBase.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

:global(.uui-slider-handle) {
position: absolute;
left: 0;
inset-inline-start: 0;

&:focus {
outline-offset: 1px;
Expand All @@ -23,6 +23,7 @@

:global(.uui-slider-filled) {
position: absolute;
inset-inline-start: 0;
pointer-events: none;
}

Expand All @@ -32,10 +33,12 @@

:global(.uui-slider-scale-number) {
position: absolute;
inset-inline-start: 0;
}

:global(.uui-slider-scale-dot) {
position: absolute;
inset-inline-start: 0;
}
}
}
Expand Down
24 changes: 19 additions & 5 deletions uui-components/src/inputs/Slider/SliderBase.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import {
IHasCX, IEditable, IDisableable, IHasRawProps, IHasForwardedRef,
IHasCX, IEditable, IDisableable, IHasRawProps, IHasForwardedRef, getDir,
} from '@epam/uui-core';

export interface SliderBaseProps<TSelection>
Expand Down Expand Up @@ -90,12 +90,26 @@ export abstract class SliderBase<TSelection, TState extends SliderBaseState> ext
};

getValue = (mouseX: number, valueWidth?: number) => {
if (mouseX < this.slider.getBoundingClientRect().left) {
const rect = this.slider.getBoundingClientRect();

if (this.isRtl()) {
if (mouseX > rect.right) {
return this.props.min;
}
if (mouseX < rect.left) {
return this.props.max;
}
return this.roundToStep((rect.right - mouseX) / valueWidth + this.props.min, this.props.step);
}

if (mouseX < rect.left) {
return this.props.min;
} else if (mouseX > this.slider.getBoundingClientRect().right) {
}
if (mouseX > rect.right) {
return this.props.max;
} else {
return this.roundToStep((mouseX - this.slider.getBoundingClientRect().left) / valueWidth + this.props.min, this.props.step);
}
return this.roundToStep((mouseX - rect.left) / valueWidth + this.props.min, this.props.step);
};

isRtl = () => getDir() === 'rtl';
}
12 changes: 8 additions & 4 deletions uui-components/src/inputs/Slider/SliderHandle.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { IHasCX, cx, IHasRawProps } from '@epam/uui-core';
import { IHasCX, cx, IHasRawProps, useDocumentDir } from '@epam/uui-core';
import css from './SliderHandle.module.scss';
import { useFloating, arrow, autoUpdate, offset } from '@floating-ui/react';
import { DropdownContainer, Portal } from '../../overlays';
Expand Down Expand Up @@ -32,6 +32,10 @@ export const SliderHandle: React.FC<SliderHandleProps> = (props) => {
const sliderHandleRef = React.useRef<HTMLDivElement | null>(null);
const arrowRef = React.useRef<HTMLDivElement | null>(null);
const updateTooltipRafRef = React.useRef<number | null>(null);
const dir = useDocumentDir();

const isRtl = dir === 'rtl';
const handleOffsetSign = isRtl ? -1 : 1;

const { refs, floatingStyles, placement, middlewareData, update } = useFloating({
placement: 'top',
Expand Down Expand Up @@ -108,9 +112,9 @@ export const SliderHandle: React.FC<SliderHandleProps> = (props) => {

const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>): void => {
if (e.key === 'ArrowLeft') {
onKeyDownUpdate?.('left');
onKeyDownUpdate?.(isRtl ? 'right' : 'left');
} else if (e.key === 'ArrowRight') {
onKeyDownUpdate?.('right');
onKeyDownUpdate?.(isRtl ? 'left' : 'right');
}
};

Expand Down Expand Up @@ -162,7 +166,7 @@ export const SliderHandle: React.FC<SliderHandleProps> = (props) => {
tabIndex={ 0 }
ref={ setRefs }
className={ cx(uuiSlider.handle, propsCx) }
style={ { transform: `translateX(${handleOffset || 0}px)` } }
style={ { transform: `translateX(${handleOffsetSign * (handleOffset || 0)}px)` } }
onMouseDown={ handleMouseDown }
onKeyDown={ handleKeyDown }
onFocus={ handleFocus }
Expand Down
5 changes: 2 additions & 3 deletions uui-components/src/inputs/Slider/SliderScale.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import * as React from 'react';
import { SliderScaleBase, getSliderTrackMarginInlineStart } from './SliderScaleBase';
import { SliderScaleElement } from './SliderScaleElement';
import { SliderScaleBase } from './SliderScaleBase';
import { isClientSide } from '@epam/uui-core';

export class SliderScale extends SliderScaleBase<number> {
renderSliderScaleElements() {
const splitAt = this.props.splitAt || this.props.max - this.props.min;
const sliderWidth = this.props.slider?.offsetWidth;
return this.generateScale(splitAt).map((value, index) => {
const offset = (value - this.props.min) * this.props.valueWidth;
const sliderMargin = isClientSide && this.props.slider && +window.getComputedStyle(this.props.slider).marginLeft.slice(0, -2);
const sliderMargin = getSliderTrackMarginInlineStart(this.props.slider);
return (
<SliderScaleElement
key={ index }
Expand Down
10 changes: 10 additions & 0 deletions uui-components/src/inputs/Slider/SliderScaleBase.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import * as React from 'react';
import { isClientSide } from '@epam/uui-core';
import { SliderScaleElement } from './SliderScaleElement';
import { uuiSlider } from './SliderBase';

export function getSliderTrackMarginInlineStart(slider: HTMLElement | null): number {
if (!isClientSide || !slider) {
return 0;
}
const raw = window.getComputedStyle(slider).marginInlineStart;
const parsed = parseFloat(raw);
return Number.isFinite(parsed) ? parsed : 0;
}

interface SliderScaleProps<THandleOffsetValue> {
min: number;
max: number;
Expand Down
23 changes: 15 additions & 8 deletions uui-components/src/inputs/Slider/SliderScaleElement.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { cx } from '@epam/uui-core';
import { cx, getDir } from '@epam/uui-core';
import { uuiSlider } from './SliderBase';

interface SliderScaleElementProps {
Expand All @@ -26,17 +26,24 @@ export class SliderScaleElement extends React.Component<SliderScaleElementProps,
}

calculateLabelPosition = () => {
if (this.props.offset === 0) {
const { offset, sliderWidth, sliderMargin } = this.props;
const scaleNumberWidth = this.scaleNumber ? this.state.scaleNumberWidth ?? 0 : 0;

if (offset === 0) {
return 0;
}
if (this.props.sliderWidth === parseInt(`${this.props.offset}`, 10)) {
return this.props.offset - Math.ceil(this.scaleNumber ? this.state.scaleNumberWidth : 0) + 2 * this.props.sliderMargin;
if (Math.abs(sliderWidth - offset) < 1) {
return offset - Math.ceil(scaleNumberWidth) + 2 * sliderMargin;
}
return this.props.offset + this.props.sliderMargin - Math.ceil(this.scaleNumber ? this.state.scaleNumberWidth / 2 : 0);
return offset + sliderMargin - Math.ceil(scaleNumberWidth / 2);
};

render() {
const dotOffset = this.props.offset + this.props.sliderMargin - (this.scaleDot ? this.state.scaleDotWidth / 2 : 0);
const { offset, sliderMargin } = this.props;
const isRtl = getDir() === 'rtl';
const sign = isRtl ? -1 : 1;

const dotOffset = offset + sliderMargin - (this.scaleDot ? (this.state.scaleDotWidth ?? 0) / 2 : 0);
const numberOffset = this.calculateLabelPosition();

return (
Expand All @@ -46,14 +53,14 @@ export class SliderScaleElement extends React.Component<SliderScaleElementProps,
ref={ (scaleDotRef) => {
(this.scaleDot = scaleDotRef);
} }
style={ { transform: `translateX(${dotOffset}px)` } }
style={ { transform: `translateX(${sign * dotOffset}px)` } }
/>
<div
className={ uuiSlider.scaleNumber }
ref={ (scaleNumberRef) => {
(this.scaleNumber = scaleNumberRef);
} }
style={ { transform: `translateX(${numberOffset}px)` } }
style={ { transform: `translateX(${sign * numberOffset}px)` } }
>
{this.props.label}
</div>
Expand Down
2 changes: 1 addition & 1 deletion uui-components/src/layout/Accordion.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
}

.arrow {
margin-left: auto;
margin-inline-start: auto;
}
16 changes: 16 additions & 0 deletions uui-components/src/overlays/PopoverArrow.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
// calc function - arrow size correction
transform: translateX(calc(100% - 1px)) translateY(20%) rotate(45deg);
}

&:dir(rtl)::after {
left: 0;
}
}
}

Expand All @@ -22,6 +26,10 @@
&::after {
transform: translateX(calc(-50% + 1px)) translateY(20%) rotate(45deg);
}

&:dir(rtl)::after {
left: 0;
}
}
}

Expand All @@ -33,6 +41,10 @@
&::after {
transform: translateX(30%) translateY(calc(-50% + 1px)) rotate(45deg);
}

&:dir(rtl)::after {
transform: translateX(-30%) translateY(calc(-50% + 1px)) rotate(45deg);
}
}
}

Expand All @@ -44,5 +56,9 @@
&::after {
transform: translateX(30%) translateY(calc(100% - 1px)) rotate(45deg);
}

&:dir(rtl)::after {
transform: translateX(-30%) translateY(calc(100% - 1px)) rotate(45deg);
}
}
}
1 change: 1 addition & 0 deletions uui-components/src/pickers/PickerToggler.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
background: none;
cursor: pointer;
text-overflow: ellipsis;
direction: inherit;

&[type='search']::-webkit-search-decoration,
&[type='search']::-webkit-search-cancel-button,
Expand Down
2 changes: 1 addition & 1 deletion uui/assets/styles/typography.scss
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
}

li {
margin-left: 1.25em;
margin-inline-start: 1.25em;
line-height: 1.5;
}

Expand Down
Loading
Loading