@@ -8,97 +8,120 @@ export const useRating = withHeadless({
88 defaultProps,
99 setup : ( { props, elementRef } ) => {
1010 const { readOnly, disabled } = props ;
11- const [ valueState , setValueState ] = React . useState ( props . modelValue ) ;
12- const focusedOptionIndexRef = React . useRef ( - 1 ) ;
13- const isFocusVisibleItemRef = React . useRef ( false ) ;
14- const [ focusedOptionIndex , setFocusedOptionIndex ] = React . useState ( - 1 ) ;
11+ const [ valueState , setValueState ] = React . useState ( props . allowHalf ? ( props . defaultValue ?? props . value ) : Math . ceil ( props . defaultValue ?? props . value ?? 0 ) ) ;
12+ const [ hoverValueState , setHoverValueState ] = React . useState < number | undefined > ( undefined ) ;
13+ const [ focusedOptionIndex , setFocusedOptionIndex ] = React . useState < number | undefined > ( undefined ) ;
1514 const [ isFocusVisibleItem , setIsFocusVisibleItem ] = React . useState ( false ) ;
1615
16+ const hoverValueRef = React . useRef < number | undefined > ( undefined ) ;
17+
1718 const state = {
1819 value : valueState ,
20+ hoverValue : hoverValueState ,
1921 focusedOptionIndex,
2022 isFocusVisibleItem
2123 } ;
2224
2325 // methods
24- const setFocusedOption = ( val : number ) => {
25- focusedOptionIndexRef . current = val ;
26- setFocusedOptionIndex ( val ) ;
27- } ;
2826
29- const setIsFocusVisible = ( val : boolean ) => {
30- isFocusVisibleItemRef . current = val ;
31- setIsFocusVisibleItem ( val ) ;
32- } ;
27+ const onInputFocus = ( event : React . FocusEvent < HTMLInputElement > ) => {
28+ const inputValue = parseFloat ( event . target . value ) ;
29+ const starIndex = Math . ceil ( inputValue ) ;
3330
34- const onOptionClick = ( event : React . MouseEvent < HTMLDivElement > , value : number ) => {
35- if ( ! readOnly && ! disabled ) {
36- onOptionSelect ( value ) ;
37- setIsFocusVisible ( false ) ;
31+ setFocusedOptionIndex ( starIndex ) ;
3832
39- const firstFocusableEl = getFirstFocusableElement ( event . currentTarget ) ;
33+ const native = event . nativeEvent as FocusEvent & {
34+ sourceCapabilities ?: {
35+ firesTouchEvents : boolean ;
36+ } ;
37+ } ;
4038
41- if ( firstFocusableEl && firstFocusableEl instanceof HTMLElement ) {
42- focus ( firstFocusableEl ) ;
43- }
44- }
39+ setIsFocusVisibleItem ( native . sourceCapabilities ?. firesTouchEvents === false ) ;
4540 } ;
4641
47- const onOptionSelect = ( value : number ) => {
48- if ( readOnly || disabled ) return ;
42+ const onInputBlur = ( event : React . FocusEvent < HTMLInputElement > ) => {
43+ const relatedTarget = event . relatedTarget as HTMLElement | null ;
4944
50- if ( focusedOptionIndexRef . current === value || valueState === value ) {
51- setFocusedOption ( - 1 ) ;
52- setIsFocusVisible ( false ) ;
53- setValueState ( undefined ) ;
54- } else {
55- setFocusedOption ( value ) ;
56- setIsFocusVisible ( true ) ;
57- setValueState ( value ) ;
45+ if ( relatedTarget && elementRef . current ?. contains ( relatedTarget ) ) {
46+ return ;
5847 }
48+
49+ setFocusedOptionIndex ( undefined ) ;
5950 } ;
6051
61- const onFocus = ( event : React . FocusEvent < HTMLInputElement > , value : number ) => {
62- if ( readOnly || disabled ) return ;
52+ const onInputChange = ( event : React . ChangeEvent < HTMLInputElement > ) => {
53+ setValueState ( Number ( event . target . value ) ) ;
54+ const inputValue = parseFloat ( event . target . value ) ;
55+ const starIndex = Math . ceil ( inputValue ) ;
6356
64- setFocusedOption ( value ) ;
65- setIsFocusVisible ( ( event . nativeEvent as FocusEvent & { sourceCapabilities ?: { firesTouchEvents : boolean } | null } ) . sourceCapabilities ?. firesTouchEvents === false ) ;
57+ setFocusedOptionIndex ( starIndex ) ;
58+ setIsFocusVisibleItem ( true ) ;
6659 } ;
6760
68- const onBlur = ( event : React . FocusEvent < HTMLInputElement > ) => {
61+ const onOptionClick = ( event : React . MouseEvent < HTMLDivElement > , value : number | undefined ) => {
6962 if ( readOnly || disabled ) return ;
7063
71- const relatedTarget = event . relatedTarget as HTMLElement | null ;
64+ const effectiveValue = props . allowHalf ? value : Math . ceil ( value ?? 0 ) ;
7265
73- if ( relatedTarget && elementRef . current ?. contains ( relatedTarget ) ) {
74- return ;
66+ if ( hoverValueRef . current === effectiveValue ) {
67+ setHoverValueState ( undefined ) ;
7568 }
7669
77- setFocusedOption ( - 1 ) ;
78- setIsFocusVisible ( false ) ;
79- // formField.onBlur?.();
70+ setValueState ( ( prev ) => ( prev === effectiveValue ? undefined : effectiveValue ) ) ;
71+ setIsFocusVisibleItem ( false ) ;
72+
73+ const firstFocusableEl = getFirstFocusableElement ( event . currentTarget ) ;
74+
75+ if ( firstFocusableEl && firstFocusableEl instanceof HTMLElement ) {
76+ focus ( firstFocusableEl ) ;
77+ }
8078 } ;
8179
82- const onChange = ( event : React . ChangeEvent < HTMLInputElement > , value : number ) => {
80+ const onOptionHover = ( event : React . PointerEvent < HTMLDivElement > , value : number | undefined ) => {
8381 if ( readOnly || disabled ) return ;
8482
85- onOptionSelect ( value ) ;
86- setIsFocusVisible ( true ) ;
83+ setFocusedOptionIndex ( undefined ) ;
84+ const newValue = value ? ( props . allowHalf ? value : Math . ceil ( value ?? 0 ) ) : undefined ;
85+
86+ setHoverValueState ( newValue ) ;
87+ hoverValueRef . current = newValue ;
88+ } ;
89+
90+ const getOptionState = ( value : number ) => {
91+ const effectiveValue = hoverValueState ?? valueState ?? 0 ;
92+
93+ const floor = Math . floor ( effectiveValue ) ;
94+
95+ let state = 'empty' ;
96+
97+ if ( value <= floor ) {
98+ state = 'filled' ;
99+ } else if ( value === floor + 1 && ! Number . isInteger ( effectiveValue ) ) {
100+ state = 'half' ;
101+ }
102+
103+ return state ;
87104 } ;
88105
89106 // effects
90107
91108 React . useEffect ( ( ) => {
92- props ?. onChange ?.( { value : valueState , originalEvent : null } ) ;
109+ props ?. onValueChange ?.( { value : valueState , originalEvent : null } ) ;
93110 } , [ valueState ] ) ;
94111
112+ React . useEffect ( ( ) => {
113+ setValueState ( props . allowHalf ? ( props . defaultValue ?? props . value ) : Math . ceil ( props . defaultValue ?? props . value ?? 0 ) ) ;
114+ } , [ props . value , props . defaultValue , props . allowHalf ] ) ;
115+
95116 return {
96117 state,
97118 // methods
119+ onInputFocus,
120+ onInputBlur,
121+ onInputChange,
122+ getOptionState,
98123 onOptionClick,
99- onFocus,
100- onBlur,
101- onChange
124+ onOptionHover
102125 } ;
103126 }
104127} ) ;
0 commit comments