├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── babel.config.js ├── dist ├── vue3-roulette.esm.js └── vue3-roulette.min.js ├── docs ├── 404.html ├── CNAME ├── assets │ ├── favicon.adef23e4.ico │ ├── index.019fedf3.css │ ├── index.58c620c4.js │ └── vendor.aed0c264.js ├── index.html ├── og-image.jpg └── roulette.png ├── favicon.ico ├── index.html ├── jest.config.js ├── package-lock.json ├── package.json ├── pages ├── App.vue ├── components │ ├── Example.vue │ ├── Footer.vue │ ├── ItemsManager.vue │ ├── Navbar.vue │ └── WheelManager.vue ├── data │ ├── examplesData.js │ └── homeData.js ├── main.js ├── router.js └── views │ ├── Custom.vue │ ├── Docs.vue │ ├── Examples.vue │ └── Home.vue ├── postcss.config.js ├── public ├── 404.html ├── CNAME ├── og-image.jpg └── roulette.png ├── rollup.config.js ├── src ├── components │ └── Roulette.vue ├── index.js └── styles │ └── index.scss ├── tests └── index.test.js └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /docs 5 | /tests 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | extends: ['eslint:recommended', 'plugin:vue/recommended'], 5 | 6 | env: { 7 | node: true, 8 | }, 9 | 10 | parserOptions: { 11 | parser: 'babel-eslint', 12 | }, 13 | 14 | overrides: [ 15 | { 16 | files: 'tests/**/*', 17 | rules: { 18 | // Disable no-undefined rule for Jest tests because `describe()`, `test()` 19 | // are methods available under the global NodeJS namespace. 20 | 'no-undef': 'off', 21 | }, 22 | }, 23 | ], 24 | 25 | rules: { 26 | // allow paren-less arrow functions 27 | 'arrow-parens': 0, 28 | // allow async-await 29 | 'generator-star-spacing': 0, 30 | // allow debugger during development 31 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 32 | 33 | 'vue/no-v-html': 'off', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage 4 | dist 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .eslintignore 3 | .eslintrc.js 4 | .gitlab-ci.yml 5 | babel.config.js 6 | coverage 7 | jest.config.js 8 | postcss.config.js 9 | sample 10 | src 11 | tests 12 | rollup.config.js 13 | vue.config.js 14 | sample 15 | index.html 16 | vite.config.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ludovic Nitoumbi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue3 Roulette 2 | 3 | > A customizable and flexible fortune wheel for vue3 4 | 5 | ## Demo 6 | 7 | https://roulette.nitocode.com/ 8 | 9 | See also: [codesandbox template](https://codesandbox.io/s/vue3-roulette-c8tml) 10 | 11 | ## Installation 12 | 13 | ### Using npm 14 | 15 | `npm i --save vue3-roulette` 16 | 17 | ## Usage 18 | 19 | #### main.js 20 | ```js 21 | import { createApp } from 'vue' 22 | import App from './App.vue' 23 | import { Roulette } from 'vue3-roulette' 24 | 25 | createApp(App).component("roulette", Roulette).mount('#app') 26 | ``` 27 | 28 | #### vuejs html template 29 | ```html 30 | 31 | ``` 32 | 33 | #### vuejs script 34 | 35 | Using the [sfc syntax](https://v3.vuejs.org/api/sfc-script-setup.html) 36 | ```html 37 | 51 | ``` 52 | 53 | ## Events API 54 | 55 | `wheel-start` and `wheel-end` which provide the item selected 56 | 57 | ```html 58 | 63 | ``` 64 | 65 | ## Methods API 66 | 67 | Composition API 68 | ```javascript 69 | wheel.value.launchWheel(); 70 | wheel.value.reset(); 71 | ``` 72 | Option API 73 | ```javascript 74 | this.$refs.wheel.launchWheel(); 75 | this.$refs.wheel.reset(); 76 | ``` 77 | 78 | ## Props API (Wheel) 79 | 80 | | Props | Type | Required | Default | Options | Details | 81 | |------------|------------|----------|------------|----------------|--| 82 | | items | Object | yes | - | | 4 items minimum | 83 | | first-item-index | Object | no | { value: 0 } | | 84 | | wheel-result-index | Object | no | { value: null } | from 0 to items length | 85 | | centered-indicator | Boolean | no | false | | 86 | | indicator-position | String | no | "top" | "top" \| "right" \| "bottom" \| "left" | 87 | | size | Number | no | 300 | | size unit: pixel | 88 | | display-shadow | Boolean | no | false | | 89 | | duration | Number | no | 4 | | duration unit: seconds | | 90 | | result-variation | Number | no | 0 | number between 0 and 100 | varies the result angle to fake wheel smoothness | 91 | | easing | String | no | "ease" | "ease" \| "bounce" | wheel animation | 92 | | counter-clockwise | Boolean | no | false | | rotation direction 93 | | horizontal-content | Boolean | no | false | | text item orientation 94 | | display-border | Boolean | no | false | | 95 | | display-indicator | Boolean | no | false | | 96 | 97 | ## Props API (Wheel base) 98 | 99 | | Props | Type | Required | Default | Options | Details | 100 | |------------|------------|----------|------------|----------------|--| 101 | | base-display | Boolean | no | false | | | 102 | | base-size | Number | no | 100 | | size unit: pixel | 103 | | base-display-shadow | Boolean | no | false | | | 104 | | base-display-indicator | Boolean | no | false | | | 105 | | base-background | String | no | "" | rgb(100,0,0) \| red \| #FF0000 | | 106 | 107 | 108 | ## Slots 109 | 110 | You can use your own html for the wheel base 111 | 112 | ```html 113 | 114 | 117 | 118 | ``` 119 | 120 | ## Contribution 121 | 122 | ### Project setup 123 | 124 | ```bash 125 | npm install 126 | ``` 127 | 128 | ### Compiles and hot-reloads for development 129 | 130 | ```bash 131 | npm run serve 132 | ``` 133 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // Build presets are used when building the component library with Rollup 2 | const buildPresets = [ 3 | [ 4 | '@babel/preset-env', 5 | {} 6 | ] 7 | ] 8 | 9 | // Dev presets are used when running a local development server 10 | const devPresets = ['@vue/cli-plugin-babel/preset'] 11 | 12 | // Test presets are used when running unit-tests 13 | const testPresets = [ 14 | [ 15 | 'env', 16 | { 17 | targets: { 18 | node: 'current' 19 | } 20 | } 21 | ] 22 | ] 23 | 24 | module.exports = { 25 | presets: process.env.NODE_ENV === 'production' 26 | ? buildPresets : process.env.NODE_ENV === 'development' 27 | ? devPresets : testPresets 28 | } 29 | -------------------------------------------------------------------------------- /dist/vue3-roulette.esm.js: -------------------------------------------------------------------------------- 1 | import { defineComponent, openBlock, createElementBlock, normalizeClass, normalizeStyle, createElementVNode, renderSlot, createCommentVNode, Fragment, renderList } from 'vue'; 2 | 3 | var script = defineComponent({ 4 | name: "Roulette", 5 | emits: ["wheelStart", "wheelEnd"], 6 | props: { 7 | items: { 8 | type: Object, 9 | required: true, 10 | validator: function validator(value) { 11 | return value.length >= 4; 12 | } 13 | }, 14 | firstItemIndex: { 15 | type: Object, 16 | required: false, 17 | "default": function _default() { 18 | return { 19 | value: 0 20 | }; 21 | } 22 | }, 23 | wheelResultIndex: { 24 | type: Object, 25 | required: false, 26 | "default": function _default() { 27 | return { 28 | value: null 29 | }; 30 | }, 31 | validator: function validator(obj) { 32 | return typeof obj.value === "number"; 33 | } 34 | }, 35 | centeredIndicator: { 36 | type: Boolean, 37 | required: false, 38 | "default": false 39 | }, 40 | indicatorPosition: { 41 | type: String, 42 | required: false, 43 | "default": "top", 44 | validator: function validator(value) { 45 | return ["top", "right", "bottom", "left"].includes(value); 46 | } 47 | }, 48 | size: { 49 | type: Number, 50 | required: false, 51 | "default": 300 52 | }, 53 | displayShadow: { 54 | type: Boolean, 55 | required: false, 56 | "default": false 57 | }, 58 | duration: { 59 | type: Number, 60 | required: false, 61 | "default": 4 62 | }, 63 | resultVariation: { 64 | type: Number, 65 | required: false, 66 | "default": 0, 67 | validator: function validator(value) { 68 | return value >= 0 && value <= 100; 69 | } 70 | }, 71 | easing: { 72 | type: String, 73 | required: false, 74 | "default": "ease", 75 | validator: function validator(value) { 76 | return ["ease", "bounce"].includes(value); 77 | } 78 | }, 79 | counterClockwise: { 80 | type: Boolean, 81 | required: false, 82 | "default": false 83 | }, 84 | horizontalContent: { 85 | type: Boolean, 86 | required: false, 87 | "default": false 88 | }, 89 | displayBorder: { 90 | type: Boolean, 91 | required: false, 92 | "default": false 93 | }, 94 | displayIndicator: { 95 | type: Boolean, 96 | required: false, 97 | "default": true 98 | }, 99 | baseDisplay: { 100 | type: Boolean, 101 | required: false, 102 | "default": false 103 | }, 104 | baseSize: { 105 | type: Number, 106 | required: false, 107 | "default": 100 108 | }, 109 | baseDisplayShadow: { 110 | type: Boolean, 111 | required: false, 112 | "default": false 113 | }, 114 | baseDisplayIndicator: { 115 | type: Boolean, 116 | required: false, 117 | "default": false 118 | }, 119 | baseBackground: { 120 | type: String, 121 | required: false, 122 | "default": "" 123 | } 124 | }, 125 | data: function data() { 126 | return { 127 | randomIdRoulette: 0, 128 | itemSelected: null, 129 | processingLock: false 130 | }; 131 | }, 132 | computed: { 133 | itemAngle: function itemAngle() { 134 | return 360 / this.items.length; 135 | }, 136 | startingAngle: function startingAngle() { 137 | if (this.centeredIndicator) { 138 | return -1 * this.firstItemIndex.value * this.itemAngle - this.itemAngle / 2; 139 | } else { 140 | return -1 * this.firstItemIndex.value * this.itemAngle; 141 | } 142 | }, 143 | degreesVariation: function degreesVariation() { 144 | if (!this.resultVariation) { 145 | return 0; 146 | } 147 | 148 | var minDegreesVariation = this.itemAngle / 2 * this.resultVariation / 100 * -1; 149 | var maxDegreesVariation = this.itemAngle / 2 * this.resultVariation / 100; // Return random value between min and max degrees variation 150 | 151 | return Number((Math.random() * (maxDegreesVariation - minDegreesVariation) + minDegreesVariation).toFixed(2)); 152 | }, 153 | counterClockWiseOperator: function counterClockWiseOperator() { 154 | return this.counterClockwise ? -1 : 1; 155 | } 156 | }, 157 | mounted: function mounted() { 158 | var _this = this; 159 | 160 | this.randomIdRoulette = Number((Math.random() * (999999 - 1) + 1).toFixed(0)); 161 | this.$nextTick(function () { 162 | _this.reset(); 163 | 164 | document.querySelector("#wheel-container-".concat(_this.randomIdRoulette, " .wheel")).addEventListener("transitionend", function () { 165 | _this.processingLock = false; 166 | 167 | _this.$emit("wheel-end", _this.itemSelected); 168 | }); 169 | }); 170 | }, 171 | methods: { 172 | reset: function reset() { 173 | this.itemSelected = null; 174 | document.querySelector("#wheel-container-".concat(this.randomIdRoulette, " .wheel")).style.transform = "rotate(".concat(this.startingAngle, "deg)"); 175 | }, 176 | launchWheel: function launchWheel() { 177 | if (this.processingLock && this.itemSelected != null) { 178 | return; 179 | } 180 | 181 | this.processingLock = true; 182 | var wheelResult; 183 | 184 | if (this.wheelResultIndex.value !== null) { 185 | wheelResult = this.wheelResultIndex.value % this.items.length; 186 | } else { 187 | wheelResult = Math.floor(Math.random() * this.items.length + 1) - 1; 188 | } 189 | 190 | var wheelElt = document.querySelector("#wheel-container-".concat(this.randomIdRoulette, " .wheel")); 191 | this.itemSelected = this.items[wheelResult]; 192 | wheelElt.style.transform = "rotate(".concat(this.counterClockWiseOperator * (360 * 3) + -wheelResult * this.itemAngle - this.itemAngle / 2 + this.degreesVariation, "deg)"); 193 | this.$emit("wheel-start", this.itemSelected); 194 | } 195 | } 196 | }); 197 | 198 | var _hoisted_1 = ["id"]; 199 | var _hoisted_2 = { 200 | "class": "wheel-base" 201 | }; 202 | var _hoisted_3 = { 203 | key: 0, 204 | "class": "wheel-base-indicator" 205 | }; 206 | var _hoisted_4 = ["innerHTML"]; 207 | function render(_ctx, _cache, $props, $setup, $data, $options) { 208 | return openBlock(), createElementBlock("div", { 209 | id: "wheel-container-".concat(_ctx.randomIdRoulette), 210 | "class": normalizeClass(["wheel-container", ["indicator-".concat(_ctx.indicatorPosition), { 211 | 'wheel-container-indicator': _ctx.displayIndicator 212 | }, { 213 | 'wheel-container-shadow': _ctx.displayShadow 214 | }, { 215 | 'wheel-container-border': _ctx.displayBorder 216 | }]]) 217 | }, [_ctx.baseDisplay ? (openBlock(), createElementBlock("div", { 218 | key: 0, 219 | "class": normalizeClass(["wheel-base-container", [{ 220 | 'wheel-base-container-shadow': _ctx.baseDisplayShadow 221 | }]]), 222 | style: normalizeStyle({ 223 | width: "".concat(_ctx.baseSize, "px"), 224 | height: "".concat(_ctx.baseSize, "px"), 225 | background: "".concat(_ctx.baseBackground) 226 | }) 227 | }, [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "baseContent")]), _ctx.baseDisplayIndicator ? (openBlock(), createElementBlock("div", _hoisted_3)) : createCommentVNode("", true)], 6)) : createCommentVNode("", true), createElementVNode("div", { 228 | "class": normalizeClass(["wheel", ["easing-".concat(_ctx.easing), { 229 | 'wheel-border': _ctx.displayBorder 230 | }]]), 231 | style: normalizeStyle({ 232 | width: "".concat(_ctx.size, "px"), 233 | height: "".concat(_ctx.size, "px"), 234 | transitionDuration: "".concat(_ctx.duration, "s"), 235 | transform: "rotate(".concat(_ctx.startingAngle, "deg)") 236 | }) 237 | }, [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.items, function (item, index) { 238 | return openBlock(), createElementBlock("div", { 239 | key: item.id, 240 | "class": "wheel-item", 241 | style: normalizeStyle({ 242 | transform: "rotate(".concat(_ctx.itemAngle * index, "deg) skewY(").concat(-(90 - _ctx.itemAngle), "deg)"), 243 | background: item.background 244 | }) 245 | }, [createElementVNode("div", { 246 | "class": normalizeClass(["content", { 247 | 'horizontal-content': _ctx.horizontalContent 248 | }]), 249 | style: normalizeStyle({ 250 | transform: "skewY(".concat(90 - _ctx.itemAngle, "deg) rotate(").concat(_ctx.itemAngle / 2, "deg)") 251 | }) 252 | }, [createElementVNode("span", { 253 | style: normalizeStyle({ 254 | color: item.textColor 255 | }), 256 | innerHTML: item.htmlContent 257 | }, null, 12, _hoisted_4)], 6)], 4); 258 | }), 128))], 6)], 10, _hoisted_1); 259 | } 260 | 261 | function styleInject(css, ref) { 262 | if ( ref === void 0 ) ref = {}; 263 | var insertAt = ref.insertAt; 264 | 265 | if (!css || typeof document === 'undefined') { return; } 266 | 267 | var head = document.head || document.getElementsByTagName('head')[0]; 268 | var style = document.createElement('style'); 269 | style.type = 'text/css'; 270 | 271 | if (insertAt === 'top') { 272 | if (head.firstChild) { 273 | head.insertBefore(style, head.firstChild); 274 | } else { 275 | head.appendChild(style); 276 | } 277 | } else { 278 | head.appendChild(style); 279 | } 280 | 281 | if (style.styleSheet) { 282 | style.styleSheet.cssText = css; 283 | } else { 284 | style.appendChild(document.createTextNode(css)); 285 | } 286 | } 287 | 288 | var css_248z = ".wheel-container[data-v-2d0cf945],\n.wheel-base[data-v-2d0cf945],\n.wheel-base-container[data-v-2d0cf945],\n.wheel-base-indicator[data-v-2d0cf945] {\n transition: transform 1s ease-in-out;\n}\n.wheel-container[data-v-2d0cf945] {\n position: relative;\n display: inline-block;\n overflow: hidden;\n border-radius: 50%;\n cursor: pointer;\n}\n.wheel-container-indicator[data-v-2d0cf945]:before {\n content: \"\";\n position: absolute;\n z-index: 4;\n width: 0;\n height: 0;\n border-left: 20px solid transparent;\n border-right: 20px solid transparent;\n border-top: 20px solid black;\n transform: translateX(-50%);\n}\n.wheel-container.indicator-top[data-v-2d0cf945] {\n transform: rotate(0deg);\n}\n.wheel-container.indicator-right[data-v-2d0cf945] {\n transform: rotate(90deg);\n}\n.wheel-container.indicator-right .wheel-base[data-v-2d0cf945] {\n transform: rotate(-90deg);\n}\n.wheel-container.indicator-bottom[data-v-2d0cf945] {\n transform: rotate(180deg);\n}\n.wheel-container.indicator-bottom .wheel-base[data-v-2d0cf945] {\n transform: rotate(-180deg);\n}\n.wheel-container.indicator-left[data-v-2d0cf945] {\n transform: rotate(270deg);\n}\n.wheel-container.indicator-left .wheel-base[data-v-2d0cf945] {\n transform: rotate(-270deg);\n}\n.wheel-container-border[data-v-2d0cf945] {\n border: 8px solid black;\n}\n.wheel-container-shadow[data-v-2d0cf945] {\n box-shadow: 5px 5px 15px -5px #000000;\n}\n.wheel-base-container[data-v-2d0cf945] {\n position: absolute;\n z-index: 2;\n top: 50%;\n left: 50%;\n border-radius: 50%;\n border: 5px solid black;\n transform: translate(-50%, -50%);\n}\n.wheel-base-container-shadow[data-v-2d0cf945] {\n box-shadow: 5px 5px 15px -5px #000000;\n}\n.wheel-base-container .wheel-base[data-v-2d0cf945] {\n position: absolute;\n z-index: 2;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow: hidden;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n}\n.wheel-base-container .wheel-base-indicator[data-v-2d0cf945] {\n position: absolute;\n z-index: 1;\n width: 100%;\n height: 100%;\n}\n.wheel-base-container .wheel-base-indicator[data-v-2d0cf945]:before {\n content: \"\";\n position: absolute;\n z-index: 1;\n top: -20px;\n width: 0;\n height: 0;\n border-left: 20px solid transparent;\n border-right: 20px solid transparent;\n border-bottom: 20px solid black;\n transform: translateX(-50%);\n}\n.wheel[data-v-2d0cf945] {\n background: white;\n border-radius: 50%;\n margin: auto;\n overflow: hidden;\n}\n.wheel.easing-ease[data-v-2d0cf945] {\n transition: transform cubic-bezier(0.65, 0, 0.35, 1);\n}\n.wheel.easing-bounce[data-v-2d0cf945] {\n transition: transform cubic-bezier(0.49, 0.02, 0.52, 1.12);\n}\n.wheel-border[data-v-2d0cf945]:after {\n content: \"\";\n width: 100%;\n height: 100%;\n position: absolute;\n left: 0;\n top: 0;\n z-index: 3;\n border-radius: 50%;\n background-image: linear-gradient(to left, black 33%, rgba(255, 255, 255, 0) 0%);\n background-position: bottom;\n background-size: 3px 1px;\n /* background:linear-gradient(red,purple,orange); */\n -webkit-mask: radial-gradient(transparent 65%, #000 66%);\n mask: radial-gradient(transparent 65%, #000 66%);\n}\n.wheel-item[data-v-2d0cf945] {\n overflow: hidden;\n position: absolute;\n top: 0;\n right: 0;\n width: 50%;\n height: 50%;\n transform-origin: 0% 100%;\n border: 1px solid black;\n}\n.wheel-item[data-v-2d0cf945]:nth-child(odd) {\n background-color: skyblue;\n}\n.wheel-item[data-v-2d0cf945]:nth-child(even) {\n background-color: pink;\n}\n.wheel .content[data-v-2d0cf945] {\n position: absolute;\n left: -100%;\n width: 200%;\n height: 200%;\n text-align: center;\n transform: skewY(30deg) rotate(0deg);\n padding-top: 20px;\n}\n.wheel .content.horizontal-content[data-v-2d0cf945] {\n left: initial;\n right: 100%;\n width: 50%;\n height: 250%;\n text-align: right;\n}\n.wheel .content.horizontal-content span[data-v-2d0cf945] {\n display: block;\n transform: rotate(270deg);\n}"; 289 | styleInject(css_248z); 290 | 291 | script.render = render; 292 | script.__scopeId = "data-v-2d0cf945"; 293 | 294 | export { script as Roulette }; 295 | -------------------------------------------------------------------------------- /dist/vue3-roulette.min.js: -------------------------------------------------------------------------------- 1 | var vue3Roulette=function(e,t){"use strict";var n=t.defineComponent({name:"Roulette",emits:["wheelStart","wheelEnd"],props:{items:{type:Object,required:!0,validator:function(e){return e.length>=4}},firstItemIndex:{type:Object,required:!1,default:function(){return{value:0}}},wheelResultIndex:{type:Object,required:!1,default:function(){return{value:null}},validator:function(e){return"number"==typeof e.value}},centeredIndicator:{type:Boolean,required:!1,default:!1},indicatorPosition:{type:String,required:!1,default:"top",validator:function(e){return["top","right","bottom","left"].includes(e)}},size:{type:Number,required:!1,default:300},displayShadow:{type:Boolean,required:!1,default:!1},duration:{type:Number,required:!1,default:4},resultVariation:{type:Number,required:!1,default:0,validator:function(e){return e>=0&&e<=100}},easing:{type:String,required:!1,default:"ease",validator:function(e){return["ease","bounce"].includes(e)}},counterClockwise:{type:Boolean,required:!1,default:!1},horizontalContent:{type:Boolean,required:!1,default:!1},displayBorder:{type:Boolean,required:!1,default:!1},displayIndicator:{type:Boolean,required:!1,default:!0},baseDisplay:{type:Boolean,required:!1,default:!1},baseSize:{type:Number,required:!1,default:100},baseDisplayShadow:{type:Boolean,required:!1,default:!1},baseDisplayIndicator:{type:Boolean,required:!1,default:!1},baseBackground:{type:String,required:!1,default:""}},data:function(){return{randomIdRoulette:0,itemSelected:null,processingLock:!1}},computed:{itemAngle:function(){return 360/this.items.length},startingAngle:function(){return this.centeredIndicator?-1*this.firstItemIndex.value*this.itemAngle-this.itemAngle/2:-1*this.firstItemIndex.value*this.itemAngle},degreesVariation:function(){if(!this.resultVariation)return 0;var e=this.itemAngle/2*this.resultVariation/100*-1,t=this.itemAngle/2*this.resultVariation/100;return Number((Math.random()*(t-e)+e).toFixed(2))},counterClockWiseOperator:function(){return this.counterClockwise?-1:1}},mounted:function(){var e=this;this.randomIdRoulette=Number((999998*Math.random()+1).toFixed(0)),this.$nextTick((function(){e.reset(),document.querySelector("#wheel-container-".concat(e.randomIdRoulette," .wheel")).addEventListener("transitionend",(function(){e.processingLock=!1,e.$emit("wheel-end",e.itemSelected)}))}))},methods:{reset:function(){this.itemSelected=null,document.querySelector("#wheel-container-".concat(this.randomIdRoulette," .wheel")).style.transform="rotate(".concat(this.startingAngle,"deg)")},launchWheel:function(){if(!this.processingLock||null==this.itemSelected){var e;this.processingLock=!0,e=null!==this.wheelResultIndex.value?this.wheelResultIndex.value%this.items.length:Math.floor(Math.random()*this.items.length+1)-1;var t=document.querySelector("#wheel-container-".concat(this.randomIdRoulette," .wheel"));this.itemSelected=this.items[e],t.style.transform="rotate(".concat(1080*this.counterClockWiseOperator+-e*this.itemAngle-this.itemAngle/2+this.degreesVariation,"deg)"),this.$emit("wheel-start",this.itemSelected)}}}}),a=["id"],o={class:"wheel-base"},r={key:0,class:"wheel-base-indicator"},i=["innerHTML"];return function(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!=typeof document){var a=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css","top"===n&&a.firstChild?a.insertBefore(o,a.firstChild):a.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}('.wheel-container[data-v-2d0cf945],\n.wheel-base[data-v-2d0cf945],\n.wheel-base-container[data-v-2d0cf945],\n.wheel-base-indicator[data-v-2d0cf945] {\n transition: transform 1s ease-in-out;\n}\n.wheel-container[data-v-2d0cf945] {\n position: relative;\n display: inline-block;\n overflow: hidden;\n border-radius: 50%;\n cursor: pointer;\n}\n.wheel-container-indicator[data-v-2d0cf945]:before {\n content: "";\n position: absolute;\n z-index: 4;\n width: 0;\n height: 0;\n border-left: 20px solid transparent;\n border-right: 20px solid transparent;\n border-top: 20px solid black;\n transform: translateX(-50%);\n}\n.wheel-container.indicator-top[data-v-2d0cf945] {\n transform: rotate(0deg);\n}\n.wheel-container.indicator-right[data-v-2d0cf945] {\n transform: rotate(90deg);\n}\n.wheel-container.indicator-right .wheel-base[data-v-2d0cf945] {\n transform: rotate(-90deg);\n}\n.wheel-container.indicator-bottom[data-v-2d0cf945] {\n transform: rotate(180deg);\n}\n.wheel-container.indicator-bottom .wheel-base[data-v-2d0cf945] {\n transform: rotate(-180deg);\n}\n.wheel-container.indicator-left[data-v-2d0cf945] {\n transform: rotate(270deg);\n}\n.wheel-container.indicator-left .wheel-base[data-v-2d0cf945] {\n transform: rotate(-270deg);\n}\n.wheel-container-border[data-v-2d0cf945] {\n border: 8px solid black;\n}\n.wheel-container-shadow[data-v-2d0cf945] {\n box-shadow: 5px 5px 15px -5px #000000;\n}\n.wheel-base-container[data-v-2d0cf945] {\n position: absolute;\n z-index: 2;\n top: 50%;\n left: 50%;\n border-radius: 50%;\n border: 5px solid black;\n transform: translate(-50%, -50%);\n}\n.wheel-base-container-shadow[data-v-2d0cf945] {\n box-shadow: 5px 5px 15px -5px #000000;\n}\n.wheel-base-container .wheel-base[data-v-2d0cf945] {\n position: absolute;\n z-index: 2;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow: hidden;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n}\n.wheel-base-container .wheel-base-indicator[data-v-2d0cf945] {\n position: absolute;\n z-index: 1;\n width: 100%;\n height: 100%;\n}\n.wheel-base-container .wheel-base-indicator[data-v-2d0cf945]:before {\n content: "";\n position: absolute;\n z-index: 1;\n top: -20px;\n width: 0;\n height: 0;\n border-left: 20px solid transparent;\n border-right: 20px solid transparent;\n border-bottom: 20px solid black;\n transform: translateX(-50%);\n}\n.wheel[data-v-2d0cf945] {\n background: white;\n border-radius: 50%;\n margin: auto;\n overflow: hidden;\n}\n.wheel.easing-ease[data-v-2d0cf945] {\n transition: transform cubic-bezier(0.65, 0, 0.35, 1);\n}\n.wheel.easing-bounce[data-v-2d0cf945] {\n transition: transform cubic-bezier(0.49, 0.02, 0.52, 1.12);\n}\n.wheel-border[data-v-2d0cf945]:after {\n content: "";\n width: 100%;\n height: 100%;\n position: absolute;\n left: 0;\n top: 0;\n z-index: 3;\n border-radius: 50%;\n background-image: linear-gradient(to left, black 33%, rgba(255, 255, 255, 0) 0%);\n background-position: bottom;\n background-size: 3px 1px;\n /* background:linear-gradient(red,purple,orange); */\n -webkit-mask: radial-gradient(transparent 65%, #000 66%);\n mask: radial-gradient(transparent 65%, #000 66%);\n}\n.wheel-item[data-v-2d0cf945] {\n overflow: hidden;\n position: absolute;\n top: 0;\n right: 0;\n width: 50%;\n height: 50%;\n transform-origin: 0% 100%;\n border: 1px solid black;\n}\n.wheel-item[data-v-2d0cf945]:nth-child(odd) {\n background-color: skyblue;\n}\n.wheel-item[data-v-2d0cf945]:nth-child(even) {\n background-color: pink;\n}\n.wheel .content[data-v-2d0cf945] {\n position: absolute;\n left: -100%;\n width: 200%;\n height: 200%;\n text-align: center;\n transform: skewY(30deg) rotate(0deg);\n padding-top: 20px;\n}\n.wheel .content.horizontal-content[data-v-2d0cf945] {\n left: initial;\n right: 100%;\n width: 50%;\n height: 250%;\n text-align: right;\n}\n.wheel .content.horizontal-content span[data-v-2d0cf945] {\n display: block;\n transform: rotate(270deg);\n}'),n.render=function(e,n,d,l,s,c){return t.openBlock(),t.createElementBlock("div",{id:"wheel-container-".concat(e.randomIdRoulette),class:t.normalizeClass(["wheel-container",["indicator-".concat(e.indicatorPosition),{"wheel-container-indicator":e.displayIndicator},{"wheel-container-shadow":e.displayShadow},{"wheel-container-border":e.displayBorder}]])},[e.baseDisplay?(t.openBlock(),t.createElementBlock("div",{key:0,class:t.normalizeClass(["wheel-base-container",[{"wheel-base-container-shadow":e.baseDisplayShadow}]]),style:t.normalizeStyle({width:"".concat(e.baseSize,"px"),height:"".concat(e.baseSize,"px"),background:"".concat(e.baseBackground)})},[t.createElementVNode("div",o,[t.renderSlot(e.$slots,"baseContent")]),e.baseDisplayIndicator?(t.openBlock(),t.createElementBlock("div",r)):t.createCommentVNode("",!0)],6)):t.createCommentVNode("",!0),t.createElementVNode("div",{class:t.normalizeClass(["wheel",["easing-".concat(e.easing),{"wheel-border":e.displayBorder}]]),style:t.normalizeStyle({width:"".concat(e.size,"px"),height:"".concat(e.size,"px"),transitionDuration:"".concat(e.duration,"s"),transform:"rotate(".concat(e.startingAngle,"deg)")})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(e.items,(function(n,a){return t.openBlock(),t.createElementBlock("div",{key:n.id,class:"wheel-item",style:t.normalizeStyle({transform:"rotate(".concat(e.itemAngle*a,"deg) skewY(").concat(-(90-e.itemAngle),"deg)"),background:n.background})},[t.createElementVNode("div",{class:t.normalizeClass(["content",{"horizontal-content":e.horizontalContent}]),style:t.normalizeStyle({transform:"skewY(".concat(90-e.itemAngle,"deg) rotate(").concat(e.itemAngle/2,"deg)")})},[t.createElementVNode("span",{style:t.normalizeStyle({color:n.textColor}),innerHTML:n.htmlContent},null,12,i)],6)],4)})),128))],6)],10,a)},n.__scopeId="data-v-2d0cf945",e.Roulette=n,Object.defineProperty(e,"__esModule",{value:!0}),e}({},Vue); -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Vue3 Roulette 13 | 22 | 23 | 24 | 39 | 40 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | roulette.nitocode.com -------------------------------------------------------------------------------- /docs/assets/favicon.adef23e4.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitocode/vue3-roulette/c4c869f98f9e0e1ef614685b6cf2d47a1bd58294/docs/assets/favicon.adef23e4.ico -------------------------------------------------------------------------------- /docs/assets/index.019fedf3.css: -------------------------------------------------------------------------------- 1 | @import"https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.0.0/github-markdown-light.min.css";.item{display:flex;flex-direction:row;justify-content:center;align-items:center}.wheel-container[data-v-313a8203],.wheel-base[data-v-313a8203],.wheel-base-container[data-v-313a8203],.wheel-base-indicator[data-v-313a8203]{transition:transform 1s ease-in-out}.wheel-container[data-v-313a8203]{position:relative;display:inline-block;overflow:hidden;border-radius:50%;cursor:pointer}.wheel-container-indicator[data-v-313a8203]:before{content:"";position:absolute;z-index:4;width:0;height:0;border-left:20px solid transparent;border-right:20px solid transparent;border-top:20px solid black;transform:translate(-50%)}.wheel-container.indicator-top[data-v-313a8203]{transform:rotate(0)}.wheel-container.indicator-right[data-v-313a8203]{transform:rotate(90deg)}.wheel-container.indicator-right .wheel-base[data-v-313a8203]{transform:rotate(-90deg)}.wheel-container.indicator-bottom[data-v-313a8203]{transform:rotate(180deg)}.wheel-container.indicator-bottom .wheel-base[data-v-313a8203]{transform:rotate(-180deg)}.wheel-container.indicator-left[data-v-313a8203]{transform:rotate(270deg)}.wheel-container.indicator-left .wheel-base[data-v-313a8203]{transform:rotate(-270deg)}.wheel-container-border[data-v-313a8203]{border:8px solid black}.wheel-container-shadow[data-v-313a8203]{box-shadow:5px 5px 15px -5px #000}.wheel-base-container[data-v-313a8203]{position:absolute;z-index:2;top:50%;left:50%;border-radius:50%;border:5px solid black;transform:translate(-50%,-50%)}.wheel-base-container-shadow[data-v-313a8203]{box-shadow:5px 5px 15px -5px #000}.wheel-base-container .wheel-base[data-v-313a8203]{position:absolute;z-index:2;display:flex;justify-content:center;align-items:center;overflow:hidden;width:100%;height:100%;border-radius:50%}.wheel-base-container .wheel-base-indicator[data-v-313a8203]{position:absolute;z-index:1;width:100%;height:100%}.wheel-base-container .wheel-base-indicator[data-v-313a8203]:before{content:"";position:absolute;z-index:1;top:-20px;width:0;height:0;border-left:20px solid transparent;border-right:20px solid transparent;border-bottom:20px solid black;transform:translate(-50%)}.wheel[data-v-313a8203]{background:white;border-radius:50%;margin:auto;overflow:hidden}.wheel.easing-ease[data-v-313a8203]{transition:transform cubic-bezier(.65,0,.35,1)}.wheel.easing-bounce[data-v-313a8203]{transition:transform cubic-bezier(.49,.02,.52,1.12)}.wheel-border[data-v-313a8203]:after{content:"";width:100%;height:100%;position:absolute;left:0;top:0;z-index:3;border-radius:50%;background-image:linear-gradient(to left,black 33%,rgba(255,255,255,0) 0%);background-position:bottom;background-size:3px 1px;-webkit-mask:radial-gradient(transparent 65%,#000 66%);mask:radial-gradient(transparent 65%,#000 66%)}.wheel-item[data-v-313a8203]{overflow:hidden;position:absolute;top:0;right:0;width:50%;height:50%;transform-origin:0% 100%;border:1px solid black}.wheel-item[data-v-313a8203]:nth-child(odd){background-color:#87ceeb}.wheel-item[data-v-313a8203]:nth-child(even){background-color:pink}.wheel .content[data-v-313a8203]{position:absolute;left:-100%;width:200%;height:200%;text-align:center;transform:skewY(30deg) rotate(0);padding-top:20px}.wheel .content.horizontal-content[data-v-313a8203]{left:initial;right:100%;width:50%;height:250%;text-align:right}.wheel .content.horizontal-content span[data-v-313a8203]{display:block;transform:rotate(270deg)}.wheel-anim{transition:transform 4s cubic-bezier(.58,-.26,.24,1.11);transform:rotate(-1800deg) scale(1.25)}.wheel-anim-started{transform:rotate(0) scale(1)}[data-v-65fe6d42] img{display:none}@media screen and (max-width: 450px){[data-v-2a21abdc] .big-wheel .relative{transform:scale(.7)}}.drawer-toggle:checked~.drawer-content{z-index:100}.drawer-toggle:checked~.drawer-side{z-index:101}.fade-enter-active,.fade-leave-active{transition:opacity .5s ease}.fade-enter-from,.fade-leave-to{opacity:0} 2 | -------------------------------------------------------------------------------- /docs/assets/index.58c620c4.js: -------------------------------------------------------------------------------- 1 | var L=Object.defineProperty,T=Object.defineProperties;var Y=Object.getOwnPropertyDescriptors;var M=Object.getOwnPropertySymbols;var J=Object.prototype.hasOwnProperty,K=Object.prototype.propertyIsEnumerable;var U=(t,o,s)=>o in t?L(t,o,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[o]=s,S=(t,o)=>{for(var s in o||(o={}))J.call(o,s)&&U(t,s,o[s]);if(M)for(var s of M(o))K.call(o,s)&&U(t,s,o[s]);return t},B=(t,o)=>T(t,Y(o));import{o as r,c as h,a as e,b as g,F as I,r as A,w as m,v as G,d as C,e as y,f as q,n as f,g as Z,h as k,i as Q,j as b,k as v,l as w,m as $,p as D,q as N,s as p,t as X,u as ee,x as W,y as te,z as ne,T as oe,A as se,B as ae}from"./vendor.aed0c264.js";const ie=function(){const o=document.createElement("link").relList;if(o&&o.supports&&o.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))l(n);new MutationObserver(n=>{for(const i of n)if(i.type==="childList")for(const a of i.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&l(a)}).observe(document,{childList:!0,subtree:!0});function s(n){const i={};return n.integrity&&(i.integrity=n.integrity),n.referrerpolicy&&(i.referrerPolicy=n.referrerpolicy),n.crossorigin==="use-credentials"?i.credentials="include":n.crossorigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function l(n){if(n.ep)return;n.ep=!0;const i=s(n);fetch(n.href,i)}};ie();var _=(t,o)=>{for(const[s,l]of o)t[s]=l;return t};const le={name:"ItemsManager",emits:["updateItems"],props:{initialFirstItemIndex:{type:Object,required:!1,default:null},initialItems:{type:Object,required:!0}},data(){return{firstItemIndex:null,items:[]}},mounted(){this.items=this.initialItems,this.firstItemIndex=this.initialFirstItemIndex},methods:{add(){const t=this.items[this.items.length-1];this.items.push({id:t.id+1,name:"",htmlContent:"",textColor:"",background:""})},remove(t){this.items.length<5||this.items.splice(t,1)},removeAll(){this.items=[]},update(){this.$emit("update-items",this.items)}}},re={class:"table w-full"},de={class:"text-center"},ce={key:0},ue=e("th",null,"Name",-1),he=e("th",null,"Html content",-1),me=e("th",null,"Text color",-1),ge=e("th",null,"Background",-1),pe=e("th",null,"Delete",-1),be={key:0},fe=["value"],we=["onUpdate:modelValue"],Ce=["onUpdate:modelValue"],_e=["onUpdate:modelValue"],ye=["onUpdate:modelValue"],ve={class:"text-center"},ke=["onClick"],xe=e("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",class:"inline-block w-4 h-4 stroke-current"},[e("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1),Se=[xe],Ie={class:"text-center"},De={key:0},ze=e("th",null,"Name",-1),Ee=e("th",null,"Html content",-1),Be=e("th",null,"Text color",-1),Ae=e("th",null,"Background",-1),$e=e("th",null,"Delete",-1),Ne=e("div",{class:"divider"},null,-1);function Ve(t,o,s,l,n,i){return r(),h("div",null,[e("table",re,[e("thead",de,[e("tr",null,[s.initialFirstItemIndex!=null?(r(),h("th",ce," First Item ")):g("",!0),ue,he,me,ge,pe])]),e("tbody",null,[(r(!0),h(I,null,A(n.items,(a,d)=>(r(),h("tr",{key:a.id},[s.initialFirstItemIndex!=null?(r(),h("td",be,[m(e("input",{"onUpdate:modelValue":o[0]||(o[0]=u=>n.firstItemIndex.value=u),name:"firstItem",class:"block mx-auto radio radio-primary",type:"radio",value:d},null,8,fe),[[G,n.firstItemIndex.value]])])):g("",!0),e("td",null,[m(e("input",{"onUpdate:modelValue":u=>a.name=u,class:"input input-bordered w-full",type:"text"},null,8,we),[[C,a.name]])]),e("td",null,[m(e("textarea",{"onUpdate:modelValue":u=>a.htmlContent=u,class:"textarea h-12 textarea-bordered textarea-primary w-full"},null,8,Ce),[[C,a.htmlContent]])]),e("td",null,[m(e("input",{"onUpdate:modelValue":u=>a.textColor=u,class:"input input-bordered w-full",type:"text"},null,8,_e),[[C,a.textColor]])]),e("td",null,[m(e("input",{"onUpdate:modelValue":u=>a.background=u,class:"input input-bordered w-full",type:"text"},null,8,ye),[[C,a.background]])]),e("td",ve,[e("button",{class:"btn btn-error btn-outline btn-circle btn-sm",onClick:u=>i.remove(d)},Se,8,ke)])]))),128))]),e("tfoot",Ie,[e("tr",null,[s.initialFirstItemIndex!=null?(r(),h("th",De," First Item ")):g("",!0),ze,Ee,Be,Ae,$e])])]),Ne,e("div",null,[e("button",{class:"btn btn-primary",onClick:o[1]||(o[1]=(...a)=>i.add&&i.add(...a))}," Add Item ")])])}var V=_(le,[["render",Ve]]);const Re={name:"WheelManager",props:{initialSettings:{type:Object,required:!0}},data(){return{settings:{}}},mounted(){this.settings=this.initialSettings}},He={class:"flex flex-col lg:flex-row items-top justify-around"},Me=e("p",{class:"text-xl mb-4"}," Wheel manager ",-1),Ue={class:"form-control"},qe={class:"cursor-pointer label"},We=e("span",{class:"label-text"},"Display Shadow",-1),Pe={class:"form-control"},Fe={class:"cursor-pointer label"},Oe=e("span",{class:"label-text"},"Display Border",-1),je={class:"form-control"},Le={class:"cursor-pointer label"},Te=e("span",{class:"label-text"},"Display Indicator",-1),Ye={class:"form-control"},Je={class:"cursor-pointer label"},Ke=e("span",{class:"label-text"},"Centered indicator",-1),Ge={class:"form-control"},Ze={class:"cursor-pointer label"},Qe=e("span",{class:"label-text"},"Counter clockwise",-1),Xe={class:"form-control"},et={class:"cursor-pointer label"},tt=e("span",{class:"label-text"},"Horizontal content",-1),nt={class:"form-control"},ot=e("label",{class:"label"},[e("span",{class:"label-text"},"Size")],-1),st={class:"input-group input-group-md"},at=e("span",null,"px",-1),it={class:"form-control"},lt=e("label",{class:"label"},[e("span",{class:"label-text"},"Duration")],-1),rt={class:"input-group input-group-md"},dt=e("span",null,"\xA0s\xA0",-1),ct={class:"form-control"},ut=e("label",{class:"label"},[e("span",{class:"label-text"},"Variation")],-1),ht={class:"input-group input-group-md"},mt=e("span",null,"%\xA0",-1),gt={class:"form-control w-full max-w-xs"},pt=e("label",{for:"position",class:"label"},[e("span",{class:"label-text"},"Indicator position")],-1),bt=e("option",{value:"top"}," Top ",-1),ft=e("option",{value:"right"}," Right ",-1),wt=e("option",{value:"bottom"}," Bottom ",-1),Ct=e("option",{value:"left"}," Left ",-1),_t=[bt,ft,wt,Ct],yt={class:"form-control w-full max-w-xs"},vt=e("label",{for:"easing",class:"label"},[e("span",{class:"label-text"},"Easing")],-1),kt=e("option",{value:"ease"}," Ease ",-1),xt=e("option",{value:"bounce"}," Bounce ",-1),St=[kt,xt],It=e("div",{class:"divider lg:divider-vertical"},null,-1),Dt=e("p",{class:"text-xl mb-4"}," Wheel base manager ",-1),zt={class:"form-control"},Et={class:"cursor-pointer label"},Bt=e("span",{class:"label-text"},"Display base",-1),At={class:"form-control"},$t={class:"cursor-pointer label"},Nt=e("span",{class:"label-text"},"Display base shadow",-1),Vt={class:"form-control"},Rt={class:"cursor-pointer label"},Ht=e("span",{class:"label-text"},"Display base indicator",-1),Mt={class:"form-control"},Ut=e("label",{class:"label"},[e("span",{class:"label-text"},"Base size")],-1),qt={class:"input-group input-group-md"},Wt=e("span",null,"px",-1),Pt={class:"form-control"},Ft=e("label",{class:"label"},[e("span",{class:"label-text"},"Base background")],-1),Ot={class:"form-control"},jt=e("label",{class:"label"},[e("span",{class:"label-text"},"Base HTML Content")],-1);function Lt(t,o,s,l,n,i){return r(),h("div",He,[e("div",null,[Me,e("div",Ue,[e("label",qe,[We,m(e("input",{id:"shadow","onUpdate:modelValue":o[0]||(o[0]=a=>n.settings.displayShadow=a),class:"toggle toggle-primary",type:"checkbox"},null,512),[[y,n.settings.displayShadow]])])]),e("div",Pe,[e("label",Fe,[Oe,m(e("input",{id:"border","onUpdate:modelValue":o[1]||(o[1]=a=>n.settings.displayBorder=a),class:"toggle toggle-primary",type:"checkbox"},null,512),[[y,n.settings.displayBorder]])])]),e("div",je,[e("label",Le,[Te,m(e("input",{id:"indicator","onUpdate:modelValue":o[2]||(o[2]=a=>n.settings.displayIndicator=a),class:"toggle toggle-primary",type:"checkbox"},null,512),[[y,n.settings.displayIndicator]])])]),e("div",Ye,[e("label",Je,[Ke,m(e("input",{id:"centered","onUpdate:modelValue":o[3]||(o[3]=a=>n.settings.centeredIndicator=a),type:"checkbox",class:"checkbox checkbox-primary"},null,512),[[y,n.settings.centeredIndicator]])])]),e("div",Ge,[e("label",Ze,[Qe,m(e("input",{id:"clock","onUpdate:modelValue":o[4]||(o[4]=a=>n.settings.counterClockwise=a),type:"checkbox",class:"checkbox checkbox-primary"},null,512),[[y,n.settings.counterClockwise]])])]),e("div",Xe,[e("label",et,[tt,m(e("input",{id:"horizontal","onUpdate:modelValue":o[5]||(o[5]=a=>n.settings.horizontalContent=a),type:"checkbox",class:"checkbox checkbox-primary"},null,512),[[y,n.settings.horizontalContent]])])]),e("div",nt,[ot,e("label",st,[m(e("input",{id:"size","onUpdate:modelValue":o[6]||(o[6]=a=>n.settings.size=a),class:"input input-primary input-bordered input-md w-full",type:"number"},null,512),[[C,n.settings.size]]),at])]),e("div",it,[lt,e("label",rt,[m(e("input",{id:"duration","onUpdate:modelValue":o[7]||(o[7]=a=>n.settings.duration=a),class:"input input-primary input-bordered input-md w-full",type:"number"},null,512),[[C,n.settings.duration]]),dt])]),e("div",ct,[ut,e("label",ht,[m(e("input",{id:"variation","onUpdate:modelValue":o[8]||(o[8]=a=>n.settings.resultVariation=a),class:"input input-primary input-bordered input-md w-full",type:"number"},null,512),[[C,n.settings.resultVariation]]),mt]),m(e("input",{"onUpdate:modelValue":o[9]||(o[9]=a=>n.settings.resultVariation=a),type:"range",max:"100",class:"range range-primary mt-2"},null,512),[[C,n.settings.resultVariation]])]),e("div",gt,[pt,m(e("select",{id:"position","onUpdate:modelValue":o[10]||(o[10]=a=>n.settings.indicatorPosition=a),name:"position",class:"select select-bordered select-primary w-full max-w-xs"},_t,512),[[q,n.settings.indicatorPosition]])]),e("div",yt,[vt,m(e("select",{id:"easing","onUpdate:modelValue":o[11]||(o[11]=a=>n.settings.easing=a),name:"easing",class:"select select-bordered select-primary w-full max-w-xs"},St,512),[[q,n.settings.easing]])])]),It,e("div",null,[Dt,e("div",zt,[e("label",Et,[Bt,m(e("input",{id:"base","onUpdate:modelValue":o[12]||(o[12]=a=>n.settings.baseDisplay=a),class:"toggle toggle-primary",type:"checkbox"},null,512),[[y,n.settings.baseDisplay]])])]),e("div",{class:f({"opacity-50":!n.settings.baseDisplay})},[e("div",At,[e("label",$t,[Nt,m(e("input",{id:"baseShadow","onUpdate:modelValue":o[13]||(o[13]=a=>n.settings.baseDisplayShadow=a),class:"toggle toggle-primary",type:"checkbox"},null,512),[[y,n.settings.baseDisplayShadow]])])]),e("div",Vt,[e("label",Rt,[Ht,m(e("input",{id:"baseIndicator","onUpdate:modelValue":o[14]||(o[14]=a=>n.settings.baseDisplayIndicator=a),class:"toggle toggle-primary",type:"checkbox"},null,512),[[y,n.settings.baseDisplayIndicator]])])]),e("div",Mt,[Ut,e("label",qt,[m(e("input",{id:"baseSize","onUpdate:modelValue":o[15]||(o[15]=a=>n.settings.baseSize=a),class:"input input-primary input-bordered input-md w-full",type:"number"},null,512),[[C,n.settings.baseSize]]),Wt])]),e("div",Pt,[Ft,m(e("input",{id:"baseBackground","onUpdate:modelValue":o[16]||(o[16]=a=>n.settings.baseBackground=a),class:"input input-primary input-bordered",type:"text"},null,512),[[C,n.settings.baseBackground]])]),e("div",Ot,[jt,m(e("textarea",{"onUpdate:modelValue":o[17]||(o[17]=a=>n.settings.baseHtmlContent=a),class:"textarea h-24 textarea-bordered textarea-primary"},null,512),[[C,n.settings.baseHtmlContent]])])],2)])])}var R=_(Re,[["render",Lt]]);const Tt=Z({name:"Roulette",emits:["wheelStart","wheelEnd"],props:{items:{type:Object,required:!0,validator(t){return t.length>=4}},firstItemIndex:{type:Object,required:!1,default(){return{value:0}}},wheelResultIndex:{type:Object,required:!1,default(){return{value:null}},validator(t){return typeof t.value=="number"}},centeredIndicator:{type:Boolean,required:!1,default:!1},indicatorPosition:{type:String,required:!1,default:"top",validator(t){return["top","right","bottom","left"].includes(t)}},size:{type:Number,required:!1,default:300},displayShadow:{type:Boolean,required:!1,default:!1},duration:{type:Number,required:!1,default:4},resultVariation:{type:Number,required:!1,default:0,validator(t){return t>=0&&t<=100}},easing:{type:String,required:!1,default:"ease",validator(t){return["ease","bounce"].includes(t)}},counterClockwise:{type:Boolean,required:!1,default:!1},horizontalContent:{type:Boolean,required:!1,default:!1},displayBorder:{type:Boolean,required:!1,default:!1},displayIndicator:{type:Boolean,required:!1,default:!0},baseDisplay:{type:Boolean,required:!1,default:!1},baseSize:{type:Number,required:!1,default:100},baseDisplayShadow:{type:Boolean,required:!1,default:!1},baseDisplayIndicator:{type:Boolean,required:!1,default:!1},baseBackground:{type:String,required:!1,default:""}},data(){return{randomIdRoulette:0,itemSelected:null,processingLock:!1}},computed:{itemAngle:function(){return 360/this.items.length},startingAngle:function(){return this.centeredIndicator?-1*this.firstItemIndex.value*this.itemAngle-this.itemAngle/2:-1*this.firstItemIndex.value*this.itemAngle},degreesVariation:function(){if(!this.resultVariation)return 0;const t=this.itemAngle/2*this.resultVariation/100*-1,o=this.itemAngle/2*this.resultVariation/100;return Number((Math.random()*(o-t)+t).toFixed(2))},counterClockWiseOperator:function(){return this.counterClockwise?-1:1}},mounted(){this.randomIdRoulette=Number((Math.random()*(999999-1)+1).toFixed(0)),this.$nextTick(()=>{this.reset(),document.querySelector(`#wheel-container-${this.randomIdRoulette} .wheel`).addEventListener("transitionend",()=>{this.processingLock=!1,this.$emit("wheel-end",this.itemSelected)})})},methods:{reset(){this.itemSelected=null,document.querySelector(`#wheel-container-${this.randomIdRoulette} .wheel`).style.transform=`rotate(${this.startingAngle}deg)`},launchWheel(){if(this.processingLock&&this.itemSelected!=null)return;this.processingLock=!0;let t;this.wheelResultIndex.value!==null?t=this.wheelResultIndex.value%this.items.length:t=Math.floor(Math.random()*this.items.length+1)-1;const o=document.querySelector(`#wheel-container-${this.randomIdRoulette} .wheel`);this.itemSelected=this.items[t],o.style.transform=`rotate(${this.counterClockWiseOperator*(360*3)+-t*this.itemAngle-this.itemAngle/2+this.degreesVariation}deg)`,this.$emit("wheel-start",this.itemSelected)}}}),Yt=["id"],Jt={class:"wheel-base"},Kt={key:0,class:"wheel-base-indicator"},Gt=["innerHTML"];function Zt(t,o,s,l,n,i){return r(),h("div",{id:`wheel-container-${t.randomIdRoulette}`,class:f(["wheel-container",[`indicator-${t.indicatorPosition}`,{"wheel-container-indicator":t.displayIndicator},{"wheel-container-shadow":t.displayShadow},{"wheel-container-border":t.displayBorder}]])},[t.baseDisplay?(r(),h("div",{key:0,class:f(["wheel-base-container",[{"wheel-base-container-shadow":t.baseDisplayShadow}]]),style:k({width:`${t.baseSize}px`,height:`${t.baseSize}px`,background:`${t.baseBackground}`})},[e("div",Jt,[Q(t.$slots,"baseContent",{},void 0,!0)]),t.baseDisplayIndicator?(r(),h("div",Kt)):g("",!0)],6)):g("",!0),e("div",{class:f(["wheel",[`easing-${t.easing}`,{"wheel-border":t.displayBorder}]]),style:k({width:`${t.size}px`,height:`${t.size}px`,transitionDuration:`${t.duration}s`,transform:`rotate(${t.startingAngle}deg)`})},[(r(!0),h(I,null,A(t.items,(a,d)=>(r(),h("div",{key:a.id,class:"wheel-item",style:k({transform:`rotate(${t.itemAngle*d}deg) skewY(${-(90-t.itemAngle)}deg)`,background:a.background})},[e("div",{class:f(["content",{"horizontal-content":t.horizontalContent}]),style:k({transform:`skewY(${90-t.itemAngle}deg) rotate(${t.itemAngle/2}deg)`})},[e("span",{style:k({color:a.textColor}),innerHTML:a.htmlContent},null,12,Gt)],6)],4))),128))],6)],10,Yt)}var H=_(Tt,[["render",Zt],["__scopeId","data-v-313a8203"]]),P={items:[{id:1,name:"Banana",htmlContent:"Banana",textColor:"",background:""},{id:2,name:"Apple",htmlContent:"Apple",textColor:"",background:""},{id:3,name:"Orange",htmlContent:"Orange",textColor:"",background:""},{id:4,name:"Cherry",htmlContent:"Cherry",textColor:"",background:""},{id:5,name:"Strawberry",htmlContent:"Strawberry",textColor:"",background:""},{id:6,name:"Grape",htmlContent:"Grape",textColor:"",background:""}],firstItemIndex:{value:0},wheelSettings:{centeredIndicator:!0,indicatorPosition:"top",size:300,displayShadow:!0,duration:5,resultVariation:70,easing:"bounce",counterClockwise:!0,horizontalContent:!1,displayBorder:!0,displayIndicator:!0,baseDisplay:!0,baseSize:100,baseDisplayShadow:!0,baseDisplayIndicator:!0,baseBackground:"#EEAA33",baseHtmlContent:"Touch
Me!
"}};const Qt={name:"Home",components:{Roulette:H,ItemsManager:V,WheelManager:R},data(){return B(S({},P),{wheelActive:!0,startAnim:!1,managerId:1,result:null})},mounted(){setTimeout(()=>{this.startAnim=!0},500)},methods:{launchWheel(){this.$refs.wheel.launchWheel()},wheelStartedCallback(t){console.log("wheel started !",t)},wheelEndedCallback(t){console.log("wheel ended !",t),this.result=t},onSoftReset(t){this.items=t||this.items,this.$refs.wheel.reset()},onHardReset(){this.wheelActive=!1,this.result=null,setTimeout(()=>{this.wheelActive=!0},10)}}},Xt=e("h1",{class:"text-4xl"}," Vue3 Roulette ",-1),en={class:"py-10 relative"},tn=["innerHTML"],nn={class:"absolute bottom-2 left-1/2 transform -translate-x-1/2"},on=e("p",{class:"text-xl text-gray-500 italic mb-10"}," A customizable and flexible fortune wheel made with vue3 ",-1),sn={class:"tabs tabs-boxed justify-center"},an=e("div",{class:"divider"},null,-1);function ln(t,o,s,l,n,i){const a=b("Roulette"),d=b("ItemsManager"),u=b("WheelManager");return r(),h("div",null,[Xt,e("div",en,[e("div",{class:f(["wheel-anim",{"wheel-anim-started":n.startAnim}])},[n.wheelActive?(r(),v(a,{key:0,ref:"wheel",items:t.items,"first-item-index":t.firstItemIndex,"centered-indicator":t.wheelSettings.centeredIndicator,"indicator-position":t.wheelSettings.indicatorPosition,size:t.wheelSettings.size,"display-shadow":t.wheelSettings.displayShadow,"display-border":t.wheelSettings.displayBorder,"display-indicator":t.wheelSettings.displayIndicator,duration:t.wheelSettings.duration,"result-variation":t.wheelSettings.resultVariation,easing:t.wheelSettings.easing,"counter-clockwise":t.wheelSettings.counterClockwise,"horizontal-content":t.wheelSettings.horizontalContent,"base-display":t.wheelSettings.baseDisplay,"base-size":t.wheelSettings.baseSize,"base-display-indicator":t.wheelSettings.baseDisplayIndicator,"base-display-shadow":t.wheelSettings.baseDisplayShadow,"base-background":t.wheelSettings.baseBackground,onClick:i.launchWheel,onWheelStart:i.wheelStartedCallback,onWheelEnd:i.wheelEndedCallback},{baseContent:w(()=>[t.wheelSettings.baseHtmlContent?(r(),h("div",{key:0,innerHTML:t.wheelSettings.baseHtmlContent},null,8,tn)):g("",!0)]),_:1},8,["items","first-item-index","centered-indicator","indicator-position","size","display-shadow","display-border","display-indicator","duration","result-variation","easing","counter-clockwise","horizontal-content","base-display","base-size","base-display-indicator","base-display-shadow","base-background","onClick","onWheelStart","onWheelEnd"])):g("",!0)],2),m(e("div",nn,[e("button",{class:"btn btn-xs mx-2",onClick:o[0]||(o[0]=c=>i.onHardReset())}," Hard reset "),e("button",{class:"btn btn-xs mx-2",onClick:o[1]||(o[1]=c=>i.onSoftReset())}," Soft reset ")],512),[[$,n.result]])]),on,e("div",sn,[e("a",{class:f(["tab",{"tab-active":n.managerId===1}]),onClick:o[2]||(o[2]=c=>n.managerId=1)},"Items manager",2),e("a",{class:f(["tab",{"tab-active":n.managerId===2}]),onClick:o[3]||(o[3]=c=>n.managerId=2)},"Wheel manager",2)]),an,n.managerId===1?(r(),v(d,{key:0,class:"item-manager overflow-scroll lg:overflow-auto","initial-items":t.items,"initial-first-item-index":t.firstItemIndex,onUpdateItems:i.onSoftReset},null,8,["initial-items","initial-first-item-index","onUpdateItems"])):g("",!0),n.managerId===2?(r(),v(u,{key:1,"initial-settings":t.wheelSettings,onHardReset:i.onHardReset},null,8,["initial-settings","onHardReset"])):g("",!0)])}var rn=_(Qt,[["render",ln]]);const dn=D('

Vue3 Roulette

A customizable and flexible fortune wheel for vue3

Demo

https://roulette.nitocode.com/

See also: codesandbox template

Installation

Using npm

npm i --save vue3-roulette

Usage

main.js

',10),cn=e("pre",null,[e("code",{class:"language-js","v-pre":"true"},`import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { Roulette } from 'vue3-roulette' 4 | 5 | createApp(App).component("roulette", Roulette).mount('#app') 6 | `)],-1),un=e("h4",null,"vuejs html template",-1),hn=e("pre",null,[e("code",{class:"language-html","v-pre":"true"},` 7 | `)],-1),mn=e("h4",null,"vuejs script",-1),gn=e("p",null,[N("Using the "),e("a",{href:"https://v3.vuejs.org/api/sfc-script-setup.html"},"sfc syntax")],-1),pn=e("pre",null,[e("code",{class:"language-html","v-pre":"true"},` 30 | 31 | 32 | 33 | 34 |
35 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitocode/vue3-roulette/c4c869f98f9e0e1ef614685b6cf2d47a1bd58294/docs/og-image.jpg -------------------------------------------------------------------------------- /docs/roulette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitocode/vue3-roulette/c4c869f98f9e0e1ef614685b6cf2d47a1bd58294/docs/roulette.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitocode/vue3-roulette/c4c869f98f9e0e1ef614685b6cf2d47a1bd58294/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Vue3 Roulette 20 | 29 | 30 | 31 |
32 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: true, 3 | collectCoverageFrom: [ 4 | '/src/**/*.{js,vue}', 5 | '!/src/index.js', 6 | '!/pages/**/*' 7 | ], 8 | moduleFileExtensions: [ 'js', 'json', 'vue' ], 9 | transform: { 10 | '.*\\.(vue)$': 'vue-jest', 11 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 12 | '^.+\\.js$': 'babel-jest' 13 | }, 14 | moduleNameMapper: { 15 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/tests/mocks/file-mock.js' 16 | }, 17 | transformIgnorePatterns: [], 18 | snapshotSerializers: [ 19 | 'jest-serializer-vue' 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-roulette", 3 | "version": "0.3.3", 4 | "main": "dist/vue3-roulette.js", 5 | "browser": "dist/vue3-roulette.esm.js", 6 | "module": "dist/vue3-roulette.esm.js", 7 | "unpkg": "dist/vue3-roulette.min.js", 8 | "scripts": { 9 | "serve": "vite", 10 | "prebuild": "rimraf ./dist", 11 | "build": "cross-env NODE_ENV=production rollup --config rollup.config.js", 12 | "buildpages": "vite build", 13 | "test": "cross-env NODE_ENV=test jest" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.15.5", 17 | "@babel/preset-env": "^7.15.6", 18 | "@rollup/plugin-babel": "^5.3.0", 19 | "@rollup/plugin-buble": "^0.21.3", 20 | "@rollup/plugin-commonjs": "^20.0.0", 21 | "@rollup/plugin-node-resolve": "^13.0.4", 22 | "@rollup/plugin-replace": "^3.0.0", 23 | "@types/jest": "^26.0.22", 24 | "@vitejs/plugin-vue": "^1.3.0", 25 | "@vue/cli-plugin-babel": "^4.5.4", 26 | "@vue/cli-plugin-eslint": "^4.5.4", 27 | "@vue/cli-service": "^4.5.4", 28 | "@vue/compiler-sfc": "^3.1.5", 29 | "@vue/eslint-config-standard": "^4.0.0", 30 | "@vue/test-utils": "^1.0.4", 31 | "babel-core": "^6.26.3", 32 | "babel-eslint": "^10.0.3", 33 | "babel-jest": "^25.5.1", 34 | "babel-preset-env": "^1.7.0", 35 | "cross-env": "^7.0.3", 36 | "eslint": "^6.8.0", 37 | "eslint-plugin-vue": "^5.2.3", 38 | "jest": "^25.5.4", 39 | "jest-serializer-vue": "^2.0.2", 40 | "jest-transform-stub": "^2.0.0", 41 | "postcss": "^8.3.6", 42 | "rimraf": "^3.0.2", 43 | "rollup": "^2.55.1", 44 | "rollup-plugin-postcss": "^4.0.0", 45 | "rollup-plugin-svg": "^2.0.0", 46 | "rollup-plugin-terser": "^7.0.2", 47 | "rollup-plugin-vue": "^6.0.0", 48 | "sass": "^1.37.2", 49 | "sass-loader": "^8.0.0", 50 | "vite": "^2.4.4", 51 | "vite-plugin-markdown": "^2.0.2", 52 | "vue": "^3.2.11", 53 | "vue-jest": "^5.0.0-alpha.10", 54 | "vue-router": "^4.0.12" 55 | }, 56 | "peerDependencies": { 57 | "vue": "^3.2.11" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pages/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 34 | 35 | 47 | -------------------------------------------------------------------------------- /pages/components/Example.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 164 | 165 | -------------------------------------------------------------------------------- /pages/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 51 | 52 | -------------------------------------------------------------------------------- /pages/components/ItemsManager.vue: -------------------------------------------------------------------------------- 1 |