Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
4 changes: 2 additions & 2 deletions src/PickerInput/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
onSubmit,
} = props;

const { prefixCls } = React.useContext(PickerContext);
const { prefixCls, alignedPlacement } = React.useContext(PickerContext);
const panelPrefixCls = `${prefixCls}-panel`;

const rtl = direction === 'rtl';
Expand Down Expand Up @@ -213,7 +213,7 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
);

if (range) {
const realPlacement = getRealPlacement(placement, rtl);
const realPlacement = getRealPlacement(alignedPlacement || placement, rtl);
const offsetUnit = getoffsetUnit(realPlacement, rtl);
renderNode = (
<div
Expand Down
16 changes: 15 additions & 1 deletion src/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@ function RangePicker<DateType extends object = any>(
onKeyDown?.(event, preventDefault);
};

// ======================= popup align =======================
const [alignedPlacement, setAlignedPlacement] = React.useState<string>();

// ======================= Context ========================
const context = React.useMemo(
() => ({
Expand All @@ -674,8 +677,18 @@ function RangePicker<DateType extends object = any>(
generateConfig,
button: components.button,
input: components.input,
alignedPlacement,
setAlignedPlacement,
}),
[prefixCls, locale, generateConfig, components.button, components.input],
[
prefixCls,
locale,
generateConfig,
components.button,
components.input,
alignedPlacement,
setAlignedPlacement,
],
);

// ======================== Effect ========================
Expand Down Expand Up @@ -739,6 +752,7 @@ function RangePicker<DateType extends object = any>(
// Visible
visible={mergedOpen}
onClose={onPopupClose}
alignedPlacement={alignedPlacement}
// Range
range
>
Expand Down
12 changes: 6 additions & 6 deletions src/PickerInput/Selector/RangeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ function RangeSelector<DateType extends object = any>(
const rtl = direction === 'rtl';

// ======================== Prefix ========================
const { prefixCls } = React.useContext(PickerContext);
const { prefixCls, alignedPlacement } = React.useContext(PickerContext);

// ========================== Id ==========================
const ids = React.useMemo(() => {
Expand Down Expand Up @@ -173,7 +173,7 @@ function RangeSelector<DateType extends object = any>(
});

// ====================== ActiveBar =======================
const realPlacement = getRealPlacement(placement, rtl);
const realPlacement = getRealPlacement(alignedPlacement || placement, rtl);
const offsetUnit = getoffsetUnit(realPlacement, rtl);
const placementRight = realPlacement?.toLowerCase().endsWith('right');
const [activeBarStyle, setActiveBarStyle] = React.useState<React.CSSProperties>({
Expand All @@ -186,9 +186,9 @@ function RangeSelector<DateType extends object = any>(
if (input) {
const { offsetWidth, offsetLeft, offsetParent } = input.nativeElement;
const parentWidth = (offsetParent as HTMLElement)?.offsetWidth || 0;
const activeOffset = placementRight ? (parentWidth - offsetWidth - offsetLeft) : offsetLeft;
setActiveBarStyle((ori) => ({
...ori,
const activeOffset = placementRight ? parentWidth - offsetWidth - offsetLeft : offsetLeft;
setActiveBarStyle(({ position }) => ({
position,
width: offsetWidth,
[offsetUnit]: activeOffset,
}));
Expand All @@ -198,7 +198,7 @@ function RangeSelector<DateType extends object = any>(

React.useEffect(() => {
syncActiveOffset();
}, [activeIndex]);
}, [activeIndex, alignedPlacement]);

// ======================== Clear =========================
const showClear = clearIcon && ((value[0] && !disabled[0]) || (value[1] && !disabled[1]));
Expand Down
4 changes: 3 additions & 1 deletion src/PickerInput/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export interface PickerContextProps<DateType = any> {
/** Customize button component */
button?: Components['button'];
input?: Components['input'];

/** trigger will change placement while aligining */
alignedPlacement?: string;
setAlignedPlacement?: React.Dispatch<React.SetStateAction<string>>;
}

const PickerContext = React.createContext<PickerContextProps>(null!);
Expand Down
21 changes: 17 additions & 4 deletions src/PickerTrigger/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type PickerTriggerProps = {
placement?: string;
builtinPlacements?: BuildInPlacements;
direction?: 'ltr' | 'rtl';

alignedPlacement: string;
// Visible
visible: boolean;
onClose: () => void;
Expand All @@ -72,15 +72,15 @@ function PickerTrigger({
placement,
builtinPlacements = BUILT_IN_PLACEMENTS,
direction,

alignedPlacement,
// Visible
visible,
onClose,
}: PickerTriggerProps) {
const { prefixCls } = React.useContext(PickerContext);
const { prefixCls, setAlignedPlacement } = React.useContext(PickerContext);
const dropdownPrefixCls = `${prefixCls}-dropdown`;

const realPlacement = getRealPlacement(placement, direction === 'rtl');
const realPlacement = getRealPlacement(alignedPlacement || placement, direction === 'rtl');

return (
<Trigger
Expand All @@ -100,6 +100,19 @@ function PickerTrigger({
popupStyle={popupStyle}
stretch="minWidth"
getPopupContainer={getPopupContainer}
onPopupAlign={(_, align) => {
if (!setAlignedPlacement) return;

const matchedKey = Object.keys(BUILT_IN_PLACEMENTS).find(
(key) =>
BUILT_IN_PLACEMENTS[key].points[0] === align.points[0] &&
BUILT_IN_PLACEMENTS[key].points[1] === align.points[1],
);

if (matchedKey) {
setAlignedPlacement(matchedKey);
}
}}
onPopupVisibleChange={(nextVisible) => {
if (!nextVisible) {
onClose();
Expand Down
140 changes: 138 additions & 2 deletions tests/new-range.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { act, fireEvent, render } from '@testing-library/react';
import dayjs, { type Dayjs } from 'dayjs';
import 'dayjs/locale/ar';
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
import { spyElementPrototype, spyElementPrototypes } from 'rc-util/lib/test/domHook';
import { resetWarned } from 'rc-util/lib/warning';
import React from 'react';
import type { RangePickerProps } from '../src';
Expand Down Expand Up @@ -43,6 +43,89 @@ describe('NewPicker.Range', () => {
jest.useRealTimers();
});

let rangeRect = { x: 0, y: 0, width: 0, height: 0 };

beforeEach(() => {
rangeRect = {
x: 0,
y: 0,
width: 200,
height: 100,
};

document.documentElement.scrollLeft = 0;
// jest.useFakeTimers();
});

beforeAll(() => {
jest.spyOn(document.documentElement, 'scrollWidth', 'get').mockReturnValue(1000);

// Viewport size
spyElementPrototypes(HTMLElement, {
clientWidth: {
get: () => 400,
},
clientHeight: {
get: () => 400,
},
});

// Popup size
spyElementPrototypes(HTMLDivElement, {
getBoundingClientRect() {
if (this.className.includes('rc-picker-dropdown')) {
return {
x: 0,
y: 0,
width: 300,
height: 100,
};
}
if (this.className.includes('rc-picker-range')) {
return rangeRect;
}
if (this.className.includes('rc-picker')) {
return rangeRect;
}
},
offsetWidth: {
get() {
if (this.className.includes('rc-picker-range-wrapper')) {
return rangeRect.width;
}
if (this.className.includes('rc-picker-range-arrow')) {
return 10;
}
if (this.className.includes('rc-picker-input')) {
return 100;
}
if (this.className.includes('rc-picker-dropdown')) {
return 300;
}
},
},
offsetLeft: {
get() {
if (this.className.includes('rc-picker-input')) {
return 0;
}
},
},
});
spyElementPrototypes(HTMLElement, {
offsetParent: {
get: () => document.body,
},
offsetWidth: {
get() {
if (this.tagName === 'BODY') {
return 200;
}
},
},
});
});

describe('PickerValue', () => {
it('defaultPickerValue should reset every time when opened', () => {
const { container } = render(
Expand Down Expand Up @@ -1098,7 +1181,7 @@ describe('NewPicker.Range', () => {
it('pass tabIndex', () => {
const { container } = render(
<div>
<DayRangePicker tabIndex={-1}/>
<DayRangePicker tabIndex={-1} />
</div>,
);

Expand Down Expand Up @@ -1338,4 +1421,57 @@ describe('NewPicker.Range', () => {
}
expect(existed).toBeTruthy();
});

describe('pupop aligned position', () => {
it('the arrow should be set to `inset-inline-start` when the popup is aligned to `bottomLeft`.', async () => {
render(<DayRangePicker open />);

await act(async () => {
jest.runAllTimers();

await Promise.resolve();
});
expect(document.querySelector('.rc-picker-range-arrow')).toHaveStyle({
'inset-inline-start': '0',
});
});

it('the arrow should be set to `inset-inline-end` when the popup is aligned to `bottomRight`.', async () => {
const mock = spyElementPrototypes(HTMLDivElement, {
getBoundingClientRect() {
if (this.className.includes('rc-picker-dropdown')) {
return {
x: 0,
y: 0,
width: 300,
height: 100,
};
}
if (this.className.includes('rc-picker-range')) {
return {
...rangeRect,
x: 300,
};
}
},
});

render(<DayRangePicker open />);

await act(async () => {
jest.runAllTimers();

await Promise.resolve();
});
expect(document.querySelector('.rc-picker-range-arrow')).toHaveStyle({
'inset-inline-end': '100px',
});

expect(document.querySelector('.rc-picker-active-bar')).toHaveStyle({
'inset-inline-end': '100px',
});

mock.mockRestore();
});
});
});