diff --git a/commitlint.config.js b/commitlint.config.js index 7a8c8f49..aa9800a5 100755 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,3 +1,5 @@ +// rule由name和配置数组组成,如:'name:[0, 'always', 72]',数组中第一位为level,可选0,1,2,0为disable,1为warning,2为error,第二位为应用与否,可选always|never + const types = [ 'feat', 'fix', diff --git a/devui/accordion/accordion.component.scss b/devui/accordion/accordion.component.scss index bbb996fc..d5b1a10a 100755 --- a/devui/accordion/accordion.component.scss +++ b/devui/accordion/accordion.component.scss @@ -1,5 +1,7 @@ @import '../style/mixins/index'; @import '../style/theme/color'; +@import '../style/theme/shadow'; +@import '../style/theme/corner'; :host { display: block; @@ -19,11 +21,11 @@ background: $devui-base-bg; width: 100%; overflow-y: auto; - border-radius: 2px; + border-radius: $devui-border-radius; height: 100%; &.devui-accordion-menu-normal { - box-shadow: 0 2px 4px 0 $devui-shadow; + box-shadow: $devui-shadow-length-base $devui-shadow; } & > .devui-accordion-list { diff --git a/devui/accordion/demo/accordion-demo.moudule.ts b/devui/accordion/demo/accordion-demo.moudule.ts index 6910428b..04414025 100755 --- a/devui/accordion/demo/accordion-demo.moudule.ts +++ b/devui/accordion/demo/accordion-demo.moudule.ts @@ -45,7 +45,7 @@ import { DDemoNavModule } from 'src/app/component/d-demo-nav.module'; ChangeKeyComponent, ], providers: [], - + }) export class AccordionDemoModule { } diff --git a/devui/accordion/demo/template/template.component.scss b/devui/accordion/demo/template/template.component.scss index 250953dc..42bea05d 100644 --- a/devui/accordion/demo/template/template.component.scss +++ b/devui/accordion/demo/template/template.component.scss @@ -8,7 +8,7 @@ display: inline-block; width: 16px; height: 16px; - border-radius: 8px; + border-radius: (16px/2); font-size: $devui-font-size; line-height: 12px; padding: 2px; diff --git a/devui/alert/alert.component.scss b/devui/alert/alert.component.scss index 83e64b94..10f040ed 100755 --- a/devui/alert/alert.component.scss +++ b/devui/alert/alert.component.scss @@ -1,7 +1,7 @@ @import '../style/mixins/index'; @import '../style/theme/color'; -@import '../style/theme/shadow'; -@import '../style/core/_font'; +@import '../style/theme/corner'; +@import '../style/theme/_font'; :host { display: block; @@ -13,13 +13,14 @@ border: 1px solid transparent; padding: 10px; line-height: 20px; - border-radius: 2px; + border-radius: $devui-border-radius; padding-left: 16px; + word-break: normal; + word-wrap: break-word; button.devui-close { color: $devui-text; opacity: 1; - box-shadow: none; & > svg path { fill: $devui-light-text; @@ -36,7 +37,6 @@ background-color: $devui-success-bg; border-color: $devui-success-line; color: $devui-text; - box-shadow: $hwc-shadow-success; button.devui-close { & > svg path { @@ -49,7 +49,6 @@ background-color: $devui-info-bg; border-color: $devui-info-line; color: $devui-text; - box-shadow: $hwc-shadow-prompt; button.devui-close { & > svg path { @@ -62,7 +61,6 @@ background-color: $devui-warning-bg; border-color: $devui-warning-line; color: $devui-text; - box-shadow: $hwc-shadow-warn; button.devui-close { & > svg path { @@ -75,7 +73,6 @@ background-color: $devui-danger-bg; border-color: $devui-danger-line; color: $devui-text; - box-shadow: $hwc-shadow-error; button.devui-close { & > svg path { diff --git a/devui/alert/demo/alert-demo.module.ts b/devui/alert/demo/alert-demo.module.ts index 9f96951b..1974d22e 100755 --- a/devui/alert/demo/alert-demo.module.ts +++ b/devui/alert/demo/alert-demo.module.ts @@ -34,7 +34,7 @@ import { DDemoNavModule } from 'src/app/component/d-demo-nav.module'; BasicComponent, CloseComponent, ], - + }) export class AlertDemoModule { } diff --git a/devui/anchor/anchor-box.directive.ts b/devui/anchor/anchor-box.directive.ts index 1ac3ed6e..02606ad4 100755 --- a/devui/anchor/anchor-box.directive.ts +++ b/devui/anchor/anchor-box.directive.ts @@ -1,4 +1,4 @@ -import { ContentChildren, Directive, Input, QueryList, OnInit } from '@angular/core'; +import { ContentChildren, Directive, Input, QueryList, OnInit, OnDestroy } from '@angular/core'; import { Subject, Subscription} from 'rxjs'; import { AnchorActiveChangeSource, IAnchorBox } from './anchor.type'; import { AnchorDirective } from './anchor.directive'; @@ -7,7 +7,7 @@ import { filter } from 'rxjs/operators'; @Directive({ selector: '[dAnchorBox]' }) -export class AnchorBoxDirective implements IAnchorBox { +export class AnchorBoxDirective implements IAnchorBox, OnDestroy { public isScrollingToTarget = false; private activeChangeSubject = new Subject(); public activeChange = this.activeChangeSubject.asObservable(); @@ -59,4 +59,10 @@ export class AnchorBoxDirective implements IAnchorBox { }); } } + ngOnDestroy() { + this.activeChangeSubject.complete(); + if (this.sub) { + this.sub.unsubscribe(); + } + } } diff --git a/devui/anchor/demo/anchor-demo.module.ts b/devui/anchor/demo/anchor-demo.module.ts index 338fc03c..0d15ae74 100755 --- a/devui/anchor/demo/anchor-demo.module.ts +++ b/devui/anchor/demo/anchor-demo.module.ts @@ -45,7 +45,7 @@ import { ScrollTargetComponent } from './scroll-target/scroll-target.component'; HashComponent, ScrollTargetComponent ], - + }) export class AnchorDemoModule { } diff --git a/devui/auto-complete/auto-complete-popup.component.ts b/devui/auto-complete/auto-complete-popup.component.ts index 801c8457..ce2abeb7 100755 --- a/devui/auto-complete/auto-complete-popup.component.ts +++ b/devui/auto-complete/auto-complete-popup.component.ts @@ -1,10 +1,4 @@ -import { - Component, - ElementRef, - Input, - TemplateRef, - ViewChild -} from '@angular/core'; +import { Component, ElementRef, Input, TemplateRef, ViewChild } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; import { AutoCompleteConfig } from './auto-complete-config'; import { fadeInOut } from 'ng-devui/utils'; @@ -15,10 +9,10 @@ import { CdkOverlayOrigin } from '@angular/cdk/overlay'; templateUrl: './auto-complete-popup.component.html', styleUrls: ['auto-complete-popup.component.scss'], animations: [fadeInOut], - preserveWhitespaces: false + preserveWhitespaces: false, }) export class AutoCompletePopupComponent implements ControlValueAccessor { - @Input() width; + @Input() width: number; @Input() cssClass: string; @Input() maxHeight: number; @Input() disabled: boolean; @@ -48,10 +42,7 @@ export class AutoCompletePopupComponent implements ControlValueAccessor { private onChange = (_: any) => null; private onTouched = () => null; - constructor( - private autoCompleteConfig: AutoCompleteConfig, - public elementRef: ElementRef - ) { + constructor(private autoCompleteConfig: AutoCompleteConfig, public elementRef: ElementRef) { this.formatter = this.autoCompleteConfig.autoComplete.formatter; this.maxHeight = 300; } @@ -78,7 +69,8 @@ export class AutoCompletePopupComponent implements ControlValueAccessor { event.stopPropagation(); return; } - if (this.overview === 'single') { // 单选场景和单行场景不需要冒泡 + if (this.overview === 'single') { + // 单选场景和单行场景不需要冒泡 event.preventDefault(); event.stopPropagation(); } @@ -102,20 +94,17 @@ export class AutoCompletePopupComponent implements ControlValueAccessor { scrollToActive(): void { const that = this; - setTimeout(_ => { - try { - const selectIndex = that.activeIndex; - const scrollPane: any = that.dropdownUl.nativeElement.children[selectIndex]; - if (scrollPane.scrollIntoViewIfNeeded) { - scrollPane.scrollIntoViewIfNeeded(false); - } else { - const containerInfo = that.dropdownUl.nativeElement.getBoundingClientRect(); - const elementInfo = scrollPane.getBoundingClientRect(); - if (elementInfo.bottom > containerInfo.bottom || elementInfo.top < containerInfo.top) { - scrollPane.scrollIntoView(false); - } + setTimeout(() => { + const selectIndex = that.activeIndex; + const scrollPane: any = that.dropdownUl.nativeElement.children[selectIndex]; + if (scrollPane.scrollIntoViewIfNeeded) { + scrollPane.scrollIntoViewIfNeeded(false); + } else { + const containerInfo = that.dropdownUl.nativeElement.getBoundingClientRect(); + const elementInfo = scrollPane.getBoundingClientRect(); + if (elementInfo.bottom > containerInfo.bottom || elementInfo.top < containerInfo.top) { + scrollPane.scrollIntoView(false); } - } catch (e) { } }); } diff --git a/devui/auto-complete/auto-complete.directive.ts b/devui/auto-complete/auto-complete.directive.ts index 0004d361..d57d0d87 100755 --- a/devui/auto-complete/auto-complete.directive.ts +++ b/devui/auto-complete/auto-complete.directive.ts @@ -17,7 +17,7 @@ import { HostBinding, SimpleChanges, OnChanges, - Renderer2 + Renderer2, } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { fromEvent, Observable, Subscription, of } from 'rxjs'; @@ -30,11 +30,13 @@ import { CdkOverlayOrigin } from '@angular/cdk/overlay'; @Directive({ selector: '[dAutoComplete]', exportAs: 'autoComplete', - providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => AutoCompleteDirective), - multi: true - }] + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AutoCompleteDirective), + multi: true, + }, + ], }) export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, ControlValueAccessor { @HostBinding('attr.autocomplete') autocomplete = 'off'; @@ -47,7 +49,7 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont @Input() itemTemplate: TemplateRef; @Input() noResultItemTemplate: TemplateRef; @Input() searchingTemplate: TemplateRef; - @Input() set isSearching (isSearching) { + @Input() set isSearching(isSearching) { if (this.popupRef && this.searchingTemplate) { const pop = this.popupRef.instance; pop.isSearching = isSearching; @@ -59,6 +61,7 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont } @Input() appendToBody = false; + @Input() dAutoCompleteWidth: number; @Input() formatter: (item: any) => string; @Input() sceneType = ''; // sceneType使用场景:select(下拉框) suggest(联想) @Input() tipsText = ''; // 提示文字 @@ -77,13 +80,12 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont * 【可选】启用数据懒加载,默认不启用 */ @Input() enableLazyLoad = false; - @Input() allowEmptyValueSearch = false; // 在value为空时,是否允许进行搜索 + @Input() allowEmptyValueSearch = false; // 在value为空时,是否允许进行搜索 @Output() loadMore = new EventEmitter(); @Output() selectValue = new EventEmitter(); @Output() transInputFocusEmit = new EventEmitter(); // input状态传给父组件函数 @Output() changeDropDownStatus = new EventEmitter(); - KEYBOARD_EVENT_NOT_REFRESH = ['escape', 'enter', 'arrowup', 'arrowdown', - /*ie 10 edge */ 'esc', 'up', 'down']; + KEYBOARD_EVENT_NOT_REFRESH = ['escape', 'enter', 'arrowup', 'arrowdown', /*ie 10 edge */ 'esc', 'up', 'down']; popupRef: ComponentRef; popTipsText = ''; @@ -105,7 +107,8 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont private renderer: Renderer2, private injector: Injector, private positionService: PositionService, - private changeDetectorRef: ChangeDetectorRef) { + private changeDetectorRef: ChangeDetectorRef + ) { this.minLength = this.autoCompleteConfig.autoComplete.minLength; this.itemTemplate = this.autoCompleteConfig.autoComplete.itemTemplate; this.noResultItemTemplate = this.autoCompleteConfig.autoComplete.noResultItemTemplate; @@ -116,8 +119,7 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont ngOnInit() { this.valueChanges = this.registerInputEvent(this.elementRef); // 调用时机:input keyup - this.subscription = this.valueChanges - .subscribe(source => this.onSourceChange(source)); + this.subscription = this.valueChanges.subscribe((source) => this.onSourceChange(source)); // 动态的创建了popup组件, const factory = this.componentFactoryResolver.resolveComponentFactory(AutoCompletePopupComponent); @@ -127,12 +129,12 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont if (!this.searchFn) { this.searchFn = (term) => { - return of(this.source.filter(lang => this.formatter(lang).toLowerCase().indexOf(term.toLowerCase()) !== -1)); + return of(this.source.filter((lang) => this.formatter(lang).toLowerCase().indexOf(term.toLowerCase()) !== -1)); }; } // 调用时机:选中回车或者鼠标单击下拉选项 - this.popupRef.instance.registerOnChange(item => { + this.popupRef.instance.registerOnChange((item) => { if (item.type === 'loadMore') { this.loadMore.emit(item.value); return; @@ -143,7 +145,8 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont this.hidePopup(); this.selectValue.emit(item.value); if (this.overview && this.overview !== 'single') { - setTimeout(() => { // 这里稍微延迟一下,等待光标的位置发生变化,好重新获取光标的位置 + setTimeout(() => { + // 这里稍微延迟一下,等待光标的位置发生变化,好重新获取光标的位置 this.restLatestSource(); }, 0); } @@ -167,7 +170,8 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont // 调用时机:input keyup onSourceChange(source) { if (!this.elementRef.nativeElement.value) { - if (this.sceneType !== 'select' && !this.allowEmptyValueSearch) { // 下拉场景不展示最近输入 + if (this.sceneType !== 'select' && !this.allowEmptyValueSearch) { + // 下拉场景不展示最近输入 this.showLatestSource(); } else { this.showSource(source, true, true); @@ -180,13 +184,13 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont private showLatestSource() { let tempSource = []; if (this.latestSource && this.latestSource.length > 0) { - this.searchFn('').subscribe(source => { + this.searchFn('').subscribe((source) => { const t = this.latestSource; - tempSource = t.filter(data => { + tempSource = t.filter((data) => { if (!data.label) { - return source.find(item => item === data); + return source.find((item) => item === data); } else { - return source.find(item => item.label === data.label); + return source.find((item) => item.label === data.label); } }); @@ -264,11 +268,11 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont this.focus = true; this.transInputFocusEmit.emit({ focus: true, - popupRef: this.popupRef + popupRef: this.popupRef, }); const isOpen = this.sceneType !== 'select'; if (this.sceneType === 'select') { - this.searchFn('').subscribe(source => { + this.searchFn('').subscribe((source) => { this.showSource(source, isOpen, false); }); } @@ -319,28 +323,27 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont if (this.focus) { this.transInputFocusEmit.emit({ focus: this.focus, - popupRef: this.popupRef + popupRef: this.popupRef, }); } // TODO: sceneType为'select'时,自定义了太多处理,十分不优雅,需要合一化 const hostElement = this.elementRef.nativeElement; if (this.popupRef && this.popupRef.instance.isOpen) { - if (!hostElement.contains($event.target) && this.sceneType === 'select' - || this.sceneType !== 'select') { + if ((!hostElement.contains($event.target) && this.sceneType === 'select') || this.sceneType !== 'select') { this.hidePopup(); } if (!hostElement.contains($event.target)) { this.transInputFocusEmit.emit({ focus: false, - popupRef: this.popupRef + popupRef: this.popupRef, }); } } else if (hostElement.contains($event.target) && this.sceneType !== 'select') { if (!this.elementRef.nativeElement.value && !this.allowEmptyValueSearch) { this.showLatestSource(); } else { - this.searchFn(this.elementRef.nativeElement.value).subscribe(source => { + this.searchFn(this.elementRef.nativeElement.value).subscribe((source) => { this.showSource(source, true, false); }); } @@ -372,16 +375,15 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont if (this.appendToBody) { pop.appendToBody = true; pop.origin = new CdkOverlayOrigin(this.elementRef); - pop.width = this.elementRef.nativeElement.offsetWidth; + pop.width = this.dAutoCompleteWidth ? this.dAutoCompleteWidth : this.elementRef.nativeElement.offsetWidth; } else { pop.appendToBody = false; } - ['formatter', 'itemTemplate', 'noResultItemTemplate', 'cssClass', 'dropdown', 'popTipsText', 'position', 'overview'] - .forEach(key => { - if (this[key] !== undefined) { - pop[key] = this[key]; - } - }); + ['formatter', 'itemTemplate', 'noResultItemTemplate', 'cssClass', 'dropdown', 'popTipsText', 'position', 'overview'].forEach((key) => { + if (this[key] !== undefined) { + pop[key] = this[key]; + } + }); } private writeInputValue(value) { @@ -408,17 +410,16 @@ export class AutoCompleteDirective implements OnInit, OnDestroy, OnChanges, Cont } private registerInputEvent(elementRef: ElementRef) { - return fromEvent(elementRef.nativeElement, 'keyup') - .pipe( - filter((e: KeyboardEvent) => { - return this.KEYBOARD_EVENT_NOT_REFRESH.indexOf(e.key.toLocaleLowerCase()) === -1; - }), - map((e: any) => e.target.value), - tap(term => this.onTouched()), - filter(term => !this.disabled && this.searchFn && term.length >= 0), - debounceTime(this.delay), - tap(term => this.onTermChange(term)), - switchMap(term => this.searchFn(term, this)) - ); + return fromEvent(elementRef.nativeElement, 'keyup').pipe( + filter((e: KeyboardEvent) => { + return this.KEYBOARD_EVENT_NOT_REFRESH.indexOf(e.key.toLocaleLowerCase()) === -1; + }), + map((e: any) => e.target.value), + tap((term) => this.onTouched()), + filter((term) => !this.disabled && this.searchFn && term.length >= 0), + debounceTime(this.delay), + tap((term) => this.onTermChange(term)), + switchMap((term) => this.searchFn(term, this)) + ); } } diff --git a/devui/auto-complete/auto-complete.module.ts b/devui/auto-complete/auto-complete.module.ts index 69e44154..e8715bb2 100755 --- a/devui/auto-complete/auto-complete.module.ts +++ b/devui/auto-complete/auto-complete.module.ts @@ -14,6 +14,6 @@ import { AutoCompletePopupComponent } from './auto-complete-popup.component'; exports: [AutoCompleteDirective, AutoCompletePopupComponent], declarations: [AutoCompleteDirective, AutoCompletePopupComponent], providers: [AutoCompleteConfig], - + }) export class AutoCompleteModule { } diff --git a/devui/auto-complete/doc/api.md b/devui/auto-complete/doc/api.md index 23182230..8bf89c2d 100755 --- a/devui/auto-complete/doc/api.md +++ b/devui/auto-complete/doc/api.md @@ -1,33 +1,34 @@ ### d-auto-complete 参数 -| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | -| :---: | :---------------: | :-----------------: | :------------------------ | ------------ | -| allowEmptyValueSearch | `boolean` | false | 可选,在绑定的输入框 value 为空时,是否进行搜索提示操作 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | -| appendToBody | `boolean` | false | 可选,下拉弹出是否 append to body | [自定义模板展示](/components/auto-complete/demo#auto-custom) | -| source | `Array` | -- | 必选,有 searchFn 的情况下可以不必选 | [基本用法](/components/auto-complete/demo#basic-usage) | -| disabled | `boolean` | false | 可选,是否禁止指令 | [设置禁用](/components/auto-complete/demo#auto-disable) | -| delay | `number` | 300 | 可选,只有在 delay 时间经过后并且输入新值,才做搜索查询 |[自定义模板展示](/components/auto-complete/demo#auto-custom) | -| disabledKey | `string` | -- | 可选,禁用单个选项;当传入资源 source 选项类型为对象,比如设置为'disabled',则当对象的 disable 属性为 true 时,比如{label: xxx, disabled: true},该选项将禁用 | [设置禁用](/components/auto-complete/demo#auto-disable) | -| itemTemplate | `TemplateRef` | -- | 可选,自定义展示模板 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | -| noResultItemTemplate | `TemplateRef` | -- | 可选,没有匹配项的展示结果 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | -| formatter | `Function` | -- | 可选,格式化函数 | [设置禁用](/components/auto-complete/demo#auto-disable) | -| isSearching | `boolean` | false | 可选,是否在搜索中,用于控制 searchingTemplate 是否显示 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | -| searchFn | `Function` | (term: string, target?: AutoCompleteDirective) => Observable | 可选,自定义搜索过滤 | [自定义数据匹配方法](/components/auto-complete/demo#auto-object) | -| searchingTemplate | `TemplateRef` | -- | 可选,自定义搜索中显示模板 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | -| sceneType | `string` | -- | 可选,值为 select、suggest | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | -| tipsText | `string` | -- latestSource | 可选,提示文字 | [设置禁用](/components/auto-complete/demo#auto-disable) | -| overview | `border\|none\|multiline\|single` | -- | 可选(不推荐) | -| latestSource | `Array` | -- | 可选, 最近输入 | [最近输入](/components/auto-complete/demo#auto-latest) | -| valueParser | `Function` | -- | 可选, 对选中后数据进行处理 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | -| enableLazyLoad | `boolean` | false | 可选,是否允许懒加载 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| :-------------------: | :-------------------------------: | :-----------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| allowEmptyValueSearch | `boolean` | false | 可选,在绑定的输入框 value 为空时,是否进行搜索提示操作 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| appendToBody | `boolean` | false | 可选,下拉弹出是否 append to body | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| source | `Array` | -- | 必选,有 searchFn 的情况下可以不必选 | [基本用法](/components/auto-complete/demo#basic-usage) | +| disabled | `boolean` | false | 可选,是否禁止指令 | [设置禁用](/components/auto-complete/demo#auto-disable) | +| delay | `number` | 300 | 可选,只有在 delay 时间经过后并且输入新值,才做搜索查询 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| disabledKey | `string` | -- | 可选,禁用单个选项;当传入资源 source 选项类型为对象,比如设置为'disabled',则当对象的 disable 属性为 true 时,比如{label: xxx, disabled: true},该选项将禁用 | [设置禁用](/components/auto-complete/demo#auto-disable) | +| itemTemplate | `TemplateRef` | -- | 可选,自定义展示模板 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| noResultItemTemplate | `TemplateRef` | -- | 可选,没有匹配项的展示结果 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| formatter | `Function` | -- | 可选,格式化函数 | [设置禁用](/components/auto-complete/demo#auto-disable) | +| isSearching | `boolean` | false | 可选,是否在搜索中,用于控制 searchingTemplate 是否显示 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| searchFn | `Function` | (term: string, target?: AutoCompleteDirective) => Observable | 可选,自定义搜索过滤 | [自定义数据匹配方法](/components/auto-complete/demo#auto-object) | +| searchingTemplate | `TemplateRef` | -- | 可选,自定义搜索中显示模板 | [自定义模板展示](/components/auto-complete/demo#auto-custom) | +| sceneType | `string` | -- | 可选,值为 select、suggest | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| tipsText | `string` | -- latestSource | 可选,提示文字 | [设置禁用](/components/auto-complete/demo#auto-disable) | +| overview | `border\|none\|multiline\|single` | -- | 可选(不推荐) | +| latestSource | `Array` | -- | 可选, 最近输入 | [最近输入](/components/auto-complete/demo#auto-latest) | +| valueParser | `Function` | -- | 可选, 对选中后数据进行处理 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| enableLazyLoad | `boolean` | false | 可选,是否允许懒加载 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| dAutoCompleteWidth | `number` | -- | 可选,调整宽度 | ### d-auto-complete 事件 -| 参数 | 类型 | 说明 | 跳转 Demo | -| :------: | :-----------------: | :--: | :---------------------------- | -| loadMore | `EventEmitter` | 可选,懒加载触发事件,配合 enableLazyLoad 使用,使用\`$event.loadFinish()\`关闭loading状态,$event 为弹窗组件 AutoCompletePopupComponent 的实例 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | -| selectValue | `EventEmitter` | 可选,选择选项之后的回调函数 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | -| transInputFocusEmit | `EventEmitter<{focus, popupRef}>` | 可选,input focus 时回调函数 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| 参数 | 类型 | 说明 | 跳转 Demo | +| :-----------------: | :-------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------- | +| loadMore | `EventEmitter` | 可选,懒加载触发事件,配合 enableLazyLoad 使用,使用\`$event.loadFinish()\`关闭loading状态,$event 为弹窗组件 AutoCompletePopupComponent 的实例 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| selectValue | `EventEmitter` | 可选,选择选项之后的回调函数 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | +| transInputFocusEmit | `EventEmitter<{focus, popupRef}>` | 可选,input focus 时回调函数 | [启用懒加载](/components/auto-complete/demo#auto-lazy-load) | #### searchFn 默认值 diff --git a/devui/back-top/back-top.component.html b/devui/back-top/back-top.component.html new file mode 100644 index 00000000..92356960 --- /dev/null +++ b/devui/back-top/back-top.component.html @@ -0,0 +1,24 @@ +
+ + +
+ + + + + +
+
+
diff --git a/devui/back-top/back-top.component.scss b/devui/back-top/back-top.component.scss new file mode 100644 index 00000000..608c6083 --- /dev/null +++ b/devui/back-top/back-top.component.scss @@ -0,0 +1,24 @@ +@import '../style/theme/color'; + +.devui-backtop { + width: 40px; + height: 40px; + position: fixed; + cursor: pointer; + z-index: 1; + + &-content { + width: 40px; + height: 40px; + background-color: $devui-text-weak; + opacity: 0.4; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + opacity: 1; + } + } +} diff --git a/devui/back-top/back-top.component.ts b/devui/back-top/back-top.component.ts new file mode 100644 index 00000000..d1d3eb54 --- /dev/null +++ b/devui/back-top/back-top.component.ts @@ -0,0 +1,77 @@ +import { Component, OnInit, OnChanges, OnDestroy, Input, Output, TemplateRef, EventEmitter } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, SimpleChanges } from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { debounceTime, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'd-back-top', + templateUrl: './back-top.component.html', + styleUrls: ['./back-top.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + preserveWhitespaces: false, +}) +export class BackTopComponent implements OnInit, OnChanges, OnDestroy { + @Input() customTemplate: TemplateRef; + @Input() visibleHeight = 300; + @Input() bottom = '50px'; + @Input() right = '30px'; + @Input() scrollTarget: HTMLElement; + @Output() backTopEvent: EventEmitter = new EventEmitter(); + + currScrollTop = 0; + isVisible = false; + private destroy$ = new Subject(); + SCROLL_REFRESH_INTERVAL = 100; + target: HTMLElement | Window; + constructor(private cdr: ChangeDetectorRef) {} + + ngOnInit() { + this.addScrollEvent(); + this.showButton(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['scrollTarget']) { + this.addScrollEvent(); + } + } + + addScrollEvent() { + this.destroy$.next(); + fromEvent(this.getScrollTarget(), 'scroll') + .pipe(debounceTime(this.SCROLL_REFRESH_INTERVAL), takeUntil(this.destroy$)) + .subscribe(() => { + this.showButton(); + this.cdr.detectChanges(); + }); + } + + getScrollTarget() { + this.target = this.scrollTarget || window; + return this.target; + } + + showButton() { + this.currScrollTop = this.target === window ? + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) : this.scrollTarget.scrollTop; + if (this.isVisible !== (this.currScrollTop >= this.visibleHeight)) { + this.isVisible = !this.isVisible; + } + } + + goTop() { + if (this.target === window) { + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; + } else { + this.scrollTarget.style.scrollBehavior = 'smooth'; + this.scrollTarget.scrollTop = 0; + } + this.backTopEvent.emit(true); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/devui/back-top/back-top.module.ts b/devui/back-top/back-top.module.ts new file mode 100644 index 00000000..d3628242 --- /dev/null +++ b/devui/back-top/back-top.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { BackTopComponent } from './back-top.component'; + +@NgModule({ + imports: [ + CommonModule + ], + exports: [BackTopComponent], + declarations: [BackTopComponent] +}) +export class BackTopModule { +} diff --git a/devui/back-top/demo/back-top-demo.component.html b/devui/back-top/demo/back-top-demo.component.html new file mode 100644 index 00000000..b32db0e0 --- /dev/null +++ b/devui/back-top/demo/back-top-demo.component.html @@ -0,0 +1,24 @@ +
+ +
+
基本用法
+
回到顶部按钮的默认样式,距离底部50px, 右侧30px。
+ + + +
+
+
自定义
+
+ + + +
+
+
滚动容器
+
通过设置scrollTarget参数,可对特定容器进行返回顶部操作
+ + + +
+
diff --git a/devui/back-top/demo/back-top-demo.component.ts b/devui/back-top/demo/back-top-demo.component.ts new file mode 100644 index 00000000..531eb4b5 --- /dev/null +++ b/devui/back-top/demo/back-top-demo.component.ts @@ -0,0 +1,31 @@ +import { Component } from '@angular/core'; +import { DevuiSourceData } from 'ng-devui/shared/devui-codebox'; + +@Component({ + selector: 'd-demo-back-top', + templateUrl: './back-top-demo.component.html' +}) + +export class BackTopDemoComponent { + basicSource: Array = [ + {title: 'HTML', language: 'xml', code: require('!!raw-loader!./basic/basic.component.html')}, + {title: 'TS', language: 'typescript', code: require('!!raw-loader!./basic/basic.component.ts')}, + {title: 'SCSS', language: 'css', code: require('!!raw-loader!./basic/basic.component.scss')} + ]; + customizeSource: Array = [ + {title: 'HTML', language: 'xml', code: require('!!raw-loader!./customize/customize.component.html')}, + {title: 'TS', language: 'typescript', code: require('!!raw-loader!./customize/customize.component.ts')}, + {title: 'SCSS', language: 'css', code: require('!!raw-loader!./customize/customize.component.scss')} + ]; + scrollContainerSource: Array = [ + {title: 'HTML', language: 'xml', code: require('!!raw-loader!./scroll-container/scroll-container.component.html')}, + {title: 'TS', language: 'typescript', code: require('!!raw-loader!./scroll-container/scroll-container.component.ts')}, + {title: 'SCSS', language: 'css', code: require('!!raw-loader!./scroll-container/scroll-container.component.scss')} + ]; + + navItems = [ + { dAnchorLink: 'back-top-basic', value: '基本用法'}, + { dAnchorLink: 'back-top-customize', value: '自定义'}, + { dAnchorLink: 'back-top-scroll-container', value: '滚动容器'}, + ]; +} diff --git a/devui/back-top/demo/back-top-demo.module.ts b/devui/back-top/demo/back-top-demo.module.ts new file mode 100644 index 00000000..b3dc6e0e --- /dev/null +++ b/devui/back-top/demo/back-top-demo.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { DevUIApiModule } from 'ng-devui/shared/devui-api/devui-api.module'; +import { DevUIApiComponent } from 'ng-devui/shared/devui-api/devui-api.component'; +import { DevUICodeboxModule } from 'ng-devui/shared/devui-codebox/devui-codebox.module'; +import { DDemoNavModule } from 'src/app/component/d-demo-nav.module'; +import { TooltipModule } from 'ng-devui/tooltip'; +import { ToggleModule } from 'ng-devui/toggle'; + +import { BackTopModule } from '../back-top.module'; +import { BackTopDemoComponent } from './back-top-demo.component'; +import { BasicComponent } from './basic/basic.component'; +import { CustomizeComponent } from './customize/customize.component'; +import { ScrollContainerComponent } from './scroll-container/scroll-container.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + DevUIApiModule, + DevUICodeboxModule, + DDemoNavModule, + TooltipModule, + ToggleModule, + BackTopModule, + RouterModule.forChild([ + { path: '', redirectTo: 'demo' }, + { path: 'demo', component: BackTopDemoComponent}, + { path: 'api', component: DevUIApiComponent, data: { + api: require('!html-loader!markdown-loader!../doc/api.md') + }} + ]) + ], + exports: [BackTopDemoComponent], + declarations: [ + BackTopDemoComponent, + BasicComponent, + CustomizeComponent, + ScrollContainerComponent + ] +}) + +export class BackTopDemoModule { +} diff --git a/devui/back-top/demo/basic/basic.component.html b/devui/back-top/demo/basic/basic.component.html new file mode 100644 index 00000000..2381e0ff --- /dev/null +++ b/devui/back-top/demo/basic/basic.component.html @@ -0,0 +1,27 @@ +
+

默认样式

+
+ + + + + +
+ +
+ 开启后,向下滑动滚动条即可见回到顶部默认按钮: + +
+
diff --git a/devui/back-top/demo/basic/basic.component.scss b/devui/back-top/demo/basic/basic.component.scss new file mode 100644 index 00000000..2930a16e --- /dev/null +++ b/devui/back-top/demo/basic/basic.component.scss @@ -0,0 +1,18 @@ +@import '~ng-devui/styles-var/devui-var.scss'; + +.devui-backtop-content { + width: 40px; + height: 40px; + background-color: $devui-text-weak; + opacity: 0.4; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.toggle { + margin: 20px 0; + display: flex; + align-items: center; +} diff --git a/devui/back-top/demo/basic/basic.component.ts b/devui/back-top/demo/basic/basic.component.ts new file mode 100644 index 00000000..0717c5f6 --- /dev/null +++ b/devui/back-top/demo/basic/basic.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'd-back-top-basic', + templateUrl: './basic.component.html', + styleUrls: ['./basic.component.scss'] +}) + +export class BasicComponent { + isShow = false; + constructor() {} + + backTop(event) { + console.log(event); + } +} diff --git a/devui/back-top/demo/customize/customize.component.html b/devui/back-top/demo/customize/customize.component.html new file mode 100644 index 00000000..5e2a70f7 --- /dev/null +++ b/devui/back-top/demo/customize/customize.component.html @@ -0,0 +1,46 @@ +
+

自定义回到顶部按钮的样式,限制宽高:40px * 40px。

+
+
+ +
+ + +
+ +
+
+
+
+ 开启开关,向下滑动滚动条展示按钮: + +
+
+

滚动高度达到visibleHeight所设值后展示回到顶部按钮。

+
+
+ + + + + +
+ +
+ 开启开关,向下滑动滚动条展示按钮: + +
+
+
diff --git a/devui/back-top/demo/customize/customize.component.scss b/devui/back-top/demo/customize/customize.component.scss new file mode 100644 index 00000000..4486ad52 --- /dev/null +++ b/devui/back-top/demo/customize/customize.component.scss @@ -0,0 +1,36 @@ +@import '~ng-devui/styles-var/devui-var.scss'; + +.container { + margin: 20px 0; + + .devui-backtop-custom { + text-align: center; + border-radius: 50%; + background-color: $devui-brand; + width: 40px; + height: 40px; + + .icon-arrow-up { + font-size: 16px; + color: $devui-light-text; + line-height: 40px; + } + } + + .devui-backtop-basic { + width: 40px; + height: 40px; + background-color: $devui-text-weak; + opacity: 0.4; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } + + .toggle { + margin: 20px 0; + display: flex; + align-items: center; + } +} diff --git a/devui/back-top/demo/customize/customize.component.ts b/devui/back-top/demo/customize/customize.component.ts new file mode 100644 index 00000000..9450907c --- /dev/null +++ b/devui/back-top/demo/customize/customize.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'd-back-top-customize', + templateUrl: './customize.component.html', + styleUrls: ['./customize.component.scss'] +}) + +export class CustomizeComponent { + isShow1 = false; + isShow2 = false; + constructor() {} + + 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 new file mode 100644 index 00000000..40683ee4 --- /dev/null +++ b/devui/back-top/demo/scroll-container/scroll-container.component.html @@ -0,0 +1,8 @@ +
+
+
  • + {{ index + 1 + '. ' + item }} +
  • +
    + +
    diff --git a/devui/back-top/demo/scroll-container/scroll-container.component.scss b/devui/back-top/demo/scroll-container/scroll-container.component.scss new file mode 100644 index 00000000..c9332121 --- /dev/null +++ b/devui/back-top/demo/scroll-container/scroll-container.component.scss @@ -0,0 +1,14 @@ +@import '~ng-devui/styles-var/devui-var.scss'; + +.devui-scroll-container { + width: 300px; + height: 180px; + overflow-y: scroll; + margin-top: 20px; + list-style-type: none; +} + +.item { + line-height: 35px; + border-bottom: 1px solid $devui-dividing-line; +} diff --git a/devui/back-top/demo/scroll-container/scroll-container.component.ts b/devui/back-top/demo/scroll-container/scroll-container.component.ts new file mode 100644 index 00000000..7fa2a521 --- /dev/null +++ b/devui/back-top/demo/scroll-container/scroll-container.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'd-back-top-scroll-container', + templateUrl: './scroll-container.component.html', + styleUrls: ['./scroll-container.component.scss'] +}) + +export class ScrollContainerComponent implements OnInit { + scrollElement; + list = []; + sentence = 'all work and no play make jack a dull boy'; + constructor() {} + + ngOnInit() { + for (let i = 0; i < 20; i++) { + this.list.push(this.sentence); + } + this.scrollElement = document.querySelector('.devui-scroll-container'); + } + + backTop(event) { + console.log(event); + } +} diff --git a/devui/back-top/doc/api.md b/devui/back-top/doc/api.md new file mode 100644 index 00000000..d5269279 --- /dev/null +++ b/devui/back-top/doc/api.md @@ -0,0 +1,15 @@ +### d-back-top 参数 + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | +| :---------: | :------------: | :-----: | :--------------------------------------------------------------------------- | | +| customTemplate | `TemplateRef` | -- | 可选,自定义按钮样式 | [自定义](/components/back-top/demo#back-top-customize) | +| bottom | `string` | '50px' | 可选,按钮距离页面底部位置 | [自定义](/components/back-top/demo#back-top-customize) | +| right | `string` | '30px' | 可选,按钮距离页面右边距 | [自定义](/components/back-top/demo#back-top-customize) | +| visibleHeight | `number` | 300 | 可选,滚动高度达到visibleHeight所设值后展示回到顶部按钮,单位为px | [自定义](/components/back-top/demo#back-top-customize) | +| scrollTarget | `HTMLElement` | window | 可选,触发滚动的对象 | [滚动容器](/components/back-top/demo#back-top-scroll-container) | + +### d-back-top 事件 + +| 参数 | 类型 | 说明 | 跳转 Demo | +| :------: | :-----------------: | :-------------------------------------------------------------------------------------- | ---------------------------------------------- | +| backTopEvent | `EventEmitter` | 可选,点击回到顶部按钮的回调函数 | [基本用法](/components/back-top/demo#back-top-basic) | \ No newline at end of file diff --git a/devui/back-top/index.ts b/devui/back-top/index.ts new file mode 100644 index 00000000..7e1a213e --- /dev/null +++ b/devui/back-top/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/devui/back-top/package.json b/devui/back-top/package.json new file mode 100644 index 00000000..be79078b --- /dev/null +++ b/devui/back-top/package.json @@ -0,0 +1,7 @@ +{ + "ngPackage": { + "lib": { + "entryFile": "public-api.ts" + } + } +} \ No newline at end of file diff --git a/devui/back-top/public-api.ts b/devui/back-top/public-api.ts new file mode 100644 index 00000000..5a0f4e48 --- /dev/null +++ b/devui/back-top/public-api.ts @@ -0,0 +1,2 @@ +export * from './back-top.module'; +export * from './back-top.component'; diff --git a/devui/badge/badge.component.scss b/devui/badge/badge.component.scss index 8fcce1a3..2b28b24f 100644 --- a/devui/badge/badge.component.scss +++ b/devui/badge/badge.component.scss @@ -1,5 +1,5 @@ @import '../style/theme/color'; -@import '../style/core/_font'; +@import '../style/theme/_font'; @mixin status-color { &-danger { @@ -42,7 +42,7 @@ line-height: 12px; text-align: center; padding: 2px 4px; - border-radius: 8px; + border-radius: (16px/2); } &-dot { @@ -89,7 +89,7 @@ line-height: 12px; text-align: center; padding: 2px 4px; - border-radius: 8px; + border-radius: (16px/2); @include status-color; } diff --git a/devui/badge/badge.spec.ts b/devui/badge/badge.spec.ts new file mode 100644 index 00000000..731be64d --- /dev/null +++ b/devui/badge/badge.spec.ts @@ -0,0 +1,154 @@ +import { Component, DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BadgeComponent } from './badge.component'; +import { BadgeModule } from 'ng-devui/badge/badge.module'; + +@Component({ + template: ` + +
    未读消息
    +
    + ` +}) +class HasContentBadgeComponent { + count = 8; + status = 'danger'; + badgePos = 'top-right'; + showDot = false; + maxCount = 99; +} + +@Component({ + template: ` + + + ` +}) +class NoContentBadgeComponent { + count = 6; + status = 'danger'; + showDot = false; +} + +describe('badge', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [BadgeModule], + declarations: [HasContentBadgeComponent, NoContentBadgeComponent] + }); + }); + + describe('has content badge', () => { + let debugEl: DebugElement; + let component: HasContentBadgeComponent; + let spanElement: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(HasContentBadgeComponent); + debugEl = fixture.debugElement; + component = fixture.componentInstance; + spanElement = debugEl.query(By.directive(BadgeComponent)).nativeElement; + + fixture.detectChanges(); + }); + + it('should create correctly', () => { + expect(component).toBeTruthy(); + }); + + it('should have create status container', () => { + expect(spanElement).toBeTruthy(); + }); + + describe('content badge', () => { + it('Badge should has content type', () => { + expect(spanElement.querySelector('.devui-badge-content')).toBeTruthy(); + }); + + it('Badge should has danger type', () => { + expect(spanElement.querySelector('.devui-badge-content-danger')).toBeTruthy(); + }); + + it('Badge should has warning type', () => { + component.status = 'warning'; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-content-warning')).toBeTruthy(); + }); + + it('Badge should has waiting type', () => { + component.status = 'waiting'; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-content-waiting')).toBeTruthy(); + }); + + it('Badge should has success type', () => { + component.status = 'success'; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-content-success')).toBeTruthy(); + }); + + it('Badge should has info type', () => { + component.status = 'info'; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-content-info')).toBeTruthy(); + }); + }); + + describe('max count', () => { + it('Badge should has max count', () => { + component.count = 12; + component.maxCount = 9; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-content-count').textContent).toBe('9+'); + }); + }); + + describe('content dot badge', () => { + it('Badge should has content dot type', () => { + component.showDot = true; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-content-dot')).toBeTruthy(); + }); + }); + }); + + describe('no content badge', () => { + let debugEl: DebugElement; + let component: NoContentBadgeComponent; + let spanElement: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(NoContentBadgeComponent); + debugEl = fixture.debugElement; + component = fixture.componentInstance; + spanElement = debugEl.query(By.directive(BadgeComponent)).nativeElement; + + fixture.detectChanges(); + }); + + it('should create correctly', () => { + expect(component).toBeTruthy(); + }); + + it('should have create status container', () => { + expect(spanElement).toBeTruthy(); + }); + + describe('count badge', () => { + it('Badge should has count type', () => { + expect(spanElement.querySelector('.devui-badge-count')).toBeTruthy(); + }); + }); + + describe('status badge', () => { + it('Badge should has status type', () => { + component.showDot = true; + fixture.detectChanges(); + expect(spanElement.querySelector('.devui-badge-status')).toBeTruthy(); + }); + }); + }); +}); diff --git a/devui/badge/demo/basic/basic.component.scss b/devui/badge/demo/basic/basic.component.scss index f3221d6d..88524e7a 100644 --- a/devui/badge/demo/basic/basic.component.scss +++ b/devui/badge/demo/basic/basic.component.scss @@ -16,7 +16,7 @@ width: 90px; height: 42px; line-height: 42px; - border-radius: 4px; + border-radius: $devui-border-radius-card; background: $devui-global-bg; font-size: $devui-font-size-card-title; text-align: center; diff --git a/devui/badge/doc/api.md b/devui/badge/doc/api.md index 181b9d24..6fef03b8 100644 --- a/devui/badge/doc/api.md +++ b/devui/badge/doc/api.md @@ -6,7 +6,7 @@ | maxCount | `number` | 99 | 可选,设置基本徽章和计数徽章最大可显示数目,当 count > maxCount 时显示maxCount+ | [基本徽章](/components/badge/demo#badge-basic) | | showDot | `boolean` | false | 可选,true时为点状徽章(有包裹)或状态徽章(无包裹),false时为基本徽章(有包裹)或计数徽章(无包裹) | [点状徽章](/components/badge/demo#badge-dot) | | status |`BadgeStatusType` | -- | 可选,状态色 'danger' \| 'warning' \| 'waiting' \| 'success' \| 'info' | [基本徽章](/components/badge/demo#badge-basic) | -| badgePos | `BadgeStatusType` | 'top-right' | 可选,徽标位置 'top-left' \| 'top-rihgt' \| 'bottpm-left' \| ''bottom-right'' | [基本徽章](/components/badge/demo#badge-basic) | +| badgePos | `BadgeStatusType` | 'top-right' | 可选,徽标位置 'top-left' \| 'top-right' \| 'bottpm-left' \| ''bottom-right'' | [基本徽章](/components/badge/demo#badge-basic) | | offsetXY | `[number, number]` | -- | 可选,有包裹时徽标位置偏移量,格式为[x,y],单位为px。x为相对right偏移量(right: -x px),y为相对top偏移量(top: y px) | [状态徽章](/components/badge/demo#badge-status) | | bgColor | `string` | -- | 可选, 可自定义徽标色 | [基本徽章](/components/badge/demo#badge-basic) | | textColor | `string` | -- | 可选, 可自定义徽标文字颜色 | [计数徽章](/components/badge/demo#badge-count) | \ No newline at end of file diff --git a/devui/breadcrumb/breadcrumb-item/breadcrumb-item.component.scss b/devui/breadcrumb/breadcrumb-item/breadcrumb-item.component.scss index 1014a060..6b279978 100644 --- a/devui/breadcrumb/breadcrumb-item/breadcrumb-item.component.scss +++ b/devui/breadcrumb/breadcrumb-item/breadcrumb-item.component.scss @@ -1,5 +1,5 @@ @import '../../style/theme/color'; -@import '../../style/core/_font'; +@import '../../style/theme/_font'; .devui-breadcrumb-font-style { font-size: $devui-font-size; diff --git a/devui/button/button.component.scss b/devui/button/button.component.scss index c730134d..f0c067df 100755 --- a/devui/button/button.component.scss +++ b/devui/button/button.component.scss @@ -1,7 +1,8 @@ @import '../style/mixins/index'; @import '../style/theme/color'; @import '../style/theme/variables'; -@import '../style/core/font'; +@import '../style/theme/font'; +@import '../style/theme/corner'; $devui-btn-loading-color: $devui-text; $devui-btn-xs-padding: 1px 5px; @@ -71,13 +72,13 @@ $devui-btn-normal-config: ( min-width: $devui-btn-lg-min-width, ), left: ( - border-radius: 2px 0 0 2px, + border-radius: $devui-border-radius 0 0 $devui-border-radius, ), right: ( - border-radius: 0 2px 2px 0, + border-radius: 0 $devui-border-radius $devui-border-radius 0, ), default: ( - border-radius: 2px, + border-radius: $devui-border-radius, ) ); @@ -164,7 +165,7 @@ $devui-btn-pseudo-config: ( font-size: $devui-btn-font-size; height: $devui-btn-height; line-height: $devui-btn-line-height; - border-radius: 2px; + border-radius: $devui-border-radius; border-width: 1px; border-color: transparent; background-color: transparent; diff --git a/devui/button/demo/button-demo.module.ts b/devui/button/demo/button-demo.module.ts index fa324661..450bf16b 100755 --- a/devui/button/demo/button-demo.module.ts +++ b/devui/button/demo/button-demo.module.ts @@ -53,7 +53,7 @@ import { DropDownModule } from 'ng-devui/dropdown'; SizeComponent, GroupsComponent, ], - + }) export class ButtonDemoModule { } diff --git a/devui/card/card.component.scss b/devui/card/card.component.scss index 3537586e..abeaf9cb 100644 --- a/devui/card/card.component.scss +++ b/devui/card/card.component.scss @@ -1,9 +1,11 @@ @import '../style/theme/color'; -@import '../style/core/_font'; +@import '../style/theme/shadow'; +@import '../style/theme/corner'; +@import '../style/theme/_font'; $card-ele-space: 8px; $card-block-space: 16px; -$card-border-radius: 6px; +$card-border-radius: $devui-border-radius-card; $card-title-font-size: $devui-font-size-card-title; $card-subtitle-font-size: $devui-font-size; $card-content-font-size: $devui-font-size; @@ -13,7 +15,7 @@ $card-content-font-size: $devui-font-size; display: block; padding: 16px 20px; border-radius: $card-border-radius; - box-shadow: 0 0 4px 0 $devui-light-shadow; + box-shadow: $devui-shadow-length-base $devui-light-shadow; } .devui-card-title { diff --git a/devui/card/card.spec.ts b/devui/card/card.spec.ts new file mode 100644 index 00000000..ee06867f --- /dev/null +++ b/devui/card/card.spec.ts @@ -0,0 +1,84 @@ +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { CardComponent } from './card.component'; +import { CardModule } from 'ng-devui/card/card.module'; +import { AvatarModule } from 'ng-devui/avatar/avatar.module'; +import { DomHelper } from '../utils/testing/dom-helper'; + +@Component({ + template: ` + + + + + title + subtitle + + content + actions + + + ` +}) +class TestCardComponent { + align = 'start'; +} + +describe('card', () => { + let fixture: ComponentFixture; + let debugEl: DebugElement; + let component: TestCardComponent; + let domHelper: DomHelper; + let cardElement: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CardModule, AvatarModule], + declarations: [TestCardComponent] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestCardComponent); + debugEl = fixture.debugElement; + component = fixture.componentInstance; + cardElement = debugEl.query(By.directive(CardComponent)).nativeElement; + domHelper = new DomHelper(fixture); + fixture.detectChanges(); + }); + + describe('basic', () => { + it('should create correctly', () => { + expect(component).toBeTruthy(); + expect(cardElement).toBeTruthy(); + }); + + it('should have correct classes', () => { + const classList = [ + '.devui-card', + '.devui-card-meta', + '.devui-card-avatar', + '.devui-card-content', + '.devui-card-title', + '.devui-card-subtitle', + '.devui-card-actions', + '.devui-card-header', + // '.devui-card-extend' + ]; + expect(domHelper.judgeStyleClasses(classList)).toBeTruthy(); + }); + + it('should align end', () => { + component.align = 'end'; + fixture.detectChanges(); + expect(cardElement.querySelector('.devui-card-actions-align-end')).toBeTruthy(); + }); + + it('should align spaceBetween', () => { + component.align = 'spaceBetween'; + fixture.detectChanges(); + expect(cardElement.querySelector('.devui-card-actions-align-space-between')).toBeTruthy(); + }); + }); +}); diff --git a/devui/card/demo/basic/basic.component.scss b/devui/card/demo/basic/basic.component.scss index a0c66fd8..e890dfd6 100644 --- a/devui/card/demo/basic/basic.component.scss +++ b/devui/card/demo/basic/basic.component.scss @@ -17,6 +17,6 @@ d-card { &:hover { - box-shadow: 0 4px 16px 0 $devui-light-shadow; + box-shadow: $devui-shadow-length-hover $devui-light-shadow; } } diff --git a/devui/card/demo/custom/custom.component.scss b/devui/card/demo/custom/custom.component.scss index 1474e024..803d22ee 100644 --- a/devui/card/demo/custom/custom.component.scss +++ b/devui/card/demo/custom/custom.component.scss @@ -16,6 +16,6 @@ d-card { &:hover { - box-shadow: 0 4px 16px 0 $devui-light-shadow; + box-shadow: $devui-shadow-length-hover $devui-light-shadow; } } diff --git a/devui/card/demo/with-media/with-media.component.scss b/devui/card/demo/with-media/with-media.component.scss index 96614303..108316c2 100644 --- a/devui/card/demo/with-media/with-media.component.scss +++ b/devui/card/demo/with-media/with-media.component.scss @@ -23,6 +23,6 @@ d-card { &:hover { - box-shadow: 0 4px 16px 0 $devui-light-shadow; + box-shadow: $devui-shadow-length-hover $devui-light-shadow; } } diff --git a/devui/carousel/carousel.component.scss b/devui/carousel/carousel.component.scss index f1bcb301..70e6e3fa 100644 --- a/devui/carousel/carousel.component.scss +++ b/devui/carousel/carousel.component.scss @@ -1,4 +1,5 @@ @import '../style/theme/color'; +@import '../style/theme/shadow'; @mixin fixed-arrow-button() { position: absolute; @@ -7,9 +8,9 @@ cursor: pointer; width: 36px; height: 36px; - border-radius: 18px; + border-radius: (36px/2); background: $devui-highlight-overlay; - box-shadow: 0 0 8px 0 $devui-light-shadow; + box-shadow: $devui-shadow-length-hover $devui-light-shadow; display: inline-flex; align-items: center; justify-content: center; @@ -80,7 +81,7 @@ .dot-item { width: 6px; height: 6px; - border-radius: 3px; + border-radius: (6px/2); margin-right: 8px; background: $devui-icon-fill; diff --git a/devui/checkbox/checkbox.component.scss b/devui/checkbox/checkbox.component.scss index 79309fe3..71b72002 100755 --- a/devui/checkbox/checkbox.component.scss +++ b/devui/checkbox/checkbox.component.scss @@ -1,5 +1,6 @@ @import '../style/mixins/index'; @import '../style/theme/color'; +@import '../style/theme/corner'; :host { display: inline-block; @@ -80,7 +81,7 @@ position: relative; user-select: none; border: 1px solid $devui-line; - border-radius: 2px; + border-radius: $devui-border-radius; margin-right: 8px; vertical-align: text-bottom; diff --git a/devui/checkbox/demo/checkbox-demo.module.ts b/devui/checkbox/demo/checkbox-demo.module.ts index 4fe988e9..ba637ef7 100755 --- a/devui/checkbox/demo/checkbox-demo.module.ts +++ b/devui/checkbox/demo/checkbox-demo.module.ts @@ -40,7 +40,7 @@ import { CheckboxConditionGroupComponent } from './condition-group/condition-gro CheckboxConditionChangeComponent, CheckboxConditionGroupComponent ], - + }) export class CheckBoxDemoModule { } diff --git a/devui/checkbox/doc/api.md b/devui/checkbox/doc/api.md index 15df81c7..de329005 100755 --- a/devui/checkbox/doc/api.md +++ b/devui/checkbox/doc/api.md @@ -13,7 +13,7 @@ | halfchecked | `boolean` | false | 可选,半选状态 | [基本用法](/components/checkbox/demo#checkbox-basic) | | color | `string` | -- | 可选,复选框颜色 | [基本用法](/components/checkbox/demo#checkbox-basic) | | showAnimation | `boolean` | true | 可选,控制是否显示动画 | [基本用法](/components/checkbox/demo#checkbox-basic) | -| beforeChange | `Function\|Promise\|Observable` | -- | 可选,checkbox 切换前的回调函数,返回 boolean 类型,返回 false 可以阻止 radio 切换 | [回调切换](/components/checkbox/demo#condition-change) | +| beforeChange | `Function\|Promise\|Observable` | -- | 可选,checkbox 切换前的回调函数,返回 boolean 类型,返回 false 可以阻止 checkbox 切换 | [回调切换](/components/checkbox/demo#condition-change) | ### d-checkbox 事件 diff --git a/devui/common/clipboard.directive.ts b/devui/common/clipboard.directive.ts index 003aaa87..2ff4ce7c 100644 --- a/devui/common/clipboard.directive.ts +++ b/devui/common/clipboard.directive.ts @@ -8,7 +8,8 @@ import { ComponentRef, ComponentFactoryResolver, ElementRef, - TemplateRef + TemplateRef, + OnInit } from '@angular/core'; import { Clipboard } from '@angular/cdk/clipboard'; import { OverlayContainerRef } from 'ng-devui/overlay-container'; @@ -20,7 +21,7 @@ import { Subscription } from 'rxjs'; @Directive({ selector: '[dClipboard]' }) -export class ClipboardDirective implements OnDestroy { +export class ClipboardDirective implements OnInit , OnDestroy { @Input('dClipboard') devuiTargetElm: HTMLInputElement | HTMLTextAreaElement | undefined | ''; @Input() container: HTMLElement; @Input() content: string | undefined; @@ -29,7 +30,7 @@ export class ClipboardDirective implements OnDestroy { @Input() tipContent: string | HTMLElement | TemplateRef; @Output() copyResultEvent = new EventEmitter(); popoverComponentRef: ComponentRef; - i18nText: I18nInterface['common']; + i18nCommonText: I18nInterface['common']; i18nSubscription: Subscription; constructor( @@ -37,10 +38,16 @@ export class ClipboardDirective implements OnDestroy { private clipboard: Clipboard, private i18n: I18nService, private overlayContainerRef: OverlayContainerRef, - private componentFactoryResolver: ComponentFactoryResolver) { - this.i18nText = this.i18n.getI18nText().common; + private componentFactoryResolver: ComponentFactoryResolver) {} + + ngOnInit(): void { + this.setI18nText(); + } + + setI18nText() { + this.i18nCommonText = this.i18n.getI18nText().common; this.i18nSubscription = this.i18n.langChange().subscribe((data) => { - this.i18nText = data.common; + this.i18nCommonText = data.common; }); } @@ -51,7 +58,7 @@ export class ClipboardDirective implements OnDestroy { if (isSupported && this.content) { isSucceeded = this.clipboard.copy(this.content); if (isSucceeded) { - this.tipContent = this.tipContent || this.i18nText.copied; + this.tipContent = this.tipContent || this.i18nCommonText.copied; this.createPopover(); } const result = { isSupported: isSupported, isSucceeded: isSucceeded, content: this.content }; diff --git a/devui/common/demo/clipboard/clipboard.component.html b/devui/common/demo/clipboard/clipboard.component.html index e8486a2f..986245a6 100644 --- a/devui/common/demo/clipboard/clipboard.component.html +++ b/devui/common/demo/clipboard/clipboard.component.html @@ -1,6 +1,6 @@
    - +