@thinkpixellab-public/px-vue 3.0.88 → 3.0.90

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.
@@ -38,11 +38,18 @@ export default {
38
38
  // selector used to find children of the element that should slide during the transition
39
39
  dragSelector: { type: String, default: '[data-drag]' },
40
40
 
41
+ // selector used to find drag elements in the dom that should animate in a reverse direction in vertical transitions
42
+ dragReverseSelector: { type: String, default: '[data-drag-reverse]' },
43
+
41
44
  // if true, slide the parent element if no slideSelect children are found (false will result just fading)
42
45
  dragElementFallback: { type: Boolean, default: true },
43
46
 
44
- // 1 moves to the right, -1 moves to the left
47
+ // 1 moves the content to the left/up, -1 moves to the right/down
45
48
  direction: { type: Number, default: 1 },
49
+
50
+ // vertical or horizontal
51
+ orientation: { type: String, default: 'horizontal' },
52
+
46
53
  // TODO rtlAdjust: {type: Boolean, default: false }
47
54
 
48
55
  duration: { type: Number, default: 1 },
@@ -82,29 +89,76 @@ export default {
82
89
  return element.querySelectorAll(this.dragSelector);
83
90
  },
84
91
 
92
+ slideElementsReverse(element) {
93
+ if (!element || !this.orientation == 'vertical') {
94
+ return [];
95
+ }
96
+ return element.querySelectorAll(this.dragReverseSelector);
97
+ },
98
+
85
99
  beforeEnter() {},
86
100
 
87
101
  enter(element, done) {
88
- let slideElements = this.slideElements(element);
102
+ const slideElements = this.slideElements(element);
103
+
89
104
  if (slideElements?.length) {
90
- // slide elements
91
- gsap.fromTo(
92
- slideElements,
93
- //{ x: this.slideAmount * this.direction * -1 },
94
- { x: this.directionAmount(this.direction * -1) },
95
- { x: 0, ...this.tweenShared, onComplete: done }
96
- );
105
+ const from = {};
106
+ const to = { ...this.tweenShared, onComplete: done };
107
+
108
+ if (this.orientation === 'vertical') {
109
+ from.y = this.directionAmount(this.direction * -1);
110
+ to.y = 0;
111
+ } else {
112
+ from.x = this.directionAmount(this.direction * -1);
113
+ to.x = 0;
114
+ }
115
+
116
+ gsap.fromTo(slideElements, from, to);
117
+ }
118
+
119
+ if (this.orientation === 'vertical') {
120
+ const slideElementsReverse = this.slideElementsReverse(element);
121
+ if (slideElementsReverse?.length) {
122
+ gsap.fromTo(
123
+ slideElementsReverse,
124
+ { y: this.directionAmount(this.direction) },
125
+ { y: 0, ...this.tweenShared }
126
+ );
127
+ }
97
128
  }
98
129
  },
99
130
  leave(element, done) {
100
- // slide elements
101
- let slideElements = this.slideElements(element);
131
+ const slideElements = this.slideElements(element);
132
+
133
+ const to = {
134
+ ...this.tweenShared,
135
+ onComplete: done,
136
+ };
137
+
138
+ if (this.orientation === 'vertical') {
139
+ to.y = this.directionAmount(this.direction, true);
140
+ } else {
141
+ to.x = this.directionAmount(this.direction, true);
142
+ }
143
+
102
144
  if (slideElements?.length) {
103
- gsap.to(slideElements, {
104
- x: this.directionAmount(this.direction, true),
105
- ...this.tweenShared,
106
- onComplete: done,
107
- });
145
+ gsap.to(slideElements, to);
146
+ }
147
+
148
+ if (this.orientation === 'vertical') {
149
+ const slideElementsReverse = this.slideElementsReverse(element);
150
+ console.log(`slideElementsReverse.length: ${slideElementsReverse.length}`);
151
+
152
+ if (slideElementsReverse?.length) {
153
+ const slideElementsReverse = this.slideElementsReverse(element);
154
+
155
+ const toReverse = {
156
+ ...this.tweenShared,
157
+ y: this.directionAmount(this.direction * -1, true),
158
+ };
159
+
160
+ gsap.to(slideElementsReverse, toReverse);
161
+ }
108
162
  }
109
163
  },
110
164
  },
@@ -12,6 +12,7 @@
12
12
  <px-slide-transition
13
13
  :disable-animations="disableAnimations"
14
14
  :direction="direction"
15
+ :orientation="orientation"
15
16
  @before-leave="transitionStart"
16
17
  @after-leave="transitionEnd"
17
18
  @leave-cancelled="transitionEnd"
@@ -55,6 +56,9 @@ export default {
55
56
  components: { PxSlideTransition, PxIcon },
56
57
 
57
58
  props: {
59
+ // vertical or horizontal
60
+ orientation: { type: String, default: 'horizontal' },
61
+
58
62
  // whether drag interaction is enabled
59
63
  dragEnabled: { type: Boolean, default: true },
60
64
 
@@ -70,9 +74,13 @@ export default {
70
74
  // selector used to find drag elements in the dom
71
75
  dragSelector: { type: String, default: '[data-drag]' },
72
76
 
77
+ // selector used to find drag elements in the dom that should animate in a reverse direction in vertical transitions
78
+ dragReverseSelector: { type: String, default: '[data-drag-reverse]' },
79
+
73
80
  // exclude from drag
74
81
  dragExcludeSelector: { type: String, default: 'input, button, [data-no-drag]' },
75
82
 
83
+ // exclude from scroll
76
84
  scrollExcludeSelector: { type: String, default: '[data-no-slidescroll]' },
77
85
 
78
86
  // override default
@@ -88,6 +96,9 @@ export default {
88
96
  lastInteractionType: null,
89
97
  activeTransitions: 0,
90
98
  scrollPreview: 0,
99
+
100
+ // true whenver the selected item is in an offset by dragging or a transition
101
+ hasOffset: false,
91
102
  };
92
103
  },
93
104
  computed: {
@@ -129,6 +140,13 @@ export default {
129
140
  },
130
141
  },
131
142
  watch: {
143
+ hasOffset(nv) {
144
+ if (nv) {
145
+ this.$emit('offset-start');
146
+ } else {
147
+ this.$emit('offset-end');
148
+ }
149
+ },
132
150
  syncSelected(nv, ov) {
133
151
  let newIndex = this.itemKeys.indexOf(nv);
134
152
  let oldIndex = this.itemKeys.indexOf(ov);
@@ -179,7 +197,7 @@ export default {
179
197
  }
180
198
 
181
199
  this.drag = new Drag(this.$el, {
182
- lock: 'x',
200
+ lock: this.orientation === 'horizontal' ? 'x' : 'y',
183
201
  onStart: this.onDragStart,
184
202
  onDrag: this.onDrag,
185
203
  onEnd: this.onDragEnd,
@@ -198,6 +216,7 @@ export default {
198
216
  transitionStart() {
199
217
  if (!this.activeTransitions) {
200
218
  this.$emit('transitions-start');
219
+ this.hasOffset = true;
201
220
  }
202
221
  this.activeTransitions++;
203
222
  },
@@ -208,6 +227,7 @@ export default {
208
227
  this.$emit('transitions-end', {
209
228
  toIndex: this.itemKeys.indexOf(this.syncSelected),
210
229
  });
230
+ this.hasOffset = false;
211
231
  }
212
232
  },
213
233
 
@@ -217,26 +237,51 @@ export default {
217
237
 
218
238
  if (slide) {
219
239
  this.dragElements = [...slide.querySelectorAll(this.dragSelector)];
240
+ this.dragElementsReverse = [...slide.querySelectorAll(this.dragReverseSelector)];
220
241
  }
242
+
243
+ this.hasOffset = true;
221
244
  },
222
245
 
223
246
  onDrag(ev) {
224
- if (this.dragElements?.length) {
247
+ if (this.dragElements?.length || this.dragElementsReverse?.length) {
225
248
  if (gsap.isTweening(this.dragElements[0])) {
226
249
  gsap.killTweensOf(this.dragElements);
227
250
  }
228
- gsap.set(this.dragElements, { x: ev.startDelta.x });
251
+
252
+ if (gsap.isTweening(this.dragElementsReverse[0])) {
253
+ gsap.killTweensOf(this.dragElementsReverse);
254
+ }
255
+
256
+ if (this.orientation === 'vertical') {
257
+ gsap.set(this.dragElements, { y: ev.startDelta.y });
258
+ gsap.set(this.dragElementsReverse, { y: -1 * ev.startDelta.y });
259
+ } else {
260
+ gsap.set(this.dragElements, { x: ev.startDelta.x });
261
+ }
262
+
263
+ this.hasOffset = true;
229
264
  }
230
265
  },
231
266
 
232
267
  onDragEnd(ev) {
233
- if (Math.abs(ev.delta.x) < 10) {
234
- gsap.to(this.dragElements, { x: 0 });
268
+ const delta = this.orientation === 'vertical' ? ev.delta.y : ev.delta.x;
269
+ if (Math.abs(delta) < 10) {
270
+ this.hasOffset = false;
271
+
272
+ // snap back
273
+ const to = {};
274
+ to[this.orientation === 'vertical' ? 'y' : 'x'] = 0;
275
+ gsap.to(this.dragElements, { ...to });
276
+
277
+ // snap back reverse elements (vertical only)
278
+ if (this.orientation === 'vertical') {
279
+ gsap.to(this.dragElementsReverse, { y: 0 });
280
+ }
235
281
  } else {
236
282
  this.lastInteractionType = 'drag';
237
283
  this.$emit('drag-complete');
238
-
239
- if (ev.delta.x > 0) {
284
+ if (delta > 0) {
240
285
  this.previous();
241
286
  } else {
242
287
  this.next();
@@ -7,7 +7,7 @@
7
7
  * classic - rotating semi-circle
8
8
  * dots - three dots that fade in and out in turn
9
9
 
10
- Size is derived from the current font size and color is derived from currentColor. These
10
+ Size is derived from the current font size and color is derived from var(--px-spinner-color). These
11
11
  properties (as well as a transform) can be set on the component itself without side effects.
12
12
 
13
13
  Future enhancements could include additional properties for controlling things like timing,
@@ -27,7 +27,10 @@ import PxBaseVisible from './PxBaseVisible.vue';
27
27
  export default {
28
28
  name: 'px-spinner',
29
29
  mixins: [PxBaseVariants, PxBaseVisible],
30
- props: { variant: { type: String, default: 'classic' } },
30
+ props: {
31
+ variant: { type: String, default: 'classic' },
32
+ duration: { type: Number, default: 2 },
33
+ },
31
34
  };
32
35
  </script>
33
36
 
@@ -39,6 +42,11 @@ export default {
39
42
  position: relative;
40
43
  pointer-events: none;
41
44
 
45
+ --px-spinner-dur: 2s;
46
+ --px-spinner-linewidth: 0.15em;
47
+ --px-spinner-color: currentColor;
48
+ --px-spinner-color-secondary: transparent;
49
+
42
50
  &__spinner {
43
51
  display: block;
44
52
  position: relative;
@@ -48,14 +56,15 @@ export default {
48
56
 
49
57
  &--classic & {
50
58
  &__spinner {
59
+ position: relative;
51
60
  width: 1.33em;
52
61
  height: 1.33em;
53
- border: 0.15em solid currentColor;
54
- border-bottom-color: transparent;
62
+ border: var(--px-spinner-linewidth) solid var(--px-spinner-color);
63
+ border-bottom-color: var(--px-spinner-color-secondary);
55
64
  border-radius: 50%;
56
65
  display: inline-block;
57
66
  box-sizing: border-box;
58
- animation: px-spinner-classic 1s linear infinite;
67
+ animation: px-spinner-classic var(--px-spinner-dur) linear infinite;
59
68
  }
60
69
  }
61
70
 
@@ -91,15 +100,15 @@ export default {
91
100
  height: 2.5em;
92
101
  border-radius: 50%;
93
102
  animation-fill-mode: both;
94
- animation: px-spinner-dots $dur infinite $ease-in-out-quart;
103
+ animation: px-spinner-dots var(--px-spinner-dur) infinite $ease-in-out-quart;
95
104
  }
96
105
  &__spinner {
97
106
  left: 5em;
98
- color: currentColor;
107
+ color: var(--px-spinner-color);
99
108
  position: relative;
100
109
  text-indent: -9999em;
101
110
  transform: translateZ(0);
102
- animation-delay: ($dur * -0.088888888);
111
+ animation-delay: calc((var(--px-spinner-dur) * -0.088888888));
103
112
  }
104
113
  &__spinner:before,
105
114
  &__spinner:after {
@@ -109,7 +118,7 @@ export default {
109
118
  }
110
119
  &__spinner:before {
111
120
  left: -5em;
112
- animation-delay: ($dur * -0.088888888 * 2);
121
+ animation-delay: calc((var(--px-spinner-dur) * -0.088888888 * 2));
113
122
  }
114
123
  &__spinner:after {
115
124
  left: 5em;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkpixellab-public/px-vue",
3
- "version": "3.0.88",
3
+ "version": "3.0.90",
4
4
  "description": "General purpose Vue components and helpers that can be used across projects.",
5
5
  "author": "Pixel Lab",
6
6
  "license": "MIT",
package/utils/utils.js CHANGED
@@ -354,6 +354,53 @@ export default {
354
354
  });
355
355
  },
356
356
 
357
+ /**
358
+ * Converts a floating-point number to a string value based on a map where the keys are string
359
+ * values and the corresponding values define ranges in the format 'start, end'. Open-ended
360
+ * ranges can be specified by using `null` for the start or end value. This approach avoids
361
+ * ambiguity with negative numbers by using commas to separate the range values. If the input
362
+ * value does not fall within any of the defined ranges, a fallback value is returned.
363
+ *
364
+ * @param {number} value - The floating-point number to be converted.
365
+ * @param {Object} valueMap - An object where keys are string values and values define ranges in
366
+ * the format 'start, end'. Use `null` to specify an open-ended start or end of a range.
367
+ * @param {string} fallbackValue - The value to return if the input does not match any defined
368
+ * range.
369
+ * @returns {string} - The string value corresponding to the range within which the input value
370
+ * falls, or the fallback value.
371
+ *
372
+ * @example
373
+ * const valueMap = {
374
+ * 'low': 'null, 0.5',
375
+ * 'medium': '0.5, 1',
376
+ * 'high': '1, null'
377
+ * };
378
+ * const fallback = 'unknown';
379
+ *
380
+ * console.log(floatToEnum(0.25, valueMap, fallback)); // Expected "low"
381
+ * console.log(floatToEnum(0.75, valueMap, fallback)); // Expected "medium"
382
+ * console.log(floatToEnum(1.5, valueMap, fallback)); // Expected "high"
383
+ * console.log(floatToEnum(-1, valueMap, fallback)); // Expected "unknown"
384
+ **/
385
+
386
+ floatToEnum(value, valueMap, fallbackValue) {
387
+ const rangeRegex = /^\s*(null|[+-]?\d*(?:\.\d+)?)\s*,\s*(null|[+-]?\d*(?:\.\d+)?)\s*$/;
388
+
389
+ for (const [stringValue, range] of Object.entries(valueMap)) {
390
+ const match = range.match(rangeRegex);
391
+ if (!match) continue; // Skip invalid ranges
392
+
393
+ const start = match[1] !== 'null' ? parseFloat(match[1]) : -Infinity;
394
+ const end = match[2] !== 'null' ? parseFloat(match[2]) : Infinity;
395
+
396
+ if ((start === -Infinity || value >= start) && (end === Infinity || value < end)) {
397
+ return stringValue;
398
+ }
399
+ }
400
+
401
+ return fallbackValue;
402
+ },
403
+
357
404
  /**
358
405
  * Generate a 6 letter random sequence of letters and numbers, based on this:
359
406
  * https://stackoverflow.com/questions/6248666/how-to-generate-short-uid-like-ax4j9z-in-js