@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 +274 -0
- package/dist/affective-slider-vue.es.js +128 -0
- package/dist/affective-slider-vue.umd.js +1 -0
- package/dist/affectiveslidervue.css +1 -0
- package/package.json +61 -0
- package/src/assets/images/AS_full.png +0 -0
- package/src/assets/images/AS_happy.png +0 -0
- package/src/assets/images/AS_intensity_cue.png +0 -0
- package/src/assets/images/AS_sleepy.png +0 -0
- package/src/assets/images/AS_sleepy_alt.png +0 -0
- package/src/assets/images/AS_thumb.png +0 -0
- package/src/assets/images/AS_track.png +0 -0
- package/src/assets/images/AS_unhappy.png +0 -0
- package/src/assets/images/AS_wideawake.png +0 -0
- package/src/components/AffectiveSlider.vue +372 -0
- package/src/components/__tests__/AffectiveSlider.spec.ts +272 -0
- package/src/index.ts +4 -0
- package/src/vite-env.d.ts +5 -0
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,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", 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
|
|
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