!22 更新Notify等组件

Merge pull request !22 from Sagi/feature/input-group
This commit is contained in:
Sagi 2022-09-27 14:55:55 +00:00 committed by Gitee
commit fa02cff8aa
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9 changed files with 248 additions and 555 deletions

View File

@ -1,106 +1,106 @@
import { ComputedRef, Ref } from 'vue'; import { ComputedRef, Ref } from 'vue';
export interface UseButton { export interface UseButton {
/** /**
* Class * Class
*/ */
buttonClass: ComputedRef<Record<string, boolean | undefined>>; buttonClass: ComputedRef<Record<string, boolean | undefined>>;
/** /**
* *
*/ */
onClickButton: ($event: Event) => void; onClickButton: ($event: Event) => void;
/** /**
* *
*/ */
onMouseEnterButton: ($event: MouseEvent) => void; onMouseEnterButton: ($event: MouseEvent) => void;
/** /**
* *
*/ */
onMouseLeaveButton: ($event: MouseEvent) => void; onMouseLeaveButton: ($event: MouseEvent) => void;
/** /**
* *
*/ */
onMouseOverButton: () => void; onMouseOverButton: () => void;
} }
export interface UseClear { export interface UseClear {
/** /**
* *
*/ */
enableClearButton: ComputedRef<boolean>; enableClearButton: ComputedRef<boolean>;
/** /**
* *
*/ */
showClearButton: Ref<boolean>; showClearButton: Ref<boolean>;
/** /**
* *
*/ */
onClearValue: ($event: Event) => void; onClearValue: ($event: Event) => void;
/** /**
* *
*/ */
onMouseEnterTextBox: ($event: MouseEvent) => void; onMouseEnterTextBox: ($event: MouseEvent) => void;
/** /**
* *
*/ */
onMouseLeaveTextBox: ($event: MouseEvent) => void; onMouseLeaveTextBox: ($event: MouseEvent) => void;
} }
export interface UseTextBox { export interface UseTextBox {
/** /**
* *
*/ */
hasFocusedTextBox: ComputedRef<boolean>; hasFocusedTextBox: ComputedRef<boolean>;
/** /**
* *
*/ */
isTextBoxReadonly: ComputedRef<boolean>; isTextBoxReadonly: ComputedRef<boolean>;
/** /**
* Class * Class
*/ */
textBoxClass: ComputedRef<Record<string, boolean | undefined>>; textBoxClass: ComputedRef<Record<string, boolean | undefined>>;
/** /**
* *
*/ */
textBoxPlaceholder: ComputedRef<string>; textBoxPlaceholder: ComputedRef<string>;
/** /**
* *
*/ */
textBoxTitle: ComputedRef<string>; textBoxTitle: ComputedRef<string>;
/** /**
* change事件 * change事件
*/ */
changeTextBoxValue: (newValue: string, showEmitChangeEmit: boolean) => void; changeTextBoxValue: (newValue: string, showEmitChangeEmit: boolean) => void;
/** /**
* *
*/ */
onBlurTextBox: ($event: Event) => void; onBlurTextBox: ($event: Event) => void;
/** /**
* *
*/ */
onClickTextBox: ($event: Event) => void; onClickTextBox: ($event: Event) => void;
/** /**
* *
*/ */
onFocusTextBox: ($event: Event) => void; onFocusTextBox: ($event: Event) => void;
/** /**
* *
*/ */
onInput: ($event: Event) => void; onInput: ($event: Event) => void;
/** /**
* *
*/ */
onMouseDownTextBox: ($event: MouseEvent) => void; onMouseDownTextBox: ($event: MouseEvent) => void;
/** /**
* *
*/ */
onKeyDownTextBox: ($event: Event) => void; onKeyDownTextBox: ($event: Event) => void;
/** /**
* *
*/ */
onKeyUpTextBox: ($event: Event) => void; onKeyUpTextBox: ($event: Event) => void;
/** /**
* *
*/ */
onTextBoxValueChange: ($event: Event) => void; onTextBoxValueChange: ($event: Event) => void;
} }

View File

@ -2,6 +2,8 @@ import { computed, defineComponent, ref, SetupContext, watch } from 'vue';
import { NotifyButton, NotifyData } from '../notify.props'; import { NotifyButton, NotifyData } from '../notify.props';
import { ToastProps, toastProps } from './toast.props'; import { ToastProps, toastProps } from './toast.props';
import './toast.css';
export default defineComponent({ export default defineComponent({
name: 'Toast', name: 'Toast',
props: toastProps, props: toastProps,
@ -41,6 +43,8 @@ export default defineComponent({
const shouldShowTitle = computed(() => toast.value.title && toast.value.msg); const shouldShowTitle = computed(() => toast.value.title && toast.value.msg);
const shouldShowMessageOnly = computed(() => !toast.value.title && toast.value.msg);
const shouldShowCloseButton = computed(() => { const shouldShowCloseButton = computed(() => {
return true; return true;
}); });
@ -108,6 +112,15 @@ export default defineComponent({
{shouldShowButtonsInTitle.value && renderNotifyButtons()} {shouldShowButtonsInTitle.value && renderNotifyButtons()}
</> </>
)} )}
{shouldShowMessageOnly.value &&
(toast.value.buttons ? (
<div class="toast-title-btns-wrapper d-flex">
<h5 class="toast-title modal-tips-title only-toast-msg" v-html={toast.value.msg}></h5>
<div class="after-toast-title text-right ml-auto">{renderNotifyButtons()}</div>
</div>
) : (
<h5 class="toast-title modal-tips-title only-toast-msg" v-html={toast.value.msg}></h5>
))}
</div> </div>
</section> </section>
)} )}

View File

@ -0,0 +1,63 @@
.toast-title-beforeshow {
opacity: 0;
}
@-webkit-keyframes farrisMoveUpIn {
0% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0;
}
100% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1;
}
}
@keyframes farrisMoveUpIn {
0% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0;
}
100% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1;
}
}
@-webkit-keyframes farrisMoveUpOut {
0% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1;
}
100% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0;
}
}
@keyframes farrisMoveUpOut {
0% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1;
}
100% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0;
}
}
.toast.fadeIn {
-webkit-animation: farrisMoveUpIn 0.2s linear;
animation: farrisMoveUpIn 0.2s linear;
}
.toast.fadeOut {
-webkit-animation: farrisMoveUpOut 0.2s linear;
animation: farrisMoveUpOut 0.2s linear;
}

View File

@ -1,222 +0,0 @@
import { Component, Input, ViewChildren, QueryList, AfterContentChecked, OnInit, Output, EventEmitter, NgZone, HostListener } from '@angular/core';
import { NotifyConfig, NotifyData } from './notifiy.options';
import { isFunction } from 'lodash-es';
import { NotifyComponent } from './notify.component';
@Component({
selector: 'farris-notify-container',
template: `
<div [id]="id" class="farris-notify" [ngClass]="[position]" [ngStyle]="style">
<farris-notify *ngFor="let toast of toasts" [toast]="toast" [animateCls]="animateCls" (close)="closeToast(toast)" (btnClick)="btnClickHander($event,toast)"></farris-notify>
</div>
`
})
export class NotifyContainerComponent implements OnInit, AfterContentChecked {
static POSITIONS: Array<String> = ['bottom-right', 'bottom-left',
'top-right', 'top-left', 'top-center', 'bottom-center', 'center-center'];
static ANIMATES: Array<string> = ['bounceInRight', 'bounceInLeft',
'bounceInRight', 'bounceInLeft', 'bounceInDown', 'bounceInUp', 'bounceIn'];
private _position = '';
style = {
'left': '',
'right': '',
'top': '',
'bottom': ''
};
notifyDistance = {
'left': 12,
'right': 12,
'top': 136,
'bottom': 12
};
animateCls = 'fadeIn';
config: NotifyConfig;
id = '';
@ViewChildren(NotifyComponent) notifyCmpList: QueryList<NotifyComponent>;
@Output() empty = new EventEmitter();
@Input() set position(value: string) {
if (value) {
let notFound = true;
for (let i = 0; i < NotifyContainerComponent.POSITIONS.length; i++) {
if (NotifyContainerComponent.POSITIONS[i] === value) {
notFound = false;
break;
}
}
if (notFound) {
value = this.config.position;
}
} else {
value = this.config.position;
}
// console.log(this.config);
// if (value === 'center-center') {
// this.animateCls = 'bounceIn';
// } else {
const i = NotifyContainerComponent.POSITIONS.indexOf(value);
//this.animateCls = NotifyContainerComponent.ANIMATES[i];
this.animateCls = 'fadeIn';
//}
this._position = 'toasty-position-' + value;
if (this.config.left) {
this.notifyDistance.left = this.config.left;
}
if (this.config.right) {
this.notifyDistance.right = this.config.right;
}
if (this.config.top) {
this.notifyDistance.top = this.config.top;
}
if (this.config.bottom) {
this.notifyDistance.bottom = this.config.bottom;
}
this.initstyle();
if (value === 'top-left') {
this.style.left = `${this.notifyDistance.left}px`;
this.style.top = `${this.notifyDistance.top}px`;
}
else if (value === 'top-center') {
this.style.top = `${this.notifyDistance.top}px`;
}
else if (value === 'top-right') {
this.style.right = `${this.notifyDistance.right}px`;
this.style.top = `${this.notifyDistance.top}px`;
}
else if (value === 'bottom-left') {
this.style.left = `${this.notifyDistance.left}px`;
this.style.bottom = `${this.notifyDistance.bottom}px`;
}
else if (value === 'bottom-center') {
this.style.bottom = `${this.notifyDistance.bottom}px`;
}
else if (value === 'bottom-right') {
this.style.right = `${this.notifyDistance.right}px`;
this.style.bottom = `${this.notifyDistance.bottom}px`;
}
}
get position() {
if (this._position) {
return this._position;
} else {
return 'toasty-position-top-center';
}
}
initstyle() {
this.style = {
'left': '',
'right': '',
'top': '',
'bottom': ''
};
}
toasts: Array<NotifyData> = [];
constructor(private ngZone: NgZone) { }
ngOnInit() {}
@HostListener('click', ['$event'])
onContainerClick($event: MouseEvent) {
$event.stopPropagation();
return false;
}
ngAfterContentChecked() {
if (this.notifyCmpList && this.notifyCmpList.length) {
this.notifyCmpList.forEach(cmp => {
cmp.close.subscribe(() => {
this.clear(cmp.toast.id);
});
});
}
}
closeToast(toast: NotifyData) {
this.clear(toast.id);
}
add(notify: NotifyData) {
if (this.toasts.length >= this.config.limit) {
this.toasts.shift();
}
this.toasts.push(notify);
if (notify.timeout) {
this._setTimeout(notify);
}
}
clear(id: number | string) {
if (id) {
this.toasts.forEach((value: any, key: number) => {
if (value.id === id) {
if (value.onRemove && isFunction(value.onRemove)) {
value.onRemove.call(this, value);
}
this.toasts.splice(key, 1);
}
});
if (this.toasts.length === 0) {
this.empty.emit();
}
} else {
throw new Error('Please provide id of Toast to close');
}
}
clearAll() {
this.toasts.forEach((value: any, key: number) => {
if (value.onRemove && isFunction(value.onRemove)) {
value.onRemove.call(this, value);
}
});
this.toasts = [];
this.empty.emit();
}
private findNotifyComponent(id: number | string) {
return this.notifyCmpList.find(item => item.toast.id === id);
}
private _setTimeout(notify: NotifyData) {
// this.ngZone.runOutsideAngular(() => {
window.setTimeout(() => {
// this.clear(notify.id);
const cmp = this.findNotifyComponent(notify.id);
if (cmp) {
cmp.state = true;
cmp.inCls[cmp.animateCls] = false;
}
}, notify.timeout);
// });
}
btnClickHander(ev, notify: NotifyData) {
const cmp = this.findNotifyComponent(notify.id);
ev.callback(ev['ev'], cmp);
}
}

View File

@ -1,223 +0,0 @@
import {
Component,
OnInit,
OnChanges,
Input,
Output,
EventEmitter,
ViewChild,
ElementRef,
SimpleChanges,
NgZone,
TemplateRef
} from '@angular/core';
import { NotifyData,NotifyButton } from './notifiy.options';
@Component({
selector: 'farris-notify',
template: `
<div #notifyDiv class="toast" [ngClass]="inCls">
<button *ngIf="toast.showClose" class="toast-close f-btn-icon f-bare" (click)="closeToast($event)">
<span class="f-icon modal_close"></span>
</button>
<section class="modal-tips" *ngIf="toast.title || toast.msg">
<div class="float-left modal-tips-iconwrap">
<span class="f-icon" [ngClass]=" getPurType()"></span>
</div>
<div class="modal-tips-content">
<ng-container *ngIf="toast.title&&toast.msg">
<h5 class="toast-title modal-tips-title" [innerHTML]="toast.title | safe: 'html'"></h5>
<p class="toast-msg" [innerHtml]="toast.msg | safe:'html'"></p>
<div class="after-toast-msg text-right" *ngIf="toast.buttons">
<ng-container [ngTemplateOutlet]="useButtonsTemplate() ? toast.buttons : defaultButtonRef"></ng-container>
</div>
</ng-container>
<ng-container *ngIf="!toast.title&&toast.msg">
<ng-container *ngIf="toast.buttons" >
<div class="toast-title-btns-wrapper d-flex" >
<h5 class="toast-title modal-tips-title only-toast-msg" [innerHtml]="toast.msg | safe:'html'"></h5>
<div class="after-toast-title text-right ml-auto">
<ng-container [ngTemplateOutlet]="useButtonsTemplate() ? toast.buttons : defaultButtonRef"></ng-container>
</div>
</div>
</ng-container>
<ng-container *ngIf="!toast.buttons" >
<h5 class="toast-title modal-tips-title only-toast-msg" [innerHtml]="toast.msg | safe:'html'"></h5>
</ng-container>
</ng-container>
</div>
</section>
</div>
<ng-template #defaultButtonRef>
<span class="'f-preten-link '+btn.cls?btn.cls:''" *ngFor="let btn of toast.buttons; last as isLast"
(click)="clickHandler($event,btn) ">
{{ btn.text }}
</span>
</ng-template>
`,
styles: [
`
.toast-title-beforeshow{
opacity:0;
}
@-webkit-keyframes farrisMoveUpIn {
0% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0
}
100% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1
}
}
@keyframes farrisMoveUpIn {
0% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0
}
100% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1
}
}
@-webkit-keyframes farrisMoveUpOut {
0% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1
}
100% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0
}
}
@keyframes farrisMoveUpOut {
0% {
transform: translateY(0);
transform-origin: 0 0;
opacity: 1
}
100% {
transform: translateY(-100%);
transform-origin: 0 0;
opacity: 0
}
}
.toast.fadeIn {
-webkit-animation: farrisMoveUpIn 0.2s linear;
animation: farrisMoveUpIn 0.2s linear;
}
.toast.fadeOut {
-webkit-animation: farrisMoveUpOut 0.2s linear;
animation: farrisMoveUpOut 0.2s linear;
}
`
]
})
export class NotifyComponent implements OnInit, OnChanges {
@Input() toast: NotifyData;
@Output() close = new EventEmitter();
@Output() btnClick = new EventEmitter();
@ViewChild('notifyDiv') notifyDiv: ElementRef;
_state = false;
@Input() animateCls: string;
outCls = '';
inCls = {};
get state() {
return this._state;
}
set state(value) {
this._state = value;
if (value) {
this.inCls[this.animateCls] = false;
this.inCls['animated'] = value;
this.inCls[this.outCls] = value;
// this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
this.close.next(this.toast);
}, 200);
// });
}
}
constructor(private ngZone: NgZone) { }
ngOnInit() {
if (this.toast.buttons) {
this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
let closeEl = this.notifyDiv.nativeElement.querySelector('.toast-close');
let contentEl =this.notifyDiv.nativeElement.querySelector('.toast-title-btns-wrapper');
if(closeEl['clientHeight']+10<contentEl['clientHeight']){
contentEl.classList.remove('d-flex');
}
contentEl.classList.remove('toast-title-beforeshow');
}, 200);
})
}
}
ngOnChanges(changes: SimpleChanges) {
this.getAnimateCls(changes.animateCls.currentValue);
}
getAnimateCls(cls?: any) {
cls = cls || 'bounceInRight';
// this.outCls = 'bounceOut' + cls.substr(8);
// switch (cls.substr(8)) {
// case 'Up':
// this.outCls = 'bounceOutDown';
// break;
// case 'Down':
// this.outCls = 'bounceOutUp';
// break;
// }
this.outCls = 'fadeOut';
// outCls += tmp;
this.inCls = {
[this.toast.type]: true,
[this.toast.theme]: true,
'animated': false,
[cls]: true,
[this.outCls]: this.state
};
}
closeToast(event: any) {
event.stopPropagation();
event.preventDefault();
this.state = true;
}
getPurType(): string {
if (this.toast && this.toast.type) {
return 'f-icon-' + this.toast.type.replace('toasty-type-', '');
}
return 'f-icon-default';
}
useButtonsTemplate() {
return this.toast.buttons instanceof TemplateRef;
}
//点击事件抛出
clickHandler(ev, btn:NotifyButton) {
if (btn.handle) {
this.btnClick.emit({ ev: ev, callback: btn.handle });
}
}
}

View File

@ -1,34 +1,83 @@
import { isFunction } from 'lodash';
import { computed, defineComponent, ref, SetupContext } from 'vue'; import { computed, defineComponent, ref, SetupContext } from 'vue';
import { NotifyContainerComponent } from './notify-container.component';
import { NotifyData, NotifyProps, notifyProps } from './notify.props'; import { NotifyData, NotifyProps, notifyProps } from './notify.props';
export default defineComponent({ export default defineComponent({
name: 'Notify', name: 'Notify',
props: notifyProps, props: notifyProps,
emits: [], emits: ['empty'],
setup(props: NotifyProps, context: SetupContext) { setup(props: NotifyProps, context: SetupContext) {
const notifyClass = computed(() => ({ const notifyClass = computed(() => ({
'farris-notify': true, 'farris-notify': true,
})); }));
const defaultNotifyDistance = {
left: 12,
right: 12,
top: 136,
bottom: 12,
};
const toasts = ref(props.toasts || []);
const notifyStyle = computed(() => ({ const notifyStyle = computed(() => ({
left: '', left: props.position.indexOf('left') > -1 ? `${props.left ? props.left : defaultNotifyDistance.left}px` : '',
right: '', right: props.position.indexOf('right') > -1 ? `${props.right ? props.right : defaultNotifyDistance.right}px` : '',
top: '', top: props.position.indexOf('top') > -1 ? `${props.top ? props.top : defaultNotifyDistance.top}px` : '',
bottom: '', bottom: props.position.indexOf('bottom') > -1 ? `${props.bottom ? props.bottom : defaultNotifyDistance.bottom}px` : '',
})); }));
const toasts = computed(() => { function closeToast(toast: NotifyData) {}
return props.toasts ? props.toasts : [];
});
function addToast(toast: NotifyData) {
if (toasts.value.length >= props.limit) {
toasts.value.shift();
}
toasts.value.push(toast);
// if (props.timeout) {
// this._setTimeout(notify);
// }
}
function invokeToastOnRemoveCallback(toast: NotifyData) {
if (toast && toast.onRemove && isFunction(toast.onRemove)) {
toast.onRemove.call(this, toast);
}
}
function clear(id: number | string) {
const targetToastIndex = toasts.value.findIndex((toast: NotifyData) => toast.id === id);
if (targetToastIndex > -1) {
const targetToast = toasts.value[targetToastIndex];
invokeToastOnRemoveCallback(targetToast);
toasts.value.splice(targetToastIndex, 1);
}
}
function clearAll() {
toasts.value.forEach((toast: NotifyData) => invokeToastOnRemoveCallback(toast));
toasts.value.length = 0;
context.emit('empty');
}
context.expose({ addToast, clear, clearAll, closeToast });
function onClose($event: Event, toast: NotifyData) {
closeToast(toast);
}
function onClick($event: Event) {}
return () => { return () => {
return ( return (
<div id={props.id} class={notifyClass.value} style={notifyStyle.value}> <div id={props.id} class={notifyClass.value} style={notifyStyle.value}>
{toasts.value.map((toastData: NotifyData) => { {toasts.value.map((toastData: NotifyData) => {
return <f-toast v-model={toastData} animate={props.animate} onClose={} onClick={}></f-toast>; return (
<f-toast
v-model={toastData}
animate={props.animate}
onClose={($event: Event) => onClose($event, toastData)}
onClick={onClick}></f-toast>
);
})} })}
</div> </div>
); );

View File

@ -1,4 +1,3 @@
import { NotifyData } from './notify.props';
import { ExtractPropTypes, PropType } from 'vue'; import { ExtractPropTypes, PropType } from 'vue';
export type NotifyPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'top-center' | 'bottom-center' | 'center-center'; export type NotifyPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'top-center' | 'bottom-center' | 'center-center';
@ -13,6 +12,8 @@ export type ToastyAnimate =
| 'bounceIn' | 'bounceIn'
| 'fadeIn'; | 'fadeIn';
export type NotifyTheme = 'default' | 'material' | 'bootstrap';
export interface NotifyButton { export interface NotifyButton {
customClass?: string; customClass?: string;
text: string; text: string;
@ -37,9 +38,18 @@ export interface NotifyData {
// export interface NotifyData extends NotifyOptions {} // export interface NotifyData extends NotifyOptions {}
export const notifyProps = { export const notifyProps = {
limit: { type: Number, default: 5 },
showCloseButton: { type: Boolean, default: true },
position: { type: String as PropType<NotifyPosition>, default: 'top-center' },
timeout: { type: Number, default: 3000 },
theme: { type: String as PropType<NotifyTheme>, default: 'bootstrap' },
left: { type: Number },
right: { type: Number },
top: { type: Number },
bottom: { type: Number },
id: { type: String }, id: { type: String },
animate: { type: String as PropType<ToastyAnimate>, default: 'fadeIn' }, animate: { type: String as PropType<ToastyAnimate>, default: 'fadeIn' },
position: { type: String as PropType<NotifyPosition>, default: 'toasty-position-top-center' },
toasts: { type: Array<NotifyData> }, toasts: { type: Array<NotifyData> },
options: { type: Object as PropType<NotifyData> },
}; };
export type NotifyProps = ExtractPropTypes<typeof notifyProps>; export type NotifyProps = ExtractPropTypes<typeof notifyProps>;

View File

@ -18,7 +18,7 @@ export default defineComponent({
<> <>
<div class="popover-arrow arrow"></div> <div class="popover-arrow arrow"></div>
{shouldShowTitle.value && <h3 class="popover-title popover-header">{props.title}</h3>} {shouldShowTitle.value && <h3 class="popover-title popover-header">{props.title}</h3>}
<div class={popoverContainerClass.value}></div> <div class={popoverContainerClass.value}>{context.slots.default && context.slots?.default()}</div>
</> </>
); );
}; };

View File

@ -1,7 +1,10 @@
import { ExtractPropTypes } from 'vue'; import { ExtractPropTypes, PropType } from 'vue';
export type PopoverPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';
export const popoverProps = { export const popoverProps = {
title: { type: String }, title: { type: String },
position: { type: String as PropType<PopoverPosition>, default: 'top' },
}; };
export type PopoverProps = ExtractPropTypes<typeof popoverProps>; export type PopoverProps = ExtractPropTypes<typeof popoverProps>;