diff --git a/packages/ui-vue/components/button/index.ts b/packages/ui-vue/components/button/index.ts index 9f8f3c4..9d95bed 100644 --- a/packages/ui-vue/components/button/index.ts +++ b/packages/ui-vue/components/button/index.ts @@ -1,7 +1,10 @@ import type { App } from 'vue'; import FButton from './src/button.component'; +import FButtonGroup from './src/button-group.component'; export * from './src/button.props'; +export * from './src/button-group.props'; + export { FButton }; diff --git a/packages/ui-vue/components/button/src/button-group.component.tsx b/packages/ui-vue/components/button/src/button-group.component.tsx index 9e635d3..3297b9c 100644 --- a/packages/ui-vue/components/button/src/button-group.component.tsx +++ b/packages/ui-vue/components/button/src/button-group.component.tsx @@ -1,28 +1,37 @@ -// import { defineComponent, computed } from 'vue'; -// import type { SetupContext } from 'vue'; -// import { buttonGroupProps, ButtonGroupProps } from './button.props'; -// import { useButtonGroup } from './composition/use-button-group'; +import { defineComponent, computed } from 'vue'; +import type { SetupContext } from 'vue'; +import { buttonGroupProps, ButtonGroupProps } from './button-group.props'; +import { useButtonGroup } from './composition/use-button-group'; -// export default defineComponent({ -// name: 'FButtonGroup', -// props: buttonGroupProps, -// emits: ['click'], -// setup(props: ButtonGroupProps, context: SetupContext) { -// // const { onClickButton } = useButtonGroup(props, context); -// // const fButtonSize = computed(() => ({ -// // 'btn-lg': props.size === 'large', -// // 'btn-sm': props.size === 'small', -// // })); -// // const fButtonType = computed(() => ({ -// // 'btn-primary': props.buttonType === 'primary', -// // 'btn-warning': props.buttonType === 'warning', -// // 'btn-danger': props.buttonType === 'danger', -// // 'btn-success': props.buttonType === 'success', -// // 'btn-link': props.buttonType === 'link', -// // 'btn-secondary': props.buttonType === 'secondary', -// // })); +export default defineComponent({ + name: 'FButtonGroup', + props: buttonGroupProps, + emits: ['click', 'changeState', 'change', 'clickMenuOut'], + setup(props: ButtonGroupProps, context: SetupContext) { + // const { onClickButton } = useButtonGroup(props, context); + const fButtonGroupSize = computed(() => ({ + 'btn-group-lg': props.size === 'large', + 'btn-group-sm': props.size === 'small' + })); + // 样式: + // 'btn '+ + // (btn.type?'btn-'+ btn.type:'btn-link')+ + // ' '+ + // (btn.type && btn.type !== 'link' ? 'f-btn-ml' :'') -// return () => ( -// ); -// }, -// }); + // btn btn-btn.type f-btn-ml + // btn btn-link + + // const fButtonType = computed(() => ({ + // 'btn-primary': props.buttonType === 'primary', + // 'btn-warning': props.buttonType === 'warning', + // 'btn-danger': props.buttonType === 'danger', + // 'btn-success': props.buttonType === 'success', + // 'btn-link': props.buttonType === 'link', + // 'btn-secondary': props.buttonType === 'secondary', + // })); + const theFlatButtons = useButtonGroup(props, context); + + return () => ''; + } +}); diff --git a/packages/ui-vue/components/button/src/button-group.props.ts b/packages/ui-vue/components/button/src/button-group.props.ts index 10fa413..157d048 100644 --- a/packages/ui-vue/components/button/src/button-group.props.ts +++ b/packages/ui-vue/components/button/src/button-group.props.ts @@ -1,26 +1,63 @@ -// import { ExtractPropTypes, PropType } from 'vue'; +import { ExtractPropTypes, PropType } from 'vue'; -// type ButtonType = 'primary' | 'warning' | 'danger' | 'success' | 'link' | 'secondary'; -// type SizeType = 'small' | 'large'; +// eslint-disable-next-line max-len +type PlacementDirection = 'top' | 'top-left' | 'top-right' | 'left' | 'left-top' | 'left-bottom' | 'bottom' | 'bottom-left' | 'bottom-right' | 'right' | 'right-top' | 'right-bottom'; -// export const buttonProps = { -// /** -// * 组件标识 -// */ -// id: String, -// /** -// * 设置按钮类型 -// */ -// buttonType: { type: String as PropType, default: 'primary' }, -// /** -// * 是否禁用 -// */ -// disable: { type: Boolean, default: false }, -// /** -// * 按钮尺寸 -// */ -// size: { type: String as PropType, default: 'small' }, -// // 待确定:text参数 -// }; +export const buttonGroupProps = { + /** + * 组件标识 + */ + id: String, + /** + * 横向纠正的参照 + */ + rectifyReferenceH: { type: HTMLElement, default: 'referenceEl' }, + /** + * 纵向纠正的参照 + */ + rectifyReferenceV: { type: HTMLElement, default: 'referenceEl' }, + /** + * 是否自动纠正位置 + */ + autoRectify: { type: Boolean, default: 'true' }, + /** + * 计算方向的真正placement + */ + realPlacement: { type: String, default: 'bottom-right' }, + /** + * 重新计算后的placement + */ + rectifyPlacement: { type: String, default: 'bottom-right' }, + /** + * 按钮信息 + */ + data: { type: Array }, + /** + * 显示的按钮数量 默认为2 + */ + count: { type: Number, default: 2 }, + /** + * 按钮大小 + */ + size: { type: String, default: 'small' }, + /** + * 按钮样式 + */ + type: { type: String, default: 'primary' }, + /** + * 按钮展示位置 + */ + placement: { type: String, default: 'bottom' }, /** + /** + * 下拉面板显示 + */ + showPanel: { type: Boolean, default: false }, + /** + * 下拉面板显示标志 + */ + dpFlag: { type: Boolean, default: false }, -// export type ButtonProps = ExtractPropTypes; +}; +export default buttonGroupProps; + +export type ButtonGroupProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/button/src/composition/types-group.ts b/packages/ui-vue/components/button/src/composition/types-group.ts index 5253540..302e597 100644 --- a/packages/ui-vue/components/button/src/composition/types-group.ts +++ b/packages/ui-vue/components/button/src/composition/types-group.ts @@ -1,13 +1,13 @@ -// import { ComputedRef } from 'vue'; +import { ComputedRef } from 'vue'; -// export interface UseButton { -// /** -// * 附加按钮的Class -// */ -// // buttonClass: ComputedRef>; -// /** -// * 点击附加按钮事件响应函数 -// */ -// onClickButton: ($event: Event) => void; +export interface UseButtonGroup { + /** + * 附加按钮的Class + */ + // buttonClass: ComputedRef>; + /** + * 点击附加按钮事件响应函数 + */ + clickEvent: ($event: Event) => void; -// } +} diff --git a/packages/ui-vue/components/button/src/composition/use-button-group.ts b/packages/ui-vue/components/button/src/composition/use-button-group.ts index c26e7c7..a31b404 100644 --- a/packages/ui-vue/components/button/src/composition/use-button-group.ts +++ b/packages/ui-vue/components/button/src/composition/use-button-group.ts @@ -1,24 +1,329 @@ -// import { UseButton } from './types'; -// import { ButtonProps } from '../button.props'; -// import { computed, SetupContext } from 'vue'; +import { UseButtonGroup } from './types-group'; +import { ButtonGroupProps } from '../button-group.props'; +import { computed, SetupContext } from 'vue'; -// export function useButton(props: ButtonProps, context: SetupContext): UseButton { +export function useButtonGroup(props: ButtonGroupProps, context: SetupContext): UseButtonGroup { + // ngAfterViewChecked() { + // if (this.show) { + // this.setPosition(this.event) + // } + // } -// // const buttonClass = computed(() => ({ -// // // 'input-group-append': true, -// // // 'append-force-show': props.showButtonWhenDisabled && (props.readonly || props.disable), -// // })); + // function onClickButton($event: Event) { + // $event.stopPropagation(); + // // this.disabled + // if (props.disable) { + // context.emit('clickButton', $event); + // } + // } -// function onClickButton($event: Event) { -// $event.stopPropagation(); -// // this.disabled -// if (props.disable) { -// context.emit('clickButton', $event); -// } -// } + function clickEvent($event: Event) { + $event.stopPropagation(); + props.showPanel = !props.showPanel; + // body添加面板 + if (props.showPanel) { + this.appendBody(); + this.event = $event; + // 绑定相应的事件 + this.ngZone.runOutsideAngular(() => { + this.bindMenuMouseenter(); + this.bindiMenuMouseleave(); + this.mouseNotEnterLeave(); + }); + } + // 面板显示 脏值检测 + this.changeRef.detectChanges(); + this.changeRef.markForCheck(); + context.emit('changeState', props.showPanel); + } -// return { -// // buttonClass, -// onClickButton -// }; -// } + /* 按钮触发事件 */ + function toggle($event: any, btn) { + $event.stopPropagation(); + if (btn.disabled) return; + props.showPanel = false; + // 关闭下拉按钮面板 脏值检测 + this.changeRef.detectChanges(); + this.changeRef.markForCheck(); + context.emit('change', btn.id); + context.emit('click', btn); + } + + /* 显示出来的按钮组 */ + function flatButtons() { + return props.data && props.data.slice(0, this.count); + } + + function dpButtons() { + return props.data && props.data.slice(this.count); + } + + // 下拉按钮显示到body中 可改变面板方向 + function appendBody() { + if (this.dpMenu.nativeElement) { + // 添加到body 便于全部显示 + document.body.appendChild(this.dpMenu.nativeElement); + } + } + + /** + * 当下拉超出边界时 转换方向, + * 并未处理,边界不够下拉展示的情况 + * @param btnSize + */ + function changePlacement(btnSize: any) { + if (!props.autoRectify) { + return; + } + const referPosition = this.getReferencePosition(); + let newPlacement = props.realPlacement; + if (newPlacement.indexOf('bottom') > -1) { + if (this._menuHeight > referPosition.bottom - btnSize.bottom) { + newPlacement = newPlacement.replace('bottom', 'top'); + } + } else if (newPlacement.indexOf('top') > -1) { + if (this._menuHeight > btnSize.top - referPosition.top) { + newPlacement = newPlacement.replace('top', 'bottom'); + } + } + if (newPlacement.indexOf('left') > -1) { + if (this._menuWidth > btnSize.left - referPosition.left) { + newPlacement = newPlacement.replace('left', 'right'); + } + } else if (newPlacement.indexOf('right') > -1) { + if (this._menuWidth > referPosition.right - btnSize.right) { + newPlacement = newPlacement.replace('right', 'left'); + } + } + this.rectifyPlacement = newPlacement; + } + /** + * 确认参照的边界 + */ + function getReferencePosition() { + let rRight = document.documentElement.clientWidth; + let rBottom = document.documentElement.clientHeight; + let rTop = 0; + let rLeft = 0; + // 横向参照 + if (props.rectifyReferenceH) { + rRight = props.rectifyReferenceH.getBoundingClientRect().right; + rLeft = props.rectifyReferenceH.getBoundingClientRect().left; + } + // 纵向参照 + if (props.rectifyReferenceV) { + rBottom = props.rectifyReferenceV.getBoundingClientRect().bottom; + rTop = props.rectifyReferenceV.getBoundingClientRect().top; + } + return { top: rTop, left: rLeft, right: rRight, bottom: rBottom }; + } + /** + * 变化对应的class + * @param position + */ + function _getClsName(position) { + let className = ''; + switch (position) { + case 'top-right': + case 'top': + // 朝上,朝上-朝右 + className = 'dropup'; + break; + case 'top-left': + // 朝上-朝左 + className = 'dropup-left'; + break; + case 'left-bottom': + case 'left': + // 横向——朝左——朝下 + className = 'dropleft'; + break; + case 'left-top': + // 横向——朝左——朝上 + className = 'dropleft-up'; + break; + case 'right-bottom': + case 'right': + // 横向——朝右——朝下 + className = 'dropright'; + break; + case 'right-top': + // 横向——朝右——朝上 + className = 'dropright-up'; + break; + case 'bottom-left': + // 朝下——朝左 + className = 'dropdown-left'; + break; + case 'bottom-right': + className = 'dropdown'; + break; + default: + // 朝下,朝下——朝右 + className = 'dropdown'; + } + return className; + } + + function getRealPlacement(pment) { + let result = 'bottom-right'; + switch (pment) { + case 'top': + result = 'top-right'; + break; + case 'left': + result = 'left-bottom'; + break; + case 'right': + result = 'right-bottom'; + break; + case 'bottom': + result = 'bottom-right'; + break; + default: + result = pment; + } + return result; + } + + /* + * 计算的位置区分忒细化 + */ + function changePosition(btnSize: any) { + let rplacement = ''; + if (props.autoRectify) { + rplacement = this.rectifyPlacement; + } else { + rplacement = props.realPlacement; + } + let styleTop = 0; + let styleLeft = 0; + if (rplacement.indexOf('top') > -1) { + styleTop = btnSize.top - this._menuHeight; + } else if (rplacement.indexOf('bottom') > -1) { + styleTop = btnSize.bottom; + } + if (rplacement.indexOf('right') > -1) { + styleLeft = btnSize.right; + } else if (rplacement.indexOf('left') > -1) { + styleLeft = btnSize.left - this._menuWidth; + } + // 开头 + if (rplacement.indexOf('-top') > -1) { + styleTop -= btnSize.height; + } else if (rplacement.indexOf('-bottom') > -1) { + styleTop += btnSize.height; + } + this.dpMenu.nativeElement.style.top = styleTop + 'px'; + this.dpMenu.nativeElement.style.left = styleLeft + 'px'; + } + + /* 绑定下拉面板鼠标进入事件 */ + function bindMenuMouseenter() { + this.mouseenterEvent = this.changeFlagToTrue.bind(this); + this.dpMenu.nativeElement.addEventListener('mouseenter', this.mouseenterEvent); + } + + /* 绑定下拉面板鼠标离开事件 */ + function bindiMenuMouseleave() { + this.mouseleaveEvent = this.mouseLeave.bind(this); + this.dpMenu.nativeElement.addEventListener('mouseleave', this.mouseleaveEvent); + } + + /* 绑定点击面板区域之外触发的事件 */ + // bindDocClick() { + // this.documentClickEvent = this.clickDoc.bind(this); + // document.addEventListener('click', this.documentClickEvent); + // } + + /* 解绑事件 */ + function unbindMenuMouseenter() { + if (this.mouseenterEvent) { + this.dpMenu.nativeElement.removeEventListener('mouseenter', this.mouseenterEvent); + } + } + function unbindiMenuMouseleave() { + if (this.mouseleaveEvent) { + this.dpMenu.nativeElement.removeEventListener('mouseleave', this.mouseleaveEvent); + } + } + // unbindDocClick() { + // if (this.documentClickEvent) { + // document.removeEventListener('click', this.documentClickEvent); + // } + // } + + /* flag true */ + function changeFlagToTrue() { + props.dpFlag = true; + } + + /* flag false */ + function changeFlagToFalse() { + props.dpFlag = false; + } + + /* 鼠标离开时 关闭menu */ + function mouseLeave() { + if (props.dpFlag) { + this.changeFlagToFalse(); + // this.unbindDocClick(); + this.unbindiMenuMouseleave(); + this.unbindMenuMouseenter(); + this.close(); + if (this.setTimeObj) { + this.ngZone.runOutsideAngular(() => { + clearTimeout(this.setTimeObj); + }); + } + } + } + + /* 鼠标没有进入到面板 一段时间后面板自动消失 */ + function mouseNotEnterLeave() { + this.ngZone.runOutsideAngular(() => { + this.setTimeObj = setTimeout(() => { + if (!props.dpFlag) { + this.changeFlagToFalse(); + // this.unbindDocClick(); + this.unbindiMenuMouseleave(); + this.unbindMenuMouseenter(); + this.close(); + if (this.setTimeObj) { + clearTimeout(this.setTimeObj); + } + } + }, 2000); + }); + } + + /* 关闭下拉面板 */ + function close() { + props.showPanel = false; + // 关闭下拉按钮面板 脏值检测 + if (!this.changeRef.destroyed) { + this.changeRef.detectChanges(); + this.changeRef.markForCheck(); + } + this.dpBtn.nativeElement.blur(); + } + + /* 动态指定menu在body中的位置 */ + function setPosition(e) { + // 下拉按钮 + const btnSize = this.dpBtn.nativeElement.getBoundingClientRect(); + // 下拉面板 + const menuRect = this.dpMenu.nativeElement.getBoundingClientRect(); + this._menuHeight = menuRect.height; + this._menuWidth = menuRect.width; + // 如果要自动纠正方向 + if (props.autoRectify) { + this.changePlacement(btnSize); + } + this.changePosition(btnSize); + } + + return { + clickEvent + }; +}