@thinkpixellab-public/px-vue 3.0.67 → 3.0.68
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/components/PxArrow.vue +128 -0
- package/components/PxTransition.vue +241 -0
- package/package.json +1 -1
- package/utils/points.js +110 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Generates an SVG arrow based on props.
|
|
3
|
+
-->
|
|
4
|
+
<template>
|
|
5
|
+
<svg
|
|
6
|
+
version="1.1"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
9
|
+
overflow="visible"
|
|
10
|
+
:viewBox="svgData.viewBox"
|
|
11
|
+
>
|
|
12
|
+
<g
|
|
13
|
+
:stroke-width="svgData.strokeWidth"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
fill="none"
|
|
16
|
+
fill-rule="evenodd"
|
|
17
|
+
stroke-linecap="round"
|
|
18
|
+
>
|
|
19
|
+
<polyline :points="svgData.pointer"></polyline>
|
|
20
|
+
<polyline v-if="stemVisible" :points="svgData.stem"></polyline>
|
|
21
|
+
</g>
|
|
22
|
+
</svg>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import PxBaseResize from '@thinkpixellab-public/px-vue/components/PxBaseResize.vue';
|
|
27
|
+
import { getBoundingBox, rotatePoints } from '../utils/points.js';
|
|
28
|
+
import { contain } from '@thinkpixellab-public/px-vue/utils/fit.js';
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
name: 'px-arrow',
|
|
32
|
+
mixins: [PxBaseResize],
|
|
33
|
+
props: {
|
|
34
|
+
// the aspect of the arrow "rectangle"
|
|
35
|
+
pointerAspect: { type: [Number, String], default: 2 },
|
|
36
|
+
|
|
37
|
+
// the relative height of the arrow
|
|
38
|
+
pointerSize: { type: Number, default: 0.4 },
|
|
39
|
+
|
|
40
|
+
// the relative size of the stem
|
|
41
|
+
stemVisible: { type: Boolean, default: true },
|
|
42
|
+
|
|
43
|
+
// the angle in which the arrow is pointing (in degrees)
|
|
44
|
+
angle: { type: Number, default: 0 },
|
|
45
|
+
|
|
46
|
+
// the stroke thickness of the arrow
|
|
47
|
+
strokeWidth: { type: Number, default: 1 },
|
|
48
|
+
|
|
49
|
+
// when true, we scale the strokeWidth in the internal SVG to keep it at the specified value
|
|
50
|
+
// regardless of render size
|
|
51
|
+
scaleStrokeWidth: { type: Boolean, default: true },
|
|
52
|
+
},
|
|
53
|
+
data() {
|
|
54
|
+
return {
|
|
55
|
+
elementSize: { width: 100, height: 100 },
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
computed: {
|
|
59
|
+
svgData() {
|
|
60
|
+
const pointerHeight = this.pointerSize;
|
|
61
|
+
const pointerWidth = this.pointerAspect * pointerHeight;
|
|
62
|
+
|
|
63
|
+
// basic arrow (bottom left corner anchored to origin)
|
|
64
|
+
let points = [
|
|
65
|
+
// pointer
|
|
66
|
+
{ x: 0, y: pointerHeight },
|
|
67
|
+
{ x: pointerWidth / 2, y: 0 },
|
|
68
|
+
{ x: pointerWidth, y: pointerHeight },
|
|
69
|
+
|
|
70
|
+
// stem
|
|
71
|
+
{ x: pointerWidth / 2, y: 0 },
|
|
72
|
+
{ x: pointerWidth / 2, y: 1 },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// rotate
|
|
76
|
+
points = rotatePoints(points, this.angle);
|
|
77
|
+
|
|
78
|
+
// scale by 100 so the viewbox isn't ridiculous
|
|
79
|
+
points = points.map(p => {
|
|
80
|
+
return {
|
|
81
|
+
x: p.x * 100,
|
|
82
|
+
y: p.y * 100,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// get viewbox bounds
|
|
87
|
+
const bounds = getBoundingBox(points);
|
|
88
|
+
|
|
89
|
+
// get stroke scale
|
|
90
|
+
const strokeScale = this.scaleStrokeWidth ? contain(this.elementSize, bounds).scale : 1;
|
|
91
|
+
|
|
92
|
+
// convert points to a string
|
|
93
|
+
points = points.map(p => {
|
|
94
|
+
return `${p.x} ${p.y}`;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const svgData = {
|
|
98
|
+
viewBox: [bounds.x, bounds.y, bounds.width, bounds.height]
|
|
99
|
+
.map(n => Math.round(n))
|
|
100
|
+
.join(' '),
|
|
101
|
+
pointer: [points[0], points[1], points[2]].join(' '),
|
|
102
|
+
stem: [points[3], points[4]].join(' '),
|
|
103
|
+
strokeWidth: this.strokeWidth * strokeScale,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return svgData;
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
methods: {
|
|
110
|
+
resize() {
|
|
111
|
+
if (this.$el) {
|
|
112
|
+
const r = this.$el.getBoundingClientRect();
|
|
113
|
+
this.elementSize = {
|
|
114
|
+
width: r.width,
|
|
115
|
+
height: r.height,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<style lang="scss">
|
|
124
|
+
@import '@/styles/include.scss';
|
|
125
|
+
.px-arrow {
|
|
126
|
+
position: relative;
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
<PxTransition animation="fade up" />
|
|
3
|
+
-->
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<transition @enter="enter" @leave="leave">
|
|
7
|
+
<slot />
|
|
8
|
+
</transition>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
import gsap from 'gsap';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
// components: { Component },
|
|
16
|
+
// mixins: [ Mixin ],
|
|
17
|
+
props: {
|
|
18
|
+
/*
|
|
19
|
+
* Specifies the show/hide animation. Should contain a mix of the following keywords
|
|
20
|
+
*
|
|
21
|
+
* - nofade - disables the standard fade (why would you want that?)
|
|
22
|
+
* - up - translate y: 50 -> 0 / 0 -> -50
|
|
23
|
+
* - down - translate y: 0 -> -50 / 0 -> 50
|
|
24
|
+
* - left - translate x: from -50 to 0 / 0 -> 50
|
|
25
|
+
* - right - translate x: from 50 to 0 / 0 -> -50
|
|
26
|
+
* - zoomin - scale x: 0.66 -> 1 / 1 -> 1.33
|
|
27
|
+
* - zoomout - scale x: 1.33 -> 1 / 1 -> 0.66
|
|
28
|
+
* - lg - scales values by 1.5
|
|
29
|
+
* - sm - scales values by 0.5
|
|
30
|
+
* - xs - scales values by 0.25
|
|
31
|
+
*
|
|
32
|
+
* Examples:
|
|
33
|
+
*
|
|
34
|
+
* <px-transition show="fade zoomin" hide="fade zoomout" />
|
|
35
|
+
*
|
|
36
|
+
* // if only one of show or hide are specified then we reverse missing transition so the following are equivalent
|
|
37
|
+
* <px-transition show="fade up" />
|
|
38
|
+
* <px-transition show="fade up" hide="fade down" />
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
animation: {
|
|
43
|
+
type: [String, Object],
|
|
44
|
+
default: 'fade',
|
|
45
|
+
},
|
|
46
|
+
duration: {
|
|
47
|
+
type: Number,
|
|
48
|
+
default: 0.5,
|
|
49
|
+
},
|
|
50
|
+
hideAnimation: {
|
|
51
|
+
type: [String, Object],
|
|
52
|
+
default: null,
|
|
53
|
+
},
|
|
54
|
+
easing: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: 'power.out',
|
|
57
|
+
},
|
|
58
|
+
hideDuration: {
|
|
59
|
+
type: Number,
|
|
60
|
+
default: null,
|
|
61
|
+
},
|
|
62
|
+
hideEasing: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: null,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
// data() { return { b: 0 }; },f
|
|
68
|
+
computed: {
|
|
69
|
+
transitionName() {
|
|
70
|
+
return `pxtx-${this.name}`;
|
|
71
|
+
},
|
|
72
|
+
showDur() {
|
|
73
|
+
return this.duration;
|
|
74
|
+
},
|
|
75
|
+
showEase() {
|
|
76
|
+
return this.easing;
|
|
77
|
+
},
|
|
78
|
+
hideDur() {
|
|
79
|
+
return this.hideDuration || this.duration;
|
|
80
|
+
},
|
|
81
|
+
hideEase() {
|
|
82
|
+
return this.hideEasing || this.easing;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
// watch: {},
|
|
86
|
+
// mounted() {},
|
|
87
|
+
methods: {
|
|
88
|
+
keywordsToParameters(keywords, direction, reverse) {
|
|
89
|
+
const words = keywords.split(/[, ]+/).map(s => s.trim());
|
|
90
|
+
const fr = { x: 0, y: 0, scale: 1 };
|
|
91
|
+
const to = { x: 0, y: 0, scale: 1 };
|
|
92
|
+
const isHide = direction == 'hide';
|
|
93
|
+
const isShow = !isHide;
|
|
94
|
+
|
|
95
|
+
// parameter scale and percent
|
|
96
|
+
let s = 0.33;
|
|
97
|
+
s = words.includes('xl') ? 0.75 : s;
|
|
98
|
+
s = words.includes('lg') ? 0.5 : s;
|
|
99
|
+
s = words.includes('sm') ? 0.15 : s;
|
|
100
|
+
s = words.includes('xs') ? 0.05 : s;
|
|
101
|
+
|
|
102
|
+
if (words && words.length) {
|
|
103
|
+
// fade - animates opacity: 0 -> 1 / 1 -> 0
|
|
104
|
+
if (!words.includes('nofade')) {
|
|
105
|
+
fr['opacity'] = isShow ? 0 : 1;
|
|
106
|
+
to['opacity'] = isShow ? 1 : 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// up - translate y: 50 -> 0 / 0 -> -50
|
|
110
|
+
if (words.includes('up')) {
|
|
111
|
+
fr['y'] = isShow ? s : 0;
|
|
112
|
+
to['y'] = isShow ? 0 : -s;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// down - translate y: -50 -> 0 / 0 -> 50
|
|
116
|
+
if (words.includes('down')) {
|
|
117
|
+
fr['y'] = isShow ? -s : 0;
|
|
118
|
+
to['y'] = isShow ? 0 : s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// left - translate x: from -100 to 0 / 0 -> 100
|
|
122
|
+
if (words.includes('right')) {
|
|
123
|
+
fr['x'] = isShow ? -s : 0;
|
|
124
|
+
to['x'] = isShow ? 0 : s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// right - translate x: from 0.33 to 0 / 0 -> -0.33
|
|
128
|
+
if (words.includes('left')) {
|
|
129
|
+
fr['x'] = isShow ? s : 0;
|
|
130
|
+
to['x'] = isShow ? 0 : -s;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// zoomin - scale x: 0.66 -> 1 / 1 -> 1.33
|
|
134
|
+
if (words.includes('zoomin')) {
|
|
135
|
+
fr['scale'] = isShow ? 1 - s : 1;
|
|
136
|
+
to['scale'] = isShow ? 1 : 1 + s;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// zoomout - scale x: 1.33 -> 1 / 1 -> 0.66
|
|
140
|
+
if (words.includes('zoomout')) {
|
|
141
|
+
fr['scale'] = isShow ? 1 + s : 1;
|
|
142
|
+
to['scale'] = isShow ? 1 : 1 + s;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// convert tranlsate to percentages
|
|
147
|
+
|
|
148
|
+
to.y = this.cssPcnt(to.y);
|
|
149
|
+
fr.y = this.cssPcnt(fr.y);
|
|
150
|
+
to.x = this.cssPcnt(to.x);
|
|
151
|
+
fr.x = this.cssPcnt(fr.x);
|
|
152
|
+
|
|
153
|
+
if (reverse) {
|
|
154
|
+
return {
|
|
155
|
+
from: to,
|
|
156
|
+
to: fr,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
from: fr,
|
|
161
|
+
to: to,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
enter(element, done) {
|
|
165
|
+
const params = this.keywordsToParameters(this.animation, 'show');
|
|
166
|
+
if (params) {
|
|
167
|
+
gsap.fromTo(element, params.from, {
|
|
168
|
+
...params.to,
|
|
169
|
+
duration: this.showDur,
|
|
170
|
+
ease: this.showEase,
|
|
171
|
+
onComplete: done,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
leave(element, done) {
|
|
176
|
+
const params = this.hideAnimation
|
|
177
|
+
? this.keywordsToParameters(this.hideAnimation, 'hide')
|
|
178
|
+
: this.keywordsToParameters(this.animation, 'show', true);
|
|
179
|
+
if (params) {
|
|
180
|
+
gsap.to(element, {
|
|
181
|
+
...params.to,
|
|
182
|
+
duration: this.hideDur,
|
|
183
|
+
ease: this.hideEase,
|
|
184
|
+
onComplete: done,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
<style lang="scss">
|
|
193
|
+
@use '../styles/px.scss' as *;
|
|
194
|
+
|
|
195
|
+
// @at-root {
|
|
196
|
+
// body {
|
|
197
|
+
// --pxtx-transition-dur: 0.5s;
|
|
198
|
+
// }
|
|
199
|
+
// }
|
|
200
|
+
|
|
201
|
+
// $pxtx-dur: var(--pxtx-transition-dur);
|
|
202
|
+
|
|
203
|
+
// @include vue-transition(pxtx-fade) {
|
|
204
|
+
// // define the hidden state (the non-hidden state is implicty -- the normal state)
|
|
205
|
+
// @include vt-transition {
|
|
206
|
+
// @include transition(opacity, $dur: $pxtx-dur);
|
|
207
|
+
// }
|
|
208
|
+
|
|
209
|
+
// the hidden state before entering
|
|
210
|
+
// @include vt-hidden-enter {
|
|
211
|
+
// //transform: translateY(-100px);
|
|
212
|
+
// opacity: 0;
|
|
213
|
+
// }
|
|
214
|
+
|
|
215
|
+
// // the hidden after exiting
|
|
216
|
+
// @include vt-hidden-leave {
|
|
217
|
+
// //transform: translateY(100px);
|
|
218
|
+
// opacity: 0;
|
|
219
|
+
// }
|
|
220
|
+
// }
|
|
221
|
+
|
|
222
|
+
// @include vue-transition(pxtx-fade-up) {
|
|
223
|
+
// // define the hidden state (the non-hidden state is implicty -- the normal state)
|
|
224
|
+
// @include vt-transition {
|
|
225
|
+
// @include transition(opacity transform, $dur: 3s);
|
|
226
|
+
// //transform: translateY(0px);
|
|
227
|
+
// }
|
|
228
|
+
|
|
229
|
+
// // the hidden state before entering
|
|
230
|
+
// @include vt-hidden-enter {
|
|
231
|
+
// transform: scale(0.5);
|
|
232
|
+
// opacity: 0;
|
|
233
|
+
// }
|
|
234
|
+
|
|
235
|
+
// // the hidden after exiting
|
|
236
|
+
// @include vt-hidden-leave {
|
|
237
|
+
// transform: scale(0.5);
|
|
238
|
+
// opacity: 0;
|
|
239
|
+
// }
|
|
240
|
+
// } ;
|
|
241
|
+
</style>
|
package/package.json
CHANGED
package/utils/points.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// rounds things to 5 decimals (removes a lot of weird sin/cos noise)
|
|
2
|
+
function fineRound(v) {
|
|
3
|
+
return Math.round(v * 10000) / 10000;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns the bounding box for an array of points {x,y}
|
|
8
|
+
*/
|
|
9
|
+
export function getBoundingBox(points) {
|
|
10
|
+
if (points.length === 0) {
|
|
11
|
+
return null; // Return null for an empty array
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let minX = points[0].x;
|
|
15
|
+
let maxX = points[0].x;
|
|
16
|
+
let minY = points[0].y;
|
|
17
|
+
let maxY = points[0].y;
|
|
18
|
+
|
|
19
|
+
// Find the minimum and maximum x and y values
|
|
20
|
+
for (let i = 1; i < points.length; i++) {
|
|
21
|
+
const point = points[i];
|
|
22
|
+
minX = Math.min(minX, point.x);
|
|
23
|
+
maxX = Math.max(maxX, point.x);
|
|
24
|
+
minY = Math.min(minY, point.y);
|
|
25
|
+
maxY = Math.max(maxY, point.y);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Calculate the width and height of the bounding box
|
|
29
|
+
const width = maxX - minX;
|
|
30
|
+
const height = maxY - minY;
|
|
31
|
+
|
|
32
|
+
// Return the bounding box rectangle
|
|
33
|
+
return {
|
|
34
|
+
x: minX,
|
|
35
|
+
y: minY,
|
|
36
|
+
width: width,
|
|
37
|
+
height: height,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @returns A new array contain all points in the points array after having been rotated angleDeg
|
|
43
|
+
* degrees around the center of the combined points bounding box.
|
|
44
|
+
*/
|
|
45
|
+
export function rotatePoints(points, angleDeg) {
|
|
46
|
+
if (angleDeg == 0) {
|
|
47
|
+
return points;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let bounds = getBoundingBox(points);
|
|
51
|
+
|
|
52
|
+
// distance to the origin
|
|
53
|
+
const dx = (bounds.x + bounds.width) / 2;
|
|
54
|
+
const dy = (bounds.y + bounds.height) / 2;
|
|
55
|
+
|
|
56
|
+
points = points.map(p => {
|
|
57
|
+
const x = p.x - dx;
|
|
58
|
+
const y = p.y - dy;
|
|
59
|
+
const angleRad = (angleDeg * Math.PI) / 180;
|
|
60
|
+
const cosTheta = Math.cos(angleRad);
|
|
61
|
+
const sinTheta = Math.sin(angleRad);
|
|
62
|
+
const newX = x * cosTheta - y * sinTheta;
|
|
63
|
+
const newY = x * sinTheta + y * cosTheta;
|
|
64
|
+
return { x: fineRound(newX + dx), y: fineRound(newY + dy) };
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// shift back to the origin
|
|
68
|
+
bounds = getBoundingBox(points);
|
|
69
|
+
points = shiftPoints(points, bounds.x, bounds.y);
|
|
70
|
+
|
|
71
|
+
return points;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Shifts each point in an array of points {x,y} by dx, dy
|
|
76
|
+
*/
|
|
77
|
+
export function shiftPoints(points, dx, dy) {
|
|
78
|
+
return points.map(p => {
|
|
79
|
+
return {
|
|
80
|
+
x: p.x - dx,
|
|
81
|
+
y: p.y - dy,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Takes an angle in degrees and a line length and returns a point representing the end of that line
|
|
88
|
+
* relative to the origin
|
|
89
|
+
* @param {*} angleInDegrees an angle in degrees
|
|
90
|
+
* @param {*} lineLength a length
|
|
91
|
+
* @returns a point {x, y}
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
export function getLineEndPoint(angleInDegrees, lineLength) {
|
|
95
|
+
const angleInRadians = (angleInDegrees * Math.PI) / 180;
|
|
96
|
+
return {
|
|
97
|
+
x: fineRound(lineLength * Math.cos(angleInRadians)),
|
|
98
|
+
y: fineRound(lineLength * Math.sin(angleInRadians)),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getLineRect(angleInDegrees, lineLength) {
|
|
103
|
+
const endPoint = getLineEndPoint(angleInDegrees, lineLength);
|
|
104
|
+
return {
|
|
105
|
+
x: 0,
|
|
106
|
+
y: 0,
|
|
107
|
+
width: Math.abs(endPoint.x),
|
|
108
|
+
height: Math.abs(endPoint.y),
|
|
109
|
+
};
|
|
110
|
+
}
|