@konbraphat51/affectiveslidervue 0.0.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 ADDED
@@ -0,0 +1,274 @@
1
+ # Affective Slider Vue
2
+
3
+ A Vue 3 component implementation of the Affective Slider (AS) for measuring pleasure and arousal in emotion assessment.
4
+
5
+ **[Live Demo](https://konbraphat51.github.io/AffectiveSliderVue/)** | [npm Package](https://www.npmjs.com/package/affectiveslidervue)
6
+
7
+ ## About
8
+
9
+ The Affective Slider (AS) is a digital scale for the self-assessment of emotion composed of two slider controls that measure:
10
+ - **Pleasure** (sad - happy)
11
+ - **Arousal** (sleepy - wide awake)
12
+
13
+ This component is based on the original [Affective Slider](https://github.com/albertobeta/AffectiveSlider) by Alberto Betella and Paul F.M.J. Verschure.
14
+
15
+ The AS has been empirically validated and presented in the following open access scientific publication:
16
+
17
+ > Alberto Betella and Paul F.M.J. Verschure, "[The Affective Slider: A Digital Self-Assessment Scale for the Measurement of Human Emotions](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0148037)", PLoS ONE, vol. 11, p. e0148037, 2016. DOI: 10.1371/journal.pone.0148037
18
+
19
+ ## Features
20
+
21
+ - ✅ Vue 3 compatible
22
+ - ✅ Fully responsive design
23
+ - ✅ Randomizable slider order (to prevent bias)
24
+ - ✅ Interaction tracking
25
+ - ✅ Touch and mouse support
26
+ - ✅ Customizable initial values
27
+ - ✅ Event emissions for value changes
28
+ - ✅ Follows original design guidelines
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pnpm add affectiveslidervue
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Basic Usage
39
+
40
+ ```vue
41
+ <template>
42
+ <div>
43
+ <AffectiveSlider
44
+ @update:pleasureValue="handlePleasureChange"
45
+ @update:arousalValue="handleArousalChange"
46
+ @change="handleChange"
47
+ />
48
+ </div>
49
+ </template>
50
+
51
+ <script>
52
+ import AffectiveSlider from 'affectiveslidervue'
53
+
54
+ export default {
55
+ components: {
56
+ AffectiveSlider
57
+ },
58
+ methods: {
59
+ handlePleasureChange(value) {
60
+ console.log('Pleasure:', value)
61
+ },
62
+ handleArousalChange(value) {
63
+ console.log('Arousal:', value)
64
+ },
65
+ handleChange(values) {
66
+ console.log('Both values:', values)
67
+ }
68
+ }
69
+ }
70
+ </script>
71
+ ```
72
+
73
+ ### With v-model
74
+
75
+ ```vue
76
+ <template>
77
+ <AffectiveSlider
78
+ :pleasure-value="pleasure"
79
+ :arousal-value="arousal"
80
+ @update:pleasureValue="pleasure = $event"
81
+ @update:arousalValue="arousal = $event"
82
+ />
83
+ </template>
84
+
85
+ <script>
86
+ export default {
87
+ data() {
88
+ return {
89
+ pleasure: 0.5,
90
+ arousal: 0.5
91
+ }
92
+ }
93
+ }
94
+ </script>
95
+ ```
96
+
97
+ ### With Labels
98
+
99
+ ```vue
100
+ <template>
101
+ <AffectiveSlider
102
+ pleasure-left-label="Sad"
103
+ pleasure-right-label="Happy"
104
+ arousal-left-label="Sleepy"
105
+ arousal-right-label="Awake"
106
+ />
107
+ </template>
108
+
109
+ <script>
110
+ import AffectiveSlider from 'affectiveslidervue'
111
+
112
+ export default {
113
+ components: {
114
+ AffectiveSlider
115
+ }
116
+ }
117
+ </script>
118
+ ```
119
+
120
+ ## Props
121
+
122
+ | Prop | Type | Default | Description |
123
+ |------|------|---------|-------------|
124
+ | `pleasureValue` | Number | 0.5 | Initial value for pleasure slider (0-1) |
125
+ | `arousalValue` | Number | 0.5 | Initial value for arousal slider (0-1) |
126
+ | `randomizeOrder` | Boolean | true | Randomize the order of sliders to prevent bias |
127
+ | `imagePath` | String | '/images/' | Base path for slider images |
128
+ | `pleasureLeftLabel` | String | '' | Text label below left icon (sad face) of pleasure slider |
129
+ | `pleasureRightLabel` | String | '' | Text label below right icon (happy face) of pleasure slider |
130
+ | `arousalLeftLabel` | String | '' | Text label below left icon (sleepy face) of arousal slider |
131
+ | `arousalRightLabel` | String | '' | Text label below right icon (awake face) of arousal slider |
132
+
133
+ ## Events
134
+
135
+ | Event | Payload | Description |
136
+ |-------|---------|-------------|
137
+ | `update:pleasureValue` | Number | Emitted when pleasure value changes |
138
+ | `update:arousalValue` | Number | Emitted when arousal value changes |
139
+ | `change` | { pleasure: Number, arousal: Number } | Emitted when any value changes |
140
+ | `interacted` | { type: String, pleasure: Number, arousal: Number } | Emitted on first interaction with each slider |
141
+
142
+ ## Setup for Images
143
+
144
+ ### Option 1: Using Images from npm Package (Recommended)
145
+
146
+ When you install the package via npm, the required images are automatically included in `node_modules/affectiveslidervue/dist/images/`.
147
+
148
+ To make them accessible to your application, you have two options:
149
+
150
+ **A. Copy images to your public directory (recommended for most cases)**
151
+
152
+ Install a cross-platform copy utility and add a postinstall script to your `package.json`:
153
+
154
+ ```bash
155
+ # Install cross-platform copy utility
156
+ npm install --save-dev cpx2
157
+ ```
158
+
159
+ ```json
160
+ {
161
+ "scripts": {
162
+ "postinstall": "cpx \"node_modules/affectiveslidervue/dist/images/*\" public/images"
163
+ }
164
+ }
165
+ ```
166
+
167
+ **Or copy manually:**
168
+
169
+ ```bash
170
+ # Unix/Mac/Linux
171
+ cp -r node_modules/affectiveslidervue/dist/images/* public/images/
172
+
173
+ # Windows (PowerShell)
174
+ Copy-Item -Path "node_modules/affectiveslidervue/dist/images/*" -Destination "public/images/" -Recurse
175
+
176
+ # Windows (Command Prompt)
177
+ xcopy /E /I node_modules\affectiveslidervue\dist\images public\images
178
+ ```
179
+
180
+ **B. Use a custom imagePath**
181
+
182
+ If you're using a bundler like Vite or Webpack, you can configure it to serve images from node_modules:
183
+
184
+ ```vue
185
+ <AffectiveSlider
186
+ image-path="/node_modules/affectiveslidervue/dist/images/"
187
+ />
188
+ ```
189
+
190
+ ### Option 2: Manual Download
191
+
192
+ Download the image files from the [PNGs directory](https://github.com/konbraphat51/AffectiveSliderVue/tree/main/PNGs) and place them in your `public/images/` directory.
193
+
194
+ Required images:
195
+ - `AS_happy.png`
196
+ - `AS_unhappy.png`
197
+ - `AS_sleepy.png`
198
+ - `AS_wideawake.png`
199
+ - `AS_intensity_cue.png`
200
+
201
+ ### Custom Image Location
202
+
203
+ If your images are in a different location, use the `imagePath` prop:
204
+
205
+ ```vue
206
+ <AffectiveSlider
207
+ image-path="/assets/affective-slider/"
208
+ />
209
+ ```
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ # Install dependencies
215
+ pnpm install
216
+
217
+ # Run development server
218
+ pnpm dev
219
+
220
+ # Build component for production (library)
221
+ pnpm build
222
+
223
+ # Build demo for GitHub Pages
224
+ pnpm run build:demo
225
+ ```
226
+
227
+ ### GitHub Pages Demo
228
+
229
+
230
+ The `/docs` folder contains a built version of the demo application for GitHub Pages deployment.
231
+
232
+ **Live Demo**: https://konbraphat51.github.io/AffectiveSliderVue/
233
+
234
+ To rebuild the demo:
235
+ ```bash
236
+ pnpm run build:demo
237
+ ```
238
+
239
+ To publish on GitHub Pages:
240
+
241
+ 1. Push the repository to GitHub.
242
+ 2. Go to the repository Settings → Pages.
243
+ 3. Under "Build and deployment" choose "Branch: main" and folder `/docs`.
244
+ 4. Save — the site will be published from the `docs/` folder.
245
+
246
+ Note: The demo build uses a relative base (`base: './'`) so assets work correctly when served from `/docs`.
247
+
248
+ ## Publishing
249
+
250
+ For maintainers: See [PUBLISHING.md](./PUBLISHING.md) for instructions on publishing this package to npm.
251
+
252
+ ## Design Guidelines
253
+
254
+ This component follows the official design guidelines:
255
+
256
+ - Both sliders have horizontal orientation
257
+ - Sliders are presented simultaneously with thumbs at center (0.5)
258
+ - Proper spacing and sizing for easy interaction
259
+ - Intensity cue with emoticons at extremities
260
+ - Greyscale color palette
261
+ - Values range from 0 to 1 with 0.01 resolution
262
+ - Thumbs are circular and larger than track
263
+ - Order can be randomized to prevent bias
264
+ - Visual feedback on interaction
265
+
266
+ ## License
267
+
268
+ CC-BY-SA-4.0 - Same as the original Affective Slider
269
+
270
+ ## Credits
271
+
272
+ Original Affective Slider by Alberto Betella and Paul F.M.J. Verschure
273
+
274
+ Vue component implementation: This project
@@ -0,0 +1,128 @@
1
+ import { ref as c, computed as R, onMounted as q, watch as Q, createElementBlock as o, openBlock as g, Fragment as h, renderList as D, normalizeClass as I, createElementVNode as r, createCommentVNode as w, toDisplayString as m, unref as S } from "vue";
2
+ const y = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABLBJREFUeNrsnP1x2yAUwHGu/5cR2KBsEG1QbVBtUG1QOoFHIJ3A6QR2J5AzgdIJlE7gWnfQUAKIjweyHd4dl5x9Ruj3Png8kDan0wlV8ZNNhVVhVVgVVoVVYVVYLlibzab0eIhoUp5FKyZOHivBmoE053av/L8kBwHu6dwec0F0etr8pa1lALQ9t3G+LEAbRX8UGpatlbCs2Wq+LViPdLcXYTlSPp0bNrinyep+nNvDtVrWDGdvsYrh3Jin++l9MvF7m7U1uSwrB6zZErjlRtiChYS6dW9x613sdUrCmrU6GSB1mSexzgBtHkd7qbCYYbCscOrRG5S1vTRY3BCTCFpHsCFW7sXnq8LCIj5EazKjMIMC8ZqwdIvq0GVJp7nlIrBcsC4dlBSqAeOlYfWRoChAxi1TExwITB0vKwWLRoJqFQ33CbC44lI00CXVcTclYA2+Jq0kjztAl+0ME4qvlW21/A/nhMWWLqZB4tDLEcW6Ry2n457pyuCauaFgYS1QNg5321sSVAwcvJkhCZXrTuoZRkgOWNzhftRSfpnE5zkTVOxYI05CcfqadGu7FwhY2KGNzrJo7jNYko972mpmjcNLCCQs5rAqrMWNRgvsUrttBoviAsxgSAeIuCYT1yeO+MshYY02H1c0qg90MmiXAYIy1bR4YB+qu4LAotqCNGYZdFqAnbruO3lMPEvjbCFgbSPyI1ftvAeANTj6D7GuVv9dKix1YNgzyLpgQbiiq/99ZF/TEqw7D7+W8egoNhR8YsE1ycFwr0a587ASvVPfi+eU58jvTPLLMVEFwVKD5VPAAB4d3z0AwHr0vPlQ5ZL4yuD/s07ILGOLW1BVVGxJTYaIvojy+11KgN8HBnfdKseM5WaqjY+j+Hj5b3KAgnXL4gXrDlXxlgqrwqqwLh7W0ZJz3ZJgKFh/rngZE5KCeCW0IZZF3wGsl5QMniSs5q9F1G26JrVEM954Yqoum5LrWW+qiTfmguppQSePDx4d/kSvFdLPCyv+WCHIfMT7WbRcZZ8vQdUKzw2LKWFB7dKq71HvSWi+zeiCGMINdVdkwNWCmPPvENA63QWhYFFNy7HWxRDMAwPyBlOsfDTV6qC27/cJ1mU6TmnaaNiKvmXbWYp8qpXRRKvae4elAFiNZl0kYHDccbOdh4W0DtedAoE5D7hAHjniEUlqj+D2DxuLpYWEhp3rHiBh6VrpA7UYYwmmPgcUt1/YLXkH9Mm/Vhsk9Rwc5I60DRhbmIUXFZ3jAC73tJTeEKMgE1n9FDJxwB1NqUIJWLpml1wLi3gDXblo0fLxSH2szrPwuc7BhwJbQ0joGHM+YaHHgagnsTIukqdQZeZ+0IkYgi1bGVQfOwOXeCrM9CTWgMrX7allHN7LopIPZ26R+XAZKRCbuOXaQevH0o/9NpayC89gadQCKTp2loYl3ZJZlibyFQNNAiCG7EclWUo1Ys1XFcgD/V8dN3AQ1dDf6O0bQmQF9aOA5AL8cG7fUeLLMS7hJRhYLH0GwHqWDN6gDyes/RIMUzCeLeRe/A0J/rPVHEW9PMsrVi7xXTSmSUHGI9VKXtDrRu+hxECiYVWpsCqsCqvCqrDehfwVYACpkF1ChK49LwAAAABJRU5ErkJggg==", F = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABKpJREFUeNrsnP11mzAQwOW+/l82KJmgzgRRJwidoOoGdIIqE/hlArcT0A1IJnAyAekEeIPUvHck6kUfB5IwUN17evazAYkfd6fTSWLz/PzMktDkXUKQYCVYCVaClWD99/Le9udms4lZ9xZKfipXym8ZOu4OPh9O5RE+H2I1yhpKdX+aSgQpTmV/Km1XtUdp4TpFDFimsrFBCaRZnaaUp/IVtMgmT1B6yQnnHE/l56nconMXpVkdJGnRjhr+5xrT012Lw/GV5Zp7AtzRmhULVmkwte5GBQEO1aQrg4nKJcDKQWOCP3FHnXtNnQfoMGYJq9BoUxUREgVaC5o8K1hC08iCnUc639ag9si5wNprHHfGziuZxp/tzw1rN6ZBE8rg9sWCJWYOytROOTUsvhBQJmDFVLAy1OvNHZTOJFtTLx0aVoVimSVJjTqiqLAKytOZsWCrKGPBylD8Ui40LYUfeBYDlnSp8IKkMvWOIWBh9eULh5WbtCsELLEirdKNPGRIWIcVaZVOu5pQsLa6i65EaqwENh6U2R1Vk25XBuuX8v3adTAlB18rwC4YLc+tztx0x/+cedzFoJ0Xvjl4qgn2OfdGk7KJKWqdDXvN7VMDZtUf5z4+ixPHgIKZp7diwxLMPinigqaOGQsfWCUhYtcl/wSbNp1cwoMVcPMNo6eW1Yhe+sCSjpBBziSVTNF2YfGvL9bjA0sdFuSWnFbDRsykTKR1B0Ie68UqfGCpcQiWRtGoOYJSO56DI1NCgjV2Fc1WqfSGRVyoEUC66f1v8Hn08qUempUvLE3DmXnWKboZrkmimqGvD1mkuGA9IT8VAlQNDpePPL+AkIWy8oZ6XBCf5YqzVH9QOirmqBsfNK2Ogsc+XBGaOjP4ndpbqyHQzsdnCceNFQhAC7GZVMpeM14cE3JkGtjqtWrL/w1xuOQVwavRbeV4KkPK2CDWBsxUDo5wQR2ucd/kX6s8PVPMVTP6WlAZwMkLArQDoy03UrXee01ppQwTLi0B6BaO+6SBcQ/n/Y7Qs/a5s77OfjUzJe+WKybanXPpm89SbXq3svjqTVbF1wwzZEZrkgYnCkLM7lSEVMfShDPNmo0QsDhb3wxPrVOAUNP3zYq0y/jwQ8EqUAXZSnyViAELq+5Se0ZpcykhYW1R4McXBsrZ/tAr/yRb5oI2vL5sNzjuHLkA94C63SX4L1KbY2yhy5CT74YKnyHH7SO5QVOfmN/2uL3iyI/Q1gcTLJ/hjs3+2wAaxiHopWzYrAb6yYy9nQTmo/N7njssdMDygaDGpHgKIiicmXDGh7H37mBgQ2amxUhYJeEhtENBTQFLB6zvbShmKdmwPdM7hzbtfDKyU+2RzjQ+pXOm3xltfRZXzv3IXtd2/YHf7sApHy1a+gO5ge74L0M6h6n3SEtDGllECjGEIccvx1zsHHukc0OqOdQrBmyvPKiZx7TdOV9VwME0TN11b1qPiqncofPV+OvKcq3uOje+qes5vASDszAvvzC9niDYGPXcL8HAnUB3Y9fwOWZc+QTadw9adAzZQCuPiWHp4KmzMx+Qv8G9oe+wJx6sJP9KeiVUgpVgJVgJVoKV5K8AAwDfyJ7liahBeQAAAABJRU5ErkJggg==", N = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABa5JREFUeNrsnEFv5DQUxz2rip4qRaKn5YA5LbcNEnDdcAIuSz5C+gm2+wk2/QQpn2C6n2DKHqAcUKZCtBJbadLuhb3shAMjLSqakXoqlxKPnNY17yX2jJ3xRHmSpZHi2s7P//f8nMbp3dzckM7UrNfB6mB1sDpYHawOVgerg9XBah5Wr9ez3b/PCy3K46J4SL28KH8WJeMltzkolAm7gBULxmBERRkUZcq6X7CMi9IvSmgLFsijIVg+v7llAGFlytum6w4rKEpacZNMYTGv59fADnndtAKcEWhNw/L4wDEVmHCfEOmDlaQi/jkFKwTcbcpV4VmKgzHSZ+AqLEhNNiFh0CCVOQWLDXQkDTI1GXQ1FxN5LAOdCbMJCwK160AOKatspArMFiwZ1LRmVWvaIimWKQGzAct1UKJbysAah5UqgqJ8VQosgFBtWwbWbxJWrAAq5tsS04mjt2DbgVQ3agKWL3UaKgR8qESGVjoo8cXa3pXqUduwRjU5jAooDHSdonT2lphrDqT0xhqsWNr5ezXLdRlQ44rtj2r+09dse6wIPbIBS+4EmrkpEEOqAq1qTkYV3BhqO6pIKVCoJmDFUkZcF0BTxUA7UoAV10wCBAEbJ7SaR6ZhibNGa4JnnWLklazOBlL9qnxOdSICTF3LwooUFBMrBtjSZXTzLtWcKtWYiBE03mVhDRRWsICryVYCqvvQMVBYbSPItZeB5UmrV5sMvLdlYIWq24Q1tYHsihiPBwqNPRF+/9BCWMeS++pvGgVliQHTayEsX041lnHDuoy4DXbvHheFRRVShjaY6D0LxyyK+HbbbIbc8z17QDpjdq4Ca8OAr6+jLfTGS6csDdsw0UiSJMT3fadv9Pj4mMRxvHpYlFISBIHzsLhli7axrBvOOz4/P3d/uZvNoJWvtMfC73xRWDmy7bl3Pcsy52EJY8yQDXUtrA0NWBRZcsOT01OS7H/vNKzhcAilCfKeMF82Zg15Y5TPgCjjw6K8+Pv9e3J6cko+3N52ElQ+Hsv3I+8NleKZSszKKnblWQnv7ds/nFXVePxOHG+OqApTnRYscZvzHXD9UBqQU/bv9TXJ81tlvQSqPJHvZRlYomyhx7TzYHV1deWkui7eXJDrAhgCwxPuaWbCDWdCJx4ALCuBnr3+3TlVvbm49awDwAVDVVXp5FniE9JnwPW9Ul1nZ6+dgcXGIqhqD6jyQvj90hSsA2EVDIA0YsjrzNX1z+XlykFNJn+RiztV7SGBnQopw9BkBi8mUtA/Lp6XQI+Ofpy7wKqMKfzno5/EXHG/RlV7So8qNM7usHglvgzyFTAbbLbmT1S3i5zr6dOQfLC52XicevXqkFzeqfszIHBHwoQzmJ+IFzEmOsqaSTPQJ///B8aQK2w+WDboJhUGgNoBQLExJ5JHENPKKm0kZL37SGd9PnuNKYzFyTT9RQZ1AFQdCKvgkHsIUVHWIrDKt+/qBnULbLMA9fU335KHDz+ytp1hoISVDxvTrqCqGXfR3CYsZrEQIGd8dqCETowN5NGjT8nnX3xJtra2jAXyk99+LXYPYzFUPEdA3cbTGqDGYclyrgLm87rUFDTmciwtkHYMGRKjyjGkQow94HVJU7A8PgBfAVipxmfiosDiGQPHnlbUuSjLmyaTSeFy78S4JC48+8ifyqAy7n5EF5aNExZBTf2YIC/SFvBuCmj3SqG+qtOsMal+pSAijpywwICpvidanhfUeQNZ57hvQgyf3TF1oFx2yXJZ3iFqh78puXuzD3vykSu2VR4x9qVN8g6Bn7835oZQurDK84YJoMZYt6E2n2SlSBwcE4dPssqzjJ1fNnFGunyuNiBrfkYaWraxgJ1yVYSk+nXtgLT49D3kKja/65CQFnzXAXKfyAC4lXwxZNXfoqHk7ns0H1eog6UMK/8WTfeVIw3rYHWwOlgdrHWy/wQYAM+kf0tZT+XwAAAAAElFTkSuQmCC", J = "data:image/png;base64,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", U = "data:image/png;base64,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", P = (t, i) => {
3
+ const a = t.__vccOpts || t;
4
+ for (const [l, s] of i)
5
+ a[l] = s;
6
+ return a;
7
+ }, H = { class: "affective-slider" }, b = { class: "as-icon-wrapper as-icon-left" }, G = ["src", "alt"], T = {
8
+ key: 0,
9
+ class: "as-icon-label"
10
+ }, Y = ["name", "id", "value", "onInput", "onMousedown", "onTouchstart"], X = { class: "as-icon-wrapper as-icon-right" }, Z = ["src", "alt"], k = {
11
+ key: 0,
12
+ class: "as-icon-label"
13
+ }, z = { class: "as-intensity-cue" }, M = ["src"], O = {
14
+ __name: "AffectiveSlider",
15
+ props: {
16
+ // Initial value for pleasure slider (0-1)
17
+ pleasureValue: {
18
+ type: Number,
19
+ default: 0.5,
20
+ validator: (t) => t >= 0 && t <= 1
21
+ },
22
+ // Initial value for arousal slider (0-1)
23
+ arousalValue: {
24
+ type: Number,
25
+ default: 0.5,
26
+ validator: (t) => t >= 0 && t <= 1
27
+ },
28
+ // Randomize the order of sliders
29
+ randomizeOrder: {
30
+ type: Boolean,
31
+ default: !0
32
+ },
33
+ // Label below left icon for pleasure slider (unhappy face)
34
+ pleasureLeftLabel: {
35
+ type: String,
36
+ default: ""
37
+ },
38
+ // Label below right icon for pleasure slider (happy face)
39
+ pleasureRightLabel: {
40
+ type: String,
41
+ default: ""
42
+ },
43
+ // Label below left icon for arousal slider (sleepy face)
44
+ arousalLeftLabel: {
45
+ type: String,
46
+ default: ""
47
+ },
48
+ // Label below right icon for arousal slider (wide awake face)
49
+ arousalRightLabel: {
50
+ type: String,
51
+ default: ""
52
+ }
53
+ },
54
+ emits: ["update:pleasureValue", "update:arousalValue", "change", "interacted"],
55
+ setup(t, { emit: i }) {
56
+ const a = t, l = i, s = c(a.pleasureValue), n = c(a.arousalValue), u = c([]), C = c({
57
+ pleasure: !1,
58
+ arousal: !1
59
+ }), d = (A) => A === "pleasure" ? y : F, E = (A) => A === "pleasure" ? N : J, L = (A) => A === "pleasure" ? a.pleasureLeftLabel : a.arousalLeftLabel, f = (A) => A === "pleasure" ? a.pleasureRightLabel : a.arousalRightLabel, v = R(() => u.value.map((A) => ({
60
+ type: A,
61
+ value: A === "pleasure" ? s.value : n.value,
62
+ leftImage: d(A),
63
+ rightImage: E(A),
64
+ leftLabel: L(A),
65
+ rightLabel: f(A)
66
+ }))), W = (A, V) => {
67
+ const e = parseFloat(V.target.value);
68
+ A === "pleasure" ? s.value = e : n.value = e, l(`update:${A}Value`, e), l("change", { pleasure: s.value, arousal: n.value });
69
+ }, B = (A) => {
70
+ C.value[A] || (C.value[A] = !0, l("interacted", { type: A, pleasure: s.value, arousal: n.value }));
71
+ }, K = () => {
72
+ const A = ["arousal", "pleasure"];
73
+ a.randomizeOrder ? u.value = Math.random() > 0.5 ? [...A] : [...A].reverse() : u.value = ["pleasure", "arousal"];
74
+ };
75
+ return q(() => {
76
+ K();
77
+ }), Q(() => a.pleasureValue, (A) => {
78
+ s.value = A;
79
+ }), Q(() => a.arousalValue, (A) => {
80
+ n.value = A;
81
+ }), (A, V) => (g(), o("div", H, [
82
+ (g(!0), o(h, null, D(v.value, (e) => (g(), o("div", {
83
+ key: e.type,
84
+ class: I(["as-container", e.type])
85
+ }, [
86
+ r("div", b, [
87
+ r("img", {
88
+ src: e.leftImage,
89
+ alt: `${e.type} left`,
90
+ class: "as-icon"
91
+ }, null, 8, G),
92
+ e.leftLabel ? (g(), o("div", T, m(e.leftLabel), 1)) : w("", !0)
93
+ ]),
94
+ r("input", {
95
+ type: "range",
96
+ name: `AS-${e.type}`,
97
+ id: `AS-${e.type}`,
98
+ value: e.value,
99
+ min: "0",
100
+ max: "1",
101
+ step: "0.01",
102
+ onInput: (p) => W(e.type, p),
103
+ onMousedown: (p) => B(e.type),
104
+ onTouchstart: (p) => B(e.type),
105
+ class: "as-slider"
106
+ }, null, 40, Y),
107
+ r("div", X, [
108
+ r("img", {
109
+ src: e.rightImage,
110
+ alt: `${e.type} right`,
111
+ class: "as-icon"
112
+ }, null, 8, Z),
113
+ e.rightLabel ? (g(), o("div", k, m(e.rightLabel), 1)) : w("", !0)
114
+ ]),
115
+ r("div", z, [
116
+ r("img", {
117
+ src: S(U),
118
+ alt: "intensity cue"
119
+ }, null, 8, M)
120
+ ])
121
+ ], 2))), 128))
122
+ ]));
123
+ }
124
+ }, j = /* @__PURE__ */ P(O, [["__scopeId", "data-v-7a449788"]]);
125
+ export {
126
+ j as AffectiveSlider,
127
+ j as default
128
+ };
@@ -0,0 +1 @@
1
+ (function(r,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(r=typeof globalThis<"u"?globalThis:r||self,e(r.AffectiveSliderVue={},r.Vue))})(this,(function(r,e){"use strict";const V="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABLBJREFUeNrsnP1x2yAUwHGu/5cR2KBsEG1QbVBtUG1QOoFHIJ3A6QR2J5AzgdIJlE7gWnfQUAKIjweyHd4dl5x9Ruj3Png8kDan0wlV8ZNNhVVhVVgVVoVVYVVYLlibzab0eIhoUp5FKyZOHivBmoE053av/L8kBwHu6dwec0F0etr8pa1lALQ9t3G+LEAbRX8UGpatlbCs2Wq+LViPdLcXYTlSPp0bNrinyep+nNvDtVrWDGdvsYrh3Jin++l9MvF7m7U1uSwrB6zZErjlRtiChYS6dW9x613sdUrCmrU6GSB1mSexzgBtHkd7qbCYYbCscOrRG5S1vTRY3BCTCFpHsCFW7sXnq8LCIj5EazKjMIMC8ZqwdIvq0GVJp7nlIrBcsC4dlBSqAeOlYfWRoChAxi1TExwITB0vKwWLRoJqFQ33CbC44lI00CXVcTclYA2+Jq0kjztAl+0ME4qvlW21/A/nhMWWLqZB4tDLEcW6Ry2n457pyuCauaFgYS1QNg5321sSVAwcvJkhCZXrTuoZRkgOWNzhftRSfpnE5zkTVOxYI05CcfqadGu7FwhY2KGNzrJo7jNYko972mpmjcNLCCQs5rAqrMWNRgvsUrttBoviAsxgSAeIuCYT1yeO+MshYY02H1c0qg90MmiXAYIy1bR4YB+qu4LAotqCNGYZdFqAnbruO3lMPEvjbCFgbSPyI1ftvAeANTj6D7GuVv9dKix1YNgzyLpgQbiiq/99ZF/TEqw7D7+W8egoNhR8YsE1ycFwr0a587ASvVPfi+eU58jvTPLLMVEFwVKD5VPAAB4d3z0AwHr0vPlQ5ZL4yuD/s07ILGOLW1BVVGxJTYaIvojy+11KgN8HBnfdKseM5WaqjY+j+Hj5b3KAgnXL4gXrDlXxlgqrwqqwLh7W0ZJz3ZJgKFh/rngZE5KCeCW0IZZF3wGsl5QMniSs5q9F1G26JrVEM954Yqoum5LrWW+qiTfmguppQSePDx4d/kSvFdLPCyv+WCHIfMT7WbRcZZ8vQdUKzw2LKWFB7dKq71HvSWi+zeiCGMINdVdkwNWCmPPvENA63QWhYFFNy7HWxRDMAwPyBlOsfDTV6qC27/cJ1mU6TmnaaNiKvmXbWYp8qpXRRKvae4elAFiNZl0kYHDccbOdh4W0DtedAoE5D7hAHjniEUlqj+D2DxuLpYWEhp3rHiBh6VrpA7UYYwmmPgcUt1/YLXkH9Mm/Vhsk9Rwc5I60DRhbmIUXFZ3jAC73tJTeEKMgE1n9FDJxwB1NqUIJWLpml1wLi3gDXblo0fLxSH2szrPwuc7BhwJbQ0joGHM+YaHHgagnsTIukqdQZeZ+0IkYgi1bGVQfOwOXeCrM9CTWgMrX7allHN7LopIPZ26R+XAZKRCbuOXaQevH0o/9NpayC89gadQCKTp2loYl3ZJZlibyFQNNAiCG7EclWUo1Ys1XFcgD/V8dN3AQ1dDf6O0bQmQF9aOA5AL8cG7fUeLLMS7hJRhYLH0GwHqWDN6gDyes/RIMUzCeLeRe/A0J/rPVHEW9PMsrVi7xXTSmSUHGI9VKXtDrRu+hxECiYVWpsCqsCqvCqrDehfwVYACpkF1ChK49LwAAAABJRU5ErkJggg==",d="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABKpJREFUeNrsnP11mzAQwOW+/l82KJmgzgRRJwidoOoGdIIqE/hlArcT0A1IJnAyAekEeIPUvHck6kUfB5IwUN17evazAYkfd6fTSWLz/PzMktDkXUKQYCVYCVaClWD99/Le9udms4lZ9xZKfipXym8ZOu4OPh9O5RE+H2I1yhpKdX+aSgQpTmV/Km1XtUdp4TpFDFimsrFBCaRZnaaUp/IVtMgmT1B6yQnnHE/l56nconMXpVkdJGnRjhr+5xrT012Lw/GV5Zp7AtzRmhULVmkwte5GBQEO1aQrg4nKJcDKQWOCP3FHnXtNnQfoMGYJq9BoUxUREgVaC5o8K1hC08iCnUc639ag9si5wNprHHfGziuZxp/tzw1rN6ZBE8rg9sWCJWYOytROOTUsvhBQJmDFVLAy1OvNHZTOJFtTLx0aVoVimSVJjTqiqLAKytOZsWCrKGPBylD8Ui40LYUfeBYDlnSp8IKkMvWOIWBh9eULh5WbtCsELLEirdKNPGRIWIcVaZVOu5pQsLa6i65EaqwENh6U2R1Vk25XBuuX8v3adTAlB18rwC4YLc+tztx0x/+cedzFoJ0Xvjl4qgn2OfdGk7KJKWqdDXvN7VMDZtUf5z4+ixPHgIKZp7diwxLMPinigqaOGQsfWCUhYtcl/wSbNp1cwoMVcPMNo6eW1Yhe+sCSjpBBziSVTNF2YfGvL9bjA0sdFuSWnFbDRsykTKR1B0Ie68UqfGCpcQiWRtGoOYJSO56DI1NCgjV2Fc1WqfSGRVyoEUC66f1v8Hn08qUempUvLE3DmXnWKboZrkmimqGvD1mkuGA9IT8VAlQNDpePPL+AkIWy8oZ6XBCf5YqzVH9QOirmqBsfNK2Ogsc+XBGaOjP4ndpbqyHQzsdnCceNFQhAC7GZVMpeM14cE3JkGtjqtWrL/w1xuOQVwavRbeV4KkPK2CDWBsxUDo5wQR2ucd/kX6s8PVPMVTP6WlAZwMkLArQDoy03UrXee01ppQwTLi0B6BaO+6SBcQ/n/Y7Qs/a5s77OfjUzJe+WKybanXPpm89SbXq3svjqTVbF1wwzZEZrkgYnCkLM7lSEVMfShDPNmo0QsDhb3wxPrVOAUNP3zYq0y/jwQ8EqUAXZSnyViAELq+5Se0ZpcykhYW1R4McXBsrZ/tAr/yRb5oI2vL5sNzjuHLkA94C63SX4L1KbY2yhy5CT74YKnyHH7SO5QVOfmN/2uL3iyI/Q1gcTLJ/hjs3+2wAaxiHopWzYrAb6yYy9nQTmo/N7njssdMDygaDGpHgKIiicmXDGh7H37mBgQ2amxUhYJeEhtENBTQFLB6zvbShmKdmwPdM7hzbtfDKyU+2RzjQ+pXOm3xltfRZXzv3IXtd2/YHf7sApHy1a+gO5ge74L0M6h6n3SEtDGllECjGEIccvx1zsHHukc0OqOdQrBmyvPKiZx7TdOV9VwME0TN11b1qPiqncofPV+OvKcq3uOje+qes5vASDszAvvzC9niDYGPXcL8HAnUB3Y9fwOWZc+QTadw9adAzZQCuPiWHp4KmzMx+Qv8G9oe+wJx6sJP9KeiVUgpVgJVgJVoKV5K8AAwDfyJ7liahBeQAAAABJRU5ErkJggg==",f="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABa5JREFUeNrsnEFv5DQUxz2rip4qRaKn5YA5LbcNEnDdcAIuSz5C+gm2+wk2/QQpn2C6n2DKHqAcUKZCtBJbadLuhb3shAMjLSqakXoqlxKPnNY17yX2jJ3xRHmSpZHi2s7P//f8nMbp3dzckM7UrNfB6mB1sDpYHawOVgerg9XBah5Wr9ez3b/PCy3K46J4SL28KH8WJeMltzkolAm7gBULxmBERRkUZcq6X7CMi9IvSmgLFsijIVg+v7llAGFlytum6w4rKEpacZNMYTGv59fADnndtAKcEWhNw/L4wDEVmHCfEOmDlaQi/jkFKwTcbcpV4VmKgzHSZ+AqLEhNNiFh0CCVOQWLDXQkDTI1GXQ1FxN5LAOdCbMJCwK160AOKatspArMFiwZ1LRmVWvaIimWKQGzAct1UKJbysAah5UqgqJ8VQosgFBtWwbWbxJWrAAq5tsS04mjt2DbgVQ3agKWL3UaKgR8qESGVjoo8cXa3pXqUduwRjU5jAooDHSdonT2lphrDqT0xhqsWNr5ezXLdRlQ44rtj2r+09dse6wIPbIBS+4EmrkpEEOqAq1qTkYV3BhqO6pIKVCoJmDFUkZcF0BTxUA7UoAV10wCBAEbJ7SaR6ZhibNGa4JnnWLklazOBlL9qnxOdSICTF3LwooUFBMrBtjSZXTzLtWcKtWYiBE03mVhDRRWsICryVYCqvvQMVBYbSPItZeB5UmrV5sMvLdlYIWq24Q1tYHsihiPBwqNPRF+/9BCWMeS++pvGgVliQHTayEsX041lnHDuoy4DXbvHheFRRVShjaY6D0LxyyK+HbbbIbc8z17QDpjdq4Ca8OAr6+jLfTGS6csDdsw0UiSJMT3fadv9Pj4mMRxvHpYlFISBIHzsLhli7axrBvOOz4/P3d/uZvNoJWvtMfC73xRWDmy7bl3Pcsy52EJY8yQDXUtrA0NWBRZcsOT01OS7H/vNKzhcAilCfKeMF82Zg15Y5TPgCjjw6K8+Pv9e3J6cko+3N52ElQ+Hsv3I+8NleKZSszKKnblWQnv7ds/nFXVePxOHG+OqApTnRYscZvzHXD9UBqQU/bv9TXJ81tlvQSqPJHvZRlYomyhx7TzYHV1deWkui7eXJDrAhgCwxPuaWbCDWdCJx4ALCuBnr3+3TlVvbm49awDwAVDVVXp5FniE9JnwPW9Ul1nZ6+dgcXGIqhqD6jyQvj90hSsA2EVDIA0YsjrzNX1z+XlykFNJn+RiztV7SGBnQopw9BkBi8mUtA/Lp6XQI+Ofpy7wKqMKfzno5/EXHG/RlV7So8qNM7usHglvgzyFTAbbLbmT1S3i5zr6dOQfLC52XicevXqkFzeqfszIHBHwoQzmJ+IFzEmOsqaSTPQJ///B8aQK2w+WDboJhUGgNoBQLExJ5JHENPKKm0kZL37SGd9PnuNKYzFyTT9RQZ1AFQdCKvgkHsIUVHWIrDKt+/qBnULbLMA9fU335KHDz+ytp1hoISVDxvTrqCqGXfR3CYsZrEQIGd8dqCETowN5NGjT8nnX3xJtra2jAXyk99+LXYPYzFUPEdA3cbTGqDGYclyrgLm87rUFDTmciwtkHYMGRKjyjGkQow94HVJU7A8PgBfAVipxmfiosDiGQPHnlbUuSjLmyaTSeFy78S4JC48+8ifyqAy7n5EF5aNExZBTf2YIC/SFvBuCmj3SqG+qtOsMal+pSAijpywwICpvidanhfUeQNZ57hvQgyf3TF1oFx2yXJZ3iFqh78puXuzD3vykSu2VR4x9qVN8g6Bn7835oZQurDK84YJoMZYt6E2n2SlSBwcE4dPssqzjJ1fNnFGunyuNiBrfkYaWraxgJ1yVYSk+nXtgLT49D3kKja/65CQFnzXAXKfyAC4lXwxZNXfoqHk7ns0H1eog6UMK/8WTfeVIw3rYHWwOlgdrHWy/wQYAM+kf0tZT+XwAAAAAElFTkSuQmCC",m="data:image/png;base64,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",E="data:image/png;base64,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",Q=(l,c)=>{const t=l.__vccOpts||l;for(const[s,n]of c)t[s]=n;return t},w={class:"affective-slider"},L={class:"as-icon-wrapper as-icon-left"},W=["src","alt"],K={key:0,class:"as-icon-label"},R=["name","id","value","onInput","onMousedown","onTouchstart"],q={class:"as-icon-wrapper as-icon-right"},h=["src","alt"],N={key:0,class:"as-icon-label"},S={class:"as-intensity-cue"},D=["src"],p=Q({__name:"AffectiveSlider",props:{pleasureValue:{type:Number,default:.5,validator:l=>l>=0&&l<=1},arousalValue:{type:Number,default:.5,validator:l=>l>=0&&l<=1},randomizeOrder:{type:Boolean,default:!0},pleasureLeftLabel:{type:String,default:""},pleasureRightLabel:{type:String,default:""},arousalLeftLabel:{type:String,default:""},arousalRightLabel:{type:String,default:""}},emits:["update:pleasureValue","update:arousalValue","change","interacted"],setup(l,{emit:c}){const t=l,s=c,n=e.ref(t.pleasureValue),o=e.ref(t.arousalValue),i=e.ref([]),u=e.ref({pleasure:!1,arousal:!1}),y=A=>A==="pleasure"?V:d,I=A=>A==="pleasure"?f:m,F=A=>A==="pleasure"?t.pleasureLeftLabel:t.arousalLeftLabel,J=A=>A==="pleasure"?t.pleasureRightLabel:t.arousalRightLabel,U=e.computed(()=>i.value.map(A=>({type:A,value:A==="pleasure"?n.value:o.value,leftImage:y(A),rightImage:I(A),leftLabel:F(A),rightLabel:J(A)}))),P=(A,B)=>{const a=parseFloat(B.target.value);A==="pleasure"?n.value=a:o.value=a,s(`update:${A}Value`,a),s("change",{pleasure:n.value,arousal:o.value})},C=A=>{u.value[A]||(u.value[A]=!0,s("interacted",{type:A,pleasure:n.value,arousal:o.value}))},b=()=>{const A=["arousal","pleasure"];t.randomizeOrder?i.value=Math.random()>.5?[...A]:[...A].reverse():i.value=["pleasure","arousal"]};return e.onMounted(()=>{b()}),e.watch(()=>t.pleasureValue,A=>{n.value=A}),e.watch(()=>t.arousalValue,A=>{o.value=A}),(A,B)=>(e.openBlock(),e.createElementBlock("div",w,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(U.value,a=>(e.openBlock(),e.createElementBlock("div",{key:a.type,class:e.normalizeClass(["as-container",a.type])},[e.createElementVNode("div",L,[e.createElementVNode("img",{src:a.leftImage,alt:`${a.type} left`,class:"as-icon"},null,8,W),a.leftLabel?(e.openBlock(),e.createElementBlock("div",K,e.toDisplayString(a.leftLabel),1)):e.createCommentVNode("",!0)]),e.createElementVNode("input",{type:"range",name:`AS-${a.type}`,id:`AS-${a.type}`,value:a.value,min:"0",max:"1",step:"0.01",onInput:g=>P(a.type,g),onMousedown:g=>C(a.type),onTouchstart:g=>C(a.type),class:"as-slider"},null,40,R),e.createElementVNode("div",q,[e.createElementVNode("img",{src:a.rightImage,alt:`${a.type} right`,class:"as-icon"},null,8,h),a.rightLabel?(e.openBlock(),e.createElementBlock("div",N,e.toDisplayString(a.rightLabel),1)):e.createCommentVNode("",!0)]),e.createElementVNode("div",S,[e.createElementVNode("img",{src:e.unref(E),alt:"intensity cue"},null,8,D)])],2))),128))]))}},[["__scopeId","data-v-7a449788"]]);r.AffectiveSlider=p,r.default=p,Object.defineProperties(r,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -0,0 +1 @@
1
+ .affective-slider[data-v-7a449788]{width:100%;max-width:800px;margin:0 auto;padding:20px}.as-container[data-v-7a449788]{margin:3em 0;display:flex;align-items:center;justify-content:center;flex-direction:column;position:relative}.as-icon[data-v-7a449788]{width:60px;height:60px;object-fit:contain}.as-icon-wrapper[data-v-7a449788]{position:absolute;top:0;display:flex;flex-direction:column;align-items:center;gap:4px}.as-icon-left[data-v-7a449788]{left:0}.as-icon-right[data-v-7a449788]{right:0}.as-icon-label[data-v-7a449788]{font-size:12px;color:#333;text-align:center;white-space:nowrap;max-width:80px;overflow:hidden;text-overflow:ellipsis}.as-slider[data-v-7a449788]{-webkit-appearance:none;width:calc(100% - 140px);height:20px;background:#ddd;border-radius:10px;outline:none;margin:0 70px;cursor:pointer}.as-slider[data-v-7a449788]:focus{background:#ccc}.as-slider[data-v-7a449788]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:35px;height:35px;border-radius:50%;background:#fff;border:2px solid #505050;cursor:pointer;transition:all .2s ease}.as-slider[data-v-7a449788]::-webkit-slider-thumb:hover{background:#505050;border:2px solid white;box-shadow:0 0 12px #212121}.as-slider[data-v-7a449788]::-webkit-slider-thumb:active{background:#505050;border:2px solid white}.as-slider[data-v-7a449788]::-moz-range-thumb{width:35px;height:35px;border-radius:50%;background:#fff;border:2px solid #505050;cursor:pointer;transition:all .2s ease}.as-slider[data-v-7a449788]::-moz-range-thumb:hover{background:#505050;border:2px solid white;box-shadow:0 0 12px #212121}.as-slider[data-v-7a449788]::-moz-range-thumb:active{background:#505050;border:2px solid white}.as-slider[data-v-7a449788]::-moz-range-track{width:100%;height:20px;background:#ddd;border-radius:10px;border:none}.as-slider[data-v-7a449788]:focus::-moz-range-track{background:#ccc}.as-slider[data-v-7a449788]::-ms-track{width:100%;height:20px;background:transparent;border-color:transparent;border-width:16px 0;color:transparent}.as-slider[data-v-7a449788]::-ms-fill-lower{background:#ddd;border-radius:10px}.as-slider[data-v-7a449788]::-ms-fill-upper{background:#ddd;border-radius:10px}.as-slider[data-v-7a449788]::-ms-thumb{width:35px;height:35px;border-radius:50%;background:#fff;border:2px solid #505050;cursor:pointer}.as-slider[data-v-7a449788]::-ms-thumb:hover{background:#505050;border:2px solid white;box-shadow:0 0 12px #212121}.as-slider[data-v-7a449788]::-ms-thumb:active{background:#505050;border:2px solid white}.as-slider[data-v-7a449788]:focus::-ms-fill-lower{background:#ccc}.as-slider[data-v-7a449788]:focus::-ms-fill-upper{background:#ccc}.as-intensity-cue[data-v-7a449788]{width:calc(100% - 140px);margin:10px 70px 0}.as-intensity-cue img[data-v-7a449788]{width:100%;height:auto}@media(max-width:768px){.as-icon[data-v-7a449788]{width:40px;height:40px}.as-slider[data-v-7a449788]{width:calc(100% - 100px);margin:0 50px}.as-intensity-cue[data-v-7a449788]{width:calc(100% - 100px);margin:10px 50px 0}.as-icon-label[data-v-7a449788]{font-size:10px;max-width:60px}}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@konbraphat51/affectiveslidervue",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/affective-slider-vue.umd.js",
6
+ "module": "./dist/affective-slider-vue.es.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/affective-slider-vue.es.js",
10
+ "require": "./dist/affective-slider-vue.umd.js"
11
+ },
12
+ "./dist/images/*": "./dist/images/*"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "README.md",
18
+ "CHANGELOG.md"
19
+ ],
20
+ "keywords": [
21
+ "vue",
22
+ "affective-slider",
23
+ "emotion",
24
+ "slider",
25
+ "component"
26
+ ],
27
+ "author": "",
28
+ "license": "CC-BY-SA-4.0",
29
+ "description": "A Vue component implementation of the Affective Slider for measuring pleasure and arousal",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/konbraphat51/AffectiveSlider.vue.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/konbraphat51/AffectiveSlider.vue/issues"
36
+ },
37
+ "homepage": "https://github.com/konbraphat51/AffectiveSlider.vue#readme",
38
+ "engines": {
39
+ "node": "^20.19.0 || >=22.12.0"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^25.0.10",
46
+ "@vitejs/plugin-vue": "^6.0.3",
47
+ "@vue/test-utils": "^2.4.6",
48
+ "happy-dom": "^20.1.0",
49
+ "typescript": "^5.9.3",
50
+ "vite": "^7.3.1",
51
+ "vitest": "^4.0.17",
52
+ "vue": "^3.5.26",
53
+ "vue-tsc": "^3.2.3"
54
+ },
55
+ "scripts": {
56
+ "build": "vue-tsc && vite build",
57
+ "test": "vitest",
58
+ "test:run": "vitest run",
59
+ "test:coverage": "vitest run --coverage"
60
+ }
61
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,372 @@
1
+ <template>
2
+ <div class="affective-slider">
3
+ <div
4
+ v-for="slider in orderedSliders"
5
+ :key="slider.type"
6
+ :class="['as-container', slider.type]"
7
+ >
8
+ <div class="as-icon-wrapper as-icon-left">
9
+ <img
10
+ :src="slider.leftImage"
11
+ :alt="`${slider.type} left`"
12
+ class="as-icon"
13
+ />
14
+ <div v-if="slider.leftLabel" class="as-icon-label">{{ slider.leftLabel }}</div>
15
+ </div>
16
+ <input
17
+ type="range"
18
+ :name="`AS-${slider.type}`"
19
+ :id="`AS-${slider.type}`"
20
+ :value="slider.value"
21
+ min="0"
22
+ max="1"
23
+ step="0.01"
24
+ @input="handleInput(slider.type, $event)"
25
+ @mousedown="handleInteraction(slider.type)"
26
+ @touchstart="handleInteraction(slider.type)"
27
+ class="as-slider"
28
+ />
29
+ <div class="as-icon-wrapper as-icon-right">
30
+ <img
31
+ :src="slider.rightImage"
32
+ :alt="`${slider.type} right`"
33
+ class="as-icon"
34
+ />
35
+ <div v-if="slider.rightLabel" class="as-icon-label">{{ slider.rightLabel }}</div>
36
+ </div>
37
+ <div class="as-intensity-cue">
38
+ <img :src="intensityCueImage" alt="intensity cue" />
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup>
45
+ import { ref, computed, onMounted, watch } from 'vue'
46
+ import unhappyImage from '../assets/images/AS_unhappy.png'
47
+ import sleepyImage from '../assets/images/AS_sleepy.png'
48
+ import happyImage from '../assets/images/AS_happy.png'
49
+ import wideawakeImage from '../assets/images/AS_wideawake.png'
50
+ import intensityCueImage from '../assets/images/AS_intensity_cue.png'
51
+
52
+ const props = defineProps({
53
+ // Initial value for pleasure slider (0-1)
54
+ pleasureValue: {
55
+ type: Number,
56
+ default: 0.5,
57
+ validator: (value) => value >= 0 && value <= 1
58
+ },
59
+ // Initial value for arousal slider (0-1)
60
+ arousalValue: {
61
+ type: Number,
62
+ default: 0.5,
63
+ validator: (value) => value >= 0 && value <= 1
64
+ },
65
+ // Randomize the order of sliders
66
+ randomizeOrder: {
67
+ type: Boolean,
68
+ default: true
69
+ },
70
+ // Label below left icon for pleasure slider (unhappy face)
71
+ pleasureLeftLabel: {
72
+ type: String,
73
+ default: ''
74
+ },
75
+ // Label below right icon for pleasure slider (happy face)
76
+ pleasureRightLabel: {
77
+ type: String,
78
+ default: ''
79
+ },
80
+ // Label below left icon for arousal slider (sleepy face)
81
+ arousalLeftLabel: {
82
+ type: String,
83
+ default: ''
84
+ },
85
+ // Label below right icon for arousal slider (wide awake face)
86
+ arousalRightLabel: {
87
+ type: String,
88
+ default: ''
89
+ }
90
+ })
91
+
92
+ const emit = defineEmits(['update:pleasureValue', 'update:arousalValue', 'change', 'interacted'])
93
+
94
+ const pleasure = ref(props.pleasureValue)
95
+ const arousal = ref(props.arousalValue)
96
+ const sliderOrder = ref([])
97
+ const interacted = ref({
98
+ pleasure: false,
99
+ arousal: false
100
+ })
101
+
102
+ const getLeftImage = (type) => {
103
+ return type === 'pleasure' ? unhappyImage : sleepyImage
104
+ }
105
+
106
+ const getRightImage = (type) => {
107
+ return type === 'pleasure' ? happyImage : wideawakeImage
108
+ }
109
+
110
+ const getLeftLabel = (type) => {
111
+ return type === 'pleasure' ? props.pleasureLeftLabel : props.arousalLeftLabel
112
+ }
113
+
114
+ const getRightLabel = (type) => {
115
+ return type === 'pleasure' ? props.pleasureRightLabel : props.arousalRightLabel
116
+ }
117
+
118
+ const orderedSliders = computed(() => {
119
+ return sliderOrder.value.map(type => ({
120
+ type,
121
+ value: type === 'pleasure' ? pleasure.value : arousal.value,
122
+ leftImage: getLeftImage(type),
123
+ rightImage: getRightImage(type),
124
+ leftLabel: getLeftLabel(type),
125
+ rightLabel: getRightLabel(type)
126
+ }))
127
+ })
128
+
129
+ const handleInput = (type, event) => {
130
+ const value = parseFloat(event.target.value)
131
+ if (type === 'pleasure') {
132
+ pleasure.value = value
133
+ } else {
134
+ arousal.value = value
135
+ }
136
+ emit(`update:${type}Value`, value)
137
+ emit('change', { pleasure: pleasure.value, arousal: arousal.value })
138
+ }
139
+
140
+ const handleInteraction = (type) => {
141
+ if (!interacted.value[type]) {
142
+ interacted.value[type] = true
143
+ emit('interacted', { type, pleasure: pleasure.value, arousal: arousal.value })
144
+ }
145
+ }
146
+
147
+ const initializeSliderOrder = () => {
148
+ const sliders = ['arousal', 'pleasure']
149
+ if (props.randomizeOrder) {
150
+ sliderOrder.value = Math.random() > 0.5 ? [...sliders] : [...sliders].reverse()
151
+ } else {
152
+ sliderOrder.value = ['pleasure', 'arousal']
153
+ }
154
+ }
155
+
156
+ onMounted(() => {
157
+ initializeSliderOrder()
158
+ })
159
+
160
+ watch(() => props.pleasureValue, (newVal) => {
161
+ pleasure.value = newVal
162
+ })
163
+
164
+ watch(() => props.arousalValue, (newVal) => {
165
+ arousal.value = newVal
166
+ })
167
+ </script>
168
+
169
+ <style scoped>
170
+ .affective-slider {
171
+ width: 100%;
172
+ max-width: 800px;
173
+ margin: 0 auto;
174
+ padding: 20px;
175
+ }
176
+
177
+ .as-container {
178
+ margin: 3em 0;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ flex-direction: column;
183
+ position: relative;
184
+ }
185
+
186
+ .as-icon {
187
+ width: 60px;
188
+ height: 60px;
189
+ object-fit: contain;
190
+ }
191
+
192
+ .as-icon-wrapper {
193
+ position: absolute;
194
+ top: 0;
195
+ display: flex;
196
+ flex-direction: column;
197
+ align-items: center;
198
+ gap: 4px;
199
+ }
200
+
201
+ .as-icon-left {
202
+ left: 0;
203
+ }
204
+
205
+ .as-icon-right {
206
+ right: 0;
207
+ }
208
+
209
+ .as-icon-label {
210
+ font-size: 12px;
211
+ color: #333;
212
+ text-align: center;
213
+ white-space: nowrap;
214
+ max-width: 80px;
215
+ overflow: hidden;
216
+ text-overflow: ellipsis;
217
+ }
218
+
219
+ .as-slider {
220
+ -webkit-appearance: none;
221
+ width: calc(100% - 140px);
222
+ height: 20px;
223
+ background: #ddd;
224
+ border-radius: 10px;
225
+ outline: none;
226
+ margin: 0 70px;
227
+ cursor: pointer;
228
+ }
229
+
230
+ .as-slider:focus {
231
+ background: #ccc;
232
+ }
233
+
234
+ /* Webkit (Chrome, Safari, Edge) */
235
+ .as-slider::-webkit-slider-thumb {
236
+ -webkit-appearance: none;
237
+ appearance: none;
238
+ width: 35px;
239
+ height: 35px;
240
+ border-radius: 50%;
241
+ background: white;
242
+ border: 2px solid #505050;
243
+ cursor: pointer;
244
+ transition: all 0.2s ease;
245
+ }
246
+
247
+ .as-slider::-webkit-slider-thumb:hover {
248
+ background: #505050;
249
+ border: 2px solid white;
250
+ box-shadow: 0px 0px 12px #212121;
251
+ }
252
+
253
+ .as-slider::-webkit-slider-thumb:active {
254
+ background: #505050;
255
+ border: 2px solid white;
256
+ }
257
+
258
+ /* Firefox */
259
+ .as-slider::-moz-range-thumb {
260
+ width: 35px;
261
+ height: 35px;
262
+ border-radius: 50%;
263
+ background: white;
264
+ border: 2px solid #505050;
265
+ cursor: pointer;
266
+ transition: all 0.2s ease;
267
+ }
268
+
269
+ .as-slider::-moz-range-thumb:hover {
270
+ background: #505050;
271
+ border: 2px solid white;
272
+ box-shadow: 0px 0px 12px #212121;
273
+ }
274
+
275
+ .as-slider::-moz-range-thumb:active {
276
+ background: #505050;
277
+ border: 2px solid white;
278
+ }
279
+
280
+ .as-slider::-moz-range-track {
281
+ width: 100%;
282
+ height: 20px;
283
+ background: #ddd;
284
+ border-radius: 10px;
285
+ border: none;
286
+ }
287
+
288
+ .as-slider:focus::-moz-range-track {
289
+ background: #ccc;
290
+ }
291
+
292
+ /* IE/Edge */
293
+ .as-slider::-ms-track {
294
+ width: 100%;
295
+ height: 20px;
296
+ background: transparent;
297
+ border-color: transparent;
298
+ border-width: 16px 0;
299
+ color: transparent;
300
+ }
301
+
302
+ .as-slider::-ms-fill-lower {
303
+ background: #ddd;
304
+ border-radius: 10px;
305
+ }
306
+
307
+ .as-slider::-ms-fill-upper {
308
+ background: #ddd;
309
+ border-radius: 10px;
310
+ }
311
+
312
+ .as-slider::-ms-thumb {
313
+ width: 35px;
314
+ height: 35px;
315
+ border-radius: 50%;
316
+ background: white;
317
+ border: 2px solid #505050;
318
+ cursor: pointer;
319
+ }
320
+
321
+ .as-slider::-ms-thumb:hover {
322
+ background: #505050;
323
+ border: 2px solid white;
324
+ box-shadow: 0px 0px 12px #212121;
325
+ }
326
+
327
+ .as-slider::-ms-thumb:active {
328
+ background: #505050;
329
+ border: 2px solid white;
330
+ }
331
+
332
+ .as-slider:focus::-ms-fill-lower {
333
+ background: #ccc;
334
+ }
335
+
336
+ .as-slider:focus::-ms-fill-upper {
337
+ background: #ccc;
338
+ }
339
+
340
+ .as-intensity-cue {
341
+ width: calc(100% - 140px);
342
+ margin: 10px 70px 0;
343
+ }
344
+
345
+ .as-intensity-cue img {
346
+ width: 100%;
347
+ height: auto;
348
+ }
349
+
350
+ /* Responsive design */
351
+ @media (max-width: 768px) {
352
+ .as-icon {
353
+ width: 40px;
354
+ height: 40px;
355
+ }
356
+
357
+ .as-slider {
358
+ width: calc(100% - 100px);
359
+ margin: 0 50px;
360
+ }
361
+
362
+ .as-intensity-cue {
363
+ width: calc(100% - 100px);
364
+ margin: 10px 50px 0;
365
+ }
366
+
367
+ .as-icon-label {
368
+ font-size: 10px;
369
+ max-width: 60px;
370
+ }
371
+ }
372
+ </style>
@@ -0,0 +1,272 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { mount, flushPromises, VueWrapper, DOMWrapper } from '@vue/test-utils'
3
+ import AffectiveSlider from '../AffectiveSlider.vue'
4
+ import unhappyImage from '../../assets/images/AS_unhappy.png'
5
+ import sleepyImage from '../../assets/images/AS_sleepy.png'
6
+ import happyImage from '../../assets/images/AS_happy.png'
7
+ import wideawakeImage from '../../assets/images/AS_wideawake.png'
8
+ import intensityCueImage from '../../assets/images/AS_intensity_cue.png'
9
+
10
+ describe('AffectiveSlider', () => {
11
+ let wrapper: VueWrapper
12
+
13
+ beforeEach(() => {
14
+ // Reset Math.random mock before each test
15
+ vi.restoreAllMocks()
16
+ wrapper = mount(AffectiveSlider, {
17
+ props: {
18
+ // Ensure fixed order for predictable DOM queries
19
+ randomizeOrder: false
20
+ }
21
+ })
22
+ })
23
+
24
+ describe('Component Rendering', () => {
25
+ it('should render the component', async () => {
26
+ await flushPromises()
27
+ expect(wrapper.find('.affective-slider').exists()).toBe(true)
28
+ })
29
+
30
+ it('should render two slider containers', async () => {
31
+ await flushPromises()
32
+ const containers = wrapper.findAll('.as-container')
33
+ expect(containers).toHaveLength(2)
34
+ })
35
+
36
+ it('should render slider inputs', async () => {
37
+ await flushPromises()
38
+ const sliders = wrapper.findAll('.as-slider')
39
+ expect(sliders).toHaveLength(2)
40
+ })
41
+
42
+ it('should render icons for both sliders', async () => {
43
+ await flushPromises()
44
+ const icons = wrapper.findAll('.as-icon')
45
+ // 2 sliders * 2 icons each (left and right) = 4 icons
46
+ expect(icons).toHaveLength(4)
47
+ })
48
+
49
+ it('should render intensity cue images', async () => {
50
+ await flushPromises()
51
+ const intensityCues = wrapper.findAll('.as-intensity-cue img')
52
+ expect(intensityCues).toHaveLength(2)
53
+ })
54
+ })
55
+
56
+ describe('Props', () => {
57
+ it('should accept pleasureValue prop', async () => {
58
+ // Re-mount for this specific test
59
+ wrapper = mount(AffectiveSlider, {
60
+ props: { pleasureValue: 0.7, randomizeOrder: false }
61
+ })
62
+ await flushPromises()
63
+ // Note: we can't directly test ref value, so we test the rendered output
64
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
65
+ expect((pleasureSlider.element as HTMLInputElement).value).toBe('0.7')
66
+ })
67
+
68
+ it('should accept arousalValue prop', async () => {
69
+ // Re-mount for this specific test
70
+ wrapper = mount(AffectiveSlider, {
71
+ props: { arousalValue: 0.3, randomizeOrder: false }
72
+ })
73
+ await flushPromises()
74
+ const arousalSlider = wrapper.find('input[name="AS-arousal"]')
75
+ expect((arousalSlider.element as HTMLInputElement).value).toBe('0.3')
76
+ })
77
+
78
+ it('should have default values of 0.5 for both sliders', () => {
79
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
80
+ const arousalSlider = wrapper.find('input[name="AS-arousal"]')
81
+ expect((pleasureSlider.element as HTMLInputElement).value).toBe('0.5')
82
+ expect((arousalSlider.element as HTMLInputElement).value).toBe('0.5')
83
+ })
84
+
85
+ it('should render pleasure labels when provided', async () => {
86
+ await wrapper.setProps({
87
+ pleasureLeftLabel: 'Sad',
88
+ pleasureRightLabel: 'Happy'
89
+ })
90
+ await flushPromises()
91
+ const labels = wrapper.findAll('.as-icon-label')
92
+ const labelTexts = labels.map((l: DOMWrapper<Element>) => l.text())
93
+ expect(labelTexts).toContain('Sad')
94
+ expect(labelTexts).toContain('Happy')
95
+ })
96
+
97
+ it('should render arousal labels when provided', async () => {
98
+ await wrapper.setProps({
99
+ arousalLeftLabel: 'Sleepy',
100
+ arousalRightLabel: 'Awake'
101
+ })
102
+ await flushPromises()
103
+ const labels = wrapper.findAll('.as-icon-label')
104
+ const labelTexts = labels.map((l: DOMWrapper<Element>) => l.text())
105
+ expect(labelTexts).toContain('Sleepy')
106
+ expect(labelTexts).toContain('Awake')
107
+ })
108
+ })
109
+
110
+ // Props validation tests are removed as they are no longer easily accessible with <script setup>
111
+ // and are implicitly covered by TypeScript during the build process.
112
+
113
+ describe('Slider Input Handling', () => {
114
+ it('should emit update:pleasureValue event', async () => {
115
+ await flushPromises()
116
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
117
+
118
+ await pleasureSlider.setValue(0.8)
119
+ expect(wrapper.emitted('update:pleasureValue')).toBeTruthy()
120
+ expect(wrapper.emitted('update:pleasureValue')![0]).toEqual([0.8])
121
+ })
122
+
123
+ it('should emit update:arousalValue event', async () => {
124
+ await flushPromises()
125
+ const arousalSlider = wrapper.find('input[name="AS-arousal"]')
126
+
127
+ await arousalSlider.setValue(0.3)
128
+ expect(wrapper.emitted('update:arousalValue')).toBeTruthy()
129
+ expect(wrapper.emitted('update:arousalValue')![0]).toEqual([0.3])
130
+ })
131
+
132
+ it('should emit change event with both values', async () => {
133
+ await flushPromises()
134
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
135
+
136
+ await pleasureSlider.setValue(0.7)
137
+ expect(wrapper.emitted('change')).toBeTruthy()
138
+ expect(wrapper.emitted('change')![0]).toEqual([{ pleasure: 0.7, arousal: 0.5 }])
139
+ })
140
+ })
141
+
142
+ describe('Interaction Tracking', () => {
143
+ it('should emit interacted event on first mousedown', async () => {
144
+ await flushPromises()
145
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
146
+
147
+ await pleasureSlider.trigger('mousedown')
148
+ expect(wrapper.emitted('interacted')).toBeTruthy()
149
+ expect(wrapper.emitted('interacted')![0]).toEqual([{
150
+ type: 'pleasure',
151
+ pleasure: 0.5,
152
+ arousal: 0.5
153
+ }])
154
+ })
155
+
156
+ it('should emit interacted event on first touchstart', async () => {
157
+ await flushPromises()
158
+ const arousalSlider = wrapper.find('input[name="AS-arousal"]')
159
+
160
+ await arousalSlider.trigger('touchstart')
161
+ expect(wrapper.emitted('interacted')).toBeTruthy()
162
+ expect(wrapper.emitted('interacted')![0]).toEqual([{
163
+ type: 'arousal',
164
+ pleasure: 0.5,
165
+ arousal: 0.5
166
+ }])
167
+ })
168
+
169
+ it('should only emit interacted event once per slider', async () => {
170
+ await flushPromises()
171
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
172
+
173
+ await pleasureSlider.trigger('mousedown')
174
+ await pleasureSlider.trigger('mousedown')
175
+ await pleasureSlider.trigger('touchstart')
176
+
177
+ expect(wrapper.emitted('interacted')).toHaveLength(1)
178
+ })
179
+ })
180
+
181
+ describe('Slider Order', () => {
182
+ it('should randomize slider order when randomizeOrder is true', async () => {
183
+ const mockRandom = vi.spyOn(Math, 'random')
184
+
185
+ mockRandom.mockReturnValue(0.3) // pleasure, arousal
186
+ wrapper.unmount()
187
+ const wrapper1 = mount(AffectiveSlider, { props: { randomizeOrder: true } })
188
+ await flushPromises()
189
+ const order1 = wrapper1.findAll('.as-container').map((w: DOMWrapper<Element>) => w.classes().find(c => c !== 'as-container'))
190
+
191
+ mockRandom.mockReturnValue(0.7) // arousal, pleasure
192
+ wrapper1.unmount()
193
+ const wrapper2 = mount(AffectiveSlider, { props: { randomizeOrder: true } })
194
+ await flushPromises()
195
+ const order2 = wrapper2.findAll('.as-container').map((w: DOMWrapper<Element>) => w.classes().find(c => c !== 'as-container'))
196
+
197
+ expect(order1).not.toEqual(order2)
198
+ })
199
+
200
+ it('should have fixed order when randomizeOrder is false', async () => {
201
+ await flushPromises()
202
+ const containers = wrapper.findAll('.as-container')
203
+ // First slider should be pleasure, second should be arousal
204
+ expect(containers[0].classes()).toContain('pleasure')
205
+ expect(containers[1].classes()).toContain('arousal')
206
+ })
207
+ })
208
+
209
+ describe('Image Paths', () => {
210
+ it('should use correct images for all sliders', async () => {
211
+ await flushPromises()
212
+
213
+ const pleasureContainer = wrapper.find('.as-container.pleasure')
214
+ const arousalContainer = wrapper.find('.as-container.arousal')
215
+
216
+ // Pleasure slider images
217
+ expect(pleasureContainer.find('.as-icon-left img').attributes('src')).toBe(unhappyImage)
218
+ expect(pleasureContainer.find('.as-icon-right img').attributes('src')).toBe(happyImage)
219
+ expect(pleasureContainer.find('.as-intensity-cue img').attributes('src')).toBe(intensityCueImage)
220
+
221
+ // Arousal slider images
222
+ expect(arousalContainer.find('.as-icon-left img').attributes('src')).toBe(sleepyImage)
223
+ expect(arousalContainer.find('.as-icon-right img').attributes('src')).toBe(wideawakeImage)
224
+ expect(arousalContainer.find('.as-intensity-cue img').attributes('src')).toBe(intensityCueImage)
225
+ })
226
+ })
227
+
228
+ describe('Watchers', () => {
229
+ it('should update internal pleasure value when prop changes', async () => {
230
+ await wrapper.setProps({ pleasureValue: 0.9 })
231
+ const pleasureSlider = wrapper.find('input[name="AS-pleasure"]')
232
+ expect((pleasureSlider.element as HTMLInputElement).value).toBe('0.9')
233
+ })
234
+
235
+ it('should update internal arousal value when prop changes', async () => {
236
+ await wrapper.setProps({ arousalValue: 0.2 })
237
+ const arousalSlider = wrapper.find('input[name="AS-arousal"]')
238
+ expect((arousalSlider.element as HTMLInputElement).value).toBe('0.2')
239
+ })
240
+ })
241
+
242
+ describe('Slider Attributes', () => {
243
+ it('should have correct min, max, and step attributes', async () => {
244
+ await flushPromises()
245
+ const sliders = wrapper.findAll('.as-slider')
246
+
247
+ sliders.forEach((slider: DOMWrapper<Element>) => {
248
+ expect(slider.attributes('min')).toBe('0')
249
+ expect(slider.attributes('max')).toBe('1')
250
+ expect(slider.attributes('step')).toBe('0.01')
251
+ })
252
+ })
253
+
254
+ it('should have range input type', async () => {
255
+ await flushPromises()
256
+ const sliders = wrapper.findAll('.as-slider')
257
+
258
+ sliders.forEach((slider: DOMWrapper<Element>) => {
259
+ expect(slider.attributes('type')).toBe('range')
260
+ })
261
+ })
262
+
263
+ it('should have unique IDs for each slider', async () => {
264
+ await flushPromises()
265
+ const pleasureSlider = wrapper.find('#AS-pleasure')
266
+ const arousalSlider = wrapper.find('#AS-arousal')
267
+
268
+ expect(pleasureSlider.exists()).toBe(true)
269
+ expect(arousalSlider.exists()).toBe(true)
270
+ })
271
+ })
272
+ })
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import AffectiveSlider from './components/AffectiveSlider.vue'
2
+
3
+ export { AffectiveSlider }
4
+ export default AffectiveSlider
@@ -0,0 +1,5 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue'
3
+ const component: DefineComponent<{}, {}, any>
4
+ export default component
5
+ }