From d19fe85b922ca39dba2476b263f6781be8e3a741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B1=AA=E7=8F=A3?= <519367854@qq.com> Date: Thu, 7 Jul 2022 22:31:02 +0800 Subject: [PATCH] feat(module:date-picker): support date-picker placement --- components/core/overlay/overlay-position.ts | 34 +++++++++++++ .../date-picker/date-picker.component.spec.ts | 48 +++++++++++++++++++ .../date-picker/date-picker.component.ts | 47 +++++++----------- components/date-picker/demo/placement.md | 14 ++++++ components/date-picker/demo/placement.ts | 29 +++++++++++ components/date-picker/doc/index.en-US.md | 1 + components/date-picker/doc/index.zh-CN.md | 1 + 7 files changed, 143 insertions(+), 31 deletions(-) create mode 100644 components/date-picker/demo/placement.md create mode 100644 components/date-picker/demo/placement.ts diff --git a/components/core/overlay/overlay-position.ts b/components/core/overlay/overlay-position.ts index 4ca5ab08c0..c06ea6f449 100644 --- a/components/core/overlay/overlay-position.ts +++ b/components/core/overlay/overlay-position.ts @@ -72,3 +72,37 @@ export function getPlacementName(position: ConnectedOverlayPositionChange): stri } return undefined; } + +export const DATE_PICKER_POSITION_MAP = { + bottomLeft: new ConnectionPositionPair( + { originX: 'start', originY: 'bottom' }, + { overlayX: 'start', overlayY: 'top' }, + undefined, + 2 + ), + topLeft: new ConnectionPositionPair( + { originX: 'start', originY: 'top' }, + { overlayX: 'start', overlayY: 'bottom' }, + undefined, + -2 + ), + bottomRight: new ConnectionPositionPair( + { originX: 'end', originY: 'bottom' }, + { overlayX: 'end', overlayY: 'top' }, + undefined, + 2 + ), + topRight: new ConnectionPositionPair( + { originX: 'end', originY: 'top' }, + { overlayX: 'end', overlayY: 'bottom' }, + undefined, + -2 + ) +}; + +export const DEFAULT_DATE_PICKER_POSITIONS = [ + DATE_PICKER_POSITION_MAP.bottomLeft, + DATE_PICKER_POSITION_MAP.topLeft, + DATE_PICKER_POSITION_MAP.bottomRight, + DATE_PICKER_POSITION_MAP.topRight +]; diff --git a/components/date-picker/date-picker.component.spec.ts b/components/date-picker/date-picker.component.spec.ts index 9c21d79160..75eb372d6c 100644 --- a/components/date-picker/date-picker.component.spec.ts +++ b/components/date-picker/date-picker.component.spec.ts @@ -514,6 +514,52 @@ describe('NzDatePickerComponent', () => { openPickerByClickTrigger(); expect(overlayContainerElement.children[0].classList).toContain('cdk-overlay-backdrop'); })); + it('should support nzPlacement', fakeAsync(() => { + fixtureInstance.nzPlacement = 'bottomLeft'; + fixture.detectChanges(); + openPickerByClickTrigger(); + let element = queryFromOverlay('.ant-picker-dropdown'); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(true); + expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false); + triggerInputBlur(); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + fixtureInstance.nzPlacement = 'topLeft'; + fixture.detectChanges(); + openPickerByClickTrigger(); + element = queryFromOverlay('.ant-picker-dropdown'); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(true); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false); + triggerInputBlur(); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + fixtureInstance.nzPlacement = 'bottomRight'; + fixture.detectChanges(); + openPickerByClickTrigger(); + element = queryFromOverlay('.ant-picker-dropdown'); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(true); + expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false); + triggerInputBlur(); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + fixtureInstance.nzPlacement = 'topRight'; + fixture.detectChanges(); + openPickerByClickTrigger(); + element = queryFromOverlay('.ant-picker-dropdown'); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false); + expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(true); + })); }); describe('panel switch and move forward/afterward', () => { @@ -1289,6 +1335,7 @@ describe('in form', () => { [nzBorderless]="nzBorderless" [nzInline]="nzInline" [nzBackdrop]="nzBackdrop" + [nzPlacement]="nzPlacement" >
{{ current.getDate() }}
@@ -1348,6 +1395,7 @@ class NzTestDatePickerComponent { nzBorderless = false; nzInline = false; nzBackdrop = false; + nzPlacement = 'bottomLeft'; // nzRanges; nzOnPanelChange(_: string): void {} diff --git a/components/date-picker/date-picker.component.ts b/components/date-picker/date-picker.component.ts index 3e4ce081b1..32b5aef9c8 100644 --- a/components/date-picker/date-picker.component.ts +++ b/components/date-picker/date-picker.component.ts @@ -48,6 +48,7 @@ import { slideMotion } from 'ng-zorro-antd/core/animation'; import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config'; import { NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form'; import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation'; +import { DEFAULT_DATE_PICKER_POSITIONS, DATE_PICKER_POSITION_MAP } from 'ng-zorro-antd/core/overlay'; import { CandyDate, cloneDate, CompatibleValue, wrongSortOrder } from 'ng-zorro-antd/core/time'; import { BooleanInput, @@ -83,6 +84,7 @@ const POPUP_STYLE_PATCH = { position: 'relative' }; // Aim to override antd's st const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'datePicker'; export type NzDatePickerSizeType = 'large' | 'default' | 'small'; +export type NzPlacement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'; /** * The base picker for all common APIs @@ -303,6 +305,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte @Input() @WithConfig() nzSuffixIcon: string | TemplateRef = 'calendar'; @Input() @WithConfig() nzBackdrop = false; @Input() nzId: string | null = null; + @Input() nzPlacement: NzPlacement = 'bottomLeft'; // TODO(@wenqi73) The PanelMode need named for each pickers and export @Output() readonly nzOnPanelChange = new EventEmitter(); @@ -335,36 +338,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte inputValue!: NzSafeAny; activeBarStyle: object = {}; overlayOpen: boolean = false; // Available when "nzOpen" = undefined - overlayPositions: ConnectionPositionPair[] = [ - { - offsetY: 2, - originX: 'start', - originY: 'bottom', - overlayX: 'start', - overlayY: 'top' - }, - { - offsetY: -2, - originX: 'start', - originY: 'top', - overlayX: 'start', - overlayY: 'bottom' - }, - { - offsetY: 2, - originX: 'end', - originY: 'bottom', - overlayX: 'end', - overlayY: 'top' - }, - { - offsetY: -2, - originX: 'end', - originY: 'top', - overlayX: 'end', - overlayY: 'bottom' - } - ] as ConnectionPositionPair[]; + overlayPositions: ConnectionPositionPair[] = [...DEFAULT_DATE_PICKER_POSITIONS]; currentPositionX: HorizontalConnectionPos = 'start'; currentPositionY: VerticalConnectionPos = 'bottom'; @@ -680,7 +654,7 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte } ngOnChanges(changes: SimpleChanges): void { - const { nzStatus } = changes; + const { nzStatus, nzPlacement } = changes; if (changes.nzPopupStyle) { // Always assign the popup style patch this.nzPopupStyle = this.nzPopupStyle ? { ...this.nzPopupStyle, ...POPUP_STYLE_PATCH } : POPUP_STYLE_PATCH; @@ -712,6 +686,10 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte if (nzStatus) { this.setStatusStyles(this.nzStatus, this.hasFeedback); } + + if (nzPlacement) { + this.setPlacement(this.nzPlacement); + } } ngOnDestroy(): void { @@ -877,4 +855,11 @@ export class NzDatePickerComponent implements OnInit, OnChanges, OnDestroy, Afte } }); } + + private setPlacement(placement: NzPlacement): void { + const position: ConnectionPositionPair = DATE_PICKER_POSITION_MAP[placement]; + this.overlayPositions = [position, ...DEFAULT_DATE_PICKER_POSITIONS]; + this.currentPositionX = position.originX; + this.currentPositionY = position.originY; + } } diff --git a/components/date-picker/demo/placement.md b/components/date-picker/demo/placement.md new file mode 100644 index 0000000000..d48cfed7bb --- /dev/null +++ b/components/date-picker/demo/placement.md @@ -0,0 +1,14 @@ +--- +order: 6 +title: + zh-CN: 自定义位置 + en-US: Placement +--- + +## zh-CN + +可以通过 `nzPlacement` 手动指定弹出的位置。 + +## en-US + +You can manually specify the position of the popup via `nzPlacement`. diff --git a/components/date-picker/demo/placement.ts b/components/date-picker/demo/placement.ts new file mode 100644 index 0000000000..118ded5329 --- /dev/null +++ b/components/date-picker/demo/placement.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-date-picker-placement', + template: ` + + + + + + +
+
+ +
+ + `, + styles: [ + ` + nz-date-picker, + nz-range-picker { + margin: 0 8px 12px 0; + } + ` + ] +}) +export class NzDemoDatePickerPlacementComponent { + placement: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight' = 'bottomLeft'; +} diff --git a/components/date-picker/doc/index.en-US.md b/components/date-picker/doc/index.en-US.md index 961b67bb75..c07fe893f1 100644 --- a/components/date-picker/doc/index.en-US.md +++ b/components/date-picker/doc/index.en-US.md @@ -51,6 +51,7 @@ The following APIs are shared by nz-date-picker, nz-range-picker. | `[nzRenderExtraFooter]` | render extra footer in panel | `TemplateRef \| string \| (() => TemplateRef \| string)` | - | | `[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'` | - | - | | `[nzStatus]` | Set validation status | `'error' \| 'warning'` | - | +| `[nzPlacement]` | The position where the selection box pops up | `'bottomLeft' \| 'bottomRight' \| 'topLeft' \| 'topRight'` | `'bottomLeft'` | | | `[nzSuffixIcon]` | the custom suffix icon | `string` \| `TemplateRef` | - | ✅ | | `[nzBorderless]` | remove the border | `boolean` | `false` | - | | `[nzInline]` | inline mode | `boolean` | `false` | - | diff --git a/components/date-picker/doc/index.zh-CN.md b/components/date-picker/doc/index.zh-CN.md index d1f27e4ad8..a1e5082a72 100644 --- a/components/date-picker/doc/index.zh-CN.md +++ b/components/date-picker/doc/index.zh-CN.md @@ -51,6 +51,7 @@ registerLocaleData(zh); | `[nzRenderExtraFooter]` | 在面板中添加额外的页脚 | `TemplateRef \| string \| (() => TemplateRef \| string)` | - | | `[nzSize]` | 输入框大小,`large` 高度为 40px,`small` 为 24px,默认是 32px | `'large' \| 'small'` | - | - | | `[nzStatus]` | 设置校验状态 | `'error' \| 'warning'` | - | +| `[nzPlacement]` | 选择框弹出的位置 | `'bottomLeft' \| 'bottomRight' \| 'topLeft' \| 'topRight'` | `'bottomLeft'` | | | `[nzSuffixIcon]` | 自定义的后缀图标 | `string` \| `TemplateRef` | - | ✅ | | `[nzBorderless]` | 移除边框 | `boolean` | `false` | - | | `[nzInline]` | 内联模式 | `boolean` | `false` | - |