react-slot-counter
Advanced tools
Comparing version 1.8.1 to 1.9.0
@@ -15,5 +15,7 @@ import React, { RefObject } from 'react'; | ||
reverse?: boolean; | ||
sequentialAnimationMode: boolean; | ||
useMonospaceWidth: boolean; | ||
} | ||
declare function Slot({ charClassName, numbersRef, active, isChanged, effectiveDuration, delay, value, startValue, dummyList, hasInfiniteList, valueClassName, reverse, }: Props): JSX.Element; | ||
declare function Slot({ charClassName, numbersRef, active, isChanged, effectiveDuration, delay, value, startValue, dummyList, hasInfiniteList, valueClassName, reverse, sequentialAnimationMode, useMonospaceWidth, }: Props): JSX.Element; | ||
declare const _default: React.MemoExoticComponent<typeof Slot>; | ||
export default _default; |
@@ -16,2 +16,4 @@ import React from 'react'; | ||
valueClassName?: string; | ||
sequentialAnimationMode?: boolean; | ||
useMonospaceWidth?: boolean; | ||
} | ||
@@ -18,0 +20,0 @@ declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<Props & React.RefAttributes<SlotCounterRef>>>; |
@@ -127,3 +127,5 @@ 'use client'; | ||
valueClassName = _a.valueClassName, | ||
reverse = _a.reverse; | ||
reverse = _a.reverse, | ||
sequentialAnimationMode = _a.sequentialAnimationMode, | ||
useMonospaceWidth = _a.useMonospaceWidth; | ||
var _b = useState(false), | ||
@@ -138,3 +140,3 @@ localActive = _b[0], | ||
var itemRef = useRef(null); | ||
var _d = useState(shuffle(dummyList)), | ||
var _d = useState(sequentialAnimationMode ? dummyList : shuffle(dummyList)), | ||
dummyListState = _d[0], | ||
@@ -145,3 +147,7 @@ setDummyListState = _d[1]; | ||
setFontHeight = _e[1]; | ||
var _f = useState(0), | ||
maxNumberWidth = _f[0], | ||
setMaxNumberWidth = _f[1]; | ||
var slotNumbersHeight = fontHeight * (dummyList.length + 1); | ||
var isNumericValue = typeof value !== 'object' && isNumeric(value); | ||
useIsomorphicLayoutEffect(function () { | ||
@@ -151,2 +157,20 @@ var _a, _b; | ||
}, []); | ||
useIsomorphicLayoutEffect(function () { | ||
if (!isNumericValue || !useMonospaceWidth) return; | ||
var widthList = range(0, 10).map(function (i) { | ||
var _a, _b; | ||
var testElement = document.createElement('div'); | ||
testElement.style.position = 'absolute'; | ||
testElement.style.top = '0'; | ||
testElement.style.left = '-9999px'; | ||
testElement.style.visibility = 'hidden'; | ||
testElement.textContent = i.toString(); | ||
(_a = itemRef.current) === null || _a === void 0 ? void 0 : _a.appendChild(testElement); | ||
var width = testElement.getBoundingClientRect().width; | ||
(_b = itemRef.current) === null || _b === void 0 ? void 0 : _b.removeChild(testElement); | ||
return width; | ||
}); | ||
var maxWidth = Math.max.apply(Math, widthList); | ||
setMaxNumberWidth(maxWidth); | ||
}, [isNumericValue, useMonospaceWidth]); | ||
useEffect(function () { | ||
@@ -161,7 +185,7 @@ setLocalActive(active); | ||
return setLocalValue(value); | ||
}, effectiveDuration * 1000 * 1.3 / dummyList.length + delay * 1000); | ||
}, [localActive, value, effectiveDuration, delay, dummyList.length]); | ||
}, sequentialAnimationMode ? 0 : effectiveDuration * 1000 * 1.3 / dummyList.length + delay * 1000); | ||
}, [localActive, value, effectiveDuration, delay, dummyList.length, sequentialAnimationMode]); | ||
useEffect(function () { | ||
setDummyListState(shuffle(dummyList)); | ||
}, [value, dummyList]); | ||
setDummyListState(sequentialAnimationMode ? dummyList : shuffle(dummyList)); | ||
}, [value, dummyList, sequentialAnimationMode]); | ||
var renderDummyList = function () { | ||
@@ -179,2 +203,3 @@ return dummyListState.map(function (dummyNumber, slotIndex) { | ||
style: { | ||
width: useMonospaceWidth ? maxNumberWidth : undefined, | ||
height: fontHeight | ||
@@ -194,7 +219,10 @@ } | ||
className: styles.num, | ||
"aria-hidden": "true" | ||
}, startValue !== null && startValue !== void 0 ? startValue : localValue), renderDummyList(), React.createElement("div", { | ||
"aria-hidden": "true", | ||
style: { | ||
height: fontHeight | ||
} | ||
}, sequentialAnimationMode && (reverse ? localValue : prevValueRef.current), !sequentialAnimationMode && (startValue !== null && startValue !== void 0 ? startValue : localValue)), renderDummyList(), React.createElement("div", { | ||
className: mergeClassNames(styles.num, valueClassName), | ||
ref: itemRef | ||
}, localValue), hasInfiniteList ? renderDummyList() : null)); | ||
}, !sequentialAnimationMode && localValue, sequentialAnimationMode && (reverse ? prevValueRef.current : localValue)), hasInfiniteList ? renderDummyList() : null)); | ||
} | ||
@@ -222,9 +250,13 @@ var Slot$1 = memo(Slot); | ||
hasInfiniteList = _p === void 0 ? false : _p, | ||
valueClassName = _a.valueClassName; | ||
valueClassName = _a.valueClassName, | ||
_q = _a.sequentialAnimationMode, | ||
sequentialAnimationMode = _q === void 0 ? false : _q, | ||
_r = _a.useMonospaceWidth, | ||
useMonospaceWidth = _r === void 0 ? false : _r; | ||
var serializedValue = useMemo(function () { | ||
return isJSXElementArray(value) ? '' : JSON.stringify(value); | ||
}, [value]); | ||
var _q = useState(false), | ||
active = _q[0], | ||
setActive = _q[1]; | ||
var _s = useState(false), | ||
active = _s[0], | ||
setActive = _s[1]; | ||
var startAnimationOptionsRef = useRef(); | ||
@@ -280,2 +312,15 @@ var numbersRef = useRef(null); | ||
}, [startAnimation]); | ||
var getSequentialDummyListByDigit = useCallback(function (digit) { | ||
var prevValue = prevValueRef.current; | ||
if (prevValue == null || !isNumeric(prevValue) || !isNumeric(value)) { | ||
return []; | ||
} | ||
var prevNumValue = Number(toNumeric(prevValue)); | ||
var numValue = Number(toNumeric(value)); | ||
var divider = Math.pow(10, digit - 1); | ||
var dummyList = prevNumValue < numValue ? range(Math.floor(prevNumValue / divider) + 1, Math.floor(numValue / divider)) : range(Math.floor(numValue / divider) + 1, Math.floor(prevNumValue / divider)); | ||
return Array.from(new Set(dummyList.map(function (v) { | ||
return v.toString()[v.toString().length - 1]; | ||
}))).filter(Boolean); | ||
}, [value]); | ||
useEffect(function () { | ||
@@ -315,6 +360,8 @@ if (!autoAnimationStart) return; | ||
startValue: startValueList === null || startValueList === void 0 ? void 0 : startValueList[i], | ||
dummyList: dummyList, | ||
dummyList: sequentialAnimationMode ? getSequentialDummyListByDigit(valueList.length - i) : dummyList, | ||
hasInfiniteList: hasInfiniteList, | ||
valueClassName: valueClassName, | ||
reverse: reverseAnimation | ||
reverse: reverseAnimation, | ||
sequentialAnimationMode: sequentialAnimationMode, | ||
useMonospaceWidth: useMonospaceWidth | ||
}); | ||
@@ -321,0 +368,0 @@ })); |
@@ -129,3 +129,5 @@ 'use client'; | ||
valueClassName = _a.valueClassName, | ||
reverse = _a.reverse; | ||
reverse = _a.reverse, | ||
sequentialAnimationMode = _a.sequentialAnimationMode, | ||
useMonospaceWidth = _a.useMonospaceWidth; | ||
var _b = React.useState(false), | ||
@@ -140,3 +142,3 @@ localActive = _b[0], | ||
var itemRef = React.useRef(null); | ||
var _d = React.useState(shuffle(dummyList)), | ||
var _d = React.useState(sequentialAnimationMode ? dummyList : shuffle(dummyList)), | ||
dummyListState = _d[0], | ||
@@ -147,3 +149,7 @@ setDummyListState = _d[1]; | ||
setFontHeight = _e[1]; | ||
var _f = React.useState(0), | ||
maxNumberWidth = _f[0], | ||
setMaxNumberWidth = _f[1]; | ||
var slotNumbersHeight = fontHeight * (dummyList.length + 1); | ||
var isNumericValue = typeof value !== 'object' && isNumeric(value); | ||
useIsomorphicLayoutEffect(function () { | ||
@@ -153,2 +159,20 @@ var _a, _b; | ||
}, []); | ||
useIsomorphicLayoutEffect(function () { | ||
if (!isNumericValue || !useMonospaceWidth) return; | ||
var widthList = range(0, 10).map(function (i) { | ||
var _a, _b; | ||
var testElement = document.createElement('div'); | ||
testElement.style.position = 'absolute'; | ||
testElement.style.top = '0'; | ||
testElement.style.left = '-9999px'; | ||
testElement.style.visibility = 'hidden'; | ||
testElement.textContent = i.toString(); | ||
(_a = itemRef.current) === null || _a === void 0 ? void 0 : _a.appendChild(testElement); | ||
var width = testElement.getBoundingClientRect().width; | ||
(_b = itemRef.current) === null || _b === void 0 ? void 0 : _b.removeChild(testElement); | ||
return width; | ||
}); | ||
var maxWidth = Math.max.apply(Math, widthList); | ||
setMaxNumberWidth(maxWidth); | ||
}, [isNumericValue, useMonospaceWidth]); | ||
React.useEffect(function () { | ||
@@ -163,7 +187,7 @@ setLocalActive(active); | ||
return setLocalValue(value); | ||
}, effectiveDuration * 1000 * 1.3 / dummyList.length + delay * 1000); | ||
}, [localActive, value, effectiveDuration, delay, dummyList.length]); | ||
}, sequentialAnimationMode ? 0 : effectiveDuration * 1000 * 1.3 / dummyList.length + delay * 1000); | ||
}, [localActive, value, effectiveDuration, delay, dummyList.length, sequentialAnimationMode]); | ||
React.useEffect(function () { | ||
setDummyListState(shuffle(dummyList)); | ||
}, [value, dummyList]); | ||
setDummyListState(sequentialAnimationMode ? dummyList : shuffle(dummyList)); | ||
}, [value, dummyList, sequentialAnimationMode]); | ||
var renderDummyList = function () { | ||
@@ -181,2 +205,3 @@ return dummyListState.map(function (dummyNumber, slotIndex) { | ||
style: { | ||
width: useMonospaceWidth ? maxNumberWidth : undefined, | ||
height: fontHeight | ||
@@ -196,7 +221,10 @@ } | ||
className: styles.num, | ||
"aria-hidden": "true" | ||
}, startValue !== null && startValue !== void 0 ? startValue : localValue), renderDummyList(), React.createElement("div", { | ||
"aria-hidden": "true", | ||
style: { | ||
height: fontHeight | ||
} | ||
}, sequentialAnimationMode && (reverse ? localValue : prevValueRef.current), !sequentialAnimationMode && (startValue !== null && startValue !== void 0 ? startValue : localValue)), renderDummyList(), React.createElement("div", { | ||
className: mergeClassNames(styles.num, valueClassName), | ||
ref: itemRef | ||
}, localValue), hasInfiniteList ? renderDummyList() : null)); | ||
}, !sequentialAnimationMode && localValue, sequentialAnimationMode && (reverse ? prevValueRef.current : localValue)), hasInfiniteList ? renderDummyList() : null)); | ||
} | ||
@@ -224,9 +252,13 @@ var Slot$1 = React.memo(Slot); | ||
hasInfiniteList = _p === void 0 ? false : _p, | ||
valueClassName = _a.valueClassName; | ||
valueClassName = _a.valueClassName, | ||
_q = _a.sequentialAnimationMode, | ||
sequentialAnimationMode = _q === void 0 ? false : _q, | ||
_r = _a.useMonospaceWidth, | ||
useMonospaceWidth = _r === void 0 ? false : _r; | ||
var serializedValue = React.useMemo(function () { | ||
return isJSXElementArray(value) ? '' : JSON.stringify(value); | ||
}, [value]); | ||
var _q = React.useState(false), | ||
active = _q[0], | ||
setActive = _q[1]; | ||
var _s = React.useState(false), | ||
active = _s[0], | ||
setActive = _s[1]; | ||
var startAnimationOptionsRef = React.useRef(); | ||
@@ -282,2 +314,15 @@ var numbersRef = React.useRef(null); | ||
}, [startAnimation]); | ||
var getSequentialDummyListByDigit = React.useCallback(function (digit) { | ||
var prevValue = prevValueRef.current; | ||
if (prevValue == null || !isNumeric(prevValue) || !isNumeric(value)) { | ||
return []; | ||
} | ||
var prevNumValue = Number(toNumeric(prevValue)); | ||
var numValue = Number(toNumeric(value)); | ||
var divider = Math.pow(10, digit - 1); | ||
var dummyList = prevNumValue < numValue ? range(Math.floor(prevNumValue / divider) + 1, Math.floor(numValue / divider)) : range(Math.floor(numValue / divider) + 1, Math.floor(prevNumValue / divider)); | ||
return Array.from(new Set(dummyList.map(function (v) { | ||
return v.toString()[v.toString().length - 1]; | ||
}))).filter(Boolean); | ||
}, [value]); | ||
React.useEffect(function () { | ||
@@ -317,6 +362,8 @@ if (!autoAnimationStart) return; | ||
startValue: startValueList === null || startValueList === void 0 ? void 0 : startValueList[i], | ||
dummyList: dummyList, | ||
dummyList: sequentialAnimationMode ? getSequentialDummyListByDigit(valueList.length - i) : dummyList, | ||
hasInfiniteList: hasInfiniteList, | ||
valueClassName: valueClassName, | ||
reverse: reverseAnimation | ||
reverse: reverseAnimation, | ||
sequentialAnimationMode: sequentialAnimationMode, | ||
useMonospaceWidth: useMonospaceWidth | ||
}); | ||
@@ -323,0 +370,0 @@ })); |
{ | ||
"name": "react-slot-counter", | ||
"version": "1.8.1", | ||
"version": "1.9.0", | ||
"description": "A versatile and engaging component to display numbers in a captivating slot machine UI, perfect for enhancing user experience and grabbing attention in your web projects", | ||
@@ -5,0 +5,0 @@ "author": "almond-bongbong", |
@@ -17,7 +17,13 @@ # react-slot-counter | ||
- Customize animation duration and other settings | ||
- Animate only changed characters | ||
- Control animation start with a ref | ||
- Easily add custom styles to the characters and separators | ||
- **Flexible Inputs**: Support for displaying numbers, strings, and JSX elements. You can even use a combination of these in a single slot counter instance! | ||
- **Animated Changes**: Only the characters that change get animated, bringing life and motion to your app's interface. | ||
- **Customize Animation Settings**: Control the duration of the animation, or decide whether to start the animation automatically upon mounting. | ||
- **Sequential Animation Mode**: A unique feature that provides the option to animate the numbers incrementally or decrementally from the start value to the target value, rather than a random animation. | ||
- **Monospace Font Support**: The useMonospaceWidth prop ensures that all numeric characters occupy the same horizontal space as they would in a monospace font. | ||
- **Infinite List Appearance**: Option to make the list appear as continuous, seamlessly connecting the end of the target character to the beginning. | ||
- **Style Customization**: Easily add custom styles to the characters, separators, and overall container. You can even customize the class name for the slot value. | ||
- **Ref Support**: Control the animation start with a ref for increased flexibility. | ||
Immerse your users in an interactive, engaging, and enjoyable experience with `react-slot-counter`. Whether you're displaying user scores, loading status, or real-time data, `react-slot-counter` adds that extra 'spin' to your numbers and strings. | ||
## Installation | ||
@@ -43,11 +49,6 @@ | ||
<SlotCounter value={123456} /> | ||
<SlotCounter value={36.5} /> | ||
<SlotCounter value="1,234,567" /> | ||
<SlotCounter value={['1', '2', '3', '4', '5', '6']} /> | ||
<SlotCounter value="??????" /> | ||
<SlotCounter value={36.5} duration={2} /> | ||
<SlotCounter | ||
value={123456} | ||
charClassName="char" | ||
separatorClassName="sep" | ||
/> | ||
</> | ||
@@ -66,16 +67,18 @@ ); | ||
| Prop | Type | Default | Description | Version | | ||
| ------------------- | ----------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | | ||
| value _(required)_ | `number` \| `string` \| `string[]` \| `JSX.Element[]` | | The value to be displayed. It can be a number or a string with numbers and commas. | JSX.Element: 1.8.0 | | ||
| startValue | `number` \| `string` \| `string[]` \| `JSX.Element[]` | | The initial value to be displayed before the animation starts. It sets the beginning of the slot machine animation. | 1.7.0 | | ||
| duration | `number` | `0.7` | The duration of the animation in seconds. | | | ||
| dummyCharacters | `string[]` \| `JSX.Element[]` | Defaults to random numbers from 0 to 9 | An array of dummy characters to be used in the animation. | | | ||
| dummyCharacterCount | `number` | `6` | The number of dummy characters to be displayed in the animation before reaching the target character. | | | ||
| autoAnimationStart | `boolean` | `true` | Determines whether the animation should start automatically when the component is first mounted. | | | ||
| animateUnchanged | `boolean` | `false` | Determines whether to animate only the characters that have changed. | | | ||
| hasInfiniteList | `boolean` | `false` | Determines whether the list should appear as continuous, with the end of the target character seamlessly connected to the beginning. | 1.4.2 | | ||
| containerClassName | `string` | | The class name of container. | | | ||
| charClassName | `string` | | The class name of each character. | | | ||
| separatorClassName | `string` | | The class name of the separator character (`.` or `,`). | | | ||
| valueClassName | `string` | | The class name for the value of the slot, making it possible to customize the styling and visibility of the value. | 1.4.3 | | ||
| Prop | Type | Default | Description | Version | | ||
| ----------------------- | ----------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | | ||
| value _(required)_ | `number` \| `string` \| `string[]` \| `JSX.Element[]` | | The value to be displayed. It can be a number or a string with numbers and commas. | JSX.Element: 1.8.0 | | ||
| startValue | `number` \| `string` \| `string[]` \| `JSX.Element[]` | | The initial value to be displayed before the animation starts. It sets the beginning of the slot machine animation. | 1.7.0 | | ||
| duration | `number` | `0.7` | The duration of the animation in seconds. | | | ||
| dummyCharacters | `string[]` \| `JSX.Element[]` | Defaults to random numbers from 0 to 9 | An array of dummy characters to be used in the animation. | | | ||
| dummyCharacterCount | `number` | `6` | The number of dummy characters to be displayed in the animation before reaching the target character. | | | ||
| autoAnimationStart | `boolean` | `true` | Determines whether the animation should start automatically when the component is first mounted. | | | ||
| animateUnchanged | `boolean` | `false` | Determines whether to animate only the characters that have changed. | | | ||
| hasInfiniteList | `boolean` | `false` | Determines whether the list should appear as continuous, with the end of the target character seamlessly connected to the beginning. | 1.4.2 | | ||
| containerClassName | `string` | | The class name of container. | | | ||
| charClassName | `string` | | The class name of each character. | | | ||
| separatorClassName | `string` | | The class name of the separator character (`.` or `,`). | | | ||
| valueClassName | `string` | | The class name for the value of the slot, making it possible to customize the styling and visibility of the value. | 1.4.3 | | ||
| sequentialAnimationMode | `boolean` | `false` | Determines if the animation should increment or decrement sequentially from the startValue to value instead of random animation. | 1.9.0 | | ||
| useMonospaceWidth | `boolean` | `false` | Ensures that all numeric characters occupy the same horizontal space, just like they would in a monospace font. | 1.9.0 | | ||
@@ -110,6 +113,6 @@ ## Ref | ||
return ( | ||
<div> | ||
<> | ||
<SlotCounter value={123456} ref={counterRef} /> | ||
<button onClick={handleStartClick}>Start</button> | ||
</div> | ||
</> | ||
); | ||
@@ -123,14 +126,10 @@ } | ||
Contributions, issues, and feature requests are welcome! If you'd like to contribute, please follow these steps: | ||
Contributions are always welcome! | ||
1. Fork the project | ||
2. Create a new branch for your feature or bugfix | ||
3. Commit your changes | ||
4. Push your changes to the branch | ||
5. Open a pull request | ||
## Support Us | ||
Please also make sure to update tests as appropriate and follow the coding style of the project. | ||
If you find this library useful, consider giving us a star on [GitHub!](https://github.com/almond-bongbong/react-slot-counter/stargazers) Your support is greatly appreciated and it helps the project grow. | ||
## License | ||
This project is licensed under the MIT License | ||
This project is licensed under the MIT License. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
52490
761
131