Skip to content

Commit a652470

Browse files
author
Thebeard
authored
feat(module:date-picker): support date-picker placement (#7527)
1 parent d8b26dd commit a652470

File tree

7 files changed

+143
-31
lines changed

7 files changed

+143
-31
lines changed

components/core/overlay/overlay-position.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,37 @@ export function getPlacementName(position: ConnectedOverlayPositionChange): stri
7272
}
7373
return undefined;
7474
}
75+
76+
export const DATE_PICKER_POSITION_MAP = {
77+
bottomLeft: new ConnectionPositionPair(
78+
{ originX: 'start', originY: 'bottom' },
79+
{ overlayX: 'start', overlayY: 'top' },
80+
undefined,
81+
2
82+
),
83+
topLeft: new ConnectionPositionPair(
84+
{ originX: 'start', originY: 'top' },
85+
{ overlayX: 'start', overlayY: 'bottom' },
86+
undefined,
87+
-2
88+
),
89+
bottomRight: new ConnectionPositionPair(
90+
{ originX: 'end', originY: 'bottom' },
91+
{ overlayX: 'end', overlayY: 'top' },
92+
undefined,
93+
2
94+
),
95+
topRight: new ConnectionPositionPair(
96+
{ originX: 'end', originY: 'top' },
97+
{ overlayX: 'end', overlayY: 'bottom' },
98+
undefined,
99+
-2
100+
)
101+
};
102+
103+
export const DEFAULT_DATE_PICKER_POSITIONS = [
104+
DATE_PICKER_POSITION_MAP.bottomLeft,
105+
DATE_PICKER_POSITION_MAP.topLeft,
106+
DATE_PICKER_POSITION_MAP.bottomRight,
107+
DATE_PICKER_POSITION_MAP.topRight
108+
];

components/date-picker/date-picker.component.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,52 @@ describe('NzDatePickerComponent', () => {
514514
openPickerByClickTrigger();
515515
expect(overlayContainerElement.children[0].classList).toContain('cdk-overlay-backdrop');
516516
}));
517+
it('should support nzPlacement', fakeAsync(() => {
518+
fixtureInstance.nzPlacement = 'bottomLeft';
519+
fixture.detectChanges();
520+
openPickerByClickTrigger();
521+
let element = queryFromOverlay('.ant-picker-dropdown');
522+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(true);
523+
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false);
524+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false);
525+
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false);
526+
triggerInputBlur();
527+
fixture.detectChanges();
528+
tick(500);
529+
fixture.detectChanges();
530+
fixtureInstance.nzPlacement = 'topLeft';
531+
fixture.detectChanges();
532+
openPickerByClickTrigger();
533+
element = queryFromOverlay('.ant-picker-dropdown');
534+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false);
535+
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(true);
536+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false);
537+
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false);
538+
triggerInputBlur();
539+
fixture.detectChanges();
540+
tick(500);
541+
fixture.detectChanges();
542+
fixtureInstance.nzPlacement = 'bottomRight';
543+
fixture.detectChanges();
544+
openPickerByClickTrigger();
545+
element = queryFromOverlay('.ant-picker-dropdown');
546+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false);
547+
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false);
548+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(true);
549+
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false);
550+
triggerInputBlur();
551+
fixture.detectChanges();
552+
tick(500);
553+
fixture.detectChanges();
554+
fixtureInstance.nzPlacement = 'topRight';
555+
fixture.detectChanges();
556+
openPickerByClickTrigger();
557+
element = queryFromOverlay('.ant-picker-dropdown');
558+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false);
559+
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false);
560+
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false);
561+
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(true);
562+
}));
517563
});
518564

519565
describe('panel switch and move forward/afterward', () => {
@@ -1289,6 +1335,7 @@ describe('in form', () => {
12891335
[nzBorderless]="nzBorderless"
12901336
[nzInline]="nzInline"
12911337
[nzBackdrop]="nzBackdrop"
1338+
[nzPlacement]="nzPlacement"
12921339
></nz-date-picker>
12931340
<ng-template #tplDateRender let-current>
12941341
<div [class.test-first-day]="current.getDate() === 1">{{ current.getDate() }}</div>
@@ -1348,6 +1395,7 @@ class NzTestDatePickerComponent {
13481395
nzBorderless = false;
13491396
nzInline = false;
13501397
nzBackdrop = false;
1398+
nzPlacement = 'bottomLeft';
13511399

13521400
// nzRanges;
13531401
nzOnPanelChange(_: string): void {}

components/date-picker/date-picker.component.ts

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { slideMotion } from 'ng-zorro-antd/core/animation';
4848
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
4949
import { NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form';
5050
import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
51+
import { DEFAULT_DATE_PICKER_POSITIONS, DATE_PICKER_POSITION_MAP } from 'ng-zorro-antd/core/overlay';
5152
import { CandyDate, cloneDate, CompatibleValue, wrongSortOrder } from 'ng-zorro-antd/core/time';
5253
import {
5354
BooleanInput,
@@ -83,6 +84,7 @@ const POPUP_STYLE_PATCH = { position: 'relative' }; // Aim to override antd's st
8384
const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'datePicker';
8485

8586
export type NzDatePickerSizeType = 'large' | 'default' | 'small';
87+
export type NzPlacement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';
8688

8789
/**
8890
* The base picker for all common APIs
@@ -303,6 +305,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte
303305
@Input() @WithConfig() nzSuffixIcon: string | TemplateRef<NzSafeAny> = 'calendar';
304306
@Input() @WithConfig() nzBackdrop = false;
305307
@Input() nzId: string | null = null;
308+
@Input() nzPlacement: NzPlacement = 'bottomLeft';
306309

307310
// TODO(@wenqi73) The PanelMode need named for each pickers and export
308311
@Output() readonly nzOnPanelChange = new EventEmitter<NzDateMode | NzDateMode[] | string | string[]>();
@@ -335,36 +338,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte
335338
inputValue!: NzSafeAny;
336339
activeBarStyle: object = {};
337340
overlayOpen: boolean = false; // Available when "nzOpen" = undefined
338-
overlayPositions: ConnectionPositionPair[] = [
339-
{
340-
offsetY: 2,
341-
originX: 'start',
342-
originY: 'bottom',
343-
overlayX: 'start',
344-
overlayY: 'top'
345-
},
346-
{
347-
offsetY: -2,
348-
originX: 'start',
349-
originY: 'top',
350-
overlayX: 'start',
351-
overlayY: 'bottom'
352-
},
353-
{
354-
offsetY: 2,
355-
originX: 'end',
356-
originY: 'bottom',
357-
overlayX: 'end',
358-
overlayY: 'top'
359-
},
360-
{
361-
offsetY: -2,
362-
originX: 'end',
363-
originY: 'top',
364-
overlayX: 'end',
365-
overlayY: 'bottom'
366-
}
367-
] as ConnectionPositionPair[];
341+
overlayPositions: ConnectionPositionPair[] = [...DEFAULT_DATE_PICKER_POSITIONS];
368342
currentPositionX: HorizontalConnectionPos = 'start';
369343
currentPositionY: VerticalConnectionPos = 'bottom';
370344

@@ -680,7 +654,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte
680654
}
681655

682656
ngOnChanges(changes: SimpleChanges): void {
683-
const { nzStatus } = changes;
657+
const { nzStatus, nzPlacement } = changes;
684658
if (changes.nzPopupStyle) {
685659
// Always assign the popup style patch
686660
this.nzPopupStyle = this.nzPopupStyle ? { ...this.nzPopupStyle, ...POPUP_STYLE_PATCH } : POPUP_STYLE_PATCH;
@@ -712,6 +686,10 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte
712686
if (nzStatus) {
713687
this.setStatusStyles(this.nzStatus, this.hasFeedback);
714688
}
689+
690+
if (nzPlacement) {
691+
this.setPlacement(this.nzPlacement);
692+
}
715693
}
716694

717695
ngOnDestroy(): void {
@@ -877,4 +855,11 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte
877855
}
878856
});
879857
}
858+
859+
private setPlacement(placement: NzPlacement): void {
860+
const position: ConnectionPositionPair = DATE_PICKER_POSITION_MAP[placement];
861+
this.overlayPositions = [position, ...DEFAULT_DATE_PICKER_POSITIONS];
862+
this.currentPositionX = position.originX;
863+
this.currentPositionY = position.originY;
864+
}
880865
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
order: 6
3+
title:
4+
zh-CN: 自定义位置
5+
en-US: Placement
6+
---
7+
8+
## zh-CN
9+
10+
可以通过 `nzPlacement` 手动指定弹出的位置。
11+
12+
## en-US
13+
14+
You can manually specify the position of the popup via `nzPlacement`.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'nz-demo-date-picker-placement',
5+
template: `
6+
<nz-radio-group [(ngModel)]="placement">
7+
<label nz-radio-button nzValue="bottomLeft">bottomLeft</label>
8+
<label nz-radio-button nzValue="bottomRight">bottomRight</label>
9+
<label nz-radio-button nzValue="topLeft">topLeft</label>
10+
<label nz-radio-button nzValue="topRight">topRight</label>
11+
</nz-radio-group>
12+
<br />
13+
<br />
14+
<nz-date-picker [nzPlacement]="placement"></nz-date-picker>
15+
<br />
16+
<nz-range-picker [nzPlacement]="placement"></nz-range-picker>
17+
`,
18+
styles: [
19+
`
20+
nz-date-picker,
21+
nz-range-picker {
22+
margin: 0 8px 12px 0;
23+
}
24+
`
25+
]
26+
})
27+
export class NzDemoDatePickerPlacementComponent {
28+
placement: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight' = 'bottomLeft';
29+
}

components/date-picker/doc/index.en-US.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ The following APIs are shared by nz-date-picker, nz-range-picker.
5151
| `[nzRenderExtraFooter]` | render extra footer in panel | `TemplateRef \| string \| (() => TemplateRef \| string)` | - |
5252
| `[nzSize]` | determine the size of the input box, the height of `large` and `small`, are 40px and 24px respectively, while default size is 32px | `'large' \| 'small'` | - | - |
5353
| `[nzStatus]` | Set validation status | `'error' \| 'warning'` | - |
54+
| `[nzPlacement]` | The position where the selection box pops up | `'bottomLeft' \| 'bottomRight' \| 'topLeft' \| 'topRight'` | `'bottomLeft'` | |
5455
| `[nzSuffixIcon]` | the custom suffix icon | `string` \| `TemplateRef` | - ||
5556
| `[nzBorderless]` | remove the border | `boolean` | `false` | - |
5657
| `[nzInline]` | inline mode | `boolean` | `false` | - |

components/date-picker/doc/index.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ registerLocaleData(zh);
5151
| `[nzRenderExtraFooter]` | 在面板中添加额外的页脚 | `TemplateRef \| string \| (() => TemplateRef \| string)` | - |
5252
| `[nzSize]` | 输入框大小,`large` 高度为 40px,`small` 为 24px,默认是 32px | `'large' \| 'small'` | - | - |
5353
| `[nzStatus]` | 设置校验状态 | `'error' \| 'warning'` | - |
54+
| `[nzPlacement]` | 选择框弹出的位置 | `'bottomLeft' \| 'bottomRight' \| 'topLeft' \| 'topRight'` | `'bottomLeft'` | |
5455
| `[nzSuffixIcon]` | 自定义的后缀图标 | `string` \| `TemplateRef` | - ||
5556
| `[nzBorderless]` | 移除边框 | `boolean` | `false` | - |
5657
| `[nzInline]` | 内联模式 | `boolean` | `false` | - |

0 commit comments

Comments
 (0)