@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkpixellab-public/px-vue",
3
- "version": "3.0.67",
3
+ "version": "3.0.68",
4
4
  "description": "General purpose Vue components and helpers that can be used across projects.",
5
5
  "author": "Pixel Lab",
6
6
  "license": "MIT",
@@ -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
+ }