Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2b78d2d

Browse files
committedMay 28, 2021
refactor(v3/rate): use composition api
1 parent b68bb81 commit 2b78d2d

File tree

6 files changed

+383
-49
lines changed

6 files changed

+383
-49
lines changed
 

‎components/rate/Star.tsx

Lines changed: 89 additions & 0 deletions
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

Lines changed: 218 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,241 @@
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+
23+
export const rateProps = {
1224
prefixCls: PropTypes.string,
1325
count: PropTypes.number,
1426
value: PropTypes.number,
15-
defaultValue: PropTypes.number,
1627
allowHalf: PropTypes.looseBool,
1728
allowClear: PropTypes.looseBool,
1829
tooltips: PropTypes.arrayOf(PropTypes.string),
1930
disabled: PropTypes.looseBool,
2031
character: PropTypes.any,
2132
autofocus: PropTypes.looseBool,
33+
tabindex: PropTypes.number,
34+
direction: PropTypes.string,
2235
};
2336

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

‎components/rate/style/index.less

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
margin: 0;
1111
padding: 0;
1212
color: @rate-star-color;
13-
font-size: 20px;
13+
font-size: @rate-star-size;
1414
line-height: unset;
1515
list-style: none;
1616
outline: none;
@@ -25,24 +25,23 @@
2525
&-star {
2626
position: relative;
2727
display: inline-block;
28-
margin: 0;
29-
padding: 0;
3028
color: inherit;
3129
cursor: pointer;
32-
transition: all 0.3s;
3330

3431
&:not(:last-child) {
3532
margin-right: 8px;
3633
}
3734

3835
> div {
39-
&:focus {
40-
outline: 0;
41-
}
36+
transition: all 0.3s;
4237

4338
&:hover,
44-
&:focus {
45-
transform: scale(1.1);
39+
&:focus-visible {
40+
transform: @rate-star-hover-scale;
41+
}
42+
43+
&:focus:not(:focus-visible) {
44+
outline: 0;
4645
}
4746
}
4847

@@ -79,7 +78,9 @@
7978

8079
&-text {
8180
display: inline-block;
82-
margin-left: 8px;
81+
margin: 0 8px;
8382
font-size: @font-size-base;
8483
}
8584
}
85+
86+
@import './rtl';

‎components/rate/style/rtl.less

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.@{rate-prefix-cls} {
2+
&-rtl {
3+
direction: rtl;
4+
}
5+
6+
&-star {
7+
&:not(:last-child) {
8+
.@{rate-prefix-cls}-rtl & {
9+
margin-right: 0;
10+
margin-left: 8px;
11+
}
12+
}
13+
14+
&-first {
15+
.@{rate-prefix-cls}-rtl & {
16+
right: 0;
17+
left: auto;
18+
}
19+
}
20+
}
21+
}

‎components/rate/util.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* eslint-disable import/prefer-default-export */
2+
3+
function getScroll(w: Window) {
4+
let ret = w.pageXOffset;
5+
const method = 'scrollLeft';
6+
if (typeof ret !== 'number') {
7+
const d = w.document;
8+
// ie6,7,8 standard mode
9+
ret = d.documentElement[method];
10+
if (typeof ret !== 'number') {
11+
// quirks mode
12+
ret = d.body[method];
13+
}
14+
}
15+
return ret;
16+
}
17+
18+
function getClientPosition(elem: HTMLElement) {
19+
let x: number;
20+
let y: number;
21+
const doc = elem.ownerDocument;
22+
const { body } = doc;
23+
const docElem = doc && doc.documentElement;
24+
const box = elem.getBoundingClientRect();
25+
x = box.left;
26+
y = box.top;
27+
x -= docElem.clientLeft || body.clientLeft || 0;
28+
y -= docElem.clientTop || body.clientTop || 0;
29+
return {
30+
left: x,
31+
top: y,
32+
};
33+
}
34+
35+
export function getOffsetLeft(el: HTMLElement) {
36+
const pos = getClientPosition(el);
37+
const doc = el.ownerDocument;
38+
// Only IE use `parentWindow`
39+
const w: Window = doc.defaultView || (doc as any).parentWindow;
40+
pos.left += getScroll(w);
41+
return pos.left;
42+
}

‎components/style/themes/default.less

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,8 @@
597597
// ---
598598
@rate-star-color: @yellow-6;
599599
@rate-star-bg: @border-color-split;
600+
@rate-star-size: 20px;
601+
@rate-star-hover-scale: scale(1.1);
600602

601603
// Card
602604
// ---

0 commit comments

Comments
 (0)
Please sign in to comment.