react-awesome-stars-rating 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![CI](https://github.com/fedoryakubovich/react-awesome-stars-rating/actions/workflows/ci.yml/badge.svg)](https://github.com/fedoryakubovich/react-awesome-stars-rating/actions/workflows/ci.yml)
5
5
  [![license](https://img.shields.io/npm/l/react-awesome-stars-rating)](https://github.com/fedoryakubovich/react-awesome-stars-rating/blob/main/LICENSE)
6
6
 
7
- An accessible star rating component for React. Half stars from pointer position, full keyboard control, any scale or palette — in about 1.5 kB gzipped with no runtime dependencies.
7
+ An accessible star rating component for React. Half stars from pointer position, full keyboard control, any scale or palette — in under 2 kB gzipped with no runtime dependencies.
8
8
 
9
9
  **[Live demo](https://react-awesome-stars-rating.vercel.app)** · **[Storybook](https://react-awesome-stars-rating.vercel.app/storybook)**
10
10
 
@@ -12,7 +12,7 @@ An accessible star rating component for React. Half stars from pointer position,
12
12
 
13
13
  ## Highlights
14
14
 
15
- - ~1.5 kB gzipped, no runtime dependencies
15
+ - Under 2 kB gzipped, no runtime dependencies
16
16
  - Half-star precision from pointer position, plus arrow-key stepping
17
17
  - `role="slider"` with live `aria-valuenow` / `aria-valuetext`, verified with axe-core
18
18
  - ESM + CJS with correct types for each, and a UMD bundle for CDN use
@@ -21,7 +21,10 @@ An accessible star rating component for React. Half stars from pointer position,
21
21
 
22
22
  ## Requirements
23
23
 
24
- React 19 or newer (`react` and `react-dom` are peer dependencies).
24
+ React 18 or React 19 (`react` and `react-dom` are peer dependencies). The
25
+ published browser package has no Node runtime requirement. Installed-consumer
26
+ CI verifies React 18 and 19 across Node 20, 22 and 24, including server
27
+ rendering and hydration.
25
28
 
26
29
  Upgrading from 0.x? See the [migration guide](https://github.com/fedoryakubovich/react-awesome-stars-rating/blob/main/MIGRATION.md).
27
30
 
@@ -79,6 +82,7 @@ export default function Example() {
79
82
  | className | Container class | `string` | `''` |
80
83
  | primaryColor | Active star color | `string` | `'orange'` |
81
84
  | secondaryColor | Inactive star color | `string` | `'grey'` |
85
+ | hoverColor | Optional pointer-preview color | `string` | `undefined` |
82
86
  | isArrowSubmit | Arrow keys trigger `onChange` immediately | `boolean` | `false` |
83
87
  | ariaLabel | Accessible label (if no `ariaLabelledBy`) | `string` | `'Star rating'` |
84
88
  | ariaLabelledBy | ID of element that labels the control | `string` | `undefined` |
@@ -87,15 +91,37 @@ export default function Example() {
87
91
 
88
92
  The control renders as a single `role="slider"` element with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and a readable `aria-valuetext` ("3.5 of 5"). Values outside `0…count` are clamped before they are reported.
89
93
 
90
- | Key | Behaviour |
91
- | :--------- | :------------------------------------------------- |
92
- | `←` / `→` | Step by half a star (`isHalf`) or a whole star |
93
- | `Enter` | Report the current value through `onChange` |
94
- | `Tab` away | Report the current value, then leave the tab order |
94
+ | Key | Behaviour |
95
+ | :-------------- | :------------------------------------------------------------ |
96
+ | `←` / `↓` | Decrease by half a star (`isHalf`) or a whole star |
97
+ | `→` / `↑` | Increase by half a star (`isHalf`) or a whole star |
98
+ | `Home` / `End` | Move to the minimum (`0`) or maximum (`count`) |
99
+ | `Enter` | Report the current value through `onChange` |
100
+ | `Tab` away/back | Report an unsubmitted keyboard change and remain re-focusable |
95
101
 
96
102
  With `isArrowSubmit`, each arrow step reports through `onChange` immediately rather than waiting for `Enter` or blur.
97
103
 
98
- When `isEdit` is `false` the control is inert: no hover preview, no keyboard, and `tabIndex={-1}`.
104
+ When `isEdit` is `false` the control is inert regardless of `isArrowSubmit`: no hover preview, no keyboard, `tabIndex={-1}`, and `aria-readonly="true"`.
105
+
106
+ ### Hover preview color
107
+
108
+ Set `hoverColor` to distinguish the pointer preview from both the saved value
109
+ and inactive stars. It is opt-in, so omitting it preserves the original
110
+ two-color presentation.
111
+
112
+ ```tsx
113
+ <ReactStarsRating
114
+ value={3}
115
+ primaryColor="#f59e0b"
116
+ hoverColor="#38bdf8"
117
+ secondaryColor="#374151"
118
+ />
119
+ ```
120
+
121
+ When the pointer previews `1.5`, the first `1.5` stars use `hoverColor`, the
122
+ remainder of the saved value uses `primaryColor`, and inactive stars use
123
+ `secondaryColor`. When previewing above the saved value, only the additional
124
+ preview region uses `hoverColor`.
99
125
 
100
126
  ## Server components
101
127
 
@@ -7,6 +7,10 @@ type GradientProps = {
7
7
  fullId: string;
8
8
  halfId: string;
9
9
  noneId: string;
10
+ hoverColor?: string;
11
+ hoverValue?: number | null;
12
+ committedValue?: number;
13
+ hoverGradientId?: string;
10
14
  };
11
- declare const Gradient: ({ primaryColor, secondaryColor, offset, index, value, fullId, halfId, noneId, }: GradientProps) => import("react").JSX.Element | null;
15
+ declare const Gradient: ({ primaryColor, secondaryColor, offset, index, value, fullId, halfId, noneId, hoverColor, hoverValue, committedValue, hoverGradientId, }: GradientProps) => import("react").JSX.Element | null;
12
16
  export default Gradient;
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use client";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require("react"),t=require("react/jsx-runtime");var n=({primaryColor:e,secondaryColor:n,offset:r=50,index:i,value:a,fullId:o,halfId:s,noneId:c})=>{let l=r;return i===1&&(l=Math.round(a%1*1e4)/100),i===1?(0,t.jsxs)(`defs`,{children:[(0,t.jsx)(`linearGradient`,{id:o,children:(0,t.jsx)(`stop`,{offset:`100%`,stopColor:e})}),(0,t.jsx)(`linearGradient`,{id:c,children:(0,t.jsx)(`stop`,{offset:`100%`,stopColor:n})}),(0,t.jsxs)(`linearGradient`,{id:s,children:[(0,t.jsx)(`stop`,{offset:`${l}%`,stopColor:e}),(0,t.jsx)(`stop`,{offset:`${l}%`,stopColor:n})]})]}):null},r=({viewBox:e=`0 0 306 306`,size:r,onMouseMove:i,onMouseLeave:a,onChange:o,index:s,value:c,isHalf:l,primaryColor:u,secondaryColor:d,fill:f,offset:p=50,fullId:m,halfId:h,noneId:g})=>{let _=f;return _=s<=c?`url(#${m})`:l&&Math.ceil(c)===s?`url(#${h})`:`url(#${g})`,(0,t.jsxs)(`svg`,{width:r,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onMouseLeave:a,onClick:o,onMouseMove:i,children:[(0,t.jsx)(n,{primaryColor:u,secondaryColor:d,offset:p,index:s,value:c,fullId:m,halfId:h,noneId:g}),(0,t.jsx)(`polygon`,{fill:_,points:`153,230.775 247.35,299.625 211.65,187.425 306,121.125 191.25,121.125 153,6.375 114.75,121.125 0,121.125 94.35,187.425 58.65,299.625`,pointerEvents:`none`})]})},i={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},a=({id:n,value:a=0,onChange:o=()=>{},isEdit:s=!0,isHalf:c=!0,count:l=5,size:u=25,starGap:d=0,className:f=``,primaryColor:p=`orange`,secondaryColor:m=`grey`,isArrowSubmit:h=!1,ariaLabel:g=`Star rating`,ariaLabelledBy:_})=>{let v=(0,e.useId)().replace(/:/g,``),y=n??`react-stars-rating-${v}`,[b,x]=(0,e.useState)(()=>({sourceValue:a,displayValue:a})),[S,C]=(0,e.useState)(!1),w=b.sourceValue===a?b.displayValue:a,T=e=>{x({sourceValue:a,displayValue:e})},E=(0,e.useMemo)(()=>`fullId-${y}`,[y]),D=(0,e.useMemo)(()=>`halfId-${y}`,[y]),O=(0,e.useMemo)(()=>`noneId-${y}`,[y]),k=e=>e.clientX-e.currentTarget.getBoundingClientRect().left>u/2,A=(e,t)=>c?k(e)?t:t-.5:t,j=(e,t)=>{s&&T(A(e,t))},M=()=>{s&&T(a||0)},N=()=>{o(w),C(!0)},P=(e,t)=>{if(!s)return;let n=A(e,t);T(n),o(n)},F=e=>{let t=w;t%1!=.5&&(t=Math.round(t)),t=Math.min(Math.max(t+e,0),l),T(t),h&&o(t)},I=e=>{switch(e.key){case`ArrowLeft`:F(c?-.5:-1);break;case`ArrowRight`:F(c?.5:1);break;case`Enter`:o(w)}},L=()=>Array.from({length:l},(e,n)=>{let i=n+1,a=i===l?void 0:{paddingRight:d};return(0,t.jsx)(`span`,{className:`star star-${i}`,style:a,children:(0,t.jsx)(r,{index:i,value:w,size:u,isHalf:c,fullId:E,halfId:D,noneId:O,primaryColor:p,secondaryColor:m,onMouseMove:e=>j(e,i),onMouseLeave:M,onChange:e=>P(e,i)})},`react-stars-rating-char${i}`)}),R=(s||h)&&!S,z=Math.min(Math.max(w,0),l);return(0,t.jsx)(`span`,{id:y,role:`slider`,"aria-label":_?void 0:g,"aria-labelledby":_,"aria-valuemin":0,"aria-valuemax":l,"aria-valuenow":z,"aria-valuetext":`${z} of ${l}`,tabIndex:R?0:-1,onKeyDown:R?I:void 0,onBlur:R?N:void 0,style:s?i.activeContainer:i.inActiveContainer,className:f,children:L()})};exports.ReactStarsRating=a,exports.default=a;
1
+ "use client";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require("react"),t=require("react/jsx-runtime");var n=(e,t)=>Math.min(100,Math.max(0,Math.round((e-(t-1))*1e4)/100)),r=({primaryColor:e,secondaryColor:r,offset:i=50,index:a,value:o,fullId:s,halfId:c,noneId:l,hoverColor:u,hoverValue:d=null,committedValue:f=o,hoverGradientId:p})=>{if(u&&d!==null&&p){let i=Math.min(f,d),o=Math.max(f,d),s=n(i,a),c=n(o,a),l=[{start:0,end:s,color:d<=f?u:e},{start:s,end:c,color:d<=f?e:u},{start:c,end:100,color:r}].filter(({start:e,end:t})=>t>e);return(0,t.jsx)(`defs`,{children:(0,t.jsx)(`linearGradient`,{id:p,children:l.flatMap(({start:e,end:n,color:r},i)=>[(0,t.jsx)(`stop`,{offset:`${e}%`,stopColor:r},`${i}-start`),(0,t.jsx)(`stop`,{offset:`${n}%`,stopColor:r},`${i}-end`)])})})}let m=i;return a===1&&(m=Math.round(o%1*1e4)/100),a===1?(0,t.jsxs)(`defs`,{children:[(0,t.jsx)(`linearGradient`,{id:s,children:(0,t.jsx)(`stop`,{offset:`100%`,stopColor:e})}),(0,t.jsx)(`linearGradient`,{id:l,children:(0,t.jsx)(`stop`,{offset:`100%`,stopColor:r})}),(0,t.jsxs)(`linearGradient`,{id:c,children:[(0,t.jsx)(`stop`,{offset:`${m}%`,stopColor:e}),(0,t.jsx)(`stop`,{offset:`${m}%`,stopColor:r})]})]}):null},i=({viewBox:e=`0 0 306 306`,size:n,onMouseMove:i,onMouseLeave:a,onChange:o,index:s,value:c,isHalf:l,primaryColor:u,secondaryColor:d,hoverColor:f,hoverValue:p=null,committedValue:m=c,fill:h,offset:g=50,fullId:_,halfId:v,noneId:y})=>{let b=h,x=`${_}-hover-${s}`;return b=f&&p!==null?`url(#${x})`:s<=c?`url(#${_})`:l&&Math.ceil(c)===s?`url(#${v})`:`url(#${y})`,(0,t.jsxs)(`svg`,{width:n,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onMouseLeave:a,onClick:o,onMouseMove:i,children:[(0,t.jsx)(r,{primaryColor:u,secondaryColor:d,offset:g,index:s,value:c,fullId:_,halfId:v,noneId:y,hoverColor:f,hoverValue:p,committedValue:m,hoverGradientId:x}),(0,t.jsx)(`polygon`,{fill:b,points:`153,230.775 247.35,299.625 211.65,187.425 306,121.125 191.25,121.125 153,6.375 114.75,121.125 0,121.125 94.35,187.425 58.65,299.625`,pointerEvents:`none`})]})},a={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},o=({id:n,value:r=0,onChange:o=()=>{},isEdit:s=!0,isHalf:c=!0,count:l=5,size:u=25,starGap:d=0,className:f=``,primaryColor:p=`orange`,secondaryColor:m=`grey`,hoverColor:h,isArrowSubmit:g=!1,ariaLabel:_=`Star rating`,ariaLabelledBy:v})=>{let y=(0,e.useId)().replace(/:/g,``),b=n??`react-stars-rating-${y}`,[x,S]=(0,e.useState)(()=>({sourceValue:r,displayValue:r})),C=(0,e.useRef)(null),[w,T]=(0,e.useState)(null),E=x.sourceValue===r?x.displayValue:r,D=e=>{S({sourceValue:r,displayValue:e})},O=(0,e.useMemo)(()=>`fullId-${b}`,[b]),k=(0,e.useMemo)(()=>`halfId-${b}`,[b]),A=(0,e.useMemo)(()=>`noneId-${b}`,[b]);(0,e.useEffect)(()=>{C.current=null},[r]);let j=e=>{let t=e.currentTarget.getBoundingClientRect();return e.clientX-t.left>t.width/2},M=(e,t)=>c?j(e)?t:t-.5:t,N=(e,t)=>{if(!s)return;let n=M(e,t);T(n),D(n)},P=()=>{s&&(T(null),D(r||0))},F=()=>{let e=C.current;e!==null&&(C.current=null,o(e))},I=(e,t)=>{if(!s)return;let n=M(e,t);D(n),C.current=null,o(n)},L=e=>{let t=Math.min(Math.max(e,0),l);t!==E&&(D(t),g?(C.current=null,o(t)):C.current=t)},R=e=>{let t=E%1==.5?E:Math.round(E);L(t+e)},z=e=>{switch(e.key){case`ArrowLeft`:case`ArrowDown`:e.preventDefault(),R(c?-.5:-1);break;case`ArrowRight`:case`ArrowUp`:e.preventDefault(),R(c?.5:1);break;case`Home`:e.preventDefault(),L(0);break;case`End`:e.preventDefault(),L(l);break;case`Enter`:e.preventDefault(),C.current=null,o(E)}},B=()=>Array.from({length:l},(e,n)=>{let a=n+1,o=a===l?void 0:{paddingRight:d};return(0,t.jsx)(`span`,{className:`star star-${a}`,style:o,children:(0,t.jsx)(i,{index:a,value:E,size:u,isHalf:c,fullId:O,halfId:k,noneId:A,primaryColor:p,secondaryColor:m,hoverColor:h,hoverValue:w,committedValue:r,onMouseMove:e=>N(e,a),onMouseLeave:P,onChange:e=>I(e,a)})},`react-stars-rating-char${a}`)}),V=s,H=Math.min(Math.max(E,0),l);return(0,t.jsx)(`span`,{id:b,role:`slider`,"aria-label":v?void 0:_,"aria-labelledby":v,"aria-valuemin":0,"aria-valuemax":l,"aria-valuenow":H,"aria-valuetext":`${H} of ${l}`,"aria-readonly":!s,tabIndex:V?0:-1,onKeyDown:V?z:void 0,onBlur:V?F:void 0,style:s?a.activeContainer:a.inActiveContainer,className:f,children:B()})};exports.ReactStarsRating=o,exports.default=o;
package/dist/index.d.cts CHANGED
@@ -70,6 +70,13 @@ export type ReactStarsRatingProps = {
70
70
  * @defaultValue 'grey'
71
71
  */
72
72
  secondaryColor?: string;
73
+ /**
74
+ * Optional color for the portion being previewed by the pointer. The saved
75
+ * value keeps {@link ReactStarsRatingProps.primaryColor}, so moving below an
76
+ * existing rating can display preview, saved, and inactive regions at once.
77
+ * When omitted, hover previews retain the original two-color behavior.
78
+ */
79
+ hoverColor?: string;
73
80
  /**
74
81
  * Report every arrow-key step through
75
82
  * {@link ReactStarsRatingProps.onChange} immediately, instead of waiting for
@@ -103,6 +110,6 @@ export type ReactStarsRatingProps = {
103
110
  * <ReactStarsRating value={value} onChange={setValue} isHalf size={32} />
104
111
  * ```
105
112
  */
106
- declare const ReactStarsRating: ({ id, value, onChange, isEdit, isHalf, count, size, starGap, className, primaryColor, secondaryColor, isArrowSubmit, ariaLabel, ariaLabelledBy, }: ReactStarsRatingProps) => import("react").JSX.Element;
113
+ declare const ReactStarsRating: ({ id, value, onChange, isEdit, isHalf, count, size, starGap, className, primaryColor, secondaryColor, hoverColor, isArrowSubmit, ariaLabel, ariaLabelledBy, }: ReactStarsRatingProps) => import("react").JSX.Element;
107
114
  export { ReactStarsRating };
108
115
  export default ReactStarsRating;
package/dist/index.d.ts CHANGED
@@ -70,6 +70,13 @@ export type ReactStarsRatingProps = {
70
70
  * @defaultValue 'grey'
71
71
  */
72
72
  secondaryColor?: string;
73
+ /**
74
+ * Optional color for the portion being previewed by the pointer. The saved
75
+ * value keeps {@link ReactStarsRatingProps.primaryColor}, so moving below an
76
+ * existing rating can display preview, saved, and inactive regions at once.
77
+ * When omitted, hover previews retain the original two-color behavior.
78
+ */
79
+ hoverColor?: string;
73
80
  /**
74
81
  * Report every arrow-key step through
75
82
  * {@link ReactStarsRatingProps.onChange} immediately, instead of waiting for
@@ -103,6 +110,6 @@ export type ReactStarsRatingProps = {
103
110
  * <ReactStarsRating value={value} onChange={setValue} isHalf size={32} />
104
111
  * ```
105
112
  */
106
- declare const ReactStarsRating: ({ id, value, onChange, isEdit, isHalf, count, size, starGap, className, primaryColor, secondaryColor, isArrowSubmit, ariaLabel, ariaLabelledBy, }: ReactStarsRatingProps) => import("react").JSX.Element;
113
+ declare const ReactStarsRating: ({ id, value, onChange, isEdit, isHalf, count, size, starGap, className, primaryColor, secondaryColor, hoverColor, isArrowSubmit, ariaLabel, ariaLabelledBy, }: ReactStarsRatingProps) => import("react").JSX.Element;
107
114
  export { ReactStarsRating };
108
115
  export default ReactStarsRating;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- "use client";import{useId as e,useMemo as t,useState as n}from"react";import{jsx as r,jsxs as i}from"react/jsx-runtime";var a=({primaryColor:e,secondaryColor:t,offset:n=50,index:a,value:o,fullId:s,halfId:c,noneId:l})=>{let u=n;return a===1&&(u=Math.round(o%1*1e4)/100),a===1?i(`defs`,{children:[r(`linearGradient`,{id:s,children:r(`stop`,{offset:`100%`,stopColor:e})}),r(`linearGradient`,{id:l,children:r(`stop`,{offset:`100%`,stopColor:t})}),i(`linearGradient`,{id:c,children:[r(`stop`,{offset:`${u}%`,stopColor:e}),r(`stop`,{offset:`${u}%`,stopColor:t})]})]}):null},o=({viewBox:e=`0 0 306 306`,size:t,onMouseMove:n,onMouseLeave:o,onChange:s,index:c,value:l,isHalf:u,primaryColor:d,secondaryColor:f,fill:p,offset:m=50,fullId:h,halfId:g,noneId:_})=>{let v=p;return v=c<=l?`url(#${h})`:u&&Math.ceil(l)===c?`url(#${g})`:`url(#${_})`,i(`svg`,{width:t,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onMouseLeave:o,onClick:s,onMouseMove:n,children:[r(a,{primaryColor:d,secondaryColor:f,offset:m,index:c,value:l,fullId:h,halfId:g,noneId:_}),r(`polygon`,{fill:v,points:`153,230.775 247.35,299.625 211.65,187.425 306,121.125 191.25,121.125 153,6.375 114.75,121.125 0,121.125 94.35,187.425 58.65,299.625`,pointerEvents:`none`})]})},s={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},c=({id:i,value:a=0,onChange:c=()=>{},isEdit:l=!0,isHalf:u=!0,count:d=5,size:f=25,starGap:p=0,className:m=``,primaryColor:h=`orange`,secondaryColor:g=`grey`,isArrowSubmit:_=!1,ariaLabel:v=`Star rating`,ariaLabelledBy:y})=>{let b=e().replace(/:/g,``),x=i??`react-stars-rating-${b}`,[S,C]=n(()=>({sourceValue:a,displayValue:a})),[w,T]=n(!1),E=S.sourceValue===a?S.displayValue:a,D=e=>{C({sourceValue:a,displayValue:e})},O=t(()=>`fullId-${x}`,[x]),k=t(()=>`halfId-${x}`,[x]),A=t(()=>`noneId-${x}`,[x]),j=e=>e.clientX-e.currentTarget.getBoundingClientRect().left>f/2,M=(e,t)=>u?j(e)?t:t-.5:t,N=(e,t)=>{l&&D(M(e,t))},P=()=>{l&&D(a||0)},F=()=>{c(E),T(!0)},I=(e,t)=>{if(!l)return;let n=M(e,t);D(n),c(n)},L=e=>{let t=E;t%1!=.5&&(t=Math.round(t)),t=Math.min(Math.max(t+e,0),d),D(t),_&&c(t)},R=e=>{switch(e.key){case`ArrowLeft`:L(u?-.5:-1);break;case`ArrowRight`:L(u?.5:1);break;case`Enter`:c(E)}},z=()=>Array.from({length:d},(e,t)=>{let n=t+1,i=n===d?void 0:{paddingRight:p};return r(`span`,{className:`star star-${n}`,style:i,children:r(o,{index:n,value:E,size:f,isHalf:u,fullId:O,halfId:k,noneId:A,primaryColor:h,secondaryColor:g,onMouseMove:e=>N(e,n),onMouseLeave:P,onChange:e=>I(e,n)})},`react-stars-rating-char${n}`)}),B=(l||_)&&!w,V=Math.min(Math.max(E,0),d);return r(`span`,{id:x,role:`slider`,"aria-label":y?void 0:v,"aria-labelledby":y,"aria-valuemin":0,"aria-valuemax":d,"aria-valuenow":V,"aria-valuetext":`${V} of ${d}`,tabIndex:B?0:-1,onKeyDown:B?R:void 0,onBlur:B?F:void 0,style:l?s.activeContainer:s.inActiveContainer,className:m,children:z()})};export{c as ReactStarsRating,c as default};
1
+ "use client";import{useEffect as e,useId as t,useMemo as n,useRef as r,useState as i}from"react";import{jsx as a,jsxs as o}from"react/jsx-runtime";var s=(e,t)=>Math.min(100,Math.max(0,Math.round((e-(t-1))*1e4)/100)),c=({primaryColor:e,secondaryColor:t,offset:n=50,index:r,value:i,fullId:c,halfId:l,noneId:u,hoverColor:d,hoverValue:f=null,committedValue:p=i,hoverGradientId:m})=>{if(d&&f!==null&&m){let n=Math.min(p,f),i=Math.max(p,f),o=s(n,r),c=s(i,r),l=[{start:0,end:o,color:f<=p?d:e},{start:o,end:c,color:f<=p?e:d},{start:c,end:100,color:t}].filter(({start:e,end:t})=>t>e);return a(`defs`,{children:a(`linearGradient`,{id:m,children:l.flatMap(({start:e,end:t,color:n},r)=>[a(`stop`,{offset:`${e}%`,stopColor:n},`${r}-start`),a(`stop`,{offset:`${t}%`,stopColor:n},`${r}-end`)])})})}let h=n;return r===1&&(h=Math.round(i%1*1e4)/100),r===1?o(`defs`,{children:[a(`linearGradient`,{id:c,children:a(`stop`,{offset:`100%`,stopColor:e})}),a(`linearGradient`,{id:u,children:a(`stop`,{offset:`100%`,stopColor:t})}),o(`linearGradient`,{id:l,children:[a(`stop`,{offset:`${h}%`,stopColor:e}),a(`stop`,{offset:`${h}%`,stopColor:t})]})]}):null},l=({viewBox:e=`0 0 306 306`,size:t,onMouseMove:n,onMouseLeave:r,onChange:i,index:s,value:l,isHalf:u,primaryColor:d,secondaryColor:f,hoverColor:p,hoverValue:m=null,committedValue:h=l,fill:g,offset:_=50,fullId:v,halfId:y,noneId:b})=>{let x=g,S=`${v}-hover-${s}`;return x=p&&m!==null?`url(#${S})`:s<=l?`url(#${v})`:u&&Math.ceil(l)===s?`url(#${y})`:`url(#${b})`,o(`svg`,{width:t,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onMouseLeave:r,onClick:i,onMouseMove:n,children:[a(c,{primaryColor:d,secondaryColor:f,offset:_,index:s,value:l,fullId:v,halfId:y,noneId:b,hoverColor:p,hoverValue:m,committedValue:h,hoverGradientId:S}),a(`polygon`,{fill:x,points:`153,230.775 247.35,299.625 211.65,187.425 306,121.125 191.25,121.125 153,6.375 114.75,121.125 0,121.125 94.35,187.425 58.65,299.625`,pointerEvents:`none`})]})},u={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},d=({id:o,value:s=0,onChange:c=()=>{},isEdit:d=!0,isHalf:f=!0,count:p=5,size:m=25,starGap:h=0,className:g=``,primaryColor:_=`orange`,secondaryColor:v=`grey`,hoverColor:y,isArrowSubmit:b=!1,ariaLabel:x=`Star rating`,ariaLabelledBy:S})=>{let C=t().replace(/:/g,``),w=o??`react-stars-rating-${C}`,[T,E]=i(()=>({sourceValue:s,displayValue:s})),D=r(null),[O,k]=i(null),A=T.sourceValue===s?T.displayValue:s,j=e=>{E({sourceValue:s,displayValue:e})},M=n(()=>`fullId-${w}`,[w]),N=n(()=>`halfId-${w}`,[w]),P=n(()=>`noneId-${w}`,[w]);e(()=>{D.current=null},[s]);let F=e=>{let t=e.currentTarget.getBoundingClientRect();return e.clientX-t.left>t.width/2},I=(e,t)=>f?F(e)?t:t-.5:t,L=(e,t)=>{if(!d)return;let n=I(e,t);k(n),j(n)},R=()=>{d&&(k(null),j(s||0))},z=()=>{let e=D.current;e!==null&&(D.current=null,c(e))},B=(e,t)=>{if(!d)return;let n=I(e,t);j(n),D.current=null,c(n)},V=e=>{let t=Math.min(Math.max(e,0),p);t!==A&&(j(t),b?(D.current=null,c(t)):D.current=t)},H=e=>{let t=A%1==.5?A:Math.round(A);V(t+e)},U=e=>{switch(e.key){case`ArrowLeft`:case`ArrowDown`:e.preventDefault(),H(f?-.5:-1);break;case`ArrowRight`:case`ArrowUp`:e.preventDefault(),H(f?.5:1);break;case`Home`:e.preventDefault(),V(0);break;case`End`:e.preventDefault(),V(p);break;case`Enter`:e.preventDefault(),D.current=null,c(A)}},W=()=>Array.from({length:p},(e,t)=>{let n=t+1,r=n===p?void 0:{paddingRight:h};return a(`span`,{className:`star star-${n}`,style:r,children:a(l,{index:n,value:A,size:m,isHalf:f,fullId:M,halfId:N,noneId:P,primaryColor:_,secondaryColor:v,hoverColor:y,hoverValue:O,committedValue:s,onMouseMove:e=>L(e,n),onMouseLeave:R,onChange:e=>B(e,n)})},`react-stars-rating-char${n}`)}),G=d,K=Math.min(Math.max(A,0),p);return a(`span`,{id:w,role:`slider`,"aria-label":S?void 0:x,"aria-labelledby":S,"aria-valuemin":0,"aria-valuemax":p,"aria-valuenow":K,"aria-valuetext":`${K} of ${p}`,"aria-readonly":!d,tabIndex:G?0:-1,onKeyDown:G?U:void 0,onBlur:G?z:void 0,style:d?u.activeContainer:u.inActiveContainer,className:g,children:W()})};export{d as ReactStarsRating,d as default};
@@ -1 +1 @@
1
- "use client";(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react")):typeof define==`function`&&define.amd?define([`exports`,`react`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ReactAwesomeStarsRating={},e.React))})(this,function(e,t){Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var n=({primaryColor:e,secondaryColor:t,offset:n=50,index:r,value:i,fullId:a,halfId:o,noneId:s})=>{let c=n;return r===1&&(c=Math.round(i%1*1e4)/100),r===1?React.createElement(`defs`,null,React.createElement(`linearGradient`,{id:a},React.createElement(`stop`,{offset:`100%`,stopColor:e})),React.createElement(`linearGradient`,{id:s},React.createElement(`stop`,{offset:`100%`,stopColor:t})),React.createElement(`linearGradient`,{id:o},React.createElement(`stop`,{offset:`${c}%`,stopColor:e}),React.createElement(`stop`,{offset:`${c}%`,stopColor:t}))):null},r=({viewBox:e=`0 0 306 306`,size:t,onMouseMove:r,onMouseLeave:i,onChange:a,index:o,value:s,isHalf:c,primaryColor:l,secondaryColor:u,fill:d,offset:f=50,fullId:p,halfId:m,noneId:h})=>{let g=d;return g=o<=s?`url(#${p})`:c&&Math.ceil(s)===o?`url(#${m})`:`url(#${h})`,React.createElement(`svg`,{width:t,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onMouseLeave:i,onClick:a,onMouseMove:r},React.createElement(n,{primaryColor:l,secondaryColor:u,offset:f,index:o,value:s,fullId:p,halfId:m,noneId:h}),React.createElement(`polygon`,{fill:g,points:`153,230.775 247.35,299.625 211.65,187.425 306,121.125 191.25,121.125 153,6.375 114.75,121.125 0,121.125 94.35,187.425 58.65,299.625`,pointerEvents:`none`}))},i={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},a=({id:e,value:n=0,onChange:a=()=>{},isEdit:o=!0,isHalf:s=!0,count:c=5,size:l=25,starGap:u=0,className:d=``,primaryColor:f=`orange`,secondaryColor:p=`grey`,isArrowSubmit:m=!1,ariaLabel:h=`Star rating`,ariaLabelledBy:g})=>{let _=(0,t.useId)().replace(/:/g,``),v=e??`react-stars-rating-${_}`,[y,b]=(0,t.useState)(()=>({sourceValue:n,displayValue:n})),[x,S]=(0,t.useState)(!1),C=y.sourceValue===n?y.displayValue:n,w=e=>{b({sourceValue:n,displayValue:e})},T=(0,t.useMemo)(()=>`fullId-${v}`,[v]),E=(0,t.useMemo)(()=>`halfId-${v}`,[v]),D=(0,t.useMemo)(()=>`noneId-${v}`,[v]),O=e=>e.clientX-e.currentTarget.getBoundingClientRect().left>l/2,k=(e,t)=>s?O(e)?t:t-.5:t,A=(e,t)=>{o&&w(k(e,t))},j=()=>{o&&w(n||0)},M=()=>{a(C),S(!0)},N=(e,t)=>{if(!o)return;let n=k(e,t);w(n),a(n)},P=e=>{let t=C;t%1!=.5&&(t=Math.round(t)),t=Math.min(Math.max(t+e,0),c),w(t),m&&a(t)},F=e=>{switch(e.key){case`ArrowLeft`:P(s?-.5:-1);break;case`ArrowRight`:P(s?.5:1);break;case`Enter`:a(C)}},I=()=>Array.from({length:c},(e,t)=>{let n=t+1,i=n===c?void 0:{paddingRight:u};return React.createElement(`span`,{key:`react-stars-rating-char${n}`,className:`star star-${n}`,style:i},React.createElement(r,{index:n,value:C,size:l,isHalf:s,fullId:T,halfId:E,noneId:D,primaryColor:f,secondaryColor:p,onMouseMove:e=>A(e,n),onMouseLeave:j,onChange:e=>N(e,n)}))}),L=(o||m)&&!x,R=Math.min(Math.max(C,0),c);return React.createElement(`span`,{id:v,role:`slider`,"aria-label":g?void 0:h,"aria-labelledby":g,"aria-valuemin":0,"aria-valuemax":c,"aria-valuenow":R,"aria-valuetext":`${R} of ${c}`,tabIndex:L?0:-1,onKeyDown:L?F:void 0,onBlur:L?M:void 0,style:o?i.activeContainer:i.inActiveContainer,className:d},I())};e.ReactStarsRating=a,e.default=a});
1
+ "use client";(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react")):typeof define==`function`&&define.amd?define([`exports`,`react`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ReactAwesomeStarsRating={},e.React))})(this,function(e,t){Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var n=(e,t)=>Math.min(100,Math.max(0,Math.round((e-(t-1))*1e4)/100)),r=({primaryColor:e,secondaryColor:t,offset:r=50,index:i,value:a,fullId:o,halfId:s,noneId:c,hoverColor:l,hoverValue:u=null,committedValue:d=a,hoverGradientId:f})=>{if(l&&u!==null&&f){let r=Math.min(d,u),a=Math.max(d,u),o=n(r,i),s=n(a,i),c=[{start:0,end:o,color:u<=d?l:e},{start:o,end:s,color:u<=d?e:l},{start:s,end:100,color:t}].filter(({start:e,end:t})=>t>e);return React.createElement(`defs`,null,React.createElement(`linearGradient`,{id:f},c.flatMap(({start:e,end:t,color:n},r)=>[React.createElement(`stop`,{key:`${r}-start`,offset:`${e}%`,stopColor:n}),React.createElement(`stop`,{key:`${r}-end`,offset:`${t}%`,stopColor:n})])))}let p=r;return i===1&&(p=Math.round(a%1*1e4)/100),i===1?React.createElement(`defs`,null,React.createElement(`linearGradient`,{id:o},React.createElement(`stop`,{offset:`100%`,stopColor:e})),React.createElement(`linearGradient`,{id:c},React.createElement(`stop`,{offset:`100%`,stopColor:t})),React.createElement(`linearGradient`,{id:s},React.createElement(`stop`,{offset:`${p}%`,stopColor:e}),React.createElement(`stop`,{offset:`${p}%`,stopColor:t}))):null},i=({viewBox:e=`0 0 306 306`,size:t,onMouseMove:n,onMouseLeave:i,onChange:a,index:o,value:s,isHalf:c,primaryColor:l,secondaryColor:u,hoverColor:d,hoverValue:f=null,committedValue:p=s,fill:m,offset:h=50,fullId:g,halfId:_,noneId:v})=>{let y=m,b=`${g}-hover-${o}`;return y=d&&f!==null?`url(#${b})`:o<=s?`url(#${g})`:c&&Math.ceil(s)===o?`url(#${_})`:`url(#${v})`,React.createElement(`svg`,{width:t,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onMouseLeave:i,onClick:a,onMouseMove:n},React.createElement(r,{primaryColor:l,secondaryColor:u,offset:h,index:o,value:s,fullId:g,halfId:_,noneId:v,hoverColor:d,hoverValue:f,committedValue:p,hoverGradientId:b}),React.createElement(`polygon`,{fill:y,points:`153,230.775 247.35,299.625 211.65,187.425 306,121.125 191.25,121.125 153,6.375 114.75,121.125 0,121.125 94.35,187.425 58.65,299.625`,pointerEvents:`none`}))},a={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},o=({id:e,value:n=0,onChange:r=()=>{},isEdit:o=!0,isHalf:s=!0,count:c=5,size:l=25,starGap:u=0,className:d=``,primaryColor:f=`orange`,secondaryColor:p=`grey`,hoverColor:m,isArrowSubmit:h=!1,ariaLabel:g=`Star rating`,ariaLabelledBy:_})=>{let v=(0,t.useId)().replace(/:/g,``),y=e??`react-stars-rating-${v}`,[b,x]=(0,t.useState)(()=>({sourceValue:n,displayValue:n})),S=(0,t.useRef)(null),[C,w]=(0,t.useState)(null),T=b.sourceValue===n?b.displayValue:n,E=e=>{x({sourceValue:n,displayValue:e})},D=(0,t.useMemo)(()=>`fullId-${y}`,[y]),O=(0,t.useMemo)(()=>`halfId-${y}`,[y]),k=(0,t.useMemo)(()=>`noneId-${y}`,[y]);(0,t.useEffect)(()=>{S.current=null},[n]);let A=e=>{let t=e.currentTarget.getBoundingClientRect();return e.clientX-t.left>t.width/2},j=(e,t)=>s?A(e)?t:t-.5:t,M=(e,t)=>{if(!o)return;let n=j(e,t);w(n),E(n)},N=()=>{o&&(w(null),E(n||0))},P=()=>{let e=S.current;e!==null&&(S.current=null,r(e))},F=(e,t)=>{if(!o)return;let n=j(e,t);E(n),S.current=null,r(n)},I=e=>{let t=Math.min(Math.max(e,0),c);t!==T&&(E(t),h?(S.current=null,r(t)):S.current=t)},L=e=>{let t=T%1==.5?T:Math.round(T);I(t+e)},R=e=>{switch(e.key){case`ArrowLeft`:case`ArrowDown`:e.preventDefault(),L(s?-.5:-1);break;case`ArrowRight`:case`ArrowUp`:e.preventDefault(),L(s?.5:1);break;case`Home`:e.preventDefault(),I(0);break;case`End`:e.preventDefault(),I(c);break;case`Enter`:e.preventDefault(),S.current=null,r(T)}},z=()=>Array.from({length:c},(e,t)=>{let r=t+1,a=r===c?void 0:{paddingRight:u};return React.createElement(`span`,{key:`react-stars-rating-char${r}`,className:`star star-${r}`,style:a},React.createElement(i,{index:r,value:T,size:l,isHalf:s,fullId:D,halfId:O,noneId:k,primaryColor:f,secondaryColor:p,hoverColor:m,hoverValue:C,committedValue:n,onMouseMove:e=>M(e,r),onMouseLeave:N,onChange:e=>F(e,r)}))}),B=o,V=Math.min(Math.max(T,0),c);return React.createElement(`span`,{id:y,role:`slider`,"aria-label":_?void 0:g,"aria-labelledby":_,"aria-valuemin":0,"aria-valuemax":c,"aria-valuenow":V,"aria-valuetext":`${V} of ${c}`,"aria-readonly":!o,tabIndex:B?0:-1,onKeyDown:B?R:void 0,onBlur:B?P:void 0,style:o?a.activeContainer:a.inActiveContainer,className:d},z())};e.ReactStarsRating=o,e.default=o});
package/dist/star.d.ts CHANGED
@@ -4,6 +4,9 @@ type StarProps = {
4
4
  size: number;
5
5
  primaryColor: string;
6
6
  secondaryColor: string;
7
+ hoverColor?: string;
8
+ hoverValue?: number | null;
9
+ committedValue?: number;
7
10
  onMouseLeave?: (event: MouseEvent<SVGSVGElement>) => void;
8
11
  onMouseMove?: (event: MouseEvent<SVGSVGElement>) => void;
9
12
  onChange?: (event: MouseEvent<SVGSVGElement>) => void;
@@ -16,5 +19,5 @@ type StarProps = {
16
19
  halfId: string;
17
20
  noneId: string;
18
21
  };
19
- declare const StarSVG: ({ viewBox, size, onMouseMove, onMouseLeave, onChange, index, value, isHalf, primaryColor, secondaryColor, fill, offset, fullId, halfId, noneId, }: StarProps) => import("react").JSX.Element;
22
+ declare const StarSVG: ({ viewBox, size, onMouseMove, onMouseLeave, onChange, index, value, isHalf, primaryColor, secondaryColor, hoverColor, hoverValue, committedValue, fill, offset, fullId, halfId, noneId, }: StarProps) => import("react").JSX.Element;
20
23
  export default StarSVG;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-awesome-stars-rating",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Accessible star rating component for React, with half stars, keyboard control and any scale or palette.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -54,9 +54,6 @@
54
54
  "url": "https://github.com/fedoryakubovich/react-awesome-stars-rating/issues"
55
55
  },
56
56
  "license": "MIT",
57
- "engines": {
58
- "node": ">=20"
59
- },
60
57
  "scripts": {
61
58
  "dev": "vite",
62
59
  "build": "vite build && vite build -c vite.umd.config.ts && tsc -p tsconfig.build.json && node scripts/emit-cjs-types.mjs",
@@ -67,8 +64,11 @@
67
64
  "format:write": "prettier --write .",
68
65
  "test": "vitest run",
69
66
  "test:watch": "vitest",
67
+ "test:e2e": "playwright test",
68
+ "test:consumer": "node scripts/verify-consumer.mjs",
70
69
  "coverage": "vitest run --coverage",
71
70
  "verify:package": "node scripts/verify-package.mjs",
71
+ "verify:release": "node scripts/verify-release-notes.mjs",
72
72
  "lint:package": "publint && attw --pack",
73
73
  "size": "size-limit",
74
74
  "typecheck": "tsc -p tsconfig.json --noEmit",
@@ -81,16 +81,18 @@
81
81
  "clean": "rimraf dist dist-site"
82
82
  },
83
83
  "peerDependencies": {
84
- "react": ">=19",
85
- "react-dom": ">=19"
84
+ "react": "^18.0.0 || ^19.0.0",
85
+ "react-dom": "^18.0.0 || ^19.0.0"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@arethetypeswrong/cli": "^0.18.5",
89
+ "@axe-core/playwright": "^4.13.0",
89
90
  "@commitlint/cli": "^21.2.2",
90
91
  "@commitlint/config-conventional": "^21.2.2",
92
+ "@playwright/test": "^1.62.1",
91
93
  "@semantic-release/github": "^12.0.9",
92
94
  "@semantic-release/npm": "^13.1.5",
93
- "@size-limit/file": "^12.1.0",
95
+ "@size-limit/file": "^13.0.3",
94
96
  "@storybook/addon-a11y": "^10.5.10",
95
97
  "@storybook/addon-links": "^10.5.10",
96
98
  "@storybook/react-vite": "^10.5.10",
@@ -117,16 +119,16 @@
117
119
  "eslint-plugin-storybook": "10.5.10",
118
120
  "formik": "^2.4.9",
119
121
  "husky": "^9.1.7",
120
- "jsdom": "^28.1.0",
121
- "lint-staged": "^16.4.0",
122
+ "jsdom": "^30.0.1",
123
+ "lint-staged": "^17.4.1",
122
124
  "prettier": "^3.9.6",
123
125
  "publint": "^0.3.24",
124
126
  "react": "^19.2.8",
125
127
  "react-dom": "^19.2.8",
126
- "react-hook-form": "^7.86.0",
128
+ "react-hook-form": "^7.87.0",
127
129
  "rimraf": "^6.1.3",
128
130
  "semantic-release": "^25.0.9",
129
- "size-limit": "^12.1.0",
131
+ "size-limit": "^13.0.3",
130
132
  "storybook": "^10.5.10",
131
133
  "tailwindcss": "^4.3.3",
132
134
  "typescript": "^6.0.3",
@@ -137,19 +139,19 @@
137
139
  {
138
140
  "name": "ESM entry",
139
141
  "path": "dist/index.mjs",
140
- "limit": "1.6 kB",
142
+ "limit": "2 kB",
141
143
  "gzip": true
142
144
  },
143
145
  {
144
146
  "name": "CJS entry",
145
147
  "path": "dist/index.cjs",
146
- "limit": "1.65 kB",
148
+ "limit": "2.05 kB",
147
149
  "gzip": true
148
150
  },
149
151
  {
150
152
  "name": "UMD bundle",
151
153
  "path": "dist/index.umd.cjs",
152
- "limit": "1.75 kB",
154
+ "limit": "2.15 kB",
153
155
  "gzip": true
154
156
  }
155
157
  ],