From c09d2f82c7ad9b63fec986ee9bca749daf0eef12 Mon Sep 17 00:00:00 2001 From: wangyaju Date: Fri, 15 Sep 2023 10:20:17 +0800 Subject: [PATCH] chore(release): release 16.1.0 (#326) Co-authored-by: huaweidevcloud --- devui/back-top/back-top.component.html | 55 +++--- devui/back-top/back-top.component.scss | 5 +- devui/back-top/back-top.component.ts | 176 ++++++++++++++++-- .../demo/customize/customize.component.html | 11 +- .../demo/customize/customize.component.ts | 22 ++- .../scroll-container.component.html | 8 +- devui/back-top/doc/api-cn.md | 31 +-- devui/back-top/doc/api-en.md | 36 ++-- devui/button/button.spec.ts | 13 +- devui/cascader/cascader-li.component.html | 19 +- devui/cascader/cascader-li.component.scss | 4 - .../category-search.component.html | 16 +- .../category-search.component.ts | 130 +++++++------ devui/category-search/category-search.type.ts | 2 + .../auto-scroll/auto-scroll.component.html | 19 +- .../demo/auto-scroll/auto-scroll.component.ts | 13 +- .../demo/extend/extend.component.html | 2 +- devui/category-search/doc/api-cn.md | 49 ++--- devui/category-search/doc/api-en.md | 47 ++--- devui/checkbox/checkbox.component.html | 6 +- devui/checkbox/checkbox.component.ts | 50 ++--- devui/checkbox/checkbox.spec.ts | 4 +- devui/common/auto-focus.directive.ts | 6 +- .../helper-download.component.html | 4 +- .../helper-download.component.ts | 17 +- devui/common/doc/api-cn.md | 135 +++++++------- devui/common/doc/api-en.md | 139 +++++++------- devui/common/helper-utils.ts | 6 +- .../data-table/data-table-head.component.html | 1 + devui/data-table/data-table-head.component.ts | 3 +- .../data-table/data-table-row.component.scss | 4 - devui/data-table/data-table.component.html | 3 +- devui/data-table/data-table.component.scss | 82 +++++--- devui/data-table/data-table.component.ts | 20 +- .../data-table/demo/data-table-demo.module.ts | 112 +++++------ .../editable-old/editable-old.component.html | 6 +- .../data-table-demo-editable.component.html | 5 +- .../data-table-demo-maxheight.component.html | 11 +- devui/data-table/doc/api-cn.md | 5 + devui/data-table/doc/api-en.md | 4 + .../table-option-modal.component.scss | 1 + .../table/body/td/td.component.scss | 17 -- .../head/th/filter/filter.component.html | 2 +- .../head/th/filter/filter.component.scss | 5 +- .../table/head/th/sort/sort.component.scss | 4 - .../table/head/th/th.component.html | 6 +- .../data-table/table/head/th/th.component.ts | 17 +- .../data-table/table/head/thead.component.ts | 4 +- devui/data-table/table/row/tr.component.html | 1 + devui/data-table/table/row/tr.component.ts | 3 +- .../datepicker-pro.component.ts | 16 +- .../lib/calendar-panel.component.scss | 2 +- .../lib/timepicker-panel.component.scss | 18 +- .../datepicker-cdk-overlay.component.ts | 20 +- devui/datepicker/datepicker.directive.ts | 14 +- devui/drawer/demo/basic/basic.component.ts | 4 +- devui/drawer/drawer.component.scss | 14 +- devui/drawer/drawer.component.ts | 23 ++- devui/form/doc/api-cn.md | 132 ++++++------- devui/form/doc/api-en.md | 138 +++++++------- .../form-control-rules.directive.ts | 14 +- .../form/validator-directive/validate.type.ts | 18 +- devui/i18n/en-us.ts | 4 + devui/i18n/i18n.model.ts | 2 + devui/i18n/zh-cn.ts | 4 + devui/icon/icon.component.scss | 10 +- devui/input-group/input-group.component.scss | 23 ++- devui/modal/demo/modal-demo.module.ts | 4 +- devui/modal/dialog.service.ts | 33 ++-- devui/modal/modal-container.component.scss | 1 + devui/modal/modal-container.component.ts | 10 +- devui/modal/modal-header.component.ts | 10 +- devui/modal/modal.component.ts | 66 ++++--- devui/modal/modal.spec.ts | 12 +- .../number-translation.pipe.ts | 4 +- devui/package.json | 2 +- devui/pagination/pagination.component.html | 1 + devui/pagination/pagination.component.ts | 21 ++- devui/popover/popover.component.ts | 4 +- devui/radio/doc/api-cn.md | 17 +- devui/radio/doc/api-en.md | 17 +- devui/radio/radio-group.component.scss | 22 ++- devui/radio/radio-group.component.ts | 28 ++- .../demo/select-all/select-all.component.html | 12 +- .../demo/select-all/select-all.component.ts | 36 ++-- devui/select/doc/api-cn.md | 1 + devui/select/doc/api-en.md | 1 + devui/select/select.component.html | 2 +- devui/select/select.component.ts | 31 ++- .../devui-online-ide.service.ts | 2 +- devui/style/theme/_basic.scss | 4 + devui/tags-input/tags.input.component.html | 8 +- devui/tags-input/tags.input.component.scss | 24 ++- devui/tags-input/tags.input.component.ts | 103 +++++----- devui/tags/tags.component.html | 2 +- devui/theme-collection/extend-theme.scss | 15 ++ devui/theme-collection/extend-theme.ts | 8 +- devui/theme-collection/theme-initial-data.ts | 17 +- devui/theme/theme-data.ts | 6 +- .../demo/basic/basic.component.html | 2 +- devui/time-picker/time-picker.component.scss | 9 +- devui/toast/toast.component.html | 2 +- devui/toast/toast.component.scss | 2 + .../toggle-menu-list-item.component.html | 2 + .../transfer-demo-search.component.html | 12 ++ .../transfer-demo-search.component.scss | 20 ++ .../search/transfer-demo-search.component.ts | 7 +- .../transfer/demo/transfer-demo.component.ts | 3 +- devui/transfer/doc/api-cn.md | 75 +++++--- devui/transfer/doc/api-en.md | 73 +++++--- devui/transfer/transfer.component.html | 22 +++ devui/transfer/transfer.component.scss | 16 ++ devui/transfer/transfer.component.ts | 11 +- devui/tree/operable-tree.component.html | 2 + devui/tree/operable-tree.component.scss | 1 + devui/upload/demo/multi/multi.component.ts | 4 +- devui/upload/doc/api-cn.md | 2 + devui/upload/doc/api-en.md | 168 +++++++++-------- devui/upload/file-uploader.types.ts | 2 + devui/upload/multiple-upload.component.ts | 27 +-- devui/upload/select-files.utils.ts | 22 ++- devui/upload/upload.directive.ts | 15 +- devui/version.ts | 2 +- package.json | 4 +- src/app/app.module.ts | 60 +++--- src/app/component/app-content.component.ts | 1 + src/app/component/scope-list.ts | 8 +- src/assets/i18n/en-us.json | 2 +- src/assets/i18n/zh-cn.json | 2 +- src/environments/environment.prod.ts | 2 +- 130 files changed, 1756 insertions(+), 1145 deletions(-) create mode 100644 devui/transfer/demo/search/transfer-demo-search.component.scss diff --git a/devui/back-top/back-top.component.html b/devui/back-top/back-top.component.html index 0158b182..02b97c8b 100644 --- a/devui/back-top/back-top.component.html +++ b/devui/back-top/back-top.component.html @@ -1,23 +1,34 @@ -
- - -
- - - - - -
-
+
+
+ + +
+ + + + + +
+
+
diff --git a/devui/back-top/back-top.component.scss b/devui/back-top/back-top.component.scss index 608c6083..b3973036 100644 --- a/devui/back-top/back-top.component.scss +++ b/devui/back-top/back-top.component.scss @@ -4,9 +4,12 @@ width: 40px; height: 40px; position: fixed; - cursor: pointer; z-index: 1; + .devui-backtop-drag-handle { + cursor: unset !important; + } + &-content { width: 40px; height: 40px; diff --git a/devui/back-top/back-top.component.ts b/devui/back-top/back-top.component.ts index 3b07698c..d213ab85 100644 --- a/devui/back-top/back-top.component.ts +++ b/devui/back-top/back-top.component.ts @@ -1,7 +1,20 @@ import { DOCUMENT } from '@angular/common'; import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, - ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, TemplateRef + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Inject, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + TemplateRef } from '@angular/core'; import { fromEvent, Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; @@ -12,30 +25,35 @@ import { debounceTime } from 'rxjs/operators'; styleUrls: ['./back-top.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, -}) -export class BackTopComponent implements OnInit, OnChanges, OnDestroy { + }) +export class BackTopComponent implements OnChanges, OnInit, OnDestroy, AfterViewInit { @Input() customTemplate: TemplateRef; @Input() visibleHeight = 300; @Input() bottom = '50px'; @Input() right = '30px'; @Input() scrollTarget: HTMLElement; - @Output() backTopEvent = new EventEmitter(); + @Input() draggable = false; + @Output() backTopEvent = new EventEmitter(); + @Output() dragEvent = new EventEmitter(); currScrollTop = 0; + duration = 0; + cursorTimer: any; + dragBoundary: any; + moveCursor = false; isVisible = false; - SCROLL_REFRESH_INTERVAL = 100; target: HTMLElement | Window; subs: Subscription = new Subscription(); document: Document; + + SCROLL_REFRESH_INTERVAL = 100; + MOUSEDOWN_DELAY = 180; + RESIZE_DELAY = 300; + constructor(private cdr: ChangeDetectorRef, private el: ElementRef, @Inject(DOCUMENT) private doc: any) { this.document = this.doc; } - ngOnInit() { - this.addScrollEvent(); - this.showButton(); - } - ngOnChanges(changes: SimpleChanges): void { if (changes['scrollTarget']) { if (this.subs) { @@ -46,6 +64,42 @@ export class BackTopComponent implements OnInit, OnChanges, OnDestroy { } } + ngOnInit(): void { + this.addScrollEvent(); + this.showButton(); + } + + ngAfterViewInit(): void { + if (this.draggable) { + // 窗口大小改变时,如缩放比例或组件超出显示范围重置到默认位置 + this.subs.add( + fromEvent(window, 'resize') + .pipe(debounceTime(this.RESIZE_DELAY)) + .subscribe(() => { + const dom = this.dragBoundary?.dom || this.el.nativeElement.querySelector('div.devui-backtop'); + if (dom) { + // 不显示时无法获取getBoundingClientRect,使用style获取 + const style = getComputedStyle(dom); + const left = parseInt(style.left, 10) || 0; + const top = parseInt(style.top, 10) || 0; + if (window.devicePixelRatio !== 1 || left > window.innerWidth || top > window.innerHeight) { + this.onMouseUp(); + this.duration = 0; + dom.style.left = 'unset'; + dom.style.top = 'unset'; + } + } + }) + ); + } + } + + ngOnDestroy(): void { + if (this.subs) { + this.subs.unsubscribe(); + } + } + addScrollEvent() { this.subs.add( fromEvent(this.getScrollTarget(), 'scroll') @@ -67,9 +121,11 @@ export class BackTopComponent implements OnInit, OnChanges, OnDestroy { } showButton() { - this.currScrollTop = this.target === window ? - (window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop) : this.scrollTarget.scrollTop; - if (this.isVisible !== (this.currScrollTop >= this.visibleHeight)) { + this.currScrollTop = + this.target === window + ? window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop + : this.scrollTarget.scrollTop; + if (this.isVisible !== this.currScrollTop >= this.visibleHeight) { this.isVisible = !this.isVisible; } } @@ -85,9 +141,95 @@ export class BackTopComponent implements OnInit, OnChanges, OnDestroy { this.backTopEvent.emit(true); } - ngOnDestroy() { - if (this.subs) { - this.subs.unsubscribe(); + setDragBoundary() { + const dom = this.el.nativeElement.querySelector('div.devui-backtop'); + let boxRect; + let minLeft; + let minTop; + let maxLeft; + let maxTop; + if (dom) { + const { width, height } = dom.getBoundingClientRect(); + if (this.scrollTarget) { + boxRect = this.scrollTarget.getBoundingClientRect(); + minLeft = -1 * boxRect.x; + minTop = -1 * boxRect.y; + maxLeft = window.innerWidth - boxRect.x - width; + maxTop = window.innerHeight - boxRect.y - height; + } else { + boxRect = { x: 0, y: 0 }; + minLeft = 0; + minTop = 0; + maxLeft = window.innerWidth - width; + maxTop = window.innerHeight - height; + } + this.dragBoundary = { dom, boxRect, minLeft, minTop, maxLeft, maxTop }; + } + } + + // mousedown mouseup click 按顺序触发且前一个结束触发下一个 + mousedownEvent(event: MouseEvent) { + if (this.draggable) { + event.preventDefault(); + this.cursorTimer = setTimeout(() => { + this.setDragBoundary(); + this.duration = new Date().getTime(); + this.moveCursor = true; + this.cdr.markForCheck(); + this.dragEvent.emit(true); + }, this.MOUSEDOWN_DELAY); + } + } + + mouseleaveEvent() { + if (this.draggable && this.cursorTimer) { + clearTimeout(this.cursorTimer); + } + } + + clickEvent(event: MouseEvent) { + if (this.draggable && this.duration > this.MOUSEDOWN_DELAY) { + event.stopPropagation(); + this.duration = 0; + } + } + + @HostListener('document:mouseup', []) + onMouseUp() { + if (this.draggable) { + if (this.cursorTimer) { + clearTimeout(this.cursorTimer); + } + if (this.moveCursor) { + this.moveCursor = false; + this.cdr.markForCheck(); + this.dragEvent.emit(false); + } + this.duration = this.duration && new Date().getTime() - this.duration; + } + } + + @HostListener('document:mousemove', ['$event']) + onMouseMove(event: MouseEvent) { + if (this.draggable && this.moveCursor && this.dragBoundary) { + // 先判断再执行阻止默认事件,否则可能影响鼠标拖选功能 + event.preventDefault(); + const posX = event.movementX; + const posY = event.movementY; + const rect = this.dragBoundary.dom.getBoundingClientRect(); + const left = rect.x - this.dragBoundary.boxRect.x + posX; + const top = rect.y - this.dragBoundary.boxRect.y + posY; + const isLeft = left < this.dragBoundary.minLeft; + const isRight = left > this.dragBoundary.maxLeft; + const isTop = top < this.dragBoundary.minTop; + const isBottom = top > this.dragBoundary.maxTop; + this.dragBoundary.dom.style.left = `${isRight ? this.dragBoundary.maxLeft : isLeft ? this.dragBoundary.minLeft : left}px`; + this.dragBoundary.dom.style.top = `${isBottom ? this.dragBoundary.maxTop : isTop ? this.dragBoundary.minTop : top}px`; + // 如到达边界释放拖拽动作,避免鼠标偏移较大距离后返回主视窗仍可拖拽 + if ([isLeft, isRight, isTop, isBottom].includes(true)) { + this.onMouseUp(); + this.duration = 0; + } } } } diff --git a/devui/back-top/demo/customize/customize.component.html b/devui/back-top/demo/customize/customize.component.html index 323e111a..5baa5e0f 100644 --- a/devui/back-top/demo/customize/customize.component.html +++ b/devui/back-top/demo/customize/customize.component.html @@ -1,9 +1,16 @@
- + -
+
diff --git a/devui/back-top/demo/customize/customize.component.ts b/devui/back-top/demo/customize/customize.component.ts index 7469c9f8..47d604fb 100644 --- a/devui/back-top/demo/customize/customize.component.ts +++ b/devui/back-top/demo/customize/customize.component.ts @@ -1,13 +1,25 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; +import { TooltipDirective } from 'ng-devui/tooltip'; @Component({ selector: 'd-back-top-customize', templateUrl: './customize.component.html', - styleUrls: ['./customize.component.scss'] -}) - + styleUrls: ['./customize.component.scss'], + }) export class CustomizeComponent { - constructor() {} + @ViewChild('tooltipItem', { read: TooltipDirective }) tooltipItem: TooltipDirective; + content = 'Back to the top'; + + toggleTooltip(toggle) { + if (this.tooltipItem) { + if (toggle) { + this.tooltipItem.content = ''; + this.tooltipItem.hide(); + } else { + this.tooltipItem.content = this.content; + } + } + } backTop(event) { console.log(event); diff --git a/devui/back-top/demo/scroll-container/scroll-container.component.html b/devui/back-top/demo/scroll-container/scroll-container.component.html index 5cabcc7e..82d7c2d3 100644 --- a/devui/back-top/demo/scroll-container/scroll-container.component.html +++ b/devui/back-top/demo/scroll-container/scroll-container.component.html @@ -4,5 +4,11 @@ {{ index + 1 + '. ' + item }} - +
diff --git a/devui/back-top/doc/api-cn.md b/devui/back-top/doc/api-cn.md index 0b72861d..4fe36b1e 100644 --- a/devui/back-top/doc/api-cn.md +++ b/devui/back-top/doc/api-cn.md @@ -1,6 +1,6 @@ # 如何使用 -在module中引入: +在 module 中引入: ```ts import { BackTopModule } from 'ng-devui/back-top'; @@ -12,19 +12,22 @@ import { BackTopModule } from 'ng-devui/back-top'; ``` -# d-back-top -## d-back-top 参数 +## d-back-top -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo |全局配置项| -| :----------------: | :---------: | :------------: | :-----: | :--------------------------------------------------------------------------- | | -| customTemplate | `TemplateRef` | -- | 可选,自定义按钮样式 | [自定义](demo#back-top-customize) | -| bottom | `string` | '50px' | 可选,按钮距离页面底部位置 | [自定义](demo#back-top-customize) | -| right | `string` | '30px' | 可选,按钮距离页面右边距 | [自定义](demo#back-top-customize) | -| visibleHeight | `number` | 300 | 可选,滚动高度达到visibleHeight所设值后展示回到顶部按钮,单位为`px` | [自定义](demo#back-top-customize) | -| scrollTarget | `HTMLElement` | Window | 可选,触发滚动的对象 | [滚动容器](demo#back-top-scroll-container) | +### d-back-top 参数 -## d-back-top 事件 +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| -------------- | ------------------ | ----------------- | --------------------------------------------------------------------- | ------------------------------------------ | ---------- | +| customTemplate | `TemplateRef` | -- | 可选,自定义按钮样式 | [自定义](demo#back-top-customize) | | +| bottom | `string` | '50px' | 可选,按钮距离页面底部位置 | [自定义](demo#back-top-customize) | | +| right | `string` | '30px' | 可选,按钮距离页面右边距 | [自定义](demo#back-top-customize) | | +| visibleHeight | `number` | 300 | 可选,滚动高度达到 visibleHeight 所设值后展示回到顶部按钮,单位为`px` | [自定义](demo#back-top-customize) | | +| scrollTarget | `HTMLElement` |
Window
| 可选,触发滚动的对象 | [滚动容器](demo#back-top-scroll-container) | | +| draggable | `boolean` | false | 可选,是否允许拖拽改变位置,按住鼠标启动拖拽 | [滚动容器](demo#back-top-scroll-container) | | -| 参数 | 类型 | 说明 | 跳转 Demo | -| :------: | :-----------------: | :-------------------------------------------------------------------------------------- | ---------------------------------------------- | -| backTopEvent | `EventEmitter` | 可选,点击回到顶部按钮的回调函数 | [基本用法](demo#back-top-basic) | +### d-back-top 事件 + +| 参数 | 类型 | 说明 | 跳转 Demo | +| ------------ | ----------------------- | ------------------------------------ | --------------------------------- | +| backTopEvent | `EventEmitter` | 可选,点击回到顶部按钮的回调函数 | [基本用法](demo#back-top-basic) | +| dragEvent | `EventEmitter` | 可选,拖拽事件开始或结束时的回调函数 | [自定义](demo#back-top-customize) | diff --git a/devui/back-top/doc/api-en.md b/devui/back-top/doc/api-en.md index 2520254f..2c92d348 100644 --- a/devui/back-top/doc/api-en.md +++ b/devui/back-top/doc/api-en.md @@ -11,19 +11,23 @@ In the page: ```xml ``` -# d-back-top -## d-back-top Parameter - -| Parameter | Type | Default | Description | Jump to Demo |Global Config| -| :----------------: | :---------: | :------------: | :-----: | :--------------------------------------------------------------------------- | | -| customTemplate | `TemplateRef` | -- | Optional. Custom button style | [Customize](demo#back-top-customize) | -| bottom | `string` | '50px' | Optional. Position between the button and the bottom of the page | [Customize](demo#back-top-customize) | -| right | `string` | '30px' | Optional. It is the right margin between the button and the page | [Customize](demo#back-top-customize) | -| visibleHeight | `number` | 300 | Optional. When the scrolling height reaches the value of visibleHeight, the button is displayed to the top. The unit is `px` | [Customize](demo#back-top-customize) | -| scrollTarget | `HTMLElement` | Window | Optional. Object that triggers scrolling | [Scrolling Container](demo#back-top-scroll-container) | - -## d-back-top Event - -| Parameter | Type | Description | Jump to Demo | -| :------: | :-----------------: | :-------------------------------------------------------------------------------------- | ---------------------------------------------- | -| backTopEvent | `EventEmitter` | Optional. Callback function for clicking the button to return to the top | [Basic Usage](demo#back-top-basic) | + +## d-back-top + +### d-back-top Parameter + +| Parameter | Type | Default | Description | Jump to Demo | Global Config | +| -------------- | ------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------- | +| customTemplate | `TemplateRef` | -- | Optional. Custom button style | [Customize](demo#back-top-customize) | | +| bottom | `string` | '50px' | Optional. Position between the button and the bottom of the page | [Customize](demo#back-top-customize) | | +| right | `string` | '30px' | Optional. It is the right margin between the button and the page | [Customize](demo#back-top-customize) | | +| visibleHeight | `number` | 300 | Optional. When the scrolling height reaches the value of visibleHeight, the button is displayed to the top. The unit is `px` | [Customize](demo#back-top-customize) | | +| scrollTarget | `HTMLElement` | Window | Optional. Object that triggers scrolling | [Scrolling Container](demo#back-top-scroll-container) | | +| draggable | `boolean` | false | Optional. Indicates whether to allow dragging to change the position. Press and hold the mouse to start dragging. | [Customize](demo#back-top-customize) | | + +### d-back-top Event + +| Parameter | Type | Description | Jump to Demo | +| ------------ | ----------------------- | ------------------------------------------------------------------------ | ------------------------------------ | +| backTopEvent | `EventEmitter` | Optional. Callback function for clicking the button to return to the top | [Basic Usage](demo#back-top-basic) | +| dragEvent | `EventEmitter` | Optional. Callback function at the start or end of a drag event | [Customize](demo#back-top-customize) | diff --git a/devui/button/button.spec.ts b/devui/button/button.spec.ts index 569f46a1..d7165667 100644 --- a/devui/button/button.spec.ts +++ b/devui/button/button.spec.ts @@ -1,5 +1,5 @@ import { Component, DebugElement } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { ButtonModule } from 'ng-devui/button'; import { LoadingComponent } from './../loading/loading.component'; @@ -18,7 +18,7 @@ import { ButtonComponent } from './button.component'; {{text}} ` -}) + }) class TestButtonComponent { bsStyle = 'primary'; bsPosition = 'default'; @@ -37,7 +37,7 @@ class TestButtonComponent { template: ` {{text}} ` -}) + }) class TestButtonAutoFocusComponent { show = false; } @@ -203,12 +203,13 @@ describe('Button', () => { afterEach(() => { document.body.removeChild(testComponentElement); }); - it('Button should have pseudo-class :focus', () => { + it('Button should have pseudo-class :focus', fakeAsync(() => { testComponent.show = true; fixture.detectChanges(); + tick(); + fixture.detectChanges(); expect(document.activeElement).toBeTruthy(); - expect(document.activeElement.classList.contains('devui-btn')).toBe(true); - }); + })); }); }); diff --git a/devui/cascader/cascader-li.component.html b/devui/cascader/cascader-li.component.html index 54a3bb3a..fa5045ee 100644 --- a/devui/cascader/cascader-li.component.html +++ b/devui/cascader/cascader-li.component.html @@ -22,20 +22,25 @@
- - + + > + + diff --git a/devui/cascader/cascader-li.component.scss b/devui/cascader/cascader-li.component.scss index 57924b8d..ca0731a8 100644 --- a/devui/cascader/cascader-li.component.scss +++ b/devui/cascader/cascader-li.component.scss @@ -59,10 +59,6 @@ .devui-leaf-active { background-color: $devui-list-item-hover-bg; - - span { - color: $devui-brand-active; - } } .devui-dropdown-menu { diff --git a/devui/category-search/category-search.component.html b/devui/category-search/category-search.component.html index 4a03e86d..bd9156ca 100644 --- a/devui/category-search/category-search.component.html +++ b/devui/category-search/category-search.component.html @@ -5,7 +5,7 @@ (mouseenter)="isHover = true" (mouseleave)="isHover = false" > -
+
- {{ item?.label }} +
@@ -151,6 +155,10 @@ {{ item?.groupName }} + + {{ item?.label }} + +
{{ i18nCommonText?.noData }}
@@ -213,7 +221,7 @@ /> {{ textConfig.labelConnector || '|' }} - {{ val[tag.filterKey || 'label'] || '' }} + {{ val[tag.filterKey || 'label'] || '' }} diff --git a/devui/category-search/category-search.component.ts b/devui/category-search/category-search.component.ts index 72ac6c18..aa7714d9 100644 --- a/devui/category-search/category-search.component.ts +++ b/devui/category-search/category-search.component.ts @@ -47,7 +47,7 @@ import { DefaultTemplateDirective } from './default-template.directive'; selector: 'd-category-search', templateUrl: './category-search.component.html', styleUrls: ['./category-search.component.scss'], -}) + }) export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewInit, AfterContentInit { static ID_SEED = 0; @Input() category: ICategorySearchTagItem[] = []; @@ -74,6 +74,7 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI @Input() categoryInGroup = false; // 是否按组别显示分类下拉列表 @Input() groupOrderConfig: string[]; // 用户配置组顺序 @Input() customGroupNameTemplate: TemplateRef; // 用户自定义组名称显示模板 + @Input() customCategoryNameTemplate: TemplateRef; // 用户自定义分类名称显示模板 @Input() tagMaxWidth: number; @Input() textConfig: TextConfig = { keywordName: '', @@ -86,6 +87,10 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI @Input() beforeTagChange: (tag, searchKey, operation) => boolean | Promise | Observable; @Input() toggleEvent: (dropdown, tag?, currentSelectTag?) => void; @Input() @WithConfig() styleType = 'default'; + @Input() @WithConfig() showGlowStyle = true; + @HostBinding('class.devui-glow-style') get hasGlowStyle() { + return this.showGlowStyle; + } @Output() searchEvent = new EventEmitter(); @Output() selectedTagsChange = new EventEmitter(); @Output() createFilterEvent = new EventEmitter(); @@ -99,11 +104,6 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI @ViewChildren(DefaultTemplateDirective) defaultTemplates: QueryList; @ContentChildren(ContentTemplateDirective) contentTemplates: QueryList; - @Input() @WithConfig() showGlowStyle = true; - @HostBinding('class.devui-glow-style') get hasGlowStyle() { - return this.showGlowStyle; - } - id: number; searchField: ICategorySearchTagItem[]; categoryDisplay: ICategorySearchTagItem[]; @@ -159,7 +159,7 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI this.document = this.doc; this.dateConverter = new DefaultDateConverter(); this.id = CategorySearchComponent.ID_SEED++; - this.showSearchConfig = { keyword: true, field: true, category: true }; + this.showSearchConfig = { keyword: true, field: true, category: true, noCategoriesAvailableTip: true }; this.i18n .langChange() .pipe(takeUntil(this.destroy$)) @@ -279,6 +279,7 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI keyword: this.showSearchCategory, field: this.showSearchCategory, category: this.showSearchCategory, + noCategoriesAvailableTip: this.showSearchCategory, } : this.showSearchCategory; this.showSearchConfig = { ...this.showSearchConfig, ...customConfig }; @@ -386,20 +387,24 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI setValue(data: any, isSelectedTags = false) { if (Array.isArray(data) && data.length) { data.forEach((item) => { - if (isSelectedTags && this.category) { - let result = ''; - const originItem = this.category.find((categoryItem) => categoryItem.field === item.field); - mergeWith(item, originItem, this.mergeCheck); - if (item.value.value) { - item.value.cache = cloneDeep(item.value.value); - result = this.joinLabelTypes.includes(item.type) ? this.getItemValue(item.value.value, item.filterKey || 'label') : ''; - } - item.title = this.setTitle(item, item.type, result); - if (item.type === 'label' && item.options[0] && !item.options[0].$label) { - this.mergeToLabel(item); - } - } else { + if (!isSelectedTags || !this.category) { item = this.initCategoryItem(item); + return; + } + let result = ''; + const originItem = this.category.find((categoryItem) => categoryItem.field === item.field); + mergeWith(item, originItem, this.mergeCheck); + if (item.value.value) { + if (item.type === 'treeSelect') { + this.getTreeValue(item, false); + return; + } + item.value.cache = cloneDeep(item.value.value); + result = this.joinLabelTypes.includes(item.type) ? this.getItemValue(item.value.value, item.filterKey || 'label') : ''; + } + item.title = this.setTitle(item, item.type, result); + if (item.type === 'label' && item.options[0] && !item.options[0].$label) { + this.mergeToLabel(item); } }); } @@ -425,7 +430,7 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI this.handleGroupLength(item, item.isSelected, isInit); return item; }); - this.showNoDataTips = this.categoryDisplay.every((item) => item.isSelected); + this.showNoDataTips = this.showSearchConfig.noCategoriesAvailableTip ? this.categoryDisplay.every((item) => item.isSelected) : false; } initGroupAndDictionary() { @@ -447,16 +452,14 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI keys.forEach((key) => { const item = this.categoryDictionary[key]; if (item) { - if (item) { - if (this.categoryInGroup) { - if (item.groupName) { - this.categoryOrder.push(item, ...item.children); - } else if (!item.group) { - this.categoryOrder.push(item); - } - } else { + if (this.categoryInGroup) { + if (item.groupName) { + this.categoryOrder.push(item, ...item.children); + } else if (!item.group) { this.categoryOrder.push(item); } + } else { + this.categoryOrder.push(item); } } }); @@ -481,14 +484,6 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI } } - search() { - this.searchEvent.emit({ - selectedTags: this.getSelectedTagsExceptKeyword(), - searchKey: this.setSearchKeyTag(), - }); - this.isFocus = true; - } - searchCategory(item: ICategorySearchTagItem, value?: string) { if (this.valueIsArrayTypes.includes(item.type)) { return; @@ -500,9 +495,11 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI this.finishChoose(); } - searchInputValue(event: Event) { - event.preventDefault(); - event.stopPropagation(); + searchInputValue(event?: Event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } // 当有分类正在选择时输入关键字不处理 if (!this.currentSelectTag) { this.searchEvent.emit({ @@ -510,6 +507,7 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI searchKey: this.setSearchKeyTag(), }); } + this.isFocus = true; } chooseCategory(item: ICategorySearchTagItem, inputDropdown: DropDownDirective) { @@ -843,11 +841,11 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI } } - // checkbox | label 将选中项对应filterKey的值合并的方法,当前多选已通过data展示,可考虑移除 + // checkbox | label 将选中项对应filterKey的值合并的方法 getItemValue(value: any, key: string) { if (value && Array.isArray(value)) { const result = value.map((item) => item[key]); - return result.join(','); + return result.join(' | '); } } @@ -888,7 +886,7 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI this.handleAccordingType(this.currentSelectTag.type, this.currentSelectTag.options); this.scrollToTailFlag = true; } - this.showNoDataTips = this.categoryDisplay.every((item) => item.isSelected); + this.showNoDataTips = this.showSearchConfig.noCategoriesAvailableTip ? this.categoryDisplay.every((item) => item.isSelected) : false; } resetTreeRender(dropdown: DropDownDirective, tag: any, flag: boolean, isCurrentSelectTag?: boolean) { @@ -985,8 +983,10 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI } // treeSelect 树 处理选中项方法 - getTreeValue(tag: ICategorySearchTagItem) { - this.afterDropdownClosed(); + getTreeValue(tag: ICategorySearchTagItem, isConfirm = true) { + if (isConfirm) { + this.afterDropdownClosed(); + } const result = []; const selectedIds = []; const data = tag.value.value as ITagOption[]; @@ -995,18 +995,44 @@ export class CategorySearchComponent implements OnChanges, OnDestroy, AfterViewI selectedIds.push(item[tag.treeNodeIdKey || 'id']); }); if (result.length) { - tag.value.options = cloneDeep(tag.options); + tag.value.options = isConfirm ? cloneDeep(tag.options) : this.initTreeChecked(tag, tag.options, selectedIds); tag.value.cache = cloneDeep(tag.value.value); tag.value[tag.filterKey || 'label'] = result.join(','); tag.title = this.setTitle(tag, 'treeSelect'); - if (tag.multiple) { - const halfCheckedIds = []; - this.treeInstance.nodes.forEach((item) => item.data.halfChecked && halfCheckedIds.push(item.id)); - this.updateTreeData(tag, tag.value.options, selectedIds, halfCheckedIds); - } - this.updateSelectedTags(tag); } else { this.removeTag(tag); + return; + } + if (isConfirm) { + this.updateTreeChecked(tag, selectedIds); + this.updateSelectedTags(tag); + } + } + + initTreeChecked(tag: ICategorySearchTagItem, tree: any, selectedIds: string[]) { + if (Array.isArray(tree)) { + return tree.map((item) => this.initTreeChecked(tag, item, selectedIds)); + } else if (tree) { + const idKey = tag.treeNodeIdKey || 'id'; + const childKey = tag.treeNodeChildrenKey || 'children'; + if (Array.isArray(tree[childKey])) { + let checkedChild = 0; + tree[childKey] = this.initTreeChecked(tag, tree[childKey], selectedIds); + tree[childKey].forEach((child) => child.isChecked && checkedChild++); + tree.halfChecked = checkedChild > 0 && checkedChild < tree[childKey].length; + tree.isChecked = checkedChild > 0 && !tree.halfChecked; + } else { + tree.isChecked = selectedIds.includes(tree[idKey]); + } + return tree; + } + } + + updateTreeChecked(tag: ICategorySearchTagItem, selectedIds: string[]) { + if (tag.multiple) { + const halfCheckedIds = []; + this.treeInstance.nodes.forEach((item) => item.data.halfChecked && halfCheckedIds.push(item.id)); + this.updateTreeData(tag, tag.value.options, selectedIds, halfCheckedIds); } } diff --git a/devui/category-search/category-search.type.ts b/devui/category-search/category-search.type.ts index 2d7d5d94..e2649343 100644 --- a/devui/category-search/category-search.type.ts +++ b/devui/category-search/category-search.type.ts @@ -131,6 +131,8 @@ export interface SearchConfig { fieldDescription?: (label: string) => string; category?: boolean; categoryDescription?: string; + noCategoriesAvailableTip?: boolean; + searchInputMaxLength?: number; } export interface TextConfig { diff --git a/devui/category-search/demo/auto-scroll/auto-scroll.component.html b/devui/category-search/demo/auto-scroll/auto-scroll.component.html index fc279120..47161b18 100644 --- a/devui/category-search/demo/auto-scroll/auto-scroll.component.html +++ b/devui/category-search/demo/auto-scroll/auto-scroll.component.html @@ -8,17 +8,28 @@ [toggleScrollToTail]="true" [categoryInGroup]="true" [groupOrderConfig]="groupOrderConfig" + [customGroupNameTemplate]="groupTpl" + [customCategoryNameTemplate]="categoryTpl" (searchEvent)="searchEvent($event)" (createFilterEvent)="createFilter($event)" (searchKeyChange)="searchKeyChange($event)" (selectedTagsChange)="selectedTagsChange($event)" (clearAllEvent)="clearAllEvent($event)" > -
+

The categories of the current search:

- >
{{ item?.label }}: {{ (item?.value)[item.filterKey] || item?.value?.label }}
-
-

The current search keyword: {{ finalSearchKey }}

+ +
The current search keyword: {{ finalSearchKey }}
+ + + + {{ item?.groupName }} + + + + + {{ item?.label }} + diff --git a/devui/category-search/demo/auto-scroll/auto-scroll.component.ts b/devui/category-search/demo/auto-scroll/auto-scroll.component.ts index df2fb23a..74b805a3 100644 --- a/devui/category-search/demo/auto-scroll/auto-scroll.component.ts +++ b/devui/category-search/demo/auto-scroll/auto-scroll.component.ts @@ -6,7 +6,18 @@ import { demoData } from '../demo-data'; @Component({ selector: 'd-auto-scroll', templateUrl: './auto-scroll.component.html', -}) + styles: [ + ` + section { + padding-top: 16px; + } + + i.icon { + margin-right: 8px; + } + `, + ], + }) export class AutoScrollComponent { category = cloneDeep(demoData.slice(0, -2)); groupOrderConfig = ['Basic', 'Personnel-related', 'Time-related', 'User-defined']; diff --git a/devui/category-search/demo/extend/extend.component.html b/devui/category-search/demo/extend/extend.component.html index 763806d6..efb6bf20 100644 --- a/devui/category-search/demo/extend/extend.component.html +++ b/devui/category-search/demo/extend/extend.component.html @@ -22,7 +22,7 @@
- +
reset status diff --git a/devui/category-search/doc/api-cn.md b/devui/category-search/doc/api-cn.md index 850f5c26..9576f358 100644 --- a/devui/category-search/doc/api-cn.md +++ b/devui/category-search/doc/api-cn.md @@ -14,29 +14,30 @@ import { CategorySearchModule } from 'ng-devui/category-search'; ### d-category-search 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 | -| ----------------------- | ----------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------- | --------------------------------------- | -| category | `ICategorySearchTagItem` | -- | 必选,传入分类搜索源数据 | [基本用法](demo#basic-usage) | -| defaultSearchField | `String[]` | [] | 可选,配置输入关键字时可在哪些分类中筛选 | [基本用法](demo#basic-usage) | -| selectedTags | `ICategorySearchTagItem` | -- | 可选,传入需要默认选中的分类数据 | [基本用法](demo#basic-usage) | -| placeholderText | `string` | -- | 可选, 自定义搜索输入框占位文字 | [基本用法](demo#basic-usage) | -| ~~allowSave~~ | `boolean` | true | 可选,是否显示保存当前过滤的按钮 | [基本用法](demo#basic-usage) | -| ~~allowClear~~ | `boolean` | true | 可选,是否显示清除当前过滤的按钮 | [基本用法](demo#basic-usage) | -| ~~allowShowMore~~ | `boolean` | false | 可选,是否显示当前过滤条件下拉列表的按钮 | [大数据量优化展示](demo#auto-scroll) | -| extendedConfig | `ExtendedConfig` | -- | 可选,配置右侧扩展按钮功能 | [自定义展示模板](demo#custom-template) | -| showSearchCategory | `boolean \| SearchConfig` | true | 可选,是否显示搜索关键字下拉菜单 | [自定义展示模板](demo##custom-template) | -| searchKey | `string` | '' | 可选,搜索框内的默认展示值 | [基本用法](demo#basic-usage) | -| beforeTagChange | `(tag, searchKey, operation) => boolean \| Promise \| Observable` | -- | 可选,改变标签前调用的方法,返回 boolean 类型,返回 false 可以阻止分类值改变 | | -| toggleEvent | `(dropdown, tag?, currentSelectTag?) => void` | -- | 可选,已选分类的下拉菜单开关时调用的方法,可使用 return 阻止之后的默认方法执行 | [基本用法](demo#basic-usage) | -| inputReadOnly | `boolean` | false | 可选,限制是否可通过搜索框输入关键字搜索,`true`则无法输入关键字,仅可根据提供的分类数据筛选 | | -| tagMaxWidth | `number` | -- | 可选,单个过滤条件的最大宽度,超过则显示省略号,不设置则不限制 | [大数据量优化展示](demo#auto-scroll) | -| textConfig | `{keywordName: string, createFilter: string, filterTitle: string}` | -- | 可选,配置关键字分类名称、保存过滤器下拉窗口的文字内容 | [自定义展示模板](demo##custom-template) | -| toggleScrollToTail | `boolean` | false | 可选,在有滚动条存在时初始化加载或选择过滤内容后自动滚动至最右侧,以便用户选择新的过滤内容 | [大数据量优化展示](demo#auto-scroll) | -| filterNameRules | `DValidateRules` | false | 可选,配置保存过滤器标题的校验规则,详细规则参见 ng-devui 库 form 组件 | [基本用法](demo#basic-usage) | -| categoryInGroup | `boolean` | false | 可选,是否按组别显示分类下拉列表 | [大数据量优化展示](demo#auto-scroll) | -| groupOrderConfig | `String[]` | -- | 可选,配置组的排序 | [大数据量优化展示](demo#auto-scroll) | -| customGroupNameTemplate | `TemplateRef` | -- | 可选,自定义组名称显示模板 | [大数据量优化展示](demo#auto-scroll) | -| showGlowStyle | `boolean` | true | 可选,是否显示悬浮发光效果 | +| 参数 | 类型 | 默认 | 说明 | 跳转 | +| -------------------------- | ----------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------- | --------------------------------------- | +| category | `ICategorySearchTagItem` | -- | 必选,传入分类搜索源数据 | [基本用法](demo#basic-usage) | +| defaultSearchField | `String[]` | [] | 可选,配置输入关键字时可在哪些分类中筛选 | [基本用法](demo#basic-usage) | +| selectedTags | `ICategorySearchTagItem` | -- | 可选,传入需要默认选中的分类数据 | [基本用法](demo#basic-usage) | +| placeholderText | `string` | -- | 可选, 自定义搜索输入框占位文字 | [基本用法](demo#basic-usage) | +| ~~allowSave~~ | `boolean` | true | 可选,是否显示保存当前过滤的按钮 | [基本用法](demo#basic-usage) | +| ~~allowClear~~ | `boolean` | true | 可选,是否显示清除当前过滤的按钮 | [基本用法](demo#basic-usage) | +| ~~allowShowMore~~ | `boolean` | false | 可选,是否显示当前过滤条件下拉列表的按钮 | [大数据量优化展示](demo#auto-scroll) | +| extendedConfig | `ExtendedConfig` | -- | 可选,配置右侧扩展按钮功能 | [自定义展示模板](demo#custom-template) | +| showSearchCategory | `boolean \| SearchConfig` | true | 可选,是否显示搜索关键字下拉菜单 | [自定义展示模板](demo##custom-template) | +| searchKey | `string` | '' | 可选,搜索框内的默认展示值 | [基本用法](demo#basic-usage) | +| beforeTagChange | `(tag, searchKey, operation) => boolean \| Promise \| Observable` | -- | 可选,改变标签前调用的方法,返回 boolean 类型,返回 false 可以阻止分类值改变 | | +| toggleEvent | `(dropdown, tag?, currentSelectTag?) => void` | -- | 可选,已选分类的下拉菜单开关时调用的方法,可使用 return 阻止之后的默认方法执行 | [基本用法](demo#basic-usage) | +| inputReadOnly | `boolean` | false | 可选,限制是否可通过搜索框输入关键字搜索,`true`则无法输入关键字,仅可根据提供的分类数据筛选 | | +| tagMaxWidth | `number` | -- | 可选,单个过滤条件的最大宽度,超过则显示省略号,不设置则不限制 | [大数据量优化展示](demo#auto-scroll) | +| textConfig | `{keywordName: string, createFilter: string, filterTitle: string}` | -- | 可选,配置关键字分类名称、保存过滤器下拉窗口的文字内容 | [自定义展示模板](demo##custom-template) | +| toggleScrollToTail | `boolean` | false | 可选,在有滚动条存在时初始化加载或选择过滤内容后自动滚动至最右侧,以便用户选择新的过滤内容 | [大数据量优化展示](demo#auto-scroll) | +| filterNameRules | `DValidateRules` | false | 可选,配置保存过滤器标题的校验规则,详细规则参见 ng-devui 库 form 组件 | [基本用法](demo#basic-usage) | +| categoryInGroup | `boolean` | false | 可选,是否按组别显示分类下拉列表 | [大数据量优化展示](demo#auto-scroll) | +| groupOrderConfig | `String[]` | -- | 可选,配置组的排序 | [大数据量优化展示](demo#auto-scroll) | +| customGroupNameTemplate | `TemplateRef` | -- | 可选,自定义组名称显示模板 | [大数据量优化展示](demo#auto-scroll) | +| customCategoryNameTemplate | `TemplateRef` | -- | 可选,自定义分类名称显示模板 | [大数据量优化展示](demo#auto-scroll) | +| showGlowStyle | `boolean` | true | 可选,是否显示悬浮发光效果 | ### d-category-search 事件 @@ -173,6 +174,8 @@ export interface SearchConfig { fieldDescription?: ((label: string) => string); category?: boolean; categoryDescription?: string; + noCategoriesAvailableTip?: boolean; + searchInputMaxLength?: number; } export interface TextConfig { diff --git a/devui/category-search/doc/api-en.md b/devui/category-search/doc/api-en.md index 551c987d..9147f92a 100644 --- a/devui/category-search/doc/api-en.md +++ b/devui/category-search/doc/api-en.md @@ -14,28 +14,29 @@ In the page ### d-category-search 参数 -| Parameter | Type | Default | Description | Jump to Demo | -| ----------------------- | ----------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| category | `ICategorySearchTagItem` | -- | Required. The data of categories. | [Basic usage](demo#basic-usage) | -| defaultSearchField | `String[]` | [] | Optional. Configure the categories that can be filtered when entering keywords. | [Basic usage](demo#basic-usage) | -| selectedTags | `ICategorySearchTagItem` | -- | Optional. The categories to be selected by default. | [Basic usage](demo#basic-usage) | -| ~~allowSave~~ | `boolean` | true | Optional. Whether to show save current filter button. | [Basic usage](demo#basic-usage) | -| ~~allowClear~~ | `boolean` | true | Optional. Whether to display the button for clearing the current filter. | [Basic usage](demo#basic-usage) | -| ~~allowShowMore~~ | `boolean` | false | Optional. Whether to display the button in the drop-down list of the current filter criteria. | [Large-scale data display optimization](demo#auto-scroll) | -| extendedConfig | `ExtendedConfig` | -- | Optional. Configure the function of the expansion button on the right. | [Customize drop-down list box](demo##custom-template) | -| showSearchCategory | `boolean \| SearchConfig` | true | Optional. Whether to display the search keyword drop-down list. | [Customize drop-down list box](demo##custom-template) | -| searchKey | `string` | '' | Optional. Default value displayed in the search box. | [Basic usage](demo#basic-usage) | -| beforeTagChange | `(tag, searchKey, operation) => boolean \| Promise \| Observable` | -- | Optional. Method called before changing the tag, returns the boolean type, and returns false to prevent the classification value from changing. | | -| toggleEvent | `(dropdown, tag?, currentSelectTag?) => void` | -- | Optional. Method called when the drop-down menu switch of the selected classification is enabled, which can be executed by the default method after being blocked by return. | [Basic usage](demo#basic-usage) | -| inputReadOnly | `boolean` | false | Optional. Specifies whether to enter keywords in the search box. If it is `true`, you cannot enter keywords and can only filter data based on the provided classification data. | [Basic usage](demo#basic-usage) | -| tagMaxWidth | `number` | -- | Optional. Maximum width of a single filter criterion. If the width exceeds the value, an ellipsis is displayed. If this parameter is not set, no restriction is imposed. | | -| textConfig | `{keywordName: string, createFilter: string, filterTitle: string}` | -- | Optional. Configure the keyword category name and the text in the filter drop-down list box. | [Customizing a Display Template](demo##custom-template) | -| toggleScrollToTail | `boolean` | false | Optional. When a scroll bar exists, the system automatically scrolls to the right after loading or filtering content is selected, so that users can select new filtering content. | [Large-scale data display optimization](demo#auto-scroll) | -| filterNameRules | `DValidateRules` | false | Optional. Configure the validation rule for saving the filter title. For details, see the form component in the ng-devui library. | [Basic usage](demo#basic-usage) | -| categoryInGroup | `boolean` | false | Optional. Indicates whether to display the category drop-down list by group. | [Large-scale data display optimization](demo#auto-scroll) | -| groupOrderConfig | `String[]` | -- | Optional. Configure the sorting of groups. | [Large-scale data display optimization](demo#auto-scroll) | -| customGroupNameTemplate | `TemplateRef` | -- | Optional. Custom Group Name Display Template. | [Large-scale data display optimization](demo#auto-scroll) | -| showGlowStyle | `boolean` | true | (Optional) Indicates whether to display the floating glow effect.| +| Parameter | Type | Default | Description | Jump to Demo | +| -------------------------- | ----------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| category | `ICategorySearchTagItem` | -- | Required. The data of categories. | [Basic usage](demo#basic-usage) | +| defaultSearchField | `String[]` | [] | Optional. Configure the categories that can be filtered when entering keywords. | [Basic usage](demo#basic-usage) | +| selectedTags | `ICategorySearchTagItem` | -- | Optional. The categories to be selected by default. | [Basic usage](demo#basic-usage) | +| ~~allowSave~~ | `boolean` | true | Optional. Whether to show save current filter button. | [Basic usage](demo#basic-usage) | +| ~~allowClear~~ | `boolean` | true | Optional. Whether to display the button for clearing the current filter. | [Basic usage](demo#basic-usage) | +| ~~allowShowMore~~ | `boolean` | false | Optional. Whether to display the button in the drop-down list of the current filter criteria. | [Large-scale data display optimization](demo#auto-scroll) | +| extendedConfig | `ExtendedConfig` | -- | Optional. Configure the function of the expansion button on the right. | [Customize drop-down list box](demo##custom-template) | +| showSearchCategory | `boolean \| SearchConfig` | true | Optional. Whether to display the search keyword drop-down list. | [Customize drop-down list box](demo##custom-template) | +| searchKey | `string` | '' | Optional. Default value displayed in the search box. | [Basic usage](demo#basic-usage) | +| beforeTagChange | `(tag, searchKey, operation) => boolean \| Promise \| Observable` | -- | Optional. Method called before changing the tag, returns the boolean type, and returns false to prevent the classification value from changing. | | +| toggleEvent | `(dropdown, tag?, currentSelectTag?) => void` | -- | Optional. Method called when the drop-down menu switch of the selected classification is enabled, which can be executed by the default method after being blocked by return. | [Basic usage](demo#basic-usage) | +| inputReadOnly | `boolean` | false | Optional. Specifies whether to enter keywords in the search box. If it is `true`, you cannot enter keywords and can only filter data based on the provided classification data. | [Basic usage](demo#basic-usage) | +| tagMaxWidth | `number` | -- | Optional. Maximum width of a single filter criterion. If the width exceeds the value, an ellipsis is displayed. If this parameter is not set, no restriction is imposed. | | +| textConfig | `{keywordName: string, createFilter: string, filterTitle: string}` | -- | Optional. Configure the keyword category name and the text in the filter drop-down list box. | [Customizing a Display Template](demo##custom-template) | +| toggleScrollToTail | `boolean` | false | Optional. When a scroll bar exists, the system automatically scrolls to the right after loading or filtering content is selected, so that users can select new filtering content. | [Large-scale data display optimization](demo#auto-scroll) | +| filterNameRules | `DValidateRules` | false | Optional. Configure the validation rule for saving the filter title. For details, see the form component in the ng-devui library. | [Basic usage](demo#basic-usage) | +| categoryInGroup | `boolean` | false | Optional. Indicates whether to display the category drop-down list by group. | [Large-scale data display optimization](demo#auto-scroll) | +| groupOrderConfig | `String[]` | -- | Optional. Configure the sorting of groups. | [Large-scale data display optimization](demo#auto-scroll) | +| customGroupNameTemplate | `TemplateRef` | -- | Optional. Custom group name display template. | [Large-scale data display optimization](demo#auto-scroll) | +| customCategoryNameTemplate | `TemplateRef` | -- | Optional. Custom category name display template. | [Large-scale data display optimization](demo#auto-scroll) | +| showGlowStyle | `boolean` | true | Optional. Indicates whether to display the floating glow effect. | ### d-category-search 事件 @@ -172,6 +173,8 @@ export interface SearchConfig { fieldDescription?: (label: string) => string; category?: boolean; categoryDescription?: string; + noCategoriesAvailableTip?: boolean; + searchInputMaxLength?: number; } export interface TextConfig { diff --git a/devui/checkbox/checkbox.component.html b/devui/checkbox/checkbox.component.html index 7c0c0ce0..79dc50f7 100755 --- a/devui/checkbox/checkbox.component.html +++ b/devui/checkbox/checkbox.component.html @@ -7,7 +7,7 @@ unchecked: !checked }" > -
+
- -
-

{{ name | dSafeNullPipe }}

-

{{ age | dSafeNullPipe:'***' }}

-
- @@ -52,26 +46,27 @@ import { HelperUtils } from 'ng-devui'; ``` -# dLazyLoad +## dLazyLoad -## dLzayLoad 参数 +### dLzayLoad 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo |全局配置项| -| :----------------: | :------------: | :-------: | :---: | :------------------: | ---------------------------- | -| enableLazyLoad | `boolean` | false | 可选,是否使用懒加载 | [懒加载指令](demo#lazy-load) | -| target | `HTMLElement` | 宿主 | 可选,滚动监听的目标。 | [懒加载指令](demo#lazy-load) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| -------------- | ------------------------ | ---------- | ---------------------- | ---------------------------- | ---------- | +| enableLazyLoad | `boolean` | false | 可选,是否使用懒加载 | [懒加载指令](demo#lazy-load) | +| target | `HTMLElement` | 宿主 | 可选,滚动监听的目标。 | [懒加载指令](demo#lazy-load) | +| direction | `vertical \| horizontal` | 'vertical' | 可选,懒加载滚动方向。 | | ## dLazyLoad 事件 -| 参数 | 类型 | 说明 | 跳转 Demo | -| :------: | :---------------------------: | :----------------------: | ---------------------------- | +| 参数 | 类型 | 说明 | 跳转 Demo | +| -------- | ----------------------------- | ------------------------ | ---------------------------- | | loadMore | `EventEmitter< HTMLElement >` | 必选,触发懒加载响应事件 | [懒加载指令](demo#lazy-load) | -# dAutoFocus +## dAutoFocus 自动聚焦。 -# dDatePipe +## dDatePipe 解析日期,鉴于 angular 自带的 date pipe 在 IE 兼容性有些问题,故本组件库提供一套基于 date-fns 的 pipe,能提供最大的兼容性。 @@ -307,68 +302,64 @@ import { HelperUtils } from 'ng-devui'; -# dSafeNullPipe - -当数据为`'',null,undefined`时,自动进行占位展示,默认为:`--` - -# HelperUtils 静态方法 +## HelperUtils 静态方法 -## getBrowserName() => void +### getBrowserName() => void 方法描述:获取当前浏览器名称(`IE`|`ClassicEdge`|`Firefox`|`Opera`|`Edge`|`Chrome`|`Safari`|`Other`) -## getBrowserVersion() => void +### getBrowserVersion() => void 方法描述:获取当前浏览器版本(大版本号) -## jumpOuterUrl(url: string, target?: string) => void +### jumpOuterUrl(url: string, target?: string) => void 方法描述:主要用于跳转到外部的 url。解决 router.navigate 无法跳转到外部, window.open 会被浏览器安全拦击。此方法使用了模拟了 a 标签的跳转。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :----: | :------: | :-------: | :-------------------------------------------------------------------------------------------: | --------------------------------- | -| url | `string` | -- | 必选,跳转的 url 地址。 | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ------ | --------------------------- | --------- | ----------------------------- | ----------------------------- | +| url | `string` | -- | 必选,跳转的 url 地址。 | | target | [`TargetType`](#targettype) | '\_black' | 可选,指定跳转超链接的 target | [公共函数](demo#helper-utils) | -## downloadFile(url: string, options?: [DownloadOptionsType](#downloadoptionstype), onError?: (response) => void) +### downloadFile(url: string, options?: [DownloadOptionsType](#downloadoptionstype), onError?: (response) => void) 方法描述:主要用于页面内无刷新无弹窗下载文件。实现原理利用隐藏的 iframe 和表单提交数据,主要用于 post 请求。也可用于 get 请求,get 请求推荐用 a 标签加 download 属性解决。 注意事项:浏览器对不同类型的默认打开方式不一致,如果要触发下载行为,请为返回的文件流响应包的头部指定"Content-Disposition: attachment; filename=文件名.后缀。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :---------------: | :---------------------------------: | :---------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | --------------------------------- | -| url | `string` | -- | 必选,下载的文件地址,对应表单的 action | -| option | `Object` | -- | 可选 | -| option.method | `'POST'\| 'GET' \| 'post' \| 'get'` | 'post' | 可选 | [公共函数](demo#helper-utils) | -| option.params | `Object` | -- | 可选,参数对象左值类型为 string,右值类型为 string。对应表单的表单域,method 类型为 GET/get 的时候,会将参数拼接到 url 上,method 类型为 POST/post 时候,参数会拼接才 payload 里。 | [公共函数](demo#helper-utils) | -| option.enctype | [`EncType`](#enctype) | 'application/x-www-form-urlencoded' | 可选,对应表单的 enctype 域 | -| option.iframename | `string` | 'download' | 可选,指定 iframe 的名字,预防和其他 iframe 名字冲突 | -| onError | `(response: any) => void` | -- | 可选,用于下载失败时候的回调,类型为 (response) => void, 参数 response 为请求返回的错误信息,response 试图将返回信息转为 json 如果失败则返回原返回数据的 textContent | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ----------------- | ----------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| url | `string` | -- | 必选,下载的文件地址,对应表单的 action | +| option | `Object` | -- | 可选 | +| option.method | `'POST'\| 'GET' \| 'post' \| 'get'` | 'post' | 可选 | [公共函数](demo#helper-utils) | +| option.params | `Object` | -- | 可选,参数对象左值类型为 string,右值类型为 string。对应表单的表单域,method 类型为 GET/get 的时候,会将参数拼接到 url 上,method 类型为 POST/post 时候,参数会拼接才 payload 里。 | [公共函数](demo#helper-utils) | +| option.enctype | [`EncType`](#enctype) | 'application/x-www-form-urlencoded' | 可选,对应表单的 enctype 域 | +| option.iframename | `string` | 'download' | 可选,指定 iframe 的名字,预防和其他 iframe 名字冲突 | +| onError | `(response: any) => void` | -- | 可选,用于下载失败时候的回调,类型为 (response) => void, 参数 response 为请求返回的错误信息,response 试图将返回信息转为 json 如果失败则返回原返回数据的 textContent | -## downloadFileByHttpClient(httpClient: HttpClient, url: string, options?: [ClientDownloadOptions](#clientdownloadoptions), onError?: (response) => void, onSuccess?: (response) => void) +### downloadFileByHttpClient(httpClient: HttpClient, url: string, options?: [ClientDownloadOptions](#clientdownloadoptions), onError?: (response) => void, onSuccess?: (response) => void) 方法描述:主要用于页面内无刷新无弹窗下载文件。实现原理 http 客户端网络提交获取下载文件二进制流并触发下载,主要用于 post 请求。也可用于 get 请求,get 请求推荐用 a 标签加 download 属性解决。 注意事项:浏览器对不同类型的默认打开方式不一致,如果要触发下载行为,请为返回的文件流响应包的头部指定"Content-Disposition: attachment; filename=文件名.后缀。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :-------------------------------------: | :---------------------------------: | :-----------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | --------- | -| httpClient | `HttpClient` | -- | 必选,提供注入的 http 客户端,见备注 1 | -| url | `string` | -- | 必选,下载的文件地址 | -| option | `Object` | {} | 可选 | -| option.method | `'POST'\| 'GET' \| 'post' \| 'get'` | 'post' | 可选 | -| option.params | `Object` | -- | 可选,参数对象左值类型为 string,右值类型为 string。对应表单的表单域,method 类型为 GET/get 的时候,会将参数拼接到 url 上,method 类型为 POST/post 时候,参数会拼接才 payload 里。 | -| option.enctype | `enctype` | \'application/x-www-form-urlencoded\' | 可选,类型为[['application/x-www-form-urlencoded' \| 'multipart/form-data'\| 'text/plain']], 对应表单的 enctype 域 | -| option.header | `Object` | -- | 可选,用于设置请求 header,使用键值对的 Object 设置 header 值,左值为 header 选项的 key,右值为 header 选项的 value, 如`{'X-lang': 'en'}` | -| option.responseOption | `'response' \| 'body' \| 'json'` | 'json' | 可选用于指定失败时候返回的默认格式,若格式处理失败会降级返回 | -| option.filename | `string` | -- | 可选,默认不需要设置,优先设置为配置项,其次会从响应头 Content-Type 指定的 filename 获取,再其次从访问路径获取 | -| option.reportProgress | `boolean` | false | 可选,默认为flase,是否监听下载的进度,设置为true是可以用onProgress方法监听下载进度 | -| option.withCredentials | `boolean` | false | 可选,调用 http 接口是否启用 xhr.withCredentials | -| option.downloadWithoutDispositionHeader | `boolean` | false | 可选,默认需要请求头标记 Content-Disposition: attachment 否则当作非文件流出错处理。 设置为 true,则返回响应头 http 码为成功(2xx)的情况下则强制将返回的 response | -| onError | `(res: any) => void` | -- | 可选,用于下载失败时候的回调,类型为 (response) => void, 参数 response 为请求返回的错误信息,response 试图将返回信息转为 json 如果失败则返回原返回数据的 textcontent,此处和 downloadFile 一致 | -| onSuccess | `(res: HttpResponse) => void` | -- | 可选,用于下载成功回调,类型为 (response) => void, 参数 response 为请求返回的整个 Http 信息,response 内 body 的加载类型为 ArrayBuffer。由于 body 为下载的文件流,故不会将 body 转为 json 或者试图解析 body 为文本,此处与 onError 不同 | -| onProgress | `(res: HttpProgressEvent) => void` | -- | 可选,用于下载进度事件的回调,类型为 (response) => void, 参数 response 为请求返回的下载进度信息,response的加载类型为 blob。 | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| --------------------------------------- | ------------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| httpClient | `HttpClient` | -- | 必选,提供注入的 http 客户端,见备注 1 | +| url | `string` | -- | 必选,下载的文件地址 | +| option | `Object` | {} | 可选 | +| option.method | `'POST'\| 'GET' \| 'post' \| 'get'` | 'post' | 可选 | +| option.params | `Object` | -- | 可选,参数对象左值类型为 string,右值类型为 string。对应表单的表单域,method 类型为 GET/get 的时候,会将参数拼接到 url 上,method 类型为 POST/post 时候,参数会拼接才 payload 里。 | +| option.enctype | `enctype` | \'application/x-www-form-urlencoded\' | 可选,类型为[['application/x-www-form-urlencoded' \| 'multipart/form-data'\| 'text/plain']], 对应表单的 enctype 域 | +| option.header | `Object` | -- | 可选,用于设置请求 header,使用键值对的 Object 设置 header 值,左值为 header 选项的 key,右值为 header 选项的 value, 如`{'X-lang': 'en'}` | +| option.responseOption | `'response' \| 'body' \| 'json'` | 'json' | 可选用于指定失败时候返回的默认格式,若格式处理失败会降级返回 | +| option.filename | `string` | -- | 可选,默认不需要设置,优先设置为配置项,其次会从响应头 Content-Type 指定的 filename 获取,再其次从访问路径获取 | +| option.reportProgress | `boolean` | false | 可选,默认为 flase,是否监听下载的进度,设置为 true 是可以用 onProgress 方法监听下载进度 | +| option.withCredentials | `boolean` | false | 可选,调用 http 接口是否启用 xhr.withCredentials | +| option.downloadWithoutDispositionHeader | `boolean` | false | 可选,默认需要请求头标记 Content-Disposition: attachment 否则当作非文件流出错处理。 设置为 true,则返回响应头 http 码为成功(2xx)的情况下则强制将返回的 response | +| onError | `(res: any) => void` | -- | 可选,用于下载失败时候的回调,类型为 (response) => void, 参数 response 为请求返回的错误信息,response 试图将返回信息转为 json 如果失败则返回原返回数据的 textcontent,此处和 downloadFile 一致 | +| onSuccess | `(res: HttpResponse) => void` | -- | 可选,用于下载成功回调,类型为 (response) => void, 参数 response 为请求返回的整个 Http 信息,response 内 body 的加载类型为 ArrayBuffer。由于 body 为下载的文件流,故不会将 body 转为 json 或者试图解析 body 为文本,此处与 onError 不同 | +| onProgress | `(res: HttpProgressEvent) => void` | -- | 可选,用于下载进度事件的回调,类型为 (response) => void, 参数 response 为请求返回的下载进度信息,response 的加载类型为 blob。 | 如何获取 httpClient 实例: @@ -383,41 +374,41 @@ IE 11 无 TextDecoder,如果使用 downloadFileByHttpClient,需要处理失 import 'fastestsmallesttextencoderdecoder'; ``` -## dSimulateATag +### dSimulateATag 指令描述:使用了 HelperUtils 的 jumpOuterUrl 方法,实现模拟 a 标签。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :----: | :------: | :-------: | :--------------------------------------------------------------------------------------------------: | --------- | -| href | `string` | -- | 必选,跳转的 url 地址 | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ------ | --------------------------- | --------- | ----------------------------- | --------- | +| href | `string` | -- | 必选,跳转的 url 地址 | | target | [`TargetType`](#targettype) | '\_blank' | 可选,指定跳转超链接的 target | -## dIframeEventPropagate +### dIframeEventPropagate 使用方法:在 iframe 的祖先元素里添加该指令,将 iframe 的鼠标事件(默认为 click 事件)传递到该祖先元素。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| ----- | ------ | ------- | ------------------------------------------------- | --------------------------------------------------- | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ----- | ------ | ------- | ------------------------------------------------- | ---------------------------------------- | | event | string | 'click' | 可选,值为 MouseEvent 的 type,指定冒泡的鼠标事件 | [iframe 冒泡事件](demo#iframe-propagate) | ## dClipboard 指令描述:用于复制指定文字内容到剪贴板,复制成功后弹出 popover 提示。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :--------: | :----------------------------------------------: | :---: | :----------------------------------------------------------------------------------------: | ---------------------------------- | -| content | `string` | -- | 必选,复制的文字内容 | [复制到剪贴板指令](demo#clipboard) | -| position | [`PositionType \| PositionType[]`](#positontype) | 'top' | 可选,复制成功提示弹出方向,如果传入数组形式,则当前将按照传入数组次序,自适应选取一个方向 | [复制到剪贴板指令](demo#clipboard) | -| sticky | `boolean` | false | 可选,提示弹出后是不消失,直到点击页面其他位置,默认弹出 3 秒后消失 | [复制到剪贴板指令](demo#clipboard) | -| tipContent | `string \| HTMLElement \| TemplateRef` | -- | 可选,复制成功弹出提示的显示内容或模板引 | [复制到剪贴板指令](demo#clipboard) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ---------- | ------------------------------------------------ | ----- | ------------------------------------------------------------------------------------------ | ---------------------------------- | +| content | `string` | -- | 必选,复制的文字内容 | [复制到剪贴板指令](demo#clipboard) | +| position | [`PositionType \| PositionType[]`](#positontype) | 'top' | 可选,复制成功提示弹出方向,如果传入数组形式,则当前将按照传入数组次序,自适应选取一个方向 | [复制到剪贴板指令](demo#clipboard) | +| sticky | `boolean` | false | 可选,提示弹出后是不消失,直到点击页面其他位置,默认弹出 3 秒后消失 | [复制到剪贴板指令](demo#clipboard) | +| tipContent | `string \| HTMLElement \| TemplateRef` | -- | 可选,复制成功弹出提示的显示内容或模板引 | [复制到剪贴板指令](demo#clipboard) | ### dClipboard 事件 -| 事件 | 类型 | 说明 | 跳转 Demo | -| :-------------: | :-----------------: | :----------------------------------------------------------------------------------: | ---------------------------------- | +| 事件 | 类型 | 说明 | 跳转 Demo | +| --------------- | ----------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------- | | copyResultEvent | [`EventEmitter`](#copyresult) | 复制后发出的事件,如果是不支持复制操作,isSupported 为 false,可用于提示用户自行复制 | [复制到剪贴板指令](demo#clipboard) | -# 接口 & 类型定义 +## 接口 & 类型定义 ### DownloadOptionsType @@ -462,7 +453,7 @@ export type TargetType = '_blank' | '_self' | '_parent' | '_top' | ; ### EncType ```ts -export type EncType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text-plain' +export type EncType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text-plain'; ``` ### CopyResult diff --git a/devui/common/doc/api-en.md b/devui/common/doc/api-en.md index f620a6c5..d7af72ef 100644 --- a/devui/common/doc/api-en.md +++ b/devui/common/doc/api-en.md @@ -26,12 +26,6 @@ In the page:

{{ date | dDatePipe: 'y/MM/dd' }}

- -
-

{{ name | dSafeNullPipe }}

-

{{ age | dSafeNullPipe:'***' }}

-
- @@ -46,26 +40,27 @@ In the page: ``` -# dLazyLoad +## dLazyLoad -## dLzayLoad Parameters +### dLazyLoad Parameters -| Parameter | Type |Default| Description | Jump to Demo |Global Config| -| :----------------: | :------------: | :-------: | :---: | :------------------: | ---------------------------- | -| enableLazyLoad | `boolean` | false | Optional. Whether to use lazyload | [Lazyload Directive](demo#lazy-load) | -| target | `HTMLElement` | host | Optional. Indicates the target of the scrolling monitoring. | [懒加载指令](demo#lazy-load) | +| Parameter | Type | Default | Description | Jump to Demo | Global Config | +| -------------- | ------------------------ | ---------- | ----------------------------------------------------------- | ------------------------------------ | ------------- | +| enableLazyLoad | `boolean` | false | Optional. Whether to use lazyload | [Lazyload Directive](demo#lazy-load) | +| target | `HTMLElement` | host | Optional. Indicates the target of the scrolling monitoring. | [Lazyload Directive](demo#lazy-load) | +| direction | `vertical \| horizontal` | 'vertical' | Optional. Scrolling direction. | | -## dLazyLoad Event +### dLazyLoad Event -| Parameter| Type | Description | Jump to Demo | -| :------: | :---------------------------: | :----------------------: | ---------------------------- | -| loadMore | `EventEmitter< HTMLElement >` | Required. Trigger the lazy loading response event. | [Lazyload Directive](demo#lazy-load) | +| Parameter | Type | Description | Jump to Demo | +| --------- | ----------------------------- | -------------------------------------------------- | ------------------------------------ | +| loadMore | `EventEmitter< HTMLElement >` | Required. Trigger the lazy loading response event. | [Lazyload Directive](demo#lazy-load) | -# dAutoFocus +## dAutoFocus Automatic focus. -# dDatePipe +## dDatePipe Date parsing. Since the date pipe provided by Angular has some compatibility issues with IE, this component library provides a set of date-fns-based pipe to provide maximum compatibility. @@ -301,68 +296,64 @@ The formatting format is slightly different from the format provided by the Angu -# dSafeNullPipe - -Placeholder display automatically when data is `'', null, undefined`,default is: `--` - -# HelperUtils Static Methods +## HelperUtils Static Methods -## getBrowserName() => void +### getBrowserName() => void Method description: Obtains the current browser name (`IE`|`ClassicEdge`|`Firefox`|`Opera`|`Edge`|`Chrome`|`Safari`|`Other`) -## getBrowserVersion() => void +### getBrowserVersion() => void Method description: Obtaining the Current Browser Version (Major Version) -## jumpOuterUrl(url: string, target?: string) => void +### jumpOuterUrl(url: string, target?: string) => void Method description: This method is used to redirect to an external URL. The router.navigate cannot be redirected to an external system. Window.open is blocked by the browser. This method uses the a tag to simulate the jump. -| Parameter | Type | Default | Description | Jump to Demo | -| :-------: | :------: | :-------: | :---------------------------------------------------------------------------------------------------------------------: | --------------------------------- | -| url | `string` | -- | Required. Redirected URL | -| target | [`TargetType`](#targettype) | '\_black' | Optional. Specifies the target of the hyperlink. | [Helper Utils](demo#helper-utils) | +| Parameter | Type | Default | Description | Jump to Demo | +| --------- | --------------------------- | --------- | ------------------------------------------------ | --------------------------------- | +| url | `string` | -- | Required. Redirected URL | +| target | [`TargetType`](#targettype) | '\_black' | Optional. Specifies the target of the hyperlink. | [Helper Utils](demo#helper-utils) | -## downloadFile(url: string, options?: [DownloadOptionsType](#downloadoptionstype), onError?: (response) => void) +### downloadFile(url: string, options?: [DownloadOptionsType](#downloadoptionstype), onError?: (response) => void) Method description: This method is used to download a file without refreshing the page or pop-up window. Implementation Principle Uses hidden iframes and forms to submit data, which is mainly used for post requests. It can also be used for get requests. It is recommended that the a tag and the download attribute be used for get requests. Note: The browsers use different default opening modes for different types of files. To trigger download, specify Content-Disposition: attachment in the header of the returned file stream response packet. filename=File name.Suffix. -| Parameter | Type | Default | Description | Jump to Demo | -| :---------------: | :--------------------------------: | :---------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | --------------------------------- | -| url | `string` | -- | Required. File address to be downloaded, corresponding to the action of the form. | -| option | `Object` | -- | Optional. | -| option.method | `'POST'\| 'GET' \|'post' \| 'get'` | 'post' | Optional. | [Helper Utils](demo#helper-utils) | -| option.params | `Object` | -- | Optional. The left value type of a parameter object is string, and the right value type of a parameter object is string. Indicates the form field. When the method type is GET/get, parameters are combined to the URL. When the method type is POST/post, parameters are combined to the payload. | [Helper Utils](demo#helper-utils) | -| option.enctype | [`EncType`](#enctype) | 'application/x-www-form-urlencoded' | Optional. Corresponding to the enctype field in the form. | -| option.iframename | `string` | 'download' | Optional. Specifies the iframe name to prevent conflicts with other iframe names. | -| onError | `(response: any) => void` | -- | Optional. It is used for callback when the download fails. The type is (response) => void. The parameter response indicates the error information returned by the request. The response attempts to convert the returned information to JSON. If the download fails, the textContent of the original returned data is returned. | +| Parameter | Type | Default | Description | Jump to Demo | +| ----------------- | ---------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| url | `string` | -- | Required. File address to be downloaded, corresponding to the action of the form. | +| option | `Object` | -- | Optional. | +| option.method | `'POST'\| 'GET' \|'post' \| 'get'` | 'post' | Optional. | [Helper Utils](demo#helper-utils) | +| option.params | `Object` | -- | Optional. The left value type of a parameter object is string, and the right value type of a parameter object is string. Indicates the form field. When the method type is GET/get, parameters are combined to the URL. When the method type is POST/post, parameters are combined to the payload. | [Helper Utils](demo#helper-utils) | +| option.enctype | [`EncType`](#enctype) | 'application/x-www-form-urlencoded' | Optional. Corresponding to the enctype field in the form. | +| option.iframename | `string` | 'download' | Optional. Specifies the iframe name to prevent conflicts with other iframe names. | +| onError | `(response: any) => void` | -- | Optional. It is used for callback when the download fails. The type is (response) => void. The parameter response indicates the error information returned by the request. The response attempts to convert the returned information to JSON. If the download fails, the textContent of the original returned data is returned. | -## downloadFileByHttpClient(httpClient: HttpClient, url: string, options?: [ClientDownloadOptions](#clientdownloadoptions), onError?: (response) => void, onSuccess?: (response) => void) +### downloadFileByHttpClient(httpClient: HttpClient, url: string, options?: [ClientDownloadOptions](#clientdownloadoptions), onError?: (response) => void, onSuccess?: (response) => void) Method description: This method is used to download files without refreshing the page or pop-up window. Implementation Principle: The HTTP client submits a binary stream to obtain the downloaded file and triggers the download. This is mainly used for post requests. It can also be used for get requests. It is recommended that the a tag and the download attribute be used for get requests. Note: The browsers use different default opening modes for different types of files. To trigger download, specify Content-Disposition: attachment for the header of the returned file stream response packet. filename=File name.Suffix. -| Parameter | Type | Default | Description | Jump to Demo | -| :-------------------------------------: | :--------------------------------: | :-----------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | ------------ | -| httpClient | `HttpClient` | -- | Required. HTTP client that provides ingestion. For details, see Remarks 1. | -| url | `string` | -- | Required. File download address. | -| option | `Object` | {} | Optional. | -| option.method | `'POST'\| 'GET' \|'post' \| 'get'` | 'post' | Optional. | -| option.params | `Object` | -- | Optional. The left value type of the parameter object is string, and the right value type of the parameter object is string. Indicates the form field. When the method type is GET/get, parameters are combined to the URL. When the method type is POST/post, parameters are combined to the payload. | -| option.enctype | `enctype` | \'application/x-www-form-urlencoded\' | Optional. Corresponding to the enctype field in the form. | -| option.header | `Object` | -- | Optional. This parameter is used to set the request header. The object of the key-value pair is used to set the header value. The left value is the key of the header option, and the right value is the value of the header option, for example, `{'X-lang':'en'}`. | -| option.responseOption | `response' \| 'body' \| 'json'` | 'json' | Optional. This parameter is used to specify the default format returned when the format fails to be processed. If the format fails to be processed, the format will be degraded. | -| option.filename | `string` | -- | Optional. This parameter is not set by default. It is set to a configuration item first, obtained from filename specified in Content-Type in the response header, and then obtained from the access path. | -| option.withCredentials | `boolean` | false | Optional. Indicates whether to enable xhr.withCredentials when HTTP interfaces are invoked. | -| option.reportProgress | `boolean` | false | is optional. The default value is flase. It indicates whether to monitor the download progress. If it is set to true, the onProgress method can be used to monitor the download progress.| -| option.downloadWithoutDispositionHeader | `boolean` | false | Optional. Content-Disposition: attachment is required in the request header by default. Otherwise, an error occurs in the non-file stream. If this parameter is set to true, the returned response is forcibly returned when the HTTP code in the response header is 2xx. | -| onError | `(res: any) => void` | -- | Optional. It is used for callback when the download fails. The type is (response) => void. The parameter response indicates the error information returned by the request. The response attempts to convert the returned information to JSON. If the download fails, the textcontent of the original returned data is returned. The value is the same as that of downloadFile. | -| onSuccess | `(res: HttpResponse) => void` | -- | Optional. It is used to call back the download success. The type is (response) => void. The parameter response indicates the entire HTTP information returned by the request. The loading type of the body in the response is ArrayBuffer. The body is the downloaded file stream. Therefore, the body is not converted to JSON or attempted to parse the body as text. This is different from onError. | -| onProgress | `(res: HttpProgressEvent) => void` | -- | is optional. It is used to call back the download progress event. The type is (response) => void. The parameter response is the download progress information returned by the request. The loading type of the response is blob.| +| Parameter | Type | Default | Description | Jump to Demo | +| --------------------------------------- | ------------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| httpClient | `HttpClient` | -- | Required. HTTP client that provides ingestion. For details, see Remarks 1. | +| url | `string` | -- | Required. File download address. | +| option | `Object` | {} | Optional. | +| option.method | `'POST'\| 'GET' \|'post' \| 'get'` | 'post' | Optional. | +| option.params | `Object` | -- | Optional. The left value type of the parameter object is string, and the right value type of the parameter object is string. Indicates the form field. When the method type is GET/get, parameters are combined to the URL. When the method type is POST/post, parameters are combined to the payload. | +| option.enctype | `enctype` | \'application/x-www-form-urlencoded\' | Optional. Corresponding to the enctype field in the form. | +| option.header | `Object` | -- | Optional. This parameter is used to set the request header. The object of the key-value pair is used to set the header value. The left value is the key of the header option, and the right value is the value of the header option, for example, `{'X-lang':'en'}`. | +| option.responseOption | `response' \| 'body' \| 'json'` | 'json' | Optional. This parameter is used to specify the default format returned when the format fails to be processed. If the format fails to be processed, the format will be degraded. | +| option.filename | `string` | -- | Optional. This parameter is not set by default. It is set to a configuration item first, obtained from filename specified in Content-Type in the response header, and then obtained from the access path. | +| option.withCredentials | `boolean` | false | Optional. Indicates whether to enable xhr.withCredentials when HTTP interfaces are invoked. | +| option.reportProgress | `boolean` | false | is optional. The default value is flase. It indicates whether to monitor the download progress. If it is set to true, the onProgress method can be used to monitor the download progress. | +| option.downloadWithoutDispositionHeader | `boolean` | false | Optional. Content-Disposition: attachment is required in the request header by default. Otherwise, an error occurs in the non-file stream. If this parameter is set to true, the returned response is forcibly returned when the HTTP code in the response header is 2xx. | +| onError | `(res: any) => void` | -- | Optional. It is used for callback when the download fails. The type is (response) => void. The parameter response indicates the error information returned by the request. The response attempts to convert the returned information to JSON. If the download fails, the textcontent of the original returned data is returned. The value is the same as that of downloadFile. | +| onSuccess | `(res: HttpResponse) => void` | -- | Optional. It is used to call back the download success. The type is (response) => void. The parameter response indicates the entire HTTP information returned by the request. The loading type of the body in the response is ArrayBuffer. The body is the downloaded file stream. Therefore, the body is not converted to JSON or attempted to parse the body as text. This is different from onError. | +| onProgress | `(res: HttpProgressEvent) => void` | -- | is optional. It is used to call back the download progress event. The type is (response) => void. The parameter response is the download progress information returned by the request. The loading type of the response is blob. | How to get httpClient instance: @@ -377,16 +368,16 @@ Internet Explorer 11 does not have TextDecoder. If downloadFileByHttpClient is u import 'fastestsmallesttextencoderdecoder'; ``` -## dSimulateATag +### dSimulateATag - Instruction description: The jumpOuterUrl method of HelperUtils is used to simulate the a tag. -| Parameter | Type | Default | Description | Jump to Demo | -| :-------: | :------: | :-------: | :------------------------------------------------------------------------------------------------------------------------: | ------------ | -| href | `string` | -- | Required. Redirection URL. | -| target | [`TargetType`](#targettype) | '\_blank' | Optional. Specifies the target of the hyperlink.| +| Parameter | Type | Default | Description | Jump to Demo | +| --------- | --------------------------- | --------- | ------------------------------------------------ | ------------ | +| href | `string` | -- | Required. Redirection URL. | +| target | [`TargetType`](#targettype) | '\_blank' | Optional. Specifies the target of the hyperlink. | -## dIframeEventPropagate +### dIframeEventPropagate Usage: Add the instruction to the ancestor element of the iframe to pass the mouse event (click event by default) of the iframe to the ancestor element. @@ -398,20 +389,20 @@ Usage: Add the instruction to the ancestor element of the iframe to pass the mou Instruction description: This command is used to copy a specified text to the clipboard. After the copy is successful, the popover message is displayed. -| Parameter | Type | Default | Description | Jump to Demo | -| :--------: | :----------------------------------------------: | :-----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | ----------------------------------------------- | -| content | `string` | -- | Required. Copied text content. | [Copy to Clipboard Directive](demo#clipboard) | -| position | [`PositionType \| PositionType[]`](#positontype) | 'top' | Optional. If the copy is successful, the pop-up direction is displayed. If the array format is input, a direction is automatically selected based on the input array sequence. | [Copy to Clipboard Directive](demo#clipboard) | -| sticky | `boolean` | false | Optional. The message will not disappear after being popped up until you click another position on the page. By default, the message disappears after 3 seconds. | [Copy to Clipboard Directive](demo#clipboard) | -| tipContent | `string\|HTMLElement\|TemplateRef` | -- | Optional. Display content or template index displayed when a copy is successful. | [Copy to Clipboard Directive](demo#clipboard) | +| Parameter | Type | Default | Description | Jump to Demo | +| ---------- | ------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| content | `string` | -- | Required. Copied text content. | [Copy to Clipboard Directive](demo#clipboard) | +| position | [`PositionType \| PositionType[]`](#positontype) | 'top' | Optional. If the copy is successful, the pop-up direction is displayed. If the array format is input, a direction is automatically selected based on the input array sequence. | [Copy to Clipboard Directive](demo#clipboard) | +| sticky | `boolean` | false | Optional. The message will not disappear after being popped up until you click another position on the page. By default, the message disappears after 3 seconds. | [Copy to Clipboard Directive](demo#clipboard) | +| tipContent | `string\|HTMLElement\|TemplateRef` | -- | Optional. Display content or template index displayed when a copy is successful. | [Copy to Clipboard Directive](demo#clipboard) | ### dClipboard event -| Event | Type | Description | Jump to Demo | -| :-------------: | :-----------------: | :-----------------------------------------------------------------------------------------------------------------------------------------: | ----------------------------------------------- | +| Event | Type | Description | Jump to Demo | +| --------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | copyResultEvent | [`EventEmitter`](#copyresult) | Event sent after copying. If the copy operation is not supported, isSupported is false, which can be used to prompt users to copy the data. | [Copy to Clipboard Directive](demo#clipboard) | -# Interface & Type Definition +## Interface & Type Definition ### DownloadOptionsType @@ -456,7 +447,7 @@ export type TargetType = '_blank' | '_self' | '_parent' | '_top' | ; ### EncType ```ts -export type EncType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text-plain' +export type EncType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text-plain'; ``` ### CopyResult diff --git a/devui/common/helper-utils.ts b/devui/common/helper-utils.ts index d02a3abf..b3d9ab5b 100755 --- a/devui/common/helper-utils.ts +++ b/devui/common/helper-utils.ts @@ -259,7 +259,7 @@ export class HelperUtils { } }; - httpClient.request(requestMethod, requestUrl, requestOption).subscribe((res: HttpEvent) => { + const subscriber = httpClient.request(requestMethod, requestUrl, requestOption).subscribe((res: HttpEvent) => { if (res.type === HttpEventType.DownloadProgress) { if (onProgress) { onProgress(res); @@ -298,6 +298,8 @@ export class HelperUtils { onError(response); } }); + + return subscriber; } private static utf8ArrayToStr(arrayBuffer) { @@ -321,7 +323,7 @@ export class HelperUtils { @Directive({ selector: '[dSimulateATag]' -}) + }) export class SimulateATagDirective { @Input() href: string; @Input() target: '_blank' | '_self' | '_parent' | '_top' | string = '_blank'; diff --git a/devui/data-table/data-table-head.component.html b/devui/data-table/data-table-head.component.html index 8d407bce..8adfb554 100755 --- a/devui/data-table/data-table-head.component.html +++ b/devui/data-table/data-table-head.component.html @@ -48,6 +48,7 @@ [halfchecked]="halfChecked" dPopover [position]="'right'" + [zIndex]="checkOptionsIndex" [visible]="checkOptions?.length && showTip" [content]="checkboxTemplate" > diff --git a/devui/data-table/data-table-head.component.ts b/devui/data-table/data-table-head.component.ts index 7bee436b..d8f4c52c 100755 --- a/devui/data-table/data-table-head.component.ts +++ b/devui/data-table/data-table-head.component.ts @@ -15,12 +15,13 @@ import { DataTableColumnTmplComponent } from './tmpl/data-table-column-tmpl.comp templateUrl: './data-table-head.component.html', styleUrls: ['./data-table-head.component.scss'], preserveWhitespaces: false, -}) + }) export class DataTableHeadComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy, DoCheck { @Input() checkable: boolean; @Input() headerCheckDisabled: boolean; @Input() headerCheckVisible: boolean; @Input() checkOptions: TableCheckOptions[]; + @Input() checkOptionsIndex = 1050; @Input() showExpandToggle: boolean; @Input() pageAllChecked: boolean; @Input() columns: DataTableColumnTmplComponent[]; diff --git a/devui/data-table/data-table-row.component.scss b/devui/data-table/data-table-row.component.scss index 31c114f4..14e0ec4f 100755 --- a/devui/data-table/data-table-row.component.scss +++ b/devui/data-table/data-table-row.component.scss @@ -78,10 +78,6 @@ svg.svg-icon-arrow > g > polygon { margin-left: -5px; padding-left: 5px; min-height: 20px; - - &:hover { - background: $devui-list-item-hover-bg; - } } .expand-icon-wrapper { diff --git a/devui/data-table/data-table.component.html b/devui/data-table/data-table.component.html index 7d659da3..d3d49306 100755 --- a/devui/data-table/data-table.component.html +++ b/devui/data-table/data-table.component.html @@ -13,7 +13,7 @@ class="table-wrap" [style.overflow-x]="'hidden'" [style.scrollbar-width]="'none'" - [style.overflow-y]="tableOverflowType === 'overlay' ? 'overlay' : 'scroll'" + [style.overflow-y]="'scroll'" [style.max-height]="maxHeight ? maxHeight : null" [style.max-width]="maxWidth ? maxWidth : null" [style.width]="!maxWidth ? tableWidth : null" @@ -192,6 +192,7 @@ td { background-clip: padding-box; vertical-align: middle; @@ -338,19 +380,6 @@ $devui-table-inset-shadow-right: var(--devui-table-inset-shadow-right, -8px 0 8p &.editable-cell { position: relative; - - &:hover::after { - content: ''; - display: block; - width: calc(100% - 32px); - position: absolute; - height: calc(100% - 14px); - border: 1px solid $devui-line; - left: 12px; - top: 8px; - border-radius: $devui-border-radius; - pointer-events: none; - } } &.devui-checkable-cell { @@ -500,15 +529,6 @@ $devui-table-inset-shadow-right: var(--devui-table-inset-shadow-right, -8px 0 8p } } -.devui-table ::ng-deep { - thead > tr > th.can-sort { - d-table-filter { - flex: 1; - text-align: right; - } - } -} - .devui-table ::ng-deep { tbody > tr > td.devui-operation-cell { padding: 5px 20px; diff --git a/devui/data-table/data-table.component.ts b/devui/data-table/data-table.component.ts index 5ca97c40..95d0486b 100755 --- a/devui/data-table/data-table.component.ts +++ b/devui/data-table/data-table.component.ts @@ -26,6 +26,7 @@ import { } from '@angular/core'; import { merge, Subscription } from 'rxjs'; import { switchMap, takeUntil } from 'rxjs/operators'; +import { DataTableHeadComponent } from './data-table-head.component'; import { CellSelectedEventArg, CheckableRelation, ColumnResizeEventArg, RowCheckChangeEventArg, RowSelectedEventArg, SortEventArg, TableCheckOptions, TableCheckStatusArg, @@ -37,21 +38,23 @@ import { TableThComponent } from './table/head/th/th.component'; import { TableTheadComponent } from './table/head/thead.component'; import { DataTableColumnTmplComponent } from './tmpl/data-table-column-tmpl.component'; +const SCROLL_BAR_WIDTH = 8; + @Component({ selector: 'd-data-table', templateUrl: './data-table.component.html', styleUrls: [ - './data-table.component.scss', - './data-table.component.color.scss' + './data-table.component.scss', + './data-table.component.color.scss' ], // changeDetection: ChangeDetectionStrategy.OnPush, exportAs: 'dataTable', preserveWhitespaces: false, providers: [{ - provide: DATA_TABLE, - useExisting: forwardRef(() => DataTableComponent) + provide: DATA_TABLE, + useExisting: forwardRef(() => DataTableComponent) }], -}) + }) export class DataTableComponent implements OnDestroy, OnInit, OnChanges, AfterContentInit, AfterViewInit { /** * 【可选】Datatable是否提供勾选行的功能 @@ -70,6 +73,8 @@ export class DataTableComponent implements OnDestroy, OnInit, OnChanges, AfterCo */ @Input() checkOptions: TableCheckOptions[]; + @Input() checkOptionsIndex = 1050; + @Input() selectOptionOnCheckbox = false; /** * 【可选】是否提供显示行详情的功能 @@ -313,6 +318,7 @@ export class DataTableComponent implements OnDestroy, OnInit, OnChanges, AfterCo @ViewChild('cdkVirtualScrollViewport') virtualScrollViewport: CdkVirtualScrollViewport; @ViewChild('normalScroll') normalScrollElement: ElementRef; @ViewChild('scrollViewTpl') vitualScrollElement: TemplateRef; + @ViewChild(DataTableHeadComponent) columnHeaderComponent: DataTableHeadComponent; @ViewChild('devuiNormalScrollBody', {read: ElementRef}) devuiNormalScrollBody: ElementRef; @HostBinding('style.height') get hostHeight() { @@ -442,8 +448,8 @@ export class DataTableComponent implements OnDestroy, OnInit, OnChanges, AfterCo if (this.tableBodyEl) { const tableHeader = this.tableBodyEl.nativeElement.querySelector('thead'); - const tableHeaderHeight = (tableHeader?.offsetHeight + 8) || 0; - const curTotalHeight = this.dataSource.length * this.virtualItemSize + tableHeaderHeight; + const tableHeaderHeight = (tableHeader?.offsetHeight + SCROLL_BAR_WIDTH) || 0; + const curTotalHeight = this.dataSource.length * this.virtualItemSize + tableHeaderHeight + SCROLL_BAR_WIDTH; this.virtualBodyHeight = curTotalHeight < parseInt(this.maxHeight, 10) ? curTotalHeight + 'px' : this.maxHeight; return; } diff --git a/devui/data-table/demo/data-table-demo.module.ts b/devui/data-table/demo/data-table-demo.module.ts index 54c2d757..6aade009 100755 --- a/devui/data-table/demo/data-table-demo.module.ts +++ b/devui/data-table/demo/data-table-demo.module.ts @@ -5,6 +5,7 @@ import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { DevUIModule } from 'ng-devui'; import { AutoCompleteModule } from 'ng-devui/auto-complete'; +import { DCommonModule } from 'ng-devui/common'; import { DataTableModule } from 'ng-devui/data-table'; import { I18nModule } from 'ng-devui/i18n'; import { LoadingModule } from 'ng-devui/loading'; @@ -46,65 +47,66 @@ import { TreeDataComponent } from './tree-table/tree-data.component'; import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component'; @NgModule({ imports: [ - TranslateModule, - CommonModule, - ScrollingModule, - DevUIModule, - FormsModule, - DataTableModule, - DevUICodeboxModule, - DevUIApiModule, - TooltipModule, - AutoCompleteModule, - I18nModule, - DDemoNavModule, - LoadingModule, - SplitterModule, - RouterModule.forChild([ - { path: '', redirectTo: 'demo', pathMatch: 'full' }, - { - path: 'design', - component: DataTableDesignComponent, - }, - { path: 'demo', component: DataTableDemoComponent}, - { path: 'api', component: DevUIApiComponent, data: { - 'zh-cn': require('!html-loader!markdown-loader!../doc/api-cn.md'), - 'en-us': require('!html-loader!markdown-loader!../doc/api-en.md') - }} + TranslateModule, + CommonModule, + ScrollingModule, + DevUIModule, + FormsModule, + DataTableModule, + DevUICodeboxModule, + DevUIApiModule, + TooltipModule, + AutoCompleteModule, + I18nModule, + DDemoNavModule, + LoadingModule, + SplitterModule, + DCommonModule, + RouterModule.forChild([ + { path: '', redirectTo: 'demo', pathMatch: 'full' }, + { + path: 'design', + component: DataTableDesignComponent, + }, + { path: 'demo', component: DataTableDemoComponent}, + { path: 'api', component: DevUIApiComponent, data: { + 'zh-cn': require('!html-loader!markdown-loader!../doc/api-cn.md'), + 'en-us': require('!html-loader!markdown-loader!../doc/api-en.md') + }} ]) ], exports: [DataTableDemoComponent], declarations: [ - DataTableDemoComponent, - DataTableDesignComponent, - DatatableDemoBasicComponent, - DatatableDemoAsyncComponent, - DatatableDemoMaxheightComponent, - DatatableDemoMultiHeaderComponent, - DatatableDemoLazyloadDataComponent, - DatatableDemoEditableComponent, - TreeDataComponent, - ExpandRowComponent, - FixColumnComponent, - DragColumnComponent, - CellMergeComponent, - DragRowComponent, - InteractionComponent, - BasicOldComponent, - HeaderGroupingComponent, - EditableOldComponent, - ExpandRowOldComponent, - TreeTableOldComponent, - FixColumnOldComponent, - MutiDragRowComponent, - CheckOptionsComponent, - CheckOptionsColumnComponent, - InteractionColumnComponent, - VirtualScrollComponent, - MutilStylesComponent, - FixHeightVirtualScrollComponent, - DynamicColsDemoComponent, + DataTableDemoComponent, + DataTableDesignComponent, + DatatableDemoBasicComponent, + DatatableDemoAsyncComponent, + DatatableDemoMaxheightComponent, + DatatableDemoMultiHeaderComponent, + DatatableDemoLazyloadDataComponent, + DatatableDemoEditableComponent, + TreeDataComponent, + ExpandRowComponent, + FixColumnComponent, + DragColumnComponent, + CellMergeComponent, + DragRowComponent, + InteractionComponent, + BasicOldComponent, + HeaderGroupingComponent, + EditableOldComponent, + ExpandRowOldComponent, + TreeTableOldComponent, + FixColumnOldComponent, + MutiDragRowComponent, + CheckOptionsComponent, + CheckOptionsColumnComponent, + InteractionColumnComponent, + VirtualScrollComponent, + MutilStylesComponent, + FixHeightVirtualScrollComponent, + DynamicColsDemoComponent, ], providers: [], -}) + }) export class DataTableDemoModule {} diff --git a/devui/data-table/demo/editable-old/editable-old.component.html b/devui/data-table/demo/editable-old/editable-old.component.html index 07971ef4..2545ee0b 100644 --- a/devui/data-table/demo/editable-old/editable-old.component.html +++ b/devui/data-table/demo/editable-old/editable-old.component.html @@ -11,7 +11,8 @@
- + +
@@ -19,7 +20,7 @@ - {{ cellItem | i18nDate: 'full':false }} + {{ cellItem | i18nDate : 'full' : false }} @@ -69,6 +70,7 @@ [options]="genderSource" [isSearch]="true" [filterKey]="'label'" + [appendToBody]="true" [autoFocus]="true" [toggleOnFocus]="true" [(ngModel)]="rowItem[column.field]" diff --git a/devui/data-table/demo/editable/data-table-demo-editable.component.html b/devui/data-table/demo/editable/data-table-demo-editable.component.html index 8f39c72b..6d27da6e 100755 --- a/devui/data-table/demo/editable/data-table-demo-editable.component.html +++ b/devui/data-table/demo/editable/data-table-demo-editable.component.html @@ -22,9 +22,10 @@ > {{ rowItem?.lastName }}
+ - {{ rowItem?.dob | i18nDate: 'short':false }} + {{ rowItem?.dob | i18nDate : 'short' : false }}
diff --git a/devui/data-table/demo/max-height/data-table-demo-maxheight.component.html b/devui/data-table/demo/max-height/data-table-demo-maxheight.component.html index 4c24ebbe..3a99a957 100755 --- a/devui/data-table/demo/max-height/data-table-demo-maxheight.component.html +++ b/devui/data-table/demo/max-height/data-table-demo-maxheight.component.html @@ -1,4 +1,11 @@ - + {{ colOption.header }} @@ -8,7 +15,7 @@ - {{ colOption.fieldType === 'date' ? (rowItem[colOption.field] | i18nDate: 'short':false) : rowItem[colOption.field] }} + {{ colOption.fieldType === 'date' ? (rowItem[colOption.field] | i18nDate : 'short' : false) : rowItem[colOption.field] }} diff --git a/devui/data-table/doc/api-cn.md b/devui/data-table/doc/api-cn.md index aeacdeb0..68f61485 100644 --- a/devui/data-table/doc/api-cn.md +++ b/devui/data-table/doc/api-cn.md @@ -43,6 +43,7 @@ import { DataTableModule } from 'ng-devui/data-table'; | tableWidthConfig | [`TableWidthConfig[]`](#tablewidthconfig) | [] | 可选,配置表格的列宽,在含有子表格如树形表格的情况下必选 | [基本用法](demo#basic-usage) | | checkable | `boolean` | -- | 可选,Datatable是否提供勾选行的功能 | [表格交互](demo#table-interaction) | | checkOptions | [`TableCheckOptions[]`](#tablecheckoptions) | -- | 可选,表头选中的下拉项及操作 | [自定义表格选中操作](demo#table-check-options) | +| checkOptionsIndex | `number` | 1050 | 可选,表头选中的下拉的z-index | -- | | headerCheckDisabled | `boolean` | -- | 可选,表头checkbox是否disabled | | headerCheckVisible | `boolean` | true | 可选,表头checkbox是否可见 | | showExpandToggle | `boolean` | -- | 可选,是否提供显示扩展行的功能,为true则在配置了扩展行的行前面生成操作按钮 | [扩展行](demo#expand-row) | @@ -116,6 +117,7 @@ import { DataTableModule } from 'ng-devui/data-table'; | :-----------: | :-------------------: | :----- | :------------------------------------------------------------------: | :------------------------------------------------------------------- | | checkable | `boolean` | -- | 可选,在表头第一列显示checkbox,用于全选,可以跟行数据的选中状态联动 | [表格交互](demo#table-interaction) | | checkOptions | `TableCheckOptions[]` | -- | 可选,表头选中的下拉项及操作 | [自定义表格选中操作](demo#table-check-options) | +| checkOptionsIndex | `number` | 1050 | 可选,表头选中的下拉的z-index | -- | | checkDisabled | `boolean` | -- | 可选,表头checkbox是否disabled | @@ -623,6 +625,9 @@ toggleAllNodesExpand(e) { ``` javascript @ViewChild('VirtualTableTree') VirtualTableTree: VirtualScrollTreeTableComponent; +// 搜索所有列字段为'all', 搜索指定几个列字段用['name', 'attr'] +searchAttr: 'all' | string[]; + searchSelectChange() { this.BigTableTree.searchAttr = this.searchAttr; this.BigTableTree.searchSelectChange(); diff --git a/devui/data-table/doc/api-en.md b/devui/data-table/doc/api-en.md index c90a232c..45f14390 100644 --- a/devui/data-table/doc/api-en.md +++ b/devui/data-table/doc/api-en.md @@ -43,6 +43,7 @@ On the page: | tableWidthConfig | [`TableWidthConfig[]`](#tablewidthconfig) | [] | Optional. It is used to configure the column width of the table.This parameter is mandatory when subtables such as tree tables are included. | [Basic usage](demo#basic-usage) | | checkable | `boolean` | -- | Optional. Whether the Datatable provides the function of selecting rows. | [Table interaction](demo#table-interaction) | | checkOptions | [`TableCheckOptions[]`](#tablecheckoptions) | -- | Optional. drop-down list box in the table header and operations | [Customized table selection](demo#table-check-options) | +| checkOptionsIndex | `number` | 1050 | (Optional) z-index in the drop-down list box selected in the table header | -- | | headerCheckDisabled | `boolean` | -- | Optional. Indicates whether the header checkbox is disabled. | | headerCheckVisible | `boolean` | true | Optional. Indicates whether the checkbox in the header is visible. | | showExpandToggle | `boolean` | -- |: Indicates whether to display extended rows. If the value is true, an operation button is generated before the row where extended rows are configured. | [Extended row](demo#expand-row) | @@ -116,6 +117,7 @@ On the page: | :-----------: | :-------------------: | :----- | :------------------------------------------------------------------: | :------------------------------------------------------------------- | | checkable | `boolean` | -- | Optional. The checkbox is displayed in the first column of the table header for selecting all data. The checkbox can be associated with the selection status of the row data. | [Table interaction](demo#table-interaction) | | checkOptions | `TableCheckOptions[]` | -- | Optional. Drop-down list box in the table header and operations | [Customized table selection](demo#table-check-options) | +| checkOptionsIndex | `number` | 1050 | (Optional) z-index in the drop-down list box selected in the table header | -- | | checkDisabled | `boolean` | -- | Optional. Indicates whether the header checkbox is disabled. | ## dHeadCell Parameter @@ -608,6 +610,8 @@ Use @ViewChild to invoke the search function ``` javascript @ViewChild('VirtualTableTree') VirtualTableTree: VirtualScrollTreeTableComponent; +searchAttr: 'all' | string[]; + searchSelectChange() { this.BigTableTree.searchAttr = this.searchAttr; this.BigTableTree.searchSelectChange(); diff --git a/devui/data-table/table-option-modal.component.scss b/devui/data-table/table-option-modal.component.scss index 55baae27..4f0adf73 100644 --- a/devui/data-table/table-option-modal.component.scss +++ b/devui/data-table/table-option-modal.component.scss @@ -77,6 +77,7 @@ .devui-option-panel-col-drag { flex: 1; overflow: hidden; + scrollbar-gutter: stable; &:hover { overflow-y: auto; diff --git a/devui/data-table/table/body/td/td.component.scss b/devui/data-table/table/body/td/td.component.scss index 007941a2..f2b6738a 100644 --- a/devui/data-table/table/body/td/td.component.scss +++ b/devui/data-table/table/body/td/td.component.scss @@ -11,19 +11,6 @@ svg.svg-icon-arrow > g > polygon { :host.editable-cell { position: relative; - - &:hover::after { - content: ''; - display: block; - width: calc(100% - 32px); - position: absolute; - height: calc(100% - 14px); - border: 1px solid $devui-line; - left: 12px; - top: 8px; - border-radius: $devui-border-radius; - pointer-events: none; - } } .cell-modify { @@ -97,10 +84,6 @@ svg.svg-icon-arrow > g > polygon { margin-left: -5px; padding-left: 5px; min-height: 20px; - - &:hover { - background: $devui-list-item-hover-bg; - } } .cell-placeholder { diff --git a/devui/data-table/table/head/th/filter/filter.component.html b/devui/data-table/table/head/th/filter/filter.component.html index cc8362d3..86fe2532 100644 --- a/devui/data-table/table/head/th/filter/filter.component.html +++ b/devui/data-table/table/head/th/filter/filter.component.html @@ -95,7 +95,7 @@
-
+
{{ i18nCommonText?.btnOk }}
diff --git a/devui/data-table/table/head/th/filter/filter.component.scss b/devui/data-table/table/head/th/filter/filter.component.scss index 6b3f4248..b4f9f642 100644 --- a/devui/data-table/table/head/th/filter/filter.component.scss +++ b/devui/data-table/table/head/th/filter/filter.component.scss @@ -4,6 +4,7 @@ :host { text-align: right; + margin-left: 8px; &.devui-icon-show { display: block; @@ -42,8 +43,8 @@ .line { height: 1px; background: $devui-dividing-line; - margin-top: 5px; - margin-bottom: 5px; + margin-top: 4px; + margin-bottom: 4px; } .checkbox-group { diff --git a/devui/data-table/table/head/th/sort/sort.component.scss b/devui/data-table/table/head/th/sort/sort.component.scss index f5760c94..bdfd57f1 100644 --- a/devui/data-table/table/head/th/sort/sort.component.scss +++ b/devui/data-table/table/head/th/sort/sort.component.scss @@ -6,11 +6,7 @@ } :host { - height: 20px; - line-height: 20px; - margin-right: 4px; margin-left: 8px; - flex: 1; display: none; &.devui-icon-show { diff --git a/devui/data-table/table/head/th/th.component.html b/devui/data-table/table/head/th/th.component.html index 20db64fd..2f6669c6 100644 --- a/devui/data-table/table/head/th/th.component.html +++ b/devui/data-table/table/head/th/th.component.html @@ -34,7 +34,7 @@ - + - + - + diff --git a/devui/data-table/table/head/th/th.component.ts b/devui/data-table/table/head/th/th.component.ts index 8ec59d2f..30296ed3 100644 --- a/devui/data-table/table/head/th/th.component.ts +++ b/devui/data-table/table/head/th/th.component.ts @@ -14,10 +14,10 @@ import { TABLE_TH } from './th.token'; templateUrl: './th.component.html', styleUrls: ['./th.component.scss'], providers: [{ - provide: TABLE_TH, - useExisting: forwardRef(() => TableThComponent) + provide: TABLE_TH, + useExisting: forwardRef(() => TableThComponent) }], -}) + }) export class TableThComponent implements OnChanges, OnDestroy { @HostBinding('class.resizeable') resizeEnabledClass = false; @HostBinding('class.operable') operableClass = false; @@ -84,6 +84,7 @@ export class TableThComponent implements OnChanges, OnDestroy { resizeOverlay: HTMLElement; nextElement: any; initialWidth: number; + initialOffset: number; totalWidth: number; mouseDownScreenX: number; resizeHandleElement: HTMLElement; @@ -217,7 +218,7 @@ export class TableThComponent implements OnChanges, OnDestroy { this.resizeStartEvent.emit(event); // emit begin resize event this.initialWidth = this.element.clientWidth; - const initialOffset = this.element.offsetLeft; + this.initialOffset = this.element.offsetLeft; this.mouseDownScreenX = event.clientX; event.stopPropagation(); this.nextElement = this.element.nextElementSibling; @@ -236,10 +237,12 @@ export class TableThComponent implements OnChanges, OnDestroy { this.renderer2.addClass(resizeBar, 'resize-bar'); this.tableElement = this.tableViewRefElement.nativeElement.querySelector('.devui-scrollbar table'); + + this.initialOffset = this.initialOffset - this.tableElement.parentElement.scrollLeft; if (this.tableElement) { this.renderer2.appendChild(this.tableElement, resizeBar); this.renderer2.setStyle(resizeBar, 'display', 'block'); - this.renderer2.setStyle(resizeBar, 'left', initialOffset + this.initialWidth - 2 + 'px'); + this.renderer2.setStyle(resizeBar, 'left', this.initialOffset + this.initialWidth - 2 + 'px'); this.resizeBarRefElement = resizeBar; } @@ -248,7 +251,7 @@ export class TableThComponent implements OnChanges, OnDestroy { if (this.tableHeaderElement) { this.renderer2.appendChild(this.tableHeaderElement, resizeBar); this.renderer2.setStyle(resizeBar, 'display', 'block'); - this.renderer2.setStyle(resizeBar, 'left', initialOffset + this.initialWidth - 2 + 'px'); + this.renderer2.setStyle(resizeBar, 'left', this.initialOffset + this.initialWidth - 2 + 'px'); } this.renderer2.addClass(this.element, 'hover-bg'); @@ -304,7 +307,7 @@ export class TableThComponent implements OnChanges, OnDestroy { const finalWidth = this.getFinalWidth(newWidth); if (this.resizeBarRefElement) { - this.renderer2.setStyle(this.resizeBarRefElement, 'left', `${finalWidth + this.element.offsetLeft}px`); + this.renderer2.setStyle(this.resizeBarRefElement, 'left', `${finalWidth + this.initialOffset}px`); } this.resizingEvent.emit({ width: finalWidth }); } diff --git a/devui/data-table/table/head/thead.component.ts b/devui/data-table/table/head/thead.component.ts index 13fcac67..f1c05a83 100644 --- a/devui/data-table/table/head/thead.component.ts +++ b/devui/data-table/table/head/thead.component.ts @@ -10,11 +10,12 @@ import { TableThComponent } from './th/th.component'; /* eslint-disable-next-line @angular-eslint/component-selector*/ selector: '[dTableHead]', templateUrl: './thead.component.html' -}) + }) export class TableTheadComponent implements OnInit, AfterContentInit, OnDestroy, OnChanges { @Input() checkable: boolean; @Input() checkDisabled: boolean; @Input() checkOptions: TableCheckOptions[]; + @Input() checkOptionsIndex = 1050; @Input() selectOptionOnCheckbox = false; @ContentChildren(TableTrComponent) headerRowList: QueryList; @ContentChildren(TableThComponent, { descendants: true }) thList: QueryList; @@ -38,6 +39,7 @@ export class TableTheadComponent implements OnInit, AfterContentInit, OnDestroy, this.headerFirstRow.headerCheckable = this.checkable; this.headerFirstRow.headerCheckDisabled = this.checkDisabled; this.headerFirstRow.headerCheckOptions = this.checkOptions; + this.headerFirstRow.checkOptionsIndex = this.checkOptionsIndex; this.headerFirstRow.selectOptionOnCheckbox = this.selectOptionOnCheckbox; this.checkStatusSubscription = this.headerFirstRow.headerCheckStatusEvent.subscribe((status) => { diff --git a/devui/data-table/table/row/tr.component.html b/devui/data-table/table/row/tr.component.html index d50c65a7..682c087a 100644 --- a/devui/data-table/table/row/tr.component.html +++ b/devui/data-table/table/row/tr.component.html @@ -25,6 +25,7 @@ [halfchecked]="headerHalfChecked" [disabled]="headerCheckDisabled" dPopover + [zIndex]="checkOptionsIndex" [position]="'right'" [visible]="headerCheckOptions?.length && showTip" [content]="checkboxTemplate" diff --git a/devui/data-table/table/row/tr.component.ts b/devui/data-table/table/row/tr.component.ts index 0de7e37c..c39ae319 100644 --- a/devui/data-table/table/row/tr.component.ts +++ b/devui/data-table/table/row/tr.component.ts @@ -9,13 +9,14 @@ import { TableThComponent } from '../head/th/th.component'; selector: '[dTableRow]', templateUrl: './tr.component.html', styleUrls: ['./tr.component.scss'] -}) + }) export class TableTrComponent implements OnInit, AfterViewInit { @ContentChildren(TableThComponent) thList: QueryList; headerCheckable: boolean; headerCheckDisabled: boolean; headerRowspan = 1; headerCheckOptions: TableCheckOptions[]; + checkOptionsIndex = 1050; curLabel = ''; showTip = false; selectOptionOnCheckbox = false; diff --git a/devui/datepicker-pro/datepicker-pro.component.ts b/devui/datepicker-pro/datepicker-pro.component.ts index 94f4ae11..7523ed9f 100644 --- a/devui/datepicker-pro/datepicker-pro.component.ts +++ b/devui/datepicker-pro/datepicker-pro.component.ts @@ -26,15 +26,15 @@ import { DateConfig } from './lib/datepicker-pro.type'; templateUrl: './datepicker-pro.component.html', styleUrls: ['./datepicker-pro.component.scss'], providers: [ - DatepickerProService, - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DatepickerProComponent), - multi: true - } + DatepickerProService, + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatepickerProComponent), + multi: true + } ], preserveWhitespaces: false, -}) + }) export class DatepickerProComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor { @Input() mode: 'year' | 'month' | 'date' = 'date'; @Input() showTime = false; @@ -193,7 +193,7 @@ export class DatepickerProComponent implements OnInit, AfterViewInit, OnDestroy, } const inputDate = this.datepickerConvert.parse(this.dateValue, this.curFormat); - if (inputDate instanceof Date && inputDate.getTime() === this.pickerSrv.curDate.getTime()) { + if (inputDate instanceof Date && inputDate.getTime() === this.pickerSrv.curDate?.getTime()) { return; } diff --git a/devui/datepicker-pro/lib/calendar-panel.component.scss b/devui/datepicker-pro/lib/calendar-panel.component.scss index 5a876a90..630bc1bc 100644 --- a/devui/datepicker-pro/lib/calendar-panel.component.scss +++ b/devui/datepicker-pro/lib/calendar-panel.component.scss @@ -118,7 +118,7 @@ &.devui-table-date-today { span { - color: $devui-brand; + color: $devui-info; } } diff --git a/devui/datepicker-pro/lib/timepicker-panel.component.scss b/devui/datepicker-pro/lib/timepicker-panel.component.scss index 840a4591..5e85779f 100644 --- a/devui/datepicker-pro/lib/timepicker-panel.component.scss +++ b/devui/datepicker-pro/lib/timepicker-panel.component.scss @@ -40,27 +40,23 @@ .devui-time-list { flex: 1; - overflow-x: hidden; - overflow-y: scroll; - overflow-y: overlay; + overflow: hidden; scroll-behavior: auto; - padding-right: 12px; - scrollbar-width: none; + padding-right: 6px; + scrollbar-gutter: stable; + scrollbar-width: thin; &:last-child { padding-right: 0; } &::-webkit-scrollbar { - width: 0 !important; + width: $devui-scrollbar-width-sm; } &:hover { - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: 4px !important; - } + overflow-y: scroll; + overflow-y: overlay; } .devui-time-item { diff --git a/devui/datepicker/datepicker-cdk-overlay.component.ts b/devui/datepicker/datepicker-cdk-overlay.component.ts index e9a0ea6f..1671a410 100755 --- a/devui/datepicker/datepicker-cdk-overlay.component.ts +++ b/devui/datepicker/datepicker-cdk-overlay.component.ts @@ -5,7 +5,6 @@ import { Component, ElementRef, EventEmitter, - forwardRef, HostListener, Inject, Input, @@ -16,21 +15,22 @@ import { Renderer2, SimpleChanges, TemplateRef, - ViewContainerRef + ViewContainerRef, + forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { I18nInterface, I18nService } from 'ng-devui/i18n'; import { - addClassToOrigin, AppendToBodyDirection, AppendToBodyDirectionsConfig, DateConverter, DefaultDateConverter, + addClassToOrigin, fadeInOut, formWithDropDown, removeClassFromOrigin } from 'ng-devui/utils'; -import { fromEvent, Observable, Subscription } from 'rxjs'; +import { Observable, Subscription, fromEvent } from 'rxjs'; import { debounceTime, filter, map } from 'rxjs/operators'; import { SelectDateChangeEventArgs, SelectDateChangeReason } from './date-change-event-args.model'; import { DatePickerConfigService as DatePickerConfig } from './date-picker.config.service'; @@ -39,9 +39,9 @@ import { DatePickerConfigService as DatePickerConfig } from './date-picker.confi /* eslint-disable-next-line @angular-eslint/component-selector*/ selector: '[dDatepicker][appendToBody]', providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DatePickerAppendToBodyComponent), - multi: true + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatePickerAppendToBodyComponent), + multi: true }], exportAs: 'datepicker', template: ` @@ -61,11 +61,11 @@ import { DatePickerConfigService as DatePickerConfig } from './date-picker.confi `, animations: [ - fadeInOut + fadeInOut ], styleUrls: ['./datepicker-cdk-overlay.component.scss'], preserveWhitespaces: false, -}) + }) export class DatePickerAppendToBodyComponent implements OnInit, OnChanges, OnDestroy, ControlValueAccessor { @Input() appendToBodyDirections: Array = [ 'rightDown', 'leftDown', 'rightUp', 'leftUp' @@ -76,7 +76,7 @@ export class DatePickerAppendToBodyComponent implements OnInit, OnChanges, OnDes @Input() dateConverter: DateConverter; @Input() customViewTemplate: TemplateRef; @Input() autoOpen = false; - @Input() showAnimation = true; + @Input() showAnimation = false; @Output() selectedDateChange = new EventEmitter(); selectedDate: Date; _isOpen = false; diff --git a/devui/datepicker/datepicker.directive.ts b/devui/datepicker/datepicker.directive.ts index 814b3fc4..de676129 100755 --- a/devui/datepicker/datepicker.directive.ts +++ b/devui/datepicker/datepicker.directive.ts @@ -38,13 +38,13 @@ import { DatepickerComponent } from './datepicker.component'; selector: '[dDatepicker]:not([appendToBody])', exportAs: 'datepicker', providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DatepickerDirective), - multi: true, - }, + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatepickerDirective), + multi: true, + }, ], -}) + }) export class DatepickerDirective implements OnInit, OnDestroy, ControlValueAccessor { @Input() locale: string; @Input() cssClass: string; @@ -55,7 +55,7 @@ export class DatepickerDirective implements OnInit, OnDestroy, ControlValueAcces @Input() direction: 'up' | 'down' = 'down'; @Input() customViewTemplate: TemplateRef; @Input() autoOpen = false; - @Input() @WithConfig() showAnimation = true; + @Input() @WithConfig() showAnimation = false; @Output() selectedDateChange = new EventEmitter(); selectedDate: Date; private _isOpen = false; diff --git a/devui/drawer/demo/basic/basic.component.ts b/devui/drawer/demo/basic/basic.component.ts index 00af2d71..90e68cb3 100755 --- a/devui/drawer/demo/basic/basic.component.ts +++ b/devui/drawer/demo/basic/basic.component.ts @@ -6,7 +6,7 @@ import { DrawerContentComponent } from '../drawerContent/drawer-content.componen @Component({ selector: 'd-basic', templateUrl: './basic.component.html', -}) + }) export class BasicComponent { results: IDrawerOpenResult; constructor(private drawerService: DrawerService, private dialogService: DialogService) { @@ -16,7 +16,7 @@ export class BasicComponent { openDrawer() { this.results = this.drawerService.open({ drawerContentComponent: DrawerContentComponent, - width: '300px', + width: '30%', zIndex: 1000, isCover: true, fullScreen: true, diff --git a/devui/drawer/drawer.component.scss b/devui/drawer/drawer.component.scss index c8eebeb4..d734bf91 100755 --- a/devui/drawer/drawer.component.scss +++ b/devui/drawer/drawer.component.scss @@ -50,11 +50,19 @@ .drawer-content { left: 0; right: 0; - overflow: hidden; box-shadow: $devui-shadow-length-fullscreen-overlay $devui-shadow; + overflow: auto; + scrollbar-color: transparent transparent; + + &::-webkit-scrollbar-thumb { + background-color: transparent; + } &:hover { - overflow: auto; - overflow: overlay; + scrollbar-color: $devui-placeholder transparent; + + &::-webkit-scrollbar-thumb { + background-color: $devui-placeholder; + } } } diff --git a/devui/drawer/drawer.component.ts b/devui/drawer/drawer.component.ts index d9e47817..67ba0783 100755 --- a/devui/drawer/drawer.component.ts +++ b/devui/drawer/drawer.component.ts @@ -16,12 +16,12 @@ import { } from '@angular/core'; import { backdropFadeInOut, flyInOut } from 'ng-devui/utils'; import { isNumber, parseInt, trim } from 'lodash-es'; -import { fromEvent, Observable, Subject, Subscription } from 'rxjs'; +import { Observable, Subject, Subscription, fromEvent } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; @Directive({ selector: '[dDrawerContentHost]', -}) + }) export class DrawerContentDirective { constructor(public viewContainerRef: ViewContainerRef) {} } @@ -32,7 +32,7 @@ export class DrawerContentDirective { styleUrls: ['./drawer.component.scss'], animations: [backdropFadeInOut, flyInOut], preserveWhitespaces: false, -}) + }) export class DrawerComponent implements OnInit, OnDestroy { animateState = 'void'; @Input() id: string; @@ -59,10 +59,12 @@ export class DrawerComponent implements OnInit, OnDestroy { oldWidth: string; _isCover: boolean; subscription: Subscription; + isFullScreen: boolean; animationDone = new Subject(); animationDoneSub: Subscription; resizeSub: Subscription; + windowResizeSub: Subscription; documentOverFlow: boolean; scrollTop: number; scrollLeft: number; @@ -98,6 +100,15 @@ export class DrawerComponent implements OnInit, OnDestroy { const widthStr = trim(width, '%'); const widthNum = parseInt(widthStr, 10); this._width = isNumber(widthNum) ? (widthNum * window.innerWidth) / 100 + 'px' : '0px'; + if (!this.windowResizeSub) { + this.windowResizeSub = fromEvent(window, 'resize') + .pipe(debounceTime(100)) + .subscribe(() => { + if (!this.isFullScreen) { + this.setWidth(this.width); + } + }); + } } else { this._width = width; } @@ -112,6 +123,10 @@ export class DrawerComponent implements OnInit, OnDestroy { if (this.resizeSub) { this.resizeSub.unsubscribe(); } + + if (this.windowResizeSub) { + this.windowResizeSub.unsubscribe(); + } } @HostListener('document:keydown.escape', ['$event']) keydownHandler(event: KeyboardEvent) { @@ -246,6 +261,7 @@ export class DrawerComponent implements OnInit, OnDestroy { const drawerContainerEle = this.drawerContainer.nativeElement; if (this._width === this.oldWidth) { if (fullScreen === true || fullScreen === undefined) { + this.isFullScreen = true; this._width = this._isCover ? '100%' : window.innerWidth + 'px'; this.renderer.setStyle( drawerContainerEle, @@ -262,6 +278,7 @@ export class DrawerComponent implements OnInit, OnDestroy { } } else { if (!fullScreen) { + this.isFullScreen = false; this._width = this.oldWidth; if (this.resizeSub) { this.resizeSub.unsubscribe(); diff --git a/devui/form/doc/api-cn.md b/devui/form/doc/api-cn.md index 9fdc6618..a3014e61 100644 --- a/devui/form/doc/api-cn.md +++ b/devui/form/doc/api-cn.md @@ -47,46 +47,46 @@ import { FormsModule } from '@angular/forms'; ### dForm 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | -| :----------: | :-----------------------------------: | :----------: | :------------------------------------------------------------------------: | :------------------------------------------- | ---------- | -| layout | `'horizontal'\|'vertical'\|'columns'` | 'horizontal' | 可选,设置表单的排列方式 | [基本用法](demo#basic-usage) | -| labelSize | `'sm' \| '' \| 'lg'` | '' | 可选,设置 label 的占宽,未设置默认为 100px,'sm'对应 80px,'lg'对应 150px | [label 横向排列](demo#demo-label-horizontal) | -| labelAlign | `'start' \| 'center' \| 'end'` | 'start' | 可选,设置水平布局方式下,label 对齐方式 | [label 横向排列](demo#demo-label-horizontal) | -| dHasFeedback | `boolean` | false | 可选,设置当前 form 是否显示反馈图标 | | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| ------------ | ------------------------------------- | ------------ | -------------------------------------------------------------------------- | -------------------------------------------- | ---------- | +| layout | `'horizontal'\|'vertical'\|'columns'` | 'horizontal' | 可选,设置表单的排列方式 | [基本用法](demo#basic-usage) | +| labelSize | `'sm' \| '' \| 'lg'` | '' | 可选,设置 label 的占宽,未设置默认为 100px,'sm'对应 80px,'lg'对应 150px | [label 横向排列](demo#demo-label-horizontal) | +| labelAlign | `'start' \| 'center' \| 'end'` | 'start' | 可选,设置水平布局方式下,label 对齐方式 | [label 横向排列](demo#demo-label-horizontal) | +| dHasFeedback | `boolean` | false | 可选,设置当前 form 是否显示反馈图标 | | ### dForm 事件 -| 参数 | 类型 | 说明 | 跳转 Demo | -| :-----: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------- | ------------------------------------------------------- | +| 参数 | 类型 | 说明 | 跳转 Demo | +| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------- | | dSubmit | `EventEmitter<{valid: boolean, directive: `[`DFormGroupRuleDirective`](#dformgroupruledirective) `\| AbstractControlDirective}, errors: {[key: string]: ValidationErrors}>` | 可选,使用 dFormSubmit 绑定元素触发提交时,响应事件 | [模板驱动表单验证(推荐)](demo#demo-validate-template) | ## d-form-item ### d-form-item 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :----------: | :-------: | :---: | :------------------------------------------ | --------------------------------------------- | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ------------ | --------- | ----- | ------------------------------------------- | --------------------------------------------- | | dHasFeedback | `boolean` | false | 可选,设置当前 formControl 是否显示反馈图标 | [响应式表单验证](demo#demo-validate-reactive) | ## d-form-label ### d-form-label 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :------: | :-------: | :---: | :------------------------------------------------- | ---------------------------- | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| -------- | --------- | ----- | -------------------------------------------------- | ---------------------------- | | required | `boolean` | false | 可选,表单选项是否必填 | [基本用法](demo#basic-usage) | | hasHelp | `boolean` | false | 可选,表单项是否需要帮助指引 | [基本用法](demo#basic-usage) | -| helpTips | `string` | '' | 可选,表单项帮助指引提示内容,需配合 `hasHelp`使用 | [基本用法](demo#basic-usage) | +| helpTips | `string` | '' | 可选,表单项帮助指引提示内容,需配合 `hasHelp`使用 | [基本用法](demo#basic-usage) | ## d-form-control ### d-form-control 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :------------: | :-----------------------------------------: | :--: | :----------------------------------------- | -------------------------------------------- | -| extraInfo | `string \| TemplateRef` | -- | 可选,附件信息,一般用于补充表单选项的说明 | [label 横向排列](demo#demo-label-horizontal) | -| feedbackStatus | [`DFormControlStatus`](#dformcontrolstatus) | -- | 可选,手动指定当前 control 状态反馈 | [指定表单状态](demo#demo-custom-status) | -| suffixTemplate | `TemplateRef` | -- | 可选,可传入图标模板作为输入框后缀 | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| -------------- | ------------------------------------------- | ---- | ------------------------------------------ | -------------------------------------------- | +| extraInfo | `string \| TemplateRef` | -- | 可选,附件信息,一般用于补充表单选项的说明 | [label 横向排列](demo#demo-label-horizontal) | +| feedbackStatus | [`DFormControlStatus`](#dformcontrolstatus) | -- | 可选,手动指定当前 control 状态反馈 | [指定表单状态](demo#demo-custom-status) | +| suffixTemplate | `TemplateRef` | -- | 可选,可传入图标模板作为输入框后缀 | ## dFormSubmit @@ -95,10 +95,10 @@ import { FormsModule } from '@angular/forms'; ### dFormSubmit 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :-------------: | :------: | :-----: | :-------------------------------------------------------------------: | :-------------------------------------------: | -| dFormSubmit | `string` | 'click' | 可选,配置用于触发 submit 的事件名 | [响应式表单验证](demo#demo-validate-reactive) | -| dFormSubmitData | `any` | -- | 可选,配置需要传递与 dSubmit 回调事件数据,可用于需区分多个按钮的场景 | [响应式表单验证](demo#demo-validate-reactive) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| --------------- | -------- | ------- | --------------------------------------------------------------------- | --------------------------------------------- | +| dFormSubmit | `string` | 'click' | 可选,配置用于触发 submit 的事件名 | [响应式表单验证](demo#demo-validate-reactive) | +| dFormSubmitData | `any` | -- | 可选,配置需要传递与 dSubmit 回调事件数据,可用于需区分多个按钮的场景 | [响应式表单验证](demo#demo-validate-reactive) | ## dFormReset @@ -107,8 +107,8 @@ import { FormsModule } from '@angular/forms'; ### dFormReset 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :--------: | :------: | :-----: | :--------------------------------: | :-------: | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ---------- | -------- | ------- | ---------------------------------- | --------- | | dFormReset | `string` | 'click' | 可选,配置用于触发 submit 的事件名 | | ## dValidateRules 表单验证 @@ -133,22 +133,22 @@ import { FormsModule } from '@angular/forms'; ### dValidateRules 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :----------------: | :---------------------------------: | :--: | :--------------------: | :-----------------------------------------------------: | -| dValidateRules | [`DValidateRules`](#dvalidaterules) | -- | 必选,配置你的校验规则 | [模板驱动表单验证(推荐)](demo#demo-validate-template) | -| dValidatePopConfig | [`DPopConfig`](#dpopconfig) | -- | 可选,popover 提示配置 | [模板驱动表单验证(推荐)](demo#demo-validate-template) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ------------------ | ----------------------------------- | ---- | ---------------------- | ------------------------------------------------------- | +| dValidateRules | [`DValidateRules`](#dvalidaterules) | -- | 必选,配置你的校验规则 | [模板驱动表单验证(推荐)](demo#demo-validate-template) | +| dValidatePopConfig | [`DPopConfig`](#dpopconfig) | -- | 可选,popover 提示配置 | [模板驱动表单验证(推荐)](demo#demo-validate-template) | ### dValidateSyncKey 参数 表单协同校验。 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :--------------: | :------: | :--: | :-----------------------------------------------------------------------------------------------------------: | :-------------------------------------: | -| dValidateSyncKey | `string` | -- | 必选,配置唯一标识 key,相同 key 表单元素将在其中一个元素值发生变更时,同时触发校验,支持响应式与模板驱动表单 | [表单协同验证](demo#demo-validate-sync) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ---------------- | -------- | ---- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| dValidateSyncKey | `string` | -- | 必选,配置唯一标识 key,相同 key 表单元素将在其中一个元素值发生变更时,同时触发校验,支持响应式与模板驱动表单 | [表单协同验证](demo#demo-validate-sync) | -# dActiveFormControl +### dActiveFormControl -给业务的自定义容器添加该directive,可以获得与其他组件表单(如text-input, select, cascader)同样的点击交互效果。 +给业务的自定义容器添加该 directive,可以获得与其他组件表单(如 text-input, select, cascader)同样的点击交互效果。 ## 封装的校验规则 @@ -169,20 +169,21 @@ const rules: DValidateRules = { }; // 自定义校验器的写法可以参考下方代码 -// public static contains(contain: string | number): ValidatorFn { -// return (control: AbstractControl): ValidationErrors | null => { -// if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(contain)) { -// return null; -// } -// return control.value.indexOf(contain) === -1 ? { contains: { requiredContains: contain,actualValue: control.value } } : null; -// }; -// } -// public static alphabet(control: AbstractControl): ValidationErrors | null { -// if (DValidators.isEmptyInput(control.value)) { -// return null; -// } -// return DValidators.AlphabetPattern.test(control.value) ? null : { alphabet: true }; -// } +public static contains(contain: string | number): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(contain)) { + return null; + } + return control.value.indexOf(contain) === -1 ? { contains: { requiredContains: contain,actualValue: control.value } } : null; + }; +} + +public static alphabet(control: AbstractControl): ValidationErrors | null { + if (DValidators.isEmptyInput(control.value)) { + return null; + } + return DValidators.AlphabetPattern.test(control.value) ? null : { alphabet: true }; +} ``` ```html @@ -192,22 +193,22 @@ const rules: DValidateRules = { ### 校验规则 -| 校验器 | 说明 | -| :---------: | :----------------------------: | -| contains | 校验是否包含 | -| notContains | 校验是否不包含 | -| equal | 校验是否等于 | -| notEqual | 校验是否不等于 | -| port | 校验端口号是否属于 [0, 65535] | -| date | 校验日期是否合法 | -| url | 校验 url 是否合法 | -| integer | 校验是否是整数 | -| digits | 校验是否是数字 | -| number | 校验是否是数字,包括科学计数法 | -| alphabet | 校验是否是字母 | -| script | 校验是否是 script 标签 | -| ipv4 | 校验 ipv4 地址是否合法 | -| ipv6 | 校验 ipv6 地址是否合法 | +| 校验器 | 说明 | +| ----------- | ------------------------------ | +| contains | 校验是否包含 | +| notContains | 校验是否不包含 | +| equal | 校验是否等于 | +| notEqual | 校验是否不等于 | +| port | 校验端口号是否属于 [0, 65535] | +| date | 校验日期是否合法 | +| url | 校验 url 是否合法 | +| integer | 校验是否是整数 | +| digits | 校验是否是数字 | +| number | 校验是否是数字,包括科学计数法 | +| alphabet | 校验是否是字母 | +| script | 校验是否是 script 标签 | +| ipv4 | 校验 ipv4 地址是否合法 | +| ipv6 | 校验 ipv6 地址是否合法 | ## 接口 & 类型定义 @@ -351,8 +352,9 @@ export const dDefaultValidators = { ```TS export type DPopConfig = { - popMaxWidth?: number, + popMaxWidth?: number; scrollElement?: Element; - zIndex?: number, + zIndex?: number; + showAnimation?: boolean; } ``` diff --git a/devui/form/doc/api-en.md b/devui/form/doc/api-en.md index 5eaa7a54..5ef3250e 100644 --- a/devui/form/doc/api-en.md +++ b/devui/form/doc/api-en.md @@ -47,46 +47,46 @@ In the page ### dForm Parameter -| Parameter | Type | Default | Description | Jump to Demo | Global Config | -| :----------: | :-----------------------------------: | :----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------- | ------------- | -| layout | `'horizontal'\|'vertical'\|'columns'` | 'horizontal' | Optional. Sets the form arrangement mode. | [Basic usage](demo#basic-usage) | -| labelSize | `'sm' \| '' \| 'lg'` | '' | Optional. Sets the width of the label. If this parameter is not set, the default value is 100 px. 'sm' corresponds to 80 px, 'lg' corresponds to 150px | [Label horizontal arrangement](demo#demo-label-horizontal) | -| labelAlign | `'start'\|'center'\|'end'` | 'start' | Optional. This parameter specifies the label alignment mode in horizontal layout mode. | [label horizontal arrangement](demo#demo-label-horizontal) | -| dHasFeedback | `boolean` | false | Optional. Sets whether to display the feedback icon for the current form. | | +| Parameter | Type | Default | Description | Jump to Demo | Global Config | +| ------------ | ------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------------- | +| layout | `'horizontal'\|'vertical'\|'columns'` | 'horizontal' | Optional. Sets the form arrangement mode. | [Basic usage](demo#basic-usage) | +| labelSize | `'sm' \| '' \| 'lg'` | '' | Optional. Sets the width of the label. If this parameter is not set, the default value is 100 px. 'sm' corresponds to 80 px, 'lg' corresponds to 150px | [Label horizontal arrangement](demo#demo-label-horizontal) | +| labelAlign | `'start'\|'center'\|'end'` | 'start' | Optional. This parameter specifies the label alignment mode in horizontal layout mode. | [label horizontal arrangement](demo#demo-label-horizontal) | +| dHasFeedback | `boolean` | false | Optional. Sets whether to display the feedback icon for the current form. | | ### dForm Event -| Parameter | Type | Description | Jump to Demo | -| :-------: | :------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| dSubmit | `EventEmitter<{valid: boolean, directive: `[`DFormGroupRuleDirective`](#dformgroupruledirective) `\| AbstractControlDirective}>` | Optional. This event is responded to when the dFormSubmit binding element is used to trigger submission. | [Template-driven form verification (recommended)](demo#demo-validate-template) | +| Parameter | Type | Description | Jump to Demo | +| --------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| dSubmit | `EventEmitter<{valid: boolean, directive: `[`DFormGroupRuleDirective`](#dformgroupruledirective) `\| AbstractControlDirective}>` | Optional. This event is responded to when the dFormSubmit binding element is used to trigger submission. | [Template-driven form verification (recommended)](demo#demo-validate-template) | ## d-form-item ### d-form-item parameter -| Parameter | Type | Default | Description | Jump to Demo | -| :----------: | :-------: | :-----: | :-------------------------------------------------------------------------------- | ------------------------------------------------------- | -| dHasFeedback | `boolean` | false | Optional. Sets whether to display the feedback icon for the current form control. | [Reactive form validation](demo#demo-validate-reactive) | +| Parameter | Type | Default | Description | Jump to Demo | +| ------------ | --------- | ------- | --------------------------------------------------------------------------------- | ------------------------------------------------------- | +| dHasFeedback | `boolean` | false | Optional. Sets whether to display the feedback icon for the current form control. | [Reactive form validation](demo#demo-validate-reactive) | ## d-form-label ### d-form-label parameter -| Parameter | Type | Default | Description | Jump to Demo | -| :-------: | :-------: | :-----: | :--------------------------------------------------------- | ------------------------------- | -| required | `boolean` | false | Optional. Indicating whether the form option is mandatory. | [Basic usage](demo#basic-usage) | -| hasHelp | `boolean` | false | Optional. Indicating whether a form item requires help. | [Basic usage](demo#basic-usage) | -| helpTips | `string` | '' | Optional. This parameter is used together with `hasHelp`. | [Basic usage](demo#basic-usage) | +| Parameter | Type | Default | Description | Jump to Demo | +| --------- | --------- | ------- | ---------------------------------------------------------- | ------------------------------- | +| required | `boolean` | false | Optional. Indicating whether the form option is mandatory. | [Basic usage](demo#basic-usage) | +| hasHelp | `boolean` | false | Optional. Indicating whether a form item requires help. | [Basic usage](demo#basic-usage) | +| helpTips | `string` | '' | Optional. This parameter is used together with `hasHelp`. | [Basic usage](demo#basic-usage) | ## d-form-control ### d-form-control parameters -| Parameter | Type | Default | Description | Jump to Demo | -| :------------: | :-----------------------------------------: | :-----: | :---------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -| extraInfo | `string \| TemplateRef` | -- | Optional. attachment information, which is used to supplement the description of table options. | [Label horizontal arrangement](demo#demo-label-horizontal) | -| feedbackStatus | [`DFormControlStatus`](#dformcontrolstatus) | -- | Optional. Manually specify the current control status. | [Specify form status](demo#demo-custom-status) | -| suffixTemplate | `TemplateRef` | -- | Optional. Pass icon template to be the suffix of Input. | +| Parameter | Type | Default | Description | Jump to Demo | +| -------------- | ------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| extraInfo | `string \| TemplateRef` | -- | Optional. attachment information, which is used to supplement the description of table options. | [Label horizontal arrangement](demo#demo-label-horizontal) | +| feedbackStatus | [`DFormControlStatus`](#dformcontrolstatus) | -- | Optional. Manually specify the current control status. | [Specify form status](demo#demo-custom-status) | +| suffixTemplate | `TemplateRef` | -- | Optional. Pass icon template to be the suffix of Input. | ## dFormSubmit @@ -95,10 +95,10 @@ In the page ### dFormSubmit Parameter -| Parameter | Type | Default | Description | Jump to Demo | -| :-------------: | :------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------: | -| dFormSubmit | `string` | 'click' | Optional. Configure the event name used to trigger submit. | [Reactive form validation](demo#demo-validate-reactive) | -| dFormSubmitData | `any` | -- | Optional. Configure the data that needs to be transferred and the dSubmit callback event, which can be used to distinguish multiple buttons. | [Reactive form validation](demo#demo-validate-reactive) | +| Parameter | Type | Default | Description | Jump to Demo | +| --------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| dFormSubmit | `string` | 'click' | Optional. Configure the event name used to trigger submit. | [Reactive form validation](demo#demo-validate-reactive) | +| dFormSubmitData | `any` | -- | Optional. Configure the data that needs to be transferred and the dSubmit callback event, which can be used to distinguish multiple buttons. | [Reactive form validation](demo#demo-validate-reactive) | ## dFormReset @@ -107,8 +107,8 @@ In the page ### dFormReset Parameter -| Parameter | Type | Default | Description | Jump to Demo | -| :--------: | :------: | :-----: | :-------------------------------------------------------: | :----------: | +| Parameter | Type | Default | Description | Jump to Demo | +| ---------- | -------- | ------- | --------------------------------------------------------- | ------------ | | dFormReset | `string` | 'click' | Optional. Configure the event name for triggering submit. | | ## dValidateRules Form Validation @@ -133,20 +133,20 @@ import { Forms } from '@angular/forms'; ### dValidateRules Parameter -| Parameter | Type | Default | Description | Jump to Demo | -| :----------------: | :---------------------------------: | :-----: | :----------------------------------------: | :----------------------------------------------------------------------------: | -| dValidateRules | [`DValidateRules`](#dvalidaterules) | -- | Required. Configure the verification rule. | [Template-driven form verification (recommended)](demo#demo-validate-template) | -| dValidatePopConfig | [`DPopConfig`](#dpopconfig) | -- | Optional. popover hint config | [Template-driven form verification (recommended)](demo#demo-validate-template) | +| Parameter | Type | Default | Description | Jump to Demo | +| ------------------ | ----------------------------------- | ------- | ------------------------------------------ | ------------------------------------------------------------------------------ | +| dValidateRules | [`DValidateRules`](#dvalidaterules) | -- | Required. Configure the verification rule. | [Template-driven form verification (recommended)](demo#demo-validate-template) | +| dValidatePopConfig | [`DPopConfig`](#dpopconfig) | -- | Optional. popover hint config | [Template-driven form verification (recommended)](demo#demo-validate-template) | ### dValidateSyncKey Parameter Collaborative form validation. -| Parameter | Type | Default | Description | Jump to Demo | -| :--------------: | :------: | :-----: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------: | -| dValidateSyncKey | `string` | -- | Required. This parameter is mandatory. It specifies the unique key. When the value of one element in the form of the same key changes, verification is triggered. responsive and template-driven forms are supported. | [Form collaboration verification](demo#demo-validate-sync) | +| Parameter | Type | Default | Description | Jump to Demo | +| ---------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| dValidateSyncKey | `string` | -- | Required. This parameter is mandatory. It specifies the unique key. When the value of one element in the form of the same key changes, verification is triggered. responsive and template-driven forms are supported. | [Form collaboration verification](demo#demo-validate-sync) | -# dActiveFormControl +## dActiveFormControl Add the direct to the customized container of the service to obtain the same click interaction effect as other component forms (such as text-input, select, and cascader). @@ -169,20 +169,21 @@ const rules: DValidateRules = { }; // You can custom your validator like following -// public static contains(contain: string | number): ValidatorFn { -// return (control: AbstractControl): ValidationErrors | null => { -// if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(contain)) { -// return null; -// } -// return control.value.indexOf(contain) === -1 ? { contains: { requiredContains: contain,actualValue: control.value } } : null; -// }; -// } -// public static alphabet(control: AbstractControl): ValidationErrors | null { -// if (DValidators.isEmptyInput(control.value)) { -// return null; -// } -// return DValidators.AlphabetPattern.test(control.value) ? null : { alphabet: true }; -// } +public static contains(contain: string | number): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (DValidators.isEmptyInput(control.value) || DValidators.isEmptyInput(contain)) { + return null; + } + return control.value.indexOf(contain) === -1 ? { contains: { requiredContains: contain,actualValue: control.value } } : null; + }; +} + +public static alphabet(control: AbstractControl): ValidationErrors | null { + if (DValidators.isEmptyInput(control.value)) { + return null; + } + return DValidators.AlphabetPattern.test(control.value) ? null : { alphabet: true }; +} ``` ```html @@ -192,22 +193,22 @@ const rules: DValidateRules = { ### Validators -| Validator | Description | -| :---------: | :----------------------------------------------------: | -| contains | Check if it contains sth. | -| notContains | Check if it not contains sth. | -| equal | Check if it is equal to sth. | -| notEqual | Check if it is not equal to sth. | -| port | Check if a port number is in [0, 65535] | -| date | Check if a date is valid | -| url | Check if a url is valid | -| integer | Check integer | -| digits | Check digit | -| number | Check if it is a number, including scientific notation | -| alphabet | Check if it is an alphabet | -| script | Check if it is a script tag | -| ipv4 | Check if it is a valid ipv4 address | -| ipv6 | Check if it is a valid ipv6 address | +| Validator | Description | +| ----------- | ------------------------------------------------------ | +| contains | Check if it contains sth. | +| notContains | Check if it not contains sth. | +| equal | Check if it is equal to sth. | +| notEqual | Check if it is not equal to sth. | +| port | Check if a port number is in [0, 65535] | +| date | Check if a date is valid | +| url | Check if a url is valid | +| integer | Check integer | +| digits | Check digit | +| number | Check if it is a number, including scientific notation | +| alphabet | Check if it is an alphabet | +| script | Check if it is a script tag | +| ipv4 | Check if it is a valid ipv4 address | +| ipv6 | Check if it is a valid ipv6 address | ## Interface & Type Definition @@ -352,8 +353,9 @@ export const dDefaultValidators = { ```TS export type DPopConfig = { - popMaxWidth?: number, + popMaxWidth?: number; scrollElement?: Element; - zIndex?: number, + zIndex?: number; + showAnimation?: boolean; } ``` diff --git a/devui/form/validator-directive/form-control-rules.directive.ts b/devui/form/validator-directive/form-control-rules.directive.ts index 818c8966..57e21a8b 100644 --- a/devui/form/validator-directive/form-control-rules.directive.ts +++ b/devui/form/validator-directive/form-control-rules.directive.ts @@ -14,7 +14,7 @@ import { Self, SimpleChanges, SkipSelf, - TemplateRef, + TemplateRef } from '@angular/core'; import { AbstractControl, @@ -23,7 +23,7 @@ import { ControlContainer, NgControl, ValidationErrors, - ValidatorFn, + ValidatorFn } from '@angular/forms'; import { I18nInterface, I18nService } from 'ng-devui/i18n'; import { OverlayContainerRef } from 'ng-devui/overlay-container'; @@ -40,7 +40,7 @@ import { DValidateRule, DValidateRules, DValidationErrorStrategy, - ruleReservedWords, + ruleReservedWords } from './validate.type'; @Directive() @@ -459,7 +459,7 @@ const dControlErrorStatusHost = { /* eslint-disable-next-line @angular-eslint/no-host-metadata-property*/ host: dControlErrorStatusHost, exportAs: 'dValidateRules', -}) + }) export class DFormGroupRuleDirective extends DAbstractControlRuleDirective implements OnInit, OnChanges, OnDestroy { @Input('dValidateRules') rules: DValidateRules; @Output() dRulesStatusChange: EventEmitter = new EventEmitter(); @@ -504,7 +504,7 @@ export class DFormGroupRuleDirective extends DAbstractControlRuleDirective imple /* eslint-disable-next-line @angular-eslint/no-host-metadata-property*/ host: dControlErrorStatusHost, exportAs: 'dValidateRules', -}) + }) export class DFormControlRuleDirective extends DAbstractControlRuleDirective implements OnInit, OnChanges, OnDestroy { @Input('dValidateRules') rules: DValidateRules; @Output() dRulesStatusChange: EventEmitter = new EventEmitter(); @@ -628,8 +628,10 @@ export class DFormControlRuleDirective extends DAbstractControlRuleDirective imp position: this.popPosition, popType: type, popMaxWidth: this.popConfig?.popMaxWidth || 200, - appendToBody: true, + scrollElement: this.popConfig?.scrollElement, zIndex: this.popConfig?.zIndex || 1060, + showAnimation: this.popConfig?.showAnimation ?? true, + appendToBody: true, }); } diff --git a/devui/form/validator-directive/validate.type.ts b/devui/form/validator-directive/validate.type.ts index 9e3f1e25..e9d0217c 100644 --- a/devui/form/validator-directive/validate.type.ts +++ b/devui/form/validator-directive/validate.type.ts @@ -31,7 +31,8 @@ export interface DValidateRule { priority?: number; isNgValidator?: boolean; validateLevel?: 'error' | 'warning'; // 校验级别 - [id: string]: boolean | number | string | { [key: string]: string } | RegExp | DValidatorFn | ValidatorFn | undefined | TemplateRef; // 万能key + [id: string]: boolean | number | string | { [key: string]: string } + | RegExp | DValidatorFn | ValidatorFn | undefined | TemplateRef; // 万能key } export interface DAsyncValidateRule { id?: string; @@ -41,7 +42,16 @@ export interface DAsyncValidateRule { priority?: number; isNgValidator?: boolean; validateLevel?: 'error' | 'warning'; // 校验级别 - [id: string]: boolean | number | string | { [key: string]: string } | RegExp | DAsyncValidatorFn | AsyncValidatorFn | undefined | TemplateRef; // 万能key + [id: string]: + | boolean + | number + | string + | { [key: string]: string } + | RegExp + | DAsyncValidatorFn + | AsyncValidatorFn + | undefined + | TemplateRef; // 万能key } // TODO: 还需提供一个debounceTime @@ -84,14 +94,12 @@ export type DValidateRules = } | DValidateRule[]; - export interface DValidateErrorStatus { errorMessage: string | { [key: string]: string } | null; showError: boolean; errors: { [key: string]: any }; } - /* TODO: 这里是否需要导出 */ export const ruleReservedWords = [ 'id', @@ -117,9 +125,9 @@ export const dDefaultValidators = { whitespace: DValidators.whiteSpace, }; - export interface DPopConfig { popMaxWidth?: number; scrollElement?: Element; zIndex?: number; + showAnimation?: boolean; } diff --git a/devui/i18n/en-us.ts b/devui/i18n/en-us.ts index 61dba8dc..ae4ec829 100644 --- a/devui/i18n/en-us.ts +++ b/devui/i18n/en-us.ts @@ -191,6 +191,9 @@ export default { getBeyondMaximalFileSizeMsg(filename, maximalSize) { return `Maximum file size (MB): ${maximalSize}. Files whose size exceeds the maximum value: ${filename}`; }, + getBeyondMaximumFileCountMsg(maximalSize){ + return `Maximum file count: ${maximalSize}. Files exceeds the maximum value`; + }, getAllFilesBeyondMaximalFileSizeMsg(maximalSize) { return `Maximum file size (MB): ${maximalSize}. The selected files exceed the maximum value`; }, @@ -274,6 +277,7 @@ export default { tagsInput: { tagsReachMaxLength: 'The length of tags has reached maxTags', tagsReachMaxNumber: 'Maximum number reached: ', + create: 'Create' }, categorySearch: { confirm: 'Confirm', diff --git a/devui/i18n/i18n.model.ts b/devui/i18n/i18n.model.ts index bcee66f5..f65765e5 100644 --- a/devui/i18n/i18n.model.ts +++ b/devui/i18n/i18n.model.ts @@ -99,6 +99,7 @@ export interface I18nInterface { cancelUpload: string; getNotAllowedFileTypeMsg(filename, scope): string; getBeyondMaximalFileSizeMsg(filename, maximalSize): string; + getBeyondMaximumFileCountMsg(maximalSize): string; getExistSameNameFilesMsg(sameNames): string; getAllFilesBeyondMaximalFileSizeMsg(maximalSize): string; getSelectedFilesCount(filesCount): string; @@ -156,6 +157,7 @@ export interface I18nInterface { tagsInput: { tagsReachMaxLength: string; tagsReachMaxNumber: string; + create: string; }; categorySearch: { confirm: string; diff --git a/devui/i18n/zh-cn.ts b/devui/i18n/zh-cn.ts index c77c0a1c..f2c209c4 100644 --- a/devui/i18n/zh-cn.ts +++ b/devui/i18n/zh-cn.ts @@ -177,6 +177,9 @@ export default { getBeyondMaximalFileSizeMsg(filename, maximalSize) { return `最大支持上传${maximalSize}MB的文件, 您上传的文件"${filename}"超过可上传文件大小`; }, + getBeyondMaximumFileCountMsg(maximalSize) { + return `最大支持上传${maximalSize}个文件, 您上传的文件数量超过最大限制`; + }, getAllFilesBeyondMaximalFileSizeMsg(maximalSize) { return `最大支持上传${maximalSize}MB的文件, 您本次上传的所有文件超过可上传文件大小`; }, @@ -260,6 +263,7 @@ export default { tagsInput: { tagsReachMaxLength: '您输入的标签已达到最大长度限制', tagsReachMaxNumber: '已达到最大个数:', + create: '生成' }, categorySearch: { confirm: '确定', diff --git a/devui/icon/icon.component.scss b/devui/icon/icon.component.scss index 50fd0cef..48db39af 100644 --- a/devui/icon/icon.component.scss +++ b/devui/icon/icon.component.scss @@ -75,6 +75,14 @@ margin-right: $devui-icon-space; } +::ng-deep .devui-icon-container:not(.disabled) span[iconPrefix] { + color: $devui-text; +} + +::ng-deep .devui-icon-container:not(.disabled) span[iconSuffix] { + color: $devui-text; +} + ::ng-deep .devui-icon-container [iconSuffix] { vertical-align: middle; margin-left: $devui-icon-space; @@ -102,7 +110,7 @@ cursor: pointer; a { - color: $devui-aide-text; + color: $devui-text; } &:hover { diff --git a/devui/input-group/input-group.component.scss b/devui/input-group/input-group.component.scss index 3ccfc8d7..79f4e946 100644 --- a/devui/input-group/input-group.component.scss +++ b/devui/input-group/input-group.component.scss @@ -15,22 +15,12 @@ $border-change-function: $devui-animation-ease-in-out-smooth; [dTextInput], d-select { margin-right: -1px; - z-index: 0; &, & > div.devui-dropdown-origin { border-radius: 0 !important; } - &:hover { - z-index: 1; - } - - &:focus, - &:has(:focus) { - z-index: 2; - } - &:first-child { border-top-#{$first}-radius: #{$firstValue} !important; border-bottom-#{$first}-radius: #{$firstValue} !important; @@ -52,6 +42,19 @@ $border-change-function: $devui-animation-ease-in-out-smooth; } } } + + [dTextInput] { + z-index: 0; + + &:hover { + z-index: 1; + } + + &:focus, + &:has(:focus) { + z-index: 2; + } + } } @mixin embedSpliceInner($direction) { diff --git a/devui/modal/demo/modal-demo.module.ts b/devui/modal/demo/modal-demo.module.ts index 9079763c..8a76f4e2 100755 --- a/devui/modal/demo/modal-demo.module.ts +++ b/devui/modal/demo/modal-demo.module.ts @@ -15,7 +15,6 @@ import { DDemoNavModule } from 'devui-commons/src/demo-nav/d-demo-nav.module'; import { AutofocusComponent } from './autofocus/autofocus.component'; import { BasicUpdateComponent } from './basic-update/basic-update.component'; import { BasicComponent } from './basic/basic.component'; -import { MaximizeComponent } from './maximize/maximize.component'; import { CasesComponent } from './cases/cases.component'; import { ModalCasesComponent } from './cases/modal-cases.component'; import { CustomizeComponent } from './customize/customize.component'; @@ -24,6 +23,7 @@ import { ModalNoBtnComponent } from './customize/modal-no-btn.component'; import { FixedWrapperComponent } from './fixed/fixed-wrapper.component'; import { HideComponent } from './hide/hide.component'; import { ModalFormComponent } from './hide/modal-form.component'; +import { MaximizeComponent } from './maximize/maximize.component'; import { ModalDemoComponent } from './modal-demo.component'; import { ModalDesignComponent } from './modal-design.component'; import { ModalTestComponent } from './modal-test.component'; @@ -81,7 +81,7 @@ import { TipsComponent } from './tips/tips.component'; BasicUpdateComponent, FixedWrapperComponent, CasesComponent, - ModalCasesComponent, + ModalCasesComponent ], }) export class ModalDemoModule {} diff --git a/devui/modal/dialog.service.ts b/devui/modal/dialog.service.ts index 29d431e2..2ee70299 100755 --- a/devui/modal/dialog.service.ts +++ b/devui/modal/dialog.service.ts @@ -1,5 +1,11 @@ import { DOCUMENT } from '@angular/common'; -import { ComponentFactoryResolver, ComponentRef, Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; +import { + ComponentFactoryResolver, + ComponentRef, + Inject, + Injectable, + Renderer2, RendererFactory2 +} from '@angular/core'; import { OverlayContainerRef } from 'ng-devui/overlay-container'; import { DevConfigService } from 'ng-devui/utils'; import { assign, isUndefined } from 'lodash-es'; @@ -13,13 +19,10 @@ export class DialogService { private renderer: Renderer2; document: Document; - constructor( - private componentFactoryResolver: ComponentFactoryResolver, - private overlayContainerRef: OverlayContainerRef, - private rendererFactory: RendererFactory2, - private devConfigService: DevConfigService, - @Inject(DOCUMENT) private doc: any - ) { + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private overlayContainerRef: OverlayContainerRef, private rendererFactory: RendererFactory2, + private devConfigService: DevConfigService, + @Inject(DOCUMENT) private doc: any) { this.renderer = this.rendererFactory.createRenderer(null, null); this.document = this.doc; } @@ -94,14 +97,11 @@ export class DialogService { offsetX, offsetY, bodyScrollable, - escapable, + escapable }); - const modalContainerRef = modalRef.instance.modalContainerHost.viewContainerRef.createComponent( - finalComponentFactoryResolver.resolveComponentFactory(ModalContainerComponent), - 0, - injector - ); + const modalContainerRef = modalRef.instance.modalContainerHost.viewContainerRef + .createComponent(finalComponentFactoryResolver.resolveComponentFactory(ModalContainerComponent), 0, injector); assign(modalContainerRef.instance, { title, buttons, maxHeight, dialogtype, showCloseBtn, showMaximizeBtn }); if (contentTemplate) { @@ -110,9 +110,8 @@ export class DialogService { if (typeof content === 'string') { assign(modalContainerRef.instance, { content, html }); } else { - this.contentRef = modalContainerRef.instance.modalContentHost.viewContainerRef.createComponent( - finalComponentFactoryResolver.resolveComponentFactory(content) - ); + this.contentRef = modalContainerRef.instance.modalContentHost.viewContainerRef + .createComponent(finalComponentFactoryResolver.resolveComponentFactory(content)); assign(this.contentRef.instance, { data, dialogtype }); } } diff --git a/devui/modal/modal-container.component.scss b/devui/modal/modal-container.component.scss index 128cb96e..ac03a442 100755 --- a/devui/modal/modal-container.component.scss +++ b/devui/modal/modal-container.component.scss @@ -3,6 +3,7 @@ .modal-body { padding: 12px 32px 12px 32px; overflow-y: hidden; + scrollbar-gutter: stable; &:hover { overflow-y: auto; diff --git a/devui/modal/modal-container.component.ts b/devui/modal/modal-container.component.ts index 34289d31..fdfc6952 100755 --- a/devui/modal/modal-container.component.ts +++ b/devui/modal/modal-container.component.ts @@ -1,4 +1,10 @@ -import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { + Component, + Input, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { IButtonStyle } from 'ng-devui/button'; import { ModalComponent } from './modal.component'; @@ -59,7 +65,7 @@ export class ModalContainerComponent implements OnInit { updateButtonOptions(buttonOptions = []) { this.buttons = this.buttons.map((button, index) => { - return { ...button, ...buttonOptions[index] }; + return {...button, ...buttonOptions[index]}; }); } } diff --git a/devui/modal/modal-header.component.ts b/devui/modal/modal-header.component.ts index 9ef8537d..29ea742d 100755 --- a/devui/modal/modal-header.component.ts +++ b/devui/modal/modal-header.component.ts @@ -1,4 +1,12 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output +} from '@angular/core'; import { I18nInterface, I18nService } from 'ng-devui/i18n'; import { Subscription } from 'rxjs'; @Component({ diff --git a/devui/modal/modal.component.ts b/devui/modal/modal.component.ts index 804681b8..7122f797 100755 --- a/devui/modal/modal.component.ts +++ b/devui/modal/modal.component.ts @@ -2,17 +2,21 @@ import { DOCUMENT } from '@angular/common'; import { Component, ElementRef, Inject, Input, OnDestroy, OnInit, Renderer2, TemplateRef, ViewChild } from '@angular/core'; import { backdropFadeInOut, wipeInOutAnimation } from 'ng-devui/utils'; import { isUndefined } from 'lodash-es'; -import { Observable, Subscription, fromEvent } from 'rxjs'; +import { fromEvent, Observable, Subscription } from 'rxjs'; import { ModalContainerDirective } from './modal.directive'; @Component({ selector: 'd-modal', templateUrl: './modal.component.html', styleUrls: ['./modal.component.scss'], - animations: [backdropFadeInOut, wipeInOutAnimation], + animations: [ + backdropFadeInOut, + wipeInOutAnimation + ], preserveWhitespaces: false, -}) + }) export class ModalComponent implements OnInit, OnDestroy { + @Input() id: string; @Input() showAnimation = true; @Input() width: string; @@ -45,20 +49,24 @@ export class ModalComponent implements OnInit, OnDestroy { maximized = false; _oldWidth: string; - constructor(private elementRef: ElementRef, private renderer: Renderer2, @Inject(DOCUMENT) private doc: any) { - this.backdropCloseable = isUndefined(this.backdropCloseable) ? true : this.backdropCloseable; + constructor( + private elementRef: ElementRef, + private renderer: Renderer2, + @Inject(DOCUMENT) private doc: any + ) { + this.backdropCloseable = isUndefined(this.backdropCloseable) + ? true + : this.backdropCloseable; this.document = this.doc; } ngOnInit() { if (this.escapable) { - this.pressEscToClose.add( - fromEvent(window, 'keydown').subscribe((event) => { - if (event['keyCode'] === 27) { - this.hide(); - } - }) - ); + this.pressEscToClose.add(fromEvent(window, 'keydown').subscribe((event) => { + if (event['keyCode'] === 27) { + this.hide(); + } + })); } const handle = this.elementRef.nativeElement.querySelector('#d-modal-header'); @@ -70,9 +78,11 @@ export class ModalComponent implements OnInit, OnDestroy { } // Will overwrite this method in modal service - onHidden() {} + onHidden() { + } - updateButtonOptions(buttonOptions: Array) {} + updateButtonOptions(buttonOptions: Array) { + } canHideModel() { let hiddenResult = Promise.resolve(true); @@ -95,12 +105,8 @@ export class ModalComponent implements OnInit, OnDestroy { onModalClick = ($event) => { // 一定要document.contains($event.target),因为$event.target可能已经不在document里了,这个时候就不能进hide了,使用document.body兼容IE - if ( - this.backdropCloseable && - !this.ignoreBackDropClick && - !this.dialogElement.nativeElement.contains($event.target) && - this.document.body.contains($event.target) - ) { + if (this.backdropCloseable && !this.ignoreBackDropClick && + (!this.dialogElement.nativeElement.contains($event.target) && this.document.body.contains($event.target))) { this.hide(); } this.ignoreBackDropClick = false; @@ -165,16 +171,16 @@ export class ModalComponent implements OnInit, OnDestroy { resolveTransformTranslate() { let autoOffsetYByPlacement; switch (this.placement) { - case 'top': - autoOffsetYByPlacement = '40px'; - break; - case 'bottom': - autoOffsetYByPlacement = '-40px'; - break; - case 'center': - default: - autoOffsetYByPlacement = '0'; - break; + case 'top': + autoOffsetYByPlacement = '40px'; + break; + case 'bottom': + autoOffsetYByPlacement = '-40px'; + break; + case 'center': + default: + autoOffsetYByPlacement = '0'; + break; } const offsetX = this.offsetX ? this.offsetX : '0'; const offsetY = this.offsetY ? this.offsetY : autoOffsetYByPlacement; diff --git a/devui/modal/modal.spec.ts b/devui/modal/modal.spec.ts index e0aaa51b..bef4331a 100644 --- a/devui/modal/modal.spec.ts +++ b/devui/modal/modal.spec.ts @@ -1,5 +1,5 @@ import { Component, DebugElement, Input, TemplateRef, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, flush, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -20,7 +20,7 @@ import { IDialogOptions, IModalOptions } from './modal.types';
I am Template
`, -}) + }) class TestDialogComponent { @ViewChild('testContentTemplate') testContentTemplate: TemplateRef; @@ -269,6 +269,7 @@ describe('dialog', () => { component.dialogConfig.bodyScrollable = true; fixture.detectChanges(); + debugEl.query(By.css('.devui-btn')).nativeElement.dispatchEvent(new Event('click')); fixture.detectChanges(); @@ -294,9 +295,6 @@ describe('dialog', () => { expect(buttonEle2.querySelector('.button-content').textContent.trim()).toBe('testDialogBtn2'); expect(buttonEle1.style.width).toBe('300px'); expect(buttonEle2.style.width).toBe('100px'); - // buttons的disabled不测 - expect(document.activeElement).toBeTruthy(); - expect(document.activeElement.classList.contains('devui-btn')).toBe(true); expect(modalWrapper.style.width).toBe('500px'); document.querySelector('.modal').dispatchEvent(new Event('click')); tick(); @@ -325,7 +323,7 @@ describe('dialog', () => {

Modal Component

iAmBtn
` -}) + }) class OpenModalComponent { constructor() {} @Input() data: any; @@ -346,7 +344,7 @@ class OpenModalComponent { template: ` click me! `, -}) + }) class TestModalComponent { results: any; diff --git a/devui/number-translation/number-translation.pipe.ts b/devui/number-translation/number-translation.pipe.ts index c4970ad0..eec76df7 100644 --- a/devui/number-translation/number-translation.pipe.ts +++ b/devui/number-translation/number-translation.pipe.ts @@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'dNumberTrans' -}) + }) export class NumberTransPipe implements PipeTransform { getFlowStr(value: number, fixedNum: number) { @@ -58,7 +58,7 @@ export class NumberTransPipe implements PipeTransform { switch(type) { case 'comma': - numberStr = number.toLocaleString(); + numberStr = number.toLocaleString('zh-CN', {maximumFractionDigits: fixedNum}); break; case 'flow': numberStr = this.getFlowStr(number, fixedNum); diff --git a/devui/package.json b/devui/package.json index 59abf74d..34980fe2 100755 --- a/devui/package.json +++ b/devui/package.json @@ -1,6 +1,6 @@ { "name": "ng-devui", - "version": "16.0.0", + "version": "16.1.0", "license": "MIT", "description": "DevUI components based on Angular", "keywords": [ diff --git a/devui/pagination/pagination.component.html b/devui/pagination/pagination.component.html index a0288860..92b070b6 100755 --- a/devui/pagination/pagination.component.html +++ b/devui/pagination/pagination.component.html @@ -150,6 +150,7 @@ type="text" autocomplete="off" class="devui-input {{ size ? 'devui-input-' + size : '' }}" + [class.error]="jumpPage < 1 || (jumpPage > totalPage && pageIndex === totalPage)" name="pageIndex" [(ngModel)]="jumpPage" (keyup.enter)="jump()" diff --git a/devui/pagination/pagination.component.ts b/devui/pagination/pagination.component.ts index b2306e99..c4e9c704 100755 --- a/devui/pagination/pagination.component.ts +++ b/devui/pagination/pagination.component.ts @@ -1,6 +1,9 @@ import { ConnectedPosition } from '@angular/cdk/overlay'; import { - AfterViewInit, ApplicationRef, ChangeDetectionStrategy, ChangeDetectorRef, + AfterViewInit, + ApplicationRef, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, ElementRef, EventEmitter, @@ -15,7 +18,7 @@ import { import { DropDownAppendToBodyComponent } from 'ng-devui/dropdown'; import { I18nInterface, I18nService } from 'ng-devui/i18n'; import { AppendToBodyDirection } from 'ng-devui/utils'; -import { Subscription, fromEvent } from 'rxjs'; +import { fromEvent, Subscription } from 'rxjs'; @Component({ selector: 'd-pagination', styleUrls: ['./pagination.component.scss'], @@ -23,7 +26,7 @@ import { Subscription, fromEvent } from 'rxjs'; exportAs: 'pagination', changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, -}) + }) export class PaginationComponent implements OnChanges, AfterViewInit, OnDestroy, OnInit { static EFFECT_PAGE_RANGE_KEYS = ['total', 'pageSize', 'pageIndex', 'maxItems', 'pageSizeOptions']; @@ -182,11 +185,13 @@ export class PaginationComponent implements OnChanges, AfterViewInit, OnDestroy, } jump() { - const pageInput = parseInt(this.jumpPage, 10); - if (pageInput) { - if (pageInput > 0 && pageInput <= this.totalPage) { - this.onPageIndexChange(pageInput); + let pageInput = parseInt(this.jumpPage, 10); + if (pageInput && (pageInput < this.totalPage || this.pageIndex < this.totalPage)) { + if (pageInput > this.totalPage) { + this.jumpPage = this.totalPage; + pageInput = this.totalPage; } + this.onPageIndexChange(pageInput); } } @@ -348,9 +353,11 @@ export class PaginationComponent implements OnChanges, AfterViewInit, OnDestroy, this.litePaginator.nativeElement.style.width = `${Math.max(minWidth, width)}px`; } } + onToggle(event) { this.rotateDegrees = event ? 180 : 0; } + toggleMenu(force: boolean = null) { if (force !== null) { this.showConfig = force; diff --git a/devui/popover/popover.component.ts b/devui/popover/popover.component.ts index d85c6753..7b16c75f 100755 --- a/devui/popover/popover.component.ts +++ b/devui/popover/popover.component.ts @@ -30,7 +30,7 @@ interface PopoverStyle { templateUrl: './popover.component.html', styleUrls: [`./popover.component.scss`], animations: [directionFadeInOut], -}) + }) export class PopoverComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { @Input() triggerElementRef: ElementRef; currentPosition: PositionType = 'top'; @@ -73,7 +73,7 @@ export class PopoverComponent implements OnInit, AfterViewInit, OnDestroy, OnCha return 'devui-popover ' + this.currentPosition + ' ' + this.connectionBias + ' devui-popover-' + this.popType; } @HostBinding('@directionFadeInOut') get state() { - return this.animateState; + return this.showAnimation && this.animateState; } @HostBinding('@.disabled') get disabled() { return !this.showAnimation; diff --git a/devui/radio/doc/api-cn.md b/devui/radio/doc/api-cn.md index a97012a7..d3ad8a92 100644 --- a/devui/radio/doc/api-cn.md +++ b/devui/radio/doc/api-cn.md @@ -35,14 +35,15 @@ import { RadioModule } from 'ng-devui/radio'; ### d-radio-group 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| ------------ | ----------------------------------- | -------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -| name | `string` | -- | 必选,单选项名称 (radio 唯一标识符) | [竖向排列](demo#vertical) | -| values | `array` | -- | 必选,单选数据组 | [竖向排列](demo#vertical) | -| disabled | `boolean` | false | 可选,是否禁用该选项组 | [radio-group 根据条件终止切换操作](demo#condition-radio-group) | -| ~~cssStyle~~ | `'row' \| 'column'` | 'column' | 可选,设置横向或纵向排列(`已废弃,请使用direction`) | [横向排列](demo#horizontal) | -| direction | `'row' \| 'column'` | 'column' | 可选,设置横向或纵向排列 | [横向排列](demo#horizontal) | -| beforeChange | `Function \| Promise \| Observable` | -- | 可选,radio-group 切换前的回调函数,返回 boolean 类型,返回 false 可以阻止 radio-group 的切换 | [回调切换](demo#condition-radio-group) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| ------------- | ----------------------------------- | -------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | +| name | `string` | -- | 必选,单选项名称 (radio 唯一标识符) | [竖向排列](demo#vertical) | +| values | `array` | -- | 必选,单选数据组 | [竖向排列](demo#vertical) | +| disabled | `boolean` | false | 可选,是否禁用该选项组 | [radio-group 根据条件终止切换操作](demo#condition-radio-group) | +| showGlowStyle | `boolean` | true | 可选,是否显示悬浮发光效果 | +| ~~cssStyle~~ | `'row' \| 'column'` | 'column' | 可选,设置横向或纵向排列(`已废弃,请使用direction`) | [横向排列](demo#horizontal) | +| direction | `'row' \| 'column'` | 'column' | 可选,设置横向或纵向排列 | [横向排列](demo#horizontal) | +| beforeChange | `Function \| Promise \| Observable` | -- | 可选,radio-group 切换前的回调函数,返回 boolean 类型,返回 false 可以阻止 radio-group 的切换 | [回调切换](demo#condition-radio-group) | ### d-radio-group 事件 diff --git a/devui/radio/doc/api-en.md b/devui/radio/doc/api-en.md index 70faf791..8f1519f5 100644 --- a/devui/radio/doc/api-en.md +++ b/devui/radio/doc/api-en.md @@ -35,14 +35,15 @@ In the page: ### d-radio-group Parameters -| Parameter | Type | Default | Description | Jump to Demo | -| ------------ | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | -| name | `string` | -- | Required. Single-option name (unique identifier of the radio) | [Vertical Arrangement](demo#vertical) | -| values | `array` | -- | Required. Single-choice data group | [Vertical Arrangement](demo#vertical) | -| disabled | `boolean` | false | Optional. Whether to disable this radio-group | [Switch With Condition in A Radio Group](demo#condition-radio-group) | -| ~~cssStyle~~ | `'row' \| 'column'` | 'column' | Optional. Set the horizontal or vertical arrangement(`deprecated,Use direction`) | [Horizontal Arrangement](demo#horizontal) | -| direction | `'row' \| 'column'` | 'column' | Optional. Set the horizontal or vertical arrangement | [Horizontal Arrangement](demo#horizontal) | -| beforeChange | `Function \| Promise \| Observable` | -- | Optional. Callback function before radio-group switching. The return value is of the boolean type. If false is returned, radio-group switching is prevented. | [Switch With Condition in A Radio Group](demo#condition-radio-group) | +| Parameter | Type | Default | Description | Jump to Demo | +| ------------- | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | +| name | `string` | -- | Required. Single-option name (unique identifier of the radio) | [Vertical Arrangement](demo#vertical) | +| values | `array` | -- | Required. Single-choice data group | [Vertical Arrangement](demo#vertical) | +| disabled | `boolean` | false | Optional. Whether to disable this radio-group | [Switch With Condition in A Radio Group](demo#condition-radio-group) | +| showGlowStyle | `boolean` | true | Optional. Indicates whether to display the floating glow effect. | +| ~~cssStyle~~ | `'row' \| 'column'` | 'column' | Optional. Set the horizontal or vertical arrangement(`deprecated,Use direction`) | [Horizontal Arrangement](demo#horizontal) | +| direction | `'row' \| 'column'` | 'column' | Optional. Set the horizontal or vertical arrangement | [Horizontal Arrangement](demo#horizontal) | +| beforeChange | `Function \| Promise \| Observable` | -- | Optional. Callback function before radio-group switching. The return value is of the boolean type. If false is returned, radio-group switching is prevented. | [Switch With Condition in A Radio Group](demo#condition-radio-group) | ### d-radio-group Event diff --git a/devui/radio/radio-group.component.scss b/devui/radio/radio-group.component.scss index b76cdd26..042a4a7c 100755 --- a/devui/radio/radio-group.component.scss +++ b/devui/radio/radio-group.component.scss @@ -1,3 +1,6 @@ +@import '../style/core/animation'; +@import '../style/theme/color'; + .devui-radio-horizontal { .devui-radio-list-item { display: inline-block; @@ -17,6 +20,21 @@ line-height: 28px; } -:host ::ng-deep d-radio { - line-height: 28px; +:host { + ::ng-deep d-radio { + line-height: 28px; + } + + &.devui-glow-style .devui-radio-material { + transition: box-shadow $devui-animation-duration-slow $devui-animation-ease-in-out-smooth; + + &:not(.disabled) { + &:hover, + &:active { + .devui-radio-material { + box-shadow: 0 0 0 6px $devui-range-item-hover-bg; + } + } + } + } } diff --git a/devui/radio/radio-group.component.ts b/devui/radio/radio-group.component.ts index f7110696..0d24936d 100755 --- a/devui/radio/radio-group.component.ts +++ b/devui/radio/radio-group.component.ts @@ -4,6 +4,7 @@ import { ContentChildren, EventEmitter, forwardRef, + HostBinding, HostListener, Input, OnChanges, @@ -12,6 +13,7 @@ import { SimpleChanges } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { DevConfigService, WithConfig } from 'ng-devui/utils'; import { Observable } from 'rxjs'; import { RadioComponent } from './radio.component'; @@ -20,16 +22,15 @@ import { RadioComponent } from './radio.component'; templateUrl: './radio-group.component.html', styleUrls: ['./radio-group.component.scss', './radio.component.scss'], providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => RadioGroupComponent), - multi: true, - }, + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RadioGroupComponent), + multi: true, + }, ], preserveWhitespaces: false, -}) + }) export class RadioGroupComponent implements ControlValueAccessor, OnChanges, AfterViewInit { - constructor() {} @Input() name: string; @Input() values: any[]; /** @@ -40,6 +41,10 @@ export class RadioGroupComponent implements ControlValueAccessor, OnChanges, Aft } @Input() direction: 'row' | 'column'; @Input() disabled: boolean; + @Input() @WithConfig() showGlowStyle = true; + @HostBinding('class.devui-glow-style') get hasGlowStyle() { + return this.showGlowStyle; + } @Input() beforeChange: (value) => boolean | Promise | Observable; @Output() change = new EventEmitter(); @ContentChildren(RadioComponent) radios: QueryList = new QueryList(); @@ -51,10 +56,15 @@ export class RadioGroupComponent implements ControlValueAccessor, OnChanges, Aft onRadioChange(event) { const target = event.target; if (target.tagName.toLowerCase() === 'input') { + let value = target.value; if (this.disabled) { event.preventDefault(); } - this.canChange(target.value).then((change) => { + if (this.radios.length) { + const result = this.radios.find((item) => item.id === target.id); + value = result?.value || value; + } + this.canChange(value).then((change) => { if (!change) { event.preventDefault(); } @@ -62,6 +72,8 @@ export class RadioGroupComponent implements ControlValueAccessor, OnChanges, Aft } } + constructor(private devConfigService: DevConfigService) {} + ngAfterViewInit(): void { this.radios.forEach((radio) => { this.registerRadio(radio); diff --git a/devui/select/demo/select-all/select-all.component.html b/devui/select/demo/select-all/select-all.component.html index db415bf3..50b99e73 100644 --- a/devui/select/demo/select-all/select-all.component.html +++ b/devui/select/demo/select-all/select-all.component.html @@ -1,12 +1,20 @@
- + +
-
Current Select: {{ select | json }}
+
Current Select: {{ selectedItems | json }}
diff --git a/devui/select/demo/select-all/select-all.component.ts b/devui/select/demo/select-all/select-all.component.ts index 95891624..3efbfe28 100644 --- a/devui/select/demo/select-all/select-all.component.ts +++ b/devui/select/demo/select-all/select-all.component.ts @@ -2,23 +2,25 @@ import { Component } from '@angular/core'; @Component({ selector: 'd-select-all', templateUrl: './select-all.component.html', -}) + }) export class SelectAllComponent { - options = [ - 'Option 1', - 'Option 2', - 'Option 3', - 'Option 4', - 'Option 5', - 'Option 6', - 'Option 7', - 'Option 8', - 'Option 9', - 'Option 10', - 'Option 11', - 'Option 12', - 'Option 13', - 'Option 14', + options = [ + { + name: 'Option 1', + value: 4, + }, + { + name: 'Option 2', + value: 8, + }, + { + name: 'Option 3', + value: 16, + }, + { + name: 'Option 4', + value: 32, + }, ]; - select = ['Option 8', 'Option 3']; + selectedItems = [...this.options]; } diff --git a/devui/select/doc/api-cn.md b/devui/select/doc/api-cn.md index 60eb2941..9c5b505a 100644 --- a/devui/select/doc/api-cn.md +++ b/devui/select/doc/api-cn.md @@ -67,6 +67,7 @@ import { SelectModule } from 'ng-devui/select'; | showAnimation | `boolean` | true | 可选,是否开启动画 | | ✔ | | color | `string` | -- | 可选,复选框颜色 | | | showGlowStyle | `boolean` | true | 可选,是否显示悬浮发光效果 | +| showItemTitle | `boolean` | false | 可选,是否强制显示选项 title,默认在使用自定义模板时不显示选项 title | | beforeChange | `(index, option, action) => boolean \| Promise \| Observable` | -- | 可选,选择选项前的回调函数。返回 boolean 类型,返回 false 可以阻止选择 | ### d-select 事件 diff --git a/devui/select/doc/api-en.md b/devui/select/doc/api-en.md index 402d9697..531a602e 100644 --- a/devui/select/doc/api-en.md +++ b/devui/select/doc/api-en.md @@ -67,6 +67,7 @@ In the page: | showAnimation | `boolean` | true | optional. Whether to enable animation. | | ✔ | | color | `string` | -- | Optional. Check box color | | | showGlowStyle | `boolean` | true | Optional. Indicates whether to display the floating glow effect. | +| showItemTitle | `boolean` | false | Optional. Indicates whether to forcibly display the option title. By default, the option title is not displayed when a customized template is used. | | beforeChange | `(index, option, action) => boolean \| Promise \| Observable` | -- | Optional. Callback function before selecting an option. Returns a boolean type, and returns false to prevent selection. | ### d-select event diff --git a/devui/select/select.component.html b/devui/select/select.component.html index bf2c8aca..073f4db4 100755 --- a/devui/select/select.component.html +++ b/devui/select/select.component.html @@ -284,7 +284,7 @@
  • + > + + {{ i18nTagsInputText?.create }} + + diff --git a/devui/tags-input/tags.input.component.scss b/devui/tags-input/tags.input.component.scss index 8c745758..d4e8b5e6 100755 --- a/devui/tags-input/tags.input.component.scss +++ b/devui/tags-input/tags.input.component.scss @@ -31,6 +31,12 @@ } &.disabled { + cursor: not-allowed; + + d-toggle-menu-container div.devui-toggle-menu { + background-color: $devui-disabled-bg; + } + d-toggle-menu-search .devui-toggle-menu-search input { cursor: not-allowed; } @@ -123,11 +129,19 @@ } ::ng-deep { - .devui-dropdown-menu d-toggle-menu-list-item .devui-dropdown-item:not(.disabled) { - &:active, - &:active:hover { - color: $devui-list-item-hover-text; - background-color: $devui-list-item-hover-bg; + .devui-dropdown-menu { + d-toggle-menu-list-item .devui-dropdown-item:not(.disabled) { + &:active, + &:active:hover { + color: $devui-list-item-hover-text; + background-color: $devui-list-item-hover-bg; + } + } + + .devui-tags-create-item { + float: right; + font-size: $devui-font-size; + color: $devui-placeholder; } } } diff --git a/devui/tags-input/tags.input.component.ts b/devui/tags-input/tags.input.component.ts index 9bf548ea..ad9cad63 100755 --- a/devui/tags-input/tags.input.component.ts +++ b/devui/tags-input/tags.input.component.ts @@ -26,15 +26,15 @@ import { debounceTime, map, switchMap } from 'rxjs/operators'; templateUrl: './tags.input.component.html', styleUrls: ['./tags.input.component.scss'], providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => TagsInputComponent), - multi: true, - }, + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TagsInputComponent), + multi: true, + }, ], exportAs: 'TagsInput', preserveWhitespaces: false, -}) + }) export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestroy, OnChanges, AfterViewInit { /** * 【必选】记录输入的标签 @@ -164,7 +164,7 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr private onChange = (_: any) => null; private onTouch = () => null; - constructor(private i18n: I18nService, private devConfigService: DevConfigService) { } + constructor(private i18n: I18nService, private devConfigService: DevConfigService) {} private setI18nText() { this.i18nCommonText = this.i18n.getI18nText().common; @@ -199,14 +199,20 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr this.newTag = ''; this._suggestionList = [...this.suggestionList]; this.searchFn = (term: any) => { + const data = this.showSuggestion && this._suggestionList ? this._suggestionList : []; + const matchItem = data.find((item) => + this.caseSensitivity ? item[this.displayProperty] === term : item[this.displayProperty].toLowerCase() === term.toLowerCase() + ); return of( - (this._suggestionList ? this._suggestionList : []).filter((item) => - term === '' - ? true - : this.caseSensitivity - ? item[this.displayProperty].indexOf(term) !== -1 - : item[this.displayProperty].toLowerCase().indexOf(term.toLowerCase()) !== -1 - ) + matchItem + ? [matchItem] + : data.filter((item) => + term === '' + ? true + : this.caseSensitivity + ? item[this.displayProperty].indexOf(term) !== -1 + : item[this.displayProperty].toLowerCase().indexOf(term.toLowerCase()) !== -1 + ) ); }; this.registerFilterChange(); @@ -266,6 +272,15 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr ) .subscribe((options) => { this.availableOptions = options; + if (this.newTag && this.newTagValid) { + const obj = { isDevuiTagsInputCreated: true }; + obj[this.displayProperty] = this.newTag; + if (this.availableOptions.length && this.availableOptions[0].isDevuiTagsInputCreated) { + this.availableOptions[0] = obj; + } else { + this.availableOptions.unshift(obj); + } + } if (this.selectBoxContainer) { this.selectBoxContainer.updatePosition(); } @@ -275,6 +290,12 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr }); } + checkIsIncludes(option, item, isString?) { + const key = this.displayProperty; + const value = isString ? item : item[key]; + return this.caseSensitivity ? option[key] === value : option[key].toLowerCase() === value.toLowerCase(); + } + reduceSuggestionList() { this.selectedItems = this.tags.map((option, id) => ({ option, id })); if (this.isReduce) { @@ -284,13 +305,7 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr this.isReduce = true; // 使用用户最初传入的数据来进行过滤 this._suggestionList = this.suggestionList.filter((suggestion) => { - return ( - this.selectedItems.findIndex(({ option }) => - this.caseSensitivity - ? option[this.displayProperty] === suggestion[this.displayProperty] - : option[this.displayProperty].toLowerCase() === suggestion[this.displayProperty].toLowerCase() - ) === -1 - ); + return this.selectedItems.findIndex(({ option }) => this.checkIsIncludes(option, suggestion)) === -1; }); if (this.sourceSubscription && this.searchFn) { this.sourceSubscription.next(''); @@ -303,7 +318,9 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr const dom = this.searchBox.el.nativeElement.querySelector('input'); if (dom && this.selectBox) { dom.focus(); - this.selectBox.resetIndex(false); + if (this.selectBox.availableOptions.length) { + this.selectBox.resetIndex(false); + } } } } @@ -342,11 +359,7 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr index < 0 || index >= this.availableOptions.length || this.maxTags <= this.selectedItems.length || - this.selectedItems.findIndex(({ option }) => - this.caseSensitivity - ? option[this.displayProperty] === value[this.displayProperty] - : option[this.displayProperty].toLowerCase() === value[this.displayProperty].toLowerCase() - ) !== -1 + this.selectedItems.findIndex(({ option }) => this.checkIsIncludes(option, value)) !== -1 ) { return; } @@ -354,15 +367,16 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr if (!result) { return; } - this.checkMaxTags(this.availableOptions[index]); + const tag = this.availableOptions[index]; + const isCreated = tag.isDevuiTagsInputCreated; + delete tag.isDevuiTagsInputCreated; + this.checkMaxTags(tag); this.onChange(this.selectedItems.map((tagItem) => tagItem.option)); - this.valueChange.emit(this.availableOptions[index]); - const suggestionListIndex = this._suggestionList.findIndex((item) => - this.caseSensitivity - ? item[this.displayProperty] === value[this.displayProperty] - : item[this.displayProperty].toLowerCase() === value[this.displayProperty].toLowerCase() - ); - this._suggestionList.splice(suggestionListIndex, 1); + this.valueChange.emit(tag); + if (!isCreated) { + const suggestionListIndex = this._suggestionList.findIndex((item) => this.checkIsIncludes(item, value)); + this._suggestionList.splice(suggestionListIndex, 1); + } this.delayResetNewTag(); this.sourceSubscription.next(''); }); @@ -377,7 +391,7 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr } // onPush下 数组长度变化不会触发变更检测 this.availableOptions = [...this.availableOptions, this.selectedItems[index]?.option]; - this._suggestionList = this.availableOptions; + this._suggestionList = [...this._suggestionList, this.selectedItems[index]?.option]; const tag = this.selectedItems[index].option; this.selectedItems.splice(index, 1); if (this.selectedItems.length === 0) { @@ -390,17 +404,12 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr tagIsValid() { const tag = this.newTag; - const tmp = this.displayProperty; const result = tag && tag.length >= this.minLength && tag.length <= this.maxLength && - this._suggestionList.findIndex((item) => - this.caseSensitivity ? item[tmp] === tag : item[tmp].toLowerCase() === tag.toLowerCase() - ) === -1 && - this.selectedItems.findIndex(({ option }) => - this.caseSensitivity ? option[tmp] === tag : option[tmp].toLowerCase() === tag.toLowerCase() - ) === -1 && + (this.showSuggestion ? this._suggestionList.findIndex((item) => this.checkIsIncludes(item, tag, true)) === -1 : true) && + this.selectedItems.findIndex(({ option }) => this.checkIsIncludes(option, tag, true)) === -1 && !this.isEmptyString(tag); this.newTagValid = tag === '' || !!result; return result; @@ -430,8 +439,12 @@ export class TagsInputComponent implements ControlValueAccessor, OnInit, OnDestr this.newTagValid = false; } }) - .finally(() => this.delayResetNewTag()); - this.sourceSubscription.next(''); + .finally(() => { + if (this.newTag && this.newTagValid) { + this.delayResetNewTag(); + this.sourceSubscription.next(''); + } + }); } canAdd(value?) { diff --git a/devui/tags/tags.component.html b/devui/tags/tags.component.html index 7bfa1350..e3236cde 100755 --- a/devui/tags/tags.component.html +++ b/devui/tags/tags.component.html @@ -23,7 +23,7 @@ [controlled]="true" [tag]="'+ ' + beyondTags.length" [mode]="'checkable'" - [popoverStyle]="{ backgroundColor: 'white', color: 'black' }" + [popoverStyle]="{ backgroundColor: 'var(--devui-base-bg)' }" >
  • diff --git a/devui/theme-collection/extend-theme.scss b/devui/theme-collection/extend-theme.scss index 46c40c46..9c0f4642 100644 --- a/devui/theme-collection/extend-theme.scss +++ b/devui/theme-collection/extend-theme.scss @@ -53,6 +53,21 @@ body[ui-theme='galaxy-theme'] { d-button .devui-btn-primary:disabled { --devui-light-text: #838383; } + + .devui-table .devui-month-table .devui-table-date.devui-table-date-selected span, + .devui-month-panel .devui-month-list .devui-month-item.devui-table-month-selected span, + .year-list-panel .devui-year-list .devui-year-title.devui-active-year, + .devui-table .devui-month-table .devui-table-date.devui-table-date-marked span::after { + background-color: #ced1db !important; + } + + .devui-table .devui-month-table .devui-table-date.devui-table-date-marked.devui-table-date-selected span::after { + background-color: #4660b8 !important; + } + + .devui-checkbox.disabled .devui-checkbox-material { + --devui-icon-fill-active-disabled: #2a3c85; + } } body[ui-theme='infinity-theme'], diff --git a/devui/theme-collection/extend-theme.ts b/devui/theme-collection/extend-theme.ts index 0565073d..74503618 100644 --- a/devui/theme-collection/extend-theme.ts +++ b/devui/theme-collection/extend-theme.ts @@ -191,11 +191,11 @@ export const galaxyTheme: Theme = new Theme({ 'devui-line': '#4e5057', 'devui-dividing-line': '#323438', 'devui-list-item-hover-bg': '#3d3d42', - 'devui-list-item-active-bg': '#303548', - 'devui-list-item-active-hover-bg': '#303548', - 'devui-list-item-selected-bg': '#303548', + 'devui-list-item-active-bg': '#33384B', + 'devui-list-item-active-hover-bg': '#33384B', + 'devui-list-item-selected-bg': '#33384B', 'devui-list-item-hover-text': '#F5F5F5', - 'devui-list-item-active-text': '#4660B8', + 'devui-list-item-active-text': '#526ECC', 'devui-primary-disabled': '#3f3f3f', 'devui-form-control-line': '#4e5057', 'devui-form-control-bg': '#292A2E', diff --git a/devui/theme-collection/theme-initial-data.ts b/devui/theme-collection/theme-initial-data.ts index fbe5454a..8974905f 100644 --- a/devui/theme-collection/theme-initial-data.ts +++ b/devui/theme-collection/theme-initial-data.ts @@ -23,13 +23,13 @@ export const devuiInitialThemeData = { 'devui-link-light': '#96adfa', 'devui-link-light-active': '#beccfa', 'devui-line': '#d7d8da', - 'devui-dividing-line': '#f2f2f3', + 'devui-dividing-line': '#eef0f5', 'devui-block': '#ffffff', 'devui-area': '#f5f5f5', 'devui-danger': '#f66f6a', 'devui-warning': '#fac20a', 'devui-waiting': '#beccfa', - 'devui-success': '#50d4ab', + 'devui-success': '#3ac295', 'devui-info': '#5e7ce0', 'devui-initial': '#e9edfa', 'devui-unavailable': '#f5f5f5', @@ -37,6 +37,7 @@ export const devuiInitialThemeData = { 'devui-light-shadow': 'rgba(37, 43, 58, 0.12)', 'devui-connected-overlay-shadow': 'rgba(37, 43, 58, 0.16)', 'devui-feedback-overlay-shadow': 'rgba(37, 43, 58, 0.2)', + 'devui-hover-shadow': 'rgba(37, 43, 58, 0.2)', 'devui-icon-text': '#71757f', 'devui-icon-bg': '#ffffff', 'devui-icon-fill': '#71757f', @@ -52,18 +53,18 @@ export const devuiInitialThemeData = { 'devui-form-control-bg': '#ffffff', 'devui-form-control-line-hover': '#9b9fa8', 'devui-form-control-line-active': '#5e7ce0', - 'devui-form-control-interactive-outline': 'rgba(94,124,224,0.08)', + 'devui-form-control-interactive-outline': 'rgba(94, 124, 224, 0.08)', 'devui-form-control-line-active-hover': '#344899', - 'devui-list-item-active-bg': '#f2f5fc', + 'devui-list-item-active-bg': '#e9edfa', 'devui-list-item-active-text': '#252b3a', - 'devui-list-item-active-hover-bg': '#f2f5fc', + 'devui-list-item-active-hover-bg': '#e9edfa', 'devui-list-item-hover-bg': '#f2f2f3', 'devui-list-item-hover-text': '#252b3a', - 'devui-list-item-selected-bg': '#f2f5fc', + 'devui-list-item-selected-bg': '#e9edfa', 'devui-list-item-strip-bg': '#f2f5fc', 'devui-disabled-bg': '#f5f5f5', 'devui-disabled-line': '#dfe1e6', - 'devui-disabled-text': '#cfd0d3', + 'devui-disabled-text': '#babbc0', 'devui-primary-disabled': '#beccfa', 'devui-icon-fill-active-disabled': '#beccfa', 'devui-label-bg': '#e9edfa', @@ -102,7 +103,7 @@ export const devuiInitialThemeData = { 'devui-shadow-length-slide-right': '2px 0 8px 0', 'devui-shadow-length-connected-overlay': '0 2px 12px 0', 'devui-shadow-length-hover': '0 4px 16px 0', - 'devui-shadow-length-feedback-overlay': '0 8px 16px 0', + 'devui-shadow-length-feedback-overlay': '0 4px 16px 0', 'devui-shadow-length-fullscreen-overlay': '0 10px 24px 0', 'devui-border-radius': '4px', 'devui-border-radius-feedback': '4px', diff --git a/devui/theme/theme-data.ts b/devui/theme/theme-data.ts index 7f9ea681..7d3dbcef 100644 --- a/devui/theme/theme-data.ts +++ b/devui/theme/theme-data.ts @@ -43,6 +43,7 @@ export const devuiLightTheme: Theme = new Theme({ 'devui-light-shadow': 'rgba(37, 43, 58, 0.12)', 'devui-connected-overlay-shadow': 'rgba(37, 43, 58, 0.16)', 'devui-feedback-overlay-shadow': 'rgba(37, 43, 58, 0.2)', + 'devui-hover-shadow': 'rgba(37, 43, 58, 0.2)', 'devui-icon-text': '#252b3a', 'devui-icon-bg': '#ffffff', 'devui-icon-fill': '#252b3a', @@ -78,6 +79,9 @@ export const devuiLightTheme: Theme = new Theme({ 'devui-fullscreen-overlay-bg': '#ffffff', 'devui-feedback-overlay-bg': '#464d6e', 'devui-feedback-overlay-text': '#dfe1e6', + 'devui-gray-form-control-bg':'#f5f5f5', + 'devui-gray-form-control-hover-bg':'#ebebeb', + 'devui-nav-expand-bg':'#fbfbfc', 'devui-embed-search-bg': '#f2f5fc', 'devui-embed-search-bg-hover': '#eef0f5', 'devui-float-block-shadow': 'rgba(94, 124, 224, 0.3)', @@ -140,8 +144,6 @@ export const devuiLightTheme: Theme = new Theme({ 'devui-z-index-drawer': '1040', 'devui-z-index-framework': '1000', 'devui-icon-active-color': '#ffffff', - 'devui-gray-form-control-bg': '#f5f5f5', - 'devui-gray-form-control-hover-bg': '#ebebeb', }, isDark: false, }); diff --git a/devui/time-picker/demo/basic/basic.component.html b/devui/time-picker/demo/basic/basic.component.html index 560bfeb2..998a6820 100644 --- a/devui/time-picker/demo/basic/basic.component.html +++ b/devui/time-picker/demo/basic/basic.component.html @@ -13,7 +13,7 @@
    basic
    #timePicker1="timePicker" (selectedTimeChange)="timeChange($event)" (ngModelChange)="modelChange($event)" - [timePickerWidth]="300" + [timePickerWidth]="250" />
    diff --git a/devui/time-picker/time-picker.component.scss b/devui/time-picker/time-picker.component.scss index f0d626c8..f139fd62 100644 --- a/devui/time-picker/time-picker.component.scss +++ b/devui/time-picker/time-picker.component.scss @@ -26,11 +26,17 @@ flex: 1; overflow-y: hidden; overflow-x: hidden; + scrollbar-gutter: stable; + padding-left: $devui-scrollbar-width-sm; + scrollbar-width: thin; + + &::-webkit-scrollbar { + width: $devui-scrollbar-width-sm; + } &:hover { overflow-y: scroll; overflow-y: overlay; - scrollbar-width: thin; } &:not(:last-child) { @@ -43,6 +49,7 @@ line-height: 24px; text-align: center; cursor: pointer; + border-radius: $devui-border-radius-feedback; &:hover { color: $devui-list-item-hover-text; diff --git a/devui/toast/toast.component.html b/devui/toast/toast.component.html index ea11eee0..aabb4ad1 100755 --- a/devui/toast/toast.component.html +++ b/devui/toast/toast.component.html @@ -138,7 +138,7 @@

    {{ msg.content }}

    -

    +

    diff --git a/devui/toast/toast.component.scss b/devui/toast/toast.component.scss index a236932c..b24e17b1 100755 --- a/devui/toast/toast.component.scss +++ b/devui/toast/toast.component.scss @@ -114,6 +114,8 @@ .devui-toast-message { margin-left: 20px; + word-break: break-all; + word-wrap: break-word; p { padding: 0 8px 0 4px; diff --git a/devui/toggle-menu/toggle-menu-list-item.component.html b/devui/toggle-menu/toggle-menu-list-item.component.html index eaa1dd12..25502598 100644 --- a/devui/toggle-menu/toggle-menu-list-item.component.html +++ b/devui/toggle-menu/toggle-menu-list-item.component.html @@ -8,6 +8,7 @@ [class.group]="optionGroupKey && item.option[optionGroupKey]" (click)="choose(item.option, item.id, $event)" > + + diff --git a/devui/transfer/demo/search/transfer-demo-search.component.html b/devui/transfer/demo/search/transfer-demo-search.component.html index d42022a9..8a84a132 100644 --- a/devui/transfer/demo/search/transfer-demo-search.component.html +++ b/devui/transfer/demo/search/transfer-demo-search.component.html @@ -4,6 +4,7 @@ [sourceOption]="sourceOption1" [isSearch]="true" [showOptionTitle]="true" + [noResultTemplate]="noDataTpl" [titles]="{ source: 'Source', target: 'Target' }" (afterTransfer)="afterTransfer($event)" > @@ -23,3 +24,14 @@ >
    + + +
    + + + {{ + (position === 'source' && sourceKeyword) || (position === 'target' && targetKeyword) ? 'No records found.' : 'No data available.' + }} + +
    +
    diff --git a/devui/transfer/demo/search/transfer-demo-search.component.scss b/devui/transfer/demo/search/transfer-demo-search.component.scss new file mode 100644 index 00000000..e6060e7b --- /dev/null +++ b/devui/transfer/demo/search/transfer-demo-search.component.scss @@ -0,0 +1,20 @@ +@import '~ng-devui/styles-var/devui-var.scss'; + +.no-data-tip { + cursor: not-allowed; + color: $devui-disabled-text; + font-size: $devui-font-size; + background: $devui-base-bg; + width: 100%; + line-height: 22px; + padding: 0; + text-align: center; + user-select: none; + position: absolute; + top: 50%; + transform: translateY(-50%); + + i.icon-advisory { + padding-right: 8px; + } +} diff --git a/devui/transfer/demo/search/transfer-demo-search.component.ts b/devui/transfer/demo/search/transfer-demo-search.component.ts index 25f80698..25b283cb 100644 --- a/devui/transfer/demo/search/transfer-demo-search.component.ts +++ b/devui/transfer/demo/search/transfer-demo-search.component.ts @@ -3,8 +3,9 @@ import { TransferDirection } from 'ng-devui'; @Component({ selector: 'd-transfer-demo-search', - templateUrl: './transfer-demo-search.component.html' -}) + templateUrl: './transfer-demo-search.component.html', + styleUrls: ['./transfer-demo-search.component.scss'] + }) export class TransferDemoSearchComponent { disabled = false; sourceOption1 = [ @@ -55,7 +56,7 @@ export class TransferDemoSearchComponent { { name: 'Option22', value: 3, id: 22, checked: false } ]; sourceOptionCopy2 = [...this.sourceOption2]; - targetOptionCopy2 = []; + targetOptionCopy2 = [...this.targetOption2]; onChange(event) { this.disabled = event; diff --git a/devui/transfer/demo/transfer-demo.component.ts b/devui/transfer/demo/transfer-demo.component.ts index bccd0dc1..f93fa76f 100644 --- a/devui/transfer/demo/transfer-demo.component.ts +++ b/devui/transfer/demo/transfer-demo.component.ts @@ -6,7 +6,7 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'd-transfer-demo', templateUrl: './transfer-demo.component.html', -}) + }) export class TransferDemoComponent implements OnInit, OnDestroy { basicSource: Array = [ { title: 'HTML', language: 'html', code: require('./basic/transfer-demo-base.component.html?raw') }, @@ -19,6 +19,7 @@ export class TransferDemoComponent implements OnInit, OnDestroy { ]; searchSource: Array = [ { title: 'HTML', language: 'html', code: require('./search/transfer-demo-search.component.html?raw') }, + { title: 'SCSS', language: 'css', code: require('./search/transfer-demo-search.component.scss?raw') }, { title: 'TS', language: 'typescript', code: require('./search/transfer-demo-search.component.ts?raw') }, ]; sortSource: Array = [ diff --git a/devui/transfer/doc/api-cn.md b/devui/transfer/doc/api-cn.md index 7e751920..21214db9 100644 --- a/devui/transfer/doc/api-cn.md +++ b/devui/transfer/doc/api-cn.md @@ -1,50 +1,63 @@ # 如何使用 -在module中引入: + +在 module 中引入: + ```ts import { TransferModule } from 'ng-devui/transfer'; ``` + 在页面中使用: + ```html ``` -# d-transfer - -## d-transfer 参数 - -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo |全局配置项| -| :----------------: | :---------------: | :-----: | :---: | :------------------------- | ------------------------------------------------------------ | -| sourceOption | `array` | [] | 可选参数,穿梭框源数据 | [基本用法](demo#transfer-demo-base) | -| targetOption | `array` | [] | 可选参数,穿梭框目标数据 | [基本用法](demo#transfer-demo-base) | -| titles | `array` | [] | 可选参数,穿梭框标题 | [基本用法](demo#transfer-demo-base) | -| height | `string` | 320px | 可选参数,穿梭框高度 | -| isSearch | `boolean` | false | 可选参数,是否可以搜索 | [搜索穿梭框](demo#transfer-demo-search) | -| isSourceDroppable | `boolean` | false | 可选参数,源是否可以拖拽 | -| isTargetDroppable | `boolean` | false | 可选参数,目标是否可以拖拽 | [排序穿梭框](demo#transfer-demo-sort) | -| virtualScroll | `boolean` | false | 可选参数,是否虚拟滚动 | [虚拟滚动](demo#transfer-demo-virtual-scroll) | -| disabled | `boolean` | false | 可选参数 穿梭框禁止使用 | [基本用法](demo#transfer-demo-base) | -| showOptionTitle | `boolean` | false | 可选,鼠标悬浮于数据是否显示title | [搜索穿梭框](demo#transfer-demo-search) | -| beforeTransfer | `(sourceOption, targetOption) => boolean \| Promise \| Observable` | - | 可选参数 在transfer事件发生前判断事件是否允许触发 | [基本用法](demo#transfer-demo-base) | -|customSourceCheckedLen|`number`| 0 |可选,使用模板时判断源是否可穿梭| [自定义穿梭框](demo#transfer-demo-custom) | -|customTargetCheckedLen|`number`| 0 |可选,使用模板时判断目标是否可穿梭| [自定义穿梭框](demo#transfer-demo-custom) | - -## d-transfer 事件 - -| 事件 | 类型 | 说明 | 跳转 Demo | -| :--------------: | :------------------------------------: | :--------------------------------------: | -------------------------------------------------------- | -| transferToSource | `EventEmitter<{sourceOption, targetOption}>` | 当点击右穿梭时,返回穿梭框源和目标数据; | [基本用法](demo#transfer-demo-base) | -| transferToTarget | `EventEmitter<{sourceOption, targetOption}>` | 当点击左穿梭时,返回穿梭框源和目标数据; | [基本用法](demo#transfer-demo-base) | -| searching | `EventEmitter<{direction, keyword}>` | 当搜索时触发,返回目标穿梭框和搜索文字,不设置此事件则会使用默认方法; | [搜索穿梭框](demo#transfer-demo-search) | -| transferring | `EventEmitter` | 当穿梭时触发,返回目标穿梭框,不设置此事件则会使用默认方法; | [搜索穿梭框](demo#transfer-demo-search) | -| afterTransfer | `EventEmitter` | 当穿梭完成后,返回目标穿梭框,不设置transferEvent才会触发; | [搜索穿梭框](demo#transfer-demo-search) | +## d-transfer + +### d-transfer 参数 + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| ---------------------- | ------------------------------------------------------------------------------------ | ----- | --------------------------------------------------- | --------------------------------------------- | ---------- | +| sourceOption | `array` | [] | 可选参数,穿梭框源数据 | [基本用法](demo#transfer-demo-base) | +| targetOption | `array` | [] | 可选参数,穿梭框目标数据 | [基本用法](demo#transfer-demo-base) | +| titles | `array` | [] | 可选参数,穿梭框标题 | [基本用法](demo#transfer-demo-base) | +| height | `string` | 320px | 可选参数,穿梭框高度 | +| isSearch | `boolean` | false | 可选参数,是否可以搜索 | [搜索穿梭框](demo#transfer-demo-search) | +| isSourceDroppable | `boolean` | false | 可选参数,源是否可以拖拽 | +| isTargetDroppable | `boolean` | false | 可选参数,目标是否可以拖拽 | [排序穿梭框](demo#transfer-demo-sort) | +| virtualScroll | `boolean` | false | 可选参数,是否虚拟滚动 | [虚拟滚动](demo#transfer-demo-virtual-scroll) | +| disabled | `boolean` | false | 可选参数 穿梭框禁止使用 | [基本用法](demo#transfer-demo-base) | +| showOptionTitle | `boolean` | false | 可选,鼠标悬浮于数据是否显示 title | [搜索穿梭框](demo#transfer-demo-search) | +| beforeTransfer | `(sourceOption, targetOption) => boolean \| Promise \| Observable` | - | 可选参数 在 transfer 事件发生前判断事件是否允许触发 | [基本用法](demo#transfer-demo-base) | +| customSourceCheckedLen | `number` | 0 | 可选,使用模板时判断源是否可穿梭 | [自定义穿梭框](demo#transfer-demo-custom) | +| customTargetCheckedLen | `number` | 0 | 可选,使用模板时判断目标是否可穿梭 | [自定义穿梭框](demo#transfer-demo-custom) | +| noResultTemplate | `TemplateRef` | -- | 可选,自定义无数据模板 | [搜索穿梭框](demo#transfer-demo-search) | + +### d-transfer 事件 + +| 事件 | 类型 | 说明 | 跳转 Demo | +| ---------------- | -------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------- | +| transferToSource | `EventEmitter<{sourceOption, targetOption}>` | 当点击右穿梭时,返回穿梭框源和目标数据; | [基本用法](demo#transfer-demo-base) | +| transferToTarget | `EventEmitter<{sourceOption, targetOption}>` | 当点击左穿梭时,返回穿梭框源和目标数据; | [基本用法](demo#transfer-demo-base) | +| searching | `EventEmitter<{direction, keyword}>` | 当搜索时触发,返回目标穿梭框和搜索文字,不设置此事件则会使用默认方法; | [搜索穿梭框](demo#transfer-demo-search) | +| transferring | `EventEmitter` | 当穿梭时触发,返回目标穿梭框,不设置此事件则会使用默认方法; | [搜索穿梭框](demo#transfer-demo-search) | +| afterTransfer | `EventEmitter` | 当穿梭完成后,返回目标穿梭框,不设置 transferEvent 才会触发; | [搜索穿梭框](demo#transfer-demo-search) | + +## 接口 & 类型定义 ### TransferDirection 类型 引入: + ```ts import { TransferDirection } from 'ng-devui'; ``` + 结构: + ```ts -enum TransferDirection { SOURCE, TARGET } +enum TransferDirection { + SOURCE, + TARGET, +} ``` diff --git a/devui/transfer/doc/api-en.md b/devui/transfer/doc/api-en.md index 34587ad6..f2aa0c8e 100644 --- a/devui/transfer/doc/api-en.md +++ b/devui/transfer/doc/api-en.md @@ -1,50 +1,63 @@ # How To Use + Import into module: + ```ts import { TransferModule } from 'ng-devui/transfer'; ``` + In the page: + ```html ``` -# d-transfer - -## d-transfer parameter - -| Parameter | Type | Default | Description | Jump to Demo |Global Config| -| :----------------: | :---------------: | :-----: | :---: | :------------------------- | ------------------------------------------------------------ | -| sourceOption | `array` | [] | Optional. This parameter indicates the source data of the shuttle box. | [Basic Usage](demo#transfer-demo-base) | -| targetOption | `array` | [] | Optional. This parameter indicates the target data of the shuttle box. | [Basic Usage](demo#transfer-demo-base) | -| titles | `array` | [] | Optional. Title of the shuttle box. | [Basic Usage](demo#transfer-demo-base) | -| height | `string` | 320px | Optional. It indicates the height of the shuttle box. | -| isSearch | `boolean` | false | Optional. Specifies whether to search. | [Search Shuttle Box](demo#transfer-demo-search) | -| isSourceDroppable | `boolean` | false | Optional. Indicates whether the source can be dragged. | -| isTargetDroppable | `boolean` | false | Optional. Indicates whether the object can be dragged. | [Sorting Shuttle Box](demo#transfer-demo-sort) | -| virtualScroll | `boolean` | false | Optional. Indicates whether to scroll virtually. | [Virtual Scroll](demo#transfer-demo-virtual-scroll) | -| disabled | `boolean` | false | Optional. The shuttle box cannot be used. | [Basic Usage](demo#transfer-demo-base) | -| showOptionTitle | `boolean` | false | Optional. Indicates whether to display title when the cursor is hovered over data. | [Search Shuttle Box](demo#transfer-demo-search) | -| beforeTransfer | `(sourceOption, targetOption) => boolean \| Promise \| Observable` | - | Optional. Determines whether the transfer event can be triggered before the transfer event occurs. | [Basic Usage](demo#transfer-demo-base) | -|customSourceCheckedLen|`number`| 0 |Optional. Determine whether the source can be shuttled when using a template.| [Custom Shuttle Box](demo#transfer-demo-custom) | -|customTargetCheckedLen|`number`| 0 |Optional. Determine whether the target can be shuttled when using a template.| [Custom Shuttle Box](demo#transfer-demo-custom) | - -## d-transfer event - -| Event | Type | Description | Jump to Demo | -| :--------------: | :--------------------: | :--------------------------------: | -------------------------------------------------------- | -| transferToSource | `EventEmitter<{sourceOption, targetOption}>` | When you click the transfer to source button, the shuttle box and target data are returned. | [Basic Usage](demo#transfer-demo-base) | -| transferToTarget | `EventEmitter<{sourceOption, targetOption}>` | When you click the transfer to target button, the shuttle box and target data are returned. | [Basic Usage](demo#transfer-demo-base) | -| searching | `EventEmitter<{direction, keyword}>` | Triggered during search. Return the shuttle box and the search text. If this event is not set, the default function is used. | [Search Shuttle Box](demo#transfer-demo-search) | -| transferring | `EventEmitter` | Triggered during transfer. Return the shuttle box. If this event is not set, the default function is used. | [Search Shuttle Box](demo#transfer-demo-search) | -| afterTransfer | `EventEmitter` | Triggered after transfer. Return the shuttle box. This event will not trigger when transferEvent is not set. | [Search Shuttle Box](demo#transfer-demo-search) | +## d-transfer + +### d-transfer parameter + +| Parameter | Type | Default | Description | Jump to Demo | Global Config | +| ---------------------- | ------------------------------------------------------------------------------------ | ------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ------------- | +| sourceOption | `array` | [] | Optional. This parameter indicates the source data of the shuttle box. | [Basic Usage](demo#transfer-demo-base) | +| targetOption | `array` | [] | Optional. This parameter indicates the target data of the shuttle box. | [Basic Usage](demo#transfer-demo-base) | +| titles | `array` | [] | Optional. Title of the shuttle box. | [Basic Usage](demo#transfer-demo-base) | +| height | `string` | 320px | Optional. It indicates the height of the shuttle box. | +| isSearch | `boolean` | false | Optional. Specifies whether to search. | [Search Shuttle Box](demo#transfer-demo-search) | +| isSourceDroppable | `boolean` | false | Optional. Indicates whether the source can be dragged. | +| isTargetDroppable | `boolean` | false | Optional. Indicates whether the object can be dragged. | [Sorting Shuttle Box](demo#transfer-demo-sort) | +| virtualScroll | `boolean` | false | Optional. Indicates whether to scroll virtually. | [Virtual Scroll](demo#transfer-demo-virtual-scroll) | +| disabled | `boolean` | false | Optional. The shuttle box cannot be used. | [Basic Usage](demo#transfer-demo-base) | +| showOptionTitle | `boolean` | false | Optional. Indicates whether to display title when the cursor is hovered over data. | [Search Shuttle Box](demo#transfer-demo-search) | +| beforeTransfer | `(sourceOption, targetOption) => boolean \| Promise \| Observable` | - | Optional. Determines whether the transfer event can be triggered before the transfer event occurs. | [Basic Usage](demo#transfer-demo-base) | +| customSourceCheckedLen | `number` | 0 | Optional. Determine whether the source can be shuttled when using a template. | [Custom Shuttle Box](demo#transfer-demo-custom) | +| customTargetCheckedLen | `number` | 0 | Optional. Determine whether the target can be shuttled when using a template. | [Custom Shuttle Box](demo#transfer-demo-custom) | +| noResultTemplate | `TemplateRef` | -- | Optional. Customize a no data template. | [Search Shuttle Box](demo#transfer-demo-search) | + +### d-transfer event + +| Event | Type | Description | Jump to Demo | +| ---------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| transferToSource | `EventEmitter<{sourceOption, targetOption}>` | When you click the transfer to source button, the shuttle box and target data are returned. | [Basic Usage](demo#transfer-demo-base) | +| transferToTarget | `EventEmitter<{sourceOption, targetOption}>` | When you click the transfer to target button, the shuttle box and target data are returned. | [Basic Usage](demo#transfer-demo-base) | +| searching | `EventEmitter<{direction, keyword}>` | Triggered during search. Return the shuttle box and the search text. If this event is not set, the default function is used. | [Search Shuttle Box](demo#transfer-demo-search) | +| transferring | `EventEmitter` | Triggered during transfer. Return the shuttle box. If this event is not set, the default function is used. | [Search Shuttle Box](demo#transfer-demo-search) | +| afterTransfer | `EventEmitter` | Triggered after transfer. Return the shuttle box. This event will not trigger when transferEvent is not set. | [Search Shuttle Box](demo#transfer-demo-search) | + +## Interface & Type Definition ### TransferDirection Type Import: + ```ts import { TransferDirection } from 'ng-devui'; ``` + Structure: + ```ts -enum TransferDirection { SOURCE, TARGET } +enum TransferDirection { + SOURCE, + TARGET, +} ``` diff --git a/devui/transfer/transfer.component.html b/devui/transfer/transfer.component.html index c603ab39..a14a64b8 100644 --- a/devui/transfer/transfer.component.html +++ b/devui/transfer/transfer.component.html @@ -28,6 +28,12 @@ (searchFn)="search(transferDirection.SOURCE, $event)" >
    + + +
    + + +