Skip to content

Commit 836ae6d

Browse files
committed
Merge branch 'sendya-refactor/v3/rate' into v3
2 parents 6b2af5c + 3186b92 commit 836ae6d

File tree

8 files changed

+393
-50
lines changed

8 files changed

+393
-50
lines changed

components/_util/hooks/useRef.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { onBeforeUpdate, readonly, ref, DeepReadonly, UnwrapRef } from 'vue';
2+
3+
export type UseRef = [(el: any) => void, DeepReadonly<UnwrapRef<any[]>>];
4+
5+
export const useRef = (): UseRef => {
6+
const refs = ref<any>([]);
7+
const setRef = (el: any) => {
8+
refs.value.push(el);
9+
};
10+
onBeforeUpdate(() => {
11+
refs.value = [];
12+
});
13+
return [setRef, readonly(refs)];
14+
};

components/rate/Star.tsx

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { defineComponent, computed, ExtractPropTypes } from 'vue';
2+
import { getPropsSlot } from '../_util/props-util';
3+
import PropTypes from '../_util/vue-types';
4+
5+
export const starProps = {
6+
value: PropTypes.number,
7+
index: PropTypes.number,
8+
prefixCls: PropTypes.string,
9+
allowHalf: PropTypes.looseBool,
10+
disabled: PropTypes.looseBool,
11+
character: PropTypes.any,
12+
characterRender: PropTypes.func,
13+
focused: PropTypes.looseBool,
14+
count: PropTypes.number,
15+
16+
onClick: PropTypes.func,
17+
onHover: PropTypes.func,
18+
};
19+
20+
export type StarProps = Partial<ExtractPropTypes<typeof starProps>>;
21+
22+
export default defineComponent({
23+
name: 'Star',
24+
inheritAttrs: false,
25+
props: starProps,
26+
setup(props, { slots, emit }) {
27+
const onHover = e => {
28+
const { index } = props;
29+
emit('hover', e, index);
30+
};
31+
const onClick = e => {
32+
const { index } = props;
33+
emit('click', e, index);
34+
};
35+
const onKeyDown = e => {
36+
const { index } = props;
37+
if (e.keyCode === 13) {
38+
emit('click', e, index);
39+
}
40+
};
41+
42+
const getClassName = computed(() => {
43+
const { prefixCls, index, value, allowHalf, focused } = props;
44+
const starValue = index + 1;
45+
let className = prefixCls;
46+
if (value === 0 && index === 0 && focused) {
47+
className += ` ${prefixCls}-focused`;
48+
} else if (allowHalf && value + 0.5 >= starValue && value < starValue) {
49+
className += ` ${prefixCls}-half ${prefixCls}-active`;
50+
if (focused) {
51+
className += ` ${prefixCls}-focused`;
52+
}
53+
} else {
54+
className += starValue <= value ? ` ${prefixCls}-full` : ` ${prefixCls}-zero`;
55+
if (starValue === value && focused) {
56+
className += ` ${prefixCls}-focused`;
57+
}
58+
}
59+
return className;
60+
});
61+
62+
const character = getPropsSlot(slots, props, 'character');
63+
64+
return () => {
65+
const { disabled, prefixCls, characterRender, index, count, value } = props;
66+
let star = (
67+
<li class={getClassName.value}>
68+
<div
69+
onClick={disabled ? null : onClick}
70+
onKeydown={disabled ? null : onKeyDown}
71+
onMousemove={disabled ? null : onHover}
72+
role="radio"
73+
aria-checked={value > index ? 'true' : 'false'}
74+
aria-posinset={index + 1}
75+
aria-setsize={count}
76+
tabindex={disabled ? -1 : 0}
77+
>
78+
<div class={`${prefixCls}-first`}>{character}</div>
79+
<div class={`${prefixCls}-second`}>{character}</div>
80+
</div>
81+
</li>
82+
);
83+
if (characterRender) {
84+
star = characterRender(star, props);
85+
}
86+
return star;
87+
};
88+
},
89+
});

components/rate/index.tsx

+213-39
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,236 @@
1-
import { inject, defineComponent, VNode } from 'vue';
2-
import omit from 'omit.js';
1+
import {
2+
defineComponent,
3+
ExtractPropTypes,
4+
ref,
5+
reactive,
6+
VNode,
7+
onUpdated,
8+
onBeforeUpdate,
9+
onMounted,
10+
} from 'vue';
11+
import { initDefaultProps, getPropsSlot, findDOMNode } from '../_util/props-util';
12+
import { withInstall } from '../_util/type';
13+
import { getOffsetLeft } from './util';
14+
import classNames from '../_util/classNames';
315
import PropTypes from '../_util/vue-types';
4-
import { getOptionProps, getComponent } from '../_util/props-util';
5-
import { defaultConfigProvider } from '../config-provider';
6-
import VcRate from '../vc-rate';
16+
import KeyCode from '../_util/KeyCode';
717
import StarFilled from '@ant-design/icons-vue/StarFilled';
818
import Tooltip from '../tooltip';
9-
import { withInstall } from '../_util/type';
19+
import useConfigInject from '../_util/hooks/useConfigInject';
1020

11-
export const RateProps = {
21+
import Star from './Star';
22+
import { useRef } from '../_util/hooks/useRef';
23+
24+
export const rateProps = {
1225
prefixCls: PropTypes.string,
1326
count: PropTypes.number,
1427
value: PropTypes.number,
15-
defaultValue: PropTypes.number,
1628
allowHalf: PropTypes.looseBool,
1729
allowClear: PropTypes.looseBool,
1830
tooltips: PropTypes.arrayOf(PropTypes.string),
1931
disabled: PropTypes.looseBool,
2032
character: PropTypes.any,
2133
autofocus: PropTypes.looseBool,
34+
tabindex: PropTypes.number,
35+
direction: PropTypes.string,
2236
};
2337

38+
export type RateProps = Partial<ExtractPropTypes<typeof rateProps>>;
39+
2440
const Rate = defineComponent({
2541
name: 'ARate',
26-
props: RateProps,
27-
setup() {
28-
return {
29-
configProvider: inject('configProvider', defaultConfigProvider),
42+
props: initDefaultProps(rateProps, {
43+
value: 0,
44+
count: 5,
45+
allowHalf: false,
46+
allowClear: true,
47+
prefixCls: 'ant-rate',
48+
tabindex: 0,
49+
character: '★',
50+
direction: 'ltr',
51+
}),
52+
emits: ['hoverChange', 'update:value', 'change', 'focus', 'blur', 'keydown'],
53+
setup(props, { slots, attrs, emit, expose }) {
54+
const { prefixCls, direction } = useConfigInject('rate', props);
55+
const rateRef = ref();
56+
const [setRef, starRefs] = useRef();
57+
const state = reactive({
58+
sValue: props.value,
59+
focused: false,
60+
cleanedValue: null,
61+
hoverValue: undefined,
62+
});
63+
64+
const getStarDOM = index => {
65+
return findDOMNode(starRefs[index]);
3066
};
31-
},
32-
methods: {
33-
characterRender(node: VNode, { index }) {
34-
const { tooltips } = this.$props;
67+
const getStarValue = (index, x) => {
68+
const reverse = direction.value === 'rtl';
69+
let value = index + 1;
70+
if (props.allowHalf) {
71+
const starEle = getStarDOM(index);
72+
const leftDis = getOffsetLeft(starEle);
73+
const width = starEle.clientWidth;
74+
if (reverse && x - leftDis > width / 2) {
75+
value -= 0.5;
76+
} else if (!reverse && x - leftDis < width / 2) {
77+
value -= 0.5;
78+
}
79+
}
80+
return value;
81+
};
82+
const changeValue = (value: number) => {
83+
state.sValue = value;
84+
emit('update:value', value);
85+
emit('change', value);
86+
};
87+
88+
const onHover = (e: MouseEvent, index) => {
89+
const hoverValue = getStarValue(index, e.pageX);
90+
if (hoverValue !== state.cleanedValue) {
91+
state.hoverValue = hoverValue;
92+
state.cleanedValue = null;
93+
}
94+
emit('hoverChange', hoverValue);
95+
};
96+
const onMouseLeave = () => {
97+
state.hoverValue = undefined;
98+
state.cleanedValue = null;
99+
emit('hoverChange', undefined);
100+
};
101+
const onClick = (event: MouseEvent, index) => {
102+
const { allowClear } = props;
103+
const newValue = getStarValue(index, event.pageX);
104+
let isReset = false;
105+
if (allowClear) {
106+
isReset = newValue === state.sValue;
107+
}
108+
onMouseLeave();
109+
changeValue(isReset ? 0 : newValue);
110+
state.cleanedValue = isReset ? newValue : null;
111+
};
112+
const onFocus = () => {
113+
state.focused = true;
114+
emit('focus');
115+
};
116+
const onBlur = () => {
117+
state.focused = false;
118+
emit('blur');
119+
};
120+
const onKeyDown = event => {
121+
const { keyCode } = event;
122+
const { count, allowHalf } = props;
123+
const reverse = direction.value === 'rtl';
124+
if (keyCode === KeyCode.RIGHT && state.sValue < count && !reverse) {
125+
if (allowHalf) {
126+
state.sValue += 0.5;
127+
} else {
128+
state.sValue += 1;
129+
}
130+
changeValue(state.sValue);
131+
event.preventDefault();
132+
} else if (keyCode === KeyCode.LEFT && state.sValue > 0 && !reverse) {
133+
if (allowHalf) {
134+
state.sValue -= 0.5;
135+
} else {
136+
state.sValue -= 1;
137+
}
138+
changeValue(state.sValue);
139+
event.preventDefault();
140+
} else if (keyCode === KeyCode.RIGHT && state.sValue > 0 && reverse) {
141+
if (allowHalf) {
142+
state.sValue -= 0.5;
143+
} else {
144+
state.sValue -= 1;
145+
}
146+
changeValue(state.sValue);
147+
event.preventDefault();
148+
} else if (keyCode === KeyCode.LEFT && state.sValue < count && reverse) {
149+
if (allowHalf) {
150+
state.sValue += 0.5;
151+
} else {
152+
state.sValue += 1;
153+
}
154+
changeValue(state.sValue);
155+
event.preventDefault();
156+
}
157+
emit('keydown', event);
158+
};
159+
160+
const focus = () => {
161+
if (!props.disabled) {
162+
rateRef.value.focus();
163+
}
164+
};
165+
const blur = () => {
166+
if (!props.disabled) {
167+
rateRef.value.blur();
168+
}
169+
};
170+
171+
expose({
172+
focus,
173+
blur,
174+
});
175+
176+
onMounted(() => {
177+
const { autoFocus, disabled } = props;
178+
if (autoFocus && !disabled) {
179+
focus();
180+
}
181+
});
182+
183+
const characterRender = (node: VNode, { index }) => {
184+
const { tooltips } = props;
35185
if (!tooltips) return node;
36186
return <Tooltip title={tooltips[index]}>{node}</Tooltip>;
37-
},
38-
focus() {
39-
(this.$refs.refRate as HTMLUListElement).focus();
40-
},
41-
blur() {
42-
(this.$refs.refRate as HTMLUListElement).blur();
43-
},
44-
},
45-
render() {
46-
const { prefixCls: customizePrefixCls, ...restProps } = getOptionProps(this);
47-
const { getPrefixCls } = this.configProvider;
48-
const prefixCls = getPrefixCls('rate', customizePrefixCls);
49-
50-
const character = getComponent(this, 'character') || <StarFilled />;
51-
const rateProps = {
52-
character,
53-
characterRender: this.characterRender,
54-
prefixCls,
55-
...omit(restProps, ['tooltips']),
56-
...this.$attrs,
57-
ref: 'refRate',
58-
};
59-
return <VcRate {...rateProps} />;
187+
};
188+
const character = getPropsSlot(slots, props, 'character') || <StarFilled />;
189+
190+
return () => {
191+
const { count, allowHalf, disabled, tabindex } = props;
192+
const { class: className, style } = attrs;
193+
const stars = [];
194+
const disabledClass = disabled ? `${prefixCls.value}-disabled` : '';
195+
for (let index = 0; index < count; index++) {
196+
stars.push(
197+
<Star
198+
ref={setRef}
199+
key={index}
200+
index={index}
201+
count={count}
202+
disabled={disabled}
203+
prefixCls={`${prefixCls.value}-star`}
204+
allowHalf={allowHalf}
205+
value={state.hoverValue === undefined ? state.sValue : state.hoverValue}
206+
onClick={onClick}
207+
onHover={onHover}
208+
character={character}
209+
characterRender={characterRender}
210+
focused={state.focused}
211+
/>,
212+
);
213+
}
214+
const rateClassName = classNames(prefixCls.value, disabledClass, className, {
215+
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
216+
});
217+
return (
218+
<ul
219+
{...attrs}
220+
class={rateClassName}
221+
style={style}
222+
onMouseleave={disabled ? null : onMouseLeave}
223+
tabindex={disabled ? -1 : tabindex}
224+
onFocus={disabled ? null : onFocus}
225+
onBlur={disabled ? null : onBlur}
226+
onKeydown={disabled ? null : onKeyDown}
227+
ref={rateRef}
228+
role="radiogroup"
229+
>
230+
{stars}
231+
</ul>
232+
);
233+
};
60234
},
61235
});
62236

0 commit comments

Comments
 (0)