refactor(v3/rate): use composition api
							parent
							
								
									b68bb81652
								
							
						
					
					
						commit
						2b78d2dbc0
					
				|  | @ -0,0 +1,89 @@ | |||
| import { defineComponent, computed, ExtractPropTypes } from 'vue'; | ||||
| import { getPropsSlot } from '../_util/props-util'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| 
 | ||||
| export const starProps = { | ||||
|   value: PropTypes.number, | ||||
|   index: PropTypes.number, | ||||
|   prefixCls: PropTypes.string, | ||||
|   allowHalf: PropTypes.looseBool, | ||||
|   disabled: PropTypes.looseBool, | ||||
|   character: PropTypes.any, | ||||
|   characterRender: PropTypes.func, | ||||
|   focused: PropTypes.looseBool, | ||||
|   count: PropTypes.number, | ||||
| 
 | ||||
|   onClick: PropTypes.func, | ||||
|   onHover: PropTypes.func, | ||||
| }; | ||||
| 
 | ||||
| export type StarProps = Partial<ExtractPropTypes<typeof starProps>>; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'Star', | ||||
|   inheritAttrs: false, | ||||
|   props: starProps, | ||||
|   setup(props, { slots, emit }) { | ||||
|     const onHover = e => { | ||||
|       const { index } = props; | ||||
|       emit('hover', e, index); | ||||
|     }; | ||||
|     const onClick = e => { | ||||
|       const { index } = props; | ||||
|       emit('click', e, index); | ||||
|     }; | ||||
|     const onKeyDown = e => { | ||||
|       const { index } = props; | ||||
|       if (e.keyCode === 13) { | ||||
|         emit('click', e, index); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     const getClassName = computed(() => { | ||||
|       const { prefixCls, index, value, allowHalf, focused } = props; | ||||
|       const starValue = index + 1; | ||||
|       let className = prefixCls; | ||||
|       if (value === 0 && index === 0 && focused) { | ||||
|         className += ` ${prefixCls}-focused`; | ||||
|       } else if (allowHalf && value + 0.5 >= starValue && value < starValue) { | ||||
|         className += ` ${prefixCls}-half ${prefixCls}-active`; | ||||
|         if (focused) { | ||||
|           className += ` ${prefixCls}-focused`; | ||||
|         } | ||||
|       } else { | ||||
|         className += starValue <= value ? ` ${prefixCls}-full` : ` ${prefixCls}-zero`; | ||||
|         if (starValue === value && focused) { | ||||
|           className += ` ${prefixCls}-focused`; | ||||
|         } | ||||
|       } | ||||
|       return className; | ||||
|     }); | ||||
| 
 | ||||
|     const character = getPropsSlot(slots, props, 'character'); | ||||
| 
 | ||||
|     return () => { | ||||
|       const { disabled, prefixCls, characterRender, index, count, value } = props; | ||||
|       let star = ( | ||||
|         <li class={getClassName.value}> | ||||
|           <div | ||||
|             onClick={disabled ? null : onClick} | ||||
|             onKeydown={disabled ? null : onKeyDown} | ||||
|             onMousemove={disabled ? null : onHover} | ||||
|             role="radio" | ||||
|             aria-checked={value > index ? 'true' : 'false'} | ||||
|             aria-posinset={index + 1} | ||||
|             aria-setsize={count} | ||||
|             tabindex={disabled ? -1 : 0} | ||||
|           > | ||||
|             <div class={`${prefixCls}-first`}>{character}</div> | ||||
|             <div class={`${prefixCls}-second`}>{character}</div> | ||||
|           </div> | ||||
|         </li> | ||||
|       ); | ||||
|       if (characterRender) { | ||||
|         star = characterRender(star, props); | ||||
|       } | ||||
|       return star; | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
|  | @ -1,62 +1,241 @@ | |||
| import { inject, defineComponent, VNode } from 'vue'; | ||||
| import omit from 'omit.js'; | ||||
| import { | ||||
|   defineComponent, | ||||
|   ExtractPropTypes, | ||||
|   ref, | ||||
|   reactive, | ||||
|   VNode, | ||||
|   onUpdated, | ||||
|   onBeforeUpdate, | ||||
|   onMounted, | ||||
| } from 'vue'; | ||||
| import { initDefaultProps, getPropsSlot, findDOMNode } from '../_util/props-util'; | ||||
| import { withInstall } from '../_util/type'; | ||||
| import { getOffsetLeft } from './util'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import { getOptionProps, getComponent } from '../_util/props-util'; | ||||
| import { defaultConfigProvider } from '../config-provider'; | ||||
| import VcRate from '../vc-rate'; | ||||
| import KeyCode from '../_util/KeyCode'; | ||||
| import StarFilled from '@ant-design/icons-vue/StarFilled'; | ||||
| import Tooltip from '../tooltip'; | ||||
| import { withInstall } from '../_util/type'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| 
 | ||||
| export const RateProps = { | ||||
| import Star from './Star'; | ||||
| 
 | ||||
| export const rateProps = { | ||||
|   prefixCls: PropTypes.string, | ||||
|   count: PropTypes.number, | ||||
|   value: PropTypes.number, | ||||
|   defaultValue: PropTypes.number, | ||||
|   allowHalf: PropTypes.looseBool, | ||||
|   allowClear: PropTypes.looseBool, | ||||
|   tooltips: PropTypes.arrayOf(PropTypes.string), | ||||
|   disabled: PropTypes.looseBool, | ||||
|   character: PropTypes.any, | ||||
|   autofocus: PropTypes.looseBool, | ||||
|   tabindex: PropTypes.number, | ||||
|   direction: PropTypes.string, | ||||
| }; | ||||
| 
 | ||||
| export type RateProps = Partial<ExtractPropTypes<typeof rateProps>>; | ||||
| 
 | ||||
| const Rate = defineComponent({ | ||||
|   name: 'ARate', | ||||
|   props: RateProps, | ||||
|   setup() { | ||||
|     return { | ||||
|       configProvider: inject('configProvider', defaultConfigProvider), | ||||
|   props: initDefaultProps(rateProps, { | ||||
|     value: 0, | ||||
|     count: 5, | ||||
|     allowHalf: false, | ||||
|     allowClear: true, | ||||
|     prefixCls: 'ant-rate', | ||||
|     tabindex: 0, | ||||
|     character: '★', | ||||
|     direction: 'ltr', | ||||
|   }), | ||||
|   emits: ['hoverChange', 'update:value', 'change', 'focus', 'blur', 'keydown'], | ||||
|   setup(props, { slots, attrs, emit, expose }) { | ||||
|     const { prefixCls, direction } = useConfigInject('rate', props); | ||||
|     const rateRef = ref(); | ||||
|     const starRefs = ref([]); | ||||
|     const state = reactive({ | ||||
|       sValue: props.value, | ||||
|       focused: false, | ||||
|       cleanedValue: null, | ||||
|       hoverValue: undefined, | ||||
|     }); | ||||
|     const saveRef = (el: any) => { | ||||
|       starRefs.value.push(el); | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     characterRender(node: VNode, { index }) { | ||||
|       const { tooltips } = this.$props; | ||||
|     onBeforeUpdate(() => { | ||||
|       starRefs.value = []; | ||||
|     }); | ||||
| 
 | ||||
|     const getStarDOM = index => { | ||||
|       return findDOMNode(starRefs.value[index]); | ||||
|     }; | ||||
|     const getStarValue = (index, x) => { | ||||
|       const reverse = direction.value === 'rtl'; | ||||
|       let value = index + 1; | ||||
|       if (props.allowHalf) { | ||||
|         const starEle = getStarDOM(index); | ||||
|         const leftDis = getOffsetLeft(starEle); | ||||
|         const width = starEle.clientWidth; | ||||
|         if (reverse && x - leftDis > width / 2) { | ||||
|           value -= 0.5; | ||||
|         } else if (!reverse && x - leftDis < width / 2) { | ||||
|           value -= 0.5; | ||||
|         } | ||||
|       } | ||||
|       return value; | ||||
|     }; | ||||
|     const changeValue = (value: number) => { | ||||
|       state.sValue = value; | ||||
|       emit('update:value', value); | ||||
|       emit('change', value); | ||||
|     }; | ||||
| 
 | ||||
|     const onHover = (e: MouseEvent, index) => { | ||||
|       const hoverValue = getStarValue(index, e.pageX); | ||||
|       if (hoverValue !== state.cleanedValue) { | ||||
|         state.hoverValue = hoverValue; | ||||
|         state.cleanedValue = null; | ||||
|       } | ||||
|       emit('hoverChange', hoverValue); | ||||
|     }; | ||||
|     const onMouseLeave = () => { | ||||
|       state.hoverValue = undefined; | ||||
|       state.cleanedValue = null; | ||||
|       emit('hoverChange', undefined); | ||||
|     }; | ||||
|     const onClick = (event: MouseEvent, index) => { | ||||
|       const { allowClear } = props; | ||||
|       const newValue = getStarValue(index, event.pageX); | ||||
|       let isReset = false; | ||||
|       if (allowClear) { | ||||
|         isReset = newValue === state.sValue; | ||||
|       } | ||||
|       onMouseLeave(); | ||||
|       changeValue(isReset ? 0 : newValue); | ||||
|       state.cleanedValue = isReset ? newValue : null; | ||||
|     }; | ||||
|     const onFocus = () => { | ||||
|       state.focused = true; | ||||
|       emit('focus'); | ||||
|     }; | ||||
|     const onBlur = () => { | ||||
|       state.focused = false; | ||||
|       emit('blur'); | ||||
|     }; | ||||
|     const onKeyDown = event => { | ||||
|       const { keyCode } = event; | ||||
|       const { count, allowHalf } = props; | ||||
|       const reverse = direction.value === 'rtl'; | ||||
|       if (keyCode === KeyCode.RIGHT && state.sValue < count && !reverse) { | ||||
|         if (allowHalf) { | ||||
|           state.sValue += 0.5; | ||||
|         } else { | ||||
|           state.sValue += 1; | ||||
|         } | ||||
|         changeValue(state.sValue); | ||||
|         event.preventDefault(); | ||||
|       } else if (keyCode === KeyCode.LEFT && state.sValue > 0 && !reverse) { | ||||
|         if (allowHalf) { | ||||
|           state.sValue -= 0.5; | ||||
|         } else { | ||||
|           state.sValue -= 1; | ||||
|         } | ||||
|         changeValue(state.sValue); | ||||
|         event.preventDefault(); | ||||
|       } else if (keyCode === KeyCode.RIGHT && state.sValue > 0 && reverse) { | ||||
|         if (allowHalf) { | ||||
|           state.sValue -= 0.5; | ||||
|         } else { | ||||
|           state.sValue -= 1; | ||||
|         } | ||||
|         changeValue(state.sValue); | ||||
|         event.preventDefault(); | ||||
|       } else if (keyCode === KeyCode.LEFT && state.sValue < count && reverse) { | ||||
|         if (allowHalf) { | ||||
|           state.sValue += 0.5; | ||||
|         } else { | ||||
|           state.sValue += 1; | ||||
|         } | ||||
|         changeValue(state.sValue); | ||||
|         event.preventDefault(); | ||||
|       } | ||||
|       emit('keydown', event); | ||||
|     }; | ||||
| 
 | ||||
|     const focus = () => { | ||||
|       if (!props.disabled) { | ||||
|         rateRef.value.focus(); | ||||
|       } | ||||
|     }; | ||||
|     const blur = () => { | ||||
|       if (!props.disabled) { | ||||
|         rateRef.value.blur(); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     expose({ | ||||
|       focus, | ||||
|       blur, | ||||
|     }); | ||||
| 
 | ||||
|     onMounted(() => { | ||||
|       const { autoFocus, disabled } = props; | ||||
|       if (autoFocus && !disabled) { | ||||
|         focus(); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     const characterRender = (node: VNode, { index }) => { | ||||
|       const { tooltips } = props; | ||||
|       if (!tooltips) return node; | ||||
|       return <Tooltip title={tooltips[index]}>{node}</Tooltip>; | ||||
|     }, | ||||
|     focus() { | ||||
|       (this.$refs.refRate as HTMLUListElement).focus(); | ||||
|     }, | ||||
|     blur() { | ||||
|       (this.$refs.refRate as HTMLUListElement).blur(); | ||||
|     }, | ||||
|   }, | ||||
|   render() { | ||||
|     const { prefixCls: customizePrefixCls, ...restProps } = getOptionProps(this); | ||||
|     const { getPrefixCls } = this.configProvider; | ||||
|     const prefixCls = getPrefixCls('rate', customizePrefixCls); | ||||
| 
 | ||||
|     const character = getComponent(this, 'character') || <StarFilled />; | ||||
|     const rateProps = { | ||||
|       character, | ||||
|       characterRender: this.characterRender, | ||||
|       prefixCls, | ||||
|       ...omit(restProps, ['tooltips']), | ||||
|       ...this.$attrs, | ||||
|       ref: 'refRate', | ||||
|     }; | ||||
|     return <VcRate {...rateProps} />; | ||||
|     const character = getPropsSlot(slots, props, 'character') || <StarFilled />; | ||||
| 
 | ||||
|     return () => { | ||||
|       const { count, allowHalf, disabled, tabindex } = props; | ||||
|       const { class: className, style } = attrs; | ||||
|       const stars = []; | ||||
|       const disabledClass = disabled ? `${prefixCls.value}-disabled` : ''; | ||||
|       for (let index = 0; index < count; index++) { | ||||
|         stars.push( | ||||
|           <Star | ||||
|             ref={saveRef} | ||||
|             key={index} | ||||
|             index={index} | ||||
|             count={count} | ||||
|             disabled={disabled} | ||||
|             prefixCls={`${prefixCls.value}-star`} | ||||
|             allowHalf={allowHalf} | ||||
|             value={state.hoverValue === undefined ? state.sValue : state.hoverValue} | ||||
|             onClick={onClick} | ||||
|             onHover={onHover} | ||||
|             character={character} | ||||
|             characterRender={characterRender} | ||||
|             focused={state.focused} | ||||
|           />, | ||||
|         ); | ||||
|       } | ||||
|       const rateClassName = classNames(prefixCls.value, disabledClass, className, { | ||||
|         [`${prefixCls.value}-rtl`]: direction.value === 'rtl', | ||||
|       }); | ||||
|       return ( | ||||
|         <ul | ||||
|           {...attrs} | ||||
|           class={rateClassName} | ||||
|           style={style} | ||||
|           onMouseleave={disabled ? null : onMouseLeave} | ||||
|           tabindex={disabled ? -1 : tabindex} | ||||
|           onFocus={disabled ? null : onFocus} | ||||
|           onBlur={disabled ? null : onBlur} | ||||
|           onKeydown={disabled ? null : onKeyDown} | ||||
|           ref={rateRef} | ||||
|           role="radiogroup" | ||||
|         > | ||||
|           {stars} | ||||
|         </ul> | ||||
|       ); | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   color: @rate-star-color; | ||||
|   font-size: 20px; | ||||
|   font-size: @rate-star-size; | ||||
|   line-height: unset; | ||||
|   list-style: none; | ||||
|   outline: none; | ||||
|  | @ -25,24 +25,23 @@ | |||
|   &-star { | ||||
|     position: relative; | ||||
|     display: inline-block; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     color: inherit; | ||||
|     cursor: pointer; | ||||
|     transition: all 0.3s; | ||||
| 
 | ||||
|     &:not(:last-child) { | ||||
|       margin-right: 8px; | ||||
|     } | ||||
| 
 | ||||
|     > div { | ||||
|       &:focus { | ||||
|         outline: 0; | ||||
|       } | ||||
|       transition: all 0.3s; | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus { | ||||
|         transform: scale(1.1); | ||||
|       &:focus-visible { | ||||
|         transform: @rate-star-hover-scale; | ||||
|       } | ||||
| 
 | ||||
|       &:focus:not(:focus-visible) { | ||||
|         outline: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -79,7 +78,9 @@ | |||
| 
 | ||||
|   &-text { | ||||
|     display: inline-block; | ||||
|     margin-left: 8px; | ||||
|     margin: 0 8px; | ||||
|     font-size: @font-size-base; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @import './rtl'; | ||||
|  |  | |||
|  | @ -0,0 +1,21 @@ | |||
| .@{rate-prefix-cls} { | ||||
|   &-rtl { | ||||
|     direction: rtl; | ||||
|   } | ||||
| 
 | ||||
|   &-star { | ||||
|     &:not(:last-child) { | ||||
|       .@{rate-prefix-cls}-rtl & { | ||||
|         margin-right: 0; | ||||
|         margin-left: 8px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &-first { | ||||
|       .@{rate-prefix-cls}-rtl & { | ||||
|         right: 0; | ||||
|         left: auto; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,42 @@ | |||
| /* eslint-disable import/prefer-default-export */ | ||||
| 
 | ||||
| function getScroll(w: Window) { | ||||
|   let ret = w.pageXOffset; | ||||
|   const method = 'scrollLeft'; | ||||
|   if (typeof ret !== 'number') { | ||||
|     const d = w.document; | ||||
|     // ie6,7,8 standard mode
 | ||||
|     ret = d.documentElement[method]; | ||||
|     if (typeof ret !== 'number') { | ||||
|       // quirks mode
 | ||||
|       ret = d.body[method]; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| function getClientPosition(elem: HTMLElement) { | ||||
|   let x: number; | ||||
|   let y: number; | ||||
|   const doc = elem.ownerDocument; | ||||
|   const { body } = doc; | ||||
|   const docElem = doc && doc.documentElement; | ||||
|   const box = elem.getBoundingClientRect(); | ||||
|   x = box.left; | ||||
|   y = box.top; | ||||
|   x -= docElem.clientLeft || body.clientLeft || 0; | ||||
|   y -= docElem.clientTop || body.clientTop || 0; | ||||
|   return { | ||||
|     left: x, | ||||
|     top: y, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function getOffsetLeft(el: HTMLElement) { | ||||
|   const pos = getClientPosition(el); | ||||
|   const doc = el.ownerDocument; | ||||
|   // Only IE use `parentWindow`
 | ||||
|   const w: Window = doc.defaultView || (doc as any).parentWindow; | ||||
|   pos.left += getScroll(w); | ||||
|   return pos.left; | ||||
| } | ||||
|  | @ -597,6 +597,8 @@ | |||
| // --- | ||||
| @rate-star-color: @yellow-6; | ||||
| @rate-star-bg: @border-color-split; | ||||
| @rate-star-size: 20px; | ||||
| @rate-star-hover-scale: scale(1.1); | ||||
| 
 | ||||
| // Card | ||||
| // --- | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Sendya
						Sendya