react-awesome-stars-rating 1.3.0 → 1.4.1

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,15 +4,15 @@
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 under 3 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 approximately 3 kB gzipped with no runtime dependencies.
8
8
 
9
- **[Live demo](https://react-awesome-stars-rating.vercel.app)** · **[Storybook](https://react-awesome-stars-rating.vercel.app/storybook)**
9
+ **[Live demo](https://react-awesome-stars-rating-orpin.vercel.app/)** · **[Storybook](https://react-awesome-stars-rating-orpin.vercel.app/storybook/)**
10
10
 
11
11
  ![react-awesome-stars-rating preview](https://raw.githubusercontent.com/fedoryakubovich/react-awesome-stars-rating/main/images/gifs/react-awesome-stars-rating.gif)
12
12
 
13
13
  ## Highlights
14
14
 
15
- - Under 3 kB gzipped, no runtime dependencies
15
+ - Approximately 3 kB gzipped (ESM under 3 kB), 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
@@ -56,40 +56,88 @@ expose React as a global, and expects `window.React` to be present.
56
56
  ## Usage
57
57
 
58
58
  ```tsx
59
+ import { useState } from 'react';
59
60
  import ReactStarsRating from 'react-awesome-stars-rating';
60
61
 
61
- const onChange = (value: number) => {
62
- console.log(`Rating: ${value}`);
63
- };
64
-
65
62
  export default function Example() {
66
- return <ReactStarsRating onChange={onChange} value={3.5} />;
63
+ const [rating, setRating] = useState(3.5);
64
+ return <ReactStarsRating value={rating} onChange={setRating} />;
67
65
  }
68
66
  ```
69
67
 
68
+ Use `value` with a state-updating `onChange` when your application owns the
69
+ rating. For a self-managed input, use `<ReactStarsRating defaultValue={3.5} />`.
70
+ Do not switch between controlled and uncontrolled usage during an input's lifetime.
71
+
72
+ ### Clear a rating
73
+
74
+ Keep zero as the empty rating and provide an explicitly labeled button:
75
+
76
+ ```tsx
77
+ const [rating, setRating] = useState(0);
78
+
79
+ <>
80
+ <ReactStarsRating
81
+ value={rating}
82
+ onChange={setRating}
83
+ ariaLabel="Your rating"
84
+ />
85
+ <button type="button" onClick={() => setRating(0)}>
86
+ Clear rating
87
+ </button>
88
+ </>;
89
+ ```
90
+
91
+ ### Display an average
92
+
93
+ Use `readOnly` for aggregate scores, with visible text so the number and review
94
+ count are available without interpreting the star graphic:
95
+
96
+ ```tsx
97
+ <>
98
+ <ReactStarsRating value={4.3} readOnly ariaLabel="Average rating" />
99
+ <span>4.3 out of 5 · 128 reviews</span>
100
+ </>
101
+ ```
102
+
103
+ With `isHalf`, the SVG displays partial fill for fractional values; editing
104
+ still selects half-star steps. Do not round an average just to display it.
105
+
106
+ ### Next.js App Router
107
+
108
+ The published entry point includes `"use client"`. A Server Component can render
109
+ a read-only rating with serializable props. Put state and callbacks inside your
110
+ own Client Component (`"use client"` at the top of its file), as in the usage
111
+ example above; do not pass ordinary callback functions from a Server Component.
112
+
70
113
  ## Props
71
114
 
72
- | Name | Description | Type | Default |
73
- | :------------- | :---------------------------------------- | :------------------------- | :-------------- |
74
- | id | Identifier | `string` | auto-generated |
75
- | value | Controlled current value | `number` | `undefined` |
76
- | defaultValue | Initial uncontrolled value | `number` | `0` |
77
- | onChange | Called when value changes | `(value: number) => void` | `() => {}` |
78
- | name | Hidden native form input name | `string` | `undefined` |
79
- | disabled | Disable interaction and form submission | `boolean` | `false` |
80
- | isEdit | Editing mode | `boolean` | `true` |
81
- | isHalf | Allow half stars | `boolean` | `true` |
82
- | count | Number of stars | `number` | `5` |
83
- | size | Star size (px) | `number` | `25` |
84
- | starGap | Gap between stars | `number` | `0` |
85
- | className | Container class | `string` | `''` |
86
- | primaryColor | Active star color | `string` | `'orange'` |
87
- | secondaryColor | Inactive star color | `string` | `'grey'` |
88
- | hoverColor | Optional pointer-preview color | `string` | `undefined` |
89
- | isArrowSubmit | Arrow keys trigger `onChange` immediately | `boolean` | `false` |
90
- | ariaLabel | Accessible label (if no `ariaLabelledBy`) | `string` | `'Star rating'` |
91
- | ariaLabelledBy | ID of element that labels the control | `string` | `undefined` |
92
- | getValueText | Formats accessible value text | `(value, count) => string` | English text |
115
+ | Name | Description | Type | Default |
116
+ | :--------------- | :---------------------------------------- | :----------------------------------------- | :-------------- |
117
+ | id | Identifier | `string` | auto-generated |
118
+ | value | Controlled current value | `number` | `undefined` |
119
+ | defaultValue | Initial uncontrolled value | `number` | `0` |
120
+ | onChange | Called when value changes | `(value: number) => void` | `() => {}` |
121
+ | onBlur / onFocus | Native slider focus callbacks | `React.FocusEventHandler<HTMLSpanElement>` | `undefined` |
122
+ | dir | Layout, fill and horizontal key direction | `'ltr' \| 'rtl'` | `'ltr'` |
123
+ | name | Hidden native form input name | `string` | `undefined` |
124
+ | form | Associated form ID | `string` | `undefined` |
125
+ | disabled | Disable interaction and form submission | `boolean` | `false` |
126
+ | readOnly | Prevent editing | `boolean` | `false` |
127
+ | isEdit | Deprecated inverse editing switch | `boolean` | `true` |
128
+ | isHalf | Allow half stars | `boolean` | `true` |
129
+ | count | Number of stars | `number` | `5` |
130
+ | size | Star size (px) | `number` | `25` |
131
+ | starGap | Gap between stars | `number` | `0` |
132
+ | className | Container class | `string` | `''` |
133
+ | style | Container style and CSS variables | `ReactStarsRatingStyle` | `undefined` |
134
+ | primaryColor | Active star color | `string` | `'orange'` |
135
+ | secondaryColor | Inactive star color | `string` | `'grey'` |
136
+ | hoverColor | Optional pointer-preview color | `string` | `undefined` |
137
+ | isArrowSubmit | Arrow keys trigger `onChange` immediately | `boolean` | `false` |
138
+ | ariaLabel | Accessible label (if no `ariaLabelledBy`) | `string` | `'Star rating'` |
139
+ | ariaLabelledBy | ID of element that labels the control | `string` | `undefined` |
140
+ | getValueText | Formats accessible value text | `(value, count) => string` | English text |
93
141
 
94
142
  ## Keyboard and accessibility
95
143
 
@@ -105,7 +153,15 @@ The control renders as a single `role="slider"` element with `aria-valuemin`, `a
105
153
 
106
154
  With `isArrowSubmit`, each arrow step reports through `onChange` immediately rather than waiting for `Enter` or blur.
107
155
 
108
- When `isEdit` is `false` the control is inert regardless of `isArrowSubmit`: no hover preview, no keyboard, `tabIndex={-1}`, and `aria-readonly="true"`.
156
+ Pointer changes commit only when a primary, left-button gesture starts on the
157
+ control and completes. Right/middle clicks, unrelated releases and canceled
158
+ gestures do not submit a rating. Touch dragging uses the same gesture tracking.
159
+
160
+ `onFocus` and `onBlur` receive native React focus events without replacing the
161
+ internal focus handling. A pending keyboard change is committed before `onBlur`,
162
+ so form libraries can mark the field touched with its latest value.
163
+
164
+ When `readOnly` is `true` the control is inert regardless of `isArrowSubmit`: no hover preview, no keyboard, `tabIndex={-1}`, and `aria-readonly="true"`. The older `isEdit={false}` API remains supported but is deprecated.
109
165
 
110
166
  The slider explicitly reports `aria-orientation="horizontal"`. Use
111
167
  `getValueText` to localize its spoken value:
@@ -117,6 +173,18 @@ The slider explicitly reports `aria-orientation="horizontal"`. Use
117
173
  />
118
174
  ```
119
175
 
176
+ ### Right-to-left layouts
177
+
178
+ Pass `dir="rtl"` explicitly to reverse star order, half-star filling, pointer
179
+ selection and horizontal keyboard direction. The default is `ltr`, independent
180
+ of ancestor direction. In RTL, `ArrowLeft` increases and `ArrowRight` decreases;
181
+ `ArrowUp`, `ArrowDown`, `Home` and `End` retain their normal behavior. Star gaps
182
+ use logical spacing in both directions.
183
+
184
+ ```tsx
185
+ <ReactStarsRating dir="rtl" defaultValue={2.5} ariaLabel="التقييم" />
186
+ ```
187
+
120
188
  ### Hover preview color
121
189
 
122
190
  Set `hoverColor` to distinguish the pointer preview from both the saved value
@@ -137,6 +205,27 @@ remainder of the saved value uses `primaryColor`, and inactive stars use
137
205
  `secondaryColor`. When previewing above the saved value, only the additional
138
206
  preview region uses `hoverColor`.
139
207
 
208
+ ### Styling
209
+
210
+ The forwarded `ref`, `className`, `style`, `title`, and `data-*` attributes
211
+ target the slider container. CSS variables provide lightweight theming:
212
+
213
+ ```tsx
214
+ <ReactStarsRating
215
+ defaultValue={3.5}
216
+ style={{
217
+ '--stars-rating-primary-color': '#f59e0b',
218
+ '--stars-rating-secondary-color': '#475569',
219
+ '--stars-rating-focus-color': '#2563eb',
220
+ '--stars-rating-size': '2rem',
221
+ '--stars-rating-gap': '0.25rem',
222
+ }}
223
+ />
224
+ ```
225
+
226
+ The default focus-visible ring uses the system `Highlight` color, and SVG
227
+ stars allow browser forced-colors adjustments for high-contrast modes.
228
+
140
229
  ## Server components
141
230
 
142
231
  The package carries the `'use client'` directive, so it can be imported directly from a server component in the Next.js App Router without a wrapper.
@@ -157,7 +246,58 @@ uncontrolled usage and native form reset behavior.
157
246
  ```
158
247
 
159
248
  Use `value` with `onChange` for controlled state. Supplying `value` always
160
- takes precedence over `defaultValue`.
249
+ takes precedence over `defaultValue`. Use `form="form-id"` when the rating is
250
+ rendered outside its associated form.
251
+
252
+ An uncanceled native reset restores `defaultValue` without calling `onChange`.
253
+ Calling `event.preventDefault()` in the form's `onReset` preserves the rating.
254
+ Controlled values remain the responsibility of the parent.
255
+
256
+ HTML hidden inputs do not participate in native constraint validation, so a
257
+ `required` attribute would be misleading and is intentionally not exposed.
258
+ For required ratings, validate the controlled value or submitted `FormData`
259
+ and expose the result with `aria-invalid` and descriptive error text.
260
+
261
+ For example, inside a component, validate on submit and focus the rating when
262
+ invalid (also validate submitted data on your server):
263
+
264
+ ```tsx
265
+ const [rating, setRating] = useState(0);
266
+ const [error, setError] = useState(false);
267
+ const ratingRef = useRef<HTMLSpanElement>(null);
268
+ const errorId = useId();
269
+
270
+ <form
271
+ onSubmit={(event) => {
272
+ event.preventDefault();
273
+ const invalid = rating === 0;
274
+ setError(invalid);
275
+ if (invalid) ratingRef.current?.focus();
276
+ else console.log('Submit rating:', rating);
277
+ }}
278
+ >
279
+ <ReactStarsRating
280
+ ref={ratingRef}
281
+ name="rating"
282
+ value={rating}
283
+ onChange={(value) => {
284
+ setRating(value);
285
+ setError(false);
286
+ }}
287
+ ariaLabel="Your rating (required)"
288
+ aria-invalid={error || undefined}
289
+ aria-describedby={error ? errorId : undefined}
290
+ />
291
+ {error && (
292
+ <p id={errorId} role="alert">
293
+ Choose a rating before submitting.
294
+ </p>
295
+ )}
296
+ <button type="submit">Submit</button>
297
+ </form>;
298
+ ```
299
+
300
+ Import `useState`, `useRef` and `useId` from React for this recipe.
161
301
 
162
302
  ### React Hook Form
163
303
 
@@ -171,7 +311,12 @@ const { control } = useForm({ defaultValues: { rating: 3.5 } });
171
311
  control={control}
172
312
  name="rating"
173
313
  render={({ field }) => (
174
- <ReactStarsRating value={field.value} onChange={field.onChange} />
314
+ <ReactStarsRating
315
+ ref={field.ref}
316
+ value={field.value}
317
+ onChange={field.onChange}
318
+ onBlur={field.onBlur}
319
+ />
175
320
  )}
176
321
  />;
177
322
  ```
@@ -187,6 +332,7 @@ import ReactStarsRating from 'react-awesome-stars-rating';
187
332
  <ReactStarsRating
188
333
  value={field.value}
189
334
  onChange={(value) => form.setFieldValue('rating', value)}
335
+ onBlur={() => form.setFieldTouched('rating', true)}
190
336
  />
191
337
  )}
192
338
  </Field>;
@@ -218,6 +364,7 @@ const form = useForm<FormValues>({
218
364
  <ReactStarsRating
219
365
  value={field.state.value}
220
366
  onChange={field.handleChange}
367
+ onBlur={field.handleBlur}
221
368
  />
222
369
  )}
223
370
  </form.Field>
@@ -239,6 +386,9 @@ npm run build # library build (ESM + CJS + UMD + types) → dist/
239
386
  npm run verify:package # render every entry point from dist/
240
387
  npm run lint:package # publint + are-the-types-wrong on the packed tarball
241
388
  npm run size # size-limit budgets for every entry point
389
+ npm run test:e2e # Chromium, Firefox and WebKit interaction/a11y tests
390
+ npm run verify:links # README HTTPS links (requires network)
391
+ npm run verify:site # public demo + Storybook smoke tests (requires Chromium/network)
242
392
  npm run build:site # demo site → dist-site/
243
393
  ```
244
394
 
@@ -249,6 +399,20 @@ Pre-commit (Husky) runs lint-staged (Prettier + ESLint on staged files) and comm
249
399
  - **CI** — on push/PR to `main`: format, lint, commitlint, typecheck, tests, coverage, build, package verification, publint/attw, size budgets, Storybook and site builds.
250
400
  - **Deploy** — Vercel builds the demo site on every push (see `vercel.json`), serving Storybook at `/storybook`.
251
401
  - **Release** — [semantic-release](https://github.com/semantic-release/semantic-release) on push to `main`: analyzes Conventional Commits, publishes to npm and creates a GitHub release.
402
+ - **Recovery** — the manually dispatched `Verify or repair a published release` workflow verifies an exact npm version and its provenance, and can recreate a missing GitHub release only when its tag already exists.
403
+ - **Security** — CodeQL and OpenSSF Scorecard publish findings to GitHub code scanning; dependency review checks pull requests for vulnerable dependencies.
404
+ - **Documentation health** — changed documentation/check scripts, pushes to `main`, a weekly schedule and manual runs check README links and the deployed demo/Storybook. These external-service checks are separate from release gates. If npm's website blocks automated requests, the check explicitly reports that limitation and verifies the package through the registry instead.
405
+
406
+ Browser failures retain screenshots, traces and an HTML report in GitHub Actions
407
+ artifacts for seven days. Local browser tests require `npx playwright install`.
408
+ Development and release tooling use the Node 22 version in `.nvmrc`; consumer
409
+ compatibility is tested separately.
410
+
411
+ Published-release verification cryptographically checks the Sigstore signature
412
+ and transparency proofs, requires this repository's `ci.yml` identity on `main`,
413
+ and checks the signed package digest against the downloaded tarball. The signed
414
+ source commit must match the version's Git tag; release CI also requires it to
415
+ match the current build commit. Verification never republishes the package.
252
416
 
253
417
  Publishing uses npm trusted publishing (OIDC) from `.github/workflows/ci.yml`; the workflow does not use an npm token.
254
418
 
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=(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,onPointerMove:i,onPointerLeave:a,onPointerDown:o,onPointerUp:s,onPointerCancel:c,index:l,value:u,isHalf:d,primaryColor:f,secondaryColor:p,hoverColor:m,hoverValue:h=null,committedValue:g=u,fill:_,offset:v=50,fullId:y,halfId:b,noneId:x})=>{let S=_,C=`${y}-hover-${l}`;return S=m&&h!==null?`url(#${C})`:l<=u?`url(#${y})`:d&&Math.ceil(u)===l?`url(#${b})`:`url(#${x})`,(0,t.jsxs)(`svg`,{width:n,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onPointerLeave:a,onPointerMove:i,onPointerDown:o,onPointerUp:s,onPointerCancel:c,children:[(0,t.jsx)(r,{primaryColor:f,secondaryColor:p,offset:v,index:l,value:u,fullId:y,halfId:b,noneId:x,hoverColor:m,hoverValue:h,committedValue:g,hoverGradientId:C}),(0,t.jsx)(`polygon`,{fill:S,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=e=>Number.isFinite(e)&&e>=1?Math.floor(e):5,s=(e,t,n=0)=>Number.isFinite(e)&&e>=n?e:t,c=(e,t,n)=>{let r=Math.min(Math.max(Number.isFinite(e)?e:0,0),t);return n?r:Math.round(r)},l=({id:n,value:r,defaultValue:l=0,onChange:u=()=>{},name:d,disabled:f=!1,isEdit:p=!0,isHalf:m=!0,count:h=5,size:g=25,starGap:_=0,className:v=``,primaryColor:y=`orange`,secondaryColor:b=`grey`,hoverColor:x,isArrowSubmit:S=!1,ariaLabel:C=`Star rating`,ariaLabelledBy:w,getValueText:ee=(e,t)=>`${e} of ${t}`})=>{let T=o(h),te=s(g,25,2**-52),E=s(_,0),D=r!==void 0,[ne,O]=(0,e.useState)(()=>c(l,T,m)),k=c(r??ne,T,m),A=(0,e.useId)().replace(/:/g,``),j=n??`react-stars-rating-${A}`,[M,N]=(0,e.useState)(()=>({sourceValue:k,displayValue:k})),P=(0,e.useRef)(null),[F,I]=(0,e.useState)(null),L=(0,e.useRef)(null),R=M.sourceValue===k?M.displayValue:k,z=e=>{N({sourceValue:k,displayValue:c(e,T,m)})},re=(0,e.useMemo)(()=>`fullId-${j}`,[j]),B=(0,e.useMemo)(()=>`halfId-${j}`,[j]),V=(0,e.useMemo)(()=>`noneId-${j}`,[j]);(0,e.useEffect)(()=>{P.current=null},[k]),(0,e.useEffect)(()=>{if(D)return;let e=L.current?.closest(`form`),t=()=>{let e=c(l,T,m);P.current=null,I(null),O(e),N({sourceValue:e,displayValue:e})};return e?.addEventListener(`reset`,t),()=>e?.removeEventListener(`reset`,t)},[l,D,m,T]);let H=(e,t)=>{let n=Array.from(L.current?.querySelectorAll(`svg`)??[]),r=n.find(t=>{let n=t.getBoundingClientRect();return n.width>0&&e.clientX<=n.right})??n[n.length-1]??e.currentTarget,i=n.indexOf(r)+1||t;if(!m)return i;let a=r.getBoundingClientRect();return e.clientX-a.left>a.width/2?i:i-.5},U=(e,t)=>{if(!p||f)return;let n=typeof e.currentTarget.hasPointerCapture==`function`&&e.currentTarget.hasPointerCapture(e.pointerId);if(e.pointerType===`touch`&&!n)return;let r=H(e,t);P.current=null,I(r),z(r)},W=e=>{!p||f||e.buttons!==0||(I(null),z(k))},G=()=>{let e=P.current;e!==null&&(P.current=null,K(e))},K=e=>{let t=c(e,T,m);z(t),D||O(t),u(t)},q=(e,t)=>{if(!p||f)return;L.current?.focus(),e.currentTarget.setPointerCapture?.(e.pointerId),P.current=null;let n=H(e,t);I(n),z(n)},J=(e,t)=>{!p||f||(K(H(e,t)),e.currentTarget.releasePointerCapture?.(e.pointerId),e.pointerType===`touch`&&I(null))},Y=e=>{e.currentTarget.releasePointerCapture?.(e.pointerId),P.current=null,I(null),z(k)},X=e=>{let t=c(e,T,m);t!==R&&(z(t),S?(P.current=null,K(t)):P.current=t)},Z=e=>{let t=R%1==.5?R:Math.round(R);X(t+e)},ie=e=>{switch(e.key){case`ArrowLeft`:case`ArrowDown`:e.preventDefault(),Z(m?-.5:-1);break;case`ArrowRight`:case`ArrowUp`:e.preventDefault(),Z(m?.5:1);break;case`Home`:e.preventDefault(),X(0);break;case`End`:e.preventDefault(),X(T);break;case`Enter`:e.preventDefault(),P.current=null,K(R)}},ae=()=>Array.from({length:T},(e,n)=>{let r=n+1,a=r===T?void 0:{paddingRight:E};return(0,t.jsx)(`span`,{className:`star star-${r}`,style:a,children:(0,t.jsx)(i,{index:r,value:R,size:te,isHalf:m,fullId:re,halfId:B,noneId:V,primaryColor:y,secondaryColor:b,hoverColor:x,hoverValue:F,committedValue:k,onPointerMove:e=>U(e,r),onPointerLeave:W,onPointerDown:e=>q(e,r),onPointerUp:e=>J(e,r),onPointerCancel:Y})},`react-stars-rating-char${r}`)}),Q=p&&!f,$=c(R,T,m);return(0,t.jsxs)(`span`,{ref:L,id:j,role:`slider`,"aria-label":w?void 0:C,"aria-labelledby":w,"aria-valuemin":0,"aria-valuemax":T,"aria-valuenow":$,"aria-valuetext":ee($,T),"aria-orientation":`horizontal`,"aria-readonly":!p,"aria-disabled":f||void 0,tabIndex:Q?0:-1,onKeyDown:Q?ie:void 0,onBlur:Q?G:void 0,style:p?a.activeContainer:a.inActiveContainer,className:v,children:[ae(),d?(0,t.jsx)(`input`,{type:`hidden`,name:d,value:k,disabled:f}):null]})};exports.ReactStarsRating=l,exports.default=l;
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,onPointerMove:i,onPointerLeave:a,onPointerDown:o,onPointerUp:s,onPointerCancel:c,index:l,value:u,isHalf:d,isRtl:f,onLostPointerCapture:p,primaryColor:m,secondaryColor:h,hoverColor:g,hoverValue:_=null,committedValue:v=u,fill:y,offset:b=50,fullId:x,halfId:S,noneId:C})=>{let w=y,T=`${x}-hover-${l}`;return w=g&&_!==null?`url(#${T})`:l<=u?`url(#${x})`:d&&Math.ceil(u)===l?`url(#${S})`:`url(#${C})`,(0,t.jsxs)(`svg`,{width:n,style:{forcedColorAdjust:`auto`,width:`var(--stars-rating-size)`,transform:f?`scaleX(-1)`:void 0},viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onPointerLeave:a,onPointerMove:i,onPointerDown:o,onPointerUp:s,onPointerCancel:c,onLostPointerCapture:p,children:[(0,t.jsx)(r,{primaryColor:m,secondaryColor:h,offset:b,index:l,value:u,fullId:x,halfId:S,noneId:C,hoverColor:g,hoverValue:_,committedValue:v,hoverGradientId:T}),(0,t.jsx)(`polygon`,{fill:w,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=e=>Number.isFinite(e)&&e>=1?Math.floor(e):5,s=(e,t,n=0)=>Number.isFinite(e)&&e>=n?e:t,c=(e,t,n)=>{let r=Math.min(Math.max(Number.isFinite(e)?e:0,0),t);return n?r:Math.round(r)},l=(0,e.forwardRef)(function({id:n,value:r,defaultValue:l=0,onChange:u=()=>{},onBlur:d,onFocus:f,name:p,form:m,disabled:h=!1,readOnly:g=!1,isEdit:_=!0,isHalf:v=!0,count:y=5,size:b=25,starGap:x=0,className:S=``,style:C,dir:w=`ltr`,primaryColor:T=`var(--stars-rating-primary-color, orange)`,secondaryColor:ee=`var(--stars-rating-secondary-color, grey)`,hoverColor:E,isArrowSubmit:te=!1,ariaLabel:ne=`Star rating`,ariaLabelledBy:D,getValueText:re=(e,t)=>`${e} of ${t}`,...ie},O){let k=w===`rtl`,A=_&&!g,j=o(y),M=s(b,25,2**-52),ae=s(x,0),N=r!==void 0,[oe,P]=(0,e.useState)(()=>c(l,j,v)),F=c(r??oe,j,v),I=(0,e.useId)().replace(/:/g,``),L=n??`react-stars-rating-${I}`,[R,z]=(0,e.useState)(()=>({sourceValue:F,displayValue:F})),B=(0,e.useRef)(null),V=(0,e.useRef)(null),[se,H]=(0,e.useState)(null),[ce,U]=(0,e.useState)(!1),W=(0,e.useRef)(null);(0,e.useImperativeHandle)(O,()=>W.current,[]),R.sourceValue!==F&&(z({sourceValue:F,displayValue:F}),H(null));let G=R.sourceValue===F?R.displayValue:F,K=e=>{z({sourceValue:F,displayValue:c(e,j,v)})},le=(0,e.useMemo)(()=>`fullId-${L}`,[L]),ue=(0,e.useMemo)(()=>`halfId-${L}`,[L]),de=(0,e.useMemo)(()=>`noneId-${L}`,[L]);(0,e.useEffect)(()=>{B.current=null},[F]),(0,e.useEffect)(()=>{if(N)return;let e=m?document.getElementById(m):W.current?.closest(`form`),t=!0,n=e=>{setTimeout(()=>{if(!t||e.defaultPrevented)return;let n=c(l,j,v);B.current=null,V.current=null,H(null),P(n),z({sourceValue:n,displayValue:n})})};return e?.addEventListener(`reset`,n),()=>{t=!1,e?.removeEventListener(`reset`,n)}},[l,m,N,v,j]);let q=(e,t)=>{let n=Array.from(W.current?.querySelectorAll(`svg`)??[]),r=n.find(t=>{let n=t.getBoundingClientRect();return n.width>0&&(k?e.clientX>=n.left:e.clientX<=n.right)})??n[n.length-1]??e.currentTarget,i=n.indexOf(r)+1||t;if(!v)return i;let a=r.getBoundingClientRect();return(k?a.right-e.clientX:e.clientX-a.left)>a.width/2?i:i-.5},fe=(e,t)=>{if(!A||h)return;let n=V.current;if(n!==null&&n!==e.pointerId||n===null&&(e.pointerType===`touch`||e.buttons!==0))return;let r=q(e,t);B.current=null,H(r),K(r)},pe=e=>{!A||h||e.buttons!==0||(H(null),K(F))},me=e=>{U(!1);let t=B.current;B.current=null,A&&!h&&t!==null&&J(t),d?.(e)},J=e=>{let t=c(e,j,v);K(t),N||P(t),u(t)},he=(e,t)=>{if(!A||h||e.button!==0||e.isPrimary===!1||V.current!==null)return;W.current?.focus(),V.current=e.pointerId,e.currentTarget.setPointerCapture?.(e.pointerId),B.current=null;let n=q(e,t);H(n),K(n)},ge=(e,t)=>{V.current===e.pointerId&&e.button===0&&(V.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId),!(!A||h)&&(J(q(e,t)),e.pointerType===`touch`&&H(null)))},Y=e=>{V.current===e.pointerId&&(V.current=null,B.current=null,H(null),K(F))},X=e=>{let t=c(e,j,v);t!==G&&(K(t),te?(B.current=null,J(t)):B.current=t)},Z=e=>{let t=G%1==.5?G:Math.round(G);X(t+e)},_e=e=>{switch(e.key){case`ArrowLeft`:e.preventDefault(),Z((v?.5:1)*(k?1:-1));break;case`ArrowRight`:e.preventDefault(),Z((v?.5:1)*(k?-1:1));break;case`ArrowDown`:e.preventDefault(),Z(v?-.5:-1);break;case`ArrowUp`:e.preventDefault(),Z(v?.5:1);break;case`Home`:e.preventDefault(),X(0);break;case`End`:e.preventDefault(),X(j);break;case`Enter`:e.preventDefault(),B.current=null,J(G)}},ve=()=>Array.from({length:j},(e,n)=>{let r=n+1,a=r!==j;return(0,t.jsx)(`span`,{className:`star star-${r}`,style:a?{paddingInlineEnd:`var(--stars-rating-gap)`}:void 0,children:(0,t.jsx)(i,{index:r,value:G,size:M,isHalf:v,isRtl:k,fullId:le,halfId:ue,noneId:de,primaryColor:T,secondaryColor:ee,hoverColor:E,hoverValue:se,committedValue:F,onPointerMove:e=>fe(e,r),onPointerLeave:pe,onPointerDown:e=>he(e,r),onPointerUp:e=>ge(e,r),onPointerCancel:Y,onLostPointerCapture:Y})},`react-stars-rating-char${r}`)}),Q=A&&!h,$=c(G,j,v),ye=e=>{U(e.currentTarget.matches(`:focus-visible`)),f?.(e)},be={...A?a.activeContainer:a.inActiveContainer,"--stars-rating-gap":`${ae}px`,"--stars-rating-size":`${M}px`,...ce?{outline:`2px solid var(--stars-rating-focus-color, highlight)`,outlineOffset:2}:void 0,...C};return(0,t.jsxs)(`span`,{...ie,ref:W,id:L,dir:w,role:`slider`,"aria-label":D?void 0:ne,"aria-labelledby":D,"aria-valuemin":0,"aria-valuemax":j,"aria-valuenow":$,"aria-valuetext":re($,j),"aria-orientation":`horizontal`,"aria-readonly":!A,"aria-disabled":h||void 0,tabIndex:Q?0:-1,onKeyDown:Q?_e:void 0,onBlur:me,onFocus:ye,style:be,className:S,children:[ve(),p?(0,t.jsx)(`input`,{type:`hidden`,name:p,value:F,disabled:h,form:m}):null]})});exports.ReactStarsRating=l,exports.default=l;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,13 @@
1
- export type ReactStarsRatingProps = {
1
+ import { type ComponentPropsWithoutRef, type CSSProperties } from 'react';
2
+ type ReservedSpanProps = 'aria-disabled' | 'aria-label' | 'aria-labelledby' | 'aria-orientation' | 'aria-readonly' | 'aria-valuemax' | 'aria-valuemin' | 'aria-valuenow' | 'aria-valuetext' | 'children' | 'className' | 'dangerouslySetInnerHTML' | 'defaultValue' | 'dir' | 'id' | 'onChange' | 'onKeyDown' | 'onPointerCancel' | 'onPointerDown' | 'onPointerLeave' | 'onPointerMove' | 'onPointerUp' | 'role' | 'style' | 'tabIndex';
3
+ export type ReactStarsRatingStyle = CSSProperties & {
4
+ '--stars-rating-focus-color'?: string;
5
+ '--stars-rating-gap'?: string;
6
+ '--stars-rating-primary-color'?: string;
7
+ '--stars-rating-secondary-color'?: string;
8
+ '--stars-rating-size'?: string;
9
+ };
10
+ export type ReactStarsRatingProps = Omit<ComponentPropsWithoutRef<'span'>, ReservedSpanProps> & {
2
11
  /**
3
12
  * `id` of the rendered element. Defaults to a generated, SSR-safe id, so
4
13
  * only set this when something else needs to reference the control.
@@ -18,20 +27,27 @@ export type ReactStarsRatingProps = {
18
27
  */
19
28
  defaultValue?: number;
20
29
  /**
21
- * Called with the new rating on click, on `Enter`, and on blur — and on
30
+ * Called when a pointer gesture commits, on `Enter`, and on blur — and on
22
31
  * every arrow key when {@link ReactStarsRatingProps.isArrowSubmit} is set.
23
- * Hovering never calls it.
32
+ * Pointer previews never call it.
24
33
  */
25
34
  onChange?: (value: number) => void;
26
35
  /** Name of the hidden input contributed to the nearest HTML form. */
27
36
  name?: string;
37
+ /** `id` of a form to associate with the hidden input. */
38
+ form?: string;
39
+ /** Layout, fill and horizontal keyboard direction. Defaults to `ltr`. */
40
+ dir?: 'ltr' | 'rtl';
28
41
  /** Disable interaction and native form submission. */
29
42
  disabled?: boolean;
43
+ /** Prevent editing while keeping the current value exposed to assistive technology. */
44
+ readOnly?: boolean;
30
45
  /**
31
46
  * Whether the rating can be changed. When `false` the control has no hover
32
47
  * preview, no keyboard handling, and is removed from the tab order.
33
48
  *
34
49
  * @defaultValue true
50
+ * @deprecated Prefer {@link ReactStarsRatingProps.readOnly}.
35
51
  */
36
52
  isEdit?: boolean;
37
53
  /**
@@ -55,7 +71,7 @@ export type ReactStarsRatingProps = {
55
71
  */
56
72
  size?: number;
57
73
  /**
58
- * Space between stars, in pixels. Applied as right padding to every star
74
+ * Space between stars, in pixels. Applied as inline-end padding to every star
59
75
  * except the last.
60
76
  *
61
77
  * @defaultValue 0
@@ -68,16 +84,18 @@ export type ReactStarsRatingProps = {
68
84
  * @defaultValue ''
69
85
  */
70
86
  className?: string;
87
+ /** Container styles, including the documented `--stars-rating-*` variables. */
88
+ style?: ReactStarsRatingStyle;
71
89
  /**
72
90
  * Any CSS color for the filled part of a star.
73
91
  *
74
- * @defaultValue 'orange'
92
+ * @defaultValue 'var(--stars-rating-primary-color, orange)'
75
93
  */
76
94
  primaryColor?: string;
77
95
  /**
78
96
  * Any CSS color for the empty part of a star.
79
97
  *
80
- * @defaultValue 'grey'
98
+ * @defaultValue 'var(--stars-rating-secondary-color, grey)'
81
99
  */
82
100
  secondaryColor?: string;
83
101
  /**
@@ -126,6 +144,131 @@ export type ReactStarsRatingProps = {
126
144
  * <ReactStarsRating value={value} onChange={setValue} isHalf size={32} />
127
145
  * ```
128
146
  */
129
- declare const ReactStarsRating: ({ id, value, defaultValue, onChange, name, disabled, isEdit, isHalf, count, size, starGap, className, primaryColor, secondaryColor, hoverColor, isArrowSubmit, ariaLabel, ariaLabelledBy, getValueText, }: ReactStarsRatingProps) => import("react").JSX.Element;
147
+ declare const ReactStarsRating: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref">, ReservedSpanProps> & {
148
+ /**
149
+ * `id` of the rendered element. Defaults to a generated, SSR-safe id, so
150
+ * only set this when something else needs to reference the control.
151
+ */
152
+ id?: string;
153
+ /**
154
+ * Controlled current rating. Values outside `0` to
155
+ * {@link ReactStarsRatingProps.count} are clamped. When omitted, the
156
+ * component manages its own value.
157
+ */
158
+ value?: number;
159
+ /**
160
+ * Initial value when the component is uncontrolled. Ignored when
161
+ * {@link ReactStarsRatingProps.value} is provided.
162
+ *
163
+ * @defaultValue 0
164
+ */
165
+ defaultValue?: number;
166
+ /**
167
+ * Called when a pointer gesture commits, on `Enter`, and on blur — and on
168
+ * every arrow key when {@link ReactStarsRatingProps.isArrowSubmit} is set.
169
+ * Pointer previews never call it.
170
+ */
171
+ onChange?: (value: number) => void;
172
+ /** Name of the hidden input contributed to the nearest HTML form. */
173
+ name?: string;
174
+ /** `id` of a form to associate with the hidden input. */
175
+ form?: string;
176
+ /** Layout, fill and horizontal keyboard direction. Defaults to `ltr`. */
177
+ dir?: "ltr" | "rtl";
178
+ /** Disable interaction and native form submission. */
179
+ disabled?: boolean;
180
+ /** Prevent editing while keeping the current value exposed to assistive technology. */
181
+ readOnly?: boolean;
182
+ /**
183
+ * Whether the rating can be changed. When `false` the control has no hover
184
+ * preview, no keyboard handling, and is removed from the tab order.
185
+ *
186
+ * @defaultValue true
187
+ * @deprecated Prefer {@link ReactStarsRatingProps.readOnly}.
188
+ */
189
+ isEdit?: boolean;
190
+ /**
191
+ * Whether half stars can be selected. When `true` the half of the star the
192
+ * pointer is over decides between `n - 0.5` and `n`, and the arrow keys step
193
+ * by `0.5` instead of `1`.
194
+ *
195
+ * @defaultValue true
196
+ */
197
+ isHalf?: boolean;
198
+ /**
199
+ * How many stars to render. Also becomes `aria-valuemax`.
200
+ *
201
+ * @defaultValue 5
202
+ */
203
+ count?: number;
204
+ /**
205
+ * Width of one star, in pixels.
206
+ *
207
+ * @defaultValue 25
208
+ */
209
+ size?: number;
210
+ /**
211
+ * Space between stars, in pixels. Applied as inline-end padding to every star
212
+ * except the last.
213
+ *
214
+ * @defaultValue 0
215
+ */
216
+ starGap?: number;
217
+ /**
218
+ * Class name for the container element. The stars themselves always carry
219
+ * `star` and `star-1`, `star-2`, … class names.
220
+ *
221
+ * @defaultValue ''
222
+ */
223
+ className?: string;
224
+ /** Container styles, including the documented `--stars-rating-*` variables. */
225
+ style?: ReactStarsRatingStyle;
226
+ /**
227
+ * Any CSS color for the filled part of a star.
228
+ *
229
+ * @defaultValue 'var(--stars-rating-primary-color, orange)'
230
+ */
231
+ primaryColor?: string;
232
+ /**
233
+ * Any CSS color for the empty part of a star.
234
+ *
235
+ * @defaultValue 'var(--stars-rating-secondary-color, grey)'
236
+ */
237
+ secondaryColor?: string;
238
+ /**
239
+ * Optional color for the portion being previewed by the pointer. The saved
240
+ * value keeps {@link ReactStarsRatingProps.primaryColor}, so moving below an
241
+ * existing rating can display preview, saved, and inactive regions at once.
242
+ * When omitted, hover previews retain the original two-color behavior.
243
+ */
244
+ hoverColor?: string;
245
+ /**
246
+ * Report every arrow-key step through
247
+ * {@link ReactStarsRatingProps.onChange} immediately, instead of waiting for
248
+ * `Enter` or blur.
249
+ *
250
+ * @defaultValue false
251
+ */
252
+ isArrowSubmit?: boolean;
253
+ /**
254
+ * Accessible name for the control. Ignored when
255
+ * {@link ReactStarsRatingProps.ariaLabelledBy} is set.
256
+ *
257
+ * @defaultValue 'Star rating'
258
+ */
259
+ ariaLabel?: string;
260
+ /**
261
+ * `id` of an element that labels this control, for when the label already
262
+ * exists on the page. Takes precedence over
263
+ * {@link ReactStarsRatingProps.ariaLabel}.
264
+ */
265
+ ariaLabelledBy?: string;
266
+ /**
267
+ * Formats the accessible value description.
268
+ *
269
+ * @defaultValue `(value, count) => `${value} of ${count}``
270
+ */
271
+ getValueText?: (value: number, count: number) => string;
272
+ } & import("react").RefAttributes<HTMLSpanElement>>;
130
273
  export { ReactStarsRating };
131
274
  export default ReactStarsRating;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,13 @@
1
- export type ReactStarsRatingProps = {
1
+ import { type ComponentPropsWithoutRef, type CSSProperties } from 'react';
2
+ type ReservedSpanProps = 'aria-disabled' | 'aria-label' | 'aria-labelledby' | 'aria-orientation' | 'aria-readonly' | 'aria-valuemax' | 'aria-valuemin' | 'aria-valuenow' | 'aria-valuetext' | 'children' | 'className' | 'dangerouslySetInnerHTML' | 'defaultValue' | 'dir' | 'id' | 'onChange' | 'onKeyDown' | 'onPointerCancel' | 'onPointerDown' | 'onPointerLeave' | 'onPointerMove' | 'onPointerUp' | 'role' | 'style' | 'tabIndex';
3
+ export type ReactStarsRatingStyle = CSSProperties & {
4
+ '--stars-rating-focus-color'?: string;
5
+ '--stars-rating-gap'?: string;
6
+ '--stars-rating-primary-color'?: string;
7
+ '--stars-rating-secondary-color'?: string;
8
+ '--stars-rating-size'?: string;
9
+ };
10
+ export type ReactStarsRatingProps = Omit<ComponentPropsWithoutRef<'span'>, ReservedSpanProps> & {
2
11
  /**
3
12
  * `id` of the rendered element. Defaults to a generated, SSR-safe id, so
4
13
  * only set this when something else needs to reference the control.
@@ -18,20 +27,27 @@ export type ReactStarsRatingProps = {
18
27
  */
19
28
  defaultValue?: number;
20
29
  /**
21
- * Called with the new rating on click, on `Enter`, and on blur — and on
30
+ * Called when a pointer gesture commits, on `Enter`, and on blur — and on
22
31
  * every arrow key when {@link ReactStarsRatingProps.isArrowSubmit} is set.
23
- * Hovering never calls it.
32
+ * Pointer previews never call it.
24
33
  */
25
34
  onChange?: (value: number) => void;
26
35
  /** Name of the hidden input contributed to the nearest HTML form. */
27
36
  name?: string;
37
+ /** `id` of a form to associate with the hidden input. */
38
+ form?: string;
39
+ /** Layout, fill and horizontal keyboard direction. Defaults to `ltr`. */
40
+ dir?: 'ltr' | 'rtl';
28
41
  /** Disable interaction and native form submission. */
29
42
  disabled?: boolean;
43
+ /** Prevent editing while keeping the current value exposed to assistive technology. */
44
+ readOnly?: boolean;
30
45
  /**
31
46
  * Whether the rating can be changed. When `false` the control has no hover
32
47
  * preview, no keyboard handling, and is removed from the tab order.
33
48
  *
34
49
  * @defaultValue true
50
+ * @deprecated Prefer {@link ReactStarsRatingProps.readOnly}.
35
51
  */
36
52
  isEdit?: boolean;
37
53
  /**
@@ -55,7 +71,7 @@ export type ReactStarsRatingProps = {
55
71
  */
56
72
  size?: number;
57
73
  /**
58
- * Space between stars, in pixels. Applied as right padding to every star
74
+ * Space between stars, in pixels. Applied as inline-end padding to every star
59
75
  * except the last.
60
76
  *
61
77
  * @defaultValue 0
@@ -68,16 +84,18 @@ export type ReactStarsRatingProps = {
68
84
  * @defaultValue ''
69
85
  */
70
86
  className?: string;
87
+ /** Container styles, including the documented `--stars-rating-*` variables. */
88
+ style?: ReactStarsRatingStyle;
71
89
  /**
72
90
  * Any CSS color for the filled part of a star.
73
91
  *
74
- * @defaultValue 'orange'
92
+ * @defaultValue 'var(--stars-rating-primary-color, orange)'
75
93
  */
76
94
  primaryColor?: string;
77
95
  /**
78
96
  * Any CSS color for the empty part of a star.
79
97
  *
80
- * @defaultValue 'grey'
98
+ * @defaultValue 'var(--stars-rating-secondary-color, grey)'
81
99
  */
82
100
  secondaryColor?: string;
83
101
  /**
@@ -126,6 +144,131 @@ export type ReactStarsRatingProps = {
126
144
  * <ReactStarsRating value={value} onChange={setValue} isHalf size={32} />
127
145
  * ```
128
146
  */
129
- declare const ReactStarsRating: ({ id, value, defaultValue, onChange, name, disabled, isEdit, isHalf, count, size, starGap, className, primaryColor, secondaryColor, hoverColor, isArrowSubmit, ariaLabel, ariaLabelledBy, getValueText, }: ReactStarsRatingProps) => import("react").JSX.Element;
147
+ declare const ReactStarsRating: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref">, ReservedSpanProps> & {
148
+ /**
149
+ * `id` of the rendered element. Defaults to a generated, SSR-safe id, so
150
+ * only set this when something else needs to reference the control.
151
+ */
152
+ id?: string;
153
+ /**
154
+ * Controlled current rating. Values outside `0` to
155
+ * {@link ReactStarsRatingProps.count} are clamped. When omitted, the
156
+ * component manages its own value.
157
+ */
158
+ value?: number;
159
+ /**
160
+ * Initial value when the component is uncontrolled. Ignored when
161
+ * {@link ReactStarsRatingProps.value} is provided.
162
+ *
163
+ * @defaultValue 0
164
+ */
165
+ defaultValue?: number;
166
+ /**
167
+ * Called when a pointer gesture commits, on `Enter`, and on blur — and on
168
+ * every arrow key when {@link ReactStarsRatingProps.isArrowSubmit} is set.
169
+ * Pointer previews never call it.
170
+ */
171
+ onChange?: (value: number) => void;
172
+ /** Name of the hidden input contributed to the nearest HTML form. */
173
+ name?: string;
174
+ /** `id` of a form to associate with the hidden input. */
175
+ form?: string;
176
+ /** Layout, fill and horizontal keyboard direction. Defaults to `ltr`. */
177
+ dir?: "ltr" | "rtl";
178
+ /** Disable interaction and native form submission. */
179
+ disabled?: boolean;
180
+ /** Prevent editing while keeping the current value exposed to assistive technology. */
181
+ readOnly?: boolean;
182
+ /**
183
+ * Whether the rating can be changed. When `false` the control has no hover
184
+ * preview, no keyboard handling, and is removed from the tab order.
185
+ *
186
+ * @defaultValue true
187
+ * @deprecated Prefer {@link ReactStarsRatingProps.readOnly}.
188
+ */
189
+ isEdit?: boolean;
190
+ /**
191
+ * Whether half stars can be selected. When `true` the half of the star the
192
+ * pointer is over decides between `n - 0.5` and `n`, and the arrow keys step
193
+ * by `0.5` instead of `1`.
194
+ *
195
+ * @defaultValue true
196
+ */
197
+ isHalf?: boolean;
198
+ /**
199
+ * How many stars to render. Also becomes `aria-valuemax`.
200
+ *
201
+ * @defaultValue 5
202
+ */
203
+ count?: number;
204
+ /**
205
+ * Width of one star, in pixels.
206
+ *
207
+ * @defaultValue 25
208
+ */
209
+ size?: number;
210
+ /**
211
+ * Space between stars, in pixels. Applied as inline-end padding to every star
212
+ * except the last.
213
+ *
214
+ * @defaultValue 0
215
+ */
216
+ starGap?: number;
217
+ /**
218
+ * Class name for the container element. The stars themselves always carry
219
+ * `star` and `star-1`, `star-2`, … class names.
220
+ *
221
+ * @defaultValue ''
222
+ */
223
+ className?: string;
224
+ /** Container styles, including the documented `--stars-rating-*` variables. */
225
+ style?: ReactStarsRatingStyle;
226
+ /**
227
+ * Any CSS color for the filled part of a star.
228
+ *
229
+ * @defaultValue 'var(--stars-rating-primary-color, orange)'
230
+ */
231
+ primaryColor?: string;
232
+ /**
233
+ * Any CSS color for the empty part of a star.
234
+ *
235
+ * @defaultValue 'var(--stars-rating-secondary-color, grey)'
236
+ */
237
+ secondaryColor?: string;
238
+ /**
239
+ * Optional color for the portion being previewed by the pointer. The saved
240
+ * value keeps {@link ReactStarsRatingProps.primaryColor}, so moving below an
241
+ * existing rating can display preview, saved, and inactive regions at once.
242
+ * When omitted, hover previews retain the original two-color behavior.
243
+ */
244
+ hoverColor?: string;
245
+ /**
246
+ * Report every arrow-key step through
247
+ * {@link ReactStarsRatingProps.onChange} immediately, instead of waiting for
248
+ * `Enter` or blur.
249
+ *
250
+ * @defaultValue false
251
+ */
252
+ isArrowSubmit?: boolean;
253
+ /**
254
+ * Accessible name for the control. Ignored when
255
+ * {@link ReactStarsRatingProps.ariaLabelledBy} is set.
256
+ *
257
+ * @defaultValue 'Star rating'
258
+ */
259
+ ariaLabel?: string;
260
+ /**
261
+ * `id` of an element that labels this control, for when the label already
262
+ * exists on the page. Takes precedence over
263
+ * {@link ReactStarsRatingProps.ariaLabel}.
264
+ */
265
+ ariaLabelledBy?: string;
266
+ /**
267
+ * Formats the accessible value description.
268
+ *
269
+ * @defaultValue `(value, count) => `${value} of ${count}``
270
+ */
271
+ getValueText?: (value: number, count: number) => string;
272
+ } & import("react").RefAttributes<HTMLSpanElement>>;
130
273
  export { ReactStarsRating };
131
274
  export default ReactStarsRating;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
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,onPointerMove:n,onPointerLeave:r,onPointerDown:i,onPointerUp:s,onPointerCancel:l,index:u,value:d,isHalf:f,primaryColor:p,secondaryColor:m,hoverColor:h,hoverValue:g=null,committedValue:_=d,fill:v,offset:y=50,fullId:b,halfId:x,noneId:S})=>{let C=v,w=`${b}-hover-${u}`;return C=h&&g!==null?`url(#${w})`:u<=d?`url(#${b})`:f&&Math.ceil(d)===u?`url(#${x})`:`url(#${S})`,o(`svg`,{width:t,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onPointerLeave:r,onPointerMove:n,onPointerDown:i,onPointerUp:s,onPointerCancel:l,children:[a(c,{primaryColor:p,secondaryColor:m,offset:y,index:u,value:d,fullId:b,halfId:x,noneId:S,hoverColor:h,hoverValue:g,committedValue:_,hoverGradientId:w}),a(`polygon`,{fill:C,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=e=>Number.isFinite(e)&&e>=1?Math.floor(e):5,f=(e,t,n=0)=>Number.isFinite(e)&&e>=n?e:t,p=(e,t,n)=>{let r=Math.min(Math.max(Number.isFinite(e)?e:0,0),t);return n?r:Math.round(r)},m=({id:s,value:c,defaultValue:m=0,onChange:h=()=>{},name:g,disabled:_=!1,isEdit:v=!0,isHalf:y=!0,count:b=5,size:x=25,starGap:S=0,className:C=``,primaryColor:w=`orange`,secondaryColor:T=`grey`,hoverColor:E,isArrowSubmit:ee=!1,ariaLabel:D=`Star rating`,ariaLabelledBy:O,getValueText:k=(e,t)=>`${e} of ${t}`})=>{let A=d(b),j=f(x,25,2**-52),M=f(S,0),N=c!==void 0,[te,P]=i(()=>p(m,A,y)),F=p(c??te,A,y),ne=t().replace(/:/g,``),I=s??`react-stars-rating-${ne}`,[L,R]=i(()=>({sourceValue:F,displayValue:F})),z=r(null),[B,V]=i(null),H=r(null),U=L.sourceValue===F?L.displayValue:F,W=e=>{R({sourceValue:F,displayValue:p(e,A,y)})},G=n(()=>`fullId-${I}`,[I]),K=n(()=>`halfId-${I}`,[I]),q=n(()=>`noneId-${I}`,[I]);e(()=>{z.current=null},[F]),e(()=>{if(N)return;let e=H.current?.closest(`form`),t=()=>{let e=p(m,A,y);z.current=null,V(null),P(e),R({sourceValue:e,displayValue:e})};return e?.addEventListener(`reset`,t),()=>e?.removeEventListener(`reset`,t)},[m,N,y,A]);let J=(e,t)=>{let n=Array.from(H.current?.querySelectorAll(`svg`)??[]),r=n.find(t=>{let n=t.getBoundingClientRect();return n.width>0&&e.clientX<=n.right})??n[n.length-1]??e.currentTarget,i=n.indexOf(r)+1||t;if(!y)return i;let a=r.getBoundingClientRect();return e.clientX-a.left>a.width/2?i:i-.5},re=(e,t)=>{if(!v||_)return;let n=typeof e.currentTarget.hasPointerCapture==`function`&&e.currentTarget.hasPointerCapture(e.pointerId);if(e.pointerType===`touch`&&!n)return;let r=J(e,t);z.current=null,V(r),W(r)},ie=e=>{!v||_||e.buttons!==0||(V(null),W(F))},ae=()=>{let e=z.current;e!==null&&(z.current=null,Y(e))},Y=e=>{let t=p(e,A,y);W(t),N||P(t),h(t)},oe=(e,t)=>{if(!v||_)return;H.current?.focus(),e.currentTarget.setPointerCapture?.(e.pointerId),z.current=null;let n=J(e,t);V(n),W(n)},se=(e,t)=>{!v||_||(Y(J(e,t)),e.currentTarget.releasePointerCapture?.(e.pointerId),e.pointerType===`touch`&&V(null))},ce=e=>{e.currentTarget.releasePointerCapture?.(e.pointerId),z.current=null,V(null),W(F)},X=e=>{let t=p(e,A,y);t!==U&&(W(t),ee?(z.current=null,Y(t)):z.current=t)},Z=e=>{let t=U%1==.5?U:Math.round(U);X(t+e)},le=e=>{switch(e.key){case`ArrowLeft`:case`ArrowDown`:e.preventDefault(),Z(y?-.5:-1);break;case`ArrowRight`:case`ArrowUp`:e.preventDefault(),Z(y?.5:1);break;case`Home`:e.preventDefault(),X(0);break;case`End`:e.preventDefault(),X(A);break;case`Enter`:e.preventDefault(),z.current=null,Y(U)}},ue=()=>Array.from({length:A},(e,t)=>{let n=t+1,r=n===A?void 0:{paddingRight:M};return a(`span`,{className:`star star-${n}`,style:r,children:a(l,{index:n,value:U,size:j,isHalf:y,fullId:G,halfId:K,noneId:q,primaryColor:w,secondaryColor:T,hoverColor:E,hoverValue:B,committedValue:F,onPointerMove:e=>re(e,n),onPointerLeave:ie,onPointerDown:e=>oe(e,n),onPointerUp:e=>se(e,n),onPointerCancel:ce})},`react-stars-rating-char${n}`)}),Q=v&&!_,$=p(U,A,y);return o(`span`,{ref:H,id:I,role:`slider`,"aria-label":O?void 0:D,"aria-labelledby":O,"aria-valuemin":0,"aria-valuemax":A,"aria-valuenow":$,"aria-valuetext":k($,A),"aria-orientation":`horizontal`,"aria-readonly":!v,"aria-disabled":_||void 0,tabIndex:Q?0:-1,onKeyDown:Q?le:void 0,onBlur:Q?ae:void 0,style:v?u.activeContainer:u.inActiveContainer,className:C,children:[ue(),g?a(`input`,{type:`hidden`,name:g,value:F,disabled:_}):null]})};export{m as ReactStarsRating,m as default};
1
+ "use client";import{forwardRef as e,useEffect as t,useId as n,useImperativeHandle as r,useMemo as i,useRef as a,useState as o}from"react";import{jsx as s,jsxs as c}from"react/jsx-runtime";var l=(e,t)=>Math.min(100,Math.max(0,Math.round((e-(t-1))*1e4)/100)),u=({primaryColor:e,secondaryColor:t,offset:n=50,index:r,value:i,fullId:a,halfId:o,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),a=l(n,r),o=l(i,r),c=[{start:0,end:a,color:f<=p?d:e},{start:a,end:o,color:f<=p?e:d},{start:o,end:100,color:t}].filter(({start:e,end:t})=>t>e);return s(`defs`,{children:s(`linearGradient`,{id:m,children:c.flatMap(({start:e,end:t,color:n},r)=>[s(`stop`,{offset:`${e}%`,stopColor:n},`${r}-start`),s(`stop`,{offset:`${t}%`,stopColor:n},`${r}-end`)])})})}let h=n;return r===1&&(h=Math.round(i%1*1e4)/100),r===1?c(`defs`,{children:[s(`linearGradient`,{id:a,children:s(`stop`,{offset:`100%`,stopColor:e})}),s(`linearGradient`,{id:u,children:s(`stop`,{offset:`100%`,stopColor:t})}),c(`linearGradient`,{id:o,children:[s(`stop`,{offset:`${h}%`,stopColor:e}),s(`stop`,{offset:`${h}%`,stopColor:t})]})]}):null},d=({viewBox:e=`0 0 306 306`,size:t,onPointerMove:n,onPointerLeave:r,onPointerDown:i,onPointerUp:a,onPointerCancel:o,index:l,value:d,isHalf:f,isRtl:p,onLostPointerCapture:m,primaryColor:h,secondaryColor:g,hoverColor:_,hoverValue:v=null,committedValue:y=d,fill:b,offset:x=50,fullId:S,halfId:C,noneId:w})=>{let T=b,E=`${S}-hover-${l}`;return T=_&&v!==null?`url(#${E})`:l<=d?`url(#${S})`:f&&Math.ceil(d)===l?`url(#${C})`:`url(#${w})`,c(`svg`,{width:t,style:{forcedColorAdjust:`auto`,width:`var(--stars-rating-size)`,transform:p?`scaleX(-1)`:void 0},viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onPointerLeave:r,onPointerMove:n,onPointerDown:i,onPointerUp:a,onPointerCancel:o,onLostPointerCapture:m,children:[s(u,{primaryColor:h,secondaryColor:g,offset:x,index:l,value:d,fullId:S,halfId:C,noneId:w,hoverColor:_,hoverValue:v,committedValue:y,hoverGradientId:E}),s(`polygon`,{fill:T,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`})]})},f={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},p=e=>Number.isFinite(e)&&e>=1?Math.floor(e):5,m=(e,t,n=0)=>Number.isFinite(e)&&e>=n?e:t,h=(e,t,n)=>{let r=Math.min(Math.max(Number.isFinite(e)?e:0,0),t);return n?r:Math.round(r)},g=e(function({id:e,value:l,defaultValue:u=0,onChange:g=()=>{},onBlur:_,onFocus:v,name:y,form:b,disabled:x=!1,readOnly:S=!1,isEdit:C=!0,isHalf:w=!0,count:T=5,size:E=25,starGap:ee=0,className:te=``,style:ne,dir:D=`ltr`,primaryColor:re=`var(--stars-rating-primary-color, orange)`,secondaryColor:ie=`var(--stars-rating-secondary-color, grey)`,hoverColor:ae,isArrowSubmit:oe=!1,ariaLabel:se=`Star rating`,ariaLabelledBy:O,getValueText:ce=(e,t)=>`${e} of ${t}`,...le},ue){let k=D===`rtl`,A=C&&!S,j=p(T),M=m(E,25,2**-52),de=m(ee,0),N=l!==void 0,[fe,P]=o(()=>h(u,j,w)),F=h(l??fe,j,w),pe=n().replace(/:/g,``),I=e??`react-stars-rating-${pe}`,[L,R]=o(()=>({sourceValue:F,displayValue:F})),z=a(null),B=a(null),[me,V]=o(null),[he,H]=o(!1),U=a(null);r(ue,()=>U.current,[]),L.sourceValue!==F&&(R({sourceValue:F,displayValue:F}),V(null));let W=L.sourceValue===F?L.displayValue:F,G=e=>{R({sourceValue:F,displayValue:h(e,j,w)})},K=i(()=>`fullId-${I}`,[I]),ge=i(()=>`halfId-${I}`,[I]),_e=i(()=>`noneId-${I}`,[I]);t(()=>{z.current=null},[F]),t(()=>{if(N)return;let e=b?document.getElementById(b):U.current?.closest(`form`),t=!0,n=e=>{setTimeout(()=>{if(!t||e.defaultPrevented)return;let n=h(u,j,w);z.current=null,B.current=null,V(null),P(n),R({sourceValue:n,displayValue:n})})};return e?.addEventListener(`reset`,n),()=>{t=!1,e?.removeEventListener(`reset`,n)}},[u,b,N,w,j]);let q=(e,t)=>{let n=Array.from(U.current?.querySelectorAll(`svg`)??[]),r=n.find(t=>{let n=t.getBoundingClientRect();return n.width>0&&(k?e.clientX>=n.left:e.clientX<=n.right)})??n[n.length-1]??e.currentTarget,i=n.indexOf(r)+1||t;if(!w)return i;let a=r.getBoundingClientRect();return(k?a.right-e.clientX:e.clientX-a.left)>a.width/2?i:i-.5},ve=(e,t)=>{if(!A||x)return;let n=B.current;if(n!==null&&n!==e.pointerId||n===null&&(e.pointerType===`touch`||e.buttons!==0))return;let r=q(e,t);z.current=null,V(r),G(r)},ye=e=>{!A||x||e.buttons!==0||(V(null),G(F))},be=e=>{H(!1);let t=z.current;z.current=null,A&&!x&&t!==null&&J(t),_?.(e)},J=e=>{let t=h(e,j,w);G(t),N||P(t),g(t)},xe=(e,t)=>{if(!A||x||e.button!==0||e.isPrimary===!1||B.current!==null)return;U.current?.focus(),B.current=e.pointerId,e.currentTarget.setPointerCapture?.(e.pointerId),z.current=null;let n=q(e,t);V(n),G(n)},Se=(e,t)=>{B.current===e.pointerId&&e.button===0&&(B.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId),!(!A||x)&&(J(q(e,t)),e.pointerType===`touch`&&V(null)))},Y=e=>{B.current===e.pointerId&&(B.current=null,z.current=null,V(null),G(F))},X=e=>{let t=h(e,j,w);t!==W&&(G(t),oe?(z.current=null,J(t)):z.current=t)},Z=e=>{let t=W%1==.5?W:Math.round(W);X(t+e)},Ce=e=>{switch(e.key){case`ArrowLeft`:e.preventDefault(),Z((w?.5:1)*(k?1:-1));break;case`ArrowRight`:e.preventDefault(),Z((w?.5:1)*(k?-1:1));break;case`ArrowDown`:e.preventDefault(),Z(w?-.5:-1);break;case`ArrowUp`:e.preventDefault(),Z(w?.5:1);break;case`Home`:e.preventDefault(),X(0);break;case`End`:e.preventDefault(),X(j);break;case`Enter`:e.preventDefault(),z.current=null,J(W)}},we=()=>Array.from({length:j},(e,t)=>{let n=t+1,r=n!==j;return s(`span`,{className:`star star-${n}`,style:r?{paddingInlineEnd:`var(--stars-rating-gap)`}:void 0,children:s(d,{index:n,value:W,size:M,isHalf:w,isRtl:k,fullId:K,halfId:ge,noneId:_e,primaryColor:re,secondaryColor:ie,hoverColor:ae,hoverValue:me,committedValue:F,onPointerMove:e=>ve(e,n),onPointerLeave:ye,onPointerDown:e=>xe(e,n),onPointerUp:e=>Se(e,n),onPointerCancel:Y,onLostPointerCapture:Y})},`react-stars-rating-char${n}`)}),Q=A&&!x,$=h(W,j,w),Te=e=>{H(e.currentTarget.matches(`:focus-visible`)),v?.(e)},Ee={...A?f.activeContainer:f.inActiveContainer,"--stars-rating-gap":`${de}px`,"--stars-rating-size":`${M}px`,...he?{outline:`2px solid var(--stars-rating-focus-color, highlight)`,outlineOffset:2}:void 0,...ne};return c(`span`,{...le,ref:U,id:I,dir:D,role:`slider`,"aria-label":O?void 0:se,"aria-labelledby":O,"aria-valuemin":0,"aria-valuemax":j,"aria-valuenow":$,"aria-valuetext":ce($,j),"aria-orientation":`horizontal`,"aria-readonly":!A,"aria-disabled":x||void 0,tabIndex:Q?0:-1,onKeyDown:Q?Ce:void 0,onBlur:be,onFocus:Te,style:Ee,className:te,children:[we(),y?s(`input`,{type:`hidden`,name:y,value:F,disabled:x,form:b}):null]})});export{g as ReactStarsRating,g 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=(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,onPointerMove:n,onPointerLeave:i,onPointerDown:a,onPointerUp:o,onPointerCancel:s,index:c,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-${c}`;return x=p&&m!==null?`url(#${S})`:c<=l?`url(#${v})`:u&&Math.ceil(l)===c?`url(#${y})`:`url(#${b})`,React.createElement(`svg`,{width:t,viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onPointerLeave:i,onPointerMove:n,onPointerDown:a,onPointerUp:o,onPointerCancel:s},React.createElement(r,{primaryColor:d,secondaryColor:f,offset:_,index:c,value:l,fullId:v,halfId:y,noneId:b,hoverColor:p,hoverValue:m,committedValue:h,hoverGradientId:S}),React.createElement(`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`}))},a={activeContainer:{border:`none`,display:`inline-flex`},inActiveContainer:{border:`none`,outline:0,display:`inline-flex`}},o=e=>Number.isFinite(e)&&e>=1?Math.floor(e):5,s=(e,t,n=0)=>Number.isFinite(e)&&e>=n?e:t,c=(e,t,n)=>{let r=Math.min(Math.max(Number.isFinite(e)?e:0,0),t);return n?r:Math.round(r)},l=({id:e,value:n,defaultValue:r=0,onChange:l=()=>{},name:u,disabled:d=!1,isEdit:f=!0,isHalf:p=!0,count:m=5,size:h=25,starGap:g=0,className:_=``,primaryColor:v=`orange`,secondaryColor:y=`grey`,hoverColor:b,isArrowSubmit:x=!1,ariaLabel:S=`Star rating`,ariaLabelledBy:C,getValueText:w=(e,t)=>`${e} of ${t}`})=>{let T=o(m),E=s(h,25,2**-52),D=s(g,0),O=n!==void 0,[k,A]=(0,t.useState)(()=>c(r,T,p)),j=c(n??k,T,p),ee=(0,t.useId)().replace(/:/g,``),M=e??`react-stars-rating-${ee}`,[N,P]=(0,t.useState)(()=>({sourceValue:j,displayValue:j})),F=(0,t.useRef)(null),[I,L]=(0,t.useState)(null),R=(0,t.useRef)(null),z=N.sourceValue===j?N.displayValue:j,B=e=>{P({sourceValue:j,displayValue:c(e,T,p)})},V=(0,t.useMemo)(()=>`fullId-${M}`,[M]),H=(0,t.useMemo)(()=>`halfId-${M}`,[M]),U=(0,t.useMemo)(()=>`noneId-${M}`,[M]);(0,t.useEffect)(()=>{F.current=null},[j]),(0,t.useEffect)(()=>{if(O)return;let e=R.current?.closest(`form`),t=()=>{let e=c(r,T,p);F.current=null,L(null),A(e),P({sourceValue:e,displayValue:e})};return e?.addEventListener(`reset`,t),()=>e?.removeEventListener(`reset`,t)},[r,O,p,T]);let W=(e,t)=>{let n=Array.from(R.current?.querySelectorAll(`svg`)??[]),r=n.find(t=>{let n=t.getBoundingClientRect();return n.width>0&&e.clientX<=n.right})??n[n.length-1]??e.currentTarget,i=n.indexOf(r)+1||t;if(!p)return i;let a=r.getBoundingClientRect();return e.clientX-a.left>a.width/2?i:i-.5},G=(e,t)=>{if(!f||d)return;let n=typeof e.currentTarget.hasPointerCapture==`function`&&e.currentTarget.hasPointerCapture(e.pointerId);if(e.pointerType===`touch`&&!n)return;let r=W(e,t);F.current=null,L(r),B(r)},K=e=>{!f||d||e.buttons!==0||(L(null),B(j))},q=()=>{let e=F.current;e!==null&&(F.current=null,J(e))},J=e=>{let t=c(e,T,p);B(t),O||A(t),l(t)},Y=(e,t)=>{if(!f||d)return;R.current?.focus(),e.currentTarget.setPointerCapture?.(e.pointerId),F.current=null;let n=W(e,t);L(n),B(n)},te=(e,t)=>{!f||d||(J(W(e,t)),e.currentTarget.releasePointerCapture?.(e.pointerId),e.pointerType===`touch`&&L(null))},ne=e=>{e.currentTarget.releasePointerCapture?.(e.pointerId),F.current=null,L(null),B(j)},X=e=>{let t=c(e,T,p);t!==z&&(B(t),x?(F.current=null,J(t)):F.current=t)},Z=e=>{let t=z%1==.5?z:Math.round(z);X(t+e)},re=e=>{switch(e.key){case`ArrowLeft`:case`ArrowDown`:e.preventDefault(),Z(p?-.5:-1);break;case`ArrowRight`:case`ArrowUp`:e.preventDefault(),Z(p?.5:1);break;case`Home`:e.preventDefault(),X(0);break;case`End`:e.preventDefault(),X(T);break;case`Enter`:e.preventDefault(),F.current=null,J(z)}},ie=()=>Array.from({length:T},(e,t)=>{let n=t+1,r=n===T?void 0:{paddingRight:D};return React.createElement(`span`,{key:`react-stars-rating-char${n}`,className:`star star-${n}`,style:r},React.createElement(i,{index:n,value:z,size:E,isHalf:p,fullId:V,halfId:H,noneId:U,primaryColor:v,secondaryColor:y,hoverColor:b,hoverValue:I,committedValue:j,onPointerMove:e=>G(e,n),onPointerLeave:K,onPointerDown:e=>Y(e,n),onPointerUp:e=>te(e,n),onPointerCancel:ne}))}),Q=f&&!d,$=c(z,T,p);return React.createElement(`span`,{ref:R,id:M,role:`slider`,"aria-label":C?void 0:S,"aria-labelledby":C,"aria-valuemin":0,"aria-valuemax":T,"aria-valuenow":$,"aria-valuetext":w($,T),"aria-orientation":`horizontal`,"aria-readonly":!f,"aria-disabled":d||void 0,tabIndex:Q?0:-1,onKeyDown:Q?re:void 0,onBlur:Q?q:void 0,style:f?a.activeContainer:a.inActiveContainer,className:_},ie(),u?React.createElement(`input`,{type:`hidden`,name:u,value:j,disabled:d}):null)};e.ReactStarsRating=l,e.default=l});
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,onPointerMove:n,onPointerLeave:i,onPointerDown:a,onPointerUp:o,onPointerCancel:s,index:c,value:l,isHalf:u,isRtl:d,onLostPointerCapture:f,primaryColor:p,secondaryColor:m,hoverColor:h,hoverValue:g=null,committedValue:_=l,fill:v,offset:y=50,fullId:b,halfId:x,noneId:S})=>{let C=v,w=`${b}-hover-${c}`;return C=h&&g!==null?`url(#${w})`:c<=l?`url(#${b})`:u&&Math.ceil(l)===c?`url(#${x})`:`url(#${S})`,React.createElement(`svg`,{width:t,style:{forcedColorAdjust:`auto`,width:`var(--stars-rating-size)`,transform:d?`scaleX(-1)`:void 0},viewBox:e,xmlns:`http://www.w3.org/2000/svg`,xmlnsXlink:`http://www.w3.org/1999/xlink`,onPointerLeave:i,onPointerMove:n,onPointerDown:a,onPointerUp:o,onPointerCancel:s,onLostPointerCapture:f},React.createElement(r,{primaryColor:p,secondaryColor:m,offset:y,index:c,value:l,fullId:b,halfId:x,noneId:S,hoverColor:h,hoverValue:g,committedValue:_,hoverGradientId:w}),React.createElement(`polygon`,{fill:C,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=e=>Number.isFinite(e)&&e>=1?Math.floor(e):5,s=(e,t,n=0)=>Number.isFinite(e)&&e>=n?e:t,c=(e,t,n)=>{let r=Math.min(Math.max(Number.isFinite(e)?e:0,0),t);return n?r:Math.round(r)},l=(0,t.forwardRef)(function({id:e,value:n,defaultValue:r=0,onChange:l=()=>{},onBlur:u,onFocus:d,name:f,form:p,disabled:m=!1,readOnly:h=!1,isEdit:g=!0,isHalf:_=!0,count:v=5,size:y=25,starGap:b=0,className:x=``,style:S,dir:C=`ltr`,primaryColor:w=`var(--stars-rating-primary-color, orange)`,secondaryColor:ee=`var(--stars-rating-secondary-color, grey)`,hoverColor:te,isArrowSubmit:ne=!1,ariaLabel:T=`Star rating`,ariaLabelledBy:E,getValueText:D=(e,t)=>`${e} of ${t}`,...O},re){let k=C===`rtl`,A=g&&!h,j=o(v),M=s(y,25,2**-52),ie=s(b,0),N=n!==void 0,[ae,P]=(0,t.useState)(()=>c(r,j,_)),F=c(n??ae,j,_),I=(0,t.useId)().replace(/:/g,``),L=e??`react-stars-rating-${I}`,[R,z]=(0,t.useState)(()=>({sourceValue:F,displayValue:F})),B=(0,t.useRef)(null),V=(0,t.useRef)(null),[oe,H]=(0,t.useState)(null),[se,U]=(0,t.useState)(!1),W=(0,t.useRef)(null);(0,t.useImperativeHandle)(re,()=>W.current,[]),R.sourceValue!==F&&(z({sourceValue:F,displayValue:F}),H(null));let G=R.sourceValue===F?R.displayValue:F,K=e=>{z({sourceValue:F,displayValue:c(e,j,_)})},ce=(0,t.useMemo)(()=>`fullId-${L}`,[L]),le=(0,t.useMemo)(()=>`halfId-${L}`,[L]),ue=(0,t.useMemo)(()=>`noneId-${L}`,[L]);(0,t.useEffect)(()=>{B.current=null},[F]),(0,t.useEffect)(()=>{if(N)return;let e=p?document.getElementById(p):W.current?.closest(`form`),t=!0,n=e=>{setTimeout(()=>{if(!t||e.defaultPrevented)return;let n=c(r,j,_);B.current=null,V.current=null,H(null),P(n),z({sourceValue:n,displayValue:n})})};return e?.addEventListener(`reset`,n),()=>{t=!1,e?.removeEventListener(`reset`,n)}},[r,p,N,_,j]);let q=(e,t)=>{let n=Array.from(W.current?.querySelectorAll(`svg`)??[]),r=n.find(t=>{let n=t.getBoundingClientRect();return n.width>0&&(k?e.clientX>=n.left:e.clientX<=n.right)})??n[n.length-1]??e.currentTarget,i=n.indexOf(r)+1||t;if(!_)return i;let a=r.getBoundingClientRect();return(k?a.right-e.clientX:e.clientX-a.left)>a.width/2?i:i-.5},de=(e,t)=>{if(!A||m)return;let n=V.current;if(n!==null&&n!==e.pointerId||n===null&&(e.pointerType===`touch`||e.buttons!==0))return;let r=q(e,t);B.current=null,H(r),K(r)},fe=e=>{!A||m||e.buttons!==0||(H(null),K(F))},pe=e=>{U(!1);let t=B.current;B.current=null,A&&!m&&t!==null&&J(t),u?.(e)},J=e=>{let t=c(e,j,_);K(t),N||P(t),l(t)},me=(e,t)=>{if(!A||m||e.button!==0||e.isPrimary===!1||V.current!==null)return;W.current?.focus(),V.current=e.pointerId,e.currentTarget.setPointerCapture?.(e.pointerId),B.current=null;let n=q(e,t);H(n),K(n)},he=(e,t)=>{V.current===e.pointerId&&e.button===0&&(V.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId),!(!A||m)&&(J(q(e,t)),e.pointerType===`touch`&&H(null)))},Y=e=>{V.current===e.pointerId&&(V.current=null,B.current=null,H(null),K(F))},X=e=>{let t=c(e,j,_);t!==G&&(K(t),ne?(B.current=null,J(t)):B.current=t)},Z=e=>{let t=G%1==.5?G:Math.round(G);X(t+e)},ge=e=>{switch(e.key){case`ArrowLeft`:e.preventDefault(),Z((_?.5:1)*(k?1:-1));break;case`ArrowRight`:e.preventDefault(),Z((_?.5:1)*(k?-1:1));break;case`ArrowDown`:e.preventDefault(),Z(_?-.5:-1);break;case`ArrowUp`:e.preventDefault(),Z(_?.5:1);break;case`Home`:e.preventDefault(),X(0);break;case`End`:e.preventDefault(),X(j);break;case`Enter`:e.preventDefault(),B.current=null,J(G)}},_e=()=>Array.from({length:j},(e,t)=>{let n=t+1,r=n!==j;return React.createElement(`span`,{key:`react-stars-rating-char${n}`,className:`star star-${n}`,style:r?{paddingInlineEnd:`var(--stars-rating-gap)`}:void 0},React.createElement(i,{index:n,value:G,size:M,isHalf:_,isRtl:k,fullId:ce,halfId:le,noneId:ue,primaryColor:w,secondaryColor:ee,hoverColor:te,hoverValue:oe,committedValue:F,onPointerMove:e=>de(e,n),onPointerLeave:fe,onPointerDown:e=>me(e,n),onPointerUp:e=>he(e,n),onPointerCancel:Y,onLostPointerCapture:Y}))}),Q=A&&!m,$=c(G,j,_),ve=e=>{U(e.currentTarget.matches(`:focus-visible`)),d?.(e)},ye={...A?a.activeContainer:a.inActiveContainer,"--stars-rating-gap":`${ie}px`,"--stars-rating-size":`${M}px`,...se?{outline:`2px solid var(--stars-rating-focus-color, highlight)`,outlineOffset:2}:void 0,...S};return React.createElement(`span`,{...O,ref:W,id:L,dir:C,role:`slider`,"aria-label":E?void 0:T,"aria-labelledby":E,"aria-valuemin":0,"aria-valuemax":j,"aria-valuenow":$,"aria-valuetext":D($,j),"aria-orientation":`horizontal`,"aria-readonly":!A,"aria-disabled":m||void 0,tabIndex:Q?0:-1,onKeyDown:Q?ge:void 0,onBlur:pe,onFocus:ve,style:ye,className:x},_e(),f?React.createElement(`input`,{type:`hidden`,name:f,value:F,disabled:m,form:p}):null)});e.ReactStarsRating=l,e.default=l});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-awesome-stars-rating",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
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",
@@ -48,7 +48,7 @@
48
48
  "type": "git",
49
49
  "url": "git+https://github.com/fedoryakubovich/react-awesome-stars-rating.git"
50
50
  },
51
- "homepage": "https://react-awesome-stars-rating.vercel.app",
51
+ "homepage": "https://react-awesome-stars-rating-orpin.vercel.app/",
52
52
  "author": "Fedor Yakubovich",
53
53
  "bugs": {
54
54
  "url": "https://github.com/fedoryakubovich/react-awesome-stars-rating/issues"
@@ -59,17 +59,21 @@
59
59
  "build": "vite build && vite build -c vite.umd.config.ts && tsc -p tsconfig.build.json && node scripts/emit-cjs-types.mjs",
60
60
  "build:site": "vite build --config vite.site.config.ts",
61
61
  "preview": "vite preview",
62
- "lint": "eslint .",
63
- "format": "prettier --check .",
64
- "format:write": "prettier --write .",
62
+ "lint": "oxlint .",
63
+ "verify:lint": "node scripts/verify-lint.mjs",
64
+ "format": "oxfmt --check .",
65
+ "format:write": "oxfmt --write .",
65
66
  "test": "vitest run",
66
67
  "test:watch": "vitest",
67
68
  "test:e2e": "playwright test",
68
69
  "test:consumer": "node scripts/verify-consumer.mjs",
70
+ "test:next": "node scripts/verify-next.mjs",
69
71
  "coverage": "vitest run --coverage",
70
72
  "verify:package": "node scripts/verify-package.mjs",
71
73
  "verify:release": "node scripts/verify-release-notes.mjs",
72
74
  "verify:published": "node scripts/verify-published.mjs",
75
+ "verify:links": "node scripts/verify-links.mjs",
76
+ "verify:site": "playwright test --config playwright.deployed.config.ts",
73
77
  "lint:package": "publint && attw --pack",
74
78
  "size": "size-limit",
75
79
  "typecheck": "tsc -p tsconfig.json --noEmit",
@@ -94,70 +98,70 @@
94
98
  "@semantic-release/github": "^12.0.9",
95
99
  "@semantic-release/npm": "^13.1.5",
96
100
  "@size-limit/file": "^13.0.3",
97
- "@storybook/addon-a11y": "^10.5.10",
98
- "@storybook/addon-links": "^10.5.10",
99
- "@storybook/react-vite": "^10.5.10",
101
+ "@storybook/addon-a11y": "^10.6.0",
102
+ "@storybook/addon-links": "^10.6.0",
103
+ "@storybook/react-vite": "^10.6.0",
100
104
  "@tailwindcss/postcss": "^4.3.3",
101
105
  "@tanstack/react-form": "^1.33.5",
102
106
  "@testing-library/dom": "^10.4.1",
103
107
  "@testing-library/jest-dom": "^7.0.1",
104
108
  "@testing-library/react": "^16.3.3",
105
- "@testing-library/user-event": "^14.6.6",
109
+ "@testing-library/user-event": "^14.6.7",
106
110
  "@types/react": "^19.2.18",
107
- "@types/react-dom": "^19.2.5",
108
- "@typescript-eslint/eslint-plugin": "^8.68.0",
109
- "@typescript-eslint/parser": "^8.68.0",
111
+ "@types/react-dom": "^19.2.7",
110
112
  "@vitejs/plugin-react": "^6.1.1",
111
- "@vitest/coverage-v8": "^4.1.11",
113
+ "@vitest/coverage-v8": "^5.0.0",
112
114
  "axe-core": "^4.13.0",
113
115
  "conventional-changelog-conventionalcommits": "^9.3.1",
114
116
  "conventional-commits-filter": "^6.0.1",
115
117
  "eslint": "^9.39.5",
116
- "eslint-config-prettier": "^10.1.8",
117
- "eslint-plugin-jsx-a11y": "^6.10.2",
118
118
  "eslint-plugin-react": "^7.37.5",
119
119
  "eslint-plugin-react-hooks": "^7.1.1",
120
- "eslint-plugin-storybook": "10.5.10",
121
120
  "formik": "^2.4.9",
122
121
  "husky": "^9.1.7",
123
122
  "jsdom": "^30.0.1",
124
123
  "lint-staged": "^17.4.1",
125
- "prettier": "^3.9.6",
124
+ "oxfmt": "^0.66.0",
125
+ "oxlint": "^1.81.0",
126
126
  "publint": "^0.3.24",
127
127
  "react": "^19.2.8",
128
128
  "react-dom": "^19.2.8",
129
129
  "react-hook-form": "^7.87.0",
130
130
  "rimraf": "^6.1.3",
131
131
  "semantic-release": "^25.0.9",
132
+ "sigstore": "^5.0.0",
132
133
  "size-limit": "^13.0.3",
133
- "storybook": "^10.5.10",
134
+ "storybook": "^10.6.0",
134
135
  "tailwindcss": "^4.3.3",
135
136
  "typescript": "^6.0.3",
136
137
  "vite": "^8.2.2",
137
- "vitest": "^4.1.11"
138
+ "vitest": "^5.0.0"
138
139
  },
139
140
  "size-limit": [
140
141
  {
141
142
  "name": "ESM entry",
142
143
  "path": "dist/index.mjs",
143
- "limit": "2.55 kB",
144
+ "limit": "3 kB",
144
145
  "gzip": true
145
146
  },
146
147
  {
147
148
  "name": "CJS entry",
148
149
  "path": "dist/index.cjs",
149
- "limit": "2.6 kB",
150
+ "limit": "3.05 kB",
150
151
  "gzip": true
151
152
  },
152
153
  {
153
154
  "name": "UMD bundle",
154
155
  "path": "dist/index.umd.cjs",
155
- "limit": "2.7 kB",
156
+ "limit": "3.1 kB",
156
157
  "gzip": true
157
158
  }
158
159
  ],
159
160
  "lint-staged": {
160
- "*.{ts,tsx,js,jsx,mjs,cjs,json,css,md}": "prettier --write",
161
- "*.{ts,tsx,js,jsx,mjs}": "eslint --fix"
161
+ "*.{ts,tsx,js,jsx,mjs,cjs}": [
162
+ "oxlint --fix",
163
+ "oxfmt --write"
164
+ ],
165
+ "*.{json,jsonc,css,md,yml,yaml,html}": "oxfmt --write"
162
166
  }
163
167
  }